import { Subscription, EventAggregator } from 'aurelia-event-aggregator';
import { UIDrop } from './../shared/ui-drop';
import { UIInput } from './../forms/ui-input';
import { DatePickerTranslations, Internationalization } from './../../constants/constants';
import { bindable, BindingEngine, bindingMode, computedFrom, customElement, Disposable, viewResources } from "aurelia-framework";
import { endOfDay, endOfMonth, endOfWeek, isBefore, isValid, startOfDay, startOfMonth, startOfWeek } from "date-fns";
import { UIFormat } from "../../utils/ui-format";
import { CalendarHead } from "./calendar-head";
import { buildHeaderConfig, CALENDAR_VIEWS, changeMonth, getTitle, IRangePreset, IHeaderConfig, parseDate } from "./calendar-utils";
import { DaysPage } from "./days-page";
import { MonthsPage } from "./months-page";
import { TimePage } from "./time-page";
import { YearsPage } from "./years-page";
import { I18N } from 'aurelia-i18n';
import moment from 'moment';
import { DateUtils } from '../../utils/date-utils';
import { UIInternal } from '../../utils/ui-internal';
import { Config } from '../../config';
import { BaseInput } from './../forms/base-input';

export enum UIRangePickerSelectionMode {
  Day = "day",
  Week = "week",
  Month = "month",
  MonthToMonth = "monthToMonth"
}

@customElement("ui-range-picker")
@viewResources(CalendarHead, DaysPage, MonthsPage, YearsPage, TimePage)
export class UIRangePicker extends BaseInput {
  private inputElement: UIInput;
  private dropRange: UIDrop;

  @bindable({ defaultBindingMode: bindingMode.twoWay }) public dateFrom: Date | undefined;
  @bindable({ defaultBindingMode: bindingMode.twoWay }) public dateTo: Date | undefined;
  public dateLabel: string | undefined;
  @bindable({ defaultBindingMode: bindingMode.oneTime }) public selection: UIRangePickerSelectionMode = UIRangePickerSelectionMode.Day;
  @bindable() public minDate: string | undefined;
  @bindable() public maxDate: string | undefined;
  @bindable() public format: string = "DD/MM/yyyy";
  @bindable() public displayWeek: boolean;
  @bindable() private useOnAgGrid: boolean = true;
  @bindable() private attachToViewport: boolean = true;
  @bindable({ defaultBindingMode: bindingMode.twoWay }) public presets: IRangePreset[] = [];
  @bindable({ defaultBindingMode: bindingMode.twoWay }) public selectedPreset: IRangePreset = null;
  @bindable() private autosetWhenOpened: boolean = true;
  @bindable() public placeholder: string = "";

  private translations: DatePickerTranslations = Internationalization.datePickerTranslationsEN;
  protected month = startOfMonth(new Date());
  protected currentPage = CALENDAR_VIEWS.DAYS;
  protected VIEWS = CALENDAR_VIEWS;
  protected hilight: Date;
  protected selecting: Date;
  private selectedDate: [Date, Date] = [null, null];
  protected weekStartsOn: 0 | 1 | 2 | 3 | 4 | 5 | 6 = 1;
  private changeLanguageSubscription: Subscription;
  private disposables: Array<Disposable>;

  constructor(element: Element, private i18n: I18N, private eventAggregator: EventAggregator, private dateUtils: DateUtils, private bindingEngine: BindingEngine, private config: Config) {
    super(element);
    (element as HTMLElement).focus = () => this.inputElement.focus();
    this.disposables = new Array<Disposable>();
    this.displayWeek = this.config.globalConfig.displayWeekPicker;
  }

  protected bind() {
    super.bind();
    if (this.dateFrom != undefined && this.dateTo != undefined && this.dateUtils.isAfter(moment(this.dateFrom), moment(this.dateTo))) throw Error("DateFrom cannot be after dateTo");

    if (this.selection == UIRangePickerSelectionMode.Week && !this.displayWeek) {
      this.displayWeek = true;
    } else if (this.selection == UIRangePickerSelectionMode.MonthToMonth) {
      this.currentPage = CALENDAR_VIEWS.MONTHS;
    }

    this.changeLanguageSubscription = this.eventAggregator.subscribe(UIInternal.EVT_CHANGE_LANGUAGE, () => {
      this.changeLanguage();
    });
    this.changeLanguage();
    if (this.selectedPreset != null) {
      this.selectDateFromPreset();
    }
    this.dateChanged();

    this.disposables.push(
      this.bindingEngine.propertyObserver(this, "selectedPreset").subscribe(() => {
        if (this.selectedPreset != null) {
          this.selectDateFromPreset();
          if (this.selectedPreset.action != null) {
            this.selectedPreset.action();
          }
        }
      })
    );
  }

  private selectDateFromPreset() {
    this.dateFrom = this.dateUtils.isValid(this.selectedPreset.preset[0]) ? this.updateDate(this.selectedPreset.preset[0]) : undefined;
    this.dateTo = this.dateUtils.isValid(this.selectedPreset.preset[1]) ? this.updateDate(this.selectedPreset.preset[1]) : undefined;
  }

  protected unbind() {
    this.changeLanguageSubscription.dispose();
  }

  private changeListener = () => {
    if (this.dateLabel == null || this.dateLabel == '') {
      this.dateFrom = null;
      this.dateTo = null;
    }
    else {
      this.errors = [];
      let dateLabels = this.dateLabel.split(' - ', 2);
      if (dateLabels != null && dateLabels.length == 2) {
        let mFrom = moment(dateLabels[0], this.format, true);
        if (mFrom.isValid()) {
          this.dateFrom = this.updateDate(mFrom.toDate());
        }
        else {
          this.errors.push(this.i18n.tr("validation.invalidDate", { ns: "common" }));
          this.dateFrom = null;
        }

        let mTo = moment(dateLabels[1], this.format, true);
        if (mTo.isValid()) {
          this.dateTo = this.updateDate(mTo.toDate());
        }
        else {
          this.errors.push(this.i18n.tr("validation.invalidDate", { ns: "common" }));
          this.dateTo = null;
        }
      }
    }
  }

  protected attached() {
    if (this.allowClear) {
      this.inputElement.allowClear = true;
      this.inputElement.element.addEventListener("clear", () => this.clearSelection());
    }
    this.inputElement.element.addEventListener("change", () => this.changeListener());
  }

  protected detached() {
    if (this.disposables?.length > 0) {
      this.disposables.forEach((disposable: Disposable) => {
        disposable.dispose();
      });
    }
    this.inputElement?.element?.removeEventListener("clear", () => this.clearSelection());
    this.inputElement?.element?.removeEventListener("change", () => this.changeListener());
  }

  protected dateChanged() {
    this.dateFromChanged(false);
    this.dateToChanged(false);
    this.computeLabel();
  }

  protected dateFromChanged(computeLabel: boolean = true) {
    if (this.dateFrom == null && this.errors != null && this.errors.length == 0) {
      this.selectedDate[0] = null;
      this.selectedDate[1] = null;
      this.month = moment().startOf("month").toDate();
      this.dateLabel = null;
    }
    else {
      this.selectedDate[0] = parseDate(this.dateFrom, this.dateUtils);
      if (isValid(this.selectedDate[0])) {
        this.errors = [];
        this.month = startOfMonth(this.selectedDate[0]);
      }
    }
    if (computeLabel) {
      this.computeLabel();
    }
  }

  protected dateToChanged(computeLabel: boolean = true) {
    if (this.dateTo == null && this.errors != null && this.errors.length == 0) {
      this.selectedDate[1] = null;
      this.dateLabel = null;
    }
    else {
      this.selectedDate[1] = parseDate(this.dateTo, this.dateUtils);
      if (isValid(this.selectedDate[1])) {
        this.errors = [];
        this.month = startOfMonth(this.month);
      }
    }
    if (computeLabel) {
      this.computeLabel();
    }
  }

  public computeLabel() {
    let fromDate = this.selectedDate[0];
    let toDate = this.selectedDate[1]

    if (fromDate == null && toDate == null) {
      this.dateLabel = null;
    }
    else {
      this.dateLabel = `${this.dateUtils.isSameOrBefore(fromDate, Date.MIN_DATE) ? "..." : UIFormat.date(fromDate, this.format)} - ${this.dateUtils.isSameOrAfter(toDate, Date.MAX_DATE) ? "..." : UIFormat.date(toDate, this.format)}`;
    }
  }

  @computedFrom("selectedDate", "hilight", "selecting", "minDate", "maxDate", "disabledDates")
  get rangeConfig() {
    return {
      date: this.selecting ? ([this.selecting, this.hilight] as [Date, Date]) : this.selectedDate,
      page: this.currentPage,
      minDate: parseDate(this.minDate, this.dateUtils),
      maxDate: parseDate(this.maxDate, this.dateUtils),
      disabled: []
    };
  }

  @computedFrom("month", "currentPage", "translations")
  get title(): string {
    return getTitle(this.month, this.currentPage, this.translations, this.dateUtils);
  }

  @computedFrom("month", "currentPage", "minDate", "maxDate")
  get headerOptions(): IHeaderConfig {
    return buildHeaderConfig(this.month, this.currentPage, {
      ...this.rangeConfig,
      page: this.currentPage
    }, this.dateUtils, this.translations);
  }

  protected headerClicked($event: MouseEvent) {
    const target = $event.target as HTMLElement;
    if (target.dataset.tool) {
      if (target.dataset.tool === CalendarHead.TITLE) {
        if (this.currentPage !== CALENDAR_VIEWS.YEARS) {
          this.currentPage++;
        }
      } else {
        this.month = changeMonth(this.month, this.currentPage, target.dataset.tool, this.dateUtils);
      }
    }
  }

  protected selectDate($event: MouseEvent) {
    const target = $event.target as HTMLElement;
    if (target.dataset.date) {
      const date = new Date(target.dataset.date);
      if (this.selection == UIRangePickerSelectionMode.Week || (target.dataset.week && this.selection == UIRangePickerSelectionMode.Day)) {
        this.selectedPreset = null;
        this.dateFrom = date ? this.updateDate(startOfWeek(date, { weekStartsOn: this.weekStartsOn })) : null;
        this.dateTo = date ? this.updateDate(endOfWeek(date, { weekStartsOn: this.weekStartsOn })) : null;
      }
      else if (this.selection == UIRangePickerSelectionMode.Month) {
        this.selectedPreset = null;
        this.dateFrom = date ? this.updateDate(startOfMonth(date)) : null;
        this.dateTo = date ? this.updateDate(endOfMonth(date)) : null;
      }
      else {
        this.internalSelectDate(date, startOfDay, endOfDay);
      }
    }
  }

  protected selectMonth($event: MouseEvent) {
    const target = $event.target as HTMLElement;
    if (target.dataset.date) {
      const date = new Date(target.dataset.date);
      if (this.selection == UIRangePickerSelectionMode.MonthToMonth && this.currentPage == CALENDAR_VIEWS.MONTHS) {
        this.internalSelectDate(date, startOfMonth, endOfMonth);
      } else {
        this.month = date;
        this.currentPage--;
      }
    }
  }

  private internalSelectDate(date: Date, dateFromFn: Function, dateToFn: Function) {
    this.selectedPreset = null;
    if (this.selecting) {
      if (isBefore(date, this.selecting)) {
        this.dateFrom = this.updateDate(dateFromFn(date));
        this.dateTo = this.updateDate(dateToFn(this.selecting));
      }
      else {
        this.dateFrom = this.updateDate(dateFromFn(this.selecting));
        this.dateTo = this.updateDate(dateToFn(date));
      }
      this.selecting = null;
    }
    else {
      this.selecting = date;
    }
  }

  protected cancelSelection() {
    this.selecting = null;
    this.currentPage = CALENDAR_VIEWS.DAYS;
  }

  protected clearSelection() {
    this.selecting = null;
    this.selectedDate = [null, null];
    this.dateFrom = null;
    this.dateTo = null;
    this.dateLabel = null;
  }

  protected highlightDate($event: MouseEvent) {
    const target = $event.target as HTMLElement;
    if (target.dataset.date) {
      this.hilight = new Date(target.dataset.date);
    }
  }

  protected highlightMonth($event: MouseEvent) {
    if (this.selection == UIRangePickerSelectionMode.MonthToMonth) {
      this.highlightDate($event);
    }
  }

  protected selectPreset(preset: IRangePreset, cancelSelection: boolean = true) {
    if (cancelSelection) this.cancelSelection();
    this.selectedPreset = preset;
  }

  protected toggleUIDrop(): void {
    if (!this.autosetWhenOpened) {
      return;
    }

    if (this.dateFrom == null) {
      this.dateFrom = this.updateDate(new Date());
    }
    if (this.dateTo == null) {
      this.dateTo = this.updateDate(new Date());
    }
  }

  private changeLanguage() {
    this.translations = {
      "months": moment.months().map(x => x.charAt(0).toUpperCase() + x.substring(1)),
      "monthsShort": moment.monthsShort().map(x => x.charAt(0).toUpperCase() + x.substring(1, 3)),
      "weekdaysAbbrev": moment.weekdaysMin()
    } as any;
  }

  private updateDate(dt: Date): Date {
    var date = dt != null ? new Date(dt) : new Date();
    return this.dateUtils.setHours(date, 12);
  }
}
