import fs from 'node:fs/promises';
import path from 'node:path';
import matter from 'gray-matter';
export class PagesService {
    root;
    constructor(cwd, pagesDir = 'pages') {
        this.root = path.join(cwd, pagesDir);
    }
    async ensureRoot() {
        await fs.mkdir(this.root, { recursive: true });
    }
    async listTree() {
        await this.ensureRoot();
        return await this.walk(this.root, '');
    }
    async listMarkdownFiles() {
        await this.ensureRoot();
        return this.collectMd(this.root, '');
    }
    async read(relPath) {
        const abs = this.resolveSafe(relPath);
        const raw = await fs.readFile(abs, 'utf-8');
        const parsed = matter(raw);
        return {
            path: relPath,
            frontmatter: (parsed.data ?? {}),
            body: parsed.content,
        };
    }
    async write(relPath, input) {
        const abs = this.resolveSafe(relPath);
        await fs.mkdir(path.dirname(abs), { recursive: true });
        const hasFrontmatter = input.frontmatter && Object.keys(input.frontmatter).length > 0;
        const serialized = hasFrontmatter
            ? matter.stringify(input.body, input.frontmatter)
            : input.body;
        await fs.writeFile(abs, serialized, 'utf-8');
        return {
            path: relPath,
            frontmatter: input.frontmatter ?? {},
            body: input.body,
        };
    }
    async remove(relPath) {
        const abs = this.resolveSafe(relPath);
        await fs.unlink(abs);
    }
    async search(query, limit = 50) {
        const q = query.trim();
        if (!q)
            return [];
        const lower = q.toLowerCase();
        const files = await this.listMarkdownFiles();
        const hits = [];
        for (const rel of files) {
            if (hits.length >= limit)
                break;
            const pathHit = rel.toLowerCase().includes(lower);
            const abs = this.resolveSafe(rel);
            let snippet = null;
            let line = 0;
            try {
                const raw = await fs.readFile(abs, 'utf-8');
                const lines = raw.split('\n');
                for (let i = 0; i < lines.length; i++) {
                    const text = lines[i] ?? '';
                    if (text.toLowerCase().includes(lower)) {
                        snippet = text.trim().slice(0, 160);
                        line = i + 1;
                        break;
                    }
                }
            }
            catch {
                /* ignore unreadable file */
            }
            if (pathHit || snippet) {
                hits.push({
                    path: rel,
                    line,
                    snippet: snippet ?? '',
                    matchesPath: pathHit,
                });
            }
        }
        return hits;
    }
    async exists(relPath) {
        try {
            const abs = this.resolveSafe(relPath);
            await fs.access(abs);
            return true;
        }
        catch {
            return false;
        }
    }
    resolveSafe(relPath) {
        if (!relPath || relPath.includes('\0'))
            throw new Error('invalid path');
        if (!relPath.endsWith('.md'))
            throw new Error('only .md paths allowed');
        const abs = path.resolve(this.root, relPath);
        const rel = path.relative(this.root, abs);
        if (rel.startsWith('..') || path.isAbsolute(rel)) {
            throw new Error(`path escapes pages root: ${relPath}`);
        }
        return abs;
    }
    async collectMd(dir, prefix) {
        const entries = await fs.readdir(dir, { withFileTypes: true });
        const out = [];
        for (const entry of entries) {
            if (entry.name.startsWith('.'))
                continue;
            const rel = prefix ? `${prefix}/${entry.name}` : entry.name;
            if (entry.isDirectory()) {
                out.push(...(await this.collectMd(path.join(dir, entry.name), rel)));
            }
            else if (entry.isFile() && entry.name.endsWith('.md')) {
                out.push(rel);
            }
        }
        return out;
    }
    async walk(dir, relPrefix) {
        const entries = await fs.readdir(dir, { withFileTypes: true });
        const nodes = [];
        for (const entry of entries) {
            if (entry.name.startsWith('.'))
                continue;
            const rel = relPrefix ? `${relPrefix}/${entry.name}` : entry.name;
            if (entry.isDirectory()) {
                const children = await this.walk(path.join(dir, entry.name), rel);
                nodes.push({ type: 'folder', name: entry.name, path: rel, children });
            }
            else if (entry.isFile() && entry.name.endsWith('.md')) {
                nodes.push({ type: 'file', name: entry.name, path: rel });
            }
        }
        nodes.sort((a, b) => {
            if (a.type !== b.type)
                return a.type === 'folder' ? -1 : 1;
            return a.name.localeCompare(b.name);
        });
        return nodes;
    }
}
//# sourceMappingURL=pages.js.map