import { access, readFile, rename, rm, stat, writeFile, } from 'node:fs/promises';
import { basename, dirname, relative, resolve } from 'node:path';
import { renderCanonicalAgentMarkdown } from '../../agents/canonical/index.js';
import { CliError } from '../../errors/index.js';
import { createSymlink, ensureDir } from '../../fs/io.js';
import { toPosixPath } from '../../fs/paths.js';
import { computeContentHash } from '../../manifest/hash.js';
import { addEntry } from '../../manifest/manager.js';
import { importCanonicalAgentFromCodexRole } from '../../providers/codex/codec/import-from-codex.js';
import { canonicalRuleNameForProviderEntry } from '../../rules/canonical/index.js';
export function isAdoptionConflictError(error) {
    return error instanceof CliError && error.message.startsWith('Cannot adopt ');
}
async function pathExists(path) {
    try {
        await access(path);
        return true;
    }
    catch {
        return false;
    }
}
export async function adoptStrayToCanonical(scopeRoot, stray, manifest, options = {}) {
    if (stray.adoption?.kind === 'codex_role') {
        return adoptCodexRoleStrayToCanonical(scopeRoot, stray, manifest, options);
    }
    const providerAbsolutePath = resolve(scopeRoot, stray.report.providerPath);
    const providerStat = await stat(providerAbsolutePath);
    const isFile = providerStat.isFile();
    if (isFile && stray.mapping.contentType === 'rule') {
        return adoptRuleStrayToCanonical(scopeRoot, stray, manifest, options);
    }
    const entryName = basename(stray.report.providerPath);
    const canonicalAbsolutePath = resolve(scopeRoot, stray.mapping.canonicalDir, entryName);
    await ensureDir(dirname(canonicalAbsolutePath));
    const canonicalAlreadyExists = await pathExists(canonicalAbsolutePath);
    if (canonicalAlreadyExists) {
        const canonicalHash = await computeContentHash(canonicalAbsolutePath, isFile);
        const providerHash = await computeContentHash(providerAbsolutePath, isFile);
        if (canonicalHash !== providerHash) {
            if (options.replaceCanonical) {
                await rm(canonicalAbsolutePath, { recursive: true, force: true });
                await rename(providerAbsolutePath, canonicalAbsolutePath);
            }
            else {
                throw new CliError(`Cannot adopt ${toPosixPath(relative(scopeRoot, providerAbsolutePath))}: canonical path ${toPosixPath(relative(scopeRoot, canonicalAbsolutePath))} already exists with different content.`);
            }
        }
        else {
            await rm(providerAbsolutePath, { recursive: true, force: true });
        }
    }
    else {
        await rename(providerAbsolutePath, canonicalAbsolutePath);
    }
    const strategy = await createSymlink(canonicalAbsolutePath, providerAbsolutePath, undefined, isFile);
    const canonicalPath = toPosixPath(relative(scopeRoot, canonicalAbsolutePath));
    const providerPath = toPosixPath(relative(scopeRoot, providerAbsolutePath));
    const manifestEntry = {
        canonicalPath,
        providerPath,
        provider: stray.provider,
        contentType: stray.mapping.contentType,
        strategy,
        contentHash: strategy === 'copy'
            ? await computeContentHash(canonicalAbsolutePath, isFile)
            : null,
        isFile,
        lastSynced: new Date().toISOString(),
    };
    return addEntry(manifest, manifestEntry);
}
async function adoptRuleStrayToCanonical(scopeRoot, stray, manifest, options) {
    const providerAbsolutePath = resolve(scopeRoot, stray.report.providerPath);
    const providerContent = await readFile(providerAbsolutePath, 'utf8');
    const canonicalEntryName = canonicalRuleNameForProviderEntry(basename(stray.report.providerPath), stray.mapping);
    const canonicalAbsolutePath = resolve(scopeRoot, stray.mapping.canonicalDir, canonicalEntryName);
    const canonicalRelativePath = toPosixPath(relative(scopeRoot, canonicalAbsolutePath));
    const canonicalContent = stray.mapping.parseToCanonical
        ? stray.mapping.parseToCanonical(providerContent, stray.report.providerPath)
        : providerContent;
    await ensureDir(dirname(canonicalAbsolutePath));
    const canonicalAlreadyExists = await pathExists(canonicalAbsolutePath);
    if (canonicalAlreadyExists) {
        const existing = await readFile(canonicalAbsolutePath, 'utf8');
        if (existing.trimEnd() !== canonicalContent.trimEnd()) {
            if (!options.replaceCanonical) {
                throw new CliError(`Cannot adopt ${toPosixPath(relative(scopeRoot, providerAbsolutePath))}: canonical path ${toPosixPath(relative(scopeRoot, canonicalAbsolutePath))} already exists with different content.`);
            }
        }
    }
    await writeFile(canonicalAbsolutePath, canonicalContent, 'utf8');
    const renderedProviderContent = stray.mapping.transformCanonical
        ? stray.mapping.transformCanonical(canonicalContent, canonicalRelativePath)
        : providerContent;
    await writeFile(providerAbsolutePath, renderedProviderContent, 'utf8');
    const manifestEntry = {
        canonicalPath: canonicalRelativePath,
        providerPath: toPosixPath(relative(scopeRoot, providerAbsolutePath)),
        provider: stray.provider,
        contentType: stray.mapping.contentType,
        strategy: 'copy',
        contentHash: await computeContentHash(providerAbsolutePath, true),
        isFile: true,
        lastSynced: new Date().toISOString(),
    };
    return addEntry(manifest, manifestEntry);
}
async function adoptCodexRoleStrayToCanonical(scopeRoot, stray, manifest, options) {
    const roleName = stray.adoption?.roleName;
    if (!roleName) {
        throw new CliError('Cannot adopt Codex stray without role metadata.');
    }
    const providerAbsolutePath = resolve(scopeRoot, stray.report.providerPath);
    const providerContent = await readFile(providerAbsolutePath, 'utf8');
    const imported = importCanonicalAgentFromCodexRole({
        roleName,
        roleContent: providerContent,
        description: stray.adoption?.description,
    });
    const canonicalContent = renderCanonicalAgentMarkdown(imported);
    const canonicalAbsolutePath = resolve(scopeRoot, stray.mapping.canonicalDir, `${roleName}.md`);
    await ensureDir(dirname(canonicalAbsolutePath));
    const canonicalAlreadyExists = await pathExists(canonicalAbsolutePath);
    if (canonicalAlreadyExists) {
        const existing = await readFile(canonicalAbsolutePath, 'utf8');
        if (existing.trimEnd() !== canonicalContent.trimEnd()) {
            if (!options.replaceCanonical) {
                throw new CliError(`Cannot adopt ${toPosixPath(relative(scopeRoot, providerAbsolutePath))}: canonical path ${toPosixPath(relative(scopeRoot, canonicalAbsolutePath))} already exists with different content.`);
            }
        }
    }
    await writeFile(canonicalAbsolutePath, canonicalContent, 'utf8');
    return manifest;
}
