import path from 'node:path';
import { fork } from 'node:child_process';
import { rm } from 'node:fs/promises';
import { fileURLToPath } from 'node:url';
import { buildCodebaseIndex } from '../code/build.js';
import { readSectionedCodeIndex } from '../code/index-io.js';
import { inspectWorkspaceDocumentSet } from './documents.js';
import { OccAbortError } from '../errors.js';
import { abortIfNeeded, sanitizeForkExecArgv } from '../utils.js';
function resolveExecutionMode(options) {
    const mode = options.executionMode ?? 'auto';
    if (mode === 'inline')
        return 'inline';
    if (mode === 'subprocess')
        return 'subprocess';
    // auto: subprocess when code indexing is enabled
    return options.includeCode !== false ? 'subprocess' : 'inline';
}
function mapCodeProgress(event, onProgress) {
    onProgress({
        scope: 'code',
        stage: event.phase,
        phase: event.phase,
        completed: event.completed,
        total: event.total,
        detail: event.detail,
        currentPath: event.currentPath,
        startedAt: event.startedAt,
        elapsedMs: event.elapsedMs,
    });
}
async function buildCodeInline(rootDir, options, onProgress) {
    abortIfNeeded(options.signal);
    return buildCodebaseIndex({
        repoRoot: rootDir,
        excludeDir: options.excludeDir,
        noGitignore: options.noGitignore,
        ignorePatterns: options.ignorePatterns,
        overlayIgnorePatterns: options.overlayIgnorePatterns,
        contentMode: options.contentMode,
        maxFileSizeBytes: options.maxFileSizeBytes,
        maxLines: options.maxLines,
        skipMinified: options.skipMinified,
    }, onProgress ? event => mapCodeProgress(event, onProgress) : undefined, options.signal);
}
async function buildCodeSubprocess(rootDir, options, onProgress) {
    const runnerPath = path.resolve(path.dirname(fileURLToPath(import.meta.url)), 'prepare-runner.js');
    abortIfNeeded(options.signal);
    return new Promise((resolve, reject) => {
        let settled = false;
        let abortTimer;
        const child = fork(runnerPath, [], {
            stdio: ['ignore', 'ignore', 'pipe', 'ipc'],
            execArgv: sanitizeForkExecArgv(),
        });
        const cleanup = () => {
            if (abortTimer)
                clearTimeout(abortTimer);
            options.signal?.removeEventListener('abort', handleAbort);
        };
        const rejectAbort = () => {
            cleanup();
            reject(new OccAbortError());
        };
        const handleAbort = () => {
            if (settled)
                return;
            child.send({ type: 'abort' });
            abortTimer = setTimeout(() => {
                if (!settled)
                    child.kill();
            }, 1000);
        };
        options.signal?.addEventListener('abort', handleAbort, { once: true });
        let stderr = '';
        child.stderr?.on('data', (d) => {
            stderr += d.toString();
        });
        child.on('message', (msg) => {
            if (msg.type === 'progress' && msg.event && onProgress) {
                mapCodeProgress(msg.event, onProgress);
            }
            else if (msg.type === 'aborted') {
                settled = true;
                rejectAbort();
            }
            else if (msg.type === 'complete' && msg.resultPath) {
                settled = true;
                void (async () => {
                    try {
                        const { index } = await readSectionedCodeIndex(msg.resultPath, options.signal);
                        cleanup();
                        resolve(index);
                    }
                    catch (err) {
                        cleanup();
                        reject(err instanceof Error ? err : new Error(String(err)));
                    }
                    finally {
                        await rm(msg.resultPath, { recursive: true, force: true }).catch(() => undefined);
                    }
                })();
            }
            else if (msg.type === 'error') {
                settled = true;
                cleanup();
                reject(new Error(msg.message ?? 'Subprocess failed'));
            }
        });
        child.on('error', err => {
            settled = true;
            cleanup();
            reject(err);
        });
        child.on('close', code => {
            if (settled)
                return;
            cleanup();
            if (options.signal?.aborted) {
                rejectAbort();
                return;
            }
            if (code !== 0) {
                reject(new Error(`Prepare runner exited with code ${code}: ${stderr.trim()}`));
            }
            else {
                reject(new Error('Prepare runner exited without sending a result'));
            }
        });
        child.send({
            type: 'start',
            options: {
                repoRoot: rootDir,
                excludeDir: options.excludeDir,
                noGitignore: options.noGitignore,
                ignorePatterns: options.ignorePatterns,
                overlayIgnorePatterns: options.overlayIgnorePatterns,
                contentMode: options.contentMode,
                maxFileSizeBytes: options.maxFileSizeBytes,
                maxLines: options.maxLines,
                skipMinified: options.skipMinified,
            },
        });
    });
}
export async function prepareWorkspaceContext(rootDir, options = {}, onProgress) {
    const resolvedRoot = path.resolve(rootDir);
    const startedAt = Date.now();
    const errors = [];
    const includeCode = options.includeCode !== false;
    const includeDocuments = options.includeDocuments !== false;
    abortIfNeeded(options.signal);
    onProgress?.({
        scope: 'prepare',
        stage: 'start',
        phase: 'start',
        completed: 0,
        total: 1,
        detail: 'Starting workspace preparation',
        startedAt,
    });
    // Phase 1: Code indexing
    let codeResult = null;
    if (includeCode) {
        try {
            abortIfNeeded(options.signal);
            const mode = resolveExecutionMode(options);
            const buildFn = mode === 'subprocess' ? buildCodeSubprocess : buildCodeInline;
            const index = await buildFn(resolvedRoot, options, onProgress);
            codeResult = {
                index,
                fileCount: index.files.length,
                nodeCount: index.nodes.length,
                edgeCount: index.edges.length,
                contentMode: index.contentMode,
                skippedFileCount: index.skippedFiles.length,
            };
        }
        catch (err) {
            const error = err instanceof Error ? err : new Error(String(err));
            if (error.name === 'AbortError')
                throw error;
            errors.push(`Code indexing failed: ${error.message}`);
        }
    }
    // Phase 2: Document inspection
    let documentsResult = null;
    if (includeDocuments) {
        try {
            abortIfNeeded(options.signal);
            const docSet = await inspectWorkspaceDocumentSet(resolvedRoot, {
                excludeDir: options.excludeDir,
                noGitignore: options.noGitignore,
                ignorePatterns: options.ignorePatterns,
                overlayIgnorePatterns: options.overlayIgnorePatterns,
                largeFileLimitMb: options.largeFileLimitMb,
                maxFiles: options.maxFiles,
                maxReferenceFiles: options.maxReferenceFiles,
                includeMarkdown: options.includeMarkdown,
            }, onProgress
                ? event => {
                    onProgress({
                        scope: 'documents',
                        stage: event.phase,
                        phase: event.phase,
                        completed: event.completed,
                        total: event.total,
                        detail: event.detail,
                        currentPath: event.currentPath,
                        startedAt: event.startedAt,
                        elapsedMs: event.elapsedMs,
                    });
                }
                : undefined);
            documentsResult = docSet;
            for (const err of docSet.errors) {
                errors.push(`Document error${err.path ? ` (${err.path})` : ''}: ${err.message}`);
            }
        }
        catch (err) {
            const error = err instanceof Error ? err : new Error(String(err));
            if (error.name === 'AbortError')
                throw error;
            errors.push(`Document inspection failed: ${error.message}`);
        }
    }
    const elapsedMs = Date.now() - startedAt;
    onProgress?.({
        scope: 'prepare',
        stage: 'complete',
        phase: 'complete',
        completed: 1,
        total: 1,
        detail: `Completed in ${elapsedMs}ms`,
        startedAt,
        elapsedMs,
    });
    return {
        rootDir: resolvedRoot,
        code: codeResult,
        documents: documentsResult,
        elapsedMs,
        errors,
    };
}
//# sourceMappingURL=prepare.js.map