import { Bezier } from 'bezier-js';
import { action, computed, IReactionDisposer, makeObservable, observable, reaction } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
import { Id } from '../../../fields/FieldSymbols';
import { DocCast, NumCast, StrCast } from '../../../fields/Types';
import { aggregateBounds, emptyFunction, lightOrDark, returnAlways, returnFalse, setupMoveUpEvents, Utils } from '../../../Utils';
import { DocumentManager } from '../../util/DocumentManager';
import { Transform } from '../../util/Transform';
import { CollectionFreeFormView } from '../collections/collectionFreeForm';
import { ViewBoxBaseComponent } from '../DocComponent';
import { StyleProp } from '../StyleProvider';
import { ComparisonBox } from './ComparisonBox';
import { FieldView, FieldViewProps } from './FieldView';
import './LinkBox.scss';
import { LinkDescriptionPopup } from './LinkDescriptionPopup';
import { LinkManager } from '../../util/LinkManager';

@observer
export class LinkBox extends ViewBoxBaseComponent<FieldViewProps>() {
    public static LayoutString(fieldKey: string = 'link') {
        return FieldView.LayoutString(LinkBox, fieldKey);
    }

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

    onClickScriptDisable = returnAlways;
    @computed get anchor1() {
        const anchor1 = DocCast(this.dataDoc.link_anchor_1);
        const anchor_1 = anchor1?.layout_unrendered ? DocCast(anchor1.annotationOn) : anchor1;
        return DocumentManager.Instance.getDocumentView(anchor_1, this.DocumentView?.().containerViewPath?.().lastElement());
    }
    @computed get anchor2() {
        const anchor2 = DocCast(this.dataDoc.link_anchor_2);
        const anchor_2 = anchor2?.layout_unrendered ? DocCast(anchor2.annotationOn) : anchor2;
        return DocumentManager.Instance.getDocumentView(anchor_2, this.DocumentView?.().containerViewPath?.().lastElement());
    }
    screenBounds = () => {
        if (this.layoutDoc._layout_isSvg && this.anchor1 && this.anchor2 && this.anchor1.CollectionFreeFormView) {
            const a_invXf = this.anchor1.screenToViewTransform().inverse();
            const b_invXf = this.anchor2.screenToViewTransform().inverse();
            const a_scrBds = { tl: a_invXf.transformPoint(0, 0), br: a_invXf.transformPoint(NumCast(this.anchor1.Document._width), NumCast(this.anchor1.Document._height)) };
            const b_scrBds = { tl: b_invXf.transformPoint(0, 0), br: b_invXf.transformPoint(NumCast(this.anchor2.Document._width), NumCast(this.anchor2.Document._height)) };

            const pts = [] as number[][];
            pts.push([(a_scrBds.tl[0] + a_scrBds.br[0]) / 2, (a_scrBds.tl[1] + a_scrBds.br[1]) / 2]);
            pts.push(Utils.getNearestPointInPerimeter(a_scrBds.tl[0], a_scrBds.tl[1], a_scrBds.br[0] - a_scrBds.tl[0], a_scrBds.br[1] - a_scrBds.tl[1], (b_scrBds.tl[0] + b_scrBds.br[0]) / 2, (b_scrBds.tl[1] + b_scrBds.br[1]) / 2));
            pts.push(Utils.getNearestPointInPerimeter(b_scrBds.tl[0], b_scrBds.tl[1], b_scrBds.br[0] - b_scrBds.tl[0], b_scrBds.br[1] - b_scrBds.tl[1], (a_scrBds.tl[0] + a_scrBds.br[0]) / 2, (a_scrBds.tl[1] + a_scrBds.br[1]) / 2));
            pts.push([(b_scrBds.tl[0] + b_scrBds.br[0]) / 2, (b_scrBds.tl[1] + b_scrBds.br[1]) / 2]);
            const agg = aggregateBounds(
                pts.map(pt => ({ x: pt[0], y: pt[1] })),
                0,
                0
            );
            return { left: agg.x, top: agg.y, right: agg.r, bottom: agg.b, center: undefined };
        }
        return undefined;
    };
    disposer: IReactionDisposer | undefined;
    componentDidMount() {
        this._props.setContentViewBox?.(this);
        this.disposer = reaction(
            () => {
                if (this.layoutDoc._layout_isSvg && (this.anchor1 || this.anchor2)?.CollectionFreeFormView) {
                    const a = (this.anchor1 ?? this.anchor2)!;
                    const b = (this.anchor2 ?? this.anchor1)!;

                    const parxf = this.DocumentView?.().containerViewPath?.().lastElement().ComponentView as CollectionFreeFormView;
                    const this_xf = parxf?.screenToFreeformContentsXf ?? Transform.Identity; //this.ScreenToLocalTransform();
                    const a_invXf = a.screenToViewTransform().inverse();
                    const b_invXf = b.screenToViewTransform().inverse();
                    const a_scrBds = { tl: a_invXf.transformPoint(0, 0), br: a_invXf.transformPoint(NumCast(a.Document._width), NumCast(a.Document._height)) };
                    const b_scrBds = { tl: b_invXf.transformPoint(0, 0), br: b_invXf.transformPoint(NumCast(b.Document._width), NumCast(b.Document._height)) };
                    const a_bds = { tl: this_xf.transformPoint(a_scrBds.tl[0], a_scrBds.tl[1]), br: this_xf.transformPoint(a_scrBds.br[0], a_scrBds.br[1]) };
                    const b_bds = { tl: this_xf.transformPoint(b_scrBds.tl[0], b_scrBds.tl[1]), br: this_xf.transformPoint(b_scrBds.br[0], b_scrBds.br[1]) };

                    const ppt1 = [(a_bds.tl[0] + a_bds.br[0]) / 2, (a_bds.tl[1] + a_bds.br[1]) / 2];
                    const pt1 = Utils.getNearestPointInPerimeter(a_bds.tl[0], a_bds.tl[1], a_bds.br[0] - a_bds.tl[0], a_bds.br[1] - a_bds.tl[1], (b_bds.tl[0] + b_bds.br[0]) / 2, (b_bds.tl[1] + b_bds.br[1]) / 2);
                    const pt2 = Utils.getNearestPointInPerimeter(b_bds.tl[0], b_bds.tl[1], b_bds.br[0] - b_bds.tl[0], b_bds.br[1] - b_bds.tl[1], (a_bds.tl[0] + a_bds.br[0]) / 2, (a_bds.tl[1] + a_bds.br[1]) / 2);
                    const ppt2 = [(b_bds.tl[0] + b_bds.br[0]) / 2, (b_bds.tl[1] + b_bds.br[1]) / 2];

                    const pts = [ppt1, pt1, pt2, ppt2].map(pt => [pt[0], pt[1]]);
                    const [lx, rx, ty, by] = [Math.min(pt1[0], pt2[0]), Math.max(pt1[0], pt2[0]), Math.min(pt1[1], pt2[1]), Math.max(pt1[1], pt2[1])];
                    return { pts, lx, rx, ty, by };
                }
                return undefined;
            },
            params => {
                this.renderProps = params;
                if (params) {
                    if (
                        Math.abs(params.lx - NumCast(this.layoutDoc.x)) > 1e-5 ||
                        Math.abs(params.ty - NumCast(this.layoutDoc.y)) > 1e-5 ||
                        Math.abs(params.rx - params.lx - NumCast(this.layoutDoc._width)) > 1e-5 ||
                        Math.abs(params.by - params.ty - NumCast(this.layoutDoc._height)) > 1e-5
                    ) {
                        this.layoutDoc.x = params?.lx;
                        this.layoutDoc.y = params?.ty;
                        this.layoutDoc._width = Math.max(1, params.rx - params?.lx);
                        this.layoutDoc._height = Math.max(1, params?.by - params?.ty);
                    }
                } else {
                    this.layoutDoc._width = Math.max(50, NumCast(this.layoutDoc._width));
                    this.layoutDoc._height = Math.max(50, NumCast(this.layoutDoc._height));
                }
            },
            { fireImmediately: true }
        );
    }

    select = (ctrlKey: boolean, shiftKey: boolean) => {
        LinkManager.Instance.currentLink = this.Document;
    };

    descriptionDown = (e: React.PointerEvent) => {
        setupMoveUpEvents(
            this,
            e,
            returnFalse,
            returnFalse,
            () => {
                LinkManager.Instance.currentLink = this.Document;
                LinkDescriptionPopup.Instance.popupX = e.clientX;
                LinkDescriptionPopup.Instance.popupY = e.clientY;
                LinkDescriptionPopup.Instance.display = true;

                const rect = document.body.getBoundingClientRect();
                if (LinkDescriptionPopup.Instance.popupX + 200 > rect.width) {
                    LinkDescriptionPopup.Instance.popupX -= 190;
                }
                if (LinkDescriptionPopup.Instance.popupY + 100 > rect.height) {
                    LinkDescriptionPopup.Instance.popupY -= 40;
                }
            },
            false
        );
    };
    componentWillUnmount(): void {
        this.disposer?.();
    }
    @observable renderProps: { lx: number; rx: number; ty: number; by: number; pts: number[][] } | undefined = undefined;
    render() {
        if (this.renderProps) {
            const highlight = this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.Highlighting);
            const highlightColor = highlight?.highlightIndex ? highlight?.highlightColor : undefined;
            const fontFamily = this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.FontFamily);
            const fontSize = this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.FontSize);
            const color = (c => (c !== 'transparent' ? c : undefined))(StrCast(this.layoutDoc.link_fontColor));

            const { pts, lx, ty, rx, by } = this.renderProps;
            const bez = new Bezier(pts.map(p => ({ x: p[0] - lx, y: p[1] - ty })));
            const { x, y } = bez.get(0.5);
            const linkDesc = StrCast(this.dataDoc.link_description) || ' ';
            const strokeWidth = NumCast(this.Document.stroke_width, 4);
            const dash = StrCast(this.Document.stroke_dash);
            const strokeDasharray = dash && Number(dash) ? String(strokeWidth * Number(dash)) : undefined;
            const pointerEvents = this._props.pointerEvents?.() === 'none' ? 'none' : 'all';
            const stroke = highlightColor ?? 'lightblue';
            return (
                <div className="linkBox">
                    <svg width={Math.max(100, rx - lx)} height={Math.max(100, by - ty)}>
                        <defs>
                            <filter x="0" y="0" width="1" height="1" id={`${this.Document[Id] + 'background'}`}>
                                <feFlood floodColor={`${highlightColor ?? StrCast(this.layoutDoc._backgroundColor, 'lightblue')}`} result="bg" />
                                <feMerge>
                                    <feMergeNode in="bg" />
                                    <feMergeNode in="SourceGraphic" />
                                </feMerge>
                            </filter>
                        </defs>
                        <path
                            style={{
                                pointerEvents: this._props.pointerEvents?.() === 'none' ? 'none' : 'visibleStroke', //
                                stroke,
                                strokeDasharray,
                                strokeWidth,
                            }}
                            d={`M ${bez.points[0].x} ${bez.points[0].y} C ${bez.points[1].x} ${bez.points[1].y}, 
                            ${bez.points[2].x} ${bez.points[2].y}, ${bez.points[3].x} ${bez.points[3].y}`}
                        />
                        {!linkDesc.trim().length ? (
                            <circle r={5} onPointerDown={this.descriptionDown} style={{ fill: stroke, pointerEvents }} cx={x} cy={y} />
                        ) : (
                            <text
                                onPointerDown={this.descriptionDown} //
                                filter={`url(#${this.Document[Id] + 'background'})`}
                                style={{ pointerEvents, background: stroke }}
                                x={x}
                                y={y}>
                                <tspan style={{ fontSize, fontFamily, strokeWidth: 0, background: stroke, fill: color || lightOrDark(stroke) }}>&nbsp;{linkDesc.substring(0, 50) + (linkDesc.length > 50 ? '...' : '')}&nbsp;</tspan>
                            </text>
                        )}
                    </svg>
                </div>
            );
        }
        return (
            <div className={`linkBox-container${this._props.isContentActive() ? '-interactive' : ''}`} style={{ background: this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.BackgroundColor) }}>
                <ComparisonBox
                    {...this._props} //
                    fieldKey="link_anchor"
                    setHeight={emptyFunction}
                    dontRegisterView={true}
                    renderDepth={this._props.renderDepth + 1}
                    addDocument={returnFalse}
                    removeDocument={returnFalse}
                    moveDocument={returnFalse}
                />
            </div>
        );
    }
}
