// engine/dashboard/data-loader.ts — Reads project/spec data from data/ directory
// for the dashboard. All I/O is synchronous-style via fs promises.
import { readFile, readdir, stat } from 'node:fs/promises';
import { join } from 'node:path';
/** Root data directory for all project data */
const DATA_DIR = join(process.cwd(), 'data');
/** Safely read and parse a JSON file; returns null on any error */
async function readJsonFile(filePath) {
    try {
        const content = await readFile(filePath, 'utf-8');
        return JSON.parse(content);
    }
    catch {
        return null;
    }
}
/** Safely read a text file; returns null on any error */
async function readTextFile(filePath) {
    try {
        return await readFile(filePath, 'utf-8');
    }
    catch {
        return null;
    }
}
/** Parse criteria counts from PROGRESS.md content */
export function parseCriteriaFromProgress(content) {
    const lines = content.split('\n');
    let total = 0;
    let implemented = 0;
    for (const line of lines) {
        // Match table rows like: | AC-01 | description | Hecho | or | Pendiente |
        const match = /^\|\s*AC-\d+\s*\|/.exec(line);
        if (match) {
            total++;
            if (/\|\s*Hecho\s*\|/i.test(line) || /\|\s*Done\s*\|/i.test(line)) {
                implemented++;
            }
        }
    }
    return { total, implemented };
}
/** Detect the stack label from a raw project record */
function detectStack(raw) {
    if (raw.stack) {
        return raw.stack;
    }
    const parts = [];
    if (raw.language) {
        parts.push(raw.language);
    }
    if (raw.framework) {
        parts.push(raw.framework);
    }
    return parts.length > 0 ? parts.join(' / ') : 'Unknown';
}
/** Load spec cards for a project from its specs/ subdirectory */
async function loadSpecCards(projectDir) {
    const specsDir = join(projectDir, 'specs');
    let entries;
    try {
        entries = await readdir(specsDir);
    }
    catch {
        return [];
    }
    const cards = [];
    const validStatuses = ['draft', 'review', 'approved', 'implementing', 'done'];
    for (const entry of entries) {
        const specDir = join(specsDir, entry);
        const specFile = join(specDir, 'spec.json');
        const raw = await readJsonFile(specFile);
        if (!raw) {
            continue;
        }
        const specId = raw.id ?? entry;
        const title = raw.title ?? specId;
        const rawStatus = raw.status ?? 'draft';
        const status = validStatuses.includes(rawStatus)
            ? rawStatus
            : 'draft';
        // Try to load PROGRESS.md for criteria
        const progressPath = join(specDir, 'PROGRESS.md');
        const progressContent = await readTextFile(progressPath);
        const { total, implemented } = progressContent !== null
            ? parseCriteriaFromProgress(progressContent)
            : { total: 0, implemented: 0 };
        const progressPercent = total > 0 ? Math.round((implemented / total) * 100) : 0;
        cards.push({
            id: specId,
            title,
            status,
            criteriaTotal: total,
            criteriaImplemented: implemented,
            progressPercent,
        });
    }
    // Sort by spec ID ascending
    cards.sort((a, b) => a.id.localeCompare(b.id));
    return cards;
}
/** Load all projects from data/projects/ */
export async function loadAllProjects() {
    const projectsDir = join(DATA_DIR, 'projects');
    let entries;
    try {
        entries = await readdir(projectsDir);
    }
    catch {
        return [];
    }
    const summaries = [];
    for (const entry of entries) {
        const projectDir = join(projectsDir, entry);
        // Skip non-directories
        try {
            const s = await stat(projectDir);
            if (!s.isDirectory()) {
                continue;
            }
        }
        catch {
            continue;
        }
        const projectFile = join(projectDir, 'project.json');
        const raw = await readJsonFile(projectFile);
        if (!raw) {
            continue;
        }
        const specs = await loadSpecCards(projectDir);
        const doneCount = specs.filter((s) => s.status === 'done').length;
        const lastActivity = raw.updatedAt ?? raw.createdAt ?? 'Unknown';
        summaries.push({
            id: entry,
            name: raw.name ?? entry,
            path: raw.path ?? '',
            stack: detectStack(raw),
            lastActivity,
            specCount: specs.length,
            doneCount,
            specs,
        });
    }
    return summaries;
}
/** Load a single project by its ID */
export async function loadProject(projectId) {
    const projects = await loadAllProjects();
    return projects.find((p) => p.id === projectId) ?? null;
}
/** Compute health metrics for a project */
export function computeHealthMetrics(project) {
    const totalSpecs = project.specCount;
    const donePercent = totalSpecs > 0 ? Math.round((project.doneCount / totalSpecs) * 100) : 0;
    const pendingApproval = project.specs.filter((s) => s.status === 'draft' || s.status === 'review').length;
    const activeImplementation = project.specs.filter((s) => s.status === 'implementing').length;
    return {
        totalSpecs,
        donePercent,
        pendingApproval,
        activeImplementation,
        totalEstimation: null,
    };
}
/** Read spec markdown files for the detail view */
export async function loadSpecDetail(projectId, specId) {
    const specDir = join(DATA_DIR, 'projects', projectId, 'specs', specId);
    const [hu, ficha, progress] = await Promise.all([
        readTextFile(join(specDir, 'HU.md')),
        readTextFile(join(specDir, 'FICHA-TECNICA.md')),
        readTextFile(join(specDir, 'PROGRESS.md')),
    ]);
    return { hu, ficha, progress };
}
/** Read usage stats from data/global/usage.json (written by SPEC-064) */
export async function loadUsageStats() {
    const usageFile = join(DATA_DIR, 'global', 'usage.json');
    const raw = await readJsonFile(usageFile);
    if (!raw) {
        return {
            tier: 'unknown',
            callsToday: 0,
            dailyLimit: null,
            trialDaysLeft: null,
            isAvailable: false,
        };
    }
    return {
        tier: raw.tier ?? 'free',
        callsToday: raw.callsToday ?? 0,
        dailyLimit: raw.dailyLimit ?? null,
        trialDaysLeft: raw.trialDaysLeft ?? null,
        isAvailable: true,
    };
}
//# sourceMappingURL=data-loader.js.map