import { execSync } from 'node:child_process';
import { mkdir } from 'node:fs/promises';
import { basename, isAbsolute, join, relative, resolve, sep } from 'node:path';
import { buildCommandContext, } from '../../app/command-context.js';
import { applyOatCoreGitignore, } from '../init/gitignore.js';
import { applyGitignore } from '../local/apply.js';
import { addLocalPaths } from '../local/manage.js';
import { adoptStrayToCanonical, isAdoptionConflictError, } from '../shared/adopt-stray.js';
import { detectCodexRoleStrays, filterMaterializationManagedStrays, regenerateCodexAfterAdoption, } from '../shared/codex-strays.js';
import { applyCursorSkillDisposition, isCursorSkillCandidate, } from '../shared/cursor-skill-disposition.js';
import { PROVIDER_CONFIG_REMEDIATION } from '../shared/messages.js';
import { withScopeOption } from '../shared/scope-option.js';
import { confirmAction, inputWithDefault, selectManyWithAbort, selectWithAbort, } from '../shared/shared.prompts.js';
import { readGlobalOptions, resolveConcreteScopes, } from '../shared/shared.utils.js';
import { DEFAULT_SYNC_CONFIG, loadSyncConfig, saveSyncConfig, } from '../../config/index.js';
import { detectDefaultBranch, readOatConfig, resolveLocalPaths, writeOatConfig, } from '../../config/oat-config.js';
import { resolveUserSyncConfig } from '../../config/user-sync-config.js';
import { detectStrays, filterKnownStrays, } from '../../drift/index.js';
import { configureLocalHooksPath, getHookInstallInfo, installHook, isHookInstalled, scanBundledManagedAgents, scanCanonical, uninstallHook, } from '../../engine/index.js';
import { dirExists, fileExists } from '../../fs/io.js';
import { resolveProjectRoot, resolveScopeRoot } from '../../fs/paths.js';
import { normalizeToPosixPath } from '../../fs/paths.js';
import { createEmptyManifest, loadManifest, saveManifest, } from '../../manifest/manager.js';
import { claudeAdapter } from '../../providers/claude/index.js';
import { codexAdapter } from '../../providers/codex/index.js';
import { applyCodexProjectExtensionPlan, computeCodexProjectExtensionPlan, } from '../../providers/codex/codec/sync-extension.js';
import { copilotAdapter } from '../../providers/copilot/index.js';
import { cursorAdapter } from '../../providers/cursor/index.js';
import { applyCursorProjectExtensionPlan, computeCursorProjectExtensionPlan, } from '../../providers/cursor/codec/sync-extension.js';
import { geminiAdapter } from '../../providers/gemini/index.js';
import { getActiveAdapters, getConfigAwareAdapters, } from '../../providers/shared/index.js';
import { getAdoptionSources } from '../../providers/shared/adapter.utils.js';
import { Command } from 'commander';
import { detectExistingDocs, } from './detect-docs.js';
import { createInitToolsCommand, runInitToolsWithDefaults, } from './tools/index.js';
const ADOPT_REMEDIATION = 'Run "oat init" interactively to adopt stray entries.';
const HOOK_PROMPT = 'Install optional pre-commit hook for drift warnings?';
const HOOK_CONFIGURE_PROMPT = (hooksPath) => `Configure Git hooks to use ${hooksPath} before installing the OAT hook?`;
const HOOK_GUIDANCE = 'Run "oat init --hook" to install optional pre-commit hook.';
function getDefaultAdapters() {
    return [
        claudeAdapter,
        cursorAdapter,
        codexAdapter,
        copilotAdapter,
        geminiAdapter,
    ];
}
async function ensureCanonicalDirectories(scopeRoot, scope) {
    await mkdir(join(scopeRoot, '.agents', 'skills'), { recursive: true });
    if (scope === 'project') {
        await mkdir(join(scopeRoot, '.agents', 'agents'), { recursive: true });
        await mkdir(join(scopeRoot, '.agents', 'rules'), { recursive: true });
    }
}
async function collectStraysDefault(scopeRoot, scope, manifest, canonicalEntries, activeAdapters, userConfigDir = join(scopeRoot, '.oat')) {
    const adaptersToScan = activeAdapters ??
        (await getActiveAdapters(getDefaultAdapters(), scopeRoot));
    const candidates = [];
    const hasMaterializationAdapter = adaptersToScan.some((adapter) => adapter.name === 'codex' || adapter.name === 'cursor');
    const materializationCanonicalEntries = scope === 'user' && hasMaterializationAdapter
        ? [...canonicalEntries, ...(await scanBundledManagedAgents())]
        : canonicalEntries;
    const codexExtensionPlan = adaptersToScan.some((adapter) => adapter.name === 'codex')
        ? await computeCodexProjectExtensionPlan(scopeRoot, materializationCanonicalEntries, undefined, { userConfigDir })
        : undefined;
    const cursorExtensionPlan = adaptersToScan.some((adapter) => adapter.name === 'cursor')
        ? await computeCursorProjectExtensionPlan(scopeRoot, materializationCanonicalEntries, undefined, { userConfigDir })
        : undefined;
    const materializationPlans = [
        ...(codexExtensionPlan
            ? [
                {
                    provider: 'codex',
                    operations: codexExtensionPlan.operations.map((operation) => ({
                        ...operation,
                        provider: 'codex',
                        entryName: operation.roleName,
                    })),
                    managedEntries: codexExtensionPlan.managedRoles,
                    aggregateHash: codexExtensionPlan.aggregateConfigHash,
                    metadata: codexExtensionPlan.metadata,
                },
            ]
            : []),
        ...(cursorExtensionPlan ? [cursorExtensionPlan] : []),
    ];
    for (const adapter of adaptersToScan) {
        const adoptionSources = getAdoptionSources(adapter, scope);
        for (const source of adoptionSources) {
            const providerDir = join(scopeRoot, source.directory);
            const strays = filterMaterializationManagedStrays((await detectStrays(adapter.name, providerDir, manifest, canonicalEntries, source.mapping)).map((report) => ({ provider: adapter.name, report })), materializationPlans).map((candidate) => candidate.report);
            for (const report of strays) {
                if (report.state.status !== 'stray') {
                    continue;
                }
                candidates.push({
                    provider: adapter.name,
                    report,
                    mapping: source.mapping,
                });
            }
        }
    }
    if (adaptersToScan.some((adapter) => adapter.name === 'codex')) {
        const codexStrays = await detectCodexRoleStrays(scopeRoot, canonicalEntries, new Set(codexExtensionPlan.managedRoles));
        for (const stray of codexStrays) {
            candidates.push({
                provider: 'codex',
                report: {
                    canonical: null,
                    provider: 'codex',
                    providerPath: stray.providerPath,
                    state: { status: 'stray' },
                },
                mapping: {
                    contentType: 'agent',
                    canonicalDir: '.agents/agents',
                    providerDir: '.codex/agents',
                    nativeRead: false,
                },
                adoption: {
                    kind: 'codex_role',
                    roleName: stray.roleName,
                    description: stray.description,
                },
            });
        }
    }
    return candidates;
}
async function adoptStrayDefault(scopeRoot, stray, manifest, options) {
    return adoptStrayToCanonical(scopeRoot, stray, manifest, options);
}
function createDependencies() {
    return {
        buildCommandContext,
        async resolveScopeRoot(scope, context) {
            if (scope === 'project') {
                return resolveProjectRoot(context.cwd);
            }
            return resolveScopeRoot(scope, context.cwd, context.home);
        },
        ensureCanonicalDirs: ensureCanonicalDirectories,
        loadManifest,
        saveManifest,
        scanCanonical,
        collectStrays: collectStraysDefault,
        confirmAction,
        selectManyWithAbort,
        selectProvidersWithAbort: selectManyWithAbort,
        adoptStray: adoptStrayDefault,
        isHookInstalled,
        getHookInstallInfo,
        configureLocalHooksPath,
        installHook,
        uninstallHook,
        getAdapters() {
            return getDefaultAdapters();
        },
        async loadSyncConfig(configPath) {
            return loadSyncConfig(configPath, DEFAULT_SYNC_CONFIG);
        },
        resolveUserSyncConfig,
        saveSyncConfig,
        getConfigAwareAdapters,
        applyOatCoreGitignore,
        dirExists,
        readOatConfig,
        resolveLocalPaths,
        addLocalPaths,
        applyGitignore,
        writeOatConfig,
        detectDefaultBranch,
        detectExistingDocs,
        fileExists,
        inputWithDefault,
        selectWithAbort,
        applyCursorSkillDisposition,
        runGuidedSetup: runGuidedSetupImpl,
        runToolPacks: runInitToolsWithDefaults,
        async runProviderSync(projectRoot) {
            execSync('oat sync --scope project', {
                cwd: projectRoot,
                stdio: 'inherit',
            });
        },
    };
}
function formatPathForScope(scope, providerPath) {
    if (scope === 'project') {
        return providerPath;
    }
    if (providerPath.startsWith('./')) {
        return `~/${providerPath.slice(2)}`;
    }
    if (providerPath.startsWith('.')) {
        return `~/${providerPath}`;
    }
    return `~/${providerPath}`;
}
function formatStrayChoiceLabel(scope, providerPath, provider) {
    return `[${scope}] ${basename(providerPath)} (${provider})`;
}
async function maybeHandleHook(context, dependencies, projectRoot, hookFlag) {
    if (!projectRoot) {
        return null;
    }
    const installed = await dependencies.isHookInstalled(projectRoot);
    if (hookFlag === false) {
        if (installed) {
            await dependencies.uninstallHook(projectRoot);
            context.logger.success('Removed optional pre-commit hook.');
        }
        return false;
    }
    if (installed && hookFlag === undefined) {
        return true;
    }
    let shouldInstall = false;
    if (hookFlag === true) {
        shouldInstall = true;
    }
    else if (context.interactive) {
        shouldInstall = await dependencies.confirmAction(HOOK_PROMPT, {
            interactive: context.interactive,
        });
    }
    else {
        context.logger.info(HOOK_GUIDANCE);
        return installed;
    }
    if (shouldInstall && !installed) {
        const hookInstallInfo = await dependencies.getHookInstallInfo(projectRoot);
        if (hookInstallInfo.suggestedHooksPath) {
            context.logger.warn(`Detected existing repo hook file at ${hookInstallInfo.suggestedHookPath}, but Git is not configured to use ${hookInstallInfo.suggestedHooksPath}. OAT will install into ${hookInstallInfo.hookPath} unless you configure Git first.`);
            if (context.interactive) {
                const shouldConfigureHooksPath = await dependencies.confirmAction(HOOK_CONFIGURE_PROMPT(hookInstallInfo.suggestedHooksPath), {
                    interactive: context.interactive,
                });
                if (shouldConfigureHooksPath) {
                    await dependencies.configureLocalHooksPath(projectRoot, hookInstallInfo.suggestedHooksPath);
                }
            }
        }
        const hookPath = await dependencies.installHook(projectRoot);
        context.logger.success(`Installed optional pre-commit hook at ${hookPath}.`);
        return true;
    }
    return installed || shouldInstall;
}
const LOCAL_PATH_CHOICES = [
    {
        label: '.oat/**/analysis — Analysis artifacts',
        value: '.oat/**/analysis',
        checked: true,
    },
    {
        label: '.oat/**/pr — PR description files',
        value: '.oat/**/pr',
        checked: true,
    },
    {
        label: '.oat/**/reviews/archived — Archived review artifacts',
        value: '.oat/**/reviews/archived',
        checked: true,
    },
    {
        label: '.oat/ideas — Ideas and brainstorms',
        value: '.oat/ideas',
        checked: true,
    },
];
const DOCS_TOOLING_CHOICES = [
    { label: 'Fumadocs (Next.js + MDX)', value: 'fumadocs' },
    { label: 'MkDocs (Python, Material theme)', value: 'mkdocs' },
    { label: 'Docusaurus', value: 'docusaurus' },
    { label: 'VitePress', value: 'vitepress' },
    { label: 'Nextra', value: 'nextra' },
];
function trimDocsRoot(pathValue) {
    return pathValue.replace(/\/+$/, '').replace(/^\.\//, '').trim();
}
function normalizeDocumentationRoot(repoRoot, pathValue) {
    const trimmed = pathValue.trim();
    if (!trimmed) {
        return null;
    }
    if (!isAbsolute(trimmed)) {
        const normalizedRelative = trimDocsRoot(normalizeToPosixPath(trimmed));
        if (!normalizedRelative || normalizedRelative === '.') {
            return '.';
        }
        if (normalizedRelative === '..' || normalizedRelative.startsWith('../')) {
            return null;
        }
    }
    const repoRootResolved = resolve(repoRoot);
    const absoluteResolved = isAbsolute(trimmed)
        ? resolve(trimmed)
        : resolve(repoRoot, trimmed);
    const isInsideRepo = absoluteResolved === repoRootResolved ||
        absoluteResolved.startsWith(`${repoRootResolved}${sep}`);
    if (!isInsideRepo) {
        return null;
    }
    const relativePath = normalizeToPosixPath(relative(repoRootResolved, absoluteResolved));
    const normalizedRelative = trimDocsRoot(relativePath);
    return !normalizedRelative || normalizedRelative === '.'
        ? '.'
        : normalizedRelative;
}
async function promptForManualDocsConfig(projectRoot, context, dependencies, promptMessage, defaultRoot = 'docs') {
    const hasDocs = await dependencies.confirmAction(promptMessage, {
        interactive: context.interactive,
    });
    if (!hasDocs) {
        return null;
    }
    const tooling = await dependencies.selectWithAbort('Documentation framework', DOCS_TOOLING_CHOICES, { interactive: context.interactive });
    if (!tooling) {
        return null;
    }
    while (true) {
        const rootInput = await dependencies.inputWithDefault('Docs root path (relative to repo root)', defaultRoot, { interactive: context.interactive });
        if (rootInput === null) {
            return null;
        }
        const root = normalizeDocumentationRoot(projectRoot, rootInput);
        if (root) {
            return { tooling, root };
        }
        context.logger.info('Docs root must be repo-relative or inside the repository.');
    }
}
async function runGuidedSetupImpl(context, dependencies) {
    const projectRoot = await dependencies.resolveScopeRoot('project', context);
    const adapters = dependencies.getAdapters();
    const configPath = join(projectRoot, '.oat', 'sync', 'config.json');
    const syncConfig = await dependencies.loadSyncConfig(configPath);
    const resolution = await dependencies.getConfigAwareAdapters(adapters, projectRoot, syncConfig);
    const activeProviderNames = resolution.activeAdapters.map((a) => a.displayName);
    context.logger.info('[1/5] Tool packs…');
    // Defer the per-pack scope gate to the tools flow: `gate` asks the customize
    // prompt after pack selection (and only when a user-eligible pack is
    // selected). Non-interactive setup applies additive defaults with no prompt.
    const scopeSelection = context.interactive
        ? 'gate'
        : 'defaults';
    const guidedContext = { ...context, scopeSelection };
    const installedPacks = await dependencies.runToolPacks(guidedContext);
    const installedPackSet = new Set(installedPacks);
    context.logger.info('[2/5] Local paths (gitignored artifacts)…');
    const config = await dependencies.readOatConfig(projectRoot);
    const existingPaths = new Set(dependencies.resolveLocalPaths(config));
    const applicableChoices = LOCAL_PATH_CHOICES.filter((c) => c.value !== '.oat/ideas' || installedPackSet.has('ideas'));
    const choices = applicableChoices.map((c) => ({
        ...c,
        checked: existingPaths.has(c.value) || c.checked,
    }));
    const selectedPaths = context.interactive
        ? ((await dependencies.selectManyWithAbort('Select local paths to add', choices, {
            interactive: context.interactive,
        })) ?? [])
        : [];
    let addedCount = 0;
    const guidedPathValues = new Set(applicableChoices.map((c) => c.value));
    const existingGuidedCount = [...existingPaths].filter((p) => guidedPathValues.has(p)).length;
    if (selectedPaths.length > 0) {
        const delta = selectedPaths.filter((p) => !existingPaths.has(p));
        if (delta.length > 0) {
            const addResult = await dependencies.addLocalPaths(projectRoot, delta);
            addedCount = addResult.added.length;
            await dependencies.applyGitignore(projectRoot, addResult.all);
            context.logger.info(`Added ${addResult.added.length} local path(s).`);
        }
        else {
            context.logger.info('All selected paths already configured.');
        }
    }
    context.logger.info('Add custom local paths anytime with `oat local add <path>`');
    // Detect and store default branch if not already configured
    {
        const currentConfig = await dependencies.readOatConfig(projectRoot);
        if (!currentConfig.git?.defaultBranch) {
            const detected = dependencies.detectDefaultBranch(projectRoot);
            currentConfig.git = { ...currentConfig.git, defaultBranch: detected };
            await dependencies.writeOatConfig(projectRoot, currentConfig);
            context.logger.info(`Default branch: ${detected}`);
        }
        else {
            context.logger.info(`Default branch: ${currentConfig.git.defaultBranch} (already configured)`);
        }
    }
    context.logger.info('[3/5] Documentation…');
    let docsSummary = 'skipped';
    const existingDocsConfig = config.documentation;
    if (existingDocsConfig?.tooling) {
        context.logger.info(`Documentation already configured: ${existingDocsConfig.tooling} at ${existingDocsConfig.root ?? '.'}`);
        docsSummary = `${existingDocsConfig.tooling} (already configured)`;
    }
    else {
        const detected = await dependencies.detectExistingDocs(projectRoot, {
            fileExists: dependencies.fileExists,
            dirExists: dependencies.dirExists,
        });
        let docsConfig = null;
        if (detected) {
            const confirmDetected = await dependencies.confirmAction(`Detected ${detected.tooling} docs at ${detected.root === '.' ? 'repo root' : detected.root}. Store in config?`, { interactive: context.interactive });
            if (confirmDetected) {
                docsConfig = {
                    tooling: detected.tooling,
                    root: detected.root,
                    ...(detected.config ? { config: detected.config } : {}),
                };
            }
            else {
                docsConfig = await promptForManualDocsConfig(projectRoot, context, dependencies, 'Would you like to enter docs config manually instead?', detected.root);
            }
        }
        else {
            docsConfig = await promptForManualDocsConfig(projectRoot, context, dependencies, 'Do you have documentation in this repo?');
        }
        if (docsConfig) {
            const updatedConfig = await dependencies.readOatConfig(projectRoot);
            updatedConfig.documentation = {
                ...updatedConfig.documentation,
                ...docsConfig,
            };
            await dependencies.writeOatConfig(projectRoot, updatedConfig);
            context.logger.info(`Stored docs config: ${docsConfig.tooling} at ${docsConfig.root}`);
            docsSummary = `${docsConfig.tooling} at ${docsConfig.root}`;
        }
    }
    context.logger.info('[4/5] Provider sync…');
    const syncProviders = await dependencies.confirmAction('Sync provider project views now?', { interactive: context.interactive });
    if (syncProviders) {
        await dependencies.runProviderSync(projectRoot);
    }
    context.logger.info('[5/5] Setup complete');
    context.logger.info('');
    context.logger.info('Guided setup complete.');
    context.logger.info('');
    context.logger.info(`  Providers:      ${activeProviderNames.length > 0 ? activeProviderNames.join(', ') : 'none detected'}`);
    context.logger.info(`  Tool packs:     ${installedPacks.length > 0 ? 'installed' : 'skipped'}`);
    context.logger.info(`  Local paths:    ${selectedPaths.length > 0 ? `${addedCount} added, ${existingGuidedCount} existing` : 'skipped'}`);
    context.logger.info(`  Documentation:  ${docsSummary}`);
    context.logger.info(`  Provider sync:  ${syncProviders ? 'done' : 'skipped'}`);
    context.logger.info('');
    context.logger.info('Next steps:');
    context.logger.info('  - Run `oat init tools` to customize tool pack selection');
    context.logger.info('  - Run `oat local add <path>` to add custom local paths');
    context.logger.info('  - Run `oat local status` to verify gitignore state');
    context.logger.info('  - Start a project: `oat-project-quick-start` or `oat-project-new`');
}
async function runInitCommand(context, dependencies, hookFlag, setupFlag) {
    const scopes = resolveConcreteScopes(context.scope);
    let projectRoot = null;
    let oatDirExistedBefore = true;
    const scopeSummaries = [];
    let migrationAborted = false;
    for (const scope of scopes) {
        const scopeRoot = await dependencies.resolveScopeRoot(scope, context);
        const syncConfigPath = join(scopeRoot, '.oat', 'sync', 'config.json');
        const userConfigDir = join(context.home, '.oat');
        let syncConfig = await dependencies.loadSyncConfig(syncConfigPath);
        const userSyncConfig = await dependencies.resolveUserSyncConfig(userConfigDir);
        let activeAdaptersForStrays;
        if (scope === 'project') {
            projectRoot = scopeRoot;
            oatDirExistedBefore = await dependencies.dirExists(join(scopeRoot, '.oat'));
            const adapters = dependencies.getAdapters();
            let config = syncConfig;
            let resolution = await dependencies.getConfigAwareAdapters(adapters, scopeRoot, config);
            if (!context.interactive && !context.json) {
                context.logger.info(PROVIDER_CONFIG_REMEDIATION);
            }
            if (context.interactive) {
                const providerChoices = adapters.map((adapter) => ({
                    label: adapter.name,
                    value: adapter.name,
                    description: adapter.displayName,
                    checked: config.providers[adapter.name]?.enabled === true ||
                        resolution.activeAdapters.some((active) => active.name === adapter.name),
                }));
                const selectedProviders = await dependencies.selectProvidersWithAbort('Select supported project providers', providerChoices, { interactive: context.interactive });
                if (selectedProviders !== null) {
                    const selectedProviderNames = new Set(selectedProviders);
                    const providers = { ...config.providers };
                    for (const adapter of adapters) {
                        providers[adapter.name] = {
                            ...(providers[adapter.name] ?? {}),
                            enabled: selectedProviderNames.has(adapter.name),
                        };
                    }
                    config = await dependencies.saveSyncConfig(syncConfigPath, {
                        ...config,
                        providers,
                    });
                    syncConfig = config;
                }
                resolution = await dependencies.getConfigAwareAdapters(adapters, scopeRoot, config);
            }
            activeAdaptersForStrays = resolution.activeAdapters;
        }
        await dependencies.ensureCanonicalDirs(scopeRoot, scope);
        if (scope === 'project') {
            await dependencies.applyOatCoreGitignore(scopeRoot);
        }
        const manifestPath = join(scopeRoot, '.oat', 'sync', 'manifest.json');
        let manifest = await dependencies.loadManifest(manifestPath);
        if (!manifest.entries) {
            manifest = createEmptyManifest();
        }
        const canonicalEntries = await dependencies.scanCanonical(scopeRoot, scope);
        const collectedStrays = await dependencies.collectStrays(scopeRoot, scope, manifest, canonicalEntries, activeAdaptersForStrays, userConfigDir);
        const { candidates: strays } = filterKnownStrays({
            reports: collectedStrays.map((stray) => stray.report),
            candidates: collectedStrays,
            knownStrays: {
                project: syncConfig.knownStrays,
                user: userSyncConfig.knownStrays,
            },
        });
        let straysAdopted = 0;
        let codexStrayAdopted = false;
        const cursorSkillStrays = strays.filter(isCursorSkillCandidate);
        const ordinaryStrays = strays.filter((stray) => !isCursorSkillCandidate(stray));
        if (!context.interactive && strays.length > 0) {
            context.logger.warn(ADOPT_REMEDIATION);
        }
        if (context.interactive && strays.length > 0 && !migrationAborted) {
            for (const stray of cursorSkillStrays) {
                const disposition = await dependencies.selectWithAbort(`Migrate Cursor skill [${scope}]: ${formatPathForScope(scope, stray.report.providerPath)}`, [
                    {
                        label: 'Adopt into canonical',
                        value: 'adopt',
                        description: `Move to ${stray.mapping.canonicalDir}.`,
                    },
                    {
                        label: 'Keep Cursor-only',
                        value: 'keep',
                        description: 'Leave in .cursor/skills and remember this choice.',
                    },
                ], { interactive: context.interactive });
                if (disposition === null) {
                    migrationAborted = true;
                    break;
                }
                try {
                    manifest = await dependencies.applyCursorSkillDisposition(scopeRoot, stray, manifest, disposition, syncConfigPath);
                    if (disposition === 'adopt') {
                        straysAdopted += 1;
                    }
                    else {
                        context.logger.success(`Kept Cursor-only [${scope}]: ${formatPathForScope(scope, stray.report.providerPath)}.`);
                    }
                }
                catch (error) {
                    if (error instanceof Error &&
                        error.message.startsWith('Cannot keep ')) {
                        context.logger.warn(error.message);
                        continue;
                    }
                    if (!isAdoptionConflictError(error) || disposition !== 'adopt') {
                        throw error;
                    }
                    const shouldReplace = await dependencies.confirmAction(`Conflict detected for ${formatPathForScope(scope, stray.report.providerPath)}. Replace canonical content with stray content?`, { interactive: context.interactive });
                    if (!shouldReplace) {
                        context.logger.warn(`Skipped conflicting Cursor skill [${scope}]: ${formatPathForScope(scope, stray.report.providerPath)}`);
                        continue;
                    }
                    manifest = await dependencies.applyCursorSkillDisposition(scopeRoot, stray, manifest, disposition, syncConfigPath, { replaceCanonical: true });
                    straysAdopted += 1;
                }
            }
            if (!migrationAborted && ordinaryStrays.length > 0) {
                const selectedValues = await dependencies.selectManyWithAbort(`Select stray entries to adopt [${scope}]`, ordinaryStrays.map((stray, index) => ({
                    label: formatStrayChoiceLabel(scope, stray.report.providerPath, stray.provider),
                    value: String(index),
                    description: formatPathForScope(scope, stray.report.providerPath),
                })), { interactive: context.interactive });
                if (selectedValues === null) {
                    migrationAborted = true;
                }
                else {
                    const selectedIndices = new Set(selectedValues.map((value) => Number.parseInt(value, 10)));
                    for (const [index, stray] of ordinaryStrays.entries()) {
                        if (!selectedIndices.has(index)) {
                            continue;
                        }
                        try {
                            manifest = await dependencies.adoptStray(scopeRoot, stray, manifest);
                            straysAdopted += 1;
                            codexStrayAdopted =
                                codexStrayAdopted || stray.provider === 'codex';
                        }
                        catch (error) {
                            if (!isAdoptionConflictError(error)) {
                                throw error;
                            }
                            const shouldReplace = await dependencies.confirmAction(`Conflict detected for ${formatPathForScope(scope, stray.report.providerPath)}. Replace canonical content with stray content?`, { interactive: context.interactive });
                            if (!shouldReplace) {
                                context.logger.warn(`Skipped conflicting stray entry [${scope}]: ${formatPathForScope(scope, stray.report.providerPath)}`);
                                continue;
                            }
                            manifest = await dependencies.adoptStray(scopeRoot, stray, manifest, {
                                replaceCanonical: true,
                            });
                            straysAdopted += 1;
                            codexStrayAdopted =
                                codexStrayAdopted || stray.provider === 'codex';
                        }
                    }
                }
            }
            if (codexStrayAdopted && scope === 'project') {
                await regenerateCodexAfterAdoption({
                    scopeRoot,
                    scanCanonical: async () => dependencies.scanCanonical(scopeRoot, scope),
                    computeExtensionPlan: computeCodexProjectExtensionPlan,
                    applyExtensionPlan: applyCodexProjectExtensionPlan,
                });
                if (activeAdaptersForStrays?.some((adapter) => adapter.name === 'cursor')) {
                    const refreshedCanonical = await dependencies.scanCanonical(scopeRoot, scope);
                    const cursorPlan = await computeCursorProjectExtensionPlan(scopeRoot, refreshedCanonical, undefined, { userConfigDir });
                    await applyCursorProjectExtensionPlan(scopeRoot, cursorPlan);
                }
            }
            if (straysAdopted > 0) {
                context.logger.success(`Adopted ${straysAdopted} stray entr${straysAdopted === 1 ? 'y' : 'ies'} [${scope}].`);
            }
            else {
                context.logger.info(`No stray entries adopted [${scope}].`);
            }
        }
        await dependencies.saveManifest(manifestPath, manifest);
        scopeSummaries.push({
            scope,
            straysFound: strays.length,
            straysAdopted,
        });
    }
    const hookInstalled = await maybeHandleHook(context, dependencies, projectRoot, hookFlag);
    if (context.json) {
        const payload = {
            scope: context.scope,
            directoriesCreated: scopeSummaries.length,
            straysFound: scopeSummaries.reduce((total, summary) => total + summary.straysFound, 0),
            straysAdopted: scopeSummaries.reduce((total, summary) => total + summary.straysAdopted, 0),
            hookInstalled,
            scopes: scopeSummaries,
        };
        context.logger.json(payload);
    }
    process.exitCode = 0;
    const freshInit = projectRoot !== null && !oatDirExistedBefore;
    if (setupFlag || (context.interactive && freshInit)) {
        let shouldRunSetup = !!setupFlag;
        if (!shouldRunSetup && freshInit) {
            shouldRunSetup = await dependencies.confirmAction('Would you like to run guided setup?', { interactive: context.interactive });
        }
        if (shouldRunSetup) {
            await dependencies.runGuidedSetup(context, dependencies);
        }
    }
}
export function createInitCommand(overrides = {}) {
    const dependencies = {
        ...createDependencies(),
        ...overrides,
    };
    return withScopeOption(new Command('init'))
        .description('Initialize canonical directories, manifest, and tool packs')
        .option('--hook', 'Install optional pre-commit hook')
        .option('--no-hook', 'Skip optional pre-commit hook install')
        .option('--setup', 'Run guided setup after initialization')
        .addCommand(createInitToolsCommand())
        .action(async (_options, command) => {
        const options = readGlobalOptions(command);
        const context = dependencies.buildCommandContext(options);
        await runInitCommand(context, dependencies, options.hook, options.setup);
    });
}
