import { mkdir, rm, writeFile } from 'node:fs/promises';
import { dirname, join, relative, resolve } from 'node:path';
import { copyDirectory, copySingleFile, createSymlink } from '../fs/io.js';
import { computeContentHash, computeStringHash } from '../manifest/hash.js';
import { addEntry, findEntry, removeEntry, saveManifest, } from '../manifest/manager.js';
import { insertMarker, writeDirectorySentinel } from './markers.js';
export function inferScopeRoot(canonicalPath) {
    const normalizedPath = canonicalPath.replaceAll('\\', '/');
    const marker = '/.agents/';
    const markerIndex = normalizedPath.indexOf(marker);
    if (markerIndex === -1) {
        throw new Error(`Cannot infer scope root from canonical path: ${canonicalPath}`);
    }
    return resolve(normalizedPath.slice(0, markerIndex));
}
function resolveManifestPaths(entry) {
    const scopeRoot = inferScopeRoot(resolve(entry.canonical.canonicalPath));
    return {
        canonicalPath: relative(scopeRoot, resolve(entry.canonical.canonicalPath)),
        providerPath: relative(scopeRoot, resolve(entry.providerPath)),
    };
}
async function toManifestEntry(entry, strategy) {
    const { canonicalPath, providerPath } = resolveManifestPaths(entry);
    const contentHash = strategy === 'copy'
        ? entry.renderedContent !== undefined
            ? computeStringHash(entry.renderedContent)
            : await computeContentHash(resolve(entry.canonical.canonicalPath), entry.canonical.isFile)
        : null;
    return {
        canonicalPath,
        providerPath,
        provider: entry.provider,
        contentType: entry.canonical.type,
        strategy,
        contentHash,
        isFile: entry.canonical.isFile,
        lastSynced: new Date().toISOString(),
    };
}
function markerFileNameForEntry(entry) {
    if (entry.canonical.isFile) {
        throw new Error('Directory marker filenames are only valid for copied directory entries.');
    }
    // Rules are file-based, so only agent and skill directory copies reach here.
    return entry.canonical.type === 'agent' ? 'AGENT.md' : 'SKILL.md';
}
async function applyCopyMarker(entry) {
    const markerPath = join(entry.providerPath, markerFileNameForEntry(entry));
    try {
        await writeDirectorySentinel(entry.providerPath, entry.canonical.canonicalPath);
        await insertMarker(markerPath, entry.canonical.canonicalPath);
    }
    catch (error) {
        if (typeof error === 'object' &&
            error !== null &&
            'code' in error &&
            error.code === 'ENOENT') {
            // Marker insertion is best-effort for non-standard directory layouts.
            return;
        }
        throw error;
    }
}
async function applyEntry(planEntry, manifest) {
    switch (planEntry.operation) {
        case 'create_symlink':
        case 'update_symlink': {
            if (planEntry.operation === 'update_symlink') {
                await rm(planEntry.providerPath, { recursive: true, force: true });
            }
            const strategyUsed = await createSymlink(planEntry.canonical.canonicalPath, planEntry.providerPath, undefined, planEntry.canonical.isFile);
            const manifestEntry = await toManifestEntry(planEntry, strategyUsed);
            return addEntry(manifest, manifestEntry);
        }
        case 'create_copy':
        case 'update_copy': {
            if (planEntry.operation === 'update_copy') {
                await rm(planEntry.providerPath, { recursive: true, force: true });
            }
            if (planEntry.canonical.isFile &&
                planEntry.renderedContent !== undefined) {
                await mkdir(dirname(planEntry.providerPath), { recursive: true });
                await writeFile(planEntry.providerPath, planEntry.renderedContent, 'utf8');
            }
            else if (planEntry.canonical.isFile) {
                await copySingleFile(planEntry.canonical.canonicalPath, planEntry.providerPath);
            }
            else {
                await copyDirectory(planEntry.canonical.canonicalPath, planEntry.providerPath);
                await applyCopyMarker(planEntry);
            }
            const manifestEntry = await toManifestEntry(planEntry, 'copy');
            return addEntry(manifest, manifestEntry);
        }
        case 'remove': {
            await rm(planEntry.providerPath, { recursive: true, force: true });
            const { canonicalPath } = resolveManifestPaths(planEntry);
            return removeEntry(manifest, canonicalPath, planEntry.provider);
        }
        case 'detach': {
            const { canonicalPath } = resolveManifestPaths(planEntry);
            return removeEntry(manifest, canonicalPath, planEntry.provider);
        }
        case 'skip': {
            return manifest;
        }
        default:
            return manifest;
    }
}
async function ensureSkipEntryManaged(planEntry, manifest) {
    const { canonicalPath } = resolveManifestPaths(planEntry);
    const existing = findEntry(manifest, canonicalPath, planEntry.provider);
    if (existing) {
        return manifest;
    }
    const manifestEntry = await toManifestEntry(planEntry, planEntry.strategy);
    return addEntry(manifest, manifestEntry);
}
export async function executeSyncPlan(plan, manifest, manifestPath) {
    let nextManifest = manifest;
    const result = {
        applied: 0,
        failed: 0,
        skipped: 0,
    };
    const operations = [...plan.entries, ...plan.removals];
    for (const operation of operations) {
        if (operation.operation === 'skip') {
            nextManifest = await ensureSkipEntryManaged(operation, nextManifest);
            result.skipped += 1;
            continue;
        }
        try {
            nextManifest = await applyEntry(operation, nextManifest);
            result.applied += 1;
        }
        catch {
            result.failed += 1;
        }
    }
    await saveManifest(manifestPath, nextManifest);
    return result;
}
