import { ti } from '../i18n/index.js';
import { specStore, knowledgeStore } from '../storage/index.js';
import { buildListSpecsSummary } from '../engine/human-summary.js';
import { dirname } from 'node:path';
import { checkBundledVersionGap } from '../engine/version-detector/bundled-version-checker.js';
import { readLastModifiedTimestamp, formatRelativeDate } from '../engine/spec-changelog/index.js';
import { discoverAndFlattenSpecs, filterUnprefixedSpecs, importFilesystemSpecs, migrateSpecFolderNames, reconcileSpecPaths, scanForAmbiguousCriteria, } from '../engine/spec-migrator.js';
import { detectDrift, formatDriftMessage } from '../engine/spec-migrator/drift-detector.js';
import { AutopilotSummaryCollector } from '../engine/autopilot/summary-collector.js';
import { withAudit } from '../engine/autopilot/audit-logger.js';
import { trackCost } from '../engine/cost-tracking/operation-tracker.js';
import { refreshProjectStatus } from '../engine/autopilot/state-updater.js';
import { resolveProjectIdOrAutoDetect } from './resolve-project-id.js';
import { isNativeActive, fastScanSpecs } from '../engine/core-bridge.js';
/** Track which projects have been auto-discovered this session (once per project). */
const discoveredProjects = new Set();
/** Build markdown table rows for summary mode */
function buildSummaryTable(specs) {
    const header = '| ID | Title | Status | Type | Tags |';
    const divider = '|---|---|---|---|---|';
    const rows = specs.map((s) => `| ${s.id} | ${s.title} | ${s.status} | ${s.type} | ${s.tags.join(', ')} |`);
    return [header, divider, ...rows].join('\n');
}
async function autoDiscoverProject(projectId, projectPath, collector) {
    if (discoveredProjects.has(projectId)) {
        return;
    }
    discoveredProjects.add(projectId);
    try {
        await withAudit(projectPath, 'list_specs', 'discoverAndFlattenSpecs', () => discoverAndFlattenSpecs(projectPath));
    }
    catch {
        /* best-effort — don't fail list_specs */
    }
    try {
        await withAudit(projectPath, 'list_specs', 'importFilesystemSpecs', () => importFilesystemSpecs(projectPath, {
            listSpecs: specStore.listSpecs,
            createSpec: specStore.createSpec,
        }, projectId), (imported) => ({ imported: Array.isArray(imported) ? imported.length : 0 }));
    }
    catch {
        /* best-effort */
    }
    try {
        await withAudit(projectPath, 'list_specs', 'reconcileSpecPaths', () => reconcileSpecPaths(projectPath, {
            listSpecs: specStore.listSpecs,
            updateSpec: specStore.updateSpec,
        }, projectId));
    }
    catch {
        /* best-effort */
    }
    try {
        const allStoredSpecs = await specStore.listSpecs(projectId);
        const unprefixed = filterUnprefixedSpecs(allStoredSpecs);
        if (unprefixed.length > 0) {
            await withAudit(projectPath, 'list_specs', 'migrateSpecFolderNames', () => migrateSpecFolderNames(projectPath, unprefixed, {
                listSpecs: specStore.listSpecs,
                updateSpec: specStore.updateSpec,
            }), (migrated) => ({ count: Array.isArray(migrated) ? migrated.length : 0 }));
        }
    }
    catch {
        /* best-effort */
    }
    // SPEC-1017: list_specs enforces the strict planu/ policy in check mode.
    // Mutating cleanup is handled by init/update/validate/housekeeping; list_specs
    // stays read-mostly and surfaces exact offenders if any remain.
    try {
        const { validateStrictPlanuLayout } = await import('../engine/spec-migrator/index.js');
        const layout = await validateStrictPlanuLayout(projectPath);
        if (!layout.ok) {
            collector.pushOk('strict-planu-layout', `Non-canonical planu/ paths detected:\n${layout.offenders.map((p) => `- ${p}`).join('\n')}`);
        }
    }
    catch {
        /* best-effort — never block list_specs */
    }
    // SPEC-715: list_specs is read-only. Migrations are NEVER run here.
    // Drift is detected (read-only) and reported so the LLM can decide to run heal_spec_docs.
    try {
        const drift = await detectDrift(projectPath);
        const hasDrift = drift.unifiedMigrations > 0 || drift.leanMigrations > 0 || drift.planuRootCleanup > 0;
        if (hasDrift) {
            collector.pushOk('drift', formatDriftMessage(drift));
        }
    }
    catch {
        /* best-effort — never block list_specs */
    }
    try {
        const { configureGitignoreForPlanu } = await import('./init-project/git-setup.js');
        await withAudit(projectPath, 'list_specs', 'configureGitignoreForPlanu', () => configureGitignoreForPlanu(projectPath));
        collector.pushOk('gitignore', 'Updated .gitignore with planu/ rules');
    }
    catch {
        /* best-effort */
    }
    // SPEC-493: Silently repair status.json totalSpecs count from actual store data
    try {
        await withAudit(projectPath, 'list_specs', 'refreshProjectStatus', () => refreshProjectStatus(projectPath, projectId));
    }
    catch {
        /* best-effort */
    }
}
export async function handleListSpecs(params) {
    // SPEC-584: list_specs is read-mostly and its migration-issue questions are opt-in.
    // The gate is only enforced on state-mutating tools (create_spec, init_project).
    return trackCost(params.projectPath ?? '', 'list_specs', async () => {
        const { status, type, detail = 'summary' } = params;
        // SPEC-509: auto-detect project from git root when projectPath/projectId are omitted
        const resolved = await resolveProjectIdOrAutoDetect({
            projectId: params.projectId,
            projectPath: params.projectPath,
        });
        if (!resolved.ok) {
            return resolved.errorResult;
        }
        const projectId = resolved.projectId;
        const collector = new AutopilotSummaryCollector();
        // SPEC-756: lazy version check — fire-and-forget, non-blocking
        if (params.projectPath) {
            checkBundledVersionGap(2000)
                .then(() => undefined)
                .catch(() => {
                /* best-effort */
            });
        }
        // SPEC-592 follow-up: Codex enterprise cross-workspace isolation gate
        if (params.projectPath) {
            const { guardProjectPathForCodex } = await import('../hosts/codex/workspace-scope.js');
            const guard = guardProjectPathForCodex(params.projectPath);
            if (!guard.allowed) {
                return {
                    content: [{ type: 'text', text: `Access denied: ${guard.reason}` }],
                    isError: true,
                };
            }
        }
        try {
            // Verify project exists
            const knowledge = await knowledgeStore.getKnowledge(projectId);
            if (!knowledge) {
                return {
                    content: [{ type: 'text', text: ti('project.notFound', {}) }],
                    isError: true,
                };
            }
            // ─── Native optimized scan (SPEC-1004) ──────────────────────────────────
            let nativeBriefs = null;
            if (isNativeActive() && knowledge.projectPath) {
                nativeBriefs = fastScanSpecs(knowledge.projectPath);
            }
            // Auto-discover, flatten, rename, and fix specs (once per session per project)
            if (knowledge.projectPath) {
                await autoDiscoverProject(projectId, knowledge.projectPath, collector);
            }
            // Get all specs
            let specs = await specStore.listSpecs(projectId);
            // Apply filters
            if (status) {
                specs = specs.filter((s) => s.status === status);
            }
            else {
                // Exclude discarded specs from default (unfiltered) results
                specs = specs.filter((s) => s.status !== 'discarded');
            }
            if (type) {
                specs = specs.filter((s) => s.type === type);
            }
            // Build summary statistics
            const allSpecs = await specStore.listSpecs(projectId);
            const statusCounts = {};
            const typeCounts = {};
            for (const s of allSpecs) {
                statusCounts[s.status] = (statusCounts[s.status] ?? 0) + 1;
                typeCounts[s.type] = (typeCounts[s.type] ?? 0) + 1;
            }
            const safeSpecs = specs;
            const totalDevHours = safeSpecs.reduce((sum, s) => sum + (s.estimation?.devHours ?? 0), 0);
            const totalReviewHours = safeSpecs.reduce((sum, s) => sum + (s.estimation?.reviewHours ?? 0), 0);
            const totalCostUsd = safeSpecs.reduce((sum, s) => sum + (s.estimation?.totalCostUsd ?? 0), 0);
            // Fetch last modified timestamps from changelogs (AC-09)
            const lastModifiedMap = new Map();
            if (nativeBriefs) {
                for (const b of nativeBriefs) {
                    if (b.lastModified) {
                        lastModifiedMap.set(b.id, formatRelativeDate(b.lastModified));
                    }
                }
            }
            else {
                await Promise.all(safeSpecs.map(async (s) => {
                    if (!s.specPath) {
                        return;
                    }
                    const specDir = dirname(s.specPath);
                    const timestamp = await readLastModifiedTimestamp(specDir);
                    if (timestamp) {
                        lastModifiedMap.set(s.id, formatRelativeDate(timestamp));
                    }
                }));
            }
            // SPEC-091: Auto-suggest Agent Teams when 3+ approved specs exist
            const approvedSpecs = allSpecs.filter((s) => s.status === 'approved');
            let teamSuggestion = null;
            if (approvedSpecs.length >= 3) {
                teamSuggestion = {
                    recommended: true,
                    reason: `${String(approvedSpecs.length)} approved specs can be implemented in parallel using Agent Teams.`,
                    templateId: 'parallel-specs',
                    teamSize: Math.min(approvedSpecs.length, 5),
                    specs: approvedSpecs.map((s) => s.id),
                    toolCalls: [
                        `plan_team_distribution({ projectId: "${projectId}", specIds: [${approvedSpecs.map((s) => `"${s.id}"`).join(', ')}], template: "parallel-specs" })`,
                    ],
                };
            }
            // Build spec data — summary mode returns 5 fields, full mode returns all
            const specsData = detail === 'summary'
                ? specs.map((s) => ({
                    id: s.id,
                    title: s.title,
                    status: s.status,
                    type: s.type,
                    tags: s.tags,
                }))
                : safeSpecs.map((s) => ({
                    id: s.id,
                    title: s.title,
                    type: s.type,
                    scope: s.scope,
                    status: s.status,
                    difficulty: s.difficulty !== undefined ? String(s.difficulty) : undefined,
                    risk: s.risk,
                    target: s.target,
                    tags: s.tags,
                    gitBranch: s.gitBranch,
                    estimation: s.estimation
                        ? {
                            devHours: s.estimation.devHours ?? 0,
                            totalCostUsd: s.estimation.totalCostUsd ?? 0,
                        }
                        : undefined,
                    createdAt: s.createdAt,
                    updatedAt: s.updatedAt,
                    lastModified: lastModifiedMap.get(s.id) ?? '\u2014',
                }));
            // Build text content: markdown table in summary mode, JSON in full mode
            let textContent;
            if (detail === 'summary') {
                const summarySpecs = specsData;
                textContent = buildSummaryTable(summarySpecs);
            }
            else {
                textContent = JSON.stringify({
                    projectId,
                    filters: {
                        status: status ?? 'all',
                        type: type ?? 'all',
                    },
                    count: specs.length,
                    totalCount: allSpecs.length,
                    specs: specsData,
                    summary: {
                        byStatus: statusCounts,
                        byType: typeCounts,
                        totalDevHours: Math.round(totalDevHours * 10) / 10,
                        totalReviewHours: Math.round(totalReviewHours * 10) / 10,
                        totalCostUsd: Math.round(totalCostUsd * 100) / 100,
                    },
                    teamSuggestion,
                    message: ti('tools.list_specs.success', { count: String(specs.length) }),
                });
            }
            const humanSummary = buildListSpecsSummary(allSpecs.length, statusCounts);
            // SPEC-467: Detect branches and include info for LLM branch-awareness
            let branchInfo = null;
            if (knowledge.projectPath) {
                try {
                    const { detectBranches } = await import('../engine/git/branch-detector.js');
                    branchInfo = detectBranches(knowledge.projectPath);
                }
                catch {
                    /* best-effort */
                }
            }
            // SPEC-487: Scan for ambiguous criteria — ask-then-act via InteractiveQuestion
            let migrationIssues = null;
            if (knowledge.projectPath) {
                try {
                    const ambiguous = await scanForAmbiguousCriteria(knowledge.projectPath);
                    if (ambiguous.length > 0) {
                        migrationIssues = { ambiguousCriteria: ambiguous };
                    }
                }
                catch {
                    /* best-effort — never block list_specs */
                }
            }
            const interactiveQuestions = migrationIssues
                ? [
                    {
                        question: `Found ${String(migrationIssues.ambiguousCriteria.length)} criteria with score < 50 (no GIVEN/WHEN/THEN or concrete values). Rewrite them to zero-ambiguity format?`,
                        header: 'Criteria',
                        multiSelect: false,
                        options: [
                            {
                                label: 'Yes, rewrite (Recommended)',
                                description: 'Planu will provide context per spec. You rewrite each criterion to GIVEN/WHEN/THEN, then call apply_migration_cleanup to save.',
                            },
                            {
                                label: 'Skip for now',
                                description: 'Keep existing criteria unchanged.',
                            },
                        ],
                    },
                ]
                : null;
            // SPEC-1012: Detect specs released but still in non-terminal status (best-effort, non-blocking)
            let staleStatusWarnings = [];
            if (knowledge.projectPath) {
                try {
                    const { detectReleaseStatusDrift } = await import('../engine/drift-detection/release-status-drift.js');
                    staleStatusWarnings = await detectReleaseStatusDrift(knowledge.projectPath);
                }
                catch {
                    /* best-effort — never block list_specs */
                }
            }
            return {
                content: [
                    { type: 'text', text: textContent },
                    { type: 'text', text: humanSummary },
                    ...(interactiveQuestions
                        ? [
                            {
                                type: 'text',
                                text: '⚡ INTERACTIVE — call AskUserQuestion now with the interactiveQuestions from structuredContent',
                            },
                        ]
                        : []),
                ],
                structuredContent: {
                    projectId,
                    filters: {
                        status: status ?? 'all',
                        type: type ?? 'all',
                    },
                    count: specs.length,
                    totalCount: allSpecs.length,
                    specs: specsData,
                    summary: {
                        byStatus: statusCounts,
                        byType: typeCounts,
                        totalDevHours: Math.round(totalDevHours * 10) / 10,
                        totalReviewHours: Math.round(totalReviewHours * 10) / 10,
                        totalCostUsd: Math.round(totalCostUsd * 100) / 100,
                    },
                    teamSuggestion: teamSuggestion ?? null,
                    message: ti('tools.list_specs.success', { count: String(specs.length) }),
                    teamDistributionHint: approvedSpecs.length >= 3
                        ? `${String(approvedSpecs.length)} specs approved — run plan_team_distribution for parallel assignment`
                        : undefined,
                    humanSummary,
                    branchInfo: branchInfo && branchInfo.count > 1 ? branchInfo : undefined,
                    autopilotSummary: collector.hasEntries() ? collector.getMessages() : undefined,
                    migrationIssues: migrationIssues ?? undefined,
                    interactiveQuestions: interactiveQuestions ?? undefined,
                    staleStatusWarnings: staleStatusWarnings.length > 0 ? staleStatusWarnings : undefined,
                },
            };
        }
        catch (error) {
            const message = error instanceof Error ? error.message : String(error);
            return {
                content: [{ type: 'text', text: ti('errors.internalError', { message }) }],
                isError: true,
            };
        }
    }); // end trackCost
}
//# sourceMappingURL=list-specs.js.map