import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { Tooltip } from '@mui/material';
import { action, computed, makeObservable, observable, runInAction } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
import wiki from 'wikijs';
import { returnEmptyFilter, returnEmptyString, returnFalse, returnNone, setupMoveUpEvents } from '../../../ClientUtils';
import { emptyFunction } from '../../../Utils';
import { Doc, Opt, returnEmptyDoclist } from '../../../fields/Doc';
import { Cast, DocCast, NumCast, PromiseValue, StrCast } from '../../../fields/Types';
import { DocServer } from '../../DocServer';
import { DocumentType } from '../../documents/DocumentTypes';
import { Docs } from '../../documents/Documents';
import { DragManager } from '../../util/DragManager';
import { LinkManager } from '../../util/LinkManager';
import { SearchUtil } from '../../util/SearchUtil';
import { SnappingManager } from '../../util/SnappingManager';
import { Transform } from '../../util/Transform';
import { ObservableReactComponent } from '../ObservableReactComponent';
import { returnEmptyDocViewList } from '../StyleProvider';
import { DocumentView } from './DocumentView';
import { StyleProviderFuncType } from './FieldView';
import './LinkDocPreview.scss';
import { OpenWhere } from './OpenWhere';

interface LinkDocPreviewProps {
    linkDoc?: Doc;
    linkSrc?: Doc;
    DocumentView?: () => DocumentView;
    styleProvider?: StyleProviderFuncType;
    location: number[];
    hrefs?: string[];
    showHeader?: boolean;
    noPreview?: boolean;
}
export class LinkInfo {
    // eslint-disable-next-line no-use-before-define
    private static _instance: Opt<LinkInfo>;
    constructor() {
        LinkInfo._instance = this;
        makeObservable(this);
    }
    // eslint-disable-next-line no-use-before-define
    @observable public LinkInfo: Opt<LinkDocPreviewProps> = undefined;

    public static get Instance() {
        return LinkInfo._instance ?? new LinkInfo();
    }
    public static Clear() {
        runInAction(() => {
            LinkInfo.Instance && (LinkInfo.Instance.LinkInfo = undefined);
        });
    }
    public static SetLinkInfo(info?: LinkDocPreviewProps) {
        runInAction(() => {
            LinkInfo.Instance && (LinkInfo.Instance.LinkInfo = info);
        });
    }
}

@observer
export class LinkDocPreview extends ObservableReactComponent<LinkDocPreviewProps> {
    _infoRef = React.createRef<HTMLDivElement>();
    _linkDocRef = React.createRef<HTMLDivElement>();
    @observable _targetDoc: Opt<Doc> = undefined;
    @observable _markerTargetDoc: Opt<Doc> = undefined;
    @observable _linkDoc: Opt<Doc> = undefined;
    @observable _linkSrc: Opt<Doc> = undefined;
    @observable _toolTipText = '';
    @observable _hrefInd = 0;
    constructor(props: LinkDocPreviewProps) {
        super(props);
        makeObservable(this);
    }

    @action
    init() {
        let linkTarget = this._props.linkDoc;
        this._linkSrc = this._props.linkSrc;
        this._linkDoc = this._props.linkDoc;
        const linkAnchor1 = DocCast(this._linkDoc?.link_anchor_1);
        const linkAnchor2 = DocCast(this._linkDoc?.link_anchor_2);
        if (linkAnchor1 && linkAnchor2) {
            linkTarget = Doc.AreProtosEqual(linkAnchor1, this._linkSrc) || Doc.AreProtosEqual(linkAnchor1?.annotationOn as Doc, this._linkSrc) ? linkAnchor2 : linkAnchor1;
        }
        if (linkTarget?.annotationOn && linkTarget?.type !== DocumentType.RTF) {
            linkTarget = DocCast(linkTarget.annotationOn); // want to show annotation embedContainer document if annotation is not text
        }
        this._markerTargetDoc = this._targetDoc = linkTarget;
        this._toolTipText = '';
        this.updateHref();
    }
    componentDidUpdate(prevProps: Readonly<LinkDocPreviewProps>) {
        super.componentDidUpdate(prevProps);
        if (prevProps.linkSrc !== this._props.linkSrc || prevProps.linkDoc !== this._props.linkDoc || prevProps.hrefs !== this._props.hrefs) this.init();
    }
    componentDidMount() {
        this.init();
        document.addEventListener('pointerdown', this.onPointerDown, true);
    }

    componentWillUnmount() {
        LinkInfo.Clear();
        document.removeEventListener('pointerdown', this.onPointerDown, true);
    }

    onPointerDown = (e: PointerEvent) => {
        !this._linkDocRef.current?.contains(e.target as HTMLElement) && LinkInfo.Clear(); // close preview when not clicking anywhere other than the info bar of the preview
    };

    @action
    updateHref() {
        if (this._props.hrefs?.length) {
            const href = this._props.hrefs[this._hrefInd];
            if (href.indexOf(Doc.localServerPath()) !== 0) {
                // link to a web page URL -- try to show a preview
                if (href.startsWith('https://en.wikipedia.org/wiki/')) {
                    wiki()
                        .page(href.replace('https://en.wikipedia.org/wiki/', ''))
                        .then(page =>
                            page.summary().then(
                                action(summary => {
                                    this._toolTipText = summary.substring(0, 500);
                                })
                            )
                        );
                } else {
                    this._toolTipText = 'url => ' + href;
                }
            } else {
                // hyperlink to a document .. decode doc id and retrieve from the server. this will trigger vals() being invalidated
                const anchorDocId = href.replace(Doc.localServerPath(), '').split('?')[0];
                const anchorDoc = anchorDocId ? PromiseValue(DocCast(DocServer.GetCachedRefField(anchorDocId) ?? DocServer.GetRefField(anchorDocId))) : undefined;
                anchorDoc?.then?.(
                    action(anchor => {
                        if (anchor instanceof Doc && Doc.Links(anchor).length) {
                            this._linkDoc = this._linkDoc ?? Doc.Links(anchor)[0];
                            const automaticLink = this._linkDoc.link_relationship === LinkManager.AutoKeywords;
                            if (automaticLink) {
                                // automatic links specify the target in the link info, not the source
                                const linkTarget = anchor;
                                this._linkSrc = Doc.getOppositeAnchor(this._linkDoc, linkTarget);
                                this._markerTargetDoc = this._targetDoc = linkTarget;
                            } else {
                                this._linkSrc = anchor;
                                const linkTarget = Doc.getOppositeAnchor(this._linkDoc, this._linkSrc);
                                this._markerTargetDoc = linkTarget;
                                this._targetDoc = /* linkTarget?.type === DocumentType.MARKER && */ linkTarget?.annotationOn ? (Cast(linkTarget.annotationOn, Doc, null) ?? linkTarget) : linkTarget;
                            }
                            if (LinkInfo.Instance?.LinkInfo?.noPreview || this._linkSrc?.followLinkToggle || this._markerTargetDoc?.type === DocumentType.PRES) this.followLink();
                        }
                    })
                );
            }
            return href;
        }
        return undefined;
    }

    @action
    editLink = (e: React.PointerEvent): void => {
        setupMoveUpEvents(
            this,
            e,
            returnFalse,
            emptyFunction,
            action(() => {
                LinkManager.Instance.currentLink = this._linkDoc;
                LinkManager.Instance.currentLinkAnchor = this._linkSrc;
                this._props.DocumentView?.().select(false);
                if ((SnappingManager.PropertiesWidth ?? 0) < 100) {
                    SnappingManager.SetPropertiesWidth(250);
                }
            })
        );
    };
    nextHref = (e: React.PointerEvent) => {
        setupMoveUpEvents(
            this,
            e,
            returnFalse,
            emptyFunction,
            action(() => {
                const nextHrefInd = (this._hrefInd + 1) % (this._props.hrefs?.length || 1);
                if (nextHrefInd !== this._hrefInd) {
                    this._linkDoc = undefined;
                    this._hrefInd = nextHrefInd;
                    this.updateHref();
                }
            }),
            true
        );
    };

    followLink = () => {
        LinkInfo.Clear();
        if (this._linkDoc && this._linkSrc) {
            DocumentView.FollowLink(this._linkDoc, this._linkSrc, false);
        } else if (this._props.hrefs?.length) {
            const webDoc =
                Array.from(SearchUtil.SearchCollection(Doc.MyFilesystem, this._props.hrefs[0], false).keys())
                    .filter(doc => doc.type === DocumentType.WEB)
                    .lastElement() ?? Docs.Create.WebDocument(this._props.hrefs[0], { title: this._props.hrefs[0], _nativeWidth: 850, _width: 200, _height: 400, data_useCors: true });
            DocumentView.showDocument(webDoc, {
                openLocation: OpenWhere.lightbox,
                willPan: true,
                zoomTime: 500,
            });
            // this._props.docProps?.addDocTab(webDoc, OpenWhere.lightbox);
        }
    };

    followLinkPointerDown = (e: React.PointerEvent) => setupMoveUpEvents(this, e, returnFalse, emptyFunction, this.followLink);

    width = () => {
        if (!this._targetDoc) return 225;
        if (NumCast(this._targetDoc._width) < NumCast(this._targetDoc._height)) {
            return (Math.min(225, NumCast(this._targetDoc._height)) * NumCast(this._targetDoc._width)) / NumCast(this._targetDoc._height);
        }
        return Math.min(225, NumCast(this._targetDoc._width, 225));
    };
    height = () => {
        if (!this._targetDoc) return 225;
        if (NumCast(this._targetDoc._width) > NumCast(this._targetDoc._height)) {
            return (Math.min(225, NumCast(this._targetDoc._width)) * NumCast(this._targetDoc._height)) / NumCast(this._targetDoc._width);
        }
        return Math.min(225, NumCast(this._targetDoc._height, 225));
    };
    @computed get previewHeader() {
        return !this._linkDoc || !this._markerTargetDoc || !this._targetDoc || !this._linkSrc ? null : (
            <div className="linkDocPreview-info" style={{ background: SnappingManager.userBackgroundColor }}>
                <div className="linkDocPreview-buttonBar" style={{ float: 'left' }}>
                    <Tooltip title={<div className="dash-tooltip">Edit Link</div>} placement="top">
                        <div className="linkDocPreview-button" onPointerDown={this.editLink}>
                            <FontAwesomeIcon className="linkDocPreview-fa-icon" icon="edit" color="white" size="sm" />
                        </div>
                    </Tooltip>
                </div>
                <div className="linkDocPreview-title" style={{ pointerEvents: 'all' }}>
                    {StrCast(this._markerTargetDoc.title).length > 16 ? StrCast(this._markerTargetDoc.title).substr(0, 16) + '...' : StrCast(this._markerTargetDoc.title)}
                    <p className="linkDocPreview-description"> {StrCast(this._linkDoc.link_description)}</p>
                </div>
                <div className="linkDocPreview-buttonBar" style={{ float: 'right' }}>
                    <Tooltip title={<div className="dash-tooltip">Next Link</div>} placement="top">
                        <div className="linkDocPreview-button" style={{ background: (this._props.hrefs?.length || 0) <= 1 ? 'gray' : 'green' }} onPointerDown={this.nextHref}>
                            <FontAwesomeIcon className="linkDocPreview-fa-icon" icon="chevron-right" color="white" size="sm" />
                        </div>
                    </Tooltip>
                </div>
            </div>
        );
    }

    @computed get docPreview() {
        return (!this._linkDoc || !this._targetDoc || !this._linkSrc) && !this._toolTipText ? null : (
            <div className="linkDocPreview-inner">
                {!this._props.showHeader ? null : this.previewHeader}
                <div
                    className="linkDocPreview-preview-wrapper"
                    onPointerDown={e =>
                        setupMoveUpEvents(
                            this,
                            e,
                            (moveEv, down) => {
                                if (Math.abs(moveEv.clientX - down[0]) + Math.abs(moveEv.clientY - down[1]) > 100) {
                                    DragManager.StartDocumentDrag([this._infoRef.current!], new DragManager.DocumentDragData([this._targetDoc!]), moveEv.pageX, moveEv.pageY);
                                    LinkInfo.Clear();
                                    return true;
                                }
                                return false;
                            },
                            emptyFunction,
                            this.followLink,
                            true
                        )
                    }
                    ref={this._infoRef}
                    style={{ maxHeight: this._toolTipText ? '100%' : undefined }}>
                    {this._toolTipText ? (
                        this._toolTipText
                    ) : (
                        <DocumentView
                            ref={r => {
                                const targetanchor = this._linkDoc && this._linkSrc && Doc.getOppositeAnchor(this._linkDoc, this._linkSrc);
                                targetanchor && this._targetDoc !== targetanchor && r?._props.focus?.(targetanchor, {});
                            }}
                            Document={this._targetDoc!}
                            moveDocument={returnFalse}
                            styleProvider={this._props.styleProvider}
                            containerViewPath={returnEmptyDocViewList}
                            ScreenToLocalTransform={Transform.Identity}
                            isDocumentActive={returnFalse}
                            isContentActive={returnFalse}
                            addDocument={returnFalse}
                            showTitle={returnEmptyString}
                            removeDocument={returnFalse}
                            addDocTab={returnFalse}
                            pinToPres={returnFalse}
                            dontRegisterView
                            childFilters={returnEmptyFilter}
                            childFiltersByRanges={returnEmptyFilter}
                            searchFilterDocs={returnEmptyDoclist}
                            renderDepth={0}
                            suppressSetHeight
                            PanelWidth={this.width}
                            PanelHeight={this.height}
                            pointerEvents={returnNone}
                            focus={emptyFunction}
                            whenChildContentsActiveChanged={returnFalse}
                            ignoreAutoHeight // need to ignore layout_autoHeight otherwise layout_autoHeight text boxes will expand beyond the preview panel size.
                            NativeWidth={Doc.NativeWidth(this._targetDoc) ? () => Doc.NativeWidth(this._targetDoc) : undefined}
                            NativeHeight={Doc.NativeHeight(this._targetDoc) ? () => Doc.NativeHeight(this._targetDoc) : undefined}
                        />
                    )}
                </div>
            </div>
        );
    }

    render() {
        const borders = 16; // 8px border on each side
        return (
            <div
                className="linkDocPreview"
                ref={this._linkDocRef}
                onPointerDown={this.followLinkPointerDown}
                style={{ borderColor: SnappingManager.userColor, left: this._props.location[0], top: this._props.location[1], width: this.width() + borders, height: this.height() + borders + (this._props.showHeader ? 37 : 0) }}>
                {this.docPreview}
            </div>
        );
    }
}
