import { Injectable, NgZone } from "@angular/core";
import {
    DocDbPopupActionI,
    DocDbPopupClosedReasonE,
    DocDbPopupContextI,
    DocDbPopupDetailI,
    DocDbPopupService,
    DocDbPopupTypeE,
    ObjectTriggerOptionsI,
    ObjectTriggerPositionI,
} from "../../DocDbPopupService";
import { BehaviorSubject, fromEvent, Observable, Subject } from "rxjs";
import {
    DocDbPropertyTuple,
    DocDbPropertyTypeFilterI,
    DocDbService,
    DocumentResultI,
} from "@peek/peek_core_docdb";
import { assert, sortText } from "@synerty/vortexjs";
import { DOCDB_POPUP } from "@peek/peek_core_docdb/constants";
import { first } from "rxjs/operators";

export class PopupTriggeredParams {
    actions$ = new BehaviorSubject<DocDbPopupActionI[]>([]);
    details$ = new BehaviorSubject<DocDbPopupDetailI[]>([]);

    constructor(
        public triggeredByPlugin: string,
        public position: ObjectTriggerPositionI,
        public modelSetKey: string,
        public objectKey: string,
        public options: ObjectTriggerOptionsI
    ) {}

    get actions() {
        return this.actions$.getValue();
    }

    set actions(value) {
        this.actions$.next(value);
    }

    get details() {
        return this.details$.getValue();
    }

    set details(value) {
        this.details$.next(value);
    }
}

@Injectable()
export class PrivateDocDbPopupService extends DocDbPopupService {
    showTooltipPopupSubject = new Subject<PopupTriggeredParams>();
    hideTooltipPopupSubject = new Subject<void>();
    showSummaryPopupSubject = new Subject<PopupTriggeredParams>();
    hideSummaryPopupSubject = new Subject<void>();
    showDetailPopupSubject = new Subject<PopupTriggeredParams>();
    hideDetailPopupSubject = new Subject<void>();

    private tooltipPopupSubject = new Subject<DocDbPopupContextI>();
    private tooltipPopupClosedSubject = new Subject<DocDbPopupClosedReasonE>();
    private summaryPopupSubject = new Subject<DocDbPopupContextI>();
    private summaryPopupClosedSubject = new Subject<DocDbPopupClosedReasonE>();
    private detailPopupSubject = new Subject<DocDbPopupContextI>();
    private detailPopupClosedSubject = new Subject<DocDbPopupClosedReasonE>();

    private readonly DATA_LOAD_LEAD_TIME_MS = 200;
    private readonly TOOLTIP_POPUP_DELAY_TIME_MS = 700;

    private shownPopup: DocDbPopupTypeE | null = null;
    private popupClicked: boolean = false;

    constructor(private docDbService: DocDbService, protected zone: NgZone) {
        super();
        this.handleHidePopups();
    }

    handleHidePopups(): void {
        fromEvent(document, "touchstart").subscribe((event: any) =>
            this.handleCloseEvents(event)
        );

        fromEvent(document, "mousedown").subscribe((event: any) =>
            this.handleCloseEvents(event)
        );
    }

    handleCloseEvents(event): void {
        if (!this.popupClicked) {
            return;
        }
        for (const el of event.path || event.composedPath()) {
            if (el.id === DOCDB_POPUP) {
                return;
            }
        }
        this.hideAllPopups();
    }

    showPopup(
        popupClicked: boolean,
        popupType: DocDbPopupTypeE,
        triggeredByPlugin: string,
        position: ObjectTriggerPositionI,
        modelSetKey: string,
        objectKey: string,
        options: ObjectTriggerOptionsI = {}
    ): void {
        if (this.popupClicked && !popupClicked) {
            return;
        }

        this.hidePopup(DocDbPopupTypeE.tooltipPopup);

        this.shownPopup = popupType;
        this.popupClicked = popupClicked;

        const params = new PopupTriggeredParams(
            triggeredByPlugin,
            position,
            modelSetKey,
            objectKey,
            Object.assign({ popupDelayMs: 0 }, options)
        );

        if (popupType == DocDbPopupTypeE.tooltipPopup) {
            params.options.popupDelayMs =
                params.options.popupDelayMs || this.TOOLTIP_POPUP_DELAY_TIME_MS;

            this.triggerPopup(
                popupType,
                params,
                this.showTooltipPopupSubject,
                this.hideTooltipPopupSubject,
                this.tooltipPopupSubject,
                (prop: DocDbPropertyTuple) => prop.showOnTooltip
            );
        } else if (popupType == DocDbPopupTypeE.summaryPopup) {
            this.triggerPopup(
                popupType,
                params,
                this.showSummaryPopupSubject,
                this.hideSummaryPopupSubject,
                this.summaryPopupSubject,
                (prop: DocDbPropertyTuple) => prop.showOnSummary
            );
        } else if (popupType == DocDbPopupTypeE.detailPopup) {
            this.triggerPopup(
                popupType,
                params,
                this.showDetailPopupSubject,
                this.hideDetailPopupSubject,
                this.detailPopupSubject,
                (prop: DocDbPropertyTuple) => prop.showOnDetail
            );
        } else {
            throw new Error(`showPopup:Unhandled popup type ${popupType}`);
        }
    }

    hidePopupWithReason(
        popupType: DocDbPopupTypeE,
        reason: DocDbPopupClosedReasonE
    ): void {
        this.shownPopup = null;
        this.hideTooltipPopupSubject.next();
        this.tooltipPopupClosedSubject.next(reason);
        this.hideSummaryPopupSubject.next();
        this.summaryPopupClosedSubject.next(reason);
        this.hideDetailPopupSubject.next();
        this.detailPopupClosedSubject.next(reason);
    }

    hidePopup(popupType: DocDbPopupTypeE): void {
        this.hidePopupWithReason(
            popupType,
            DocDbPopupClosedReasonE.closedByApiCall
        );
    }

    hideHoverPopup(): void {
        if (this.popupClicked) {
            return;
        }

        this.hidePopup(DocDbPopupTypeE.tooltipPopup);
    }

    hideAllPopups(): void {
        this.hidePopup(DocDbPopupTypeE.tooltipPopup);
        this.hidePopup(DocDbPopupTypeE.summaryPopup);
        this.hidePopup(DocDbPopupTypeE.detailPopup);
        this.popupClicked = false;
    }

    popupObservable(
        popupType: DocDbPopupTypeE
    ): Observable<DocDbPopupContextI> {
        if (popupType == DocDbPopupTypeE.tooltipPopup) {
            return this.tooltipPopupSubject.asObservable();
        } else if (popupType == DocDbPopupTypeE.summaryPopup) {
            return this.summaryPopupSubject.asObservable();
        } else if (popupType == DocDbPopupTypeE.detailPopup) {
            return this.detailPopupSubject.asObservable();
        } else {
            throw new Error(
                `popupObservable:Unhandled popup type ${popupType}`
            );
        }
    }

    popupClosedObservable(
        popupType: DocDbPopupTypeE
    ): Observable<DocDbPopupClosedReasonE> {
        if (popupType == DocDbPopupTypeE.tooltipPopup) {
            return this.tooltipPopupClosedSubject.asObservable();
        } else if (popupType == DocDbPopupTypeE.summaryPopup) {
            return this.summaryPopupClosedSubject.asObservable();
        } else if (popupType == DocDbPopupTypeE.detailPopup) {
            return this.detailPopupClosedSubject.asObservable();
        } else {
            throw new Error(
                `popupObservable:Unhandled popup type ${popupType}`
            );
        }
    }

    private triggerPopup(
        popupType: DocDbPopupTypeE,
        params: PopupTriggeredParams,
        showTriggerSubject: Subject<PopupTriggeredParams>,
        hideTriggerSubject: Subject<void>,
        subject: Subject<DocDbPopupContextI>,
        propFilter: DocDbPropertyTypeFilterI
    ) {
        assert(params.objectKey != null, "objectKey is null");

        // Load the data XXXms before showing the popup, if we can.
        const loadDelay =
            params.options.popupDelayMs < this.DATA_LOAD_LEAD_TIME_MS
                ? params.options.popupDelayMs
                : params.options.popupDelayMs - this.DATA_LOAD_LEAD_TIME_MS;

        let loadCancelled = false;

        const loadTimeoutHandle = setTimeout(() => {
            // FIXME: This doesn't work because all the data is under pofDiagram
            // this.docDbService.getObjects(params.modelSetKey, [params.objectKey])
            this.docDbService
                .getObjects("pofDiagram", [params.objectKey])
                .then((docs: DocumentResultI) => {
                    if (loadCancelled) return;

                    const apiHook: DocDbPopupContextI = {
                        popupType: popupType,
                        key: params.objectKey,
                        document: null,
                        modelSetKey: params.modelSetKey,
                        triggeredByPlugin: params.triggeredByPlugin,
                        options: params.options,
                        addAction: (item: DocDbPopupActionI) => {
                            params.actions = this.sortActions([
                                ...params.actions,
                                item,
                            ]);
                        },
                        addDetails: (items: DocDbPopupDetailI[]) => {
                            params.details = this.sortDetails([
                                ...params.details,
                                ...items,
                            ]);
                        },
                    };

                    const doc = docs[params.objectKey];
                    if (doc != null) {
                        apiHook.document = doc;
                        const docProps =
                            this.docDbService.getNiceOrderedProperties(
                                doc,
                                propFilter
                            );
                        params.details = [...params.details, ...docProps];
                    }

                    // Tell any observers that we're popping up
                    // Give them a chance to add their items
                    subject.next(apiHook);
                });
        }, loadDelay);

        const popupTimeoutHandle = setTimeout(
            () => showTriggerSubject.next(params),
            params.options.popupDelayMs
        );

        hideTriggerSubject.pipe(first()).subscribe(() => {
            loadCancelled = true;
            clearTimeout(popupTimeoutHandle);
            clearTimeout(loadTimeoutHandle);
        });
    }

    private sortActions(actions: DocDbPopupActionI[]): DocDbPopupActionI[] {
        actions = sortText(actions, (action) => {
            return action.name != null
                ? action.name
                : action.tooltip != null
                ? action.tooltip
                : "";
        });
        for (const action of actions) {
            action.children = this.sortActions(action.children || []);
        }
        return actions;
    }

    private sortDetails(details: DocDbPopupDetailI[]): DocDbPopupDetailI[] {
        return details.sort((a, b) => {
            const ao = a.order || 0;
            const bo = b.order || 0;
            if (ao != bo) return ao < bo ? -1 : 1;

            const an = a.title || "";
            const bn = b.title || "";

            if (an != bn) return an < bn ? -1 : 1;

            const av = a.value || "";
            const bv = b.value || "";

            if (av != bv) return av < bv ? -1 : 1;

            return 0;
        });
    }
}
