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 { AcCmEventManager, AcGeBox3d, AcGePoint2d } from '@mlightcad/data-model';
import { debounce } from 'lodash-es';
import { AcEdCorsorType, AcEdSelectionSet } from '../input';
import { AcEditor } from '../input/AcEditor';
import { AcEdHoverController } from './AcEdHoverController';
/**
 * Enumeration of view interaction modes.
 *
 * The view mode determines how the view responds to user mouse interactions:
 * - In SELECTION mode, clicks select entities
 * - In PAN mode, clicks and drags pan the view
 *
 * @example
 * ```typescript
 * // Set to selection mode for entity picking
 * view.mode = AcEdViewMode.SELECTION;
 *
 * // Set to pan mode for view navigation
 * view.mode = AcEdViewMode.PAN;
 * ```
 */
export var AcEdViewMode;
(function (AcEdViewMode) {
    /**
     * Selection mode - mouse clicks select entities.
     *
     * In this mode:
     * - Single clicks select individual entities
     * - Drag operations can create selection boxes
     * - Selected entities are highlighted with grip points
     */
    AcEdViewMode[AcEdViewMode["SELECTION"] = 0] = "SELECTION";
    /**
     * Pan mode - mouse interactions pan the view.
     *
     * In this mode:
     * - Click and drag operations move the view
     * - The cursor typically changes to indicate pan mode
     * - Entity selection is disabled
     */
    AcEdViewMode[AcEdViewMode["PAN"] = 1] = "PAN";
})(AcEdViewMode || (AcEdViewMode = {}));
/**
 * Abstract base class for all CAD view implementations.
 *
 * This class provides the foundation for rendering and interacting with CAD drawings.
 * It manages:
 * - Canvas and viewport dimensions
 * - Mouse event handling and coordinate conversion
 * - Entity selection and highlighting
 * - View modes (selection, pan, etc.)
 * - Spatial queries for entity picking
 * - Hover/unhover detection with timing
 *
 * Concrete implementations must provide specific rendering logic and coordinate
 * transformations appropriate for their rendering technology (e.g., Three.js, SVG).
 *
 * ## Key Responsibilities
 * - **Input Management**: Handles mouse events and user interactions
 * - **Selection**: Manages selected entities and visual feedback
 * - **Coordinate Systems**: Converts between screen and world coordinates
 * - **Spatial Queries**: Finds entities at specific locations
 * - **View State**: Tracks current position, zoom, and view mode
 *
 * @example
 * ```typescript
 * class MyView extends AcEdBaseView {
 *   // Implement required abstract methods
 *   get missedData() { return { fonts: {}, images: new Map() }; }
 *   get mode() { return this._mode; }
 *   set mode(value) { this._mode = value; }
 *   // ... other abstract methods
 * }
 *
 * const view = new MyView(canvasElement);
 * view.events.mouseMove.addEventListener(args => {
 *   console.log('Mouse at world coords:', args.x, args.y);
 * });
 * ```
 */
var AcEdBaseView = /** @class */ (function () {
    /**
     * Creates a new base view instance.
     *
     * Sets up the canvas, initializes internal state, and registers event listeners
     * for mouse interactions and container resize events.
     *
     * @param canvas - The HTML canvas element to render into
     * @param container - The HTML element to contain this viewer. The canvas element
     * must be children element of container element.
     */
    function AcEdBaseView(canvas, container) {
        var _this = this;
        /** Events fired by the view for various interactions */
        this.events = {
            /** Fired when mouse moves over the view */
            mouseMove: new AcCmEventManager(),
            /** Fired when the view is resized */
            viewResize: new AcCmEventManager(),
            /** Fired when the view camera is changed */
            viewChanged: new AcCmEventManager(),
            /** Fired when mouse hovers over an entity */
            hover: new AcCmEventManager(),
            /** Fired when mouse stops hovering over an entity */
            unhover: new AcCmEventManager(),
            /** Fired when a frame is rendered */
            renderFrame: new AcCmEventManager()
        };
        this._container = container;
        this._canvas = canvas;
        var rect = canvas.getBoundingClientRect();
        this._bbox = new AcGeBox3d();
        this._width = rect.width;
        this._height = rect.height;
        this._curPos = new AcGePoint2d();
        this._curMousePos = new AcGePoint2d();
        this._selectionSet = new AcEdSelectionSet();
        this._editor = new AcEditor(this);
        this._canvas.addEventListener('mousemove', function (event) { return _this.onMouseMove(event); });
        this._canvas.addEventListener('mousedown', function (event) {
            if (event.button === 1) {
                // Middle mouse button (button === 1)
                _this._editor.setCursor(AcEdCorsorType.Grab);
            }
        });
        this._canvas.addEventListener('mouseup', function (event) {
            if (event.button === 1) {
                // Middle mouse button (button === 1)
                _this._editor.restoreCursor();
            }
        });
        var debouncedWindowResize = debounce(function () { return _this.onWindowResize(); }, 0, {
            leading: false,
            trailing: true
        });
        var resizeObserver = new ResizeObserver(debouncedWindowResize);
        resizeObserver.observe(this._canvas.parentElement);
        this._selectionBoxSize = 4;
        // Initialize hover/unhover handler
        this._hoverController = new AcEdHoverController(this, this.events.hover, this.events.unhover);
    }
    Object.defineProperty(AcEdBaseView.prototype, "editor", {
        /**
         * Gets the input manager for handling user interactions.
         *
         * The editor provides high-level methods for getting user input like
         * point selection, entity selection, and cursor management.
         *
         * @returns The editor instance
         */
        get: function () {
            return this._editor;
        },
        enumerable: false,
        configurable: true
    });
    Object.defineProperty(AcEdBaseView.prototype, "selectionBoxSize", {
        /**
         * Gets the size of the selection box used for entity picking.
         *
         * This determines how close the mouse needs to be to an entity
         * to select it, measured in screen pixels.
         *
         * @returns Selection box size in pixels
         */
        get: function () {
            return this._selectionBoxSize;
        },
        /**
         * Sets the size of the selection box used for entity picking.
         *
         * @param value - Selection box size in pixels
         */
        set: function (value) {
            this._selectionBoxSize = value;
        },
        enumerable: false,
        configurable: true
    });
    /**
     * Temporarily switches the view mode, executes an action, then restores
     * the previous mode — even if the action throws.
     *
     * This is the mode-level counterpart of {@link AcEditor.withCursor} and
     * follows the same save → set → restore pattern.
     *
     * @param mode - The temporary view mode to apply
     * @param action - The async (or sync) action to run under the temporary mode
     * @returns The value returned by the action
     *
     * @example
     * ```typescript
     * await view.withMode(AcEdViewMode.SELECTION, async () => {
     *   const pt = await view.editor.getPoint('Pick a point')
     * })
     * // view.mode is restored to whatever it was before
     * ```
     */
    AcEdBaseView.prototype.withMode = function (mode, action) {
        return __awaiter(this, void 0, void 0, function () {
            var previousMode;
            return __generator(this, function (_a) {
                switch (_a.label) {
                    case 0:
                        previousMode = this.mode;
                        this.mode = mode;
                        _a.label = 1;
                    case 1:
                        _a.trys.push([1, , 3, 4]);
                        return [4 /*yield*/, Promise.resolve(action())];
                    case 2: return [2 /*return*/, _a.sent()];
                    case 3:
                        this.mode = previousMode;
                        return [7 /*endfinally*/];
                    case 4: return [2 /*return*/];
                }
            });
        });
    };
    /**
     * Set cursor type of this view
     * @param cursorType Input cursor type
     */
    AcEdBaseView.prototype.setCursor = function (cursorType) {
        this._editor.setCursor(cursorType);
    };
    /**
     * Returns selection mode based on drag direction in canvas coordinates.
     * Left-to-right = window selection, right-to-left = crossing selection.
     */
    AcEdBaseView.prototype.getSelectionMode = function (startCanvas, endCanvas) {
        return endCanvas.x >= startCanvas.x ? 'window' : 'crossing';
    };
    /**
     * Returns the selection action based on modifier keys.
     * Shift: remove, Ctrl/Cmd: add, default: replace.
     */
    AcEdBaseView.prototype.getSelectionActionFromEvent = function (e, defaultAction) {
        if (defaultAction === void 0) { defaultAction = 'replace'; }
        if (e.shiftKey)
            return 'remove';
        if (e.ctrlKey || e.metaKey)
            return 'add';
        return defaultAction;
    };
    /**
     * Determine whether a drag is effectively a click.
     */
    AcEdBaseView.prototype.isSelectionClick = function (startCanvas, endCanvas, threshold) {
        if (threshold === void 0) { threshold = 2; }
        var dx = endCanvas.x - startCanvas.x;
        var dy = endCanvas.y - startCanvas.y;
        return Math.hypot(dx, dy) < threshold;
    };
    /**
     * Returns preview styles for selection rectangle.
     */
    AcEdBaseView.prototype.getSelectionPreviewStyle = function (mode, action) {
        var windowLine = 'var(--ml-ui-canvas-line, #00ff5a)';
        var crossingLine = 'var(--ml-ui-accent-alt, #00d1ff)';
        var removeLine = 'var(--ml-ui-danger, #ff4d4f)';
        var lineColor = action === 'remove'
            ? removeLine
            : mode === 'window'
                ? windowLine
                : crossingLine;
        var background = 'var(--ml-ui-canvas-fill-mix, var(--ml-ui-canvas-fill, rgba(64, 158, 255, 0.12)))';
        var borderStyle = mode === 'window' ? 'solid' : 'dashed';
        return { borderStyle: borderStyle, lineColor: lineColor, background: background };
    };
    /**
     * Applies selection based on action.
     */
    AcEdBaseView.prototype.applySelection = function (ids, action) {
        var _this = this;
        if (action === 'replace') {
            this.selectionSet.clear();
            var unique = ids.filter(function (id) { return !_this.selectionSet.has(id); });
            if (unique.length > 0)
                this.selectionSet.add(unique);
            return;
        }
        if (ids.length === 0)
            return;
        if (action === 'add') {
            var unique = ids.filter(function (id) { return !_this.selectionSet.has(id); });
            if (unique.length > 0)
                this.selectionSet.add(unique);
        }
        else {
            var existing = ids.filter(function (id) { return _this.selectionSet.has(id); });
            if (existing.length > 0)
                this.selectionSet.delete(existing);
        }
    };
    /**
     * Select entities by box with window/crossing behavior.
     */
    AcEdBaseView.prototype.selectByBoxWithMode = function (box, mode, action) {
        if (action === void 0) { action = 'add'; }
        var ids = this.collectSelectionIdsByBox(box, mode);
        this.applySelection(ids, action);
    };
    /**
     * Collects ids using window or crossing selection rules.
     */
    AcEdBaseView.prototype.collectSelectionIdsByBox = function (box, mode) {
        var results = this.search(box);
        var ids = [];
        results.forEach(function (item) {
            if (mode === 'crossing' ||
                (item.minX >= box.min.x &&
                    item.maxX <= box.max.x &&
                    item.minY >= box.min.y &&
                    item.maxY <= box.max.y)) {
                ids.push(item.id);
            }
        });
        return ids;
    };
    /**
     * Set callback function used to calculate size of canvas when window resized
     * @param value Input callback function
     */
    AcEdBaseView.prototype.setCalculateSizeCallback = function (value) {
        this._calculateSizeCallback = value;
    };
    Object.defineProperty(AcEdBaseView.prototype, "width", {
        /**
         * Width of canvas (not width of window) in pixel
         */
        get: function () {
            return this._width;
        },
        set: function (value) {
            this._width = value;
        },
        enumerable: false,
        configurable: true
    });
    Object.defineProperty(AcEdBaseView.prototype, "height", {
        /**
         * Height of canvas (not height of window) in pixel
         */
        get: function () {
            return this._height;
        },
        set: function (value) {
            this._height = value;
        },
        enumerable: false,
        configurable: true
    });
    Object.defineProperty(AcEdBaseView.prototype, "bbox", {
        /**
         * The bounding box to include all entities in this viewer
         */
        get: function () {
            return this._bbox;
        },
        enumerable: false,
        configurable: true
    });
    Object.defineProperty(AcEdBaseView.prototype, "canvas", {
        /**
         * The canvas HTML element used by this view
         */
        get: function () {
            return this._canvas;
        },
        enumerable: false,
        configurable: true
    });
    Object.defineProperty(AcEdBaseView.prototype, "container", {
        /**
         * The HTML element to contain this view
         */
        get: function () {
            return this._container;
        },
        enumerable: false,
        configurable: true
    });
    /**
     * Converts a viewport/client-space point (e.g. MouseEvent.clientX/Y)
     * into canvas-local coordinates.
     */
    AcEdBaseView.prototype.viewportToCanvas = function (point) {
        var rect = this._canvas.getBoundingClientRect();
        return new AcGePoint2d(point.x - rect.left, point.y - rect.top);
    };
    /**
     * Converts canvas-local coordinates into viewport/client-space coordinates.
     */
    AcEdBaseView.prototype.canvasToViewport = function (point) {
        var rect = this._canvas.getBoundingClientRect();
        return new AcGePoint2d(point.x + rect.left, point.y + rect.top);
    };
    /**
     * Converts canvas-local coordinates into container-local coordinates.
     *
     * This is useful for DOM overlays mounted in `container` while CAD APIs
     * return screen points in canvas space.
     */
    AcEdBaseView.prototype.canvasToContainer = function (point) {
        var canvasRect = this._canvas.getBoundingClientRect();
        var containerRect = this._container.getBoundingClientRect();
        return new AcGePoint2d(point.x + (canvasRect.left - containerRect.left), point.y + (canvasRect.top - containerRect.top));
    };
    /**
     * Converts a viewport/client-space point into container-local coordinates.
     */
    AcEdBaseView.prototype.viewportToContainer = function (point) {
        var containerRect = this._container.getBoundingClientRect();
        return new AcGePoint2d(point.x - containerRect.left, point.y - containerRect.top);
    };
    Object.defineProperty(AcEdBaseView.prototype, "aspect", {
        get: function () {
            return this._width / this._height;
        },
        enumerable: false,
        configurable: true
    });
    Object.defineProperty(AcEdBaseView.prototype, "curPos", {
        /**
         * Postion of current mouse in world coordinate system
         */
        get: function () {
            return this._curPos;
        },
        enumerable: false,
        configurable: true
    });
    Object.defineProperty(AcEdBaseView.prototype, "curMousePos", {
        /**
         * Current mouse position in canvas-local screen coordinates (pixels).
         *
         * Note: this is NOT viewport/client coordinates; use `canvasToViewport()`
         * when browser-viewport coordinates are required by DOM APIs.
         */
        get: function () {
            return this._curMousePos;
        },
        enumerable: false,
        configurable: true
    });
    Object.defineProperty(AcEdBaseView.prototype, "selectionSet", {
        /**
         * The selection set in current view.
         */
        get: function () {
            return this._selectionSet;
        },
        enumerable: false,
        configurable: true
    });
    AcEdBaseView.prototype.onWindowResize = function () {
        if (this._calculateSizeCallback) {
            var _a = this._calculateSizeCallback(), width = _a.width, height = _a.height;
            this._width = width;
            this._height = height;
        }
        else {
            this._width = this._canvas.clientWidth;
            this._height = this._canvas.clientHeight;
        }
        this.events.viewResize.dispatch({
            width: this._width,
            height: this._height
        });
    };
    /**
     * Clears the current hover state and cancels all hover-related timers.
     */
    AcEdBaseView.prototype.clearHover = function () {
        this._hoverController.clear();
    };
    /**
     * Mouse move event handler.
     * @param event Input mouse event argument
     */
    AcEdBaseView.prototype.onMouseMove = function (event) {
        // Keep one canonical conversion path for all view input code.
        this._curMousePos = this.viewportToCanvas({
            x: event.clientX,
            y: event.clientY
        });
        var wcsPos = this.screenToWorld(this._curMousePos);
        this._curPos.copy(wcsPos);
        this.events.mouseMove.dispatch({ x: wcsPos.x, y: wcsPos.y });
        // Hover handler
        if (this.mode == AcEdViewMode.SELECTION) {
            // If it is in “input acquisition” mode, disable hover behavior
            if (!this._editor.isActive) {
                this._hoverController.handleMouseMove(wcsPos.x, wcsPos.y);
            }
        }
    };
    return AcEdBaseView;
}());
export { AcEdBaseView };
//# sourceMappingURL=AcEdBaseView.js.map