import { join } from 'node:path';
import { buildCommandContext } from '../../app/command-context.js';
import { PROVIDER_CONFIG_REMEDIATION } from '../shared/messages.js';
import { withScopeOption } from '../shared/scope-option.js';
import { selectManyWithAbort, } from '../shared/shared.prompts.js';
import { readGlobalOptions, resolveConcreteScopes, } from '../shared/shared.utils.js';
import { DEFAULT_SYNC_CONFIG, loadSyncConfig, saveSyncConfig, } from '../../config/index.js';
import { computeSyncPlan, executeSyncPlan, scanBundledManagedCodexAgents, scanCanonical, } from '../../engine/index.js';
import { CliError } from '../../errors/index.js';
import { resolveProjectRoot, resolveScopeRoot } from '../../fs/paths.js';
import { loadManifest } from '../../manifest/index.js';
import { claudeAdapter } from '../../providers/claude/index.js';
import { codexAdapter } from '../../providers/codex/index.js';
import { applyCodexProjectExtensionPlan, computeCodexProjectExtensionPlan, toCodexExtensionOperations, } from '../../providers/codex/codec/sync-extension.js';
import { copilotAdapter } from '../../providers/copilot/index.js';
import { cursorAdapter } from '../../providers/cursor/index.js';
import { geminiAdapter } from '../../providers/gemini/index.js';
import { getConfigAwareAdapters, } from '../../providers/shared/index.js';
import { formatSyncPlan } from '../../ui/output.js';
import { Command, Option } from 'commander';
import { runSyncApply } from './apply.js';
import { runSyncDryRun } from './dry-run.js';
function defaultDependencies() {
    return {
        buildCommandContext,
        async resolveScopeRoot(scope, context) {
            if (scope === 'project') {
                return resolveProjectRoot(context.cwd);
            }
            return resolveScopeRoot(scope, context.cwd, context.home);
        },
        loadManifest,
        async loadSyncConfig(configPath) {
            const config = await loadSyncConfig(configPath, DEFAULT_SYNC_CONFIG);
            return config;
        },
        saveSyncConfig,
        scanCanonical,
        scanBundledManagedCodexAgents,
        getAdapters() {
            return [
                claudeAdapter,
                cursorAdapter,
                codexAdapter,
                copilotAdapter,
                geminiAdapter,
            ];
        },
        getConfigAwareAdapters,
        selectProvidersWithAbort: selectManyWithAbort,
        computeSyncPlan,
        executeSyncPlan,
        computeCodexProjectExtensionPlan,
        toCodexExtensionOperations,
        applyCodexProjectExtensionPlan,
        formatSyncPlan,
    };
}
const INSTALL_CANONICAL_PATH_PATTERN = /^\.agents\/(skills|agents|rules)\/[^/\\]+$/;
function validateInstallCanonicalPaths(paths) {
    if (!paths?.length) {
        return undefined;
    }
    for (const path of paths) {
        if (!INSTALL_CANONICAL_PATH_PATTERN.test(path)) {
            throw new CliError(`Invalid --install-canonical path: ${path}`, 1);
        }
    }
    return paths;
}
function hasProviderMismatches(mismatches) {
    return (mismatches.detectedUnset.length > 0 ||
        mismatches.detectedDisabled.length > 0);
}
function buildMismatchChoices(adapters, mismatches) {
    const mismatchSet = new Set([
        ...mismatches.detectedUnset,
        ...mismatches.detectedDisabled,
    ]);
    return adapters
        .filter((adapter) => mismatchSet.has(adapter.name))
        .map((adapter) => {
        const isDisabled = mismatches.detectedDisabled.includes(adapter.name);
        return {
            label: adapter.name,
            value: adapter.name,
            description: isDisabled
                ? `${adapter.displayName} (detected, currently disabled)`
                : `${adapter.displayName} (detected, not configured)`,
            checked: true,
        };
    });
}
async function maybeResolveProviderMismatches(context, scope, scopeRoot, configPath, config, adapters, mismatches, dependencies) {
    if (scope !== 'project' ||
        !context.interactive ||
        !hasProviderMismatches(mismatches)) {
        return {
            config,
            mismatches,
            activeAdapters: await dependencies
                .getConfigAwareAdapters(adapters, scopeRoot, config)
                .then((resolution) => resolution.activeAdapters),
        };
    }
    const choices = buildMismatchChoices(adapters, mismatches);
    const selected = await dependencies.selectProvidersWithAbort('Detected provider directories are not enabled in config. Select providers to enable for sync.', choices, { interactive: context.interactive });
    if (selected === null) {
        const resolution = await dependencies.getConfigAwareAdapters(adapters, scopeRoot, config);
        return {
            config,
            mismatches,
            activeAdapters: resolution.activeAdapters,
        };
    }
    const selectedSet = new Set(selected);
    // `detectedUnset` and `detectedDisabled` are mutually exclusive by contract.
    const mismatchedProviders = new Set([
        ...mismatches.detectedUnset,
        ...mismatches.detectedDisabled,
    ]);
    const providers = { ...config.providers };
    for (const providerName of mismatchedProviders) {
        providers[providerName] = {
            ...(providers[providerName] ?? {}),
            enabled: selectedSet.has(providerName),
        };
    }
    const savedConfig = await dependencies.saveSyncConfig(configPath, {
        ...config,
        providers,
    });
    const resolution = await dependencies.getConfigAwareAdapters(adapters, scopeRoot, savedConfig);
    return {
        config: savedConfig,
        mismatches: {
            detectedUnset: resolution.detectedUnset,
            detectedDisabled: resolution.detectedDisabled,
        },
        activeAdapters: resolution.activeAdapters,
    };
}
async function computePlans(context, dependencies, allowedCanonicalPaths) {
    const scopePlans = [];
    for (const scope of resolveConcreteScopes(context.scope)) {
        const scopeRoot = await dependencies.resolveScopeRoot(scope, context);
        const manifestPath = join(scopeRoot, '.oat', 'sync', 'manifest.json');
        const configPath = join(scopeRoot, '.oat', 'sync', 'config.json');
        const [manifest, config, canonical] = await Promise.all([
            dependencies.loadManifest(manifestPath),
            dependencies.loadSyncConfig(configPath),
            dependencies.scanCanonical(scopeRoot, scope),
        ]);
        const adapters = dependencies.getAdapters();
        const initialResolution = await dependencies.getConfigAwareAdapters(adapters, scopeRoot, config);
        const resolved = await maybeResolveProviderMismatches(context, scope, scopeRoot, configPath, config, adapters, {
            detectedUnset: initialResolution.detectedUnset,
            detectedDisabled: initialResolution.detectedDisabled,
        }, dependencies);
        const plan = await dependencies.computeSyncPlan({
            canonical,
            adapters: resolved.activeAdapters,
            manifest,
            scope,
            config: resolved.config,
            scopeRoot,
            allowedCanonicalPaths,
        });
        let codexExtensionPlan;
        let codexExtension;
        const activeAdapterNames = resolved.activeAdapters.map((adapter) => adapter.name);
        if (activeAdapterNames.includes('codex')) {
            const codexCanonical = scope === 'user'
                ? [
                    ...canonical,
                    ...(await dependencies.scanBundledManagedCodexAgents()),
                ]
                : canonical;
            codexExtensionPlan = await dependencies.computeCodexProjectExtensionPlan(scopeRoot, codexCanonical, allowedCanonicalPaths, { userConfigDir: join(context.home, '.oat') });
            codexExtension = {
                operations: dependencies.toCodexExtensionOperations(codexExtensionPlan),
                managedRoles: codexExtensionPlan.managedRoles,
                aggregateConfigHash: codexExtensionPlan.aggregateConfigHash,
            };
        }
        scopePlans.push({
            scope,
            scopeRoot,
            manifestPath,
            manifest,
            canonical,
            activeAdapterNames,
            plan,
            providerMismatches: resolved.mismatches,
            codexExtensionPlan,
            codexExtension,
        });
    }
    return scopePlans;
}
function logNonInteractiveMismatchGuidance(context, scopePlans) {
    if (context.interactive) {
        return;
    }
    for (const scopePlan of scopePlans) {
        if (scopePlan.scope !== 'project' || !scopePlan.providerMismatches) {
            continue;
        }
        const { detectedUnset, detectedDisabled } = scopePlan.providerMismatches;
        if (detectedUnset.length === 0 && detectedDisabled.length === 0) {
            continue;
        }
        const parts = [];
        if (detectedUnset.length > 0) {
            parts.push(`unset: ${detectedUnset.join(', ')}`);
        }
        if (detectedDisabled.length > 0) {
            parts.push(`disabled: ${detectedDisabled.join(', ')}`);
        }
        context.logger.warn(`Provider config mismatch detected [project] (${parts.join('; ')}).`);
        if (!context.json) {
            context.logger.info(PROVIDER_CONFIG_REMEDIATION);
        }
    }
}
async function runSyncCommand(context, dependencies, allowedCanonicalPaths) {
    const scopePlans = await computePlans(context, dependencies, allowedCanonicalPaths);
    logNonInteractiveMismatchGuidance(context, scopePlans);
    if (context.dryRun) {
        runSyncDryRun(context, scopePlans, dependencies);
        return;
    }
    await runSyncApply(context, scopePlans, dependencies);
}
export function createSyncCommand(overrides = {}) {
    const dependencies = {
        ...defaultDependencies(),
        ...overrides,
    };
    return withScopeOption(new Command('sync'))
        .description('Sync canonical content to provider views')
        .option('--dry-run', 'Preview sync changes without applying')
        .addOption(new Option('--install-canonical <path>', 'Internal install sync filter')
        .hideHelp()
        .default([])
        .argParser((value, previous) => [
        ...(previous ?? []),
        value,
    ]))
        .action(async (_options, command) => {
        const context = dependencies.buildCommandContext(readGlobalOptions(command));
        const options = command.opts();
        await runSyncCommand(context, dependencies, validateInstallCanonicalPaths(options.installCanonical));
    });
}
