# SPDX-FileCopyrightText: 2024 Lubin Bailly <lubin.bailly@dgnum.eu>
#
# SPDX-License-Identifier: EUPL-1.2

{ config, lib, ... }:

let
  inherit (lib)
    assertMsg
    concatStringsSep
    mapAttrsToList
    mkOption
    optionalString
    ;

  inherit (lib.types)
    attrsOf
    either
    ints
    listOf
    nullOr
    str
    submodule
    unspecified
    ;

  vlan =
    { name, config, ... }:
    {
      options = {
        id = mkOption {
          type = nullOr ints.unsigned;
          default = null;
          description = ''
            The ID of this vlan, `null` means no ID.
            Incompatible with vlans.${name}.id-list.
          '';
        };
        id-list = mkOption {
          type =
            let
              range_type =
                { config, ... }:
                {
                  config.__toString = _: "${toString config.begin}-${toString config.end}";
                  options = {
                    begin = mkOption {
                      type = ints.unsigned;
                      visible = false;
                    };
                    end = mkOption {
                      type = ints.unsigned;
                      visible = false;
                    };
                    __toString = mkOption {
                      visible = false;
                      internal = true;
                      readOnly = true;
                      type = unspecified;
                    };
                  };
                };
            in
            listOf (either ints.unsigned (submodule range_type));
          default = [ ];
          example = [
            42
            {
              begin = 100;
              end = 200;
            }
          ];
          description = ''
            List of IDs or IDs range to classify as this vlan.
            Incompatible with vlans.${name}.id.
          '';
        };
        l3-interface = mkOption {
          type = nullOr str;
          default = null;
          example = "irb.0";
          description = ''
            Switch's logical interface to connect directly to this vlan.
            This allows to communicate with the switch from a vlan without
            having a cable looping back on it's management interface.
          '';
        };
        xml = mkOption {
          type = str;
          readOnly = true;
          visible = false;
        };
      };
      config.xml =
        let
          id = optionalString (config.id != null) "<vlan-id>${toString config.id}</vlan-id>";
          id-list = concatStringsSep "" (
            map (vlan: "<vlan-id-list>${toString vlan}</vlan-id-list>") config.id-list
          );
          l3-intf = optionalString (
            config.l3-interface != null
          ) "<l3-interface>${config.l3-interface}</l3-interface>";
        in
        assert assertMsg (
          config.id == null || config.id-list == [ ]
        ) "vlans.${name}.id and vlans.${name}.id-list are incompatible.";
        ''
          <vlan>
            <name>${name}</name>
            ${id}${id-list}${l3-intf}
          </vlan>
        '';
    };
in
{
  options = {
    vlans = mkOption {
      type = attrsOf (submodule vlan);
      description = ''
        Named vlans configuration. Allows to name vlans inside interface configuration,
        instead of just using their IDs.
      '';
    };
    netconf.xmls.vlans = mkOption {
      type = str;
      visible = false;
      readOnly = true;
    };
  };
  config.netconf.xmls.vlans = ''
    <vlans operation="replace">
      ${builtins.concatStringsSep "" (mapAttrsToList (_: vlan: vlan.xml) config.vlans)}
    </vlans>
  '';
}