import React = require('react');
import { Bezier } from 'bezier-js';
import { computed, IReactionDisposer, observable, reaction } from 'mobx';
import { observer } from 'mobx-react';
import { Id } from '../../../fields/FieldSymbols';
import { DocCast, NumCast, StrCast } from '../../../fields/Types';
import { aggregateBounds, emptyFunction, returnAlways, returnFalse, 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';

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

    onClickScriptDisable = returnAlways;
    @computed get anchor1() {
        const anchor1 = DocCast(this.rootDoc.link_anchor_1);
        const anchor_1 = anchor1?.layout_unrendered ? DocCast(anchor1.annotationOn) : anchor1;
        return DocumentManager.Instance.getDocumentView(anchor_1, this.props.docViewPath()[this.props.docViewPath().length - 2]); // this.props.docViewPath().lastElement());
    }
    @computed get anchor2() {
        const anchor2 = DocCast(this.rootDoc.link_anchor_2);
        const anchor_2 = anchor2?.layout_unrendered ? DocCast(anchor2.annotationOn) : anchor2;
        return DocumentManager.Instance.getDocumentView(anchor_2, this.props.docViewPath()[this.props.docViewPath().length - 2]); // this.props.docViewPath().lastElement());
    }
    screenBounds = () => {
        if (this.layoutDoc._layout_isSvg && this.anchor1 && this.anchor2 && this.anchor1.props.CollectionFreeFormDocumentView?.().props.CollectionFreeFormView) {
            const a_invXf = this.anchor1.props.ScreenToLocalTransform().inverse();
            const b_invXf = this.anchor2.props.ScreenToLocalTransform().inverse();
            const a_scrBds = { tl: a_invXf.transformPoint(0, 0), br: a_invXf.transformPoint(NumCast(this.anchor1.rootDoc._width), NumCast(this.anchor1.rootDoc._height)) };
            const b_scrBds = { tl: b_invXf.transformPoint(0, 0), br: b_invXf.transformPoint(NumCast(this.anchor2.rootDoc._width), NumCast(this.anchor2.rootDoc._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.setContentView?.(this);
        this.disposer = reaction(
            () => {
                if (this.layoutDoc._layout_isSvg && (this.anchor1 || this.anchor2)?.props.CollectionFreeFormDocumentView?.().props.CollectionFreeFormView) {
                    const a = (this.anchor1 ?? this.anchor2)!;
                    const b = (this.anchor2 ?? this.anchor1)!;

                    const parxf = this.props.docViewPath()[this.props.docViewPath().length - 2].ComponentView as CollectionFreeFormView;
                    const this_xf = parxf?.screenToLocalXf ?? Transform.Identity; //this.props.ScreenToLocalTransform();
                    const a_invXf = a.props.ScreenToLocalTransform().inverse();
                    const b_invXf = b.props.ScreenToLocalTransform().inverse();
                    const a_scrBds = { tl: a_invXf.transformPoint(0, 0), br: a_invXf.transformPoint(NumCast(a.rootDoc._width), NumCast(a.rootDoc._height)) };
                    const b_scrBds = { tl: b_invXf.transformPoint(0, 0), br: b_invXf.transformPoint(NumCast(b.rootDoc._width), NumCast(b.rootDoc._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 = params.rx - params?.lx;
                        this.layoutDoc._height = params?.by - params?.ty;
                    }
                }
            },
            { fireImmediately: true }
        );
    }
    componentWillUnmount(): void {
        this.disposer?.();
    }
    @observable renderProps: { lx: number; rx: number; ty: number; by: number; pts: number[][] } | undefined;
    render() {
        if (this.renderProps) {
            const highlight = this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.Highlighting);
            const highlightColor = highlight?.highlightIndex ? highlight?.highlightColor : undefined;

            const bez = new Bezier(this.renderProps.pts.map(p => ({ x: p[0], y: p[1] })));
            const text = bez.get(0.5);
            const linkDesc = StrCast(this.rootDoc.link_description) || 'description';
            const strokeWidth = NumCast(this.rootDoc.stroke_width, 4);
            const dash = StrCast(this.rootDoc.stroke_dash);
            const strokeDasharray = dash && Number(dash) ? String(strokeWidth * Number(dash)) : undefined;
            const { pts, lx, ty, rx, by } = this.renderProps;
            return (
                <div style={{ transition: 'inherit', pointerEvents: 'none', position: 'absolute', width: '100%', height: '100%' }}>
                    <svg width={Math.max(100, rx - lx)} height={Math.max(100, by - ty)} style={{ transition: 'inherit', overflow: 'visible' }}>
                        <defs>
                            <filter x="0" y="0" width="1" height="1" id={`${this.rootDoc[Id] + 'background'}`}>
                                <feFlood floodColor={`${StrCast(this.rootDoc._backgroundColor, 'lightblue')}`} result="bg" />
                                <feMerge>
                                    <feMergeNode in="bg" />
                                    <feMergeNode in="SourceGraphic" />
                                </feMerge>
                            </filter>
                        </defs>
                        <path
                            className="collectionfreeformlinkview-linkLine"
                            style={{
                                pointerEvents: this.props.pointerEvents?.() === 'none' ? 'none' : 'visibleStroke', //
                                stroke: highlightColor ?? 'lightblue',
                                strokeDasharray,
                                strokeWidth,
                                transition: 'inherit',
                            }}
                            d={`M ${pts[1][0] - lx} ${pts[1][1] - ty} C ${pts[1][0] + pts[1][0] - pts[0][0] - lx} ${pts[1][1] + pts[1][1] - pts[0][1] - ty}, 
                            ${pts[2][0] + pts[2][0] - pts[3][0] - lx} ${pts[2][1] + pts[2][1] - pts[3][1] - ty}, ${pts[2][0] - lx} ${pts[2][1] - ty}`}
                        />
                        <text
                            filter={`url(#${this.rootDoc[Id] + 'background'})`}
                            style={{ pointerEvents: this.props.pointerEvents?.() === 'none' ? 'none' : 'all', textAnchor: 'middle', fontSize: '12', stroke: 'black' }}
                            x={text.x - lx}
                            y={text.y - ty}>
                            <tspan>&nbsp;</tspan>
                            <tspan dy="2">{linkDesc.substring(0, 50) + (linkDesc.length > 50 ? '...' : '')}</tspan>
                            <tspan dy="2">&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}
                    isContentActive={this.props.isContentActive}
                    addDocument={returnFalse}
                    removeDocument={returnFalse}
                    moveDocument={returnFalse}
                />
            </div>
        );
    }
}
