import fs from 'fs';
import { homedir } from 'os';
import path from 'path';
import { execSync } from 'child_process';
import { createHash } from 'crypto';
import { fileURLToPath } from 'url';
import { resolveNexusStateDir } from './runtime-registry.js';
const CLIENT_WORKSPACE_ENV_KEYS = [
    'NEXUS_WORKSPACE_ROOT',
    'CODEX_PROJECT_ROOT',
    'CODEX_WORKSPACE_ROOT',
    'INIT_CWD',
    'npm_config_local_prefix',
    'CLAUDE_PROJECT_DIR',
    'CURSOR_WORKSPACE_ROOT',
    'OPENCODE_WORKSPACE_ROOT',
    'WINDSURF_WORKSPACE_ROOT',
    'CONTINUE_WORKSPACE_ROOT',
    'CLINE_WORKSPACE_ROOT',
    'AIDER_WORKSPACE_ROOT',
];
function normalizeExistingPath(candidate) {
    const value = String(candidate ?? '').trim();
    if (!value)
        return null;
    return normalizePath(value, true);
}
function normalizePath(candidate, mustExist) {
    const resolved = path.resolve(candidate);
    if (mustExist && !fs.existsSync(resolved))
        return null;
    try {
        return fs.realpathSync.native(resolved);
    }
    catch {
        return mustExist ? (fs.existsSync(resolved) ? resolved : null) : resolved;
    }
}
function detectGitRoot(cwd) {
    try {
        const root = execSync('git rev-parse --show-toplevel', {
            cwd,
            encoding: 'utf8',
            stdio: ['ignore', 'pipe', 'ignore'],
            timeout: 2_000,
        }).trim();
        return normalizeExistingPath(root);
    }
    catch {
        return null;
    }
}
function detectGitRemote(cwd) {
    try {
        const remote = execSync('git config --get remote.origin.url', {
            cwd,
            encoding: 'utf8',
            stdio: ['ignore', 'pipe', 'ignore'],
            timeout: 2_000,
        }).trim();
        return remote || null;
    }
    catch {
        return null;
    }
}
function findNearestPackageRoot(startDir) {
    let current = path.resolve(startDir);
    let parent = path.dirname(current);
    for (;;) {
        if (fs.existsSync(path.join(current, 'package.json'))) {
            return current;
        }
        if (parent === current) {
            return null;
        }
        current = parent;
        parent = path.dirname(current);
    }
}
function isPathWithin(candidate, parent) {
    const relative = path.relative(parent, candidate);
    return relative === '' || (!relative.startsWith('..') && !path.isAbsolute(relative));
}
function isFilesystemRoot(candidate) {
    if (!candidate)
        return false;
    const resolved = path.resolve(candidate);
    return resolved === path.parse(resolved).root;
}
function resolveRepoName(workspaceRoot, remoteUrl) {
    const candidate = String(remoteUrl ?? '').trim();
    if (candidate) {
        const cleaned = candidate
            .replace(/\.git$/i, '')
            .replace(/[:/]+$/, '');
        const remoteParts = cleaned.split(/[:/]/).filter(Boolean);
        const last = remoteParts[remoteParts.length - 1];
        if (last) {
            return last;
        }
    }
    return path.basename(path.resolve(workspaceRoot)) || 'workspace';
}
const MODULE_DIR = path.dirname(fileURLToPath(import.meta.url));
const MODULE_PACKAGE_ROOT = findNearestPackageRoot(MODULE_DIR) ?? path.resolve(MODULE_DIR, '..', '..');
const CANONICAL_MODULE_PACKAGE_ROOT = normalizePath(MODULE_PACKAGE_ROOT, false) ?? path.resolve(MODULE_PACKAGE_ROOT);
function shouldPreferShellPwd(shellPwd, cwd, cwdGitRoot, shellPwdGitRoot) {
    const shellWorkspaceRoot = shellPwdGitRoot ?? shellPwd;
    if (isFilesystemRoot(shellWorkspaceRoot)) {
        return false;
    }
    if (shellWorkspaceRoot === cwd || shellPwd === cwd) {
        return true;
    }
    if (isPathWithin(cwd, CANONICAL_MODULE_PACKAGE_ROOT)) {
        return true;
    }
    if (cwdGitRoot) {
        return cwdGitRoot === shellWorkspaceRoot || cwdGitRoot === shellPwd;
    }
    return true;
}
function isPackageLikeWorkspaceHint(candidate) {
    return candidate === CANONICAL_MODULE_PACKAGE_ROOT || isPathWithin(candidate, CANONICAL_MODULE_PACKAGE_ROOT);
}
function resolveActiveWorkspaceOverride(hintedRoot, shellPwd, cwdGitRoot, shellPwdGitRoot) {
    if (!isPackageLikeWorkspaceHint(hintedRoot)) {
        return null;
    }
    const activeCandidates = [
        shellPwdGitRoot,
        shellPwd,
        cwdGitRoot,
    ]
        .map((candidate) => candidate ? (normalizePath(candidate, false) ?? path.resolve(candidate)) : null)
        .filter((candidate) => Boolean(candidate))
        .filter((candidate) => !isPackageLikeWorkspaceHint(candidate));
    const hintedGitRoot = detectGitRoot(hintedRoot);
    return activeCandidates.find((candidate) => candidate !== hintedRoot && candidate !== hintedGitRoot) ?? null;
}
export function resolveHomeCodexRoot() {
    return path.resolve(process.env.CODEX_HOME ?? path.join(homedir(), '.codex'));
}
export function createWorkspaceStateKey(workspaceRoot) {
    return createHash('sha1').update(path.resolve(workspaceRoot)).digest('hex').slice(0, 12);
}
export function resolveWorkspaceStateRoot(workspaceRoot) {
    return path.join(resolveNexusStateDir(), 'workspaces', createWorkspaceStateKey(workspaceRoot));
}
function getEngineStorageOverridePath(requestedRoot, engineName) {
    return path.join(resolveWorkspaceStateRoot(requestedRoot), 'engine-overrides', `${engineName}.json`);
}
export function readEngineStorageOverride(requestedRoot, engineName) {
    const overridePath = getEngineStorageOverridePath(requestedRoot, engineName);
    if (!fs.existsSync(overridePath)) {
        return null;
    }
    try {
        const parsed = JSON.parse(fs.readFileSync(overridePath, 'utf8'));
        if (parsed.engine !== engineName)
            return null;
        if (typeof parsed.requestedRoot !== 'string' || typeof parsed.storageRoot !== 'string' || typeof parsed.reason !== 'string') {
            return null;
        }
        return {
            engine: engineName,
            requestedRoot: path.resolve(parsed.requestedRoot),
            storageRoot: path.resolve(parsed.storageRoot),
            reason: parsed.reason,
            createdAt: typeof parsed.createdAt === 'number' ? parsed.createdAt : Date.now(),
            quarantinePath: typeof parsed.quarantinePath === 'string' ? parsed.quarantinePath : undefined,
            sourceDbPath: typeof parsed.sourceDbPath === 'string' ? parsed.sourceDbPath : undefined,
        };
    }
    catch {
        return null;
    }
}
export function writeEngineStorageOverride(input) {
    const overridePath = getEngineStorageOverridePath(input.requestedRoot, input.engine);
    fs.mkdirSync(path.dirname(overridePath), { recursive: true });
    fs.writeFileSync(overridePath, JSON.stringify({
        ...input,
        requestedRoot: path.resolve(input.requestedRoot),
        storageRoot: path.resolve(input.storageRoot),
    }, null, 2));
    return overridePath;
}
function canWriteDirectory(target) {
    try {
        fs.mkdirSync(target, { recursive: true });
        fs.accessSync(target, fs.constants.W_OK);
        return true;
    }
    catch {
        return false;
    }
}
export function resolveEngineStorageRoot(requestedRoot, engineName) {
    const normalizedRoot = path.resolve(requestedRoot);
    const override = readEngineStorageOverride(normalizedRoot, engineName);
    if (override) {
        const overrideRoot = path.resolve(override.storageRoot);
        const overrideDir = path.join(overrideRoot, `.${engineName}`);
        if (canWriteDirectory(overrideDir)) {
            return {
                requestedRoot: normalizedRoot,
                storageRoot: overrideRoot,
                fallbackApplied: overrideRoot !== normalizedRoot,
                fallbackReason: override.reason,
            };
        }
    }
    const localDir = path.join(normalizedRoot, `.${engineName}`);
    if (canWriteDirectory(localDir)) {
        return {
            requestedRoot: normalizedRoot,
            storageRoot: normalizedRoot,
            fallbackApplied: false,
        };
    }
    const fallbackRoot = resolveWorkspaceStateRoot(normalizedRoot);
    const fallbackDir = path.join(fallbackRoot, `.${engineName}`);
    if (canWriteDirectory(fallbackDir)) {
        return {
            requestedRoot: normalizedRoot,
            storageRoot: fallbackRoot,
            fallbackApplied: true,
            fallbackReason: `${engineName} storage moved to a writable Nexus state root because ${localDir} was not writable.`,
        };
    }
    return {
        requestedRoot: normalizedRoot,
        storageRoot: fallbackRoot,
        fallbackApplied: true,
        fallbackReason: `${engineName} storage is unavailable in both ${localDir} and ${fallbackDir}.`,
    };
}
export function resolveWorkspaceResolution(options = {}) {
    const env = options.env ?? process.env;
    const cwd = normalizePath(options.cwd ?? process.cwd(), false) ?? path.resolve(options.cwd ?? process.cwd());
    const shellPwd = normalizeExistingPath(env.PWD);
    const cwdGitRoot = detectGitRoot(cwd);
    const shellPwdGitRoot = shellPwd ? detectGitRoot(shellPwd) : null;
    const candidateRoots = [];
    const addCandidate = (candidate) => {
        if (candidate && !candidateRoots.includes(candidate)) {
            candidateRoots.push(candidate);
        }
    };
    addCandidate(cwd);
    addCandidate(cwdGitRoot);
    addCandidate(shellPwd);
    addCandidate(shellPwdGitRoot);
    const explicitRootValue = String(options.workspaceRoot ?? '').trim();
    const explicitRoot = explicitRootValue ? (normalizePath(explicitRootValue, false) ?? path.resolve(explicitRootValue)) : null;
    if (explicitRoot) {
        addCandidate(explicitRoot);
        const explicitGitRoot = detectGitRoot(explicitRoot) ?? undefined;
        addCandidate(explicitGitRoot);
        const ignoreExplicitRoot = isFilesystemRoot(explicitRoot);
        if (!ignoreExplicitRoot && !resolveActiveWorkspaceOverride(explicitRoot, shellPwd, cwdGitRoot, shellPwdGitRoot)) {
            return {
                workspaceRoot: explicitRoot,
                cwd,
                source: 'explicit',
                gitRoot: explicitGitRoot,
                candidateRoots,
            };
        }
    }
    for (const key of CLIENT_WORKSPACE_ENV_KEYS) {
        const candidate = normalizeExistingPath(env[key]);
        if (!candidate)
            continue;
        addCandidate(candidate);
        const candidateGitRoot = detectGitRoot(candidate) ?? undefined;
        addCandidate(candidateGitRoot);
        if (isFilesystemRoot(candidate)) {
            continue;
        }
        if (resolveActiveWorkspaceOverride(candidate, shellPwd, cwdGitRoot, shellPwdGitRoot)) {
            continue;
        }
        if (isPackageLikeWorkspaceHint(candidate)) {
            continue;
        }
        return {
            workspaceRoot: candidate,
            cwd,
            source: 'client-env',
            gitRoot: candidateGitRoot,
            candidateRoots,
        };
    }
    if (shellPwd && !isFilesystemRoot(shellPwd)) {
        if (shouldPreferShellPwd(shellPwd, cwd, cwdGitRoot, shellPwdGitRoot ?? null)) {
            return {
                workspaceRoot: shellPwdGitRoot ?? shellPwd,
                cwd,
                source: 'pwd',
                gitRoot: shellPwdGitRoot,
                candidateRoots,
            };
        }
    }
    if (cwdGitRoot) {
        return {
            workspaceRoot: cwdGitRoot,
            cwd,
            source: 'git',
            gitRoot: cwdGitRoot,
            candidateRoots,
        };
    }
    const packageRoot = MODULE_PACKAGE_ROOT && fs.existsSync(MODULE_PACKAGE_ROOT) ? MODULE_PACKAGE_ROOT : null;
    if (packageRoot) {
        addCandidate(packageRoot);
        return {
            workspaceRoot: packageRoot,
            cwd,
            source: 'package',
            gitRoot: detectGitRoot(packageRoot) ?? undefined,
            candidateRoots,
        };
    }
    return {
        workspaceRoot: cwd,
        cwd,
        source: 'cwd',
        candidateRoots,
    };
}
export function resolveWorkspaceContext(options = {}) {
    const resolution = resolveWorkspaceResolution(options);
    const repoRoot = resolution.gitRoot ?? resolution.workspaceRoot;
    const remoteUrl = detectGitRemote(repoRoot) ?? detectGitRemote(resolution.workspaceRoot) ?? undefined;
    const repoName = resolveRepoName(repoRoot, remoteUrl);
    const stateRoot = resolveWorkspaceStateRoot(repoRoot);
    const notes = [];
    if (resolution.source === 'cwd' && !resolution.gitRoot) {
        notes.push('Workspace resolved from the current working directory because no explicit or git root was available.');
    }
    if (resolution.source === 'client-env') {
        notes.push('Workspace resolved from client-provided environment metadata.');
    }
    if (resolution.source === 'pwd') {
        notes.push('Workspace resolved from shell PWD before falling back to the installed package root.');
    }
    if (resolution.source === 'git') {
        notes.push('Workspace resolved from the enclosing git repository root.');
    }
    if (resolution.source === 'package') {
        notes.push('Workspace resolved from the installed package root because no git root or client workspace metadata was available.');
    }
    if ((resolution.source === 'pwd' || resolution.source === 'git' || resolution.source === 'cwd')
        && resolution.candidateRoots.some((candidate) => isPackageLikeWorkspaceHint(candidate))
        && resolution.candidateRoots.some((candidate) => !isPackageLikeWorkspaceHint(candidate))) {
        notes.push('Ignored a stale installed-package workspace hint because an active project context was detected.');
    }
    return {
        ...resolution,
        repoRoot,
        repoName,
        remoteUrl,
        workspaceSource: resolution.source,
        stateRoot,
        stateKey: createWorkspaceStateKey(repoRoot),
        notes,
    };
}
export function resolveWorkspaceRoot(options = {}) {
    return resolveWorkspaceContext(options).workspaceRoot;
}
