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 };
    }
};
import { AcDbCircle, AcGePoint3d } from '@mlightcad/data-model';
import { AcApDocManager } from '../app';
import { AcEdCommand, AcEdOpenMode, AcEdPreviewJig, AcEdPromptDistanceOptions, AcEdPromptPointOptions, AcEdPromptStateMachine, AcEdPromptStatus } from '../editor';
import { AcApI18n } from '../i18n';
var EPSILON = 1e-9;
function createFallbackCircle(point) {
    return {
        center: { x: point.x, y: point.y, z: 0 },
        radius: 1e-6
    };
}
function addKeyword(prompt, key) {
    prompt.keywords.add(AcApI18n.t("jig.circle.keywords.".concat(key, ".display")), AcApI18n.t("jig.circle.keywords.".concat(key, ".global")), AcApI18n.t("jig.circle.keywords.".concat(key, ".local")));
}
function applyCircleDefinition(entity, definition) {
    entity.center = definition.center;
    entity.radius = definition.radius;
}
function createCircleFromCenterRadius(center, radius) {
    if (!Number.isFinite(radius) || radius <= EPSILON)
        return undefined;
    return {
        center: { x: center.x, y: center.y, z: 0 },
        radius: radius
    };
}
function createCircleFromTwoPoints(first, second) {
    var dx = second.x - first.x;
    var dy = second.y - first.y;
    var diameter = Math.hypot(dx, dy);
    if (diameter <= EPSILON)
        return undefined;
    return {
        center: {
            x: (first.x + second.x) / 2,
            y: (first.y + second.y) / 2,
            z: 0
        },
        radius: diameter / 2
    };
}
function createCircleFromThreePoints(p1, p2, p3) {
    var x1 = p1.x;
    var y1 = p1.y;
    var x2 = p2.x;
    var y2 = p2.y;
    var x3 = p3.x;
    var y3 = p3.y;
    var d = 2 * (x1 * (y2 - y3) + x2 * (y3 - y1) + x3 * (y1 - y2));
    if (Math.abs(d) <= EPSILON)
        return undefined;
    var ux = ((x1 * x1 + y1 * y1) * (y2 - y3) +
        (x2 * x2 + y2 * y2) * (y3 - y1) +
        (x3 * x3 + y3 * y3) * (y1 - y2)) /
        d;
    var uy = ((x1 * x1 + y1 * y1) * (x3 - x2) +
        (x2 * x2 + y2 * y2) * (x1 - x3) +
        (x3 * x3 + y3 * y3) * (x2 - x1)) /
        d;
    var radius = Math.hypot(ux - x1, uy - y1);
    if (!Number.isFinite(radius) || radius <= EPSILON)
        return undefined;
    return {
        center: { x: ux, y: uy, z: 0 },
        radius: radius
    };
}
/**
 * Dynamic radius preview jig used by center-based circle workflows.
 */
var AcApCircleJig = /** @class */ (function (_super) {
    __extends(AcApCircleJig, _super);
    /**
     * Creates a circle jig.
     *
     * @param view - The associated view
     */
    function AcApCircleJig(view, center) {
        var _this = _super.call(this, view) || this;
        _this._circle = new AcDbCircle(center, 0);
        return _this;
    }
    Object.defineProperty(AcApCircleJig.prototype, "entity", {
        /**
         * Gets transient circle entity displayed by the jig.
         */
        get: function () {
            return this._circle;
        },
        enumerable: false,
        configurable: true
    });
    /**
     * Updates preview radius.
     *
     * @param radius - Dynamic radius value from prompt input.
     */
    AcApCircleJig.prototype.update = function (radius) {
        this._circle.radius = radius;
    };
    return AcApCircleJig;
}(AcEdPreviewJig));
export { AcApCircleJig };
/**
 * Dynamic point-based circle preview jig used by 2P/3P workflows.
 */
var AcApCircleDynamicJig = /** @class */ (function (_super) {
    __extends(AcApCircleDynamicJig, _super);
    /**
     * Creates dynamic circle preview jig.
     *
     * @param view - Active editor view.
     * @param builder - Cursor point to circle definition resolver.
     * @param fallback - Initial valid fallback circle.
     */
    function AcApCircleDynamicJig(view, builder, fallback) {
        var _this = _super.call(this, view) || this;
        _this._circle = new AcDbCircle(fallback.center, fallback.radius);
        _this._builder = builder;
        return _this;
    }
    Object.defineProperty(AcApCircleDynamicJig.prototype, "entity", {
        /**
         * Gets transient circle entity displayed by the jig.
         */
        get: function () {
            return this._circle;
        },
        enumerable: false,
        configurable: true
    });
    /**
     * Recomputes preview circle from current cursor point.
     *
     * @param point - Current cursor/input point.
     */
    AcApCircleDynamicJig.prototype.update = function (point) {
        var definition = this._builder(point);
        if (!definition)
            return;
        applyCircleDefinition(this._circle, definition);
    };
    return AcApCircleDynamicJig;
}(AcEdPreviewJig));
/**
 * Command to create one circle.
 */
var AcApCircleCmd = /** @class */ (function (_super) {
    __extends(AcApCircleCmd, _super);
    /**
     * Creates CIRCLE command instance.
     */
    function AcApCircleCmd() {
        var _this = _super.call(this) || this;
        _this.mode = AcEdOpenMode.Write;
        return _this;
    }
    /**
     * Command entry point.
     *
     * Supports center/radius, center/diameter, two-point and three-point
     * circle creation through a prompt state machine.
     *
     * @param context - Current application/document context.
     */
    AcApCircleCmd.prototype.execute = function (context) {
        return __awaiter(this, void 0, void 0, function () {
            var circle, EntryState, TwoPointFirstState, TwoPointSecondState, ThreePointFirstState, ThreePointSecondState, ThreePointThirdState, machine;
            return __generator(this, function (_a) {
                switch (_a.label) {
                    case 0:
                        EntryState = /** @class */ (function () {
                            /**
                             * Creates entry state handler.
                             *
                             * @param cmd - Owning CIRCLE command instance.
                             * @param machine - Prompt state machine used for state transitions.
                             */
                            function EntryState(cmd, machine) {
                                this.cmd = cmd;
                                this.machine = machine;
                            }
                            /**
                             * Builds first prompt for center-or-options input.
                             *
                             * @returns Configured point prompt with `3P` and `2P` keywords.
                             */
                            EntryState.prototype.buildPrompt = function () {
                                var prompt = new AcEdPromptPointOptions(AcApI18n.t('jig.circle.centerOrOptions'));
                                addKeyword(prompt, 'threeP');
                                addKeyword(prompt, 'twoP');
                                return prompt;
                            };
                            /**
                             * Handles entry step result and dispatches to selected branch.
                             *
                             * @param result - Point prompt result.
                             * @returns State-machine step result.
                             */
                            EntryState.prototype.handleResult = function (result) {
                                return __awaiter(this, void 0, void 0, function () {
                                    var keyword;
                                    var _a;
                                    return __generator(this, function (_b) {
                                        switch (_b.label) {
                                            case 0:
                                                if (!(result.status === AcEdPromptStatus.OK)) return [3 /*break*/, 2];
                                                return [4 /*yield*/, this.cmd.runCenterFlow(context, result.value)];
                                            case 1:
                                                circle = _b.sent();
                                                return [2 /*return*/, 'finish'];
                                            case 2:
                                                if (result.status !== AcEdPromptStatus.Keyword)
                                                    return [2 /*return*/, 'finish'];
                                                keyword = (_a = result.stringResult) !== null && _a !== void 0 ? _a : '';
                                                if (keyword === '3P') {
                                                    this.machine.setState(new ThreePointFirstState(this.cmd, this.machine));
                                                    return [2 /*return*/, 'continue'];
                                                }
                                                if (keyword === '2P') {
                                                    this.machine.setState(new TwoPointFirstState(this.cmd, this.machine));
                                                    return [2 /*return*/, 'continue'];
                                                }
                                                return [2 /*return*/, 'finish'];
                                        }
                                    });
                                });
                            };
                            return EntryState;
                        }());
                        TwoPointFirstState = /** @class */ (function () {
                            /**
                             * Creates 2P first-point state handler.
                             *
                             * @param cmd - Owning CIRCLE command instance.
                             * @param machine - Prompt state machine used for state transitions.
                             */
                            function TwoPointFirstState(cmd, machine) {
                                this.cmd = cmd;
                                this.machine = machine;
                            }
                            /**
                             * Builds prompt for first 2P endpoint.
                             *
                             * @returns Configured point prompt.
                             */
                            TwoPointFirstState.prototype.buildPrompt = function () {
                                return new AcEdPromptPointOptions(AcApI18n.t('jig.circle.twoPointFirst'));
                            };
                            /**
                             * Handles first 2P point and moves to second-point state.
                             *
                             * @param result - Point prompt result.
                             * @returns State-machine step result.
                             */
                            TwoPointFirstState.prototype.handleResult = function (result) {
                                return __awaiter(this, void 0, void 0, function () {
                                    return __generator(this, function (_a) {
                                        if (result.status !== AcEdPromptStatus.OK)
                                            return [2 /*return*/, 'finish'];
                                        this.machine.setState(new TwoPointSecondState(this.cmd, result.value));
                                        return [2 /*return*/, 'continue'];
                                    });
                                });
                            };
                            return TwoPointFirstState;
                        }());
                        TwoPointSecondState = /** @class */ (function () {
                            /**
                             * Creates 2P second-point state handler.
                             *
                             * @param cmd - Owning CIRCLE command instance.
                             * @param firstPoint - First 2P endpoint captured in previous step.
                             */
                            function TwoPointSecondState(cmd, firstPoint) {
                                this.cmd = cmd;
                                this.firstPoint = firstPoint;
                            }
                            /**
                             * Builds prompt for second 2P endpoint with dynamic preview.
                             *
                             * @returns Configured point prompt.
                             */
                            TwoPointSecondState.prototype.buildPrompt = function () {
                                var _this = this;
                                var prompt = new AcEdPromptPointOptions(AcApI18n.t('jig.circle.twoPointSecond'));
                                prompt.useBasePoint = true;
                                prompt.useDashedLine = true;
                                prompt.basePoint = new AcGePoint3d(this.firstPoint);
                                prompt.jig = new AcApCircleDynamicJig(context.view, function (point) { return createCircleFromTwoPoints(_this.firstPoint, point); }, createFallbackCircle(this.firstPoint));
                                return prompt;
                            };
                            /**
                             * Handles second 2P point and finalizes circle output.
                             *
                             * @param result - Point prompt result.
                             * @returns State-machine step result.
                             */
                            TwoPointSecondState.prototype.handleResult = function (result) {
                                return __awaiter(this, void 0, void 0, function () {
                                    return __generator(this, function (_a) {
                                        if (result.status === AcEdPromptStatus.OK) {
                                            circle = this.cmd.createFromTwoPoints(this.firstPoint, result.value);
                                        }
                                        return [2 /*return*/, 'finish'];
                                    });
                                });
                            };
                            return TwoPointSecondState;
                        }());
                        ThreePointFirstState = /** @class */ (function () {
                            /**
                             * Creates 3P first-point state handler.
                             *
                             * @param cmd - Owning CIRCLE command instance.
                             * @param machine - Prompt state machine used for state transitions.
                             */
                            function ThreePointFirstState(cmd, machine) {
                                this.cmd = cmd;
                                this.machine = machine;
                            }
                            /**
                             * Builds prompt for first 3P point.
                             *
                             * @returns Configured point prompt.
                             */
                            ThreePointFirstState.prototype.buildPrompt = function () {
                                return new AcEdPromptPointOptions(AcApI18n.t('jig.circle.threePointFirst'));
                            };
                            /**
                             * Handles first 3P point and advances to second-point state.
                             *
                             * @param result - Point prompt result.
                             * @returns State-machine step result.
                             */
                            ThreePointFirstState.prototype.handleResult = function (result) {
                                return __awaiter(this, void 0, void 0, function () {
                                    return __generator(this, function (_a) {
                                        if (result.status !== AcEdPromptStatus.OK)
                                            return [2 /*return*/, 'finish'];
                                        this.machine.setState(new ThreePointSecondState(this.cmd, this.machine, result.value));
                                        return [2 /*return*/, 'continue'];
                                    });
                                });
                            };
                            return ThreePointFirstState;
                        }());
                        ThreePointSecondState = /** @class */ (function () {
                            /**
                             * Creates 3P second-point state handler.
                             *
                             * @param cmd - Owning CIRCLE command instance.
                             * @param machine - Prompt state machine used for state transitions.
                             * @param firstPoint - First 3P point captured in previous step.
                             */
                            function ThreePointSecondState(cmd, machine, firstPoint) {
                                this.cmd = cmd;
                                this.machine = machine;
                                this.firstPoint = firstPoint;
                            }
                            /**
                             * Builds prompt for second 3P point.
                             *
                             * @returns Configured point prompt.
                             */
                            ThreePointSecondState.prototype.buildPrompt = function () {
                                var prompt = new AcEdPromptPointOptions(AcApI18n.t('jig.circle.threePointSecond'));
                                prompt.useBasePoint = true;
                                prompt.useDashedLine = true;
                                prompt.basePoint = new AcGePoint3d(this.firstPoint);
                                return prompt;
                            };
                            /**
                             * Handles second 3P point and advances to third-point state.
                             *
                             * @param result - Point prompt result.
                             * @returns State-machine step result.
                             */
                            ThreePointSecondState.prototype.handleResult = function (result) {
                                return __awaiter(this, void 0, void 0, function () {
                                    return __generator(this, function (_a) {
                                        if (result.status !== AcEdPromptStatus.OK)
                                            return [2 /*return*/, 'finish'];
                                        this.machine.setState(new ThreePointThirdState(this.cmd, this.firstPoint, result.value));
                                        return [2 /*return*/, 'continue'];
                                    });
                                });
                            };
                            return ThreePointSecondState;
                        }());
                        ThreePointThirdState = /** @class */ (function () {
                            /**
                             * Creates 3P third-point state handler.
                             *
                             * @param cmd - Owning CIRCLE command instance.
                             * @param firstPoint - First 3P point.
                             * @param secondPoint - Second 3P point.
                             */
                            function ThreePointThirdState(cmd, firstPoint, secondPoint) {
                                this.cmd = cmd;
                                this.firstPoint = firstPoint;
                                this.secondPoint = secondPoint;
                            }
                            /**
                             * Builds prompt for final 3P point with dynamic preview.
                             *
                             * @returns Configured point prompt.
                             */
                            ThreePointThirdState.prototype.buildPrompt = function () {
                                var _this = this;
                                var prompt = new AcEdPromptPointOptions(AcApI18n.t('jig.circle.threePointThird'));
                                prompt.useBasePoint = true;
                                prompt.useDashedLine = true;
                                prompt.basePoint = new AcGePoint3d(this.secondPoint);
                                prompt.jig = new AcApCircleDynamicJig(context.view, function (point) {
                                    return createCircleFromThreePoints(_this.firstPoint, _this.secondPoint, point);
                                }, createFallbackCircle(this.firstPoint));
                                return prompt;
                            };
                            /**
                             * Handles final 3P point and finalizes circle output.
                             *
                             * @param result - Point prompt result.
                             * @returns State-machine step result.
                             */
                            ThreePointThirdState.prototype.handleResult = function (result) {
                                return __awaiter(this, void 0, void 0, function () {
                                    return __generator(this, function (_a) {
                                        if (result.status === AcEdPromptStatus.OK) {
                                            circle = this.cmd.createFromThreePoints(this.firstPoint, this.secondPoint, result.value);
                                        }
                                        return [2 /*return*/, 'finish'];
                                    });
                                });
                            };
                            return ThreePointThirdState;
                        }());
                        machine = new AcEdPromptStateMachine();
                        machine.setState(new EntryState(this, machine));
                        return [4 /*yield*/, machine.run(function (prompt) { return AcApDocManager.instance.editor.getPoint(prompt); })];
                    case 1:
                        _a.sent();
                        if (!circle)
                            return [2 /*return*/];
                        context.doc.database.tables.blockTable.modelSpace.appendEntity(new AcDbCircle(circle.center, circle.radius));
                        return [2 /*return*/];
                }
            });
        });
    };
    /**
     * Runs center-based workflow:
     * center -> radius OR center -> diameter.
     *
     * @param context - Current application/document context.
     * @param center - Picked center point.
     * @returns Resolved circle definition, or `undefined` on cancel/invalid input.
     */
    AcApCircleCmd.prototype.runCenterFlow = function (context, center) {
        return __awaiter(this, void 0, void 0, function () {
            var radiusPrompt, radiusResult;
            var _a;
            return __generator(this, function (_b) {
                switch (_b.label) {
                    case 0:
                        radiusPrompt = new AcEdPromptDistanceOptions(AcApI18n.t('jig.circle.radiusOrDiameter'));
                        addKeyword(radiusPrompt, 'diameter');
                        radiusPrompt.allowZero = false;
                        radiusPrompt.useBasePoint = true;
                        radiusPrompt.useDashedLine = true;
                        radiusPrompt.basePoint = new AcGePoint3d(center);
                        radiusPrompt.jig = new AcApCircleJig(context.view, center);
                        return [4 /*yield*/, AcApDocManager.instance.editor.getDistance(radiusPrompt)];
                    case 1:
                        radiusResult = _b.sent();
                        if (radiusResult.status === AcEdPromptStatus.OK) {
                            return [2 /*return*/, createCircleFromCenterRadius(center, (_a = radiusResult.value) !== null && _a !== void 0 ? _a : 0)];
                        }
                        if (!(radiusResult.status === AcEdPromptStatus.Keyword &&
                            radiusResult.stringResult === 'Diameter')) return [3 /*break*/, 3];
                        return [4 /*yield*/, this.promptDiameterCircle(context, center)];
                    case 2: return [2 /*return*/, _b.sent()];
                    case 3: return [2 /*return*/, undefined];
                }
            });
        });
    };
    /**
     * Prompts diameter input in center-based workflow.
     *
     * @param context - Current application/document context.
     * @param center - Circle center point.
     * @returns Circle definition, or `undefined` when canceled/invalid.
     */
    AcApCircleCmd.prototype.promptDiameterCircle = function (context, center) {
        return __awaiter(this, void 0, void 0, function () {
            var diameterPrompt, diameterResult, radius;
            var _a;
            return __generator(this, function (_b) {
                switch (_b.label) {
                    case 0:
                        diameterPrompt = new AcEdPromptDistanceOptions(AcApI18n.t('jig.circle.diameter'));
                        diameterPrompt.allowZero = false;
                        diameterPrompt.useBasePoint = true;
                        diameterPrompt.useDashedLine = true;
                        diameterPrompt.basePoint = new AcGePoint3d(center);
                        diameterPrompt.jig = new AcApCircleJig(context.view, center);
                        return [4 /*yield*/, AcApDocManager.instance.editor.getDistance(diameterPrompt)];
                    case 1:
                        diameterResult = _b.sent();
                        if (diameterResult.status !== AcEdPromptStatus.OK)
                            return [2 /*return*/, undefined];
                        radius = ((_a = diameterResult.value) !== null && _a !== void 0 ? _a : 0) / 2;
                        return [2 /*return*/, createCircleFromCenterRadius(center, radius)];
                }
            });
        });
    };
    /**
     * Creates circle definition from two points.
     *
     * @param first - First point of diameter.
     * @param second - Second point of diameter.
     * @returns Circle definition, or `undefined` when points are invalid.
     */
    AcApCircleCmd.prototype.createFromTwoPoints = function (first, second) {
        return createCircleFromTwoPoints(first, second);
    };
    /**
     * Creates circle definition from three points.
     *
     * @param p1 - First point on circle.
     * @param p2 - Second point on circle.
     * @param p3 - Third point on circle.
     * @returns Circle definition, or `undefined` when points are degenerate.
     */
    AcApCircleCmd.prototype.createFromThreePoints = function (p1, p2, p3) {
        return createCircleFromThreePoints(p1, p2, p3);
    };
    return AcApCircleCmd;
}(AcEdCommand));
export { AcApCircleCmd };
//# sourceMappingURL=AcApCircleCmd.js.map