# support for legacy kubenix

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

with lib;

let
  parentModule = module;
  globalConfig = config;

  mkOptionDefault = mkOverride 1001;

  mkModuleOptions = moduleDefinition: module:
    let
      # gets file where module is defined by looking into moduleDefinitions
      # option.
      file =
        elemAt options.kubernetes.moduleDefinitions.files (
          (findFirst (i: i > 0) 0
            (imap
              (i: def: if hasAttr module.module def then i else 0)
              options.kubernetes.moduleDefinitions.definitions
            )
          ) - 1
        );

      injectModuleAttrs = module: attrs: (
        if isFunction module then args: (applyIfFunction file module args) // attrs
        else if isAttrs mkOptionDefault.module then module // attrs
        else module
      );
    in [
      {
        _module.args.name = module.name;
        _module.args.module = module;
      }
      ./k8s.nix
      ./legacy.nix
      (injectModuleAttrs moduleDefinition.module {_file = file;})
      {
        config.kubernetes.namespace = mkOptionDefault module.namespace;
        config.kubenix.project = mkOptionDefault config.kubenix.project;
      }
     ] ++ config.kubernetes.defaultModuleConfiguration.all
       ++ (optionals (hasAttr moduleDefinition.name config.kubernetes.defaultModuleConfiguration)
         config.kubernetes.defaultModuleConfiguration.${moduleDefinition.name});

  # prefix kubernetes objects with ${serviceName}, this magic was removed in new kubenix
  prefixResources = resources: serviceName:
    mapAttrs' (name: resource: nameValuePair "${serviceName}-${name}" resource) resources;

  defaultModuleConfigurationOptions = mapAttrs (name: moduleDefinition: mkOption {
    description = "Module default configuration for ${name} module";
    type = types.coercedTo types.unspecified (value: [value]) (types.listOf types.unspecified);
    default = [];
  }) config.kubernetes.moduleDefinitions;

  getModuleDefinition = name:
    if hasAttr name config.kubernetes.moduleDefinitions
    then config.kubernetes.moduleDefinitions.${name}
    else throw ''requested kubernetes moduleDefinition with name "${name}" does not exist'';

in {
  imports = [ ./k8s.nix ];

  options.kubernetes.moduleDefinitions = mkOption {
    description = "Legacy kubenix attribute set of module definitions";
    default = {};
    type = types.attrsOf (types.submodule ({name, ...}: {
      options = {
        name = mkOption {
          description = "Module definition name";
          type = types.str;
          default = name;
        };

        prefixResources = mkOption {
          description = "Whether resources should be automatically prefixed with module name";
          type = types.bool;
          default = true;
        };

        assignAsDefaults = mkOption {
          description = "Whether to assign resources as defaults, this is usefull for module that add some functionality";
          type = types.bool;
          default = false;
        };

        module = mkOption {
          description = "Module definition";
        };
      };
    }));
  };

  options.kubernetes.defaultModuleConfiguration = mkOption {
    description = "Legacy kubenix module default options";
    type = types.submodule {
      options = defaultModuleConfigurationOptions // {
        all = mkOption {
          description = "Module default configuration for all modules";
          type = types.coercedTo types.unspecified (value: [value]) (types.listOf types.unspecified);
          default = [];
        };
      };
    };
    default = {};
  };

  options.kubernetes.modules = mkOption {
    description = "Legacy kubenix attribute set of modules";
    default = {};
    type = types.attrsOf (types.submodule ({config, name, ...}: {
      options = {
        name = mkOption {
          description = "Module name";
          type = types.str;
          default = name;
        };

        namespace = mkOption {
          description = "Namespace where to deploy module";
          type = types.str;
          default = globalConfig.kubernetes.namespace;
        };

        labels = mkOption {
          description = "Attribute set of module lables";
          type = types.attrsOf types.str;
          default = {};
        };

        configuration = mkOption {
          description = "Module configuration";
          type = submoduleWithSpecialArgs {
            imports = mkModuleOptions (getModuleDefinition config.module) config;
          } {
            inherit kubenix;
          };
          default = {};
        };

        module = mkOption {
          description = "Name of the module to use";
          type = types.str;
          default = config.name;
        };
      };
    }));
  };

  options.kubernetes.defaults = mkOption {
    type = types.attrsOf (types.coercedTo types.unspecified (value: [value]) (types.listOf types.unspecified));
    description = "Legacy kubenix kubernetes defaults.";
    default = {};
  };

  # for back compatibility with kubernetes.customResources
  options.kubernetes.customResources = options.kubernetes.resources;

  config = {
    kubernetes = mkMerge [{
      api.defaults = mapAttrsToList (attrName: default: let
        type = head (mapAttrsToList (_: v: v) (filterAttrs (_: type: type.attrName == attrName) config.kubernetes.api.types));
      in {
        default = { imports = default; };
      } // (if (attrName == "all") then {} else {
        resource = type.name;
      })) config.kubernetes.defaults;

      resources = mkMerge (
        mapAttrsToList (name: module:
          mapAttrs' (_: type: let
            moduleDefinition = getModuleDefinition module.module;

            moduleResources = module.configuration.kubernetes.api.resources.${type.attrName} or {};

            moduleConfig =
              if moduleDefinition.prefixResources && type.kind != "CustomResourceDefinition"
              then prefixResources (moduleToAttrs moduleResources) name
              else moduleToAttrs moduleResources;
          in nameValuePair type.attrName
            (if moduleDefinition.assignAsDefaults
            then mkAllDefault moduleConfig 1000
            else moduleConfig)
          ) module.configuration.kubernetes.api.types
        ) config.kubernetes.modules
      );

      # create custom types from CRDs was old behavior
      createCustomTypesFromCRDs = true;

      defaultModuleConfiguration.all = {
        _file = head options.kubernetes.defaultModuleConfiguration.files;
        config.kubernetes.version = mkDefault config.kubernetes.version;
        config.kubernetes.moduleDefinitions = config.kubernetes.moduleDefinitions;
      };
    } {
      resources = mkAliasDefinitions options.kubernetes.customResources;
    }];
  };
}
