{
  pkgs,
  lib,
  config,
  ...
}:
let
  inherit (lib)
    concatStringsSep
    isList
    mapAttrsToList
    types
    ;

  normalizeKeyValue =
    k: v:
    let
      v' =
        if builtins.isBool v then
          (if v then "true" else "false")
        else if builtins.isAttrs v then
          (concatStringsSep ''

            ${k}='' (mapAttrsToList normalizeKeyValue v))
        else
          toString v;
    in
    if isNull v then "" else "${k}=${v'}";

  primitiveAttrs = with types; attrsOf (either primitive (listOf primitive));
  primitiveList = with types; listOf primitive;
  primitive =
    with types;
    nullOr (oneOf [
      bool
      int
      str
      path
    ]);

  toQuadletIni = lib.generators.toINI {
    listsAsDuplicateKeys = true;
    mkKeyValue = normalizeKeyValue;
  };

  # meant for ini. favours b when two values are unmergeable
  deepMerge =
    a: b:
    lib.foldl' (
      result: key:
      let
        aVal = if builtins.hasAttr key a then a.${key} else null;
        bVal = if builtins.hasAttr key b then b.${key} else null;

        # check if the types inside a list match the type of a primitive
        listMatchesType =
          list: val:
          isList list
          && builtins.length list > 0
          && builtins.typeOf (builtins.head list) == builtins.typeOf val;
      in
      if lib.isAttrs aVal && lib.isAttrs bVal then
        result // { ${key} = deepMerge aVal bVal; }
      else if isList aVal && isList bVal then
        result // { ${key} = aVal ++ bVal; }
      else if aVal == bVal then
        result // { ${key} = aVal; }
      else if aVal == null then
        result // { ${key} = bVal; }
      else if bVal == null then
        result // { ${key} = aVal; }
      else if isList aVal && listMatchesType aVal bVal then
        result // { ${key} = aVal ++ [ bVal ]; }
      else if isList bVal && listMatchesType bVal aVal then
        result // { ${key} = [ aVal ] ++ bVal; }
      else if builtins.typeOf aVal == builtins.typeOf bVal then
        result // { ${key} = bVal; }
      else
        result // { ${key} = bVal; }
    ) a (builtins.attrNames b);
in
{
  inherit primitiveAttrs;
  inherit primitiveList;
  inherit primitive;
  inherit toQuadletIni;
  inherit deepMerge;

  buildConfigAsserts =
    quadletName: extraConfig:
    let
      configRules = {
        Build = {
          ImageTag = with types; listOf str;
        };
        Container = {
          ContainerName = types.enum [ quadletName ];
        };
        Network = {
          NetworkName = types.enum [ quadletName ];
        };
        Volume = {
          VolumeName = types.enum [ quadletName ];
        };
      };

      # Function to build assertions for a specific section and its attributes.
      buildSectionAsserts =
        section: attrs:
        if builtins.hasAttr section configRules then
          lib.flatten (
            mapAttrsToList (
              attrName: attrValue:
              if builtins.hasAttr attrName configRules.${section} then
                [
                  {
                    assertion = configRules.${section}.${attrName}.check attrValue;
                    message = "In '${quadletName}' config. ${section}.${attrName}: '${toString attrValue}' does not match expected type: ${
                      configRules.${section}.${attrName}.description
                    }";
                  }
                ]
              else
                [ ]
            ) attrs
          )
        else
          [ ];

      checkImageTag =
        extraConfig:
        let
          imageTags = (extraConfig.Build or { }).ImageTag or [ ];
          containsRequiredTag = builtins.elem "homemanager/${quadletName}" imageTags;
          imageTagsStr = lib.concatMapStringsSep ''" "'' toString imageTags;
        in
        [
          {
            assertion = imageTags == [ ] || containsRequiredTag;
            message = ''In '${quadletName}' config. Build.ImageTag: '[ "${imageTagsStr}" ]' does not contain 'homemanager/${quadletName}'.'';
          }
        ];
      # Flatten assertions from all sections in `extraConfig`.
    in
    lib.flatten (
      lib.concatLists [
        (mapAttrsToList buildSectionAsserts extraConfig)
        (checkImageTag extraConfig)
      ]
    );

  extraConfigType =
    with types;
    attrsOf (
      attrsOf (oneOf [
        primitiveAttrs
        primitiveList
        primitive
      ])
    );

  # input expects a list of quadletInternalType with all the same resourceType
  generateManifestText =
    quadlets:
    let
      # create a list of all unique quadlet.resourceType in quadlets
      quadletTypes = lib.unique (map (quadlet: quadlet.resourceType) quadlets);
      # if quadletTypes is > 1, then all quadlets are not the same type
      allQuadletsSameType = lib.length quadletTypes <= 1;

      # ensures the service name is formatted correctly to be easily read
      #   by the activation script and matches `podman <resource> ls` output
      formatServiceName =
        quadlet:
        let
          # remove the podman- prefix from the service name string
          strippedName = lib.removePrefix "podman-" quadlet.serviceName;
          # specific logic for writing the unit name goes here. It should be
          #   identical to what `podman <resource> ls` shows
        in
        {
          "build" = "localhost/homemanager/${strippedName}";
          "container" = strippedName;
          "network" = strippedName;
          "volume" = strippedName;
        }
        ."${quadlet.resourceType}";
    in
    if allQuadletsSameType then
      ''
        ${concatStringsSep "\n" (map formatServiceName quadlets)}
      ''
    else
      abort ''
        All quadlets must be of the same type.
          Quadlet types in this manifest: ${concatStringsSep ", " quadletTypes}
      '';

  # podman requires setuid on newuidmad, so it cannot be provided by pkgs.shadow
  # Including all possible locations in PATH for newuidmap is a workaround.
  # NixOS provides a 'wrapped' variant at /run/wrappers/bin/newuidmap.
  # Other distros must install the 'uidmap' package, ie for ubuntu: apt install uidmap.
  # Extra paths are added to handle where distro package managers may put the uidmap binaries.
  #
  # Tracking for a potential solution: https://github.com/NixOS/nixpkgs/issues/138423
  newuidmapPaths = "/run/wrappers/bin:/usr/bin:/bin:/usr/sbin:/sbin";

  removeBlankLines =
    text:
    let
      lines = lib.splitString "\n" text;
      nonEmptyLines = lib.filter (line: line != "") lines;
    in
    concatStringsSep "\n" nonEmptyLines;

  awaitPodmanUnshare = pkgs.writeShellScript "await-podman-unshare" ''
    until ${config.services.podman.package}/bin/podman unshare ${pkgs.coreutils}/bin/true; do
      ${pkgs.coreutils}/bin/sleep 1
    done
  '';
}
