import { readFile, writeFile } from 'node:fs/promises';
import { join } from 'node:path';
const DEFAULT_BUILD_SCRIPT = 'turbo run build';
const ROOT_PACKAGE_PATH = 'package.json';
const DEFAULT_DEPENDENCIES = {
    readFile,
    writeFile,
};
function runsTurboBuild(script) {
    return /^turbo\s+(?:run\s+)?build(?:\s+.*)?$/.test(script.trim());
}
function hasShellComposition(script) {
    return /&&|\|\||[;|`]|[$][(]/.test(script);
}
function removeFilterFlags(script) {
    return script
        .replace(/\s+--filter(?:=|\s+)(?:"[^"]*"|'[^']*'|\S+)/g, '')
        .trim();
}
function getFilterFlags(script) {
    return Array.from(script.matchAll(/\s+(--filter(?:=|\s+)(?:"[^"]*"|'[^']*'|\S+))/g), (match) => match[1]);
}
function buildExcludeFilter(appName) {
    return `--filter='!${appName}'`;
}
function buildIncludeFilter(appName) {
    return `--filter=${appName}...`;
}
function buildManualSnippet(appName, baseBuildScript) {
    const buildScript = baseBuildScript?.trim() || DEFAULT_BUILD_SCRIPT;
    return [
        '"scripts": {',
        `  "build": "${buildScript} ${buildExcludeFilter(appName)}",`,
        `  "build:docs": "${buildScript} ${buildIncludeFilter(appName)}"`,
        '}',
    ].join('\n');
}
function inferIndentation(content) {
    const match = /\n([ \t]+)"/.exec(content);
    return match?.[1] ?? '  ';
}
function formatJsonWithOriginalStyle(original, value) {
    const indentation = inferIndentation(original);
    const trailingNewline = original.endsWith('\n') ? '\n' : '';
    return `${JSON.stringify(value, null, indentation)}${trailingNewline}`;
}
function splitLines(content) {
    const trimmed = content.endsWith('\n') ? content.slice(0, -1) : content;
    return trimmed.length > 0 ? trimmed.split('\n') : [];
}
function buildDiffOperations(beforeLines, afterLines) {
    const lengths = Array.from({ length: beforeLines.length + 1 }, () => Array(afterLines.length + 1).fill(0));
    for (let beforeIndex = beforeLines.length - 1; beforeIndex >= 0; beforeIndex -= 1) {
        for (let afterIndex = afterLines.length - 1; afterIndex >= 0; afterIndex -= 1) {
            lengths[beforeIndex][afterIndex] =
                beforeLines[beforeIndex] === afterLines[afterIndex]
                    ? lengths[beforeIndex + 1][afterIndex + 1] + 1
                    : Math.max(lengths[beforeIndex + 1][afterIndex], lengths[beforeIndex][afterIndex + 1]);
        }
    }
    const operations = [];
    let beforeIndex = 0;
    let afterIndex = 0;
    while (beforeIndex < beforeLines.length && afterIndex < afterLines.length) {
        if (beforeLines[beforeIndex] === afterLines[afterIndex]) {
            operations.push({ type: 'context', line: beforeLines[beforeIndex] });
            beforeIndex += 1;
            afterIndex += 1;
            continue;
        }
        if (lengths[beforeIndex + 1][afterIndex] >=
            lengths[beforeIndex][afterIndex + 1]) {
            operations.push({ type: 'remove', line: beforeLines[beforeIndex] });
            beforeIndex += 1;
            continue;
        }
        operations.push({ type: 'add', line: afterLines[afterIndex] });
        afterIndex += 1;
    }
    while (beforeIndex < beforeLines.length) {
        operations.push({ type: 'remove', line: beforeLines[beforeIndex] });
        beforeIndex += 1;
    }
    while (afterIndex < afterLines.length) {
        operations.push({ type: 'add', line: afterLines[afterIndex] });
        afterIndex += 1;
    }
    return operations;
}
function createUnifiedDiff(filePath, before, after) {
    if (before === after) {
        return '';
    }
    const beforeLines = splitLines(before);
    const afterLines = splitLines(after);
    const operations = buildDiffOperations(beforeLines, afterLines);
    return [
        `--- ${filePath}`,
        `+++ ${filePath}`,
        `@@ -1,${beforeLines.length} +1,${afterLines.length} @@`,
        ...operations.map((operation) => {
            const prefix = operation.type === 'context'
                ? ' '
                : operation.type === 'remove'
                    ? '-'
                    : '+';
            return `${prefix}${operation.line}`;
        }),
    ].join('\n');
}
export async function patchRootPackageJson(options, dependencies = DEFAULT_DEPENDENCIES) {
    const packageJsonPath = join(options.repoRoot, ROOT_PACKAGE_PATH);
    if (!options.enabled) {
        return {
            status: 'disabled',
            reason: 'disabled',
            packageJsonPath,
            warnings: [],
        };
    }
    let originalContent;
    try {
        originalContent = await dependencies.readFile(packageJsonPath, 'utf8');
    }
    catch {
        return {
            status: 'skipped',
            reason: 'missing-package-json',
            packageJsonPath,
            manualSnippet: buildManualSnippet(options.appName),
            warnings: [
                'Skipped root package.json patch: root package.json was not found.',
            ],
        };
    }
    const parsed = JSON.parse(originalContent);
    const currentScripts = parsed.scripts ?? {};
    const currentBuildScript = currentScripts.build;
    if (!currentBuildScript) {
        return {
            status: 'skipped',
            reason: 'no-build-script',
            packageJsonPath,
            manualSnippet: buildManualSnippet(options.appName),
            warnings: [
                'Skipped root package.json patch: scripts.build is missing, so there was no Turbo build command to update.',
            ],
        };
    }
    if (!runsTurboBuild(currentBuildScript)) {
        return {
            status: 'skipped',
            reason: 'non-turbo-build-script',
            packageJsonPath,
            manualSnippet: buildManualSnippet(options.appName),
            warnings: [
                'Skipped root package.json patch: scripts.build does not run a Turbo build command, so OAT left it unchanged.',
            ],
        };
    }
    if (hasShellComposition(currentBuildScript)) {
        return {
            status: 'skipped',
            reason: 'ambiguous-shell-build-script',
            packageJsonPath,
            manualSnippet: buildManualSnippet(options.appName),
            warnings: [
                'Skipped root package.json patch: scripts.build wraps the Turbo build in a larger shell expression, so OAT left it unchanged rather than rewriting the full command.',
            ],
        };
    }
    const excludeFilter = buildExcludeFilter(options.appName);
    const filterFlags = getFilterFlags(currentBuildScript);
    const hasUserAuthoredFilters = filterFlags.some((flag) => flag !== excludeFilter);
    if (hasUserAuthoredFilters) {
        return {
            status: 'skipped',
            reason: 'existing-filter-flags',
            packageJsonPath,
            manualSnippet: buildManualSnippet(options.appName, removeFilterFlags(currentBuildScript)),
            warnings: [
                'Skipped root package.json patch: scripts.build already uses `--filter`, so OAT left it unchanged rather than guessing how to merge filter semantics.',
            ],
        };
    }
    const nextScripts = { ...currentScripts };
    const nextBuildScript = currentBuildScript.includes(excludeFilter)
        ? currentBuildScript
        : `${currentBuildScript.trim()} ${excludeFilter}`;
    nextScripts.build = nextBuildScript;
    const baseBuildScript = removeFilterFlags(currentBuildScript);
    const desiredBuildDocsScript = `${baseBuildScript} ${buildIncludeFilter(options.appName)}`;
    const warnings = [];
    if (nextScripts['build:docs'] &&
        nextScripts['build:docs'] !== desiredBuildDocsScript) {
        warnings.push('Left scripts["build:docs"] unchanged because the root package already defines a different value.');
    }
    else {
        nextScripts['build:docs'] = desiredBuildDocsScript;
    }
    const nextContent = formatJsonWithOriginalStyle(originalContent, {
        ...parsed,
        scripts: nextScripts,
    });
    const diff = createUnifiedDiff(ROOT_PACKAGE_PATH, originalContent, nextContent);
    if (!diff) {
        return {
            status: 'already-configured',
            packageJsonPath,
            warnings,
        };
    }
    if (!options.dryRun) {
        await dependencies.writeFile(packageJsonPath, nextContent, 'utf8');
    }
    return {
        status: options.dryRun ? 'dry-run' : 'applied',
        reason: warnings.length > 0 ? 'existing-build-docs-script' : undefined,
        packageJsonPath,
        diff,
        manualSnippet: warnings.length > 0
            ? buildManualSnippet(options.appName, baseBuildScript)
            : undefined,
        warnings,
    };
}
