'use strict';

/**
 * fixes.ts — Bug fixes and performance patches for editors.
 *
 * - PhotoEditor: Retina/HiDPI canvas devicePixelRatio handling
 * - VectorEditor: Shape virtualization for >100 shapes
 * - AnimationEditor: Memory leak prevention (raf/timer cleanup)
 */
/**
 * Configure a canvas for Retina/HiDPI displays.
 * Scales the backing store while keeping CSS dimensions intact.
 */
function setupRetinaCanvas(canvas, options) {
    var _a;
    const dpr = (_a = options.dpr) !== null && _a !== void 0 ? _a : (typeof window !== "undefined" ? window.devicePixelRatio : 1);
    canvas.width = Math.round(options.width * dpr);
    canvas.height = Math.round(options.height * dpr);
    canvas.style.width = `${options.width}px`;
    canvas.style.height = `${options.height}px`;
    const ctx = canvas.getContext("2d");
    ctx.scale(dpr, dpr);
    return ctx;
}
/**
 * Get the correct mouse position on a Retina canvas.
 */
function retinaMousePosition(e, canvas) {
    const rect = canvas.getBoundingClientRect();
    return {
        x: e.clientX - rect.left,
        y: e.clientY - rect.top,
    };
}
/**
 * Given a list of shapes with bounding boxes and a visible viewport,
 * returns only the shapes intersecting the viewport.
 */
function virtualizeShapes(shapes, viewport, margin = 50) {
    const vx = viewport.x - margin;
    const vy = viewport.y - margin;
    const vr = viewport.x + viewport.width + margin;
    const vb = viewport.y + viewport.height + margin;
    return shapes
        .filter(({ bounds: b }) => {
        const bx2 = b.x + b.width;
        const by2 = b.y + b.height;
        return bx2 >= vx && b.x <= vr && by2 >= vy && b.y <= vb;
    })
        .map(({ shape }) => shape);
}
/**
 * Spatial hash grid for fast shape lookup.
 * Greatly speeds up rendering and hit-testing with many shapes.
 */
class SpatialHashGrid {
    constructor(cellSize = 200) {
        this.cellSize = cellSize;
        this.cells = new Map();
    }
    cellKey(cx, cy) {
        return `${cx},${cy}`;
    }
    /** Insert a shape into the grid. */
    insert(shape, bounds) {
        const x0 = Math.floor(bounds.x / this.cellSize);
        const y0 = Math.floor(bounds.y / this.cellSize);
        const x1 = Math.floor((bounds.x + bounds.width) / this.cellSize);
        const y1 = Math.floor((bounds.y + bounds.height) / this.cellSize);
        const vs = { shape, bounds };
        for (let cx = x0; cx <= x1; cx++) {
            for (let cy = y0; cy <= y1; cy++) {
                const key = this.cellKey(cx, cy);
                const cell = this.cells.get(key);
                if (cell)
                    cell.push(vs);
                else
                    this.cells.set(key, [vs]);
            }
        }
    }
    /** Query shapes overlapping the given viewport. */
    query(viewport) {
        const x0 = Math.floor(viewport.x / this.cellSize);
        const y0 = Math.floor(viewport.y / this.cellSize);
        const x1 = Math.floor((viewport.x + viewport.width) / this.cellSize);
        const y1 = Math.floor((viewport.y + viewport.height) / this.cellSize);
        const seen = new Set();
        const result = [];
        for (let cx = x0; cx <= x1; cx++) {
            for (let cy = y0; cy <= y1; cy++) {
                const cell = this.cells.get(this.cellKey(cx, cy));
                if (!cell)
                    continue;
                for (const vs of cell) {
                    if (!seen.has(vs.shape)) {
                        seen.add(vs.shape);
                        result.push(vs.shape);
                    }
                }
            }
        }
        return result;
    }
    /** Remove all shapes from the grid. */
    clear() {
        this.cells.clear();
    }
    /** Rebuild the grid from a list of shapes. */
    rebuild(shapes) {
        this.clear();
        for (const vs of shapes)
            this.insert(vs.shape, vs.bounds);
    }
}
// ── Animation Memory Leak Prevention ────────────────────────────
/**
 * Track requestAnimationFrame IDs and timers for proper cleanup.
 */
class AnimationCleanup {
    constructor() {
        this.rafIds = new Set();
        this.timerIds = new Set();
        this.intervalIds = new Set();
        this.disposed = false;
    }
    /** Wraps requestAnimationFrame with tracking. */
    requestAnimationFrame(cb) {
        if (this.disposed)
            return -1;
        const id = window.requestAnimationFrame((time) => {
            this.rafIds.delete(id);
            if (!this.disposed)
                cb(time);
        });
        this.rafIds.add(id);
        return id;
    }
    /** Wraps setTimeout with tracking. */
    setTimeout(cb, ms) {
        if (this.disposed)
            return null;
        const id = setTimeout(() => {
            this.timerIds.delete(id);
            if (!this.disposed)
                cb();
        }, ms);
        this.timerIds.add(id);
        return id;
    }
    /** Wraps setInterval with tracking. */
    setInterval(cb, ms) {
        if (this.disposed)
            return null;
        const id = setInterval(() => {
            if (this.disposed) {
                clearInterval(id);
                return;
            }
            cb();
        }, ms);
        this.intervalIds.add(id);
        return id;
    }
    /** Cancel all tracked timers and animation frames. */
    dispose() {
        this.disposed = true;
        for (const id of this.rafIds)
            window.cancelAnimationFrame(id);
        for (const id of this.timerIds)
            clearTimeout(id);
        for (const id of this.intervalIds)
            clearInterval(id);
        this.rafIds.clear();
        this.timerIds.clear();
        this.intervalIds.clear();
    }
}

exports.AnimationCleanup = AnimationCleanup;
exports.SpatialHashGrid = SpatialHashGrid;
exports.retinaMousePosition = retinaMousePosition;
exports.setupRetinaCanvas = setupRetinaCanvas;
exports.virtualizeShapes = virtualizeShapes;
//# sourceMappingURL=fixes.js.map
