import { Calendar, EventClickArg, EventSourceInput } from '@fullcalendar/core';
import dayGridPlugin from '@fullcalendar/daygrid';
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 { BoolCast, NumCast, StrCast } from '../../../../fields/Types';
import { CollectionSubView, SubCollectionViewProps } from '../../collections/CollectionSubView';
import './CalendarBox.scss';
import { Id } from '../../../../fields/FieldSymbols';
import { DocServer } from '../../../DocServer';
import { DocumentView } from '../DocumentView';
import { OpenWhere } from '../OpenWhere';
import { DragManager } from '../../../util/DragManager';
import { DocData } from '../../../../fields/DocSymbols';

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;

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

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

    componentDidMount(): void {
        this._props.setContentViewBox?.(this);
        this._eventsDisposer = reaction(
            () => this.calendarEvents,
            events => this._calendar?.setOption('events', events),
            { fireImmediately: true }
        );
    }
    componentWillUnmount(): void {
        this._eventsDisposer?.();
    }

    @computed get calendarEvents(): EventSourceInput | undefined {
        return this.childDocs.map(doc => {
            const { startDate: start, endDate: 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),
                color: 'white',
                extendedProps: {
                    description: StrCast(doc.description),
                },
            };
        });
    }

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

    // 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 { startDate, endDate } = this.dateRangeStrDates;
        if (startDate.getFullYear() !== endDate.getFullYear() || startDate.getMonth() !== endDate.getMonth()) return 'multiMonth';
        if (Math.abs(startDate.getDay() - endDate.getDay()) > 7) return 'dayGridMonth';
        return 'timeGridWeek';
    }

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

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

    handleEventClick = (arg: EventClickArg) => {
        const doc = DocServer.GetCachedRefField(arg.event._def.groupId ?? '');
        DocumentView.DeselectAll();
        if (doc) {
            DocumentView.showDocument(doc, { openLocation: OpenWhere.lightboxAlways });
            arg.jsEvent.stopPropagation();
        }
    };

    // https://fullcalendar.io
    renderCalendar = () => {
        const cal = !this._calendarRef
            ? null
            : (this._calendar = new Calendar(this._calendarRef, {
                  plugins: [multiMonthPlugin, dayGridPlugin, timeGrid],
                  headerToolbar: {
                      left: 'prev,next today',
                      center: 'title',
                      right: 'multiMonth dayGridMonth timeGridWeek timeGridDay',
                  },
                  initialView: this.calendarViewType === 'multiMonth' ? undefined : this.calendarViewType,
                  initialDate: this.dateRangeStrDates.startDate,
                  navLinks: true,
                  editable: false,
                  displayEventTime: false,
                  displayEventEnd: false,
                  aspectRatio: NumCast(this.Document.width) / NumCast(this.Document.height),
                  events: this.calendarEvents,
                  eventClick: this.handleEventClick,
              }));
        cal?.render();
    };

    onPassiveWheel = (e: WheelEvent) => e.stopPropagation();
    render() {
        return (
            <div
                key={StrCast(this.dataDoc[this.fieldKey + '_calendarType'])}
                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})`,
                    transformOrigin: 'top left',
                }}
                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>
        );
    }
}
