import { I18N } from 'aurelia-i18n';
import { EventAggregator, Subscription } from 'aurelia-event-aggregator';
import { DatePickerTranslations, Internationalization } from './../../constants/constants';
import { UIDrop } from './../shared/ui-drop';
import { UIInternal } from './../../utils/ui-internal';
import { UIInput } from './../forms/ui-input';
import { bindable, BindingEngine, bindingMode, computedFrom, customElement, Disposable, viewResources, TaskQueue } from "aurelia-framework";
import { UIFormat } from "../../utils/ui-format";
import { CalendarHead } from "./calendar-head";
import { buildHeaderConfig, CALENDAR_VIEWS, changeMonth, getTitle, IDateConfig, IDateDisabled, IDatePreset, 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 moment from 'moment';
import { DateUtils } from '../../utils/date-utils';
import { Config } from '../../config';
import { BaseInput } from './../forms/base-input';

@customElement("ui-date-picker")
@viewResources(CalendarHead, DaysPage, MonthsPage, YearsPage, TimePage)
export class UIDatePicker extends BaseInput {
  private inputElement: UIInput;
  private dropDate: UIDrop;
  private dropTime: UIDrop;

  @bindable({ defaultBindingMode: bindingMode.twoWay }) public date: Date | undefined;
  public dateLabel: string | undefined;
  @bindable() public minDate: string | undefined;
  @bindable() public maxDate: string | undefined;
  @bindable() public disabledDates: IDateDisabled;
  @bindable() public format: string;
  @bindable() public dateOnly: boolean = false;
  @bindable() public displayWeek: boolean;
  @bindable() private attachToViewport: boolean = true;
  @bindable() private closeOnSelect: boolean = true;
  @bindable() private yearOnly = false;
  @bindable() private monthOnly = false;
  @bindable onChange: (args: { date?: Date }) => void = () => { };
  @bindable({ defaultBindingMode: bindingMode.twoWay }) public presets: IDatePreset[] = [];
  @bindable({ defaultBindingMode: bindingMode.twoWay }) public selectedPreset: IDatePreset = null;
  @bindable() private autosetWhenOpened: boolean = true;
  @bindable private initDateWhenOpened: () => void = () => { };
  @bindable() public placeholder: string = "";

  //time
  @bindable() public timeOnly: boolean = false;
  @bindable() public startHour: number = 0;
  @bindable() public endHour: number = 23;
  @bindable() public hourInterval: number = 1;
  @bindable() public startMinute: number = 0;
  @bindable() public endMinute: number = 59;
  @bindable() public minuteInterval: number = 5;

  private translations: DatePickerTranslations = Internationalization.datePickerTranslationsEN;
  protected currentPage = CALENDAR_VIEWS.DAYS;
  protected month: Date = moment().startOf("month").toDate();
  protected VIEWS = CALENDAR_VIEWS;
  protected selecting: Date;
  private selectedDate: Date;
  private inputFormat: string;
  private changeLanguageSubscription: Subscription;
  private disposables: Array<Disposable>;


  constructor(element: Element, private i18n: I18N, private eventAggregator: EventAggregator, private dateUtils: DateUtils, private bindingEngine: BindingEngine, private taskQueue: TaskQueue, 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.inputFormat == null) {
      this.initFormat();
    }

    this.changeLanguageSubscription = this.eventAggregator.subscribe(UIInternal.EVT_CHANGE_LANGUAGE, () => {
      this.changeLanguage();
    });
    this.changeLanguage();
    this.dateLabel = UIFormat.datetime(this.date, this.format);
    if (this.selectedPreset != null) {
      this.date = new Date(this.selectedPreset.preset);
    }
    this.dateChanged(this.date, null);

    this.disposables.push(
      this.bindingEngine.propertyObserver(this, "selectedPreset").subscribe(() => {
        if (this.selectedPreset != null) {
          this.date = new Date(this.selectedPreset.preset);
        }
      })
    );
  }

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

  private initFormat() {
    if (this.format != null) {
      this.inputFormat = this.format.toLocaleLowerCase();
    } else {
      if (this.dateOnly) {
        this.inputFormat = "dd/mm/yyyy";
        this.format ??= "DD/MM/yyyy";
      }
      else if (this.timeOnly) {
        this.inputFormat = "HH:MM";
        this.format ??= "HH:mm";
      }
      else if (this.monthOnly) {
        this.inputFormat = "mm/yyyy";
        this.format ??= "MM/yyyy";
      }
      else if (this.yearOnly) {
        this.inputFormat = "yyyy";
        this.format ??= "yyyy";
      }
      else {
        this.inputFormat = "dd/mm/yyyy HH:MM";
        this.format ??= "DD/MM/yyyy HH:mm";
      }
    }
  }

  private clearSelectionListener() {
    this.clearSelection();
  }

  private changeListener() {
    if (this.dateLabel == null || this.dateLabel == '') {
      this.date = null;
    }
    else {
      this.errors = [];
      let m = moment(this.dateLabel, this.format);
      if (this.dateUtils.isValid(m)) {
        this.updateDate(m.toDate(), m.toDate());
      }
      else {
        this.errors.push(this.i18n.tr("validation.invalidDate", { ns: "common" }));
        this.date = null;
      }
    }
    this.taskQueue.queueMicroTask(() => {
      this.onChange({ date: this.date });
    });
  }

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

    if (this.yearOnly) {
      this.currentPage = CALENDAR_VIEWS.YEARS;
      this.dateOnly = true; // Use date only behavior to avoid bothering with time / time picker
    }
    if (this.monthOnly) {
      this.currentPage = CALENDAR_VIEWS.MONTHS;
      this.dateOnly = true; // Use date only behavior to avoid bothering with time / time picker
    }
  }

  protected detached() {
    if (this.disposables?.length > 0) {
      this.disposables.forEach((disposable: Disposable) => {
        disposable.dispose();
      });
    }

    this.inputElement?.element?.removeEventListener("clear", () => this.clearSelectionListener());
    this.inputElement?.element?.removeEventListener("change", () => this.changeListener());
  }

  protected dateChanged(newVal: Date, oldVal: Date) {
    if (newVal != oldVal) {
      if (this.date == null && this.errors != null && this.errors.length == 0) {
        this.dateLabel = null;
      }
      else {
        this.selectedDate = parseDate(newVal, this.dateUtils);
        if (this.dateUtils.isValid(this.selectedDate)) {
          this.errors = [];
          this.month = this.dateUtils.getStartOfMonth(this.selectedDate);
          this.dateLabel = UIFormat.datetime(newVal, this.format);
        }
      }
    }
  }

  @computedFrom("selectedDate", "currentPage", "minDate", "maxDate", "disabledDates")
  get dateConfig(): IDateConfig {
    return {
      date: this.selectedDate,
      page: this.currentPage,
      minDate: parseDate(this.minDate, this.dateUtils),
      maxDate: parseDate(this.maxDate, this.dateUtils),
      disabled: this.disabledDatesList
    };
  }

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

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

  get disabledDatesList() {
    if (isArray(this.disabledDates)) {
      return this.disabledDates.map(d => {
        const dt = parseDate(d, this.dateUtils);
        return !isEmpty(dt) ? this.dateUtils.getStartOfDay(dt).toISOString() : null;
      });
    }
    return this.disabledDates;
  }

  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) {
    if (!this.timeOnly) {
      const target = $event.target as HTMLElement;
      if (target.dataset.date) {
        this.selectedPreset = null;
        this.updateDate(this.dateUtils.getStartOfDay(new Date(target.dataset.date)));
        if (this.closeOnSelect === true) {
          this.dropDate.toggleDrop();
        }
      }
    }
  }

  protected selectMonth($event: MouseEvent) {
    const target = $event.target as HTMLElement;
    if (target.dataset.date) {
      this.month = new Date(target.dataset.date);
      if (!this.yearOnly) {
        this.currentPage--;
      }
      else {
        this.updateDate(this.dateUtils.getStartOfDay(new Date(target.dataset.date)));
      }
      if (this.monthOnly && this.currentPage < CALENDAR_VIEWS.MONTHS) {
        this.currentPage = CALENDAR_VIEWS.MONTHS;
        this.updateDate(this.dateUtils.getStartOfDay(new Date(target.dataset.date)));
      }
    }
  }

  protected cancelSelection() {
    if (!this.yearOnly && !this.monthOnly) {
      this.currentPage = CALENDAR_VIEWS.DAYS;
    }
  }

  protected clearSelection() {
    if (this.allowClear) {
      this.date = null;
      this.dateLabel = null;
    }
  }

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

    if (this.closeOnSelect === true) {
      this.dropDate.toggleDrop();
    }
  }

  private updateDate(dt: Date, tm: Date = this.date) {
    if (dt != null) {
      if (this.dateOnly) {
        dt.setHours(12, 0, 0, 0);
      }
      else if (tm != null) {
        dt.setHours(tm.getHours(), tm.getMinutes(), 0, 0);
      }
    }
    this.date = dt;
    this.dateLabel = UIFormat.datetime(this.date, this.format);
    this.taskQueue.queueMicroTask(() => {
      this.onChange({ date: this.date });
    });
  }

  protected toggleUIDrop(isDate: boolean): void {
    if (this.disabled || this.readonly) return;
    if (this.date == null) {
      if (this.autosetWhenOpened) {
        this.date = new Date();
      } else {
        this.initDateWhenOpened();
      }
    }
    if (!isDate) {
      UIInternal.broadcast("openTimePage");
    }
  }

  public toggleDateDrop(): void {
    this.dropDate.toggleDrop();
  }

  public toggleTimeDrop(): void {
    this.dropTime.toggleDrop();
  }

  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;
  }
}