import { Calendar, EventClickArg, EventDropArg, EventSourceInput } from '@fullcalendar/core';
import dayGridPlugin from '@fullcalendar/daygrid';
import interactionPlugin from '@fullcalendar/interaction';
import multiMonthPlugin from '@fullcalendar/multimonth';
import timeGrid from '@fullcalendar/timegrid';
import { IReactionDisposer, action, computed, makeObservable, observable, reaction } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
import { dateRangeStrToDates } from '../../../../ClientUtils';
import { Doc } from '../../../../fields/Doc';
import { DocData } from '../../../../fields/DocSymbols';
import { Id } from '../../../../fields/FieldSymbols';
import { BoolCast, NumCast, StrCast } from '../../../../fields/Types';
import { DocServer } from '../../../DocServer';
import { DragManager } from '../../../util/DragManager';
import { CollectionSubView, SubCollectionViewProps } from '../../collections/CollectionSubView';
import { ContextMenu } from '../../ContextMenu';
import { DocumentView } from '../DocumentView';
import { OpenWhere } from '../OpenWhere';
import './CalendarBox.scss';

type CalendarView = 'multiMonth' | 'dayGridMonth' | 'timeGridWeek' | 'timeGridDay';

@observer
export class CalendarBox extends CollectionSubView() {
    _calendarRef: HTMLDivElement | null = null;
    _calendar: Calendar | undefined;
    _oldWheel: HTMLElement | null = null;
    _observer: ResizeObserver | undefined;
    _eventsDisposer: IReactionDisposer | undefined;
    _selectDisposer: IReactionDisposer | undefined;

    constructor(props: SubCollectionViewProps) {
        super(props);
        makeObservable(this);
    }

    @observable _multiMonth = 0;
    isMultiMonth: boolean | undefined;

    componentDidMount(): void {
        this._props.setContentViewBox?.(this);
        this._eventsDisposer = reaction(
            () => ({ events: this.calendarEvents }),
            ({ events }) => this._calendar?.setOption('events', events),
            { fireImmediately: true }
        );
        this._selectDisposer = reaction(
            () => ({ initialDate: this.dateSelect }),
            ({ initialDate }) => {
                const state = this._calendar?.getCurrentData();
                state &&
                    this._calendar?.dispatch({
                        type: 'CHANGE_DATE',
                        dateMarker: state.dateEnv.createMarker(initialDate.start),
                    });
                setTimeout(() => (initialDate.start.toISOString() !== initialDate.end.toISOString() ? this._calendar?.select(initialDate.start, initialDate.end) : this._calendar?.select(initialDate.start)));
            },
            { fireImmediately: true }
        );
    }
    componentWillUnmount(): void {
        this._eventsDisposer?.();
        this._selectDisposer?.();
    }

    @computed get calendarEvents(): EventSourceInput | undefined {
        return this.childDocs.map(doc => {
            const { start, end } = dateRangeStrToDates(StrCast(doc.date_range));
            return {
                title: StrCast(doc.title),
                start,
                end,
                groupId: doc[Id],
                startEditable: true,
                endEditable: true,
                allDay: BoolCast(doc.allDay),
                classNames: ['mother'], // will determine the style
                editable: true, // subject to change in the future
                backgroundColor: this.eventToColor(doc),
                borderColor: this.eventToColor(doc),
                color: 'white',
                extendedProps: {
                    description: StrCast(doc.description),
                },
            };
        });
    }

    @computed get dateRangeStrDates() {
        return dateRangeStrToDates(StrCast(this.Document.date_range));
    }
    get dateSelect() {
        return dateRangeStrToDates(StrCast(this.Document.date));
    }

    // Choose a calendar view based on the date range
    @computed get calendarViewType(): CalendarView {
        if (this.dataDoc[this.fieldKey + '_calendarType']) return StrCast(this.dataDoc[this.fieldKey + '_calendarType']) as CalendarView;
        if (this.isMultiMonth) return 'multiMonth';
        const { start, end } = this.dateRangeStrDates;
        if (start.getFullYear() !== end.getFullYear() || start.getMonth() !== end.getMonth()) return 'multiMonth';
        if (Math.abs(start.getDay() - end.getDay()) > 7) return 'dayGridMonth';
        return 'timeGridWeek';
    }

    // TODO: Return a different color based on the event type
    eventToColor = (event: Doc): string => {
        return 'red';
    };

    internalDocDrop = (e: Event, de: DragManager.DropEvent, docDragData: DragManager.DocumentDragData) => {
        if (!super.onInternalDrop(e, de)) return false;
        de.complete.docDragData?.droppedDocuments.forEach(doc => {
            const today = new Date().toISOString();
            if (!doc.date_range) doc[DocData].date_range = `${today}|${today}`;
        });
        return true;
    };

    onInternalDrop = (e: Event, de: DragManager.DropEvent): boolean => {
        if (de.complete.docDragData?.droppedDocuments.length) return this.internalDocDrop(e, de, de.complete.docDragData);
        return false;
    };

    handleEventDrop = (arg: EventDropArg) => {
        const doc = DocServer.GetCachedRefField(arg.event._def.groupId ?? '');
        doc && arg.event.start && (doc.date_range = arg.event.start?.toString() + '|' + (arg.event.end ?? arg.event.start).toString());
    };

    handleEventClick = (arg: EventClickArg) => {
        const doc = DocServer.GetCachedRefField(arg.event._def.groupId ?? '');
        if (doc) {
            DocumentView.showDocument(doc, { openLocation: OpenWhere.lightboxAlways });
            arg.jsEvent.stopPropagation();
        }
    };
    handleEventContextMenu = (pageX: number, pageY: number, docid: string) => {
        const doc = DocServer.GetCachedRefField(docid ?? '');
        if (doc) {
            const cm = ContextMenu.Instance;
            cm.addItem({ description: 'Show Metadata', event: () => this._props.addDocTab(doc, OpenWhere.addRightKeyvalue), icon: 'table-columns' });
            cm.displayMenu(pageX - 15, pageY - 15, undefined, undefined);
        }
    };

    // https://fullcalendar.io
    renderCalendar = () => {
        const cal = !this._calendarRef
            ? null
            : (this._calendar = new Calendar(this._calendarRef, {
                  plugins: [multiMonthPlugin, dayGridPlugin, timeGrid, interactionPlugin],
                  headerToolbar: {
                      left: 'prev,next today',
                      center: 'title',
                      right: 'multiMonth dayGridMonth timeGridWeek timeGridDay',
                  },
                  selectable: true,
                  initialView: this.calendarViewType === 'multiMonth' ? undefined : this.calendarViewType,
                  initialDate: this.dateSelect.start,
                  navLinks: true,
                  editable: false,
                  displayEventTime: false,
                  displayEventEnd: false,
                  select: info => {
                      const start = dateRangeStrToDates(info.startStr).start.toISOString();
                      const end = dateRangeStrToDates(info.endStr).start.toISOString();
                      this.dataDoc.date = start + '|' + end;
                  },
                  aspectRatio: NumCast(this.Document.width) / NumCast(this.Document.height),
                  events: this.calendarEvents,
                  eventClick: this.handleEventClick,
                  eventDrop: this.handleEventDrop,
                  eventDidMount: arg => {
                      arg.el.addEventListener('pointerdown', ev => {
                          ev.button && ev.stopPropagation();
                      });
                      if (navigator.userAgent.includes('Macintosh')) {
                          arg.el.addEventListener('pointerup', ev => {
                              ev.button && ev.stopPropagation();
                              ev.button && this.handleEventContextMenu(ev.pageX, ev.pageY, arg.event._def.groupId);
                          });
                      }
                      arg.el.addEventListener('contextmenu', ev => {
                          if (!navigator.userAgent.includes('Macintosh')) {
                              this.handleEventContextMenu(ev.pageX, ev.pageY, arg.event._def.groupId);
                          }
                          ev.stopPropagation();
                          ev.preventDefault();
                      });
                  },
              }));
        cal?.render();
        setTimeout(() => cal?.view.calendar.select(this.dateSelect.start, this.dateSelect.end));
    };

    onPassiveWheel = (e: WheelEvent) => e.stopPropagation();
    render() {
        return (
            <div
                key={this.calendarViewType}
                className="calendarBox"
                onPointerDown={e => {
                    setTimeout(
                        action(() => {
                            const cname = (e.nativeEvent.target as HTMLButtonElement)?.className ?? '';
                            if (cname.includes('multiMonth')) this.dataDoc[this.fieldKey + '_calendarType'] = 'multiMonth';
                            if (cname.includes('dayGridMonth')) this.dataDoc[this.fieldKey + '_calendarType'] = 'dayGridMonth';
                            if (cname.includes('timeGridWeek')) this.dataDoc[this.fieldKey + '_calendarType'] = 'timeGridWeek';
                            if (cname.includes('timeGridDay')) this.dataDoc[this.fieldKey + '_calendarType'] = 'timeGridDay';
                        })
                    );
                }}
                style={{
                    width: this._props.PanelWidth() / this._props.ScreenToLocalTransform().Scale,
                    height: this._props.PanelHeight() / this._props.ScreenToLocalTransform().Scale,
                    transform: `scale(${this._props.ScreenToLocalTransform().Scale})`,
                }}
                ref={r => {
                    this.createDashEventsTarget(r);
                    this._oldWheel?.removeEventListener('wheel', this.onPassiveWheel);
                    this._oldWheel = r;
                    // prevent wheel events from passively propagating up through containers and prevents containers from preventDefault which would block scrolling
                    r?.addEventListener('wheel', this.onPassiveWheel, { passive: false });

                    if (r) {
                        this._observer?.disconnect();
                        (this._observer = new ResizeObserver(() => {
                            this._calendar?.setOption('aspectRatio', NumCast(this.Document.width) / NumCast(this.Document.height));
                            this._calendar?.updateSize();
                        })).observe(r);
                        this.renderCalendar();
                    }
                }}>
                <div className="calendarBox-wrapper" ref={r => (this._calendarRef = r)} />
            </div>
        );
    }
}
