var __extends = (this && this.__extends) || (function () {
    var extendStatics = function (d, b) {
        extendStatics = Object.setPrototypeOf ||
            ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
            function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; };
        return extendStatics(d, b);
    };
    return function (d, b) {
        if (typeof b !== "function" && b !== null)
            throw new TypeError("Class extends value " + String(b) + " is not a constructor or null");
        extendStatics(d, b);
        function __() { this.constructor = d; }
        d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
    };
})();
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
    function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
    return new (P || (P = Promise))(function (resolve, reject) {
        function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
        function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
        function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
        step((generator = generator.apply(thisArg, _arguments || [])).next());
    });
};
var __generator = (this && this.__generator) || function (thisArg, body) {
    var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g = Object.create((typeof Iterator === "function" ? Iterator : Object).prototype);
    return g.next = verb(0), g["throw"] = verb(1), g["return"] = verb(2), typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
    function verb(n) { return function (v) { return step([n, v]); }; }
    function step(op) {
        if (f) throw new TypeError("Generator is already executing.");
        while (g && (g = 0, op[0] && (_ = 0)), _) try {
            if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
            if (y = 0, t) op = [op[0] & 2, t.value];
            switch (op[0]) {
                case 0: case 1: t = op; break;
                case 4: _.label++; return { value: op[1], done: false };
                case 5: _.label++; y = op[1]; op = [0]; continue;
                case 7: op = _.ops.pop(); _.trys.pop(); continue;
                default:
                    if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
                    if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
                    if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
                    if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
                    if (t[2]) _.ops.pop();
                    _.trys.pop(); continue;
            }
            op = body.call(thisArg, _);
        } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
        if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
    }
};
var __read = (this && this.__read) || function (o, n) {
    var m = typeof Symbol === "function" && o[Symbol.iterator];
    if (!m) return o;
    var i = m.call(o), r, ar = [], e;
    try {
        while ((n === void 0 || n-- > 0) && !(r = i.next()).done) ar.push(r.value);
    }
    catch (error) { e = { error: error }; }
    finally {
        try {
            if (r && !r.done && (m = i["return"])) m.call(i);
        }
        finally { if (e) throw e.error; }
    }
    return ar;
};
var __spreadArray = (this && this.__spreadArray) || function (to, from, pack) {
    if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) {
        if (ar || !(i in from)) {
            if (!ar) ar = Array.prototype.slice.call(from, 0, i);
            ar[i] = from[i];
        }
    }
    return to.concat(ar || Array.prototype.slice.call(from));
};
import { AcDbLine, AcGiLineWeight } from '@mlightcad/data-model';
import { AcEdCommand, AcEdCorsorType, AcEdOpenMode, AcEdPreviewJig, AcEdPromptPointOptions, AcEdPromptStatus, AcEdViewMode } from '../editor';
import { AcApI18n } from '../i18n';
import { colorToCssAlpha, cssColor, makeBadge, makeDot, makeLiveBadge, makeOverlayCanvas, measurementColor } from '../util';
import { registerMeasurementCleanup } from './AcApClearMeasurementsCmd';
/**
 * Rubber-band jig: shows a preview line from the last confirmed
 * vertex to the current cursor position and fires onMove on each update.
 */
var AcApMeasureAreaJig = /** @class */ (function (_super) {
    __extends(AcApMeasureAreaJig, _super);
    function AcApMeasureAreaJig(view, from, color, onMove) {
        var _this = _super.call(this, view) || this;
        _this._line = new AcDbLine(from, from);
        _this._line.color = color;
        _this._line.lineWeight = AcGiLineWeight.LineWeight070;
        _this._onMove = onMove;
        return _this;
    }
    Object.defineProperty(AcApMeasureAreaJig.prototype, "entity", {
        get: function () {
            return this._line;
        },
        enumerable: false,
        configurable: true
    });
    AcApMeasureAreaJig.prototype.update = function (p) {
        this._line.endPoint = p;
        this._onMove(p);
    };
    return AcApMeasureAreaJig;
}(AcEdPreviewJig));
/**
 * Returns true when segment (p1→p2) properly crosses segment (p3→p4).
 * Endpoint-touches are intentionally excluded so adjacent edges never trigger.
 */
function segmentsIntersect(p1, p2, p3, p4) {
    var d1x = p2.x - p1.x, d1y = p2.y - p1.y;
    var d2x = p4.x - p3.x, d2y = p4.y - p3.y;
    var denom = d1x * d2y - d1y * d2x;
    if (Math.abs(denom) < 1e-10)
        return false; // parallel
    var dx = p3.x - p1.x, dy = p3.y - p1.y;
    var t = (dx * d2y - dy * d2x) / denom;
    var u = (dx * d1y - dy * d1x) / denom;
    return t > 0 && t < 1 && u > 0 && u < 1;
}
/** Computes the area of a polygon using the shoelace (Gauss) formula. */
function shoelaceArea(pts) {
    var area = 0;
    var n = pts.length;
    for (var i = 0; i < n; i++) {
        var j = (i + 1) % n;
        area += pts[i].x * pts[j].y;
        area -= pts[j].x * pts[i].y;
    }
    return Math.abs(area) / 2;
}
/** Returns the arithmetic centroid of a set of world points. */
function centroid(pts) {
    var x = pts.reduce(function (s, p) { return s + p.x; }, 0) / pts.length;
    var y = pts.reduce(function (s, p) { return s + p.y; }, 0) / pts.length;
    return { x: x, y: y };
}
/** Draws a filled polygon on a full-viewport canvas overlay. */
function drawAreaOnCanvas(canvas, view, points, color) {
    var rect = view.canvas.getBoundingClientRect();
    var dpr = window.devicePixelRatio || 1;
    var w = Math.round(rect.width);
    var h = Math.round(rect.height);
    var origin = view.canvasToContainer({ x: 0, y: 0 });
    canvas.style.left = "".concat(origin.x, "px");
    canvas.style.top = "".concat(origin.y, "px");
    canvas.style.width = "".concat(w, "px");
    canvas.style.height = "".concat(h, "px");
    if (canvas.width !== w * dpr || canvas.height !== h * dpr) {
        canvas.width = w * dpr;
        canvas.height = h * dpr;
    }
    var ctx = canvas.getContext('2d');
    if (!ctx || points.length < 3)
        return;
    ctx.clearRect(0, 0, canvas.width, canvas.height);
    ctx.save();
    ctx.scale(dpr, dpr);
    var spts = points.map(function (p) { return view.worldToScreen(p); });
    ctx.beginPath();
    ctx.moveTo(spts[0].x, spts[0].y);
    for (var i = 1; i < spts.length; i++)
        ctx.lineTo(spts[i].x, spts[i].y);
    ctx.closePath();
    ctx.fillStyle = colorToCssAlpha(color, 0.2);
    ctx.fill();
    ctx.strokeStyle = cssColor(color);
    ctx.lineWidth = 2.5;
    ctx.stroke();
    ctx.restore();
}
/**
 * Command that measures the area of a polygon drawn by the user.
 *
 * The user clicks successive vertices; after each click the canvas overlay
 * updates with a semi-transparent blue fill and a dashed outline. The polygon
 * auto-closes when the user clicks near the first vertex (14 px threshold),
 * clicks near the last vertex, or draws a segment that crosses an existing
 * edge — matching AutoCAD's area measurement behaviour. Pressing ESC/Enter
 * also finalises the polygon.
 *
 * Persistent overlays are placed via {@link AcTrHtmlTransientManager} for dots
 * and badge. The filled area canvas is managed with a viewChanged listener
 * cleaned up via {@link registerMeasurementCleanup}.
 */
var AcApMeasureAreaCmd = /** @class */ (function (_super) {
    __extends(AcApMeasureAreaCmd, _super);
    function AcApMeasureAreaCmd() {
        var _this = _super.call(this) || this;
        _this.mode = AcEdOpenMode.Read;
        return _this;
    }
    AcApMeasureAreaCmd.prototype.execute = function (context) {
        return __awaiter(this, void 0, void 0, function () {
            var editor, color, points, fillCanvas, liveBadge;
            var _this = this;
            return __generator(this, function (_a) {
                switch (_a.label) {
                    case 0:
                        editor = context.view.editor;
                        color = measurementColor(context.doc.database);
                        points = [];
                        fillCanvas = makeOverlayCanvas(context.view.container);
                        liveBadge = makeLiveBadge(color);
                        return [4 /*yield*/, context.view.withMode(AcEdViewMode.SELECTION, function () {
                                return editor.withCursor(AcEdCorsorType.Crosshair, function () { return __awaiter(_this, void 0, void 0, function () {
                                    var drawPolygon, redrawOnViewChange, p1Result, p1, _loop_1, state_1, _a, area, persistCanvas, redrawPersist, htManager, id, mid;
                                    return __generator(this, function (_b) {
                                        switch (_b.label) {
                                            case 0:
                                                drawPolygon = function (cursor) {
                                                    var rect = context.view.canvas.getBoundingClientRect();
                                                    var dpr = window.devicePixelRatio || 1;
                                                    var w = Math.round(rect.width);
                                                    var h = Math.round(rect.height);
                                                    var origin = context.view.canvasToContainer({ x: 0, y: 0 });
                                                    fillCanvas.style.left = "".concat(origin.x, "px");
                                                    fillCanvas.style.top = "".concat(origin.y, "px");
                                                    fillCanvas.style.width = "".concat(w, "px");
                                                    fillCanvas.style.height = "".concat(h, "px");
                                                    if (fillCanvas.width !== w * dpr || fillCanvas.height !== h * dpr) {
                                                        fillCanvas.width = w * dpr;
                                                        fillCanvas.height = h * dpr;
                                                    }
                                                    var ctx = fillCanvas.getContext('2d');
                                                    if (!ctx || points.length < 1)
                                                        return;
                                                    ctx.clearRect(0, 0, fillCanvas.width, fillCanvas.height);
                                                    ctx.save();
                                                    ctx.scale(dpr, dpr);
                                                    var confirmedSpts = points.map(function (p) { return context.view.worldToScreen(p); });
                                                    var fillSpts = cursor
                                                        ? __spreadArray(__spreadArray([], __read(confirmedSpts), false), [context.view.worldToScreen(cursor)], false) : confirmedSpts;
                                                    if (fillSpts.length >= 3) {
                                                        ctx.beginPath();
                                                        ctx.moveTo(fillSpts[0].x, fillSpts[0].y);
                                                        for (var i = 1; i < fillSpts.length; i++)
                                                            ctx.lineTo(fillSpts[i].x, fillSpts[i].y);
                                                        ctx.closePath();
                                                        ctx.fillStyle = colorToCssAlpha(color, 0.2);
                                                        ctx.fill();
                                                    }
                                                    if (confirmedSpts.length >= 2) {
                                                        ctx.beginPath();
                                                        ctx.moveTo(confirmedSpts[0].x, confirmedSpts[0].y);
                                                        for (var i = 1; i < confirmedSpts.length; i++)
                                                            ctx.lineTo(confirmedSpts[i].x, confirmedSpts[i].y);
                                                        ctx.strokeStyle = cssColor(color);
                                                        ctx.lineWidth = 2.5;
                                                        ctx.setLineDash([8, 5]);
                                                        ctx.stroke();
                                                        ctx.setLineDash([]);
                                                    }
                                                    ctx.restore();
                                                };
                                                redrawOnViewChange = function () { return drawPolygon(); };
                                                context.view.events.viewChanged.addEventListener(redrawOnViewChange);
                                                return [4 /*yield*/, editor.getPoint(new AcEdPromptPointOptions(AcApI18n.t('jig.measureArea.firstPoint')))];
                                            case 1:
                                                p1Result = _b.sent();
                                                if (p1Result.status !== AcEdPromptStatus.OK)
                                                    return [2 /*return*/];
                                                p1 = p1Result.value;
                                                points.push(p1);
                                                drawPolygon();
                                                _b.label = 2;
                                            case 2:
                                                _b.trys.push([2, 6, , 7]);
                                                _loop_1 = function () {
                                                    var prompt_1, onMove, pResult, p, sp_1, snap, last, crosses, i;
                                                    return __generator(this, function (_c) {
                                                        switch (_c.label) {
                                                            case 0:
                                                                prompt_1 = new AcEdPromptPointOptions(AcApI18n.t('jig.measureArea.nextPoint'));
                                                                prompt_1.useBasePoint = true;
                                                                // Allow the user to press Enter (without typing coordinates) to
                                                                // finish picking vertices and close the area polygon.
                                                                prompt_1.allowNone = true;
                                                                onMove = function (cursor) {
                                                                    if (points.length < 2)
                                                                        return;
                                                                    var tempPts = __spreadArray(__spreadArray([], __read(points), false), [cursor], false);
                                                                    var area = shoelaceArea(tempPts);
                                                                    liveBadge.textContent = "~ ".concat(area.toFixed(3), " m\u00B2");
                                                                    liveBadge.style.display = '';
                                                                    var mid = centroid(tempPts);
                                                                    var rect = context.view.canvas.getBoundingClientRect();
                                                                    var sc = context.view.worldToScreen(mid);
                                                                    liveBadge.style.left = "".concat(sc.x + rect.left, "px");
                                                                    liveBadge.style.top = "".concat(sc.y + rect.top, "px");
                                                                    drawPolygon(cursor);
                                                                };
                                                                prompt_1.jig = new AcApMeasureAreaJig(context.view, points[points.length - 1], color, onMove);
                                                                return [4 /*yield*/, editor.getPoint(prompt_1)];
                                                            case 1:
                                                                pResult = _c.sent();
                                                                if (pResult.status !== AcEdPromptStatus.OK)
                                                                    return [2 /*return*/, "break"];
                                                                p = pResult.value;
                                                                liveBadge.style.display = 'none';
                                                                if (points.length >= 3) {
                                                                    sp_1 = context.view.worldToScreen(p);
                                                                    snap = function (anchor) {
                                                                        var sa = context.view.worldToScreen(anchor);
                                                                        var dx = sp_1.x - sa.x;
                                                                        var dy = sp_1.y - sa.y;
                                                                        return dx * dx + dy * dy <= 14 * 14;
                                                                    };
                                                                    if (snap(points[0]) || snap(points[points.length - 1]))
                                                                        return [2 /*return*/, "break"];
                                                                }
                                                                if (points.length >= 3) {
                                                                    last = points[points.length - 1];
                                                                    crosses = false;
                                                                    for (i = 0; i < points.length - 2; i++) {
                                                                        if (segmentsIntersect(last, p, points[i], points[i + 1])) {
                                                                            crosses = true;
                                                                            break;
                                                                        }
                                                                    }
                                                                    if (crosses)
                                                                        return [2 /*return*/, "break"];
                                                                }
                                                                points.push(p);
                                                                drawPolygon();
                                                                return [2 /*return*/];
                                                        }
                                                    });
                                                };
                                                _b.label = 3;
                                            case 3:
                                                if (!(points.length < 50)) return [3 /*break*/, 5];
                                                return [5 /*yield**/, _loop_1()];
                                            case 4:
                                                state_1 = _b.sent();
                                                if (state_1 === "break")
                                                    return [3 /*break*/, 5];
                                                return [3 /*break*/, 3];
                                            case 5: return [3 /*break*/, 7];
                                            case 6:
                                                _a = _b.sent();
                                                return [3 /*break*/, 7];
                                            case 7:
                                                // Clean up construction-phase elements
                                                liveBadge.remove();
                                                context.view.events.viewChanged.removeEventListener(redrawOnViewChange);
                                                fillCanvas.remove();
                                                if (points.length < 3)
                                                    return [2 /*return*/];
                                                area = shoelaceArea(points);
                                                persistCanvas = makeOverlayCanvas(context.view.container);
                                                drawAreaOnCanvas(persistCanvas, context.view, points, color);
                                                redrawPersist = function () {
                                                    return drawAreaOnCanvas(persistCanvas, context.view, points, color);
                                                };
                                                context.view.events.viewChanged.addEventListener(redrawPersist);
                                                htManager = context.view.htmlTransientManager;
                                                id = "area-".concat(Date.now());
                                                mid = centroid(points);
                                                htManager.add("".concat(id, "-badge"), makeBadge(color, "~ ".concat(area.toFixed(3), " m\u00B2")), mid, 'measurement');
                                                points.forEach(function (p, i) {
                                                    htManager.add("".concat(id, "-dot").concat(i), makeDot(color), p, 'measurement');
                                                });
                                                registerMeasurementCleanup(function () {
                                                    persistCanvas.remove();
                                                    context.view.events.viewChanged.removeEventListener(redrawPersist);
                                                    htManager.remove("".concat(id, "-badge"));
                                                    points.forEach(function (_, i) {
                                                        htManager.remove("".concat(id, "-dot").concat(i));
                                                    });
                                                });
                                                return [2 /*return*/];
                                        }
                                    });
                                }); });
                            })];
                    case 1:
                        _a.sent();
                        return [2 /*return*/];
                }
            });
        });
    };
    return AcApMeasureAreaCmd;
}(AcEdCommand));
export { AcApMeasureAreaCmd };
//# sourceMappingURL=AcApMeasureAreaCmd.js.map