'use strict';

var vectorEditorTypes = require('./vectorEditorTypes.js');

/**
 * Boolean operations for vector shapes
 * Implements union, difference, intersection, and exclusion operations
 */
/* ────────────────────────────────────────────────────────────────
   SHAPE TO PATH CONVERSION
──────────────────────────────────────────────────────────────── */
/** Convert VShape to array of points representing its outline */
function shapeToPolygon(shape, resolution = 32) {
    const cx = shape.x + shape.w / 2;
    const cy = shape.y + shape.h / 2;
    const rx = shape.w / 2;
    const ry = shape.h / 2;
    switch (shape.type) {
        case "rect":
        case "roundrect":
            // For roundrect, we approximate with more points at corners
            if (shape.type === "roundrect" && shape.rx) {
                return roundRectToPolygon(shape.x, shape.y, shape.w, shape.h, shape.rx, resolution);
            }
            return [
                { x: shape.x, y: shape.y },
                { x: shape.x + shape.w, y: shape.y },
                { x: shape.x + shape.w, y: shape.y + shape.h },
                { x: shape.x, y: shape.y + shape.h },
            ];
        case "ellipse":
            const ellipsePts = [];
            for (let i = 0; i < resolution; i++) {
                const angle = (2 * Math.PI * i) / resolution;
                ellipsePts.push({
                    x: cx + rx * Math.cos(angle),
                    y: cy + ry * Math.sin(angle),
                });
            }
            return ellipsePts;
        case "polygon":
            const sides = shape.sides || 6;
            const polygonPts = [];
            const r = Math.min(rx, ry);
            for (let i = 0; i < sides; i++) {
                const angle = (2 * Math.PI * i) / sides - Math.PI / 2 + (shape.rotation * Math.PI) / 180;
                polygonPts.push({
                    x: cx + r * Math.cos(angle),
                    y: cy + r * Math.sin(angle),
                });
            }
            return polygonPts;
        case "star":
            const points = shape.sides || 5;
            const innerR = (shape.innerRadius || 0.4) * Math.min(rx, ry);
            const outerR = Math.min(rx, ry);
            const starPts = [];
            for (let i = 0; i < points * 2; i++) {
                const angle = (Math.PI * i) / points - Math.PI / 2 + (shape.rotation * Math.PI) / 180;
                const rad = i % 2 === 0 ? outerR : innerR;
                starPts.push({
                    x: cx + rad * Math.cos(angle),
                    y: cy + rad * Math.sin(angle),
                });
            }
            return starPts;
        case "polyline":
            return shape.points || [];
        default:
            // For line, arrow, text - return bounding box
            return [
                { x: shape.x, y: shape.y },
                { x: shape.x + shape.w, y: shape.y },
                { x: shape.x + shape.w, y: shape.y + shape.h },
                { x: shape.x, y: shape.y + shape.h },
            ];
    }
}
/** Generate rounded rectangle polygon */
function roundRectToPolygon(x, y, w, h, radius, resolution) {
    const r = Math.min(radius, w / 2, h / 2);
    const pts = [];
    const cornerRes = Math.max(4, Math.floor(resolution / 4));
    // Top-right corner
    for (let i = 0; i <= cornerRes; i++) {
        const angle = -Math.PI / 2 + (Math.PI / 2) * (i / cornerRes);
        pts.push({
            x: x + w - r + r * Math.cos(angle),
            y: y + r + r * Math.sin(angle),
        });
    }
    // Bottom-right corner
    for (let i = 0; i <= cornerRes; i++) {
        const angle = 0 + (Math.PI / 2) * (i / cornerRes);
        pts.push({
            x: x + w - r + r * Math.cos(angle),
            y: y + h - r + r * Math.sin(angle),
        });
    }
    // Bottom-left corner
    for (let i = 0; i <= cornerRes; i++) {
        const angle = Math.PI / 2 + (Math.PI / 2) * (i / cornerRes);
        pts.push({
            x: x + r + r * Math.cos(angle),
            y: y + h - r + r * Math.sin(angle),
        });
    }
    // Top-left corner
    for (let i = 0; i <= cornerRes; i++) {
        const angle = Math.PI + (Math.PI / 2) * (i / cornerRes);
        pts.push({
            x: x + r + r * Math.cos(angle),
            y: y + r + r * Math.sin(angle),
        });
    }
    return pts;
}
/* ────────────────────────────────────────────────────────────────
   POLYGON UTILITIES
──────────────────────────────────────────────────────────────── */
/** Check if point is inside polygon using ray casting */
function pointInPolygon(pt, polygon) {
    let inside = false;
    const n = polygon.length;
    for (let i = 0, j = n - 1; i < n; j = i++) {
        const xi = polygon[i].x, yi = polygon[i].y;
        const xj = polygon[j].x, yj = polygon[j].y;
        if (((yi > pt.y) !== (yj > pt.y)) &&
            (pt.x < (xj - xi) * (pt.y - yi) / (yj - yi) + xi)) {
            inside = !inside;
        }
    }
    return inside;
}
/** Get polygon centroid */
function polygonCentroid(polygon) {
    let cx = 0, cy = 0;
    for (const pt of polygon) {
        cx += pt.x;
        cy += pt.y;
    }
    return { x: cx / polygon.length, y: cy / polygon.length };
}
/** Get polygon bounding box */
function polygonBounds(polygon) {
    let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
    for (const pt of polygon) {
        minX = Math.min(minX, pt.x);
        minY = Math.min(minY, pt.y);
        maxX = Math.max(maxX, pt.x);
        maxY = Math.max(maxY, pt.y);
    }
    return { minX, minY, maxX, maxY };
}
/** Calculate polygon area (signed) */
function polygonArea(polygon) {
    let area = 0;
    const n = polygon.length;
    for (let i = 0; i < n; i++) {
        const j = (i + 1) % n;
        area += polygon[i].x * polygon[j].y;
        area -= polygon[j].x * polygon[i].y;
    }
    return area / 2;
}
/** Check if two line segments intersect and return intersection point */
function lineIntersection(p1, p2, p3, p4) {
    const d = (p4.y - p3.y) * (p2.x - p1.x) - (p4.x - p3.x) * (p2.y - p1.y);
    if (Math.abs(d) < 1e-10)
        return null;
    const ua = ((p4.x - p3.x) * (p1.y - p3.y) - (p4.y - p3.y) * (p1.x - p3.x)) / d;
    const ub = ((p2.x - p1.x) * (p1.y - p3.y) - (p2.y - p1.y) * (p1.x - p3.x)) / d;
    if (ua < 0 || ua > 1 || ub < 0 || ub > 1)
        return null;
    return {
        x: p1.x + ua * (p2.x - p1.x),
        y: p1.y + ua * (p2.y - p1.y),
    };
}
/** Find all intersection points between two polygons */
function findIntersections(polyA, polyB) {
    const intersections = [];
    for (let i = 0; i < polyA.length; i++) {
        const a1 = polyA[i];
        const a2 = polyA[(i + 1) % polyA.length];
        for (let j = 0; j < polyB.length; j++) {
            const b1 = polyB[j];
            const b2 = polyB[(j + 1) % polyB.length];
            const pt = lineIntersection(a1, a2, b1, b2);
            if (pt) {
                intersections.push(pt);
            }
        }
    }
    return intersections;
}
/* ────────────────────────────────────────────────────────────────
   SUTHERLAND-HODGMAN CLIPPING (for intersection)
──────────────────────────────────────────────────────────────── */
/** Clip polygon against a single edge using Sutherland-Hodgman */
function clipPolygonEdge(polygon, edgeStart, edgeEnd) {
    if (polygon.length === 0)
        return [];
    const output = [];
    const n = polygon.length;
    // Check which side of edge a point is on
    const inside = (p) => {
        return (edgeEnd.x - edgeStart.x) * (p.y - edgeStart.y) -
            (edgeEnd.y - edgeStart.y) * (p.x - edgeStart.x) >= 0;
    };
    for (let i = 0; i < n; i++) {
        const curr = polygon[i];
        const prev = polygon[(i + n - 1) % n];
        const currInside = inside(curr);
        const prevInside = inside(prev);
        if (currInside) {
            if (!prevInside) {
                // Entering - add intersection
                const pt = lineIntersection(prev, curr, edgeStart, edgeEnd);
                if (pt)
                    output.push(pt);
            }
            output.push(curr);
        }
        else if (prevInside) {
            // Leaving - add intersection
            const pt = lineIntersection(prev, curr, edgeStart, edgeEnd);
            if (pt)
                output.push(pt);
        }
    }
    return output;
}
/** Clip subject polygon by clip polygon using Sutherland-Hodgman */
function clipPolygon(subject, clip) {
    let output = [...subject];
    for (let i = 0; i < clip.length; i++) {
        if (output.length === 0)
            break;
        const edgeStart = clip[i];
        const edgeEnd = clip[(i + 1) % clip.length];
        output = clipPolygonEdge(output, edgeStart, edgeEnd);
    }
    return output;
}
/* ────────────────────────────────────────────────────────────────
   BOOLEAN OPERATIONS
──────────────────────────────────────────────────────────────── */
/** Union of two polygons (approximation using convex hull on combined points + edge walking) */
function unionPolygons(polyA, polyB) {
    // Simple approach: collect vertices from both polygons that are outside the other
    // plus intersection points, then create convex hull or walk the outline
    const result = [];
    // Add points from A that are outside B or on the boundary
    for (const pt of polyA) {
        if (!pointInPolygon(pt, polyB)) {
            result.push(pt);
        }
    }
    // Add points from B that are outside A
    for (const pt of polyB) {
        if (!pointInPolygon(pt, polyA)) {
            result.push(pt);
        }
    }
    // Add intersection points
    result.push(...findIntersections(polyA, polyB));
    if (result.length < 3) {
        // One polygon is inside the other - return the larger one
        const areaA = Math.abs(polygonArea(polyA));
        const areaB = Math.abs(polygonArea(polyB));
        return areaA > areaB ? [...polyA] : [...polyB];
    }
    // Create convex hull of all points
    return convexHull(result);
}
/** Intersection of two polygons */
function intersectPolygons(polyA, polyB) {
    // Use Sutherland-Hodgman clipping
    // Ensure polygons are counterclockwise
    const areaA = polygonArea(polyA);
    const areaB = polygonArea(polyB);
    const normA = areaA < 0 ? [...polyA].reverse() : polyA;
    const normB = areaB < 0 ? [...polyB].reverse() : polyB;
    return clipPolygon(normA, normB);
}
/** Difference: A - B */
function differencePolygons(polyA, polyB) {
    // Simple approach: A minus B
    // Get points from A that are outside B
    const result = [];
    for (const pt of polyA) {
        if (!pointInPolygon(pt, polyB)) {
            result.push(pt);
        }
    }
    // Add intersection points
    const intersections = findIntersections(polyA, polyB);
    result.push(...intersections);
    if (result.length < 3) {
        // B completely covers A
        return [];
    }
    // Sort points to form proper polygon
    return sortPolygonPoints(result);
}
/** XOR / Exclusion: (A ∪ B) - (A ∩ B) */
function exclusionPolygons(polyA, polyB) {
    // Returns potentially multiple polygons
    const intersection = intersectPolygons(polyA, polyB);
    if (intersection.length < 3) {
        // No overlap, return both
        return [polyA, polyB];
    }
    // Return difference of each with intersection
    const diffA = differencePolygons(polyA, polyB);
    const diffB = differencePolygons(polyB, polyA);
    const results = [];
    if (diffA.length >= 3)
        results.push(diffA);
    if (diffB.length >= 3)
        results.push(diffB);
    return results;
}
/** Sort polygon points in CCW order around centroid */
function sortPolygonPoints(points) {
    if (points.length < 3)
        return points;
    const center = polygonCentroid(points);
    return [...points].sort((a, b) => {
        const angleA = Math.atan2(a.y - center.y, a.x - center.x);
        const angleB = Math.atan2(b.y - center.y, b.x - center.x);
        return angleA - angleB;
    });
}
/** Compute convex hull using Graham scan */
function convexHull(points) {
    if (points.length < 3)
        return points;
    // Find bottom-most point
    let start = 0;
    for (let i = 1; i < points.length; i++) {
        if (points[i].y < points[start].y ||
            (points[i].y === points[start].y && points[i].x < points[start].x)) {
            start = i;
        }
    }
    const pivot = points[start];
    // Sort by polar angle
    const sorted = points
        .filter((_, i) => i !== start)
        .map(p => ({
        point: p,
        angle: Math.atan2(p.y - pivot.y, p.x - pivot.x),
        dist: Math.hypot(p.x - pivot.x, p.y - pivot.y),
    }))
        .sort((a, b) => a.angle - b.angle || a.dist - b.dist)
        .map(p => p.point);
    // Graham scan
    const hull = [pivot];
    for (const pt of sorted) {
        while (hull.length > 1) {
            const a = hull[hull.length - 2];
            const b = hull[hull.length - 1];
            const cross = (b.x - a.x) * (pt.y - a.y) - (b.y - a.y) * (pt.x - a.x);
            if (cross > 0)
                break;
            hull.pop();
        }
        hull.push(pt);
    }
    return hull;
}
/* ────────────────────────────────────────────────────────────────
   PUBLIC API
──────────────────────────────────────────────────────────────── */
/** Perform boolean operation on two shapes */
function booleanOperation(shapeA, shapeB, operation) {
    const polyA = shapeToPolygon(shapeA);
    const polyB = shapeToPolygon(shapeB);
    let resultPolygons;
    switch (operation) {
        case "union":
            resultPolygons = [unionPolygons(polyA, polyB)];
            break;
        case "intersection":
            const intersection = intersectPolygons(polyA, polyB);
            if (intersection.length < 3)
                return null;
            resultPolygons = [intersection];
            break;
        case "difference":
            const difference = differencePolygons(polyA, polyB);
            if (difference.length < 3)
                return null;
            resultPolygons = [difference];
            break;
        case "exclusion":
            resultPolygons = exclusionPolygons(polyA, polyB);
            if (resultPolygons.length === 0)
                return null;
            break;
        default:
            return null;
    }
    // Convert resulting polygons back to VShapes
    const shapes = resultPolygons
        .filter(poly => poly.length >= 3)
        .map(poly => polygonToShape(poly, shapeA));
    if (shapes.length === 0)
        return null;
    if (shapes.length === 1)
        return shapes[0];
    return shapes;
}
/** Convert polygon points back to a VShape */
function polygonToShape(polygon, template) {
    const bounds = polygonBounds(polygon);
    return {
        id: vectorEditorTypes.sid(),
        type: "polyline",
        x: bounds.minX,
        y: bounds.minY,
        w: bounds.maxX - bounds.minX,
        h: bounds.maxY - bounds.minY,
        points: polygon.map(pt => ({
            x: pt.x - bounds.minX,
            y: pt.y - bounds.minY,
        })),
        fill: template.fill,
        stroke: template.stroke,
        strokeWidth: template.strokeWidth,
        opacity: template.opacity,
        rotation: 0,
    };
}
/** Check if two shapes overlap */
function shapesOverlap(shapeA, shapeB) {
    const polyA = shapeToPolygon(shapeA);
    const polyB = shapeToPolygon(shapeB);
    // Check if any point of A is inside B
    for (const pt of polyA) {
        if (pointInPolygon(pt, polyB))
            return true;
    }
    // Check if any point of B is inside A
    for (const pt of polyB) {
        if (pointInPolygon(pt, polyA))
            return true;
    }
    // Check for edge intersections
    return findIntersections(polyA, polyB).length > 0;
}
/** Flatten a polyline to individual segment shapes (for outline stroke) */
function flattenToOutline(shape, strokeWidth) {
    const sw = strokeWidth !== null && strokeWidth !== void 0 ? strokeWidth : shape.strokeWidth;
    const points = shape.points || shapeToPolygon(shape);
    // Create offset outline
    const halfW = sw / 2;
    const outer = [];
    const inner = [];
    for (let i = 0; i < points.length; i++) {
        const curr = points[i];
        const prev = points[(i + points.length - 1) % points.length];
        const next = points[(i + 1) % points.length];
        // Calculate perpendicular direction
        const dx1 = curr.x - prev.x;
        const dy1 = curr.y - prev.y;
        const dx2 = next.x - curr.x;
        const dy2 = next.y - curr.y;
        // Average normal
        const len1 = Math.hypot(dx1, dy1) || 1;
        const len2 = Math.hypot(dx2, dy2) || 1;
        const nx = (-dy1 / len1 - dy2 / len2) / 2;
        const ny = (dx1 / len1 + dx2 / len2) / 2;
        const nlen = Math.hypot(nx, ny) || 1;
        outer.push({
            x: curr.x + (nx / nlen) * halfW,
            y: curr.y + (ny / nlen) * halfW,
        });
        inner.push({
            x: curr.x - (nx / nlen) * halfW,
            y: curr.y - (ny / nlen) * halfW,
        });
    }
    // Combine outer + reversed inner
    const outline = [...outer, ...inner.reverse()];
    const bounds = polygonBounds(outline);
    return {
        id: vectorEditorTypes.sid(),
        type: "polyline",
        x: bounds.minX,
        y: bounds.minY,
        w: bounds.maxX - bounds.minX,
        h: bounds.maxY - bounds.minY,
        points: outline.map(pt => ({
            x: pt.x - bounds.minX,
            y: pt.y - bounds.minY,
        })),
        fill: shape.stroke, // Fill with original stroke color
        stroke: "transparent",
        strokeWidth: 0,
        opacity: shape.opacity,
        rotation: 0,
    };
}

exports.booleanOperation = booleanOperation;
exports.flattenToOutline = flattenToOutline;
exports.shapesOverlap = shapesOverlap;
//# sourceMappingURL=vectorBooleanOps.js.map
