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

with lib;

let

  cfg = config.programs.offlineimap;

  accounts = filter (a: a.offlineimap.enable)
    (attrValues config.accounts.email.accounts);

  toIni = generators.toINI {
    mkKeyValue = key: value:
      let
        value' =
          if isBool value then (if value then "yes" else "no")
          else toString value;
      in
        "${key} = ${value'}";
  };

  # Generates a script to fetch only a specific account.
  #
  # Note, these scripts are not actually created and installed at the
  # moment. It will need some thinking on whether this is a good idea
  # and whether other modules should have some similar functionality.
  #
  # Perhaps have a single tool `email` that wraps the command?
  # Something like
  #
  #     $ email <account name> <program name> <program args>
  genOfflineImapScript = account: with account;
    pkgs.writeShellScriptBin "offlineimap-${name}" ''
      exec ${pkgs.offlineimap}/bin/offlineimap -a${account.name} "$@"
    '';

  accountStr = account: with account;
    let
      postSyncHook = optionalAttrs (offlineimap.postSyncHookCommand != "") {
        postsynchook =
          pkgs.writeShellScriptBin
            "postsynchook"
            offlineimap.postSyncHookCommand
          + "/bin/postsynchook";
      };

      localType =
        if account.flavor == "gmail.com"
        then "GmailMaildir"
        else "Maildir";

      remoteType =
        if account.flavor == "gmail.com"
        then "Gmail"
        else "IMAP";

      remoteHost = optionalAttrs (imap.host != null) {
        remotehost = imap.host;
      };

      remotePort = optionalAttrs ((imap.port or null) != null) {
        remoteport = imap.port;
      };

      ssl =
        if imap.tls.enable
        then
          {
            ssl = true;
            sslcacertfile = imap.tls.certificatesFile;
            starttls = imap.tls.useStartTls;
          }
        else
          {
            ssl = false;
          };

      remotePassEval =
        let
          arglist = concatMapStringsSep "," (x: "'${x}'") passwordCommand;
        in
          optionalAttrs (passwordCommand != null) {
            remotepasseval = ''get_pass("${name}", [${arglist}])'';
          };
    in
      toIni {
        "Account ${name}" = {
          localrepository = "${name}-local";
          remoterepository = "${name}-remote";
        }
        // postSyncHook
        // offlineimap.extraConfig.account;

        "Repository ${name}-local" = {
          type = localType;
          localfolders = maildir.absPath;
        }
        // offlineimap.extraConfig.local;

        "Repository ${name}-remote" = {
          type = remoteType;
          remoteuser = userName;
        }
        // remoteHost
        // remotePort
        // remotePassEval
        // ssl
        // offlineimap.extraConfig.remote;
      };

  extraConfigType = with types; attrsOf (either (either str int) bool);

in

{
  options = {
    programs.offlineimap = {
      enable = mkEnableOption "OfflineIMAP";

      pythonFile = mkOption {
        type = types.lines;
        default = ''
          import subprocess

          def get_pass(service, cmd):
              return subprocess.check_output(cmd, )
        '';
        description = ''
          Python code that can then be used in other parts of the
          configuration.
        '';
      };

      extraConfig.general = mkOption {
        type = extraConfigType;
        default = {};
        example = {
          maxage = 30;
          ui = "blinkenlights";
        };
        description = ''
          Extra configuration options added to the
          <option>general</option> section.
        '';
      };

      extraConfig.default = mkOption {
        type = extraConfigType;
        default = {};
        example = {
          gmailtrashfolder = "[Gmail]/Papierkorb";
        };
        description = ''
          Extra configuration options added to the
          <option>DEFAULT</option> section.
        '';
      };

      extraConfig.mbnames = mkOption {
        type = extraConfigType;
        default = {};
        example = literalExample ''
          {
            filename = "~/.config/mutt/mailboxes";
            header = "'mailboxes '";
            peritem = "'+%(accountname)s/%(foldername)s'";
            sep = "' '";
            footer = "'\\n'";
          }
        '';
        description = ''
          Extra configuration options added to the
          <code>mbnames</code> section.
        '';
      };
    };
  };

  config = mkIf cfg.enable {
    home.packages = [ pkgs.offlineimap ];

    xdg.configFile."offlineimap/get_settings.py".text = cfg.pythonFile;

    xdg.configFile."offlineimap/config".text =
      ''
        # Generated by Home Manager.
        # See https://github.com/OfflineIMAP/offlineimap/blob/master/offlineimap.conf
        # for an exhaustive list of options.
      ''
      + toIni ({
        general = {
          accounts = concatMapStringsSep "," (a: a.name) accounts;
          pythonfile = "${config.xdg.configHome}/offlineimap/get_settings.py";
          metadata = "${config.xdg.dataHome}/offlineimap";
        }
        // cfg.extraConfig.general;
      }
      // optionalAttrs (cfg.extraConfig.mbnames != {}) {
        mbnames = { enabled = true; } // cfg.extraConfig.mbnames;
      }
      // optionalAttrs (cfg.extraConfig.default != {}) {
        DEFAULT = cfg.extraConfig.default;
      })
      + "\n"
      + concatStringsSep "\n" (map accountStr accounts);
  };
}