import { DEFAULT_ROUGHNESS, StrokeStyle } from '../../consts.js';
import { Bound } from '../../utils/bound.js';
import { getBezierNearestPoint, } from '../../utils/curve.js';
import { linePolylineIntersects, polyLineNearestPoint, } from '../../utils/math-utils.js';
import { PointLocation } from '../../utils/point-location.js';
import { Vec } from '../../utils/vec.js';
import { SurfaceElement } from '../surface-element.js';
import { ConnectorEndpoint, ConnectorEndpointStyle, ConnectorMode, DEFAULT_FRONT_END_POINT_STYLE, DEFAULT_REAR_END_POINT_STYLE, } from './types.js';
import { renderArrow, renderCircle, renderDiamond, renderTriangle, } from './utils.js';
export class ConnectorElement extends SurfaceElement {
    constructor() {
        super(...arguments);
        this._path = [];
        this._xywh = `[0, 0, 0, 0]`;
        this._connectable = false;
    }
    // relative to it's xywh
    get path() {
        return this._path;
    }
    set path(p) {
        this._path = p;
    }
    get xywh() {
        return this._xywh;
    }
    set xywh(xywh) {
        this._xywh = xywh;
        this.renderer?.removeElement(this);
        this.renderer?.addElement(this);
    }
    get mode() {
        return this.yMap.get('mode');
    }
    get strokeWidth() {
        return this.yMap.get('strokeWidth');
    }
    get stroke() {
        return this.yMap.get('stroke');
    }
    get strokeStyle() {
        return this.yMap.get('strokeStyle');
    }
    get roughness() {
        return (this.yMap.get('roughness') ??
            DEFAULT_ROUGHNESS);
    }
    get rough() {
        return this.yMap.get('rough') ?? false;
    }
    get target() {
        return this.yMap.get('target');
    }
    get source() {
        return this.yMap.get('source');
    }
    get controllers() {
        return this.yMap.get('controllers');
    }
    get frontEndpointStyle() {
        return (this.yMap.get('frontEndpointStyle') ?? DEFAULT_FRONT_END_POINT_STYLE);
    }
    get rearEndpointStyle() {
        return (this.yMap.get('rearEndpointStyle') ??
            DEFAULT_REAR_END_POINT_STYLE);
    }
    get absolutePath() {
        const { x, y } = this;
        return this.path.map(p => p.clone().setVec([p[0] + x, p[1] + y]));
    }
    hitTest(x, y, options) {
        const point = this.mode === ConnectorMode.Curve
            ? getBezierNearestPoint(this.bezierParameters, [x, y])
            : polyLineNearestPoint(this.absolutePath, [x, y]);
        return (Vec.dist(point, [x, y]) < (options?.expand ? this.strokeWidth / 2 : 0) + 8);
    }
    containedByBounds(bounds) {
        return this.absolutePath.some(point => bounds.containsPoint(point));
    }
    getNearestPoint(point) {
        return polyLineNearestPoint(this.absolutePath, point);
    }
    intersectWithLine(start, end) {
        return linePolylineIntersects(start, end, this.absolutePath);
    }
    get bezierParameters() {
        const { absolutePath: p } = this;
        return [p[0], p[0].absOut, p[1].absIn, p[1]];
    }
    render(ctx, matrix, rc) {
        const { absolutePath: points, mode, frontEndpointStyle: frontEndpointStyle, rearEndpointStyle: rearEndpointStyle, } = this;
        // points might not be build yet in some senarios
        // eg. undo/redo, copy/paste
        if (!points.length || points.length < 2) {
            console.warn('connector points not ready yet, there is something wrong.');
            return;
        }
        const [x, y] = this.deserializeXYWH();
        ctx.setTransform(matrix.translateSelf(-x, -y));
        this._renderPoints(ctx, rc, points, this.strokeStyle === StrokeStyle.Dashed, mode === ConnectorMode.Curve);
        this._renderEndpoint(points, ctx, rc, ConnectorEndpoint.Front, frontEndpointStyle);
        this._renderEndpoint(points, ctx, rc, ConnectorEndpoint.Rear, rearEndpointStyle);
    }
    _renderPoints(ctx, rc, points, dash, curve) {
        const { seed, stroke, strokeWidth, roughness, rough } = this;
        const realStrokeColor = this.computedValue(stroke);
        if (rough) {
            const options = {
                seed,
                roughness,
                strokeLineDash: dash ? [12, 12] : undefined,
                stroke: realStrokeColor,
                strokeWidth,
            };
            if (curve) {
                const b = this.bezierParameters;
                rc.path(`M${b[0][0]},${b[0][1]} C${b[1][0]},${b[1][1]} ${b[2][0]},${b[2][1]}  ${b[3][0]},${b[3][1]} `, options);
            }
            else {
                rc.linearPath(points, options);
            }
        }
        else {
            ctx.save();
            ctx.strokeStyle = realStrokeColor;
            ctx.lineWidth = this.strokeWidth;
            ctx.lineJoin = 'round';
            dash && ctx.setLineDash([12, 12]);
            ctx.beginPath();
            if (curve) {
                points.forEach((point, index) => {
                    if (index === 0) {
                        ctx.moveTo(point[0], point[1]);
                    }
                    else {
                        const last = points[index - 1];
                        ctx.bezierCurveTo(last.absOut[0], last.absOut[1], point.absIn[0], point.absIn[1], point[0], point[1]);
                    }
                });
            }
            else {
                points.forEach((point, index) => {
                    if (index === 0) {
                        ctx.moveTo(point[0], point[1]);
                    }
                    else {
                        ctx.lineTo(point[0], point[1]);
                    }
                });
            }
            ctx.stroke();
            ctx.closePath();
            ctx.restore();
        }
    }
    _getArrowOptions(end) {
        const { stroke } = this;
        const realStrokeColor = this.computedValue(stroke);
        return {
            end,
            seed: this.seed,
            mode: this.mode,
            rough: this.rough,
            roughness: this.roughness,
            strokeColor: realStrokeColor,
            strokeWidth: this.strokeWidth,
            fillColor: realStrokeColor,
            fillStyle: 'solid',
            bezierParameters: this.bezierParameters,
        };
    }
    _renderEndpoint(location, ctx, rc, end, style) {
        const arrowOptions = this._getArrowOptions(end);
        switch (style) {
            case ConnectorEndpointStyle.Arrow:
                renderArrow(location, ctx, rc, arrowOptions);
                break;
            case ConnectorEndpointStyle.Triangle:
                renderTriangle(location, ctx, rc, arrowOptions);
                break;
            case ConnectorEndpointStyle.Circle:
                renderCircle(location, ctx, rc, arrowOptions);
                break;
            case ConnectorEndpointStyle.Diamond:
                renderDiamond(location, ctx, rc, arrowOptions);
                break;
        }
    }
    getRelativePointLocation(point) {
        return new PointLocation(Bound.deserialize(this.xywh).getRelativePoint(point));
    }
}
//# sourceMappingURL=connector-element.js.map