/**
 * @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 { UserController } from "../../../../../controllers/user-controller";
import { CustomRange } from "../custom-range";
/**
 * 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 RangeInput 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 range of input
   * @source
  */
  private _range: CustomRange;

  /**
   * Private attribute to store default cursor position
   * @source
   */
  private _defaultCursorPosition: number;

  /**
   * Private attribute to store the cursor position
   * @source
   */
  private _cursorPosition: number;

  /**
   * Private attribute to store the slider width
   * @source
   */
  private _sliderWidth: number;

  /**
   *
   */
  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 `
        <div class="range-input-container" style="--cursor-position: ${this._cursorPosition}%;">
          <input type="range" name="${this._name.replace("_", "-")}" 
            ${this._id != undefined ? `id=${this._id}` : ""}  
            class="lisio-custom-input ${this.renderClasses()}" 
            min="${this._minValue}" max="${this._maxValue}" step="${this._step}" aria-hidden="false"
          />
        </div>
      `;
  }

  /**
   * Public method to be called after render
   * @returns Returns nothing
   * @source
   */
  public postRender(): void {
    const input = (this._htmlElement as HTMLDivElement).querySelector("input");
    if(input!=undefined){
      input.value = this._value.toString();
    }
    setTimeout(()=>{
      if(this._htmlElement !== undefined){
        this._sliderWidth = this._htmlElement.offsetWidth;
        this._defaultCursorPosition = ((((this._defaultValue - this._minValue) / (this._maxValue - this._minValue)) * (this._sliderWidth - 44)) + 44 / 2) / this._sliderWidth * 100;
      }
      this.handleOnChange();
    },1);
  }

  /**
   * @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 === Number.NEGATIVE_INFINITY){
        (value as number) = this._defaultValue;
      }
      if(value === this._defaultValue){
        this._range.resetRange();
      }else{
        if(value < this._minValue){
          (value as number) = this._minValue;
        } else if(value > this._maxValue){
          (value as number) = this._maxValue;
        }
      }
      this._value = value;
      
      if (this._htmlElement != undefined) {
        (this._htmlElement as HTMLInputElement).value = value.toString();
      }
      if (hasToUpdateUser) {
        await UserController.current.updateUserNumber(
          this._name,
          this._value,
          this._value === this._defaultValue,
        );
      }
      if (hasToHandleOnChange) {
        await this.handleOnChange();
      }
      this.generalToggleBehavior(
        this._name,
        value != this._defaultValue,
        hasToEnableActiveInput,
      );      
    }
  }

  /**
   * @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,
    );
    if(typeof this._value === "number"){
      if(this._htmlElement == undefined){
        this._cursorPosition = ((this._value-this._minValue)*100)/(this._maxValue-this._minValue);
        this._attributes.set("style", `--cursor-position: ${this._cursorPosition}%;`);
      } else {
        if(this._value === this._defaultValue){
          this._htmlElement.setAttribute("style", `--cursor-position: ${this._defaultCursorPosition}%;`);
        } else {
          this._cursorPosition = ((((this._value - this._minValue) / (this._maxValue - this._minValue)) * (this._sliderWidth - 44)) + 44 / 2) / this._sliderWidth * 100;
          this._htmlElement.setAttribute("style", `--cursor-position: ${this._cursorPosition}%;`);
        }
      }
    }
  }

  /**
   * 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} range - Parent range
   * @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,
    range: CustomRange,
    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._range = range;
    this._cursorPosition = ((this._defaultValue-this._minValue)*100)/(this._maxValue-this._minValue);
    this._defaultCursorPosition = this._cursorPosition;
    this._sliderWidth = 0;
    this._eventListeners.push({
      type: "input",
      handler: (event: unknown) => {
        if (event instanceof Event) {
          const eventTarget = (event.target as HTMLInputElement).type === "range" ? (event.target as HTMLInputElement) : (event.target as HTMLInputElement).querySelector("input");
          if (eventTarget!.value === "") {
            eventTarget!.value =
              this._defaultValue.toString();
          }
          handler(event);
          this.toggle<number>(
            parseFloat(eventTarget!.value),
            true,
            true,
            parseFloat(eventTarget!.value) !== this._defaultValue,
          );
        }
      },
    });
  }
}

export { RangeInput };
