/**
 * gridGuides.ts — Grid, guides, rulers, and snap-to-grid/pixel for editors.
 *
 * Unified system for drawing grids, ruler bars, snapping to grid,
 * and managing user-defined guide lines.
 */
// ── Default Config ──────────────────────────────────────────────
// ── Grid Rendering ──────────────────────────────────────────────
/** Draw a grid on a canvas context. */
function drawGrid(ctx, width, height, config, offsetX = 0, offsetY = 0, zoom = 1) {
    if (!config.enabled)
        return;
    const gridSize = config.size * zoom;
    const subSize = gridSize / config.subdivisions;
    ctx.save();
    ctx.globalAlpha = config.opacity;
    // Sub-grid
    if (config.subdivisions > 1 && subSize >= 3) {
        ctx.strokeStyle = config.subColor;
        ctx.lineWidth = 0.5;
        ctx.beginPath();
        const startX = -(offsetX % subSize);
        const startY = -(offsetY % subSize);
        for (let x = startX; x <= width; x += subSize) {
            ctx.moveTo(Math.round(x) + 0.5, 0);
            ctx.lineTo(Math.round(x) + 0.5, height);
        }
        for (let y = startY; y <= height; y += subSize) {
            ctx.moveTo(0, Math.round(y) + 0.5);
            ctx.lineTo(width, Math.round(y) + 0.5);
        }
        ctx.stroke();
    }
    // Main grid
    ctx.strokeStyle = config.color;
    ctx.lineWidth = 1;
    ctx.beginPath();
    const mainStartX = -(offsetX % gridSize);
    const mainStartY = -(offsetY % gridSize);
    for (let x = mainStartX; x <= width; x += gridSize) {
        ctx.moveTo(Math.round(x) + 0.5, 0);
        ctx.lineTo(Math.round(x) + 0.5, height);
    }
    for (let y = mainStartY; y <= height; y += gridSize) {
        ctx.moveTo(0, Math.round(y) + 0.5);
        ctx.lineTo(width, Math.round(y) + 0.5);
    }
    ctx.stroke();
    ctx.restore();
}
// ── Ruler Rendering ─────────────────────────────────────────────
/** Draw horizontal ruler bar. */
function drawHorizontalRuler(ctx, width, config, offsetX = 0, zoom = 1) {
    if (!config.enabled)
        return;
    ctx.save();
    ctx.fillStyle = config.backgroundColor;
    ctx.fillRect(0, 0, width, config.size);
    const step = calculateRulerStep(zoom);
    ctx.strokeStyle = config.color;
    ctx.fillStyle = config.textColor;
    ctx.font = "9px monospace";
    ctx.textAlign = "center";
    const startX = Math.floor(-offsetX / (step * zoom)) * step;
    const endX = startX + width / zoom + step;
    for (let val = startX; val <= endX; val += step) {
        const x = (val + offsetX / zoom) * zoom;
        if (x < 0 || x > width)
            continue;
        // Major tick
        ctx.beginPath();
        ctx.moveTo(Math.round(x) + 0.5, config.size);
        ctx.lineTo(Math.round(x) + 0.5, config.size - config.tickSize * 2);
        ctx.stroke();
        ctx.fillText(String(Math.round(val)), x, config.size - config.tickSize * 2 - 2);
        // Minor ticks
        for (let m = 1; m < 5; m++) {
            const mx = ((val + (step * m) / 5) + offsetX / zoom) * zoom;
            if (mx < 0 || mx > width)
                continue;
            ctx.beginPath();
            ctx.moveTo(Math.round(mx) + 0.5, config.size);
            ctx.lineTo(Math.round(mx) + 0.5, config.size - config.tickSize);
            ctx.stroke();
        }
    }
    ctx.restore();
}
/** Draw vertical ruler bar. */
function drawVerticalRuler(ctx, height, config, offsetY = 0, zoom = 1) {
    if (!config.enabled)
        return;
    ctx.save();
    ctx.fillStyle = config.backgroundColor;
    ctx.fillRect(0, 0, config.size, height);
    const step = calculateRulerStep(zoom);
    ctx.strokeStyle = config.color;
    ctx.fillStyle = config.textColor;
    ctx.font = "9px monospace";
    const startY = Math.floor(-offsetY / (step * zoom)) * step;
    const endY = startY + height / zoom + step;
    for (let val = startY; val <= endY; val += step) {
        const y = (val + offsetY / zoom) * zoom;
        if (y < 0 || y > height)
            continue;
        ctx.beginPath();
        ctx.moveTo(config.size, Math.round(y) + 0.5);
        ctx.lineTo(config.size - config.tickSize * 2, Math.round(y) + 0.5);
        ctx.stroke();
        // Rotated text
        ctx.save();
        ctx.translate(config.size - config.tickSize * 2 - 2, y);
        ctx.rotate(-Math.PI / 2);
        ctx.textAlign = "center";
        ctx.fillText(String(Math.round(val)), 0, 0);
        ctx.restore();
    }
    ctx.restore();
}
function calculateRulerStep(zoom) {
    var _a;
    const idealStep = 50 / zoom;
    const magnitude = Math.pow(10, Math.floor(Math.log10(idealStep)));
    const candidates = [1, 2, 5, 10].map((m) => m * magnitude);
    return (_a = candidates.find((c) => c >= idealStep)) !== null && _a !== void 0 ? _a : idealStep;
}
// ── Guide Rendering ─────────────────────────────────────────────
/** Draw guides on a canvas context. */
function drawGuides(ctx, width, height, config, offsetX = 0, offsetY = 0, zoom = 1) {
    if (!config.enabled)
        return;
    ctx.save();
    ctx.setLineDash([6, 3]);
    ctx.lineWidth = 1;
    for (const guide of config.guides) {
        ctx.strokeStyle = guide.color || config.color;
        ctx.beginPath();
        if (guide.orientation === "horizontal") {
            const y = (guide.position + offsetY / zoom) * zoom;
            ctx.moveTo(0, Math.round(y) + 0.5);
            ctx.lineTo(width, Math.round(y) + 0.5);
        }
        else {
            const x = (guide.position + offsetX / zoom) * zoom;
            ctx.moveTo(Math.round(x) + 0.5, 0);
            ctx.lineTo(Math.round(x) + 0.5, height);
        }
        ctx.stroke();
    }
    ctx.setLineDash([]);
    ctx.restore();
}
// ── Snapping ────────────────────────────────────────────────────
/** Snap a point to the grid. */
function snapToGrid(x, y, gridSize) {
    return {
        x: Math.round(x / gridSize) * gridSize,
        y: Math.round(y / gridSize) * gridSize,
        snappedX: true,
        snappedY: true,
    };
}
/** Snap a point to nearby guides. */
function snapToGuides(x, y, guides, snapDistance) {
    let sx = x, sy = y;
    let snappedX = false, snappedY = false;
    for (const guide of guides) {
        if (guide.orientation === "vertical") {
            if (Math.abs(x - guide.position) <= snapDistance) {
                sx = guide.position;
                snappedX = true;
            }
        }
        else {
            if (Math.abs(y - guide.position) <= snapDistance) {
                sy = guide.position;
                snappedY = true;
            }
        }
    }
    return { x: sx, y: sy, snappedX, snappedY };
}
/** Snap to pixel (floor to nearest integer). */
function snapToPixel(x, y) {
    return {
        x: Math.round(x),
        y: Math.round(y),
        snappedX: true,
        snappedY: true,
    };
}
/** Combined snap: try guides first, then grid, then pixel. */
function snapPoint(x, y, gridConfig, guidesConfig) {
    // Try guides first
    if (guidesConfig.enabled && guidesConfig.snapToGuides) {
        const guideResult = snapToGuides(x, y, guidesConfig.guides, guidesConfig.snapDistance);
        if (guideResult.snappedX || guideResult.snappedY)
            return guideResult;
    }
    // Then grid
    if (gridConfig.enabled && gridConfig.snapToGrid) {
        return snapToGrid(x, y, gridConfig.size);
    }
    return { x, y, snappedX: false, snappedY: false };
}
// ── Guide Management ────────────────────────────────────────────
let nextGuideId = 1;
/** Create a new guide. */
function createGuide(orientation, position, color) {
    return {
        id: `guide-${nextGuideId++}`,
        orientation,
        position,
        color: color !== null && color !== void 0 ? color : "rgba(59, 130, 246, 0.6)",
        locked: false,
    };
}
/** Add a guide to the config. */
function addGuide(config, guide) {
    return { ...config, guides: [...config.guides, guide] };
}
/** Remove a guide by ID. */
function removeGuide(config, guideId) {
    return { ...config, guides: config.guides.filter((g) => g.id !== guideId) };
}
/** Clear all guides. */
function clearGuides(config) {
    return { ...config, guides: [] };
}

export { addGuide, clearGuides, createGuide, drawGrid, drawGuides, drawHorizontalRuler, drawVerticalRuler, removeGuide, snapPoint, snapToGrid, snapToGuides, snapToPixel };
//# sourceMappingURL=gridGuides.js.map
