import path from 'node:path';
import { findFiles } from '../walker.js';
import { parseFiles } from '../parsers/index.js';
import { aggregate } from '../stats.js';
import { checkScc, runScc } from '../scc.js';
import { documentToMarkdown } from '../markdown/convert.js';
import { extractFromMarkdown } from '../structure/index.js';
import { DEFAULT_EXCLUDE_DIRS, getExtension, mapWithConcurrency, METRIC_FIELDS } from '../utils.js';
const STRUCTURABLE_EXTS = new Set(['docx', 'pdf', 'pptx', 'odt', 'odp']);
// Matches the document-parse fan-out in parseFiles; conversion is I/O + CPU mixed.
const STRUCTURE_CONCURRENCY = 10;
function mapDocumentAggregates(results) {
    const stats = aggregate(results, {
        byFile: false,
        sort: 'files',
        showConfidence: false,
    });
    const rows = stats.rows.map(row => {
        const entry = {
            type: row.fileType,
            count: row.files,
            size: row.size,
        };
        for (const field of METRIC_FIELDS) {
            entry[field] = row[field] || 0;
        }
        return entry;
    });
    const totals = {
        files: stats.totals.files,
        size: stats.totals.size,
    };
    for (const field of METRIC_FIELDS) {
        totals[field] = stats.totals[field] || 0;
    }
    return {
        files: rows,
        totals: totals,
    };
}
function mapCodeTotals(languages) {
    return {
        totalFiles: languages.reduce((sum, item) => sum + item.Count, 0),
        totalLines: languages.reduce((sum, item) => sum + item.Lines, 0),
        totalCode: languages.reduce((sum, item) => sum + item.Code, 0),
        totalComment: languages.reduce((sum, item) => sum + item.Comment, 0),
        totalBlank: languages.reduce((sum, item) => sum + item.Blank, 0),
    };
}
async function extractStructures(rootDir, files, onProgress, convertToMarkdown) {
    const structurable = files.filter(file => STRUCTURABLE_EXTS.has(getExtension(file.path)));
    const errors = [];
    let completed = 0;
    const slots = await mapWithConcurrency(structurable, STRUCTURE_CONCURRENCY, async (file) => {
        let slot;
        try {
            const markdown = await convertToMarkdown(file.path);
            if (!markdown) {
                slot = {
                    scope: 'structure',
                    path: file.path,
                    message: 'Document conversion produced no markdown.',
                };
            }
            else {
                const structure = extractFromMarkdown(markdown);
                slot = {
                    file: path.relative(rootDir, file.path).split(path.sep).join('/'),
                    totalNodes: structure.totalNodes,
                    maxDepth: structure.maxDepth,
                    nodes: structure.rootNodes,
                };
            }
        }
        catch (err) {
            const error = err;
            slot = {
                scope: 'structure',
                path: file.path,
                message: error.message,
            };
        }
        completed += 1;
        onProgress?.({
            phase: 'structure',
            total: structurable.length,
            completed,
            detail: file.path,
        });
        return slot;
    });
    const structures = [];
    for (const slot of slots) {
        if ('scope' in slot)
            errors.push(slot);
        else
            structures.push(slot);
    }
    return { structures, errors };
}
export async function analyzeWorkspace(rootDir, options = {}, onProgress) {
    const startedAt = Date.now();
    const resolvedRoot = path.resolve(rootDir);
    const excludeDir = options.excludeDir ?? [...DEFAULT_EXCLUDE_DIRS];
    const includeDocuments = options.includeDocuments !== false;
    const includeStructures = options.includeStructures !== false;
    const includeCode = options.includeCode !== false;
    const errors = [];
    let skipped = [];
    let documentResults = [];
    let documentAggregates = null;
    if (includeDocuments) {
        const discovery = await findFiles([resolvedRoot], {
            excludeDir,
            noGitignore: options.noGitignore,
            ignorePatterns: options.ignorePatterns,
            overlayIgnorePatterns: options.overlayIgnorePatterns,
            largeFileLimit: options.largeFileLimitMb ?? 50,
        });
        skipped = discovery.skipped;
        onProgress?.({
            phase: 'discover-documents',
            total: discovery.files.length,
            completed: discovery.files.length,
            detail: 'Document discovery complete',
        });
        if (discovery.files.length > 0) {
            let parsedCount = 0;
            documentResults = await parseFiles(discovery.files, 10, (inc, detail) => {
                parsedCount += inc;
                onProgress?.({
                    phase: 'parse-documents',
                    total: discovery.files.length,
                    completed: Math.min(parsedCount, discovery.files.length),
                    detail,
                });
            });
            documentAggregates = mapDocumentAggregates(documentResults);
            for (const result of documentResults) {
                if (!result.success) {
                    errors.push({
                        scope: 'document',
                        path: result.filePath,
                        message: result.suggestion ?? 'Failed to parse document.',
                    });
                }
            }
        }
    }
    let code = null;
    if (includeCode) {
        try {
            const sccBinary = await checkScc();
            const languages = await runScc(sccBinary, [resolvedRoot], {
                excludeDir,
                noGitignore: options.noGitignore,
            });
            code = {
                languages,
                totals: mapCodeTotals(languages),
            };
            onProgress?.({
                phase: 'code',
                total: 1,
                completed: 1,
                detail: 'Code analysis complete',
            });
        }
        catch (err) {
            const error = err;
            errors.push({
                scope: 'code',
                path: resolvedRoot,
                message: error.message,
            });
        }
    }
    let structures = [];
    if (includeStructures && includeDocuments) {
        const successfulFiles = documentResults
            .filter(result => result.success)
            .map(result => ({ path: result.filePath, size: result.size }));
        const structureResult = await extractStructures(resolvedRoot, successfulFiles, onProgress, options.convertToMarkdown ?? documentToMarkdown);
        structures = structureResult.structures;
        errors.push(...structureResult.errors);
    }
    return {
        schemaVersion: 1,
        rootDir: resolvedRoot,
        summary: {
            elapsedMs: Date.now() - startedAt,
            code: code?.totals ?? null,
            documents: documentAggregates?.totals ?? null,
            structures: structures.length,
        },
        code,
        documents: documentAggregates ? { aggregates: documentAggregates } : null,
        structures,
        skipped,
        errors,
    };
}
//# sourceMappingURL=analyze.js.map