import { readdir, readFile, writeFile } from 'node:fs/promises';
import { join } from 'node:path';
import { getFrontmatterBlock } from '../shared/frontmatter.js';
import { computeManagedIndexUpdate } from '../shared/managed-index.js';
import YAML from 'yaml';
export const DECISION_INDEX_START = '<!-- OAT DECISION-INDEX -->';
export const DECISION_INDEX_END = '<!-- END OAT DECISION-INDEX -->';
function parseDecisionFrontmatter(content, filePath) {
    const rawFrontmatter = getFrontmatterBlock(content);
    if (!rawFrontmatter) {
        return null;
    }
    const parsed = YAML.parse(rawFrontmatter);
    if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
        throw new Error(`Frontmatter in ${filePath} must be a YAML object.`);
    }
    const frontmatter = parsed;
    return {
        id: String(frontmatter.id ?? ''),
        date: String(frontmatter.date ?? ''),
        status: String(frontmatter.status ?? ''),
        title: String(frontmatter.title ?? ''),
        legacyId: frontmatter.legacy_id === null || frontmatter.legacy_id === undefined
            ? ''
            : String(frontmatter.legacy_id),
    };
}
function compareRecords(a, b) {
    if (a.date > b.date) {
        return -1;
    }
    if (a.date < b.date) {
        return 1;
    }
    if (a.id < b.id) {
        return -1;
    }
    if (a.id > b.id) {
        return 1;
    }
    return 0;
}
function formatCell(value) {
    const trimmed = value.trim();
    if (trimmed.length === 0) {
        return '-';
    }
    return trimmed.replace(/\r?\n/g, ' ').replace(/\|/g, '\\|');
}
function isMissingFileError(error) {
    return (error !== null &&
        typeof error === 'object' &&
        'code' in error &&
        error.code === 'ENOENT');
}
async function readDecisionIndex(decisionsRoot) {
    const indexPath = join(decisionsRoot, 'index.md');
    try {
        return {
            content: await readFile(indexPath, 'utf8'),
            indexPath,
        };
    }
    catch (error) {
        if (isMissingFileError(error)) {
            throw new Error(`Decision index scaffold is missing at ${indexPath}. Run \`oat decision init\` before creating or regenerating decision records.`, { cause: error });
        }
        throw error;
    }
}
function findManagedSectionBounds(content, indexPath) {
    const startIndex = content.indexOf(DECISION_INDEX_START);
    const endIndex = content.indexOf(DECISION_INDEX_END);
    if (startIndex === -1 || endIndex === -1 || endIndex < startIndex) {
        throw new Error(`Managed decision index markers missing in ${indexPath}. Expected the exact marker pair:\n${DECISION_INDEX_START}\n${DECISION_INDEX_END}\nRun \`oat decision init\` if the decision scaffold is missing, or restore those exact markers in \`decisions/index.md\` before rerunning \`oat decision regenerate-index\`.`);
    }
    return { startIndex, endIndex };
}
export async function assertDecisionIndexScaffold(decisionsRoot) {
    const { content, indexPath } = await readDecisionIndex(decisionsRoot);
    findManagedSectionBounds(content, indexPath);
}
export function renderDecisionManagedSection(records) {
    const rows = records.length > 0
        ? records.map((record) => `| ${formatCell(record.id)} | ${formatCell(record.date)} | ${formatCell(record.status)} | ${formatCell(record.title)} | ${formatCell(record.legacyId)} |`)
        : ['| _No decisions yet_ | - | - | - | - |'];
    return [
        DECISION_INDEX_START,
        '| ID | Date | Status | Title | Legacy |',
        '| --- | --- | --- | --- | --- |',
        ...rows,
        DECISION_INDEX_END,
    ].join('\n');
}
export async function regenerateDecisionIndex(decisionsRoot) {
    const { content, indexPath } = await readDecisionIndex(decisionsRoot);
    const { startIndex, endIndex } = findManagedSectionBounds(content, indexPath);
    const entries = (await readdir(decisionsRoot, { withFileTypes: true }))
        .filter((entry) => entry.isFile() &&
        entry.name.endsWith('.md') &&
        entry.name !== 'index.md')
        .map((entry) => entry.name)
        .sort();
    const records = [];
    for (const entry of entries) {
        const filePath = join(decisionsRoot, entry);
        const record = parseDecisionFrontmatter(await readFile(filePath, 'utf8'), filePath);
        if (record) {
            records.push(record);
        }
    }
    records.sort(compareRecords);
    // Content-idempotent gate: when the on-disk managed block is logically equal
    // to the freshly rendered block (cells equal after trimming, so external
    // formatter padding is ignored), preserve the existing bytes and skip the
    // write. Only rewrite on a genuine content change.
    const { content: updated } = computeManagedIndexUpdate(content, startIndex, endIndex + DECISION_INDEX_END.length, renderDecisionManagedSection(records));
    if (updated === null) {
        return;
    }
    await writeFile(indexPath, updated, 'utf8');
}
