import { Calendar, DateSelectArg, EventClickArg, EventDropArg, EventMountArg, EventSourceInput } from '@fullcalendar/core';
import { EventResizeDoneArg } from '@fullcalendar/interaction';
import dayGridPlugin from '@fullcalendar/daygrid';
import interactionPlugin from '@fullcalendar/interaction';
import multiMonthPlugin from '@fullcalendar/multimonth';
import timeGrid from '@fullcalendar/timegrid';
import FullCalendar from '@fullcalendar/react';
import { IReactionDisposer, action, computed, makeObservable, observable, reaction, untracked } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
import { dateRangeStrToDates } from '../../../../ClientUtils';
import { Doc } from '../../../../fields/Doc';
import { Id } from '../../../../fields/FieldSymbols';
import { BoolCast, 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';
import { DateField } from '../../../../fields/DateField';
import { undoable } from '../../../util/UndoManager';
import { DocumentType } from '../../../documents/DocumentTypes';

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

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

    @observable _multiMonth = 0;

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

    @computed get calTypeFieldKey() {
        return this.fieldKey + '_calendarType';
    }

    componentDidMount(): void {
        this.Document.$calendar = ''; // needed only to make the keyvalue view look nice.
        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));
            },
            { fireImmediately: true }
        );
    }
    componentWillUnmount(): void {
        this._eventsDisposer?.();
        this._selectDisposer?.();
    }

    @computed get calendarEvents(): EventSourceInput | undefined {
        return this.childDocs.map(doc => {
            const { start, end } = dateRangeStrToDates(StrCast(doc.$task_dateRange));
            const isCompleted = BoolCast(doc.$task_completed); // AARAV ADD
            return {
                title: StrCast(doc.title),
                start,
                end,
                groupId: doc[Id],
                startEditable: true,
                endEditable: true,
                allDay: BoolCast(doc.$task_allDay),
                classNames: ['mother', isCompleted ? 'completed-task' : ''], // 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._calendar_dateRange));
    }
    get dateSelect() {
        return dateRangeStrToDates(StrCast(this.Document._calendar_date));
    }

    // Choose a calendar view based on the date range
    @computed get calendarViewType(): CalendarView {
        if (this.dataDoc[this.calTypeFieldKey]) return StrCast(this.dataDoc[this.calTypeFieldKey]) 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 StrCast(event.type) === DocumentType.TASK
            ? '#20B2AA' // teal for tasks
            : 'red';
    };

    // eslint-disable-next-line @typescript-eslint/no-unused-vars
    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.$task_dateRange) doc.$task_dateRange = `${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 = undoable((arg: EventDropArg | EventResizeDoneArg) => {
        const doc = DocServer.GetCachedRefField(arg.event._def.groupId ?? '');
        // doc && arg.event.start && (doc.$task_dateRange = arg.event.start?.toString() + '|' + (arg.event.end ?? arg.event.start).toString());
        if (!doc || !arg.event.start) return;

        // get the new start and end dates
        const startDate = new Date(arg.event.start);
        const endDate = new Date(arg.event.end ?? arg.event.start);

        // update date range, time range, and all day status
        doc.$task_dateRange = `${startDate.toISOString()}|${endDate.toISOString()}`;

        const allDayStatus = arg.event.allDay ?? false;
        if (doc.$task_allDay !== allDayStatus) {
            doc.$task_allDay = allDayStatus;
        }

        if (doc.$task_allDay) {
            delete doc.$task_startTime;
            delete doc.$task_endTime;
        } else {
            doc.$task_startTime = new DateField(startDate);
            doc.task_endTime = new DateField(endDate);
        }
    }, 'change event date');

    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
    @computed get renderCalendar() {
        const availableWidth = this._props.PanelWidth() / (this._props.DocumentView?.().UIBtnScaling ?? 1);
        const btn = (text: string, view: string | (() => void), hint: string) => ({ text, hint, click: typeof view === 'string' ? () => this._calendarRef?.getApi().changeView(view) : view });
        return (
            <FullCalendar
                ref={(r: unknown) => (this._calendarRef = r as FullCalendar)}
                customButtons={{
                    nowBtn: btn('Now', () => this._calendarRef?.getApi().gotoDate(new Date()), 'Go to Today'),
                    multiBtn: btn('M+', 'multiMonth', 'Multiple Month View'),
                    monthBtn: btn('M', 'dayGridMonth', 'Month View'),
                    weekBtn: btn('W', 'timeGridWeek', 'Week View'),
                    dayBtn: btn('D', 'timeGridDay', 'Day View'),
                }}
                headerToolbar={
                    availableWidth > 450
                        ? {
                              left: 'prev,next nowBtn',
                              center: 'title',
                              right: 'multiBtn monthBtn weekBtn dayBtn',
                          }
                        : availableWidth > 300
                          ? {
                                left: 'prev,next',
                                center: 'title',
                                right: '',
                            }
                          : {
                                left: '',
                                center: 'title',
                                right: '',
                            }
                }
                selectable={true}
                initialView={this.calendarViewType === 'multiMonth' ? undefined : this.calendarViewType}
                views={{
                    multiMonth: {
                        type: 'multiMonth',
                        duration: { months: 12 },
                    },
                }}
                initialDate={untracked(() => this.dateSelect.start)}
                navLinks={true}
                editable={false}
                // expandRows={true}
                // handleWindowResize={true}
                displayEventTime={false}
                displayEventEnd={false}
                plugins={[multiMonthPlugin, dayGridPlugin, timeGrid, interactionPlugin]}
                aspectRatio={this._props.PanelWidth() / this._props.PanelHeight()}
                weekends={true}
                events={this.calendarEvents}
                eventClick={this.handleEventClick}
                eventDrop={this.handleEventDrop}
                unselectAuto={false}
                // unselect={() => {}}
                select={(info: DateSelectArg) => {
                    const start = dateRangeStrToDates(info.startStr).start.toISOString();
                    const end = info.allDay ? start : dateRangeStrToDates(info.endStr).start.toISOString();
                    this.Document._calendar_date = start + '|' + end;
                }}
                // eventContent={() => {
                //     return null;
                // }}
                eventDidMount={(arg: EventMountArg) => {
                    const doc = DocServer.GetCachedRefField(arg.event._def.groupId ?? '');
                    if (!doc) return;

                    if (doc.type === DocumentType.TASK) {
                        const checkButton = document.createElement('button');
                        checkButton.innerText = doc.$task_completed ? '✅' : '⬜';
                        checkButton.style.position = 'absolute';
                        checkButton.style.right = '5px';
                        checkButton.style.top = '50%';
                        checkButton.style.transform = 'translateY(-50%)';
                        checkButton.style.background = 'transparent';
                        checkButton.style.border = 'none';
                        checkButton.style.cursor = 'pointer';
                        checkButton.style.fontSize = '18px';
                        checkButton.style.zIndex = '1000';
                        checkButton.style.padding = '0';
                        checkButton.style.margin = '0';

                        checkButton.onclick = ev => {
                            ev.stopPropagation();
                            doc.$task_completed = !doc.$task_completed;
                            this._calendar?.refetchEvents();
                        };

                        arg.el.style.position = 'relative';
                        arg.el.appendChild(checkButton);
                    }
                    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();
                    });
                }}
            />
        );
    }

    render() {
        return (
            <div
                key={this.calendarViewType}
                className={`calendarBox${this._props.isContentActive() ? '-interactive' : ''}`}
                onPointerDown={e => {
                    setTimeout(
                        action(() => {
                            const cname = (e.nativeEvent.target as HTMLButtonElement)?.className ?? '';
                            if (cname.includes('multiMonth')) this.dataDoc[this.calTypeFieldKey] = 'multiMonth';
                            if (cname.includes('dayGridMonth')) this.dataDoc[this.calTypeFieldKey] = 'dayGridMonth';
                            if (cname.includes('timeGridWeek')) this.dataDoc[this.calTypeFieldKey] = 'timeGridWeek';
                            if (cname.includes('timeGridDay')) this.dataDoc[this.calTypeFieldKey] = '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.fixWheelEvents(r, this._props.isContentActive);
                }}>
                {this.renderCalendar}
            </div>
        );
    }
}
