// tools/workspace-dashboard-handler.ts — SPEC-363: Multi-tenant Workspace Dashboard
// SPEC-772: workspace_alerts also surfaces autopilot log failures from last 24h
// SPEC-781: workspace_alerts surfaces specs with pending async analysis
import { buildWorkspaceOverview, buildWorkspaceHealth, runBulkOperation, generateWorkspaceReport, searchWorkspace, getWorkspaceAlerts, } from '../engine/workspace-manager.js';
import { usageStore } from '../storage/index.js';
import { readRegisteredToolNames } from './tool-usage-report/registered-tools.js';
import { getProjects } from '../storage/global-projects-store.js';
import { hashProjectPath } from '../storage/base-store.js';
import { readRecentAutopilotAlerts } from '../storage/autopilot-log-store.js';
import { verifyStateFiles } from '../engine/cascade-hooks/state-drift-detector.js';
import { readFile } from 'node:fs/promises';
import { join } from 'node:path';
import { getAsyncAnalysisPath } from './create-spec/post-creation.js';
export async function handleWorkspaceOverview(args) {
    const result = await buildWorkspaceOverview(args.healthThreshold);
    if (result.projectCount === 0) {
        return {
            content: [
                {
                    type: 'text',
                    text: 'No projects registered. Use `register_project_path` to add projects to the workspace.',
                },
            ],
        };
    }
    const lines = [
        `# Workspace Overview`,
        ``,
        `**${String(result.projectCount)} project(s) registered** — generated ${result.generatedAt}`,
        ``,
        `| Project | Specs | Done | Implementing | In Review | Health |`,
        `|---|---|---|---|---|---|`,
    ];
    for (const p of result.projects) {
        const shortPath = p.path.split('/').slice(-2).join('/');
        lines.push(`| ${shortPath} | ${String(p.specCount)} | ${String(p.doneCount)} | ${String(p.implementingCount)} | ${String(p.reviewCount)} | ${String(p.healthScore)}% |`);
    }
    return { content: [{ type: 'text', text: lines.join('\n') }] };
}
export async function handleWorkspaceHealth(args) {
    const metrics = await buildWorkspaceHealth(args.healthThreshold);
    const toolUsageSummary = await buildUsageSection();
    const text = JSON.stringify({ ...metrics, toolUsageSummary }, null, 2);
    return { content: [{ type: 'text', text }] };
}
async function buildUsageSection() {
    try {
        const usageLog = await usageStore.getUsageLog();
        const windowStart = new Date(Date.now() - 30 * 24 * 60 * 60 * 1000).toISOString();
        const allEvents = usageLog.events;
        const events = allEvents.filter((e) => e.timestamp >= windowStart);
        const callsByTool = new Map();
        for (const e of events) {
            callsByTool.set(e.toolName, (callsByTool.get(e.toolName) ?? 0) + 1);
        }
        const top5 = [...callsByTool.entries()].sort((a, b) => b[1] - a[1]).slice(0, 5);
        const registeredTools = await readRegisteredToolNames();
        const zeroCount = registeredTools.filter((name) => !callsByTool.has(name)).length;
        const lines = [
            `## Tool Usage (last 30 days)`,
            ``,
            `**Total calls**: ${String(events.length)} | **Zero-usage tools**: ${String(zeroCount)}`,
            ``,
        ];
        if (top5.length > 0) {
            lines.push(`| Tool | Calls |`);
            lines.push(`|---|---|`);
            for (const [name, count] of top5) {
                lines.push(`| ${name} | ${String(count)} |`);
            }
        }
        else {
            lines.push(`_No tool calls recorded in the last 30 days._`);
        }
        lines.push(``, `_Run \`tool_usage_report\` for full analytics._`);
        return lines.join('\n');
    }
    catch {
        return `## Tool Usage (last 30 days)\n\n_Usage data unavailable._`;
    }
}
export async function handleBulkProjectOperations(args) {
    const result = await runBulkOperation(args.operation, args.projectPaths, args.dryRun ?? false);
    const lines = [
        `# Bulk Operation: ${args.operation}`,
        ``,
        `**Dry run:** ${String(result.dryRun)} | **Projects affected:** ${String(result.projectsAffected)}`,
        ``,
        `## Results`,
        ``,
    ];
    for (const r of result.results) {
        const icon = r.status === 'ok' ? '✅' : r.status === 'skipped' ? '⏭️' : '❌';
        const msg = r.message !== undefined ? ` — ${r.message}` : '';
        lines.push(`${icon} \`${r.path}\`${msg}`);
    }
    return { content: [{ type: 'text', text: lines.join('\n') }] };
}
export async function handleWorkspaceReport(args) {
    void args;
    const html = await generateWorkspaceReport();
    return { content: [{ type: 'text', text: html }] };
}
export async function handleWorkspaceSearch(args) {
    const results = await searchWorkspace(args.query, args.maxResults);
    if (results.length === 0) {
        return {
            content: [
                {
                    type: 'text',
                    text: `No specs found matching "${args.query}" across registered projects.`,
                },
            ],
        };
    }
    const lines = [
        `# Search Results: "${args.query}"`,
        ``,
        `Found ${String(results.length)} spec(s)`,
        ``,
        `| Project | Spec ID | Title | Status | Matched In |`,
        `|---|---|---|---|---|`,
    ];
    for (const r of results) {
        const shortPath = r.projectPath.split('/').slice(-2).join('/');
        lines.push(`| ${shortPath} | ${r.specId} | ${r.title} | ${r.status} | ${r.matchedIn} |`);
    }
    return { content: [{ type: 'text', text: lines.join('\n') }] };
}
/** SPEC-781: Check if a spec has pending async analysis in external project data. */
async function hasPendingAnalysis(projectPath, specId) {
    try {
        await readFile(getAsyncAnalysisPath(projectPath, specId), 'utf-8');
        return false;
    }
    catch {
        return true;
    }
}
/** SPEC-781: Collect specs with pendingAnalysis: true from spec frontmatter. */
async function collectPendingAnalysisAlerts(projectPath) {
    try {
        const { glob } = await import('glob');
        const specFiles = await glob(join(projectPath, 'planu/specs/*/spec.md'));
        const results = [];
        const PENDING_RE = /^pendingAnalysis:\s*true/m;
        const ID_RE = /^id:\s*(SPEC-\d+)/m;
        for (const specFile of specFiles) {
            const content = await readFile(specFile, 'utf-8').catch(() => '');
            if (!PENDING_RE.test(content)) {
                continue;
            }
            const idMatch = ID_RE.exec(content);
            const specId = idMatch?.[1];
            if (specId === undefined) {
                continue;
            }
            if (!(await hasPendingAnalysis(projectPath, specId))) {
                continue;
            }
            results.push({ specId, message: `Spec ${specId} analysis still running` });
        }
        return results;
    }
    catch {
        return [];
    }
}
export async function handleWorkspaceAlerts(args) {
    const [alerts, projects] = await Promise.all([
        getWorkspaceAlerts(args.staleDaysThreshold),
        getProjects(),
    ]);
    // SPEC-776: collect state drift alerts across all registered projects
    const driftAlerts = [];
    await Promise.all(projects.map(async (project) => {
        try {
            const driftReport = await verifyStateFiles(project.path);
            for (const alert of driftReport.alerts) {
                driftAlerts.push({ projectPath: project.path, message: alert.message, fix: alert.fix });
            }
        }
        catch {
            // best-effort
        }
    }));
    // SPEC-772: collect autopilot log failures from last 24h across all projects
    const autopilotFailures = [];
    // SPEC-781: collect pending analysis alerts across all projects
    const pendingAnalysisAlerts = [];
    await Promise.all(projects.map(async (project) => {
        try {
            const projectId = hashProjectPath(project.path);
            const entries = await readRecentAutopilotAlerts(projectId, 24);
            for (const entry of entries) {
                if (entry.result === 'fail') {
                    autopilotFailures.push({
                        projectPath: project.path,
                        specId: entry.specId,
                        hookName: entry.hookName,
                        error: entry.error ?? 'unknown error',
                        timestamp: entry.timestamp,
                    });
                }
            }
        }
        catch {
            // best-effort
        }
        // SPEC-781: check for specs with pending analysis in this project
        try {
            const pending = await collectPendingAnalysisAlerts(project.path);
            for (const p of pending) {
                pendingAnalysisAlerts.push({ projectPath: project.path, ...p });
            }
        }
        catch {
            // best-effort
        }
    }));
    if (alerts.length === 0 &&
        autopilotFailures.length === 0 &&
        driftAlerts.length === 0 &&
        pendingAnalysisAlerts.length === 0) {
        return {
            content: [
                {
                    type: 'text',
                    text: `No stale specs found (threshold: ${String(args.staleDaysThreshold ?? 14)} days). All projects are up to date.`,
                },
            ],
        };
    }
    const lines = [`# Workspace Alerts`, ``];
    if (alerts.length > 0) {
        lines.push(`## Stale Specs (>${String(args.staleDaysThreshold ?? 14)} days)`, ``, `| Days Stale | Project | Spec ID | Title | Status |`, `|---|---|---|---|---|`);
        for (const a of alerts) {
            const shortPath = a.projectPath.split('/').slice(-2).join('/');
            lines.push(`| ${String(a.daysSinceUpdate)}d | ${shortPath} | ${a.specId} | ${a.title} | ${a.status} |`);
        }
        lines.push(``);
    }
    if (autopilotFailures.length > 0) {
        lines.push(`## Autopilot Hook Failures (last 24h)`, ``, `| Project | Spec | Hook | Error | Time |`, `|---|---|---|---|---|`);
        for (const f of autopilotFailures) {
            const shortPath = f.projectPath.split('/').slice(-2).join('/');
            const shortTime = f.timestamp.replace('T', ' ').substring(0, 19);
            lines.push(`| ${shortPath} | ${f.specId} | ${f.hookName} | ${f.error} | ${shortTime} |`);
        }
    }
    // SPEC-776: state drift alerts
    if (driftAlerts.length > 0) {
        lines.push(``, `## State File Drift`, ``, `| Project | Alert | Fix |`, `|---|---|---|`);
        for (const d of driftAlerts) {
            const shortPath = d.projectPath.split('/').slice(-2).join('/');
            const fix = d.fix ?? '—';
            lines.push(`| ${shortPath} | ${d.message} | \`${fix}\` |`);
        }
    }
    // SPEC-781: pending analysis info alerts
    if (pendingAnalysisAlerts.length > 0) {
        lines.push(``, `## Pending Analysis (info)`, ``, `| Project | Spec | Message |`, `|---|---|---|`);
        for (const p of pendingAnalysisAlerts) {
            const shortPath = p.projectPath.split('/').slice(-2).join('/');
            lines.push(`| ${shortPath} | ${p.specId} | ${p.message} |`);
        }
    }
    return { content: [{ type: 'text', text: lines.join('\n') }] };
}
//# sourceMappingURL=workspace-dashboard-handler.js.map