/**
 * @mergeTarget
 * @module Src/Components/CustomInput
 */

import { LisioNumericParameterNames } from "@lisio/lisio-profils";
import { CustomInput } from "../../custom-input";
import { InputManager } from "../../../../../managers/input-manager";
import { MessageManager } from "../../../../../managers/message-manager";
import { UserController } from "../../../../../controllers/user-controller";
import { MergedCategoriesNames } from "../../../../screens/screen-types";
import { TranslationController } from "../../../../../controllers/translation/translation-controller";
import { ElementsContainer } from "../../../containers/elements-container/elements-container";
import { LisioComponent } from "@lisio/lisio-engine";
/**
 * Class representing a number input component extending CustomInput.\
 * It aims to represent a number input component.\
 * A number input component is basically a component to display a number input.\
 */
class NumberInput extends CustomInput {
  /**
   * Private attribute to store name of input
   * @source
   */
  private _name: LisioNumericParameterNames;

  /**
   * Private attribute to store value of input
   * @source
   */
  private _value: number;

  /**
   * Private attribute to store default value of input
   * @source
   */
  private _defaultValue: number;

  /**
   * Private attribute to store min value of input
   * @source
   */
  private _minValue: number;

  /**
   * Private attribute to store max value of input
   * @source
   */
  private _maxValue: number;

  /**
   * Private attribute to store step of input
   * @source
   */
  private _step: number;

  /**
   * Private attribute to store slider of input
   * @source
   */
  private _slider: LisioComponent;

  /**
   *
   */
  public get value(): number {
    return this._value;
  }

  /**
   * @async
   * Public method implementing abstract method render of LisioComponent
   * @returns Returns a promise containing the representation of a number input in HTML string
   * @source
   */
  public async render(): Promise<string> {
    return `
        <input name="${this._name.replace("_", "-")}" ${
          this._id != undefined ? `id=${this._id}` : ""
        } type="number" class="lisio-custom-input ${this.renderClasses()}" min="${
          this._minValue
        }" max="${this._maxValue}" step="${this._step}" value="${
          this._defaultValue
        }" aria-hidden="false"
        />
      `;
  }

  /**
   * Public method to be called after render
   * @returns Returns nothing
   * @source
   */
  public postRender(): void {
    (this._htmlElement as HTMLInputElement).value = this._value.toString();
  }

  /**
   * @async
   * Public method to toggle input implementing parent abstract method
   * @typeParam T - Indicates type value expected a number value
   * @param {T} value - Value of input
   * @param {boolean} hasToHandleOnChange - Indicates if input has to handle on change
   * @param {boolean} hasToUpdateUser - Indicates if input has to update user
   * @param {boolean} hasToEnableActiveInput - Indicates if has to enable active input
   * @returns Returns a promise containing nothing
   * @source
   */
  public async toggle<T extends boolean | number | string>(
    value: T,
    hasToHandleOnChange: boolean,
    hasToUpdateUser: boolean,
    hasToEnableActiveInput: boolean,
  ): Promise<void> {
    if (typeof value === "number") {
      if (value === -1) {
        (value as number) = this._defaultValue;
      }
      if (value < this._minValue) {
        (value as number) = this._minValue;
      } else if (value > this._maxValue) {
        (value as number) = this._maxValue;
      }
      this._value = value;
      if (this._value != this._defaultValue) {
        this._slider.children[2].addClasses(["active"]);
        this._slider.addClasses(["active"]);
      } else {
        this._slider.children[2].removeClasses(["active"]);
        this._slider.removeClasses(["active"]);
      }
      if (this._htmlElement != undefined) {
        (this._htmlElement as HTMLInputElement).value = value.toString();
        (this._htmlElement as HTMLInputElement).setAttribute('value',value.toString());
      }
      if (hasToUpdateUser) {
        await UserController.current.updateUserNumber(
          this._name,
          this._value,
          this._value === this._defaultValue,
        );
      }
      this.generalToggleBehavior(
        this._name,
        value != this._defaultValue,
        hasToEnableActiveInput,
      );
      if (hasToHandleOnChange) {
        await this.handleOnChange();
      }
    }
  }

  /**
   * @async
   * Protected method to handle on change event implementing parent abstract method
   * @returns Returns a promise containing nothing
   * @source
   */
  public async handleOnChange() {
    MessageManager.current?.numberParameterAdaptMessage(
      this._name,
      this._value,
      this._defaultValue,
    );
  }

  /**
   * Protected method to handle initialization implementing parent abstract method
   * @param {boolean} hasToEnableActiveInput - Indicates if has to enable active input
   * @returns Returns nothing
   * @source
   */
  public handleOnInit(hasToEnableActiveInput: boolean): void {
    if (this._value != this._defaultValue) {
      const value: number = this._value;
      this._value = this._defaultValue;
      this.toggle<number>(value, true, false, hasToEnableActiveInput);
    }
  }

  /**
   * Constructor of class {@link NumberInput | NumberInput}
   * @param {number} value - Value of input
   * @param {number} defaultValue - Default value of input
   * @param {number} minValue - Min value of input
   * @param {number} maxValue - Max value of input
   * @param {number} step - Step of input
   * @param {LisioNumericParameterNames} name - Name of functionality
   * @param {(event: Event) => void} handler - Extra behvior for on change event
   * @param {string} id - Id of component
   * @param {string} screenName - Screen name including this input
   * @param {LisioComponent} slider - Parent slider
   * @param {MergedCategoriesNames | undefined} categoryName - Category name containing parameter or profile related to this input
   * @param {string | undefined} parentScreenName - Parent screen name in screen tree
   * @param {LisioComponent | undefined} infobox - Infobox linked to this component
   * @param {string[] | undefined} cssClasses - CSS classes of component
   * @param {object | undefined} aria - Aria attributes
   * @property {object | undefined} aria.label - Aria label datas
   * @property {boolean | undefined} aria.label.noTranslate - Indicates if label need to be translated
   * @property {string} aria.label.id - Id of the text for aria label
   * @property {boolean | undefined} aria.hidden - Aria hidden
   */
  constructor(
    value: number,
    defaultValue: number,
    minValue: number,
    maxValue: number,
    step: number,
    name: LisioNumericParameterNames,
    handler: (event: Event) => void,
    id: string,
    screenName: string,
    slider: LisioComponent,
    categoryName?: MergedCategoriesNames,
    parentScreenName?: string,
    infobox?: ElementsContainer,
    cssClasses?: string[],
    aria?: {
      label?: {
        noTranslate?: boolean;
        id: string;
      };
      hidden?: boolean;
    },
  ) {
    const attributes: Map<string, string> = new Map<string, string>();
    if (aria) {
      if (aria.label) {
        attributes.set(
          "aria-label",
          aria.label.noTranslate
            ? aria.label.id
            : TranslationController.current.getTranslation(aria.label.id),
        );
      }
      if (aria.hidden != undefined) {
        attributes.set("aria-hidden", String(aria.hidden));
      }
    }
    super(
      id,
      screenName,
      categoryName,
      parentScreenName,
      infobox,
      cssClasses,
      attributes,
    );
    InputManager.current.addInputInMap(name, this);

    this._name = name;
    this._value = value;
    this._defaultValue = defaultValue;
    this._minValue = minValue;
    this._maxValue = maxValue;
    this._step = step;
    this._slider = slider;
    this._eventListeners.push({
      type: "change",
      handler: (event: unknown) => {
        if (event instanceof Event) {
          if ((event.target as HTMLInputElement).value === "") {
            (event.target as HTMLInputElement).value =
              this._defaultValue.toString();
          }
          handler(event);
          this.toggle<number>(
            Number.parseFloat((event.target as HTMLInputElement).value),
            true,
            true,
            true,
          );
          if (this._value !== defaultValue) {
            (event.target as HTMLInputElement)
              .closest(".lisio-elements-container")
              ?.classList.add("active");
            this._slider.addClasses(["active"]);
          } else {
            (event.target as HTMLInputElement)
              .closest(".lisio-elements-container")
              ?.classList.remove("active");
            this._slider.removeClasses(["active"]);
          }
        }
      },
    });
  }
}

export { NumberInput };
