{ config, lib, pkgs, ... }:

with lib;

let
  cfg = config.services.podman;

  podman-lib = import ./podman-lib.nix { inherit lib config; };

  createQuadletSource = name: containerDef:
    let
      mapHmNetworks = network:
        if builtins.hasAttr network cfg.networks then
          "podman-${network}-network.service"
        else
          null;

      finalConfig = let
        managedNetworks = if lib.isList containerDef.network then
          map mapHmNetworks containerDef.network
        else if containerDef.network != null then
          map mapHmNetworks [ containerDef.network ]
        else
          [ ];
      in (podman-lib.deepMerge {
        Container = {
          AddCapability = containerDef.addCapabilities;
          AddDevice = containerDef.devices;
          AutoUpdate = containerDef.autoUpdate;
          ContainerName = name;
          DropCapability = containerDef.dropCapabilities;
          Entrypoint = containerDef.entrypoint;
          Environment = containerDef.environment;
          EnvironmentFile = containerDef.environmentFile;
          Exec = containerDef.exec;
          Group = containerDef.group;
          Image = containerDef.image;
          IP = containerDef.ip4;
          IP6 = containerDef.ip6;
          Label =
            (containerDef.labels // { "nix.home-manager.managed" = true; });
          Network = containerDef.network;
          NetworkAlias = containerDef.networkAlias;
          PodmanArgs = containerDef.extraPodmanArgs;
          PublishPort = containerDef.ports;
          UserNS = containerDef.userNS;
          User = containerDef.user;
          Volume = containerDef.volumes;
        };
        Install = {
          WantedBy = (if containerDef.autoStart then [
            "default.target"
            "multi-user.target"
          ] else
            [ ]);
        };
        Service = {
          Environment = {
            PATH = (builtins.concatStringsSep ":" [
              "/run/wrappers/bin"
              "/run/current-system/sw/bin"
              "${config.home.homeDirectory}/.nix-profile/bin"
            ]);
          };
          Restart = "always";
          TimeoutStopSec = 30;
        };
        Unit = {
          After = [ "network.target" ] ++ managedNetworks;
          Requires = managedNetworks;
          Description = (if (builtins.isString containerDef.description) then
            containerDef.description
          else
            "Service for container ${name}");
        };
      } containerDef.extraConfig);
    in ''
      # Automatically generated by home-manager podman container configuration
      # DO NOT EDIT THIS FILE DIRECTLY
      #
      # ${name}.container
      ${podman-lib.toQuadletIni finalConfig}
    '';

  toQuadletInternal = name: containerDef: {
    assertions = podman-lib.buildConfigAsserts name containerDef.extraConfig;
    resourceType = "container";
    serviceName =
      "podman-${name}"; # quadlet service name: 'podman-<name>.service'
    source =
      podman-lib.removeBlankLines (createQuadletSource name containerDef);
  };

  # Define the container user type as the user interface
  containerDefinitionType = types.submodule {
    options = {

      addCapabilities = mkOption {
        type = with types; listOf str;
        default = [ ];
        example = [ "CAP_DAC_OVERRIDE" "CAP_IPC_OWNER" ];
        description = "The capabilities to add to the container.";
      };

      autoStart = mkOption {
        type = types.bool;
        default = true;
        description = ''
          Whether to start the container on boot (requires user lingering).
        '';
      };

      autoUpdate = mkOption {
        type = types.enum [ null "registry" "local" ];
        default = null;
        example = "registry";
        description = "The autoupdate policy for the container.";
      };

      description = mkOption {
        type = with types; nullOr str;
        default = null;
        example = "My Container";
        description = "The description of the container.";
      };

      devices = mkOption {
        type = with types; listOf str;
        default = [ ];
        example = [ "/dev/<host>:/dev/<container>" ];
        description = "The devices to mount into the container";
      };

      dropCapabilities = mkOption {
        type = with types; listOf str;
        default = [ ];
        example = [ "CAP_DAC_OVERRIDE" "CAP_IPC_OWNER" ];
        description = "The capabilities to drop from the container.";
      };

      entrypoint = mkOption {
        type = with types; nullOr str;
        default = null;
        example = "/foo.sh";
        description = "The container entrypoint.";
      };

      environment = mkOption {
        type = podman-lib.primitiveAttrs;
        default = { };
        example = literalExpression ''
          {
            VAR1 = "0:100";
            VAR2 = true;
            VAR3 = 5;
          }
        '';
        description = "Environment variables to set in the container.";
      };

      environmentFile = mkOption {
        type = with types; listOf str;
        default = [ ];
        example = [ "/etc/environment" "/etc/other-env" ];
        description = ''
          Paths to files containing container environment variables.
        '';
      };

      exec = mkOption {
        type = with types; nullOr str;
        default = null;
        example = "sleep inf";
        description = "The command to run after the container start.";
      };

      extraPodmanArgs = mkOption {
        type = types.listOf types.str;
        default = [ ];
        example = [
          "--security-opt=no-new-privileges"
          "--security-opt=seccomp=unconfined"
        ];
        description = "Extra arguments to pass to the podman run command.";
      };

      extraConfig = mkOption {
        type = podman-lib.extraConfigType;
        default = { };
        example = literalExpression ''
          {
            Container = {
              User = 1000;
            };
            Service = {
              TimeoutStartSec = 15;
            };
          }
        '';
        description = ''
          INI sections and values to populate the Container Quadlet.
        '';
      };

      group = mkOption {
        type = with types; nullOr (either int str);
        default = null;
        description = "The group ID inside the container.";
      };

      image = mkOption {
        type = types.str;
        example = "registry.access.redhat.com/ubi9-minimal:latest";
        description = "The container image.";
      };

      ip4 = mkOption {
        type = with types; nullOr str;
        default = null;
        description = "Set an IPv4 address for the container.";
      };

      ip6 = mkOption {
        type = with types; nullOr str;
        default = null;
        description = "Set an IPv6 address for the container.";
      };

      labels = mkOption {
        type = with types; attrsOf str;
        default = { };
        example = {
          app = "myapp";
          some-label = "somelabel";
        };
        description = "The labels to apply to the container.";
      };

      network = mkOption {
        type = with types; either str (listOf str);
        default = [ ];
        apply = value: if isString value then [ value ] else value;
        example = literalMD ''
          `"host"`
          or
          `"bridge_network_1"`
          or
          `[ "bridge_network_1" "bridge_network_2" ]`
        '';
        description = ''
          The network mode or network/s to connect the container to. Equivalent
          to `podman run --network=<option>`.
        '';
      };

      networkAlias = mkOption {
        type = with types; listOf str;
        default = [ ];
        example = [ "mycontainer" "web" ];
        description = "Network aliases for the container.";
      };

      ports = mkOption {
        type = with types; listOf str;
        default = [ ];
        example = [ "8080:80" "8443:443" ];
        description = "A mapping of ports between host and container";
      };

      userNS = mkOption {
        type = with types; nullOr str;
        default = null;
        description = "Use a user namespace for the container.";
      };

      user = mkOption {
        type = with types; nullOr (either int str);
        default = null;
        description = "The user ID inside the container.";
      };

      volumes = mkOption {
        type = with types; listOf str;
        default = [ ];
        example = [ "/tmp:/tmp" "/var/run/test.secret:/etc/secret:ro" ];
        description = "The volumes to mount into the container.";
      };

    };
  };

in {

  imports = [ ./options.nix ];

  options.services.podman.containers = mkOption {
    type = types.attrsOf containerDefinitionType;
    default = { };
    description = "Defines Podman container quadlet configurations.";
  };

  config =
    let containerQuadlets = mapAttrsToList toQuadletInternal cfg.containers;
    in mkIf cfg.enable {
      services.podman.internal.quadletDefinitions = containerQuadlets;
      assertions =
        flatten (map (container: container.assertions) containerQuadlets);

      # manifest file
      home.file."${config.xdg.configHome}/podman/containers.manifest".text =
        podman-lib.generateManifestText containerQuadlets;
    };
}