import { bindable, bindingMode, customAttribute, ViewSlot, ViewCompiler, Container, createOverrideContext, BindingEngine, autoinject, TaskQueue } from "aurelia-framework";
import 'select2';
import { Options, Translation } from 'select2';
import * as breeze from 'breeze-client';
import { GridDataSource } from "../../data/data-source";
import { Datacontext } from "../../data/data-context";
import { I18N } from "aurelia-i18n";
import { UIInternal } from "./../../utils/ui-internal";

export interface CreateOtions {
  label: string;
  fn: Function;
  enabled: boolean | Function;
  this?: Object;
}
function createCustomSelect2DataAdapter() {

  // Build dependencies
  var AjaxAdapter = jQuery.fn.select2.amd.require('select2/data/ajax');
  var Utils = jQuery.fn.select2.amd.require('select2/utils');

  function CustomArrayAdapter($element, options) {
    (CustomArrayAdapter as any).__super__.constructor.call(this, $element, options);
  };

  Utils.Extend(CustomArrayAdapter, AjaxAdapter);

  // Add sorting
  // CustomArrayAdapter.prototype.current = function (callback) {

  //     var data = [];

  //     this.$element.find(':selected').each(jQuery.proxy(function(i, element) {

  //         var $option = jQuery(element);
  //         var option = this.item($option);

  //         data.push(option);
  //     }, this));

  //     // Sort by addedOn timestamp
  //     data = data.sort(function(a, b) {
  //         return a._addedOn - b._addedOn;
  //     });

  //     callback(data);
  // };


  CustomArrayAdapter.prototype.select = function (data) {
    var $option = this.$element.find('option').filter(function (i, elm) {
      return elm.value == data.id.toString();
    });

    if ($option.length === 0) {
      $option = this.option(data);

      this.addOptions($option);
    }

    (CustomArrayAdapter as any).__super__.select.call(this, data);
  };
  // Remove timestamp
  CustomArrayAdapter.prototype.unselect = function (data) {

    data._addedOn = undefined;

    return (CustomArrayAdapter as any).__super__.unselect.call(this, data);
  };

  return CustomArrayAdapter;
}
@customAttribute("select2")
@autoinject
export class Select2Attribute {
  @bindable({ defaultBindingMode: bindingMode.twoWay }) value: any;
  @bindable({ defaultBindingMode: bindingMode.oneTime }) dataSource: GridDataSource<breeze.Entity> = null;
  @bindable({ defaultBindingMode: bindingMode.oneTime }) optionsValue: string;
  @bindable({ defaultBindingMode: bindingMode.oneTime }) optionsText: string;
  @bindable({ defaultBindingMode: bindingMode.oneTime }) createOptions: CreateOtions;
  @bindable({ defaultBindingMode: bindingMode.oneTime }) sortColumn: string;
  @bindable({ defaultBindingMode: bindingMode.oneTime }) filterProperties: string;
  @bindable({ defaultBindingMode: bindingMode.oneTime }) templateResultFn: (p: any) => string;

  private select: JQuery<Element>;
  private select2Container: JQuery;
  private observer: MutationObserver;
  private pageSize: number = 25;

  constructor(private element: Element, private viewCompiler: ViewCompiler, private container: Container, private datacontext: Datacontext, private i18n: I18N, private bindingEngine: BindingEngine, private taskQueue: TaskQueue) { }
  attached() {
    if (this.select) {
      return;
    }
    var options: Options = {};
    this.fillOptions(options);
    options.language = <Translation>{
      errorLoading: () => this.i18n.tr("select2.errorLoading", { ns: "common" }),
      inputTooShort: () => this.i18n.tr("select2.inputTooShort", { ns: "common" }),
      loadingMore: () => this.i18n.tr("select2.loadingMore", { ns: "common" }),
      noResults: () => this.i18n.tr("select2.noResults", { ns: "common" }),
      searching: () => this.i18n.tr("select2.searching", { ns: "common" })
    };
    this.select = $(this.element).select2(options);
    this.select2Container = this.select.data("select2").$container;

    var $el = $(this.element);
    var $containerSelection = $el.data("select2").$selection;

    if (this.value != null && Object.keys(this.value).length > 0 && Object.keys(this.value).indexOf('id') > -1) {
      this.select.val(this.value.id + "").trigger("change");
      //this.select.val(this.value).trigger("change");
    }
    else {
      this.select.val(this.value).trigger("change");
    }
    if (Array.isArray(this.value)) {
      let observer = this.bindingEngine.collectionObserver(this.value);
      observer.subscribe(() => {
        this.select?.val(this.value).trigger("change");
      });
    }

    this.synchronizeClasses();
    this.select.on("select2:unselect", (event) => {
      if (event.originalEvent) {
        return;
      }
      var data = event.params.data;
      var targetHTMLElt = <HTMLInputElement>event.target;

      if (Array.isArray(this.value)) {
        // select multiple
        if (this.value.some(x => x == data.id)) {
          var index: number = this.value.findIndex(x => x == data.id);
          this.value.splice(index, 1);
        }
      }
      else {
        // select classic

        if (this.value == data.id) {
          this.value = null;
        }
        else if (Object.keys(this.value).length > 0 && Object.keys(this.value).indexOf('id') > -1 && this.value.id == data.id) {
          // we are binded to a dataformat object
          this.value = null;
        }
      }
      window.dispatchEvent(UIInternal.createEvent("select2:change"));
    });
    this.select.on("select2:select", (event) => {
      if (event.originalEvent) {
        return;
      }

      if (Array.isArray(this.value)) {
        let data = event.params.data;
        // select multiple
        this.value.push(data.id);
      }
      else {
        // select classic
        var targetHTMLElt = <HTMLInputElement>event.target;
        // Check if we are binded to a dataformat object
        if (this.value != null && Object.keys(this.value).length > 0 && Object.keys(this.value).indexOf('id') > -1) {
          this.value = { id: event.params.data.id.toString(), text: event.params.data.text };
        }
        else {
          if (this.value != targetHTMLElt.value) {
            var numberValue = Number(targetHTMLElt.value);
            if (!Number.isNaN(numberValue))
              this.value = numberValue;
            else
              this.value = targetHTMLElt.value;
          }
        }
      }
      window.dispatchEvent(UIInternal.createEvent("select2:change"));
    });

    $(document).on('focus', '.select2-selection.select2-selection--single', function (e) {
      $(this).closest(".select2-container").siblings('select:enabled').select2('open');
    });
    this.select.on('select2:closing', function (e) {
      $(e.target).data("select2").$selection.one('focus focusin', function (e) {
        e.stopPropagation();
      });
    });
    this.select.on('select2:open', function (e) {
      if (e.currentTarget.tagName.toUpperCase() == 'SELECT') {
        $('.select2-search--dropdown input.select2-search__field').get(0)?.focus();
        $('.select2-search--dropdown input.select2-search__field').trigger('focus');
        setTimeout(() => {
          $('.select2-search--dropdown input.select2-search__field').get(0)?.focus();
          $('.select2-search--dropdown input.select2-search__field').trigger('focus');
        }, 0);
      }
    });
    this.startClassesSyncObserver();

    if (this.createOptions != null) {
      let select2Instance = this.select.data("select2");
      let $dropdown = select2Instance.$dropdown.children().first();
      this.removeFn = this.insert($dropdown.get(0), "<template><span click.trigger='addFn()' class='select2-create-button' if.bind='canAdd'><ui-icon icon='digi-plus-circle'></ui-icon><span t='" + this.createOptions.label + "'></span></span></template>", this);
    }
  }

  get canAdd() {
    if (this.createOptions.enabled == null) return true;

    if (this.createOptions.enabled === false || this.createOptions.enabled === true) return this.createOptions.enabled;

    return this.createOptions.enabled();
  }

  removeFn: () => void;

  addFn() {
    $(this.element).select2("close");
    if (this.createOptions.this == null) {
      this.createOptions.this = (<any>this.element).au.select2.scope.bindingContext;
    }
    this.createOptions.fn.call(this.createOptions.this);
  }

  insert(containerElement, html, viewModel) {
    let viewFactory = this.viewCompiler.compile(html);
    let view = viewFactory.create(this.container);
    let anchorIsContainer = true;
    let viewSlot = new ViewSlot(containerElement, anchorIsContainer);
    viewSlot.add(view);
    view.bind(viewModel, createOverrideContext(viewModel));
    return () => {
      viewSlot.remove(view);
      view.unbind();
    };
  }
  valueChanged(newVal, oldVal) {
    if (this.select) {
      $(this.element).find('*').each(function () {
        // Supprimer tous les custom data set créés par select2 car ils ne sont pas toujours en phase avec la réalité (ex : ajout via modal dialog)
        $.removeData(this, 'data');
      });
      if (this.value != null && Object.keys(this.value).length > 0 && Object.keys(this.value).indexOf('id') > -1) {
        this.taskQueue.queueMicroTask(() => {
          this.select.val(newVal.id).trigger("change");
        });

      }
      else {
        if (newVal == null) this.select.val('').trigger("change");
        else if (newVal == "0") {
          this.select.val(null).trigger("change");
        } else {
          this.select.val(newVal).trigger("change");
        }
      }
    }
  }

  detached() {
    if (this.removeFn != null) {
      this.removeFn();
    }
    if (this.select) {
      this.select.off("change");
      this.select.off('select2:select');
      this.select.off('select2:unselect');
      this.select.off('select2:open');
      this.select.select2("destroy");
    }
    this.select = null;

    if (this.observer != null) {
      this.observer.disconnect();
      this.observer = null;
    }
  }

  addHiddenDivToBody() {
    var div = document.createElement("div");
    div.style.display = "none";
    document.body.appendChild(div);
    return div;
  }

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

  }

  fillOptions(options: Options) {
    if (this.dataSource != null) {
      var ds = this.dataSource;
      var templateSelectionFunction = null;
      var minimumInputLength = 0;

      $.extend(options, {
        ajax: {
          dataType: 'json',
          delay: 250,
          data: function (params) {
            return {
              q: params.term, // search term
              page: params.page
            };
          },
          processResults: (data: any, page) => {
            var cleanData = data.results.map(item => {
              if (this.optionsValue != null && this.optionsText != null) {
                return {
                  id: item[this.optionsValue],
                  text: this.getValue(item, this.optionsText),
                  data: item
                }
              }
            });
            // parse the results into the format expected by Select2.
            // since we are using custom formatting functions we do not need to
            // alter the remote JSON data
            //if (cleanData === []) debugger;
            return {
              results: cleanData,
              pagination: {
                more: data.more
              }
            };
          },
          transport: (params, success, failure) => {
            var fail = () => { };
            let currentPage = params.data.page != null ? params.data.page : 1;
            let startRow = (currentPage - 1) * this.pageSize;
            let endRow = startRow + this.pageSize;

            var callBack = (rowsThisPage: any[], lastRow?: number) => {
              let data = {
                results: rowsThisPage,
                more: lastRow > endRow
              }
              success(data);
            };
            var p: any = {
              startRow: startRow,
              endRow: endRow,
              successCallback: callBack,
              failCallback: failure,
              sortModel: [
                { colId: this.replaceTranslation(this.optionsText), sort: 'ASC' }
              ]

            };
            let sortModel = this.getSortModelFromSortColumn(this.sortColumn);
            if (sortModel != null && sortModel.length > 0) {
              p.sortModel = sortModel;
            }
            ds.getRowsForSelect2(p, params.data.q, this.evalFilterProperties);

            return ds;
          }
        },
        escapeMarkup: function (markup) { return markup; }, // let our custom formatter work
        minimumInputLength: minimumInputLength
        , dataAdapter: createCustomSelect2DataAdapter()
      });

      if (this.templateResultFn != null) {
        options.templateResult = this.templateResultFn;
      }
      if (templateSelectionFunction != null) {
        options.templateSelection = templateSelectionFunction;
      }

    }
  }

  private get evalFilterProperties(): string[] {
    if (this.filterProperties != null && this.filterProperties.length > 0) {
      let filterProperties = eval(this.filterProperties) as string[];
      return filterProperties.map(x => this.replaceTranslation(x));
    }
    return [];
  }

  getSortModelFromSortColumn(sortColumn: string) {
    if (sortColumn == null || sortColumn.length == 0) return null;
    let sortModel = [];
    if (sortColumn.indexOf(',') > -1) {
      var sortColumns = sortColumn.split(',');
      sortColumns.forEach(col => {
        sortModel.push({
          colId: this.replaceTranslation(col),
          sort: this.getSortDirection(col)
        });
      });
    }
    else {
      sortModel.push({
        colId: this.replaceTranslation(sortColumn),
        sort: this.getSortDirection(sortColumn)
      });
    }
    return sortModel;
  }
  getSortDirection(col: string) {
    if (col.indexOf(' ') > -1) {
      return col.split(' ')[1];
    }
    else return 'ASC';
  }
  replaceTranslation(col: string) {
    if (col.indexOf('_translation') > -1) {
      return col.replace('_translation', this.datacontext.translatePropertyName);
    }
    return col;
  }

  synchronizeClasses() {
    var sourceClassList = this.element.classList;
    var containerClassList = this.select2Container.get(0).classList;


    for (let i = 0; i < containerClassList.length; i++) {
      var className = containerClassList[i];
      if (!className.startsWith("select2")) {
        if (!sourceClassList.contains(className)) {
          this.select2Container.removeClass(className);
        }
      }
    }
    for (let i = 0; i < sourceClassList.length; i++) {
      var className = sourceClassList[i];
      if (!className.startsWith("select2")) {
        if (!containerClassList.contains(className)) {
          this.select2Container.addClass(className);
        }
      }
    }
  }
  startClassesSyncObserver() {
    this.observer = new MutationObserver((mutations) => {
      mutations.forEach((mutation) => {
        if (mutation.attributeName == "class") {
          this.synchronizeClasses();
        }
      });
    });
    var config = { attributes: true };
    this.observer.observe(this.element, config);
  }
}
