import {
  DateRangeFilterMode,
  DateRangePreset,
  Filter,
  getDatesFromPreset,
  RangeItem,
  ToggleState,
  Translate,
} from '@wix/bex-core';
import { action, makeObservable, observable, reaction } from 'mobx';
import { EventEmitter } from '@wix/bex-core/events';
import { FilterToolbarState } from '../providers';

export interface DateRangePickerStateParams {
  toolbar: FilterToolbarState;
  translate: Translate;
  filter: Filter<RangeItem<Date>>;
  filterByTime?: boolean;
  mode?: DateRangeFilterMode;
}

export class DateRangePickerState {
  readonly translate: DateRangePickerStateParams['translate'];
  readonly _filter: DateRangePickerStateParams['filter'];
  readonly dropdown = new ToggleState();
  readonly events = new EventEmitter();
  readonly toolbar;
  from: Date | null = null;
  to: Date | null = null;

  isFromTimeSet: boolean = false;
  isToTimeSet: boolean = false;

  _mode;
  _selectedPreset: string = DateRangePreset.NONE;
  filterByTime: boolean;

  get filter() {
    return this.toolbar.getPendingFilter(this._filter);
  }

  set selectedPreset(preset: string) {
    this._selectedPreset =
      this._mode === 'ONLY_CUSTOM' ? DateRangePreset.CUSTOM : preset;
  }

  get selectedPreset() {
    return this._mode === 'ONLY_CUSTOM'
      ? DateRangePreset.CUSTOM
      : this._selectedPreset;
  }

  constructor(params: DateRangePickerStateParams) {
    this.toolbar = params.toolbar;
    this.translate = params.translate;
    this._filter = params.filter;
    this.filterByTime = params.filterByTime ?? false;
    this._mode = params.mode;

    makeObservable(this, {
      from: observable,
      to: observable,
      isFromTimeSet: observable.ref,
      isToTimeSet: observable.ref,
      _selectedPreset: observable,
      init: action.bound,
      clear: action.bound,
      onFromSelected: action.bound,
      onToSelected: action.bound,
      onFromTimeSelected: action.bound,
      onToTimeSelected: action.bound,
      onPresetChanged: action.bound,
    });
  }

  clearTime() {
    this.isFromTimeSet = false;
    this.isToTimeSet = false;
  }

  clear() {
    this.from = null;
    this.to = null;
    this.clearTime();

    this.refreshIfChanged({ actionType: 'remove' });
  }

  remove() {
    this.selectedPreset = DateRangePreset.NONE;
    this.clear();
  }

  private convertToBIFormat(from: Date | null, to: Date | null) {
    return `${this.filter.itemKey({ from, to })}, ${this.selectedPreset}`;
  }

  private refreshIfChanged(
    options: { actionType?: 'add' | 'remove'; clickedValueKey?: string } = {},
  ) {
    const { from, to, filter } = this;

    if (filter.itemKey(filter.value) !== filter.itemKey({ from, to })) {
      filter.setValue(
        {
          from,
          to,
          preset: this.selectedPreset,
        },
        {
          emitEvents: ['change', 'beforeRefresh', 'refresh'],
          ...options,
        },
      );
      return true;
    }
    return false;
  }

  close() {
    const { dropdown, events } = this;
    dropdown.setOff();
    events.emit('close');
  }

  init() {
    const { filter } = this;

    const disposer = reaction(
      () => filter.value,
      () => {
        if (!filter.value.from && !filter.value.to) {
          if (
            filter.value.preset &&
            filter.value.preset !== DateRangePreset.CUSTOM &&
            filter.value.preset !== DateRangePreset.NONE
          ) {
            this.onPresetChanged(filter.value.preset);
            return;
          }
          filter.value.preset ? this.clear() : this.remove();
          return;
        }

        if (
          filter.value.from?.valueOf() === this.from?.valueOf() &&
          filter.value.to?.valueOf() === this.to?.valueOf()
        ) {
          return;
        }

        if (
          filter.value.preset &&
          filter.value.preset !== DateRangePreset.CUSTOM
        ) {
          this.onPresetChanged(filter.value.preset);
          return;
        }

        this.from = filter.value.from;
        this.to = filter.value.to;
        this.selectedPreset = DateRangePreset.CUSTOM;

        this.isFromTimeSet = !!this.from;
        this.isToTimeSet = !!this.to;
      },
      {
        fireImmediately: true,
      },
    );

    return () => {
      disposer();
    };
  }

  onPresetChanged(newPreset: string) {
    this.selectedPreset = newPreset;

    if (this.selectedPreset === DateRangePreset.CUSTOM) {
      if (!this.from && !this.to) {
        return;
      }

      this.filter.setValue(
        {
          from: this.from,
          to: this.to,
          preset: this.selectedPreset,
        },
        {
          emitEvents: ['change'],
        },
      );

      return;
    }

    if (this.selectedPreset === DateRangePreset.NONE) {
      return this.clear();
    }
    const { from, to } = getDatesFromPreset(this.selectedPreset);
    this.from = from;
    this.to = to;
    this.isFromTimeSet = true;
    this.isToTimeSet = true;

    this.refreshIfChanged({
      actionType: 'add',
      clickedValueKey: this.convertToBIFormat(from, to),
    });
  }

  onDateSelected(
    newDate: Date | null,
    curDate: Date | null,
    isInvalid: (date: Date) => boolean,
    isTimeSet: boolean,
    type: 'from' | 'to',
  ) {
    if (!newDate) {
      return { date: null, isTimeSet: false };
    }
    const timeDate = new Date(
      type === 'from'
        ? newDate.setHours(0, 0, 0, 0)
        : newDate.setHours(23, 59, 59, 999),
    );
    // takes the current selected date, and copies its time selection to the new selected date
    const resDate = curDate
      ? new Date(newDate.setHours(curDate.getHours(), curDate.getMinutes()))
      : timeDate;
    if (isInvalid(resDate)) {
      return { date: timeDate, isTimeSet: false };
    }
    return { date: resDate, isTimeSet };
  }

  onFromSelected(from: Date | null) {
    const { isTimeSet, date } = this.onDateSelected(
      from,
      this.from,
      (d) => !!(this.to && d > this.to),
      this.isFromTimeSet,
      'from',
    );
    this.from = date;
    this.isFromTimeSet = isTimeSet;
  }

  onToSelected(to: Date | null) {
    const { isTimeSet, date } = this.onDateSelected(
      to,
      this.to,
      (d) => !!(this.from && d < this.from),
      this.isToTimeSet,
      'to',
    );
    this.to = date;
    this.isToTimeSet = isTimeSet;
  }

  onTimeSelected(newTime: Date | null, curDate: Date) {
    const fromCpy = new Date(curDate);
    return {
      isTimeSet: !!newTime,
      date: new Date(
        newTime
          ? fromCpy.setHours(newTime.getHours(), newTime.getMinutes())
          : fromCpy.setHours(0, 0),
      ),
    };
  }

  onFromTimeSelected(fromTime: Date | null) {
    if (!this.from) {
      return;
    }

    const { isTimeSet, date } = this.onTimeSelected(fromTime, this.from);
    this.isFromTimeSet = isTimeSet;
    this.from = date;

    this.refreshIfChanged({
      actionType: 'add',
      clickedValueKey: this.convertToBIFormat(this.from, this.to),
    });
  }

  onToTimeSelected(toTime: Date | null) {
    if (!this.to) {
      return;
    }

    const { isTimeSet, date } = this.onTimeSelected(toTime, this.to);
    this.isToTimeSet = isTimeSet;
    this.to = date;

    this.refreshIfChanged({
      actionType: 'add',
      clickedValueKey: this.convertToBIFormat(this.from, this.to),
    });
  }

  onFromClosed() {
    this.refreshIfChanged({
      actionType: 'add',
      clickedValueKey: this.convertToBIFormat(this.from, this.to),
    });
  }

  onToClosed() {
    this.refreshIfChanged({
      actionType: 'add',
      clickedValueKey: this.convertToBIFormat(this.from, this.to),
    });
  }

  static _isBiggerOrSameDate(date1: Date, date2: Date) {
    return (
      date1.getFullYear() > date2.getFullYear() ||
      (date1.getFullYear() === date2.getFullYear() &&
        date1.getMonth() > date2.getMonth()) ||
      (date1.getFullYear() === date2.getFullYear() &&
        date1.getMonth() === date2.getMonth() &&
        date1.getDate() >= date2.getDate())
    );
  }

  static _isBiggerDate(date1: Date, date2: Date) {
    return (
      date1.getFullYear() > date2.getFullYear() ||
      (date1.getFullYear() === date2.getFullYear() &&
        date1.getMonth() > date2.getMonth()) ||
      (date1.getFullYear() === date2.getFullYear() &&
        date1.getMonth() === date2.getMonth() &&
        date1.getDate() > date2.getDate())
    );
  }

  filterDateFrom(date: Date) {
    return !this.to || DateRangePickerState._isBiggerOrSameDate(this.to, date);
  }

  filterDateTo(date: Date) {
    return (
      !this.from || DateRangePickerState._isBiggerOrSameDate(date, this.from)
    );
  }

  filterDateTimeFrom(date: Date) {
    if (!this.from) {
      return false;
    }

    return (
      !this.isToTimeSet ||
      !this.to ||
      DateRangePickerState._isBiggerDate(this.to, this.from) ||
      this.to.getHours() * 60 + this.to.getMinutes() >=
        date.getHours() * 60 + date.getMinutes()
    );
  }

  filterDateTimeTo(date: Date) {
    if (!this.to) {
      return false;
    }

    return (
      !this.isFromTimeSet ||
      !this.from ||
      DateRangePickerState._isBiggerDate(this.to, this.from) ||
      this.from.getHours() * 60 + this.from.getMinutes() <=
        date.getHours() * 60 + date.getMinutes()
    );
  }
}
