import { bindable, bindingMode, computedFrom } from "aurelia-framework";
import { UIDrop } from "../shared/ui-drop";
import { UIInternal } from "../../utils/ui-internal";
import { KeyboardShortcut } from "../../constants/constants";

export class BaseInput {
  public value: AnyObject;

  public inputEl: HTMLInputElement | HTMLTextAreaElement;

  @bindable()
  protected errors: string | string[];
  @bindable()
  protected maxlength: number = 0;
  @bindable()
  public allowClear: boolean = false;
  @bindable
  protected readonly: string | boolean = false;
  @bindable({ defaultBindingMode: bindingMode.twoWay })
  protected disabled: string | boolean = false;

  @bindable()
  public blurFn: () => {} = undefined;
  @bindable()
  public onFocusFn: () => {} = undefined;
  @bindable()
  public onKeyPressedFn: ($event: KeyboardEvent) => {} = undefined;

  protected isDisabled: string | boolean = false;
  protected showCounter: boolean = false;

  protected dropEl: UIDrop;

  constructor(protected element: Element) {
    this.showCounter = element.hasAttribute("counter");
  }

  public focus() {
    this.inputEl.focus();
  }

  protected blur() {
    if (typeof this.blurFn == "function" && this.blurFn != null) {
      this.blurFn();
    }
  }

  protected onFocus() {
    if (typeof this.onFocusFn == "function" && this.onFocusFn != null) {
      this.onFocusFn();
    }
  }

  public disable(b: boolean): void {
    this.isDisabled = b;
  }

  @computedFrom("isDisabled", "disabled", "readonly", "errors", "errors.length")
  get classes(): string {
    const classes = [];
    if (this.errors && this.errors.length > 0) {
      classes.push("ui-input--invalid");
      this.element.dispatchEvent(UIInternal.createEvent("input-invalid"));
    }
    else {
      // field was in error but is now valid
      classes.push("ui-input--valid");
      this.element.dispatchEvent(UIInternal.createEvent("input-valid"));
    }
    if (this.isTrue("readonly")) {
      classes.push("ui-input--readonly");
      this.element.dispatchEvent(UIInternal.createEvent("input-readonly"));
    } else {
      this.element.dispatchEvent(UIInternal.createEvent("input-not-readonly"));
    }
    if (this.isTrue("disabled") || this.isDisabled) {
      classes.push("ui-input--disabled");
      this.element.dispatchEvent(UIInternal.createEvent("input-disabled"));
    } else {
      this.element.dispatchEvent(UIInternal.createEvent("input-not-disabled"));
    }
    return classes.join(" ");
  }

  protected bind(bindingContext?: Object, overrideContext?: any): void {
    this.allowClear = this.isTrue("allowClear") || this.element.hasAttribute("allow-clear.trigger");
    this.readonly = this.isTrue("readonly");
    this.disabled = this.isTrue("disabled");
  }
  protected clear(): void {
    this.value = "";
    this.inputEl.focus();
    this.element.dispatchEvent(UIInternal.createEvent("clear"));
    this.element.dispatchEvent(UIInternal.createEvent("change"));
  }

  protected keyPressed($event: KeyboardEvent): boolean {
    if (typeof this.onKeyPressedFn == "function" && this.onKeyPressedFn != null) {
      this.onKeyPressedFn($event);
    }

    if ($event.key === KeyboardShortcut.Enter) {
      $event.stopEvent();
      this.element.dispatchEvent(UIInternal.createEvent("enterpressed", this.value));
    }
    return true;
  }

  protected canToggleDrop(evt: FocusEvent): void {
    if (evt.relatedTarget && evt.relatedTarget !== this.inputEl) {
      this.toggleDrop(false);
    }
  }

  protected toggleDrop(open?: boolean): boolean {
    if (open === true && this.dropEl.isOpen) {
      UIInternal.queueMicroTask(() => this.dropEl.updatePosition());
      return;
    }
    const beforeEvent = this.dropEl.isOpen && !open ? "beforeclose" : "beforeopen";
    const afterEvent = this.dropEl.isOpen && !open ? "close" : "open";
    if (this.element.dispatchEvent(UIInternal.createEvent(beforeEvent)) !== false) {
      this.dropEl.toggleDrop(open);
      this.element.dispatchEvent(UIInternal.createEvent(afterEvent));
      if (this.dropEl.isOpen) {
        this.inputEl.select();
        return true;
      } else {
        return false;
      }
    }
  }

  private isTrue(prop: string): boolean {
    return this[prop] === "" || this[prop] === prop || isTrue(this[prop]);
  }
}
