{ config, lib, ... }:
let
  inherit (lib) mkOption types;

  cfg = config.accounts.calendar;

  localModule =
    name:
    types.submodule {
      options = {
        path = mkOption {
          type = types.str;
          default = "${cfg.basePath}/${name}";
          defaultText = lib.literalExpression "‹accounts.calendar.basePath›/‹name›";
          description = "The path of the storage.";
        };

        type = mkOption {
          type = types.enum [
            "filesystem"
            "singlefile"
          ];
          default = "filesystem";
          description = "The type of the storage.";
        };

        fileExt = mkOption {
          type = types.nullOr types.str;
          default = ".ics";
          description = "The file extension to use.";
        };

        encoding = mkOption {
          type = types.nullOr types.str;
          default = null;
          description = ''
            File encoding for items, both content and file name.
            Defaults to UTF-8.
          '';
        };
      };
    };

  remoteModule = types.submodule {
    options = {
      type = mkOption {
        type = types.enum [
          "caldav"
          "http"
          "google_calendar"
        ];
        description = "The type of the storage.";
      };

      url = mkOption {
        type = types.nullOr types.str;
        default = null;
        description = "The URL of the storage.";
      };

      userName = mkOption {
        type = types.nullOr types.str;
        default = null;
        description = "User name for authentication.";
      };

      passwordCommand = mkOption {
        type = types.nullOr (types.listOf types.str);
        default = null;
        example = [
          "pass"
          "caldav"
        ];
        description = ''
          A command that prints the password to standard output.
        '';
      };
    };
  };

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

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

        primaryCollection = mkOption {
          type = types.nullOr types.str;
          default = null;
          description = ''
            The primary collection of the account. Required when an
            account has multiple collections.
          '';
        };

        local = mkOption {
          type = localModule name;
          default = { };
          description = ''
            Local configuration for the calendar.
          '';
        };

        remote = mkOption {
          type = types.nullOr remoteModule;
          default = null;
          description = ''
            Remote configuration for the calendar.
          '';
        };
      };

      config = {
        name = name;
      };
    };

in
{
  options.accounts.calendar = {
    basePath = mkOption {
      type = types.str;
      example = ".calendar";
      apply = p: if lib.hasPrefix "/" p then p else "${config.home.homeDirectory}/${p}";
      description = ''
        The base directory in which to save calendars. May be a
        relative path, in which case it is relative the home
        directory.
      '';
    };

    accounts = mkOption {
      type = types.attrsOf (
        types.submodule [
          calendarOpts
          (import ../programs/vdirsyncer/accounts.nix)
          (import ../programs/pimsync/accounts.nix)
          (import ../programs/khal/accounts.nix)
          (import ../programs/khal/calendar-accounts.nix)
        ]
      );
      default = { };
      description = "List of calendars.";
    };
  };
  config = lib.mkIf (cfg.accounts != { }) {
    assertions =
      let
        primaries = lib.catAttrs "name" (lib.filter (a: a.primary) (lib.attrValues cfg.accounts));
      in
      [
        {
          assertion = lib.length primaries <= 1;
          message =
            "Must have at most one primary calendar account but found "
            + toString (lib.length primaries)
            + ", namely "
            + lib.concatStringsSep ", " primaries;
        }
      ];
  };
}
