import { Constructable, IContainer, noop, Registration } from '@aurelia/kernel';
import { getDefaultValidationConfiguration, ValidationCustomizationOptions, ValidationConfiguration } from '@aurelia/validation';
import { ValidationContainerCustomElement, defaultContainerDefinition, defaultContainerTemplate } from './subscribers/validation-container-custom-element';
import { ValidationErrorsCustomAttribute } from './subscribers/validation-errors-custom-attribute';
import { IDefaultTrigger, ValidateBindingBehavior, ValidationTrigger } from './validate-binding-behavior';
import { IValidationController, ValidationControllerFactory } from './validation-controller';
import { ValidationHtmlCustomizationOptions } from './validation-customization-options';
import { CustomElement } from '@aurelia/runtime-html';

export type ValidationConfigurationProvider = (options: ValidationHtmlCustomizationOptions) => void;

export function getDefaultValidationHtmlConfiguration(): ValidationHtmlCustomizationOptions {
  return {
    ...getDefaultValidationConfiguration(),
    ValidationControllerFactoryType: ValidationControllerFactory,
    DefaultTrigger: ValidationTrigger.focusout,
    UseSubscriberCustomAttribute: true,
    SubscriberCustomElementTemplate: defaultContainerTemplate
  };
}

function createConfiguration(optionsProvider: ValidationConfigurationProvider) {
  return {
    optionsProvider,
    register(container: IContainer) {
      const options: ValidationHtmlCustomizationOptions = getDefaultValidationHtmlConfiguration();

      optionsProvider(options);

      container.registerFactory(IValidationController as unknown as Constructable, new options.ValidationControllerFactoryType());

      container.register(
        ValidationConfiguration.customize((opt) => {
          // copy the customization iff the key exists in validation configuration
          for (const optKey of Object.keys(opt) as (keyof ValidationCustomizationOptions)[]) {
            if (optKey in options) {
              (opt as any)[optKey] = options[optKey]; // TS cannot infer that the value of the same key is being copied from A to B, and rejects the assignment due to type broadening
            }
          }
        }),
        Registration.instance(IDefaultTrigger, options.DefaultTrigger),
        ValidateBindingBehavior,
      );
      if (options.UseSubscriberCustomAttribute) {
        container.register(ValidationErrorsCustomAttribute);
      }
      const template = options.SubscriberCustomElementTemplate;
      if (template) { // we need the boolean coercion here to ignore null, undefined, and ''
        container.register(CustomElement.define({ ...defaultContainerDefinition, template }, ValidationContainerCustomElement));
      }
      return container;
    },
    customize(cb?: ValidationConfigurationProvider) {
      return createConfiguration(cb ?? optionsProvider);
    },
  };
}

export const ValidationHtmlConfiguration = /*@__PURE__*/ createConfiguration(noop);

