import { bindable, BindingEngine, bindingMode, computedFrom, customElement, observable, viewResources } from "aurelia-framework";
import { UIInternal } from "../../utils/ui-internal";
import { Config } from "../../config";
import { BaseInput } from "./base-input";
import { InputWrapper } from "./input-wrapper";

@customElement("ui-input")
@viewResources(InputWrapper)
export class UIInput extends BaseInput {
  @bindable({ defaultBindingMode: bindingMode.twoWay })
  public value: string | number;

  @bindable() public type: "text" | "number" | "url" | "email" | "password" = "text";
  @bindable() public placeholder: string = "";
  @bindable() public autocomplete: string = "";
  @bindable() public autofocus: boolean = false;
  @bindable() public inputMode: string = null;


  @bindable({ defaultBindingMode: bindingMode.fromView })
  public min: string | number;
  @bindable({ defaultBindingMode: bindingMode.fromView })
  public max: string | number;
  @bindable({ defaultBindingMode: bindingMode.fromView })
  public step: string | number = null;

  @observable
  private inputValue: string;

  constructor(public element: Element, public config: Config, public bindingEngine: BindingEngine) {
    super(element);
  }

  protected bind() {
    super.bind();
    this.valueChanged();
    this.inputMode ??= this.config.globalConfig.uiInputInputMode;
  }

  protected attached(): void {
    this.maxlengthChanged();
    this.valueChanged();
  }

  @computedFrom("inputValue", "maxlength")
  get counter() {
    if (this.maxlength) {
      return `${this.maxlength - (this.inputValue ? this.inputValue.length : 0)}`;
    } else {
      return `${this.inputValue ? this.inputValue.length : 0}`;
    }
  }

  protected maxlengthChanged(): void {
    if (this.inputEl) {
      this.inputEl.removeAttribute("maxLength");
      if (this.maxlength > 0) {
        this.inputEl.maxLength = this.maxlength;
      }
    }
  }

  private ignoreInputValueChange: number = 0;
  private ignoreValueChange: number = 0;

  private inputValueChanged() {
    if (this.value !== this.inputValue && 0 == this.ignoreInputValueChange) {
      this.ignoreValueChange++;
      if (this.type == 'number') {
        this.value = this.inputValue ? parseFloat(this.inputValue) : null;
      } else {
        this.value = this.inputValue;
      }
      UIInternal.queueTask(() => this.ignoreValueChange--);
    }
  }

  protected valueChanged() {
    if (this.value != this.inputValue && 0 == this.ignoreValueChange) {
      this.ignoreInputValueChange++;
      this.inputValue = this.value?.toString();
      UIInternal.queueTask(() => this.ignoreInputValueChange--);
    }

  }

  protected blur(): void {
    if (this.type == 'number' && (this.value || '').toString().length > 0) {
      let castedNumber = this.toNumber(this.value);
      if (this.min != null) {
        castedNumber = Math.max(castedNumber || 0, this.toNumber(this.min));
      }
      if (this.max != null) {
        castedNumber = Math.min(castedNumber || 0, this.toNumber(this.max));
      }
      if (this.step != null && this.step != 0) {
        // If min = 1 and step = 2, valid values are 1, 3, ..., hence the "normalizedNumber"
        let step = this.toNumber(this.step);
        let min = this.min != null ? this.toNumber(this.min) : 0;
        let normalizedNumber = (castedNumber || 0) - min;

        // Minimize rounding errors by converting to ints
        let nbrDecimalsInInput = normalizedNumber.toString().split(".")[1]?.length || 0;
        let nbrDecimalsInAny = Math.max(step.toString().split(".")[1]?.length || 0, nbrDecimalsInInput);
        let multiplier = Math.pow(10, nbrDecimalsInAny);
        // Multiply each part
        normalizedNumber *= multiplier;
        step *= multiplier;

        // Limit number to the steps by rounding down to the closest step
        castedNumber = Math.floor(normalizedNumber / step) * step;

        // Set decimals back to the correct position
        castedNumber = parseFloat((castedNumber / multiplier).toFixed(nbrDecimalsInInput)) + min;
      }

      if (this.value != castedNumber.toString()) {
        this.value = castedNumber.toString();
      }
    }
    super.blur();
  }

  protected focusOut(): void {
    this.resetPlaceholder();
  }

  protected mouseOut(): void {
    this.resetPlaceholder();
  }

  protected toNumber(value: string | number): number {
    return typeof value == 'string' ? parseFloat(value) : value;
  }

  private resetPlaceholder(): void {
    UIInternal.queueTask(() => {
      if (this.placeholder != this.inputEl.placeholder) {
        this.inputEl.placeholder = this.placeholder;
      }
    });
  }

  private inputModeChanged() {
    if (this.element?.children?.length > 0) {
      (this.element.children[0] as any).inputMode = this.inputMode;
    }
  }
}
