{ config, lib, pkgs, modules, profiles, sources, nixosSystem, ... }:

let
  mkIPv4 = index: local:
    "192.168.${if local then "100" else "101"}.${toString index}";
  mkIPv6 = index: local:
    "fd00::${if local then "100" else "101"}:${toString index}";

  evalConfig = nixosConfig: (nixosSystem {
    inherit (config.nixpkgs) system;
    modules = [
      nixosConfig
      modules.nopersist
      profiles.container
      { nixpkgs.pkgs = lib.mkForce pkgs; }
    ];
    specialArgs = {
      inherit modules sources;
    };
  }).config.system.build.toplevel;

in {
  options.hacc.containers = with lib.options;
    mkOption {
      description = ''
        hacc-specific containers. These are a thin wrapper around "normal" nixos containers:
          - they automatically get an IPv4/IPv6 address assigned
            (note that these are not guaranteed to be stable across config changes,
            so please use {option}`containers.<name>.hostAddress` & friends to
            reference them elsewhere)
          - they set a couple default options (e.g. ephemeral, autoStart, privateNetwork)
          - they are evaluated with our own version of {nix}`evalConfig`, which includes a
            couple more modules by default, use our version of `nixpkgs`, and includes the
            {nix}`profiles.containers` profile setting sane defaults for containers.
      '';
      default = { };
      type = with lib.types;
        types.attrsOf (types.submodule {
          options = {
            bindToPersist = mkOption {
              default = true;
              type = types.bool;
              description =
                "Wether to mount /persist/containers/<name> at /persist into this container.";
            };

            bindSecrets = mkOption {
              default = false;
              type = types.bool;
              description =
                "Whether to mount /run/secrets/<name> at /secrets into this container.";
            };

            config = mkOption {
              type = types.unspecified;
              description =
                "The container's config, to be evaluated with our own {nix}`evalConfig`.";
            };
          };
        });
    };

  # wrapped into imap1, which enumerates the containers; IP addresses are then
  # simply assigned based on the order the containers are in the list.
  config.containers = lib.mkMerge (lib.imap1
    (index: { name, value }: let container = value; in {
      ${name} = {
        hostAddress = mkIPv4 index false;
        localAddress = mkIPv4 index true;
        hostAddress6 = mkIPv6 index false;
        localAddress6 = mkIPv6 index true;

        privateNetwork = true;
        autoStart = true;
        ephemeral = true;

        bindMounts = lib.mkMerge [
          (lib.mkIf container.bindToPersist {
            "/persist" = {
              hostPath = "/persist/containers/${name}";
              isReadOnly = false;
            };
          })
          (lib.mkIf container.bindSecrets {
            "/secrets" = {
              hostPath = "/run/secrets/${name}";
              isReadOnly = true;
            };
          })
        ];

        path = evalConfig container.config;
      };
    }) (lib.attrsToList config.hacc.containers));
}