{ config, lib, pkgs, ... }:

with lib;

let

  cfg = config.accounts.email;

  gpgModule = types.submodule {
    options = {
      key = mkOption {
        type = types.str;
        description = ''
          The key to use as listed in <command>gpg --list-keys</command>.
        '';
      };

      signByDefault = mkOption {
        type = types.bool;
        default = false;
        description = "Sign messages by default.";
      };

      encryptByDefault = mkOption {
        type = types.bool;
        default = false;
        description = "Encrypt outgoing messages by default.";
      };
    };
  };

  signatureModule = types.submodule {
    options = {
      text = mkOption {
        type = types.str;
        default = "";
        example = ''
          --
          Luke Skywalker
          May the force be with you.
        '';
        description = ''
          Signature content.
        '';
      };

      delimiter = mkOption {
        type = types.str;
        default = ''
          --
        '';
        example = literalExpression ''
          ~*~*~*~*~*~*~*~*~*~*~*~
        '';
        description = ''
          The delimiter used between the document and the signature.
        '';
      };

      command = mkOption {
        type = with types; nullOr path;
        default = null;
        example = literalExpression ''
          pkgs.writeScript "signature" "echo This is my signature"
        '';
        description = "A command that generates a signature.";
      };

      showSignature = mkOption {
        type = types.enum [ "append" "attach" "none" ];
        default = "none";
        description = "Method to communicate the signature.";
      };
    };
  };

  tlsModule = types.submodule {
    options = {
      enable = mkOption {
        type = types.bool;
        default = true;
        description = ''
          Whether to enable TLS/SSL.
        '';
      };

      useStartTls = mkOption {
        type = types.bool;
        default = false;
        description = ''
          Whether to use STARTTLS.
        '';
      };

      certificatesFile = mkOption {
        type = types.nullOr types.path;
        default = config.accounts.email.certificatesFile;
        defaultText = "config.accounts.email.certificatesFile";
        description = ''
          Path to file containing certificate authorities that should
          be used to validate the connection authenticity. If
          <literal>null</literal> then the system default is used.
          Note, if set then the system default may still be accepted.
        '';
      };
    };
  };

  imapModule = types.submodule {
    options = {
      host = mkOption {
        type = types.str;
        example = "imap.example.org";
        description = ''
          Hostname of IMAP server.
        '';
      };

      port = mkOption {
        type = types.nullOr types.port;
        default = null;
        example = 993;
        description = ''
          The port on which the IMAP server listens. If
          <literal>null</literal> then the default port is used.
        '';
      };

      tls = mkOption {
        type = tlsModule;
        default = { };
        description = ''
          Configuration for secure connections.
        '';
      };
    };
  };

  jmapModule = types.submodule {
    options = {
      host = mkOption {
        type = types.nullOr types.str;
        default = null;
        example = "jmap.example.org";
        description = ''
          Hostname of JMAP server.
          </para><para>
          If both this option and <xref
          linkend="opt-accounts.email.accounts._name_.jmap.sessionUrl"/> are specified,
          <code>host</code> is preferred by applications when establishing a
          session.
        '';
      };

      sessionUrl = mkOption {
        type = types.nullOr types.str;
        default = null;
        example = "https://jmap.example.org:443/.well-known/jmap";
        description = ''
          URL for the JMAP Session resource.
          </para><para>
          If both this option and <xref
          linkend="opt-accounts.email.accounts._name_.jmap.host"/> are specified,
          <code>host</code> is preferred by applications when establishing a
          session.
        '';
      };
    };
  };

  smtpModule = types.submodule {
    options = {
      host = mkOption {
        type = types.str;
        example = "smtp.example.org";
        description = ''
          Hostname of SMTP server.
        '';
      };

      port = mkOption {
        type = types.nullOr types.port;
        default = null;
        example = 465;
        description = ''
          The port on which the SMTP server listens. If
          <literal>null</literal> then the default port is used.
        '';
      };

      tls = mkOption {
        type = tlsModule;
        default = { };
        description = ''
          Configuration for secure connections.
        '';
      };
    };
  };

  maildirModule = types.submodule ({ config, ... }: {
    options = {
      path = mkOption {
        type = types.str;
        description = ''
          Path to maildir directory where mail for this account is
          stored. This is relative to the base maildir path.
        '';
      };

      absPath = mkOption {
        type = types.path;
        readOnly = true;
        internal = true;
        default = "${cfg.maildirBasePath}/${config.path}";
        description = ''
          A convenience option whose value is the absolute path of
          this maildir.
        '';
      };
    };
  });

  mailAccountOpts = { name, config, ... }: {
    options = {
      name = mkOption {
        type = types.str;
        readOnly = true;
        description = ''
          Unique identifier of the account. This is set to the
          attribute name of the account configuration.
        '';
      };

      primary = mkOption {
        type = types.bool;
        default = false;
        description = ''
          Whether this is the primary account. Only one account may be
          set as primary.
        '';
      };

      flavor = mkOption {
        type = types.enum [
          "plain"
          "gmail.com"
          "runbox.com"
          "fastmail.com"
          "yandex.com"
          "outlook.office365.com"
        ];
        default = "plain";
        description = ''
          Some email providers have peculiar behavior that require
          special treatment. This option is therefore intended to
          indicate the nature of the provider.
          </para><para>
          When this indicates a specific provider then, for example,
          the IMAP, SMTP, and JMAP server configuration may be set
          automatically.
        '';
      };

      address = mkOption {
        type = types.strMatching ".*@.*";
        example = "jane.doe@example.org";
        description = "The email address of this account.";
      };

      aliases = mkOption {
        type = types.listOf (types.strMatching ".*@.*");
        default = [ ];
        example = [ "webmaster@example.org" "admin@example.org" ];
        description = "Alternative email addresses of this account.";
      };

      realName = mkOption {
        type = types.str;
        example = "Jane Doe";
        description = "Name displayed when sending mails.";
      };

      userName = mkOption {
        type = types.nullOr types.str;
        default = null;
        description = ''
          The server username of this account. This will be used as
          the SMTP, IMAP, and JMAP user name.
        '';
      };

      passwordCommand = mkOption {
        type = types.nullOr (types.either types.str (types.listOf types.str));
        default = null;
        apply = p: if isString p then splitString " " p else p;
        example = "secret-tool lookup email me@example.org";
        description = ''
          A command, which when run writes the account password on
          standard output.
        '';
      };

      folders = mkOption {
        type = types.submodule {
          options = {
            inbox = mkOption {
              type = types.str;
              default = "Inbox";
              description = ''
                Relative path of the inbox mail.
              '';
            };

            sent = mkOption {
              type = types.nullOr types.str;
              default = "Sent";
              description = ''
                Relative path of the sent mail folder.
              '';
            };

            drafts = mkOption {
              type = types.nullOr types.str;
              default = "Drafts";
              description = ''
                Relative path of the drafts mail folder.
              '';
            };

            trash = mkOption {
              type = types.str;
              default = "Trash";
              description = ''
                Relative path of the deleted mail folder.
              '';
            };
          };
        };
        default = { };
        description = ''
          Standard email folders.
        '';
      };

      imap = mkOption {
        type = types.nullOr imapModule;
        default = null;
        description = ''
          The IMAP configuration to use for this account.
        '';
      };

      jmap = mkOption {
        type = types.nullOr jmapModule;
        default = null;
        description = ''
          The JMAP configuration to use for this account.
        '';
      };

      signature = mkOption {
        type = signatureModule;
        default = { };
        description = ''
          Signature configuration.
        '';
      };

      gpg = mkOption {
        type = types.nullOr gpgModule;
        default = null;
        description = ''
          GPG configuration.
        '';
      };

      smtp = mkOption {
        type = types.nullOr smtpModule;
        default = null;
        description = ''
          The SMTP configuration to use for this account.
        '';
      };

      maildir = mkOption {
        type = types.nullOr maildirModule;
        defaultText = { path = "\${name}"; };
        description = ''
          Maildir configuration for this account.
        '';
      };
    };

    config = mkMerge [
      {
        name = name;
        maildir = mkOptionDefault { path = "${name}"; };
      }

      (mkIf (config.flavor == "yandex.com") {
        userName = mkDefault config.address;

        imap = {
          host = "imap.yandex.com";
          port = 993;
          tls.enable = true;
        };

        smtp = {
          host = "smtp.yandex.com";
          port = 465;
          tls.enable = true;
        };
      })

      (mkIf (config.flavor == "outlook.office365.com") {
        userName = mkDefault config.address;

        imap = {
          host = "outlook.office365.com";
          port = 993;
          tls.enable = true;
        };

        smtp = {
          host = "smtp.office365.com";
          port = 587;
          tls = {
            enable = true;
            useStartTls = true;
          };
        };
      })

      (mkIf (config.flavor == "fastmail.com") {
        userName = mkDefault config.address;

        imap = {
          host = "imap.fastmail.com";
          port = 993;
        };

        smtp = {
          host = "smtp.fastmail.com";
          port = if config.smtp.tls.useStartTls then 587 else 465;
        };

        jmap = {
          host = "fastmail.com";
          sessionUrl = "https://jmap.fastmail.com/.well-known/jmap";
        };
      })

      (mkIf (config.flavor == "gmail.com") {
        userName = mkDefault config.address;

        imap = {
          host = "imap.gmail.com";
          port = 993;
        };

        smtp = {
          host = "smtp.gmail.com";
          port = if config.smtp.tls.useStartTls then 587 else 465;
        };
      })

      (mkIf (config.flavor == "runbox.com") {
        imap = { host = "mail.runbox.com"; };

        smtp = { host = "mail.runbox.com"; };
      })
    ];
  };

in {
  options.accounts.email = {
    certificatesFile = mkOption {
      type = types.nullOr types.path;
      default = "/etc/ssl/certs/ca-certificates.crt";
      description = ''
        Path to default file containing certificate authorities that
        should be used to validate the connection authenticity. This
        path may be overridden on a per-account basis.
      '';
    };

    maildirBasePath = mkOption {
      type = types.str;
      default = "${config.home.homeDirectory}/Maildir";
      defaultText = "$HOME/Maildir";
      apply = p:
        if hasPrefix "/" p then p else "${config.home.homeDirectory}/${p}";
      description = ''
        The base directory for account maildir directories. May be a
        relative path, in which case it is relative the home
        directory.
      '';
    };

    accounts = mkOption {
      type = types.attrsOf (types.submodule mailAccountOpts);
      default = { };
      description = "List of email accounts.";
    };
  };

  config = mkIf (cfg.accounts != { }) {
    assertions = [
      (let
        primaries =
          catAttrs "name" (filter (a: a.primary) (attrValues cfg.accounts));
      in {
        assertion = length primaries == 1;
        message = "Must have exactly one primary mail account but found "
          + toString (length primaries) + optionalString (length primaries > 1)
          (", namely " + concatStringsSep ", " primaries);
      })
    ];
  };
}
