# alot config loader is sensitive to leading space !
{
  config,
  lib,
  pkgs,
  ...
}:
let
  inherit (lib)
    concatStringsSep
    mapAttrsToList
    mkOption
    optionalAttrs
    optionalString
    types
    ;

  cfg = config.programs.alot;

  enabledAccounts = lib.filter (a: a.enable && a.notmuch.enable) (
    lib.attrValues config.accounts.email.accounts
  );

  # sorted: primary first
  alotAccounts = lib.sort (a: b: !(a.primary -> b.primary)) enabledAccounts;

  boolStr = v: if v then "True" else "False";

  mkKeyValue =
    key: value:
    let
      value' = if lib.isBool value then boolStr value else toString value;
    in
    "${key} = ${value'}";

  mk2ndLevelSectionName = name: "[" + name + "]";

  tagSubmodule = types.submodule {
    options = {
      translated = mkOption {
        type = types.nullOr types.str;
        description = ''
          Fixed string representation for this tag. The tag can be
          hidden from view, if the key translated is set to
          `""`, the empty string.
        '';
      };

      translation = mkOption {
        type = types.nullOr types.str;
        default = null;
        description = ''
          A pair of strings that define a regular substitution to
          compute the string representation on the fly using
          `re.sub`.
        '';
      };

      normal = mkOption {
        type = types.nullOr types.str;
        default = null;
        example = "'','', 'white','light red', 'white','#d66'";
        description = ''
          How to display the tag when unfocused.
          See <https://alot.readthedocs.io/en/latest/configuration/theming.html#tagstring-formatting>.
        '';
      };

      focus = mkOption {
        type = types.nullOr types.str;
        default = null;
        description = "How to display the tag when focused.";
      };
    };
  };

  accountStr =
    account:
    let
      inherit (account)
        alot
        maildir
        name
        address
        realName
        folders
        aliases
        gpg
        signature
        ;
    in
    concatStringsSep "\n" (
      [ "[[${name}]]" ]
      ++ mapAttrsToList (n: v: n + "=" + v) (
        {
          inherit address;
          realname = realName;
          sendmail_command = optionalString (alot.sendMailCommand != null) alot.sendMailCommand;
        }
        // optionalAttrs (folders.sent != null) {
          sent_box = "'maildir" + "://" + maildir.absPath + "/" + folders.sent + "'";
        }
        // optionalAttrs (folders.drafts != null) {
          draft_box = "'maildir" + "://" + maildir.absPath + "/" + folders.drafts + "'";
        }
        // optionalAttrs (aliases != [ ]) {
          aliases = concatStringsSep "," aliases;
        }
        // optionalAttrs (gpg != null) {
          gpg_key = gpg.key;
          encrypt_by_default = if gpg.encryptByDefault then "all" else "none";
          sign_by_default = boolStr gpg.signByDefault;
        }
        // optionalAttrs (signature.showSignature != "none") {
          signature = pkgs.writeText "signature.txt" signature.text;
          signature_as_attachment = boolStr (signature.showSignature == "attach");
        }
      )
      ++ [ alot.extraConfig ]
      ++ [ "[[[abook]]]" ]
      ++ mapAttrsToList (n: v: n + "=" + v) alot.contactCompletion
    );

  configFile =
    let
      bindingsToStr = attrSet: concatStringsSep "\n" (mapAttrsToList (n: v: "${n} = ${v}") attrSet);
    in
    ''
      # Generated by Home Manager.
      # See http://alot.readthedocs.io/en/latest/configuration/config_options.html

      ${lib.generators.toKeyValue { inherit mkKeyValue; } cfg.settings}
      ${cfg.extraConfig}
      [tags]
    ''
    + (
      let
        submoduleToAttrs = m: lib.filterAttrs (name: v: name != "_module" && v != null) m;
      in
      lib.generators.toINI { mkSectionName = mk2ndLevelSectionName; } (
        lib.mapAttrs (_name: submoduleToAttrs) cfg.tags
      )
    )
    + ''
      [bindings]
      ${bindingsToStr cfg.bindings.global}

      [[bufferlist]]
      ${bindingsToStr cfg.bindings.bufferlist}
      [[search]]
      ${bindingsToStr cfg.bindings.search}
      [[envelope]]
      ${bindingsToStr cfg.bindings.envelope}
      [[taglist]]
      ${bindingsToStr cfg.bindings.taglist}
      [[thread]]
      ${bindingsToStr cfg.bindings.thread}

      [accounts]

      ${lib.concatStringsSep "\n\n" (map accountStr alotAccounts)}
    '';

in
{
  options = {
    programs.alot = {
      enable = mkOption {
        type = types.bool;
        default = false;
        example = true;
        description = ''
          Whether to enable the Alot mail user agent. Alot uses the
          Notmuch email system and will therefore be automatically
          enabled for each email account that is managed by Notmuch.
        '';
      };

      package = lib.mkPackageOption pkgs "alot" { nullable = true; };

      hooks = mkOption {
        type = types.lines;
        default = "";
        description = ''
          Content of the hooks file.
        '';
      };

      bindings = mkOption {
        type = types.submodule {
          options = {
            global = mkOption {
              type = types.attrsOf types.str;
              default = { };
              description = "Global keybindings.";
            };

            bufferlist = mkOption {
              type = types.attrsOf types.str;
              default = { };
              description = "Bufferlist mode keybindings.";
            };

            search = mkOption {
              type = types.attrsOf types.str;
              default = { };
              description = "Search mode keybindings.";
            };

            envelope = mkOption {
              type = types.attrsOf types.str;
              default = { };
              description = "Envelope mode keybindings.";
            };

            taglist = mkOption {
              type = types.attrsOf types.str;
              default = { };
              description = "Taglist mode keybindings.";
            };

            thread = mkOption {
              type = types.attrsOf types.str;
              default = { };
              description = "Thread mode keybindings.";
            };
          };
        };
        default = { };
        description = ''
          Keybindings.
        '';
      };

      tags = mkOption {
        type = types.attrsOf tagSubmodule;
        default = { };
        description = "How to display the tags.";
      };

      settings = mkOption {
        type =
          with types;
          let
            primitive = either (either (either str int) bool) float;
          in
          attrsOf primitive;
        default = {
          initial_command = "search tag:inbox AND NOT tag:killed";
          auto_remove_unread = true;
          handle_mouse = true;
          prefer_plaintext = true;
        };
        example = {
          auto_remove_unread = true;
          ask_subject = false;
          thread_indent_replies = 2;
        };
        description = ''
          Configuration options added to alot configuration file.
        '';
      };

      extraConfig = mkOption {
        type = types.lines;
        default = "";
        description = ''
          Extra lines added to alot configuration file.
        '';
      };
    };

    accounts.email.accounts = mkOption {
      type = with types; attrsOf (submodule (import ./accounts.nix pkgs));
    };
  };

  config = lib.mkIf cfg.enable {
    home.packages = lib.mkIf (cfg.package != null) [ cfg.package ];

    xdg.configFile."alot/config".text = configFile;

    xdg.configFile."alot/hooks.py" = lib.mkIf (cfg.hooks != "") {
      text = ''
        # Generated by Home Manager.
      ''
      + cfg.hooks;
    };
  };
}
