// ── Canvas Plugin: ASCII Art Editor ── // // Self-registers via window.hyprcrd: // - Markdown fenced block ```ascii``` →
//   - Editor toolbar button + "asciiart" command
//   - Re-opens canvas to edit if cursor is inside an existing block

const hyprcrd = window.hyprcrd;
if (!hyprcrd) {
    throw new Error('[canvas plugin] window.hyprcrd not found — plugin API missing');
}

const $ = (id) => document.getElementById(id);

const overlay = $('ascii-canvas-overlay');
const grid = $('ascii-canvas-grid');
const toolbar = $('ascii-canvas-toolbar');
const charInput = $('ascii-char-input');
const widthInput = $('ascii-canvas-width');
const heightInput = $('ascii-canvas-height');
const btnOk = $('ascii-canvas-ok');
const btnCancel = $('ascii-canvas-cancel');
const btnClear = $('ascii-canvas-clear');
const btnResize = $('ascii-canvas-resize');
const coordsDisplay = $('ascii-canvas-coords');

let data = [];        // 2D array [row][col]
let cols = 40;
let rows = 20;
let tool = 'brush';   // brush | eraser | line | rect | circle | fill | text
let char = '#';
let drawing = false;
let startCell = null;  // {r, c} for shape tools
let previewCells = []; // cells to highlight during drag
let onInsert = null;   // callback(text)
let cellW = 0, cellH = 0;
let gridRect = null;

// ── Grid init ──

function initGrid(w, h) {
    cols = w; rows = h;
    data = [];
    for (let r = 0; r < rows; r++) {
        data.push(new Array(cols).fill(' '));
    }
    render();
}

function render() {
    const lines = data.map(row => row.join(''));
    grid.textContent = lines.join('\n');
    measureCells();
}

function measureCells() {
    if (!grid.firstChild) return;
    // Use getBoundingClientRect on the grid (inline-block = content-sized)
    gridRect = grid.getBoundingClientRect();
    cellW = gridRect.width / cols;
    cellH = gridRect.height / rows;
}

function cellFromEvent(e) {
    // Always re-measure to account for scroll/layout changes
    gridRect = grid.getBoundingClientRect();
    if (!gridRect || !cellW || !cellH) return null;
    const x = e.clientX - gridRect.left;
    const y = e.clientY - gridRect.top;
    const c = Math.floor(x / cellW);
    const r = Math.floor(y / cellH);
    if (r < 0 || r >= rows || c < 0 || c >= cols) return null;
    return { r, c };
}

// ── Drawing algorithms ──

function setCell(r, c, ch) {
    if (r >= 0 && r < rows && c >= 0 && c < cols) {
        data[r][c] = ch;
    }
}

function bresenhamLine(r0, c0, r1, c1) {
    const cells = [];
    let dr = Math.abs(r1 - r0), dc = Math.abs(c1 - c0);
    let sr = r0 < r1 ? 1 : -1, sc = c0 < c1 ? 1 : -1;
    let err = dc - dr;
    let r = r0, c = c0;
    while (true) {
        cells.push({ r, c });
        if (r === r1 && c === c1) break;
        const e2 = 2 * err;
        if (e2 > -dr) { err -= dr; c += sc; }
        if (e2 < dc) { err += dc; r += sr; }
    }
    return cells;
}

function rectCells(r0, c0, r1, c1) {
    const cells = [];
    const rMin = Math.min(r0, r1), rMax = Math.max(r0, r1);
    const cMin = Math.min(c0, c1), cMax = Math.max(c0, c1);
    for (let c = cMin; c <= cMax; c++) { cells.push({ r: rMin, c }); cells.push({ r: rMax, c }); }
    for (let r = rMin + 1; r < rMax; r++) { cells.push({ r, c: cMin }); cells.push({ r, c: cMax }); }
    return cells;
}

function circleCells(r0, c0, r1, c1) {
    const cells = [];
    const cr = (r0 + r1) / 2, cc = (c0 + c1) / 2;
    const rx = Math.abs(c1 - c0) / 2, ry = Math.abs(r1 - r0) / 2;
    if (rx < 1 && ry < 1) { cells.push({ r: Math.round(cr), c: Math.round(cc) }); return cells; }
    // Parametric ellipse
    const steps = Math.max(60, Math.round(Math.max(rx, ry) * 4));
    const seen = new Set();
    for (let i = 0; i <= steps; i++) {
        const a = (2 * Math.PI * i) / steps;
        const r = Math.round(cr + ry * Math.sin(a));
        const c = Math.round(cc + rx * Math.cos(a));
        const key = r + ',' + c;
        if (!seen.has(key)) { seen.add(key); cells.push({ r, c }); }
    }
    return cells;
}

function floodFill(r, c, ch) {
    const target = data[r]?.[c];
    if (target === undefined || target === ch) return;
    const stack = [{ r, c }];
    while (stack.length) {
        const { r: cr, c: cc } = stack.pop();
        if (cr < 0 || cr >= rows || cc < 0 || cc >= cols) continue;
        if (data[cr][cc] !== target) continue;
        data[cr][cc] = ch;
        stack.push({ r: cr - 1, c: cc }, { r: cr + 1, c: cc }, { r: cr, c: cc - 1 }, { r: cr, c: cc + 1 });
    }
}

// ── Rendering with preview overlay ──

function renderWithPreview(cells, ch) {
    const preview = new Map();
    for (const { r, c } of cells) {
        if (r >= 0 && r < rows && c >= 0 && c < cols) {
            preview.set(r + ',' + c, ch);
        }
    }
    const lines = [];
    for (let r = 0; r < rows; r++) {
        let line = '';
        for (let c = 0; c < cols; c++) {
            const key = r + ',' + c;
            line += preview.has(key) ? preview.get(key) : data[r][c];
        }
        lines.push(line);
    }
    grid.textContent = lines.join('\n');
}

// ── Mouse handlers ──

function onMouseDown(e) {
    if (e.button !== 0) return;
    e.preventDefault();
    measureCells();
    const cell = cellFromEvent(e);
    if (!cell) return;
    drawing = true;
    startCell = cell;

    if (tool === 'brush') {
        setCell(cell.r, cell.c, char);
        render();
    } else if (tool === 'eraser') {
        setCell(cell.r, cell.c, ' ');
        render();
    } else if (tool === 'fill') {
        floodFill(cell.r, cell.c, char);
        render();
        drawing = false;
    } else if (tool === 'text') {
        // Start typing at this position
        startTextMode(cell.r, cell.c);
        drawing = false;
    }
    updateCoords(cell);
}

function onMouseMove(e) {
    const cell = cellFromEvent(e);
    if (cell) updateCoords(cell);
    if (!drawing || !cell) return;

    if (tool === 'brush') {
        // Draw line from last position for smooth strokes
        if (startCell) {
            const cells = bresenhamLine(startCell.r, startCell.c, cell.r, cell.c);
            for (const { r, c } of cells) setCell(r, c, char);
            startCell = cell;
        }
        render();
    } else if (tool === 'eraser') {
        if (startCell) {
            const cells = bresenhamLine(startCell.r, startCell.c, cell.r, cell.c);
            for (const { r, c } of cells) setCell(r, c, ' ');
            startCell = cell;
        }
        render();
    } else if (tool === 'line') {
        const cells = bresenhamLine(startCell.r, startCell.c, cell.r, cell.c);
        renderWithPreview(cells, char);
    } else if (tool === 'rect') {
        const cells = rectCells(startCell.r, startCell.c, cell.r, cell.c);
        renderWithPreview(cells, char);
    } else if (tool === 'circle') {
        const cells = circleCells(startCell.r, startCell.c, cell.r, cell.c);
        renderWithPreview(cells, char);
    }
}

function onMouseUp(e) {
    if (!drawing) return;
    const cell = cellFromEvent(e);
    if (!cell) { drawing = false; return; }

    if (tool === 'line') {
        for (const p of bresenhamLine(startCell.r, startCell.c, cell.r, cell.c)) setCell(p.r, p.c, char);
    } else if (tool === 'rect') {
        for (const p of rectCells(startCell.r, startCell.c, cell.r, cell.c)) setCell(p.r, p.c, char);
    } else if (tool === 'circle') {
        for (const p of circleCells(startCell.r, startCell.c, cell.r, cell.c)) setCell(p.r, p.c, char);
    }
    drawing = false;
    render();
}

// ── Text mode ──

let textCursor = null;

function startTextMode(r, c) {
    textCursor = { r, c };
    renderTextCursor();
}

function renderTextCursor() {
    if (!textCursor) { render(); return; }
    // Show cursor as inverted cell
    const lines = [];
    for (let r = 0; r < rows; r++) {
        let line = '';
        for (let c = 0; c < cols; c++) {
            line += data[r][c];
        }
        lines.push(line);
    }
    // Build HTML with cursor highlight
    let html = '';
    for (let r = 0; r < rows; r++) {
        for (let c = 0; c < cols; c++) {
            const ch = data[r][c] === ' ' ? '\u00a0' : data[r][c];
            if (r === textCursor.r && c === textCursor.c) {
                html += `${ch}`;
            } else {
                html += escChar(data[r][c]);
            }
        }
        if (r < rows - 1) html += '\n';
    }
    grid.innerHTML = html;
}

function escChar(ch) {
    if (ch === '<') return '<';
    if (ch === '>') return '>';
    if (ch === '&') return '&';
    if (ch === ' ') return ' ';
    return ch;
}

function handleTextKey(e) {
    if (!textCursor) return false;
    if (e.key === 'Escape') {
        textCursor = null;
        render();
        return true;
    }
    if (e.key === 'ArrowLeft') { textCursor.c = Math.max(0, textCursor.c - 1); renderTextCursor(); return true; }
    if (e.key === 'ArrowRight') { textCursor.c = Math.min(cols - 1, textCursor.c + 1); renderTextCursor(); return true; }
    if (e.key === 'ArrowUp') { textCursor.r = Math.max(0, textCursor.r - 1); renderTextCursor(); return true; }
    if (e.key === 'ArrowDown') { textCursor.r = Math.min(rows - 1, textCursor.r + 1); renderTextCursor(); return true; }
    if (e.key === 'Backspace') {
        textCursor.c = Math.max(0, textCursor.c - 1);
        setCell(textCursor.r, textCursor.c, ' ');
        renderTextCursor();
        return true;
    }
    if (e.key === 'Enter') {
        textCursor.r = Math.min(rows - 1, textCursor.r + 1);
        textCursor.c = 0;
        renderTextCursor();
        return true;
    }
    if (e.key.length === 1 && !e.ctrlKey && !e.metaKey) {
        setCell(textCursor.r, textCursor.c, e.key);
        textCursor.c = Math.min(cols - 1, textCursor.c + 1);
        renderTextCursor();
        return true;
    }
    return false;
}

// ── Coords display ──

function updateCoords(cell) {
    if (coordsDisplay) coordsDisplay.textContent = `${cell.c + 1}:${cell.r + 1}`;
}

// ── Tool selection ──

function selectTool(t) {
    tool = t;
    textCursor = null;
    render();
    toolbar.querySelectorAll('[data-tool]').forEach(b => {
        b.classList.toggle('active', b.dataset.tool === t);
    });
}

// ── Character palette ──

const CHAR_SETS = {
    basic: ['#', '*', '.', '+', '-', '|', '/', '\\', '=', '_', '~', ':', '^', '@', 'o', 'x'],
    box: ['─', '│', '┌', '┐', '└', '┘', '├', '┤', '┬', '┴', '┼'],
    boxDouble: ['═', '║', '╔', '╗', '╚', '╝', '╠', '╣', '╦', '╩', '╬'],
    blocks: ['█', '▓', '▒', '░', '▄', '▀', '▌', '▐'],
    arrows: ['←', '→', '↑', '↓', '↔', '↕', '►', '◄', '▲', '▼'],
};

function buildPalette() {
    const palette = $('ascii-char-palette');
    if (!palette) return;
    palette.innerHTML = '';
    for (const [group, chars] of Object.entries(CHAR_SETS)) {
        const grp = document.createElement('div');
        grp.className = 'ascii-char-group';
        for (const ch of chars) {
            const btn = document.createElement('button');
            btn.className = 'ascii-char-btn';
            btn.textContent = ch;
            btn.title = ch;
            btn.addEventListener('click', () => {
                char = ch;
                charInput.value = ch;
                palette.querySelectorAll('.ascii-char-btn').forEach(b => b.classList.remove('active'));
                btn.classList.add('active');
            });
            if (ch === char) btn.classList.add('active');
            grp.appendChild(btn);
        }
        palette.appendChild(grp);
    }
}

// ── Export ──

function exportText() {
    // Trim trailing spaces per line, remove trailing empty lines
    const lines = data.map(row => row.join('').replace(/\s+$/, ''));
    while (lines.length > 1 && !lines[lines.length - 1]) lines.pop();
    return lines.join('\n');
}

// ── Open / Close ──

export function openCanvas(callback, existingText) {
    onInsert = callback;
    if (existingText) {
        // Parse existing canvas content
        const lines = existingText.split('\n');
        rows = Math.max(lines.length, 5);
        cols = Math.max(...lines.map(l => l.length), 20);
        heightInput.value = rows;
        widthInput.value = cols;
        data = [];
        for (let r = 0; r < rows; r++) {
            const row = [];
            const line = lines[r] || '';
            for (let c = 0; c < cols; c++) {
                row.push(c < line.length ? line[c] : ' ');
            }
            data.push(row);
        }
    } else {
        // Auto-fit to available grid container space
        overlay.classList.add('open');
        const container = grid.parentElement;
        requestAnimationFrame(() => {
            const style = getComputedStyle(grid);
            // Measure one character by rendering a single char
            grid.textContent = 'M';
            const charW = grid.getBoundingClientRect().width;
            const charH = grid.getBoundingClientRect().height;
            grid.textContent = '';
            if (charW > 0 && charH > 0) {
                const pad = parseFloat(getComputedStyle(container).paddingLeft) * 2;
                cols = Math.max(20, Math.floor((container.clientWidth - pad) / charW));
                rows = Math.max(5, Math.floor((container.clientHeight - pad) / charH));
            } else {
                cols = 80;
                rows = 30;
            }
            widthInput.value = cols;
            heightInput.value = rows;
            initGrid(cols, rows);
            buildPalette();
            selectTool('brush');
            measureCells();
        });
        return;
    }
    buildPalette();
    selectTool('brush');
    overlay.classList.add('open');
    render();
    requestAnimationFrame(() => measureCells());
}

function closeCanvas() {
    overlay.classList.remove('open');
    textCursor = null;
    drawing = false;
    onInsert = null;
}

// ── Event binding ──

function init() {
    if (!overlay) return;

    grid.addEventListener('mousedown', onMouseDown);
    window.addEventListener('mousemove', onMouseMove);
    window.addEventListener('mouseup', onMouseUp);

    // Keyboard for text tool + tool shortcuts
    overlay.addEventListener('keydown', e => {
        if (textCursor && handleTextKey(e)) { e.preventDefault(); e.stopPropagation(); return; }
        if (e.key === 'Escape') { closeCanvas(); e.stopPropagation(); return; }
        if (e.ctrlKey || e.metaKey) return;
        const shortcuts = { b: 'brush', e: 'eraser', l: 'line', r: 'rect', o: 'circle', f: 'fill', t: 'text' };
        if (shortcuts[e.key]) { selectTool(shortcuts[e.key]); e.preventDefault(); }
    });

    // Tool buttons
    toolbar.addEventListener('click', e => {
        const btn = e.target.closest('[data-tool]');
        if (btn) selectTool(btn.dataset.tool);
    });

    // Char input
    charInput.addEventListener('input', () => {
        if (charInput.value.length > 0) {
            char = charInput.value[charInput.value.length - 1];
            charInput.value = char;
            // Update palette highlight
            const palette = $('ascii-char-palette');
            palette?.querySelectorAll('.ascii-char-btn').forEach(b => {
                b.classList.toggle('active', b.textContent === char);
            });
        }
    });

    // OK button
    btnOk.addEventListener('click', () => {
        const text = exportText();
        if (onInsert && text.trim()) onInsert(text);
        closeCanvas();
    });

    // Cancel
    btnCancel.addEventListener('click', closeCanvas);

    // Clear
    btnClear.addEventListener('click', () => {
        initGrid(cols, rows);
    });

    // Resize
    btnResize.addEventListener('click', () => {
        const newW = parseInt(widthInput.value) || cols;
        const newH = parseInt(heightInput.value) || rows;
        // Preserve existing content
        const oldData = data;
        const oldRows = rows, oldCols = cols;
        cols = Math.max(5, Math.min(200, newW));
        rows = Math.max(3, Math.min(100, newH));
        widthInput.value = cols;
        heightInput.value = rows;
        data = [];
        for (let r = 0; r < rows; r++) {
            const row = [];
            for (let c = 0; c < cols; c++) {
                row.push(r < oldRows && c < oldCols ? oldData[r][c] : ' ');
            }
            data.push(row);
        }
        render();
    });

    // Close on overlay background click
    overlay.addEventListener('click', e => {
        if (e.target === overlay) closeCanvas();
    });
}

init();

// ── Plugin self-registration ──────────────────────────────────────

// Register markdown fenced block: ```ascii``` → styled 
hyprcrd.markdown.registerFencedBlock('ascii', (content, { esc }) => {
    return `
${esc(content.replace(/\s+$/, ''))}
`; }); // Register editor toolbar command "asciiart" hyprcrd.editor.registerCommand('asciiart', () => { const ed = hyprcrd.editor.api; if (!ed || !ed.body) return; const body = ed.body; // Detect if cursor is inside an existing ```ascii``` block (edit mode) const val = body.value; const pos = body.selectionStart; const before = val.substring(0, pos); const blockStart = before.lastIndexOf('```ascii\n'); const blockEndInBefore = blockStart >= 0 ? before.indexOf('\n```', blockStart + 9) : -1; let existingText = null, replaceStart = 0, replaceEnd = 0; if (blockStart >= 0 && blockEndInBefore < 0) { const contentStart = blockStart + 9; const endMark = val.indexOf('\n```', contentStart); if (endMark >= 0) { existingText = val.substring(contentStart, endMark); replaceStart = blockStart; replaceEnd = endMark + 4; } } openCanvas(text => { const block = '```ascii\n' + text + '\n```'; if (existingText !== null) { body.setRangeText(block, replaceStart, replaceEnd, 'end'); } else { const s = body.selectionStart; const prefix = s > 0 && val[s - 1] !== '\n' ? '\n' : ''; body.setRangeText(prefix + block + '\n', s, body.selectionEnd, 'end'); } ed.updatePreview?.(); }, existingText); }); // Inject toolbar button into #md-toolbar (after the "extract" button if present) function injectToolbarButton() { const toolbar = $('md-toolbar'); if (!toolbar) return; if (toolbar.querySelector('[data-md="asciiart"]')) return; // already injected const btn = document.createElement('button'); btn.setAttribute('data-md', 'asciiart'); btn.setAttribute('title', 'ASCII Canvas'); btn.className = 'toolbar-btn'; btn.innerHTML = ''; // Insert before the right-side spacer (flex-1 div), or append at end const spacer = toolbar.querySelector('.flex-1'); if (spacer) toolbar.insertBefore(btn, spacer); else toolbar.appendChild(btn); } if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', injectToolbarButton); } else { injectToolbarButton(); } // Register plugin metadata for introspection hyprcrd.registerPlugin({ name: 'canvas', version: '1.0.0', description: 'ASCII art canvas editor', });