{ pkgs, lib, ... }:

# Based on
# - https://web.mit.edu/kerberos/krb5-1.12/doc/admin/conf_files/krb5_conf.html
# - https://manpages.debian.org/unstable/heimdal-docs/krb5.conf.5heimdal.en.html

let
  inherit (lib) boolToString concatMapStringsSep concatStringsSep filter
    isAttrs isBool isList mapAttrsToList mkOption singleton splitString;
  inherit (lib.types) attrsOf bool coercedTo either enum int listOf oneOf
    path str submodule;
in
{
  enableKdcACLEntries ? false
}: rec {
  sectionType = let
    relation = oneOf [
      (listOf (attrsOf value))
      (attrsOf value)
      value
    ];
    value = either (listOf atom) atom;
    atom = oneOf [int str bool];
  in attrsOf relation;

  type = let
    aclEntry = submodule {
      options = {
        principal = mkOption {
          type = str;
          description = "Which principal the rule applies to";
        };
        access = mkOption {
          type = either
            (listOf (enum ["add" "cpw" "delete" "get" "list" "modify"]))
            (enum ["all"]);
          default = "all";
          description = "The changes the principal is allowed to make.";
        };
        target = mkOption {
          type = str;
          default = "*";
          description = "The principals that 'access' applies to.";
        };
      };
    };

    realm = submodule ({ name, ... }: {
      freeformType = sectionType;
      options = {
        acl = mkOption {
          type = listOf aclEntry;
          default = [
            { principal = "*/admin"; access = "all"; }
            { principal = "admin"; access = "all"; }
          ];
          description = ''
            The privileges granted to a user.
          '';
        };
      };
    });
  in submodule {
    freeformType = attrsOf sectionType;
    options = {
      include = mkOption {
        default = [ ];
        description = ''
          Files to include in the Kerberos configuration.
        '';
        type = coercedTo path singleton (listOf path);
      };
      includedir = mkOption {
        default = [ ];
        description = ''
          Directories containing files to include in the Kerberos configuration.
        '';
        type = coercedTo path singleton (listOf path);
      };
      module = mkOption {
        default = [ ];
        description = ''
          Modules to obtain Kerberos configuration from.
        '';
        type = coercedTo path singleton (listOf path);
      };

    }
    //
    (lib.optionalAttrs enableKdcACLEntries {
      realms = mkOption {
        type = attrsOf realm;
        description = ''
          The realm(s) to serve keys for.
        '';
      };
    });
  };

  generate = let
    indent = str: concatMapStringsSep "\n" (line: "  " + line) (splitString "\n" str);

    formatToplevel = args @ {
      include ? [ ],
      includedir ? [ ],
      module ? [ ],
      ...
    }: let
      sections = removeAttrs args [ "include" "includedir" "module" ];
    in concatStringsSep "\n" (filter (x: x != "") [
      (concatStringsSep "\n" (mapAttrsToList formatSection sections))
      (concatMapStringsSep "\n" (m: "module ${m}") module)
      (concatMapStringsSep "\n" (i: "include ${i}") include)
      (concatMapStringsSep "\n" (i: "includedir ${i}") includedir)
    ]);

    formatSection = name: section: ''
      [${name}]
      ${indent (concatStringsSep "\n" (mapAttrsToList formatRelation section))}
    '';

    formatRelation = name: relation:
      if isAttrs relation
      then ''
        ${name} = {
        ${indent (concatStringsSep "\n" (mapAttrsToList formatValue relation))}
        }''
      else if isList relation
      then
        concatMapStringsSep "\n" (formatRelation name) relation
      else formatValue name relation;

    formatValue = name: value:
      if isList value
      then concatMapStringsSep "\n" (formatAtom name) value
      else formatAtom name value;

    formatAtom = name: atom: let
      v = if isBool atom then boolToString atom else toString atom;
    in "${name} = ${v}";
  in
    name: value: pkgs.writeText name ''
      ${formatToplevel value}
    '';
}
