import { Subscription } from 'aurelia-event-aggregator';
import { Disposable } from 'aurelia-framework';
import { UIInternal } from '../../../utils/ui-internal';
import { DateUtils } from '../../../utils/date-utils';

interface IDataSourceOptions {
  rootProperty?: string;
  dataProperty?: string;
  pageProperty?: string;
  countProperty?: string;
  sortProperty?: string;
  orderProperty?: string;
  perPageProperty?: string;

  paginated?: boolean;
  recordsPerPage?: number;

  defaultSortProperty?: string;
  defaultSortOrder?: "asc" | "desc";

  filter?: (record: KeyValue) => boolean;
}

export class UIDataSource implements Disposable {
  public isPaginated: boolean = false;
  // public pageNo: number = 0;
  // public totalPages: number = 0;
  // public recordsPerPage = 50;

  public sortByProperty: string;
  public sortByOrder: "asc" | "desc" = "asc";

  public filter: (record: KeyValue) => boolean;

  public data: KeyValue[] = [];

  private original: KeyValue[] = [];

  private options: IDataSourceOptions = {
    rootProperty: "",
    dataProperty: "data",
    pageProperty: "pageno",
    countProperty: "total",
    sortProperty: "sortBy",
    orderProperty: "sortOrder",
    perPageProperty: "pageCount"
  };

  private disposables: Subscription[];

  constructor(data: KeyValue[], options: IDataSourceOptions = {}, private dateUtils: DateUtils) {
    this.options = { ...this.options, ...options };

    //this.isPaginated = options.paginated;
    //this.recordsPerPage = options.recordsPerPage || 50;

    this.sortByProperty = options.defaultSortProperty;
    this.sortByOrder = options.defaultSortOrder || "asc";
    this.disposables = [];

    this.filter = options.filter || null;

    if (isArray(data)) {
      this.original = data;
      this.listenForDataReordering();
      this.performFilter();
    }
  }

  public listenForDataReordering() {
    this.dispose();
    // observe sortProperty to reflect change immediateley    
    this.original.forEach(d => this.disposables.push(UIInternal.observe(d, this.sortByProperty, () => {
      this.deboucePerformFilter();
    })));

  }

  public dispose(): void {
    this.disposables?.forEach(d => d.dispose());
    this.disposables.splice(0);
  }

  public sortBy(property: string, order?: "desc" | "asc") {
    if (this.sortByProperty === property) {
      this.sortByOrder = order ?? (this.sortByOrder === "asc" ? "desc" : "asc");
    } else {
      this.sortByProperty = property;
      this.sortByOrder = order ?? "asc";
      this.listenForDataReordering();
    }
    this.performFilter();
  }

  public deboucePerformFilter = UIInternal.debounce(() => {
    this.performFilter();
  }, 200);

  private performFilter() {
    let data = [...this.original];
    // Filter
    if (this.filter) {
      data = data.filter(this.filter);
    }
    // Sort
    if (this.sortByProperty) {
      data = this.sortData(data);
    }
    this.data.splice(0);
    this.data.push(...data);
  }

  private sortData(data: KeyValue[]) {
    data = data.sort((a, b) => {
      const aValue = UIInternal.getValue(a, this.sortByProperty);
      const bValue = UIInternal.getValue(b, this.sortByProperty);
      let comparison: number;
      if (this.isDateValue(aValue) || this.isDateValue(bValue)) {
        comparison = this.dateUtils.isBefore(aValue, bValue) ? -1 : 1;
      } else if (this.isNumberValue(aValue) && this.isNumberValue(bValue)) {
        comparison = parseFloat(aValue) < parseFloat(bValue) ? -1 : 1;
      } else {
        comparison = (aValue?.toString().toLowerCase() || '').localeCompare(bValue?.toString().toLowerCase() || '');
      }
      return this.sortByOrder === 'desc' ? comparison * -1 : comparison;
    });
    return data;
  }
  private isDateValue(value: any): boolean {
    return value != null && isDate(value);
  }
  private isNumberValue(value: any): boolean {
    return value != null && !isNaN(value) && isDecimal(value);
  }
}
