{ lib, config, ... }:

with lib;

let

  cfg = config.services.znc;

  networkOpts = {
    options = {

      server = mkOption {
        type = types.str;
        example = "irc.libera.chat";
        description = ''
          IRC server address.
        '';
      };

      port = mkOption {
        type = types.port;
        default = 6697;
        description = ''
          IRC server port.
        '';
      };

      password = mkOption {
        type = types.str;
        default = "";
        description = ''
          IRC server password, such as for a Slack gateway.
        '';
      };

      useSSL = mkOption {
        type = types.bool;
        default = true;
        description = ''
          Whether to use SSL to connect to the IRC server.
        '';
      };

      modules = mkOption {
        type = types.listOf types.str;
        default = [ "simple_away" ];
        example = literalExpression ''[ "simple_away" "sasl" ]'';
        description = ''
          ZNC network modules to load.
        '';
      };

      channels = mkOption {
        type = types.listOf types.str;
        default = [];
        example = [ "nixos" ];
        description = ''
          IRC channels to join.
        '';
      };

      hasBitlbeeControlChannel = mkOption {
        type = types.bool;
        default = false;
        description = ''
          Whether to add the special Bitlbee operations channel.
        '';
      };

      extraConf = mkOption {
        default = "";
        type = types.lines;
        example = ''
          Encoding = ^UTF-8
          FloodBurst = 4
          FloodRate = 1.00
          IRCConnectEnabled = true
          Ident = johntron
          JoinDelay = 0
          Nick = johntron
        '';
        description = ''
          Extra config for the network. Consider using
          {option}`services.znc.config` instead.
        '';
      };
    };
  };

in

{

  options = {
    services.znc = {

      useLegacyConfig = mkOption {
        default = true;
        type = types.bool;
        description = ''
          Whether to propagate the legacy options under
          {option}`services.znc.confOptions.*` to the znc config. If this
          is turned on, the znc config will contain a user with the default name
          "znc", global modules "webadmin" and "adminlog" will be enabled by
          default, and more, all controlled through the
          {option}`services.znc.confOptions.*` options.
          You can use {command}`nix-instantiate --eval --strict '<nixpkgs/nixos>' -A config.services.znc.config`
          to view the current value of the config.

          In any case, if you need more flexibility,
          {option}`services.znc.config` can be used to override/add to
          all of the legacy options.
        '';
      };

      confOptions = {
        modules = mkOption {
          type = types.listOf types.str;
          default = [ "webadmin" "adminlog" ];
          example = [ "partyline" "webadmin" "adminlog" "log" ];
          description = ''
            A list of modules to include in the `znc.conf` file.
          '';
        };

        userModules = mkOption {
          type = types.listOf types.str;
          default = [ "chansaver" "controlpanel" ];
          example = [ "chansaver" "controlpanel" "fish" "push" ];
          description = ''
            A list of user modules to include in the `znc.conf` file.
          '';
        };

        userName = mkOption {
          default = "znc";
          example = "johntron";
          type = types.str;
          description = ''
            The user name used to log in to the ZNC web admin interface.
          '';
        };

        networks = mkOption {
          default = { };
          type = with types; attrsOf (submodule networkOpts);
          description = ''
            IRC networks to connect the user to.
          '';
          example = literalExpression ''
            {
              "libera" = {
                server = "irc.libera.chat";
                port = 6697;
                useSSL = true;
                modules = [ "simple_away" ];
              };
            };
          '';
        };

        nick = mkOption {
          default = "znc-user";
          example = "john";
          type = types.str;
          description = ''
            The IRC nick.
          '';
        };

        passBlock = mkOption {
          example = ''
            &lt;Pass password&gt;
               Method = sha256
               Hash = e2ce303c7ea75c571d80d8540a8699b46535be6a085be3414947d638e48d9e93
               Salt = l5Xryew4g*!oa(ECfX2o
            &lt;/Pass&gt;
          '';
          type = types.str;
          description = ''
            Generate with {command}`nix-shell -p znc --command "znc --makepass"`.
            This is the password used to log in to the ZNC web admin interface.
            You can also set this through
            {option}`services.znc.config.User.<username>.Pass.Method`
            and co.
          '';
        };

        port = mkOption {
          default = 5000;
          type = types.port;
          description = ''
            Specifies the port on which to listen.
          '';
        };

        useSSL = mkOption {
          default = true;
          type = types.bool;
          description = ''
            Indicates whether the ZNC server should use SSL when listening on
            the specified port. A self-signed certificate will be generated.
          '';
        };

        uriPrefix = mkOption {
          type = types.nullOr types.str;
          default = null;
          example = "/znc/";
          description = ''
            An optional URI prefix for the ZNC web interface. Can be
            used to make ZNC available behind a reverse proxy.
          '';
        };

        extraZncConf = mkOption {
          default = "";
          type = types.lines;
          description = ''
            Extra config to `znc.conf` file.
          '';
        };
      };

    };
  };

  config = mkIf cfg.useLegacyConfig {

    services.znc.config = let
      c = cfg.confOptions;
      # defaults here should override defaults set in the non-legacy part
      mkDefault = mkOverride 900;
    in {
      LoadModule = mkDefault c.modules;
      Listener.l = {
        Port = mkDefault c.port;
        IPv4 = mkDefault true;
        IPv6 = mkDefault true;
        SSL = mkDefault c.useSSL;
        URIPrefix = c.uriPrefix;
      };
      User.${c.userName} = {
        Admin = mkDefault true;
        Nick = mkDefault c.nick;
        AltNick = mkDefault "${c.nick}_";
        Ident = mkDefault c.nick;
        RealName = mkDefault c.nick;
        LoadModule = mkDefault c.userModules;
        Network = mapAttrs (name: net: {
          LoadModule = mkDefault net.modules;
          Server = mkDefault "${net.server} ${optionalString net.useSSL "+"}${toString net.port} ${net.password}";
          Chan = optionalAttrs net.hasBitlbeeControlChannel { "&bitlbee" = mkDefault {}; } //
            listToAttrs (map (n: nameValuePair "#${n}" (mkDefault {})) net.channels);
          extraConfig = if net.extraConf == "" then mkDefault null else net.extraConf;
        }) c.networks;
        extraConfig = [ c.passBlock ];
      };
      extraConfig = optional (c.extraZncConf != "") c.extraZncConf;
    };
  };

  imports = [
    (mkRemovedOptionModule ["services" "znc" "zncConf"] ''
      Instead of `services.znc.zncConf = "... foo ...";`, use
      `services.znc.configFile = pkgs.writeText "znc.conf" "... foo ...";`.
    '')
  ];
}
