/**
 * @author    : Adarsh Pastakia
 * @version   : 5.0.0
 * @copyright : 2019
 * @license   : MIT
 */

import { EventAggregator, Subscription } from "aurelia-event-aggregator";
import {
  BindingEngine,
  Container,
  DOM,
  Task,
  TaskQueue,
  View,
  ViewCompiler,
  ViewResources
} from "aurelia-framework";
import { I18N } from "aurelia-i18n";

export namespace UIInternal {
  export const EVT_VIEWPORT_WHEEL = "EVT_VIEWPORT_WHEEL";
  export const EVT_VIEWPORT_CLICK = "EVT_VIEWPORT_CLICK";
  export const EVT_VIEWPORT_RESIZE = "EVT_VIEWPORT_RESIZE";
  export const EVT_AUTH_CHANGE = "EVT_AUTH_CHANGE";
  export const EVT_APP_READY = "EVT_APP_READY";
  export const EVT_PAGINATION_CHANGE = "EVT_PAGINATION_CHANGE";
  export const EVT_SCHEDULER_REFRESHED = "EVT_SCHEDULER_REFRESHED";
  export const EVT_VIEWPORT_CSS_BREAKPOINT_CHANGE = "EVT_VIEWPORT_CSS_BREAKPOINT_CHANGE";
  export const EVT_VIEWPORT_SMALL_DEVICE_ACTIVATED = "SMALL_DEVICE_ACTIVATED";
  export const EVT_UI_TABLE_BODY_CELL_CHANGED = "EVT_UI_TABLE_BODY_CELL_CHANGED";
  export const EVT_UI_TABLE_REFRESH = "EVT_UI_TABLE_REFRESH";
  export const EVT_CHANGE_LANGUAGE = "EVT_CHANGE_LANGUAGE";
  export const EVT_ROUTE_CHANGE = "EVT_ROUTE_CHANGE";
  export const EVT_GLOBAL_LOADER_SHOW = "EVT_GLOBAL_LOADER_SHOW";
  export const EVT_GLOBAL_LOADER_HIDE = "EVT_GLOBAL_LOADER_HIDE";
  export const EVT_UI_TABLE_PAGINATION_RELOAD = "EVT_UI_TABLE_PAGINATION_RELOAD";

  // Task queue shortcut
  export function queueTask(task: () => AnyObject | Task): void {
    Container.instance.get(TaskQueue).queueTask(task);
  }
  export function queueMicroTask(task: () => AnyObject | Task): void {
    Container.instance.get(TaskQueue).queueMicroTask(task);
  }

  // Event aggregation shortcut
  export function broadcast(name: string, data?: AnyObject): void {
    Container.instance.get(EventAggregator).publish(name, data);
  }
  export function subscribe(name: string, callback: AnyObject): Subscription {
    return Container.instance.get(EventAggregator).subscribe(name, callback);
  }
  export function subscribeOnce(name: string, callback: AnyObject): Subscription {
    return Container.instance.get(EventAggregator).subscribeOnce(name, callback);
  }
  export function observe(data: AnyObject, property: string, callback: AnyObject): Subscription {
    return Container.instance
      .get(BindingEngine)
      .propertyObserver(data, property)
      .subscribe(callback);
  }

  export function createEvent<T = {}>(name: string, data?: T): CustomEvent {
    return DOM.createCustomEvent(name, {
      bubbles: true,
      cancelable: true,
      detail: data
    });
  }

  export function fireCallbackEvent(
    vm: AnyObject,
    event: string,
    data?: AnyObject
  ): Promise<boolean> {
    const ret = isFunction(vm[event])
      ? vm[event](data)
      : vm.element.dispatchEvent(UIInternal.createEvent(event, data));
    if (ret instanceof Promise) {
      return ret;
    } else {
      return Promise.resolve(ret !== false);
    }
  }

  export function invokeLifecycle(instance, name, model?): Promise<AnyObject> {
    if (instance && typeof instance[name] === "function") {
      const result = instance[name](model);
      if (result instanceof Promise) {
        return result;
      }
      if (result !== null && result !== undefined) {
        return Promise.resolve(result);
      }
      return Promise.resolve(true);
    }
    return Promise.resolve(true);
  }

  export function compileTemplate(tpl: string, viewModel: AnyObject = {}, bindingContext = {}): View {
    const viewFactory = Container.instance
      .get(ViewCompiler)
      .compile(tpl, Container.instance.get(ViewResources));
    const view = viewFactory.create(Container.instance);
    view.bind(viewModel, bindingContext);
    return view;
  }

  export function getValue(item: any, propertyPath: string): any {
    if (!item) {
      return null;
    }
    let sepIndex = propertyPath.indexOf('.');
    if (sepIndex > -1) {
      var leftPart = propertyPath.substring(0, sepIndex);
      var rightPart = propertyPath.substring(sepIndex + 1);
      return this.getValue(item[leftPart], rightPart)
    } else {
      return item[propertyPath];
    }
  }

  export function nextTick(fn: Function) {
    // let's time to aurelia dirty checking mecahnism to read the translated property (normally each 120ms)
    setTimeout(() => fn(), 150);
  }

  export function debounce<F extends Function>(func: F, wait: number): F {
    let timeoutID: number;
    if (!Number.isInteger(wait)) {
      console.warn("Called debounce without a valid number")
      wait = 300;
    }

    // conversion through any necessary as it wont satisfy criteria otherwise
    return <any>function (this: any, ...args: any[]) {
      clearTimeout(timeoutID);
      const context = this;

      timeoutID = window.setTimeout(function () {
        func.apply(context, args);
      }, wait);
    };
  }

  export async function autoDetectAndSetLocale(i18n: I18N) {
    try {
      return await i18n.setLocale(navigator.language.replace(/-.*$/, ''));
    } catch {
      return await i18n.setLocale(i18n.i18next.options.fallbackLng[0]);
    }
  }
}
