import {
  autoinject,
  bindable,
  bindingMode,
  children,
  customElement,
  DOM,
  View
} from "aurelia-framework";
import ResizeObserver from "resize-observer-polyfill";
import { calculateOverflow } from "../../utils/ui-common";
import { UIInternal } from "../../utils/ui-internal";

export interface UITabConfig {
  id?: string;
  icon?: string;
  label?: string;
  tooltip?: string;
  active?: boolean;
  disabled?: boolean;
  closeable?: boolean;

  href?: string;
  view?: AnyObject;
  model?: AnyObject;
  element?: Element;
  btnEl?: Element;
  viewModel?: AnyObject;

  classes?: string;
  styleObject?: AnyObject;
}

@autoinject()
@customElement("ui-tab-panel")
export class UITabPanel {
  @bindable()
  public tabs: UITabConfig[] = [];
  @bindable({ bindingMode: bindingMode.twoWay })
  public active: string;

  @bindable()
  public align: "top" | "bottom" = "top";

  @bindable()
  public beforechange: () => Promise<boolean> | boolean;
  @bindable()
  public beforeclose: () => Promise<boolean> | boolean;

  @children("ui-tab")
  protected innerTabs: UITabConfig[];

  @bindable({ bindingMode: bindingMode.toView })
  protected activeTab: UITabConfig;

  @bindable({ bindingMode: bindingMode.oneTime })
  protected isDragAndDrop: boolean = true;
  @bindable()
  protected dragDropFunction: ({ }) => void | Promise<void> | boolean | Promise<boolean>;

  private owningView: AnyObject;
  private composeVm: AnyObject;

  private wrapperEl: Element;
  private menuEl: Element;
  private overflowEl: Element;

  private hasOverflow: boolean = false;
  private obResize: ResizeObserver;

  private isAttached: boolean = false;

  constructor(protected element: Element) {
    if (element.hasAttribute("no-border")) {
      element.classList.add("ui-tab__panel--noborder");
    }
  }

  public async activateTab(id: string): Promise<boolean> {
    let result = true;
    if (this.composeVm.currentViewModel) {
      result = await UIInternal.invokeLifecycle(this.composeVm.currentViewModel, "canDeactivate");
    }
    if (result) {
      return UIInternal.fireCallbackEvent(this, "beforechange", {
        activeTab: this.activeTab.id,
        activeViewModel: this.composeVm.currentViewModel,
        newTab: id
      }).then(b => (b ? this.activate(id) : undefined));
    } else {
      return Promise.resolve(false);
    }
  }

  public async closeTab(id: string): Promise<boolean> {
    const tab = this.tabs.find(t => t.id === id);

    let result = true;
    if (this.activeTab.id === id && this.composeVm.currentViewModel) {
      result = await UIInternal.invokeLifecycle(this.composeVm.currentViewModel, "canDeactivate");
    }
    if (result) {
      return UIInternal.fireCallbackEvent(this, "beforeclose", tab.id).then(b =>
        b ? this.remove(id) : false
      );
    } else {
      return Promise.resolve(false);
    }
  }

  // Set current owningView
  protected created(owningView: View) {
    this.owningView = owningView;
  }

  // Update compose owningView and viewResource to current owningView
  protected attached(): void {
    this.composeVm.owningView = this.owningView;
    this.composeVm.viewResources = this.owningView.resources;

    this.obResize = new ResizeObserver(() => this.calculateOverflow());
    this.obResize.observe(this.element);

    this.isAttached = true;
    this.tabsChanged();
  }

  protected detached(): void {
    this.obResize.disconnect();
  }

  protected innerTabsChanged(): void {
    this.tabs = this.innerTabs || this.tabs;
    this.tabsChanged();
  }

  protected tabsChanged(): void {
    if (this.isAttached) {
      this.active = (this.tabs.find(tab => tab.active) || {}).id;
      if (!this.active) {
        this.activeTab = this.tabs.find(tab => !tab.disabled) || {};
        this.active = this.activeTab.id;
        this.activeTab.active = true;
      }
      UIInternal.queueTask(() => this.calculateOverflow());
    }
  }

  protected activate(id: string): boolean {
    const newTab = this.tabs.find(tab => tab.id === id);
    if (newTab && !newTab.disabled) {
      this.element.dispatchEvent(UIInternal.createEvent("change", id));

      if (this.activeTab) {
        this.activeTab.active = false;
      }
      this.activeTab = newTab;
      this.active = this.activeTab.id;
      this.activeTab.active = true;
      return true;
    }
    return false;
  }

  protected remove(id: string): boolean {
    const tab = this.tabs.find(t => t.id === id);
    this.element.dispatchEvent(UIInternal.createEvent("close", id));
    this.menuEl.innerHTML = "";
    UIInternal.queueTask(() => {
      this.tabs = [...this.tabs.splice(this.tabs.indexOf(tab), 1)];
    });
    if (tab.element) {
      UIInternal.queueTask(() => DOM.removeNode(tab.element));
    }
    return true;
  }

  protected calculateOverflow(): void {
    this.hasOverflow = calculateOverflow(this.wrapperEl, this.menuEl);
  }

  private lastPreview: HTMLElement;

  protected drag(event: DragEvent, tab: UITabConfig) {
    if (this.isDragAndDrop) {
      event.dataTransfer.setData("text", tab.id);
    }
  }

  protected ondragenter(event: DragEvent, tab: UITabConfig = null) {
    if (this.isDragAndDrop) {
      event?.preventDefault();
      event?.stopImmediatePropagation();

      this.ondragleave(event, tab, true);
      this.lastPreview = document.createElement("div");
      this.lastPreview.className = "ui-tab__preview";
      if (tab != null) {
        this.wrapperEl.insertBefore(this.lastPreview, tab.btnEl);
      }
      else {
        this.wrapperEl.insertBefore(this.lastPreview, this.overflowEl);
      }
    }
  }

  protected ondragleave(event: DragEvent, tab: UITabConfig = null, all: boolean = false) {
    if (this.isDragAndDrop) {
      event?.preventDefault();
      event?.stopImmediatePropagation();

      let previews = this.wrapperEl.getElementsByClassName("ui-tab__preview");
      if (previews.length > 0) {
        for (const preview of Array.from(previews)) {
          if (all || preview != this.lastPreview) {
            preview.remove();
          }
        }
      }
    }
  }

  protected ondragover(event: DragEvent, tab: UITabConfig = null) {
    event?.preventDefault();
    return true;
  }

  protected async drop(event: DragEvent, tab: UITabConfig = null) {
    if (this.isDragAndDrop) {
      event?.preventDefault();
      event?.stopImmediatePropagation();

      this.ondragleave(event, tab, true);
      let dataId = event.dataTransfer.getData("text");
      let tabMoving = this.innerTabs.find(x => x.id == dataId);
      let tabMovingIndex = this.innerTabs.indexOf(tabMoving);
      let tabIndex = 0;
      if (tab != null) {
        tabIndex = this.innerTabs.indexOf(tab);
        if (tabIndex > tabMovingIndex) {
          tabIndex--;
        }
      }
      else {
        tabIndex = this.innerTabs.length - 1;
      }

      if (tabMovingIndex >= 0 && tabIndex >= 0 && tabMovingIndex != tabIndex) {
        this.innerTabs.splice(tabMovingIndex, 1);
        this.innerTabs.splice(tabIndex, 0, tabMoving);
        if (this.dragDropFunction != null) {
          await this.dragDropFunction({ record: tabMoving.model, oldVal: tabMovingIndex, newVal: tabIndex });
        }
      }
    }
  }
}