import { basename, join, relative } from 'node:path';
import { buildCommandContext, } from '../../app/command-context.js';
import { adoptStrayToCanonical, isAdoptionConflictError, } from '../shared/adopt-stray.js';
import { detectCodexRoleStrays, regenerateCodexAfterAdoption, } from '../shared/codex-strays.js';
import { withScopeOption } from '../shared/scope-option.js';
import { confirmAction, selectManyWithAbort, } from '../shared/shared.prompts.js';
import { readGlobalOptions, resolveConcreteScopes, } from '../shared/shared.utils.js';
import { DEFAULT_SYNC_CONFIG, loadSyncConfig, } from '../../config/index.js';
import { readUserConfig } from '../../config/oat-config.js';
import { detectDrift, detectStrays, filterKnownStrays, } from '../../drift/index.js';
import { HOOK_DRIFT_WARNING, HOOK_STRAY_INFO, scanBundledManagedCodexAgents, scanCanonical, } from '../../engine/index.js';
import { normalizeToPosixPath, resolveProjectRoot, resolveScopeRoot, } from '../../fs/paths.js';
import { 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 { geminiAdapter } from '../../providers/gemini/index.js';
import { getActiveAdapters, getSyncMappings, } from '../../providers/shared/index.js';
import { SCOPE_CONTENT_TYPES, } from '../../shared/types.js';
import { formatStatusTable } from '../../ui/output.js';
import { Command } from 'commander';
const DEFAULT_REMEDIATION = 'Run "oat init" to adopt stray entries.';
const DEFAULT_DEPENDENCIES = {
    buildCommandContext,
    async resolveScopeRoot(scope, context) {
        if (scope === 'project') {
            return resolveProjectRoot(context.cwd);
        }
        return resolveScopeRoot(scope, context.cwd, context.home);
    },
    loadManifest,
    async loadSyncConfig(configPath) {
        return loadSyncConfig(configPath, DEFAULT_SYNC_CONFIG);
    },
    readUserConfig,
    saveManifest,
    scanCanonical,
    scanBundledManagedCodexAgents,
    getAdapters() {
        return [
            claudeAdapter,
            cursorAdapter,
            codexAdapter,
            copilotAdapter,
            geminiAdapter,
        ];
    },
    getActiveAdapters,
    getSyncMappings,
    detectDrift,
    detectStrays,
    detectCodexRoleStrays,
    computeCodexProjectExtensionPlan,
    applyCodexProjectExtensionPlan,
    selectManyWithAbort,
    confirmAction,
    adoptStray: adoptStrayDefault,
    formatStatusTable,
};
function entryInsideMapping(entryProviderPath, mappingProviderDir) {
    const normalizedEntryPath = normalizeToPosixPath(entryProviderPath);
    const normalizedProviderDir = normalizeToPosixPath(mappingProviderDir);
    return (normalizedEntryPath === normalizedProviderDir ||
        normalizedEntryPath.startsWith(`${normalizedProviderDir}/`));
}
function canonicalInsideMapping(canonicalPath, mappingCanonicalDir) {
    const normalizedCanonicalPath = normalizeToPosixPath(canonicalPath);
    const normalizedCanonicalDir = normalizeToPosixPath(mappingCanonicalDir);
    return (normalizedCanonicalPath === normalizedCanonicalDir ||
        normalizedCanonicalPath.startsWith(`${normalizedCanonicalDir}/`));
}
function contentTypeAllowed(contentType, scope) {
    return SCOPE_CONTENT_TYPES[scope].includes(contentType);
}
function summarizeReports(reports) {
    const summary = {
        total: reports.length,
        inSync: 0,
        drifted: 0,
        missing: 0,
        stray: 0,
    };
    for (const report of reports) {
        if (report.state.status === 'in_sync') {
            summary.inSync += 1;
            continue;
        }
        if (report.state.status === 'drifted') {
            summary.drifted += 1;
            continue;
        }
        if (report.state.status === 'missing') {
            summary.missing += 1;
            continue;
        }
        summary.stray += 1;
    }
    return summary;
}
async function adoptStrayDefault(scopeRoot, stray, manifest, options) {
    return adoptStrayToCanonical(scopeRoot, stray, manifest, options);
}
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 collectScopeReports(scope, context, dependencies) {
    const scopeRoot = await dependencies.resolveScopeRoot(scope, context);
    const manifestPath = join(scopeRoot, '.oat', 'sync', 'manifest.json');
    const syncConfigPath = join(scopeRoot, '.oat', 'sync', 'config.json');
    const userConfigDir = join(context.home, '.oat');
    const [manifest, canonicalEntries, syncConfig, userConfig] = await Promise.all([
        dependencies.loadManifest(manifestPath),
        dependencies.scanCanonical(scopeRoot, scope),
        dependencies.loadSyncConfig(syncConfigPath),
        dependencies.readUserConfig(userConfigDir),
    ]);
    const adapters = dependencies.getAdapters();
    const activeAdapters = await dependencies.getActiveAdapters(adapters, scopeRoot);
    const reports = [];
    const strayCandidates = [];
    const trackedCanonicalByProvider = new Set(manifest.entries.map((entry) => `${entry.provider}|${normalizeToPosixPath(entry.canonicalPath)}`));
    for (const adapter of activeAdapters) {
        const mappings = dependencies.getSyncMappings(adapter, scope);
        const mappingContentTypes = new Set(mappings.map((mapping) => mapping.contentType));
        for (const entry of manifest.entries) {
            if (entry.provider !== adapter.name) {
                continue;
            }
            if (!mappingContentTypes.has(entry.contentType)) {
                continue;
            }
            if (!contentTypeAllowed(entry.contentType, scope)) {
                continue;
            }
            const matchedMapping = mappings.find((mapping) => mapping.contentType === entry.contentType &&
                entryInsideMapping(entry.providerPath, mapping.providerDir));
            if (!matchedMapping) {
                continue;
            }
            const copyTransform = matchedMapping.transformCanonical
                ? { transformCanonical: matchedMapping.transformCanonical }
                : undefined;
            reports.push(await dependencies.detectDrift(entry, scopeRoot, copyTransform));
        }
        for (const mapping of mappings) {
            for (const canonicalEntry of canonicalEntries) {
                if (canonicalEntry.type !== mapping.contentType) {
                    continue;
                }
                const canonicalPath = normalizeToPosixPath(relative(scopeRoot, canonicalEntry.canonicalPath));
                if (!canonicalInsideMapping(canonicalPath, mapping.canonicalDir)) {
                    continue;
                }
                const trackedKey = `${adapter.name}|${canonicalPath}`;
                if (trackedCanonicalByProvider.has(trackedKey)) {
                    continue;
                }
                reports.push({
                    canonical: canonicalPath,
                    provider: adapter.name,
                    providerPath: normalizeToPosixPath(join(mapping.providerDir, canonicalEntry.name)),
                    state: { status: 'missing' },
                });
            }
        }
        for (const mapping of mappings) {
            const providerDir = join(scopeRoot, mapping.providerDir);
            const strays = await dependencies.detectStrays(adapter.name, providerDir, manifest, canonicalEntries, mapping);
            reports.push(...strays);
            for (const stray of strays) {
                if (stray.state.status !== 'stray') {
                    continue;
                }
                strayCandidates.push({
                    provider: adapter.name,
                    report: stray,
                    mapping,
                });
            }
        }
    }
    if (activeAdapters.some((adapter) => adapter.name === 'codex')) {
        const codexCanonicalEntries = scope === 'user'
            ? [
                ...canonicalEntries,
                ...(await dependencies.scanBundledManagedCodexAgents()),
            ]
            : canonicalEntries;
        const codexExtensionPlan = await dependencies.computeCodexProjectExtensionPlan(scopeRoot, codexCanonicalEntries, undefined, { userConfigDir: join(context.home, '.oat') });
        for (const operation of codexExtensionPlan.operations) {
            if (operation.action === 'skip') {
                continue;
            }
            if (operation.target === 'role') {
                reports.push({
                    canonical: operation.roleName
                        ? `.agents/agents/${operation.roleName}.md`
                        : null,
                    provider: 'codex',
                    providerPath: operation.path,
                    state: operation.action === 'create'
                        ? { status: 'missing' }
                        : { status: 'drifted', reason: 'modified' },
                });
            }
            else {
                reports.push({
                    canonical: null,
                    provider: 'codex',
                    providerPath: operation.path,
                    state: { status: 'drifted', reason: 'modified' },
                });
            }
        }
        const codexStrays = await dependencies.detectCodexRoleStrays(scopeRoot, canonicalEntries, new Set(codexExtensionPlan.managedRoles));
        for (const codexStray of codexStrays) {
            const report = {
                canonical: null,
                provider: 'codex',
                providerPath: codexStray.providerPath,
                state: { status: 'stray' },
            };
            reports.push(report);
            strayCandidates.push({
                provider: 'codex',
                report,
                mapping: {
                    contentType: 'agent',
                    canonicalDir: '.agents/agents',
                    providerDir: '.codex/agents',
                    nativeRead: false,
                },
                adoption: {
                    kind: 'codex_role',
                    roleName: codexStray.roleName,
                    description: codexStray.description,
                },
            });
        }
    }
    const filtered = filterKnownStrays({
        reports,
        candidates: strayCandidates,
        knownStrays: {
            project: syncConfig.knownStrays,
            user: userConfig.knownStrays,
        },
    });
    return {
        scope,
        scopeRoot,
        manifestPath,
        manifest,
        reports: filtered.reports,
        strayCandidates: filtered.candidates,
    };
}
async function runStatusCommand(context, dependencies, options = {}) {
    const reports = [];
    const scopeCollections = [];
    for (const scope of resolveConcreteScopes(context.scope)) {
        const scopeReportCollection = await collectScopeReports(scope, context, dependencies);
        reports.push(...scopeReportCollection.reports);
        scopeCollections.push(scopeReportCollection);
    }
    const summary = summarizeReports(reports);
    const hasIssues = summary.total > 0 && summary.inSync !== summary.total;
    if (options.hook) {
        if (summary.drifted > 0 || summary.missing > 0) {
            context.logger.warn(HOOK_DRIFT_WARNING);
            process.exitCode = 1;
        }
        else if (summary.stray > 0) {
            context.logger.info(HOOK_STRAY_INFO);
            process.exitCode = 0;
        }
        else {
            process.exitCode = 0;
        }
        return;
    }
    if (context.json) {
        const payload = {
            scope: context.scope,
            reports,
            summary,
        };
        if (!context.interactive && summary.stray > 0) {
            payload.remediation = DEFAULT_REMEDIATION;
        }
        context.logger.json(payload);
    }
    else {
        context.logger.info(dependencies.formatStatusTable(reports));
    }
    if (summary.stray > 0) {
        if (context.interactive) {
            for (const scopeCollection of scopeCollections) {
                if (scopeCollection.strayCandidates.length === 0) {
                    continue;
                }
                let manifestChanged = false;
                const selectedValues = await dependencies.selectManyWithAbort(`Select stray entries to adopt [${scopeCollection.scope}]`, scopeCollection.strayCandidates.map((strayCandidate, index) => ({
                    label: formatStrayChoiceLabel(scopeCollection.scope, strayCandidate.report.providerPath, strayCandidate.provider),
                    value: String(index),
                    description: formatPathForScope(scopeCollection.scope, strayCandidate.report.providerPath),
                })), { interactive: context.interactive });
                const selectedIndices = new Set((selectedValues ?? []).map((value) => Number.parseInt(value, 10)));
                let adoptedCount = 0;
                let codexStrayAdopted = false;
                for (const [index, strayCandidate,] of scopeCollection.strayCandidates.entries()) {
                    if (!selectedIndices.has(index)) {
                        continue;
                    }
                    try {
                        scopeCollection.manifest = await dependencies.adoptStray(scopeCollection.scopeRoot, strayCandidate, scopeCollection.manifest);
                        adoptedCount += 1;
                        manifestChanged = true;
                        codexStrayAdopted =
                            codexStrayAdopted || strayCandidate.provider === 'codex';
                    }
                    catch (error) {
                        if (!isAdoptionConflictError(error)) {
                            throw error;
                        }
                        const shouldReplace = await dependencies.confirmAction(`Conflict detected for ${formatPathForScope(scopeCollection.scope, strayCandidate.report.providerPath)}. Replace canonical with stray content?`, { interactive: context.interactive });
                        if (!shouldReplace) {
                            context.logger.warn(`Skipped adopting conflicting stray [${scopeCollection.scope}] ${formatPathForScope(scopeCollection.scope, strayCandidate.report.providerPath)}.`);
                            continue;
                        }
                        scopeCollection.manifest = await dependencies.adoptStray(scopeCollection.scopeRoot, strayCandidate, scopeCollection.manifest, { replaceCanonical: true });
                        adoptedCount += 1;
                        manifestChanged = true;
                        codexStrayAdopted =
                            codexStrayAdopted || strayCandidate.provider === 'codex';
                    }
                }
                if (codexStrayAdopted && scopeCollection.scope === 'project') {
                    await regenerateCodexAfterAdoption({
                        scopeRoot: scopeCollection.scopeRoot,
                        scanCanonical: async () => dependencies.scanCanonical(scopeCollection.scopeRoot, scopeCollection.scope),
                        computeExtensionPlan: dependencies.computeCodexProjectExtensionPlan,
                        applyExtensionPlan: dependencies.applyCodexProjectExtensionPlan,
                    });
                }
                if (manifestChanged) {
                    await dependencies.saveManifest(scopeCollection.manifestPath, scopeCollection.manifest);
                    context.logger.success(`Adopted ${adoptedCount} stray entr${adoptedCount === 1 ? 'y' : 'ies'} [${scopeCollection.scope}].`);
                }
                else {
                    context.logger.info(`No stray entries adopted [${scopeCollection.scope}].`);
                }
            }
        }
        else if (!context.json) {
            context.logger.warn(DEFAULT_REMEDIATION);
        }
    }
    process.exitCode = hasIssues ? 1 : 0;
}
export function createStatusCommand(overrides = {}) {
    const dependencies = {
        ...DEFAULT_DEPENDENCIES,
        ...overrides,
    };
    return withScopeOption(new Command('status'))
        .description('Report provider sync and drift status')
        .option('--hook', 'Emit a minimal pre-commit message: warn on managed drift, info on strays')
        .action(async (options, command) => {
        const context = dependencies.buildCommandContext(readGlobalOptions(command));
        await runStatusCommand(context, dependencies, {
            hook: Boolean(options.hook),
        });
    });
}
