{ config, lib, confSections, confSection, ... }:

with lib;

let
  mapAttrNames = f: attr:
    listToAttrs (attrValues (mapAttrs (k: v: {
      name = f k;
      value = v;
    }) attr));

  addAccountName = name: k: "${k}:account=${name}";

  oauth2Params = mkOption {
    type = with types;
      nullOr (submodule {
        options = {
          token_endpoint = mkOption {
            type = nullOr str;
            default = null;
            description = "The OAuth2 token endpoint.";
          };
          client_id = mkOption {
            type = nullOr str;
            default = null;
            description = "The OAuth2 client identifier.";
          };
          client_secret = mkOption {
            type = nullOr str;
            default = null;
            description = "The OAuth2 client secret.";
          };
          scope = mkOption {
            type = nullOr str;
            default = null;
            description = "The OAuth2 requested scope.";
          };
        };
      });
    default = null;
    example = { token_endpoint = "<token_endpoint>"; };
    description = ''
      Sets the oauth2 params if authentication mechanism oauthbearer or
      xoauth2 is used.
      See {manpage}`aerc-imap(5)`.
    '';
  };

in {
  type = mkOption {
    type = types.attrsOf (types.submodule {
      options.aerc = {
        enable = mkEnableOption "aerc";
        extraAccounts = mkOption {
          type = confSection;
          default = { };
          example =
            literalExpression ''{ source = "maildir://~/Maildir/example"; }'';
          description = ''
            Extra config added to the configuration section for this account in
            {file}`$HOME/.config/aerc/accounts.conf`.
            See {manpage}`aerc-accounts(5)`.
          '';
        };

        extraBinds = mkOption {
          type = confSections;
          default = { };
          example = literalExpression
            ''{ messages = { d = ":move ''${folder.trash}<Enter>"; }; }'';
          description = ''
            Extra bindings specific to this account, added to
            {file}`$HOME/.config/aerc/binds.conf`.
            See {manpage}`aerc-binds(5)`.
          '';
        };

        extraConfig = mkOption {
          type = confSections;
          default = { };
          example = literalExpression "{ ui = { sidebar-width = 25; }; }";
          description = ''
            Config specific to this account, added to {file}`$HOME/.config/aerc/aerc.conf`.
            Aerc only supports per-account UI configuration.
            For other sections of {file}`$HOME/.config/aerc/aerc.conf`,
            use `programs.aerc.extraConfig`.
            See {manpage}`aerc-config(5)`.
          '';
        };

        imapAuth = mkOption {
          type = with types; nullOr (enum [ "oauthbearer" "xoauth2" ]);
          default = null;
          example = "auth";
          description = ''
            Sets the authentication mechanism if imap is used as the incoming
            method.
            See {manpage}`aerc-imap(5)`.
          '';
        };

        imapOauth2Params = oauth2Params;

        smtpAuth = mkOption {
          type = with types;
            nullOr (enum [ "none" "plain" "login" "oauthbearer" "xoauth2" ]);
          default = "plain";
          example = "auth";
          description = ''
            Sets the authentication mechanism if smtp is used as the outgoing
            method.
            See {manpage}`aerc-smtp(5)`.
          '';
        };

        smtpOauth2Params = oauth2Params;
      };
    });
  };

  mkAccount = name: account:
    let
      nullOrMap = f: v: if v == null then v else f v;

      optPort = port: if port != null then ":${toString port}" else "";

      optAttr = k: v:
        if v != null && v != [ ] && v != "" then { ${k} = v; } else { };

      optPwCmd = k: p:
        optAttr "${k}-cred-cmd" (nullOrMap (concatStringsSep " ") p);

      useOauth = auth: builtins.elem auth [ "oauthbearer" "xoauth2" ];

      oauthParams = { auth, params }:
        if useOauth auth && params != null && params != { } then
          "?" + builtins.concatStringsSep "&" lib.attrsets.mapAttrsToList
          (k: v: k + "=" + lib.strings.escapeURL v) params
        else
          "";

      mkConfig = {
        maildir = cfg: {
          source =
            "maildir://${config.accounts.email.maildirBasePath}/${cfg.maildir.path}";
        };

        maildirpp = cfg: {
          source =
            "maildirpp://${config.accounts.email.maildirBasePath}/${cfg.maildir.path}/Inbox";
        };

        imap = { userName, imap, passwordCommand, aerc, ... }@cfg:
          let
            loginMethod' =
              if cfg.aerc.imapAuth != null then "+${cfg.aerc.imapAuth}" else "";

            oauthParams' = oauthParams {
              auth = cfg.aerc.imapAuth;
              params = cfg.aerc.imapOauth2Params;
            };

            protocol = if imap.tls.enable then
              if imap.tls.useStartTls then "imap" else "imaps${loginMethod'}"
            else
              "imap+insecure";

            port' = optPort imap.port;

          in {
            source =
              "${protocol}://${userName}@${imap.host}${port'}${oauthParams'}";
          } // optPwCmd "source" passwordCommand;

        smtp = { userName, smtp, passwordCommand, ... }@cfg:
          let
            loginMethod' =
              if cfg.aerc.smtpAuth != null then "+${cfg.aerc.smtpAuth}" else "";

            oauthParams' = oauthParams {
              auth = cfg.aerc.smtpAuth;
              params = cfg.aerc.smtpOauth2Params;
            };

            protocol = if smtp.tls.enable then
              if smtp.tls.useStartTls then
                "smtp${loginMethod'}"
              else
                "smtps${loginMethod'}"
            else
              "smtp+insecure${loginMethod'}";

            port' = optPort smtp.port;

          in {
            outgoing =
              "${protocol}://${userName}@${smtp.host}${port'}${oauthParams'}";
          } // optPwCmd "outgoing" passwordCommand;

        msmtp = cfg: {
          outgoing = "msmtpq --read-envelope-from --read-recipients";
        };

      };

      basicCfg = account:
        {
          from = "${account.realName} <${account.address}>";
        } // (optAttr "copy-to" account.folders.sent)
        // (optAttr "default" account.folders.inbox)
        // (optAttr "postpone" account.folders.drafts)
        // (optAttr "aliases" account.aliases);

      sourceCfg = account:
        if account.mbsync.enable && account.mbsync.flatten == null
        && account.mbsync.subFolders == "Maildir++" then
          mkConfig.maildirpp account
        else if account.mbsync.enable || account.offlineimap.enable then
          mkConfig.maildir account
        else if account.imap != null then
          mkConfig.imap account
        else
          { };

      outgoingCfg = account:
        if account.msmtp.enable then
          mkConfig.msmtp account
        else if account.smtp != null then
          mkConfig.smtp account
        else
          { };

    in (basicCfg account) // (sourceCfg account) // (outgoingCfg account)
    // account.aerc.extraAccounts;

  mkAccountConfig = name: account:
    mapAttrNames (addAccountName name) account.aerc.extraConfig;

  mkAccountBinds = name: account:
    mapAttrNames (addAccountName name) account.aerc.extraBinds;
}