---
import fs from 'node:fs';

// --- Forth syntax highlighter ---

const EMIT = new Set(['.', '.!']);
const SOUNDS = new Set([
    'sound', 's', 'saw', 'sine', 'kick', 'hat', 'snare', 'modal', 'noise',
    'square', 'tri', 'pulse', 'clap', 'rim', 'crash', 'fm', 'sample', 'plaits',
    'analog', 'waveshaping', 'granular', 'string', 'chord', 'speech', 'sub',
    'super', 'wt', 'input', 'hh',
]);
const PARAMS = new Set([
    'freq', 'note', 'gain', 'decay', 'attack', 'release', 'lpf', 'hpf', 'bpf',
    'verb', 'delay', 'pan', 'orbit', 'harmonics', 'distort', 'speed', 'voice',
    'dur', 'sustain', 'delaytime', 'delayfb', 'chorus', 'phaser', 'flanger',
    'crush', 'fold', 'wrap', 'resonance', 'begin', 'end', 'velocity', 'chan',
    'dev', 'ccnum', 'ccout', 'bend', 'pressure', 'program', 'tilt', 'slope',
    'sub_gain', 'sub_oct', 'feedback', 'depth', 'sweep', 'comb', 'damping',
    'detune', 'timbre', 'morph', 'color', 'model', 'cutoff',
]);
const STACK = new Set([
    'dup', 'drop', 'swap', 'over', 'rot', 'nip', 'tuck', '2dup', '2drop',
]);
const PROB = new Set([
    'chance', 'prob', 'choose', 'wchoose', 'cycle', 'pcycle', 'bounce',
    'rand', 'exprand', 'logrand', 'coin', 'seed', 'often', 'sometimes',
    'rarely', 'always', 'never', 'almostAlways', 'almostNever', 'every',
    'except', 'bjork', 'pbjork', 'shuffle',
]);
const KEYWORDS = new Set([
    ':', ';', 'if', 'then', 'else', 'do', 'loop', '?', '!?', 'ifelse',
    'select', 'apply', 'times', 'forget', 'all', 'noall', 'clear', 'm.',
]);
const CONTEXT = new Set([
    'step', 'beat', 'pattern', 'tempo', 'phase', 'runs', 'iter', 'fill', 'stepdur',
]);
const NOTE_RE = /^[a-g][#sb]?\d+$/i;
const NUM_RE = /^-?\d+(\.\d+)?$/;

function classifyToken(word: string): string | null {
    if (EMIT.has(word)) return 'f-emit';
    if (SOUNDS.has(word)) return 'f-snd';
    if (PARAMS.has(word)) return 'f-par';
    if (STACK.has(word)) return 'f-stack';
    if (PROB.has(word)) return 'f-prob';
    if (KEYWORDS.has(word)) return 'f-kw';
    if (CONTEXT.has(word)) return 'f-ctx';
    if (NOTE_RE.test(word)) return 'f-note';
    if (NUM_RE.test(word)) return 'f-num';
    if (word.length > 1 && (word[0] === '@' || word[0] === '!' || word[0] === ',')) return 'f-var';
    return null;
}

function highlightForth(code: string): string {
    return code.split('\n').map(line => {
        let result = '';
        let i = 0;
        while (i < line.length) {
            if (line[i] === ' ' || line[i] === '\t') { result += line[i]; i++; continue; }
            if (line[i] === ';' && line[i + 1] === ';') {
                result += `<span class="f-com">${escapeHtml(line.slice(i))}</span>`;
                break;
            }
            if (line[i] === '"') {
                let end = line.indexOf('"', i + 1);
                if (end === -1) end = line.length - 1;
                result += escapeHtml(line.slice(i, end + 1));
                i = end + 1;
                continue;
            }
            if (line[i] === '(' || line[i] === ')') { result += line[i]; i++; continue; }
            let end = i;
            while (end < line.length && line[end] !== ' ' && line[end] !== '\t') end++;
            const word = line.slice(i, end);
            const cls = classifyToken(word);
            result += cls ? `<span class="${cls}">${escapeHtml(word)}</span>` : escapeHtml(word);
            i = end;
        }
        return result;
    }).join('\n');
}

// --- Markdown renderer ---

function escapeHtml(s: string): string {
    return s.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
}

function inline(text: string): string {
    const codes: string[] = [];
    text = text.replace(/`([^`]+)`/g, (_, code) => { codes.push(code); return `\x00${codes.length - 1}\x00`; });
    text = escapeHtml(text);
    text = text.replace(/\[([^\]]+)\]\(([^)]+)\)/g, '<a href="$2" target="_blank">$1</a>');
    text = text.replace(/~~([^~]+)~~/g, '<del>$1</del>');
    text = text.replace(/\*\*([^*]+)\*\*/g, '<strong>$1</strong>');
    text = text.replace(/_([^_]+)_/g, '<em>$1</em>');
    text = text.replace(/\x00(\d+)\x00/g, (_, i) => `<code>${escapeHtml(codes[+i])}</code>`);
    return text;
}

function parseTableCells(line: string, tag: string): string {
    return line.split('|').slice(1, -1).map(cell => `<${tag}>${inline(cell.trim())}</${tag}>`).join('');
}

function renderMarkdown(md: string): string {
    const lines = md.split('\n');
    let html = '';
    let i = 0;

    while (i < lines.length) {
        const line = lines[i];
        if (line.trim() === '') { i++; continue; }

        if (line.startsWith('### ')) { html += `<h3>${inline(line.slice(4))}</h3>`; i++; continue; }
        if (line.startsWith('## '))  { html += `<h2>${inline(line.slice(3))}</h2>`; i++; continue; }
        if (line.startsWith('# '))   { html += `<h1>${inline(line.slice(2))}</h1>`; i++; continue; }

        if (/^---+$|^\*\*\*+$/.test(line.trim())) { html += '<hr>'; i++; continue; }

        if (line.startsWith('> ')) {
            html += '<blockquote>';
            while (i < lines.length && lines[i].startsWith('> ')) {
                html += `<p>${inline(lines[i].slice(2))}</p>`;
                i++;
            }
            html += '</blockquote>';
            continue;
        }

        if (line.startsWith('```')) {
            const lang = line.slice(3).trim();
            i++;
            let code = '';
            while (i < lines.length && !lines[i].startsWith('```')) {
                code += (code ? '\n' : '') + lines[i];
                i++;
            }
            i++;
            html += lang === 'forth'
                ? `<pre><code>${highlightForth(code)}</code></pre>`
                : `<pre><code>${escapeHtml(code)}</code></pre>`;
            continue;
        }

        if (line.startsWith('|')) {
            html += '<table>';
            html += `<tr>${parseTableCells(line, 'th')}</tr>`;
            i++;
            if (i < lines.length && /^\|[\s:|-]+\|$/.test(lines[i])) i++;
            while (i < lines.length && lines[i].startsWith('|')) {
                html += `<tr>${parseTableCells(lines[i], 'td')}</tr>`;
                i++;
            }
            html += '</table>';
            continue;
        }

        if (/^\d+\.\s/.test(line)) {
            html += '<ol>';
            while (i < lines.length && (/^\d+\.\s/.test(lines[i]) || /^\s+[*-]\s/.test(lines[i]))) {
                if (/^\d+\.\s/.test(lines[i])) {
                    html += `<li>${inline(lines[i].replace(/^\d+\.\s/, ''))}`;
                    i++;
                    if (i < lines.length && /^\s+[*-]\s/.test(lines[i])) {
                        html += '<ul>';
                        while (i < lines.length && /^\s+[*-]\s/.test(lines[i])) {
                            html += `<li>${inline(lines[i].replace(/^\s*[*-]\s/, ''))}</li>`;
                            i++;
                        }
                        html += '</ul>';
                    }
                    html += '</li>';
                } else {
                    i++;
                }
            }
            html += '</ol>';
            continue;
        }

        if (/^(\s*)[*-]\s/.test(line)) {
            let depth = 0;
            let first = true;
            html += '<ul>';
            while (i < lines.length && /^(\s*)[*-]\s/.test(lines[i])) {
                const indent = lines[i].match(/^(\s*)/)![1].length;
                const level = Math.floor(indent / 2);
                if (level > depth) {
                    while (level > depth) { html += '<ul>'; depth++; }
                } else if (level < depth) {
                    html += '</li>';
                    while (level < depth) { html += '</ul></li>'; depth--; }
                } else if (!first) {
                    html += '</li>';
                }
                html += `<li>${inline(lines[i].replace(/^\s*[*-]\s/, ''))}`;
                first = false;
                i++;
            }
            while (depth > 0) { html += '</li></ul>'; depth--; }
            html += '</li></ul>';
            continue;
        }

        let para = '';
        while (i < lines.length && lines[i].trim() !== ''
            && !lines[i].startsWith('#') && !lines[i].startsWith('```')
            && !lines[i].startsWith('|') && !/^(\s*)[*-]\s/.test(lines[i])
            && !/^\d+\.\s/.test(lines[i])) {
            para += (para ? ' ' : '') + lines[i];
            i++;
        }
        if (para) html += `<p>${inline(para)}</p>`;
    }
    return html;
}

// --- Section/topic manifest — mirrors src/model/docs.rs (keep in sync!) ---

const SECTIONS = [
    {
        title: "Getting Started",
        topics: [
            { title: "Welcome", file: "welcome.md" },
            { title: "Navigation", file: "getting-started/navigation.md" },
            { title: "The Big Picture", file: "getting-started/big_picture.md" },
            { title: "Banks & Patterns", file: "getting-started/banks_patterns.md" },
            { title: "Stage / Commit", file: "getting-started/staging.md" },
            { title: "Using the Sequencer", file: "getting-started/grid.md" },
            { title: "Editing a Step", file: "getting-started/editing.md" },
            { title: "The Audio Engine", file: "getting-started/engine.md" },
            { title: "Options", file: "getting-started/options.md" },
            { title: "Saving & Loading", file: "getting-started/saving.md" },
            { title: "The Sample Browser", file: "getting-started/samples.md" },
        ],
    },
    {
        title: "Cagire's Forth",
        topics: [
            { title: "About Forth", file: "forth/about_forth.md" },
            { title: "The Dictionary", file: "forth/dictionary.md" },
            { title: "The Stack", file: "forth/stack.md" },
            { title: "Creating Words", file: "forth/definitions.md" },
            { title: "Control Flow", file: "forth/control_flow.md" },
            { title: "The Prelude", file: "forth/prelude.md" },
            { title: "Cagire vs Classic", file: "forth/oddities.md" },
        ],
    },
    {
        title: "Building sounds",
        topics: [
            { title: "Introduction", file: "engine/intro.md" },
            { title: "Settings", file: "engine/settings.md" },
            { title: "Sources", file: "engine/sources.md" },
            { title: "Samples", file: "engine/samples.md" },
            { title: "Wavetables", file: "engine/wavetable.md" },
            { title: "Filters", file: "engine/filters.md" },
            { title: "Modulation", file: "engine/modulation.md" },
            { title: "Distortion", file: "engine/distortion.md" },
            { title: "Space & Time", file: "engine/space.md" },
            { title: "Audio-Rate Mod", file: "engine/audio_modulation.md" },
            { title: "Words & Sounds", file: "engine/words.md" },
        ],
    },
    {
        title: "Branching out",
        topics: [
            { title: "Introduction", file: "midi/intro.md" },
            { title: "MIDI Output", file: "midi/output.md" },
            { title: "MIDI Input", file: "midi/input.md" },
        ],
    },
    {
        title: "Tutorials",
        topics: [
            { title: "Randomness", file: "tutorials/randomness.md" },
            { title: "Notes & Harmony", file: "tutorials/harmony.md" },
            { title: "Generators", file: "tutorials/generators.md" },
            { title: "Timing with at", file: "tutorials/at.md" },
            { title: "Using Variables", file: "tutorials/variables.md" },
            { title: "Recording", file: "tutorials/recording.md" },
            { title: "Soundfonts", file: "tutorials/soundfont.md" },
            { title: "Sharing", file: "tutorials/sharing.md" },
        ],
    },
];

// Read all doc files at build time
for (const section of SECTIONS) {
    for (const topic of section.topics) {
        const raw = fs.readFileSync(`../docs/${topic.file}`, 'utf-8');
        (topic as any).html = renderMarkdown(raw);
    }
}
---

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Cagire - Documentation</title>
    <meta name="description" content="Cagire documentation — Forth-based live coding sequencer">
    <link rel="icon" href="/favicon.ico">
    <link rel="stylesheet" href="/style.css">
    <link rel="stylesheet" href="/docs.css">
</head>
<body>
    <div class="docs-topbar">
        <a class="docs-back" href="/">&larr; Back to Cagire</a>
        <div class="docs-notice" id="docs-notice">
            <span>You are reading Cagire's built-in documentation. Here it is static, but inside the app the examples are runnable and the docs are interactive.</span>
            <button onclick="dismissNotice()">Got it</button>
        </div>
    </div>
    <div class="docs-layout">
        <nav class="docs-sidebar">
            {SECTIONS.map((section, si) => (
                <Fragment>
                    <h3>{section.title}</h3>
                    {section.topics.map((topic, ti) => (
                        <button data-s={si} data-t={ti}>{topic.title}</button>
                    ))}
                </Fragment>
            ))}
        </nav>
        <main class="docs-content">
            {SECTIONS.map((section, si) =>
                section.topics.map((topic, ti) => (
                    <article id={`topic-s${si}t${ti}`} set:html={(topic as any).html} />
                ))
            )}
        </main>
    </div>
    <script is:inline src="/docs.js"></script>
</body>
</html>
