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

import { LisioStringParameterNames } from "@lisio/lisio-profils";
import { CustomInput } from "../../custom-input";
import { InputManager } from "../../../../../managers/input-manager";
import { UserController } from "../../../../../controllers/user-controller";
import { MessageManager } from "../../../../../managers/message-manager";
import { MergedCategoriesNames } from "../../../../screens/screen-types";
import { TranslationController } from "../../../../../controllers/translation/translation-controller";
import { ElementsContainer } from "../../../containers/elements-container/elements-container";

/**
 * Class representing a text input component extending CustomInput.\
 * It aims to represent a text input component.\
 * A text input component is basically a component to display a text input.\
 */
class TextInput extends CustomInput {
  /**
   * Private attribute to store name of input
   * @source
   */
  private _name:
    | LisioStringParameterNames.UNDERLINE_RED
    | LisioStringParameterNames.UNDERLINE_GREEN
    | LisioStringParameterNames.UNDERLINE_BLUE
    | "rename-user";

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

  /**
   * Setter for attribute {@link _value | _value}
   * @param {string} value - New value of input
   * @source
   */
  public set value(value: string) {
    this._value = value;
  }

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

  /**
   * @async
   * Public method to toggle input implementing parent abstract method
   * @typeParam T - Indicates type value expected a string 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 === "string" && this._value != value) {
      this._value = value;
      if (this._htmlElement != undefined) {
        (this._htmlElement as HTMLInputElement).value = value;
      }
      const isDefault: boolean = this._value === "";
      if (hasToUpdateUser && this._name !== "rename-user") {
        await UserController.current.updateUserString(
          this._name,
          this._value,
          isDefault,
        );
      }
      this.generalToggleBehavior(
        this._name,
        value != "",
        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
   */
  protected async handleOnChange(): Promise<void> {
    if (this._name !== "rename-user") {
      MessageManager.current?.stringParameterAdaptMessage(
        this._name,
        this._value,
        "default",
      );
    }
  }

  /**
   * 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 != "") {
      const value: string = this._value;
      this._value = "";
      this.toggle<string>(value, true, false, hasToEnableActiveInput);
    }
  }

  /**
   * Constructor of class {@link TextInput | TextInput}
   * @param {string} value - Value of input
   * @param {LisioStringParameterNames.UNDERLINE_RED | LisioStringParameterNames.UNDERLINE_GREEN | LisioStringParameterNames.UNDERLINE_BLUE | "rename-user"} 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 {MergedCategoriesNames | undefined} categoryName - Category name containing parameter or profile related to this input
   * @param {string | undefined} parentScreenName - Parent screen name in screen tree
   * @param {ElementsContainer | 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: string,
    name:
      | LisioStringParameterNames.UNDERLINE_RED
      | LisioStringParameterNames.UNDERLINE_GREEN
      | LisioStringParameterNames.UNDERLINE_BLUE
      | "rename-user",
    handler: (event: Event) => void,
    id: string,
    screenName: string,
    categoryName?: MergedCategoriesNames,
    parentScreenName?: string,
    infobox?: ElementsContainer,
    cssClasses?: string[],
    aria?: {
      label?: {
        noTranslate?: boolean;
        id: string;
      };
      hidden?: boolean;
      describedby?: string;
    },
  ) {
    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));
      }
      if (aria.describedby) {
        attributes.set("aria-describedby", aria.describedby);
      }
    }
    super(
      id,
      screenName,
      categoryName,
      parentScreenName,
      infobox,
      cssClasses,
      attributes,
    );
    InputManager.current.addInputInMap(name, this);

    this._name = name;
    this._value = value;
    this._eventListeners.push({
      type: "change",
      handler: (event: unknown) => {
        if (event instanceof Event) {
          handler(event);
          this.toggle<string>(
            (event.target as HTMLInputElement).value,
            true,
            true,
            true,
          );
        }
      },
    });
  }
}

export { TextInput };
