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

import {
  GoogleTranslationOptions,
  LisioStringParameterNames,
} from "@lisio/lisio-profils";
import { CustomInput } from "../../custom-input";
import {
  TranslationController,
  TranslationLanguages,
} from "../../../../../controllers/translation/translation-controller";
import { InputManager } from "../../../../../managers/input-manager";
import { MessageManager } from "../../../../../managers/message-manager";
import { UserController } from "../../../../../controllers/user-controller";
import {
  ISOToIcon,
  MergedCategoriesNames,
} from "../../../../screens/screen-types";
import { ElementsContainer } from "../../../containers/elements-container/elements-container";
import { StatisticsController } from "../../../../../controllers/statistics-controller";
import { translateEvent } from "../../../../../../misc/events";

/**
 * Class representing a radio input component extending CustomInput.\
 * It aims to represent a radio input component.\
 * A radio input component is basically a component to display a radio input.\
 */
class RadioInput extends CustomInput {
  /**
   * Private attribute to store name of input
   * @source
   */
  protected _name: LisioStringParameterNames | "users-choice";

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

  /**
   * Private attribute to stora handler for last checked input
   * @source
   */
  private _lastCheckedInputHandler: (input: RadioInput) => void;

  /**
   * Getter for attribute {@link _isChecked | _isChecked}
   * @returns Returns _isChecked attribute
   * @source
   */
  public get isChecked(): boolean {
    return this._attributes.has("checked");
  }

  /**
   * Getter for attribute {@link _value | _value}
   * @returns Returns _value attribute
   * @source
   */
  public get value(): string {
    return this._value;
  }

  /**
   * Setter for attribute {@link _value | _value}
   * @returns Returns void
   * @source
   */
  public set value(value: string) {
    this._value = value;
  }

  /**
   * Getter for attribute {@link _name | _name}
   * @returns Returns _name attribute
   * @source
   */
  public get name(): LisioStringParameterNames | "users-choice" {
    return this._name;
  }

  /**
   * Setter for attribute {@link _id | _id}
   * @returns Returns void
   * @source
   */
  public set id(id: string) {
    this._id = id;
  }

  /**
   * @async
   * Public method implementing abstract method render of LisioComponent
   * @returns Returns a promise containing the representation of a radio input in HTML string
   * @source
   */
  public async render(): Promise<string> {
    return `
        <input name="${this._name.replace("_", "-")}" ${
          this._id != undefined ? `id="${this._id}"` : ""
        } type="radio" 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 boolean 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 === "boolean" &&
      this._attributes.has("checked") !== value
    ) {
      const closest = this._htmlElement?.closest("li");
      if (value) {
        this.addAttributes("checked", String(value));
        this.addAttributes("aria-checked",  String(value));
        this.addAttributes("aria-hidden", "false");
        this._lastCheckedInputHandler(this);
        closest?.classList.add("checked");
        const infobox = closest?.querySelector(".infobox-radio");
          if (infobox) {
            infobox.querySelectorAll("p").forEach((p) => {
              p.setAttribute("aria-hidden", "false");
            });
            infobox.querySelectorAll("button").forEach((p) => {
              p.setAttribute("tabindex", "0");
            });
          }
      } else {
        this.removeAttributes("checked");
        this.removeAttributes("aria-checked");
        this.addAttributes("aria-hidden", "true");
        closest?.classList.remove("checked");
        const infobox = closest?.querySelector(".infobox-radio");
          if (infobox) {
            infobox.querySelectorAll("p").forEach((p) => {
              p.setAttribute("aria-hidden", "true");
            });
            infobox.querySelectorAll("button").forEach((p) => {
              p.setAttribute("tabindex", "-1");
            });
          }
      }
      if (this._htmlElement != undefined) {
        (this._htmlElement as HTMLInputElement).checked = value;
      }
      if (hasToUpdateUser && this._name !== "users-choice") {
        const isDefault: boolean = this._value === "default";
        await UserController.current.updateUserString(
          this._name,
          this._value,
          isDefault,
        );
      }
      this.generalToggleBehavior(
        this._name,
        value && this._value != "default",
        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 === LisioStringParameterNames.GOOGLE_TRANSLATION &&
      Object.values(GoogleTranslationOptions).includes(
        this._value as GoogleTranslationOptions,
      )
    ) {
      StatisticsController.current.addGoogleTransTracking(
        this._value as GoogleTranslationOptions,
      );
    }
    if (this._name === "users-choice") {
      await UserController.current.switchSelectedUser(this._value);
    } else {
      if (
        this._name == (LisioStringParameterNames.DEEPL_TRANSLATION as string) ||
        this._name == (LisioStringParameterNames.GOOGLE_TRANSLATION as string)
      ) {
        const lngIso =
          TranslationLanguages[
            this._value
              .toUpperCase()
              .split("_")[0] as keyof typeof TranslationLanguages
          ] || TranslationLanguages.DEFAULT;
        await TranslationController.current.loadTranslationFile(lngIso);
        const flagIso =
          this._value === "default"
            ? MessageManager.current.defaultFlagIso
            : ISOToIcon[this._value.toUpperCase() as keyof typeof ISOToIcon];
        MessageManager.current.langIso = lngIso;
        MessageManager.current.flagIso = flagIso;
        window.dispatchEvent(
          new CustomEvent(translateEvent.type, {
            detail: {
              flagIso: flagIso,
            },
          }),
        );
        MessageManager.current.sendTranslationLanguageChangeMessage();
      }
      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 != "default" && this._attributes.has("checked")) {
      this.removeAttributes("checked");
      this.toggle<boolean>(true, true, false, hasToEnableActiveInput);
    }
  }

  /**
   * Constructor of class {@link RadioInput | RadioInput}
   * @param {boolean} isChecked - State of input
   * @param {string} value - Value of input
   * @param {LisioStringParameterNames | "users-choice"} 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(
    isChecked: boolean,
    value: string,
    name: LisioStringParameterNames | "users-choice",
    lastCheckedInputHandler: (input: RadioInput) => void,
    handler: (event: Event) => void,
    id: string,
    screenName: string,
    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) {
        if (aria.label.id != "") {
          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 (isChecked) {
        attributes.set("checked", "true");
      }
    }
    super(
      id,
      screenName,
      categoryName,
      parentScreenName,
      infobox,
      cssClasses,
      attributes,
    );
    InputManager.current.addInputInMap(name, this);
    this._lastCheckedInputHandler = lastCheckedInputHandler;
    this._name = name;
    this._value = value;
    this._eventListeners.push({
      type: "change",
      handler: (event: unknown) => {
        if (event instanceof Event) {
          handler(event);
          this.toggle<boolean>(
            (event.target as HTMLInputElement).checked,
            true,
            true,
            true,
          );
        }
      },
    });
  }
}

export { RadioInput };
