import { accessSync, constants, existsSync, lstatSync, mkdirSync, readFileSync, statSync, writeFileSync } from 'fs';
import { execSync } from 'child_process';
import { homedir } from 'os';
import { dirname, join, resolve } from 'path';
import { InstructionGateway } from './instruction-gateway.js';
import { MemoryEngine } from './memory.js';
import { buildNexusMcpServerConfig, isStableNexusMcpServerConfig } from './mcp-entrypoint.js';
import { resolveHomeCodexRoot, resolveWorkspaceContext } from './workspace-resolver.js';
const WELCOME_MEMORY_MESSAGE = "Welcome to Nexus Prime! Nexus Prime is fully installed and tracking context for your workspace. Use '/nexus' to ask me to analyze the codebase, run agents, and automate tasks.";
function buildWelcomeMemoryTags(workspaceStateKey) {
    return [
        '#system',
        '#system-hidden',
        '#welcome',
        '#onboard',
        '#system-bootstrap',
        `#bootstrap-welcome:${workspaceStateKey}`,
    ];
}
const CODEX_MANAGED_START = '<!-- nexus-prime:codex-bootstrap:start -->';
const CODEX_MANAGED_END = '<!-- nexus-prime:codex-bootstrap:end -->';
const SUPPORTED_CLIENTS = ['codex', 'cursor', 'claude', 'claude-code', 'claude-desktop', 'opencode', 'windsurf', 'antigravity', 'openclaw', 'hermes', 'nanoclaw', 'picoclaw', 'aider', 'continue', 'cline'];
const HOME_SKILL_BUNDLE_DIRS = {
    antigravity: join(homedir(), '.antigravity', 'skills', 'nexus-prime'),
    openclaw: join(homedir(), '.openclaw', 'skills', 'nexus-prime'),
    hermes: join(homedir(), '.hermes', 'skills', 'nexus-prime'),
    nanoclaw: join(homedir(), '.nanoclaw', 'skills', 'nexus-prime'),
    picoclaw: join(homedir(), '.picoclaw', 'skills', 'nexus-prime'),
};
const WORKSPACE_SEED_FILES = [
    {
        relativePath: '.agents/README.md',
        content: `# Repo-local Agent Assets

Use \`.agents/skills\` and \`.agents/workflows\` for repo-local skill and workflow packs.
Nexus Prime still reads the legacy \`.agent/skills\` and \`.agent/workflows\` directories for backward compatibility.
`,
    },
    {
        relativePath: '.agents/hooks/before-mutate-guard.md',
        content: `---
name: before-mutate-guard
description: Preferred repo-local checkpoint before bounded file mutation.
trigger: before-mutate
riskClass: read
domain: orchestration
---

Project-local hook seed. Customize this file to document extra mutation rules for this repository.
`,
    },
    {
        relativePath: '.agents/hooks/failure-summary.md',
        content: `---
name: failure-summary
description: Preferred repo-local failure checkpoint for recovery notes and retry scope.
trigger: run.failed
riskClass: read
domain: workflows
---

Project-local hook seed. Customize this file to capture repo-specific failure recovery notes.
`,
    },
    {
        relativePath: '.agents/hooks/memory-stored-review.md',
        content: `---
name: memory-stored-review
description: Preferred repo-local review hook for important stored memories.
trigger: memory.stored
riskClass: read
domain: memory
---

Project-local hook seed. Customize this file when memory writes need repo-specific review rules.
`,
    },
    {
        relativePath: '.agents/automations/session-close-followup.md',
        content: `---
name: session-close-followup
description: Queue a bounded approval follow-up after a verified run.
triggerMode: event
eventTrigger: run.verified
domain: workflows
workflows:
  - workflows-approval-loop
---

Project-local automation seed. Customize this file to add repo-specific post-verify follow-up steps.
`,
    },
    {
        relativePath: '.agents/automations/failure-followup.md',
        content: `---
name: failure-followup
description: Queue a bounded retry follow-up after a failed run.
triggerMode: event
eventTrigger: run.failed
domain: orchestration
hooks:
  - retry-narrow-scope
---

Project-local automation seed. Customize this file to add repo-specific failure recovery behavior.
`,
    },
    {
        relativePath: '.agent/hooks/before-mutate-guard.md',
        content: `---
name: before-mutate-guard
description: Local checkpoint before bounded file mutation.
trigger: before-mutate
riskClass: read
domain: orchestration
---

Project-local hook seed. Customize this file to document extra mutation rules for this repository.
`,
    },
    {
        relativePath: '.agent/hooks/failure-summary.md',
        content: `---
name: failure-summary
description: Local failure checkpoint for recovery notes and retry scope.
trigger: run.failed
riskClass: read
domain: workflows
---

Project-local hook seed. Customize this file to capture repo-specific failure recovery notes.
`,
    },
    {
        relativePath: '.agent/hooks/memory-stored-review.md',
        content: `---
name: memory-stored-review
description: Local review hook for important stored memories.
trigger: memory.stored
riskClass: read
domain: memory
---

Project-local hook seed. Customize this file when memory writes need repo-specific review rules.
`,
    },
    {
        relativePath: '.agent/automations/session-close-followup.md',
        content: `---
name: session-close-followup
description: Queue a bounded approval follow-up after a verified run.
triggerMode: event
eventTrigger: run.verified
domain: workflows
workflows:
  - workflows-approval-loop
---

Project-local automation seed. Customize this file to add repo-specific post-verify follow-up steps.
`,
    },
    {
        relativePath: '.agent/automations/failure-followup.md',
        content: `---
name: failure-followup
description: Queue a bounded retry follow-up after a failed run.
triggerMode: event
eventTrigger: run.failed
domain: orchestration
hooks:
  - retry-narrow-scope
---

Project-local automation seed. Customize this file to add repo-specific failure recovery behavior.
`,
    },
];
function ensureParentDir(targetPath) {
    mkdirSync(dirname(targetPath), { recursive: true });
}
export function validateTargetPath(targetPath) {
    const parent = dirname(targetPath);
    try {
        if (!existsSync(parent)) {
            // Parent will be created by ensureParentDir — check grandparent is writable
            const grandparent = dirname(parent);
            if (existsSync(grandparent)) {
                accessSync(grandparent, constants.W_OK);
            }
            return { valid: true };
        }
        // Check parent is actually a directory and writable
        const stat = lstatSync(parent);
        if (!stat.isDirectory()) {
            return { valid: false, reason: `Parent path is not a directory: ${parent}` };
        }
        accessSync(parent, constants.W_OK);
        return { valid: true };
    }
    catch (err) {
        const message = err instanceof Error ? err.message : String(err);
        return { valid: false, reason: `Cannot write to ${parent}: ${message}` };
    }
}
function resolveClaudeCodeConfigDir() {
    // XDG-aware resolution: check XDG_CONFIG_HOME, then ~/.config/claude/, then ~/.claude/
    const xdgConfig = process.env.XDG_CONFIG_HOME;
    if (xdgConfig) {
        const xdgClaudePath = join(xdgConfig, 'claude');
        if (existsSync(xdgClaudePath))
            return xdgClaudePath;
    }
    const configClaudePath = join(homedir(), '.config', 'claude');
    if (existsSync(configClaudePath))
        return configClaudePath;
    return join(homedir(), '.claude');
}
function readJson(targetPath) {
    if (!existsSync(targetPath))
        return {};
    try {
        return JSON.parse(readFileSync(targetPath, 'utf8'));
    }
    catch {
        return {};
    }
}
function buildStandardMcpServerConfig(workspaceRoot) {
    return buildNexusMcpServerConfig(workspaceRoot);
}
function formatTomlString(value) {
    return JSON.stringify(value);
}
function formatTomlStringArray(values) {
    return `[${values.map((value) => formatTomlString(value)).join(', ')}]`;
}
function getTomlSection(doc, tableName) {
    const lines = doc.split(/\r?\n/);
    let activeSection = null;
    const collected = [];
    for (const line of lines) {
        const header = line.match(/^\s*\[([^\]]+)\]\s*$/);
        if (header) {
            activeSection = header[1].trim();
            continue;
        }
        if (activeSection === tableName) {
            collected.push(line);
        }
    }
    return collected.length > 0 ? collected.join('\n') : null;
}
function upsertTomlSection(doc, tableName, content) {
    const lines = doc.split(/\r?\n/);
    const kept = [];
    for (let index = 0; index < lines.length; index += 1) {
        const header = lines[index].match(/^\s*\[([^\]]+)\]\s*$/);
        if (!header) {
            kept.push(lines[index]);
            continue;
        }
        const sectionName = header[1].trim();
        if (sectionName !== tableName && !sectionName.startsWith(`${tableName}.`)) {
            kept.push(lines[index]);
            continue;
        }
        index += 1;
        while (index < lines.length) {
            const nextHeader = lines[index].match(/^\s*\[([^\]]+)\]\s*$/);
            if (nextHeader) {
                const nextName = nextHeader[1].trim();
                if (nextName !== tableName && !nextName.startsWith(`${tableName}.`)) {
                    index -= 1;
                    break;
                }
            }
            index += 1;
        }
    }
    const trimmed = kept.join('\n').replace(/\s+$/, '');
    return trimmed ? `${trimmed}\n\n${content.trim()}\n` : `${content.trim()}\n`;
}
function removeTomlSection(doc, tableName) {
    const lines = doc.split(/\r?\n/);
    const kept = [];
    for (let index = 0; index < lines.length; index += 1) {
        const header = lines[index].match(/^\s*\[([^\]]+)\]\s*$/);
        if (!header) {
            kept.push(lines[index]);
            continue;
        }
        const sectionName = header[1].trim();
        if (sectionName !== tableName && !sectionName.startsWith(`${tableName}.`)) {
            kept.push(lines[index]);
            continue;
        }
        index += 1;
        while (index < lines.length) {
            const nextHeader = lines[index].match(/^\s*\[([^\]]+)\]\s*$/);
            if (nextHeader) {
                const nextName = nextHeader[1].trim();
                if (nextName !== tableName && !nextName.startsWith(`${tableName}.`)) {
                    index -= 1;
                    break;
                }
            }
            index += 1;
        }
    }
    return kept.join('\n').replace(/\n{3,}/g, '\n\n').replace(/\s+$/, '') + '\n';
}
function codexTomlHasAutonomousProfile(doc) {
    const mcpSection = getTomlSection(doc, 'mcp_servers.nexus-prime');
    const envSection = getTomlSection(doc, 'mcp_servers.nexus-prime.env');
    if (!mcpSection)
        return false;
    if (codexTomlExposesInternalGraphPeer(doc))
        return false;
    const commandOk = !/(?:^|\n)\s*command\s*=\s*"(?:npx|nexus-prime)"\s*(?:\n|$)/.test(mcpSection);
    const argsOk = /(?:^|\n)\s*args\s*=\s*\[[^\]]*["'][^"']*cli\.js["'][^\]]*"mcp"[^\]]*\]\s*(?:\n|$)/s.test(mcpSection);
    const envOk = Boolean(envSection && /(?:^|\n)\s*NEXUS_MCP_TOOL_PROFILE\s*=\s*"autonomous"\s*(?:\n|$)/.test(envSection));
    const noStartupRewrite = Boolean(envSection && /(?:^|\n)\s*NEXUS_MCP_AUTO_CONFIG\s*=\s*"0"\s*(?:\n|$)/.test(envSection));
    const fullDnaOnDisconnect = Boolean(envSection && /(?:^|\n)\s*NEXUS_MCP_FULL_DNA_ON_DISCONNECT\s*=\s*"1"\s*(?:\n|$)/.test(envSection));
    return commandOk && argsOk && envOk && noStartupRewrite && fullDnaOnDisconnect;
}
function codexTomlExposesInternalGraphPeer(doc) {
    if (getTomlSection(doc, 'mcp_servers.code-review-graph'))
        return true;
    const atlasSection = getTomlSection(doc, 'mcp_servers.atlas');
    if (!atlasSection)
        return false;
    return /(?:^|\n)\s*command\s*=\s*"goatlas"\s*(?:\n|$)/.test(atlasSection)
        && /(?:^|\n)\s*args\s*=\s*\[[^\]]*"mcp"[^\]]*\]\s*(?:\n|$)/s.test(atlasSection);
}
export function resolveCodexConfigPath() {
    const codexHome = resolveHomeCodexRoot();
    const tomlPath = join(codexHome, 'config.toml');
    const jsonPath = join(codexHome, 'config.json');
    if (existsSync(tomlPath))
        return tomlPath;
    if (existsSync(jsonPath))
        return jsonPath;
    return tomlPath;
}
export function renderCodexMcpTomlConfig() {
    const server = buildNexusMcpServerConfig();
    return [
        '[mcp_servers.nexus-prime]',
        `command = ${formatTomlString(server.command)}`,
        `args = ${formatTomlStringArray(server.args)}`,
        '',
        '[mcp_servers.nexus-prime.env]',
        ...Object.entries(server.env).map(([key, value]) => `${key} = ${formatTomlString(value)}`),
    ].join('\n');
}
export function writeCodexMcpConfig(targetPath) {
    const existing = existsSync(targetPath) ? readFileSync(targetPath, 'utf8') : '';
    let merged = upsertTomlSection(existing, 'mcp_servers.nexus-prime', renderCodexMcpTomlConfig());
    merged = removeTomlSection(merged, 'mcp_servers.code-review-graph');
    if (codexTomlExposesInternalGraphPeer(merged)) {
        merged = removeTomlSection(merged, 'mcp_servers.atlas');
    }
    ensureParentDir(targetPath);
    writeFileSync(targetPath, merged, 'utf8');
}
export function hasExpectedCodexConfig(targetPath) {
    if (!existsSync(targetPath))
        return false;
    if (targetPath.endsWith('.json')) {
        const parsed = readJson(targetPath);
        const server = parsed?.mcpServers?.['nexus-prime'];
        return Boolean(server
            && isStableNexusMcpServerConfig(server)
            && !jsonExposesInternalGraphPeer(parsed));
    }
    try {
        return codexTomlHasAutonomousProfile(readFileSync(targetPath, 'utf8'));
    }
    catch {
        return false;
    }
}
function jsonExposesInternalGraphPeer(parsed) {
    const servers = parsed?.mcpServers ?? parsed?.mcp ?? {};
    return Boolean(servers?.['code-review-graph']
        || isNexusManagedGraphPeer(servers?.['atlas']));
}
function writeStandardMcpConfig(targetPath, workspaceRoot) {
    const existing = readJson(targetPath);
    existing.mcpServers = existing.mcpServers ?? {};
    existing.mcpServers['nexus-prime'] = buildStandardMcpServerConfig(workspaceRoot);
    delete existing.mcpServers['code-review-graph'];
    if (isNexusManagedGraphPeer(existing.mcpServers['atlas'])) {
        delete existing.mcpServers['atlas'];
    }
    ensureParentDir(targetPath);
    writeFileSync(targetPath, JSON.stringify(existing, null, 2));
}
function isNexusManagedGraphPeer(server) {
    if (!server || typeof server !== 'object' || Array.isArray(server))
        return false;
    const record = server;
    const args = Array.isArray(record['args']) ? record['args'].map(String) : [];
    return record['command'] === 'goatlas' && args.length === 1 && args[0] === 'mcp';
}
function writeOpencodeConfig(targetPath, workspaceRoot) {
    const existing = readJson(targetPath);
    const mcpConfig = buildStandardMcpServerConfig(workspaceRoot);
    const server = {
        type: 'local',
        command: mcpConfig.command,
        args: mcpConfig.args,
        environment: {
            ...mcpConfig.env,
        },
    };
    existing.mcp = existing.mcp ?? {};
    existing.mcp['nexus-prime'] = server;
    delete existing.mcp.servers;
    ensureParentDir(targetPath);
    writeFileSync(targetPath, JSON.stringify(existing, null, 2));
}
function renderCodexManagedBlock(content) {
    return [
        CODEX_MANAGED_START,
        '## Nexus Prime Bootstrap (managed)',
        '',
        '> This block is managed by `nexus-prime setup codex` or automatic bootstrap.',
        '> Keep your project-specific Codex guidance above or below it.',
        '',
        content.trim(),
        CODEX_MANAGED_END,
    ].join('\n');
}
function ensureFinalNewline(content) {
    return content.endsWith('\n') ? content : `${content}\n`;
}
function mergeCodexAgentsContent(existingContent, content) {
    const managedBlock = renderCodexManagedBlock(content);
    const existing = existingContent ?? '';
    if (!existing.trim()) {
        return [
            '# AGENTS.md',
            '',
            'This file is used by Codex and other repo-local agent tooling.',
            '',
            managedBlock,
            '',
        ].join('\n');
    }
    const startIndex = existing.indexOf(CODEX_MANAGED_START);
    const endIndex = existing.indexOf(CODEX_MANAGED_END);
    if (startIndex >= 0 && endIndex > startIndex) {
        const before = existing.slice(0, startIndex).trimEnd();
        const after = existing.slice(endIndex + CODEX_MANAGED_END.length).trim();
        return ensureFinalNewline([
            before,
            before ? '' : undefined,
            managedBlock,
            after ? '' : undefined,
            after,
        ].filter((value) => value !== undefined).join('\n'));
    }
    return ensureFinalNewline([
        existing.trimEnd(),
        '',
        managedBlock,
    ].join('\n'));
}
function hasCurrentCodexManagedBlock(targetPath, content) {
    if (!existsSync(targetPath))
        return 'missing';
    const existing = readFileSync(targetPath, 'utf8');
    const startIndex = existing.indexOf(CODEX_MANAGED_START);
    const endIndex = existing.indexOf(CODEX_MANAGED_END);
    if (startIndex < 0 || endIndex <= startIndex)
        return 'drifted';
    const currentBlock = existing.slice(startIndex, endIndex + CODEX_MANAGED_END.length).trim();
    return currentBlock === renderCodexManagedBlock(content).trim() ? 'installed' : 'drifted';
}
function buildInstructionFiles(clientId, packageRoot, workspaceRoot) {
    const gateway = new InstructionGateway(packageRoot);
    const resolvedClientId = clientId === 'claude' || clientId === 'claude-desktop' ? 'claude-code' : clientId;
    const bundle = gateway.renderClientBootstrapBundle(resolvedClientId, {
        toolProfile: 'autonomous',
    });
    if (clientId === 'codex') {
        return bundle.artifacts.map((artifact) => ({
            path: join(workspaceRoot, 'AGENTS.md'),
            content: artifact.content,
            mode: 'codex-managed-agents',
            scope: 'workspace',
        }));
    }
    if (clientId === 'cursor') {
        return bundle.artifacts.map((artifact) => ({
            path: join(workspaceRoot, '.cursor', 'rules', artifact.fileName),
            content: artifact.content,
            scope: 'workspace',
        }));
    }
    if (clientId === 'windsurf') {
        return bundle.artifacts.map((artifact) => ({
            path: join(workspaceRoot, artifact.fileName),
            content: artifact.content,
            scope: 'workspace',
        }));
    }
    const homeSkillDir = HOME_SKILL_BUNDLE_DIRS[clientId];
    if (homeSkillDir) {
        return bundle.artifacts.map((artifact) => ({
            path: join(homeSkillDir, artifact.fileName),
            content: artifact.content,
            scope: 'home',
        }));
    }
    if (clientId === 'aider') {
        return bundle.artifacts.map((artifact) => ({
            path: join(workspaceRoot, '.aider', artifact.fileName),
            content: artifact.content,
            scope: 'workspace',
        }));
    }
    if (clientId === 'continue') {
        return bundle.artifacts.map((artifact) => ({
            path: join(workspaceRoot, '.continue', artifact.fileName),
            content: artifact.content,
            scope: 'workspace',
        }));
    }
    if (clientId === 'cline') {
        return bundle.artifacts.map((artifact) => ({
            path: join(workspaceRoot, '.cline', artifact.fileName),
            content: artifact.content,
            scope: 'workspace',
        }));
    }
    const fileName = clientId === 'claude' || clientId === 'claude-code' || clientId === 'claude-desktop' ? 'claude-code.md' : 'opencode.md';
    return bundle.artifacts.map((artifact, index) => ({
        path: join(workspaceRoot, '.agent', 'client-bootstrap', index === 0 ? fileName : `${fileName.replace(/\.md$/, '')}-${index + 1}.md`),
        content: artifact.content,
        scope: 'workspace',
    }));
}
export function getSetupDefinition(clientId, options) {
    const workspaceRoot = resolveWorkspaceContext({ workspaceRoot: options.workspaceRoot }).workspaceRoot;
    const instructionFiles = buildInstructionFiles(clientId, resolve(options.packageRoot), workspaceRoot);
    if (clientId === 'codex') {
        return {
            id: clientId,
            label: 'Codex',
            workspaceRoot,
            configPath: resolveCodexConfigPath(),
            instructionFiles,
        };
    }
    if (clientId === 'cursor') {
        const workspacePath = workspaceRoot;
        return {
            id: clientId,
            label: 'Cursor',
            workspaceRoot,
            configPath: join(workspacePath, '.cursor', 'mcp.json'),
            instructionFiles,
        };
    }
    if (clientId === 'claude' || clientId === 'claude-code') {
        return {
            id: clientId,
            label: 'Claude Code',
            workspaceRoot,
            configPath: join(workspaceRoot, '.mcp.json'),
            instructionFiles,
        };
    }
    if (clientId === 'claude-desktop') {
        const claudeDesktopPath = process.platform === 'darwin'
            ? join(homedir(), 'Library', 'Application Support', 'Claude', 'claude_desktop_config.json')
            : process.platform === 'win32'
                ? join(process.env.APPDATA || '', 'Claude', 'claude_desktop_config.json')
                : join(resolveClaudeCodeConfigDir(), 'claude_desktop_config.json');
        return {
            id: clientId,
            label: 'Claude Desktop',
            workspaceRoot,
            configPath: claudeDesktopPath,
            instructionFiles,
        };
    }
    if (clientId === 'opencode') {
        return {
            id: clientId,
            label: 'Opencode',
            workspaceRoot,
            configPath: join(homedir(), '.config', 'opencode', 'opencode.json'),
            instructionFiles,
        };
    }
    if (clientId === 'windsurf') {
        return {
            id: clientId,
            label: 'Windsurf',
            workspaceRoot,
            configPath: join(homedir(), '.windsurf', 'mcp.json'),
            instructionFiles,
        };
    }
    if (clientId === 'aider') {
        return {
            id: clientId,
            label: 'Aider',
            workspaceRoot,
            configPath: join(homedir(), '.aider', 'mcp.json'),
            instructionFiles,
        };
    }
    if (clientId === 'continue') {
        return {
            id: clientId,
            label: 'Continue.dev',
            workspaceRoot,
            configPath: join(homedir(), '.continue', 'config.json'),
            instructionFiles,
        };
    }
    if (clientId === 'cline') {
        return {
            id: clientId,
            label: 'Cline',
            workspaceRoot,
            configPath: join(homedir(), '.vscode', 'cline-mcp.json'),
            instructionFiles,
        };
    }
    if (clientId === 'openclaw') {
        return {
            id: clientId,
            label: 'OpenClaw',
            workspaceRoot,
            configPath: join(homedir(), '.openclaw', 'openclaw.json'),
            instructionFiles,
        };
    }
    if (clientId === 'hermes') {
        return {
            id: clientId,
            label: 'Hermes',
            workspaceRoot,
            configPath: join(homedir(), '.hermes', 'mcp.json'),
            instructionFiles,
        };
    }
    if (clientId === 'nanoclaw') {
        return {
            id: clientId,
            label: 'NanoClaw',
            workspaceRoot,
            configPath: join(homedir(), '.nanoclaw', 'mcp.json'),
            instructionFiles,
        };
    }
    if (clientId === 'picoclaw') {
        return {
            id: clientId,
            label: 'PicoClaw',
            workspaceRoot,
            configPath: join(homedir(), '.picoclaw', 'mcp.json'),
            instructionFiles,
        };
    }
    return {
        id: clientId,
        label: 'Antigravity',
        workspaceRoot,
        configPath: join(homedir(), '.antigravity', 'mcp.json'),
        instructionFiles,
    };
}
export function installSetup(definition, options = {}) {
    const scope = options.scope ?? 'all';
    if (definition.configPath && scope !== 'workspace') {
        const configCheck = validateTargetPath(definition.configPath);
        if (!configCheck.valid) {
            if (!options.silent) {
                console.warn(`[nexus-prime] Skipping config for ${definition.label}: ${configCheck.reason}`);
            }
        }
        else if (definition.id === 'codex' && definition.configPath.endsWith('.toml')) {
            writeCodexMcpConfig(definition.configPath);
        }
        else if (definition.id === 'opencode') {
            writeOpencodeConfig(definition.configPath, options.workspaceRoot);
        }
        else {
            writeStandardMcpConfig(definition.configPath, options.workspaceRoot);
        }
    }
    for (const file of definition.instructionFiles) {
        if (scope === 'home' && file.scope !== 'home')
            continue;
        if (scope === 'workspace' && file.scope !== 'workspace')
            continue;
        const pathCheck = validateTargetPath(file.path);
        if (!pathCheck.valid) {
            if (!options.silent) {
                console.warn(`[nexus-prime] Skipping instruction file ${file.path}: ${pathCheck.reason}`);
            }
            continue;
        }
        ensureParentDir(file.path);
        if (file.mode === 'codex-managed-agents') {
            const existing = existsSync(file.path) ? readFileSync(file.path, 'utf8') : null;
            const merged = mergeCodexAgentsContent(existing, file.content);
            if (existing !== merged) {
                writeFileSync(file.path, merged, 'utf8');
            }
            continue;
        }
        // Skip write if content is already current
        if (existsSync(file.path) && readFileSync(file.path, 'utf8') === file.content) {
            continue;
        }
        writeFileSync(file.path, file.content, 'utf8');
    }
}
export function hasExpectedConfig(definition) {
    if (!definition.configPath || !existsSync(definition.configPath))
        return false;
    if (definition.id === 'codex') {
        return hasExpectedCodexConfig(definition.configPath);
    }
    try {
        const parsed = JSON.parse(readFileSync(definition.configPath, 'utf8'));
        if (jsonExposesInternalGraphPeer(parsed))
            return false;
        if (definition.id === 'opencode') {
            const server = parsed?.mcp?.['nexus-prime'];
            return Boolean(server
                && isStableNexusMcpServerConfig(server, 'environment', definition.workspaceRoot));
        }
        const server = parsed?.mcpServers?.['nexus-prime'];
        return Boolean(server
            && isStableNexusMcpServerConfig(server, 'env', definition.workspaceRoot));
    }
    catch {
        return false;
    }
}
function instructionState(definition, scope) {
    let hasAny = false;
    for (const file of definition.instructionFiles) {
        if (scope && file.scope !== scope)
            continue;
        if (file.mode === 'codex-managed-agents') {
            const codexState = hasCurrentCodexManagedBlock(file.path, file.content);
            if (codexState === 'drifted')
                return 'drifted';
            if (codexState === 'installed')
                hasAny = true;
            continue;
        }
        if (!existsSync(file.path))
            continue;
        hasAny = true;
        if (readFileSync(file.path, 'utf8') !== file.content) {
            return 'drifted';
        }
    }
    if (!hasAny)
        return 'missing';
    return definition.instructionFiles
        .filter((file) => !scope || file.scope === scope)
        .every((file) => existsSync(file.path))
        ? 'installed'
        : 'missing';
}
const MEMORY_HOOK_CLIENTS = new Set(['codex', 'cursor', 'claude', 'claude-code', 'claude-desktop']);
function instructionMentionsMemoryHook(content) {
    return content.includes('nexus_store_memory') && /memory hooks?/i.test(content);
}
function hasInstructionMemoryHook(definition) {
    return definition.instructionFiles.some((file) => {
        if (!existsSync(file.path))
            return false;
        try {
            return instructionMentionsMemoryHook(readFileSync(file.path, 'utf8'));
        }
        catch {
            return false;
        }
    });
}
function hasRepoMemoryHookSeed(workspaceRoot) {
    return ['.agents/hooks/memory-stored-review.md', '.agent/hooks/memory-stored-review.md']
        .some((relativePath) => existsSync(join(workspaceRoot, relativePath)));
}
function hasClaudeCodeMemoryHook() {
    const settingsPath = join(homedir(), '.claude', 'settings.json');
    if (!existsSync(settingsPath))
        return false;
    try {
        const parsed = JSON.parse(readFileSync(settingsPath, 'utf8'));
        return Boolean(parsed?.hooks?.PostToolUse?.some((entry) => (entry?.hooks?.some((hook) => hook?.command === 'nexus-prime hook memory'))));
    }
    catch {
        return false;
    }
}
function memoryHookStatus(definition) {
    if (!MEMORY_HOOK_CLIENTS.has(definition.id)) {
        return {
            memoryHookReady: false,
            memoryHookSummary: 'Memory hook readiness is not tracked for this client',
        };
    }
    const instructionReady = hasInstructionMemoryHook(definition);
    const repoHookReady = hasRepoMemoryHookSeed(definition.workspaceRoot);
    const nativeHookRequired = definition.id === 'claude' || definition.id === 'claude-code';
    const nativeHookReady = nativeHookRequired ? hasClaudeCodeMemoryHook() : true;
    const memoryHookReady = instructionReady && repoHookReady && nativeHookReady;
    const missing = [
        instructionReady ? null : 'client instruction wording',
        repoHookReady ? null : 'repo memory.stored hook seed',
        nativeHookReady ? null : 'Claude Code PostToolUse hook',
    ].filter(Boolean);
    return {
        memoryHookReady,
        memoryHookSummary: memoryHookReady
            ? nativeHookRequired
                ? 'Memory hooks ready via Claude Code PostToolUse plus Nexus lifecycle memory storage'
                : 'Memory hooks ready via Nexus lifecycle memory storage and repo memory.stored hook'
            : `Memory hooks incomplete: ${missing.join(', ')}`,
    };
}
export function statusForDefinition(definition) {
    const configOk = definition.configPath ? hasExpectedConfig(definition) : true;
    const workspaceState = instructionState(definition, 'workspace');
    const homeState = instructionState(definition, 'home');
    const homeReady = configOk && homeState !== 'drifted';
    const workspaceReady = workspaceState === 'installed' || definition.instructionFiles.every((file) => file.scope !== 'workspace');
    const memoryHook = memoryHookStatus(definition);
    if (configOk && workspaceState !== 'drifted' && homeState !== 'drifted' && workspaceReady) {
        return {
            state: 'installed',
            summary: definition.configPath
                ? 'Config and client instructions are current'
                : 'Managed client instructions are current',
            homeReady,
            workspaceReady,
            ...memoryHook,
        };
    }
    if ((definition.configPath && existsSync(definition.configPath)) || workspaceState !== 'missing' || homeState !== 'missing') {
        return {
            state: 'drifted',
            summary: definition.configPath
                ? 'Setup exists but is missing the autonomous profile or current instructions'
                : 'A client instruction file exists, but the managed Nexus Prime bootstrap block is missing or outdated',
            homeReady,
            workspaceReady,
            ...memoryHook,
        };
    }
    return {
        state: 'missing',
        summary: definition.configPath ? 'Setup not installed yet' : 'No managed client instruction file installed yet',
        homeReady: false,
        workspaceReady: false,
        ...memoryHook,
    };
}
export function supportedSetupClients() {
    return [...SUPPORTED_CLIENTS];
}
function workspaceEligible(workspaceRoot) {
    if (workspaceRoot === '/' || workspaceRoot === resolve('/'))
        return false;
    return existsSync(join(workspaceRoot, 'package.json'))
        || existsSync(join(workspaceRoot, '.git'))
        || existsSync(join(workspaceRoot, 'AGENTS.md'));
}
/**
 * Detect whether a given client is actually installed on this machine.
 * Checks for the client binary and/or its well-known config directory.
 */
function isClientPresent(clientId) {
    const home = homedir();
    const codexHome = resolveHomeCodexRoot();
    const checks = {
        'claude-code': () => hasBinary('claude') || existsSync(join(home, '.claude')),
        'claude': () => hasBinary('claude') || existsSync(join(home, '.claude')),
        'claude-desktop': () => {
            if (process.platform === 'darwin') {
                return existsSync(join(home, 'Library', 'Application Support', 'Claude'));
            }
            if (process.platform === 'win32') {
                return existsSync(join(process.env.APPDATA || '', 'Claude'));
            }
            return existsSync(join(home, '.config', 'claude'));
        },
        'cursor': () => hasBinary('cursor') || existsSync(join(home, '.cursor')),
        'opencode': () => hasBinary('opencode') || existsSync(join(home, '.config', 'opencode')),
        'windsurf': () => hasBinary('windsurf') || existsSync(join(home, '.windsurf')),
        'codex': () => hasBinary('codex') || Boolean(process.env.CODEX_HOME) || Boolean(process.env.CODEX_SESSION) || existsSync(codexHome),
        'aider': () => hasBinary('aider') || existsSync(join(home, '.aider')),
        'continue': () => existsSync(join(home, '.continue')),
        'cline': () => existsSync(join(home, '.vscode')),
        'antigravity': () => hasBinary('antigravity') || existsSync(join(home, '.antigravity')),
        'openclaw': () => hasBinary('openclaw') || existsSync(join(home, '.openclaw')),
        'hermes': () => hasBinary('hermes') || existsSync(join(home, '.hermes')),
        'nanoclaw': () => hasBinary('nanoclaw') || existsSync(join(home, '.nanoclaw')),
        'picoclaw': () => hasBinary('picoclaw') || existsSync(join(home, '.picoclaw')),
    };
    try {
        return checks[clientId]?.() ?? false;
    }
    catch {
        return false;
    }
}
function hasBinary(name) {
    try {
        execSync(`which ${name}`, { stdio: 'ignore', timeout: 2000 });
        return true;
    }
    catch {
        return false;
    }
}
function ensureWorkspaceAgentScaffold(workspaceRoot, options = {}) {
    if (workspaceRoot === '/' || workspaceRoot === resolve('/')) {
        if (!options.silent) {
            console.warn('[nexus-prime] Cannot create agent scaffold at system root. Skipping.');
        }
        return;
    }
    const agentDir = join(workspaceRoot, '.agent');
    const isFirstRun = !existsSync(agentDir);
    const directories = [
        '.agent',
        '.agent/client-bootstrap',
        '.agent/runtime',
        '.agent/rules',
        '.agent/skills',
        '.agent/workflows',
        '.agent/hooks',
        '.agent/automations',
        '.agents',
        '.agents/skills',
        '.agents/workflows',
        '.agents/hooks',
        '.agents/automations',
    ];
    directories.forEach((relativeDir) => {
        try {
            mkdirSync(join(workspaceRoot, relativeDir), { recursive: true });
        }
        catch (err) {
            if (!options.silent) {
                console.warn(`[nexus-prime] Failed to create directory ${relativeDir}: ${err}`);
            }
        }
    });
    for (const seed of WORKSPACE_SEED_FILES) {
        const target = join(workspaceRoot, seed.relativePath);
        const isCanonicalSeed = seed.relativePath.startsWith('.agents/');
        if (!existsSync(target) && (isFirstRun || isCanonicalSeed)) {
            writeFileSync(target, seed.content, 'utf8');
        }
    }
}
function bootstrapManifestPath(stateRoot, workspaceRoot) {
    const workspaceStateRoot = resolveWorkspaceContext({ workspaceRoot }).stateRoot;
    return join(stateRoot ?? workspaceStateRoot, 'bootstrap-manifest.json');
}
export function readBootstrapManifest(stateRoot, workspaceRoot) {
    const target = bootstrapManifestPath(stateRoot, workspaceRoot);
    if (!existsSync(target))
        return undefined;
    try {
        return JSON.parse(readFileSync(target, 'utf8'));
    }
    catch {
        return undefined;
    }
}
export function writeBootstrapManifest(status, stateRoot, workspaceRoot) {
    const target = bootstrapManifestPath(stateRoot, workspaceRoot ?? status.workspaceRoot);
    ensureParentDir(target);
    writeFileSync(target, JSON.stringify(status, null, 2), 'utf8');
    return status;
}
export function collectBootstrapManifest(options) {
    const workspaceRoot = resolveWorkspaceContext({ workspaceRoot: options.workspaceRoot }).workspaceRoot;
    return {
        version: 1,
        generatedAt: Date.now(),
        workspaceRoot,
        clients: SUPPORTED_CLIENTS.map((clientId) => {
            const definition = getSetupDefinition(clientId, { packageRoot: options.packageRoot, workspaceRoot });
            const status = statusForDefinition(definition);
            return {
                clientId,
                label: definition.label,
                state: status.state,
                configPath: definition.configPath,
                instructionFiles: definition.instructionFiles.map((file) => file.path),
                homeReady: status.homeReady,
                workspaceReady: status.workspaceReady,
                memoryHookReady: status.memoryHookReady,
                memoryHookSummary: status.memoryHookSummary,
                summary: status.summary,
                updatedAt: Date.now(),
            };
        }),
    };
}
export function ensureBootstrap(options) {
    const packageRoot = resolve(options.packageRoot);
    if (!existsSync(packageRoot)) {
        throw new Error(`Invalid packageRoot: path does not exist (${packageRoot})`);
    }
    const workspace = resolveWorkspaceContext({ workspaceRoot: options.workspaceRoot });
    const workspaceRoot = workspace.workspaceRoot;
    const allowWorkspace = workspaceEligible(workspaceRoot);
    const phase = options.phase ?? 'runtime';
    const scope = phase === 'install'
        ? (allowWorkspace ? 'all' : 'home')
        : (allowWorkspace ? 'workspace' : 'home');
    if (options.dryRun) {
        return collectBootstrapManifest({ packageRoot, workspaceRoot });
    }
    if (allowWorkspace) {
        ensureWorkspaceAgentScaffold(workspaceRoot, { silent: options.silent });
        // Seed a single per-workspace welcome memory, but keep it hidden from normal recall.
        // Dedup is two-layer: (1) memory.searchByTagsAndContent across ALL states catches
        //   the case where hygiene moved a prior welcome to scrap (the active-only default
        //   would have re-seeded a duplicate on every daemon restart, accumulating ~20/day);
        // (2) workspace marker file at <stateDir>/workspaces/<stateKey>/welcome-seeded acts
        //   as a fallback for cases where the memory engine resolves to a different DB.
        try {
            // Honor NEXUS_MEMORY_DB_PATH so test fixtures that pin the memory DB at a
            // specific path see the welcome land in the same DB they search.
            const memDbPath = process.env.NEXUS_MEMORY_DB_PATH?.trim();
            const memory = memDbPath ? new MemoryEngine(memDbPath) : new MemoryEngine();
            const existing = memory.searchByTagsAndContent('', [`#bootstrap-welcome:${workspace.stateKey}`], {
                scope: 'user',
                limit: 1,
                states: ['active', 'quarantined', 'scrap', 'expired'],
            });
            const stateDir = process.env.NEXUS_STATE_DIR?.trim() || join(homedir(), '.nexus-prime');
            const workspaceStateDir = join(stateDir, 'workspaces', workspace.stateKey);
            const marker = join(workspaceStateDir, 'welcome-seeded');
            const markerExists = existsSync(marker);
            if (!existing.length && !markerExists) {
                const welcomeTags = buildWelcomeMemoryTags(workspace.stateKey);
                memory.store(WELCOME_MEMORY_MESSAGE, 0.08, welcomeTags, undefined, 0, {
                    scope: 'user',
                    state: 'active',
                    source: 'system',
                    trust: 0.2,
                    provenance: {
                        summary: 'Hidden workspace bootstrap welcome memory',
                        workspaceId: workspace.stateKey,
                    },
                });
                try {
                    mkdirSync(workspaceStateDir, { recursive: true });
                    writeFileSync(marker, JSON.stringify({ seededAt: Date.now(), stateKey: workspace.stateKey }));
                }
                catch { /* best-effort marker write */ }
            }
            // Close to checkpoint WAL so other MemoryEngine instances pointed at the same DB
            // (test fixtures, separate processes) see the welcome immediately on their next read.
            try {
                memory.close();
            }
            catch { /* best-effort */ }
        }
        catch (err) {
            console.warn('Failed to initialize welcome memory:', err);
        }
    }
    for (const clientId of SUPPORTED_CLIENTS) {
        if (!isClientPresent(clientId))
            continue;
        try {
            const definition = getSetupDefinition(clientId, { packageRoot, workspaceRoot });
            const configPath = definition.configPath;
            if (phase === 'install' && configPath) {
                try {
                    const stat = statSync(configPath);
                    const mtimeMs = stat.mtimeMs;
                    const now = Date.now();
                    if (now - mtimeMs < 60000 && hasExpectedConfig(definition)) {
                        continue;
                    }
                }
                catch {
                    // File doesn't exist or other error - proceed with write
                }
            }
            installSetup(definition, { scope, silent: options.silent, workspaceRoot });
        }
        catch (error) {
            console.warn(`Bootstrap failed for client ${clientId}: ${error}`);
        }
    }
    const manifest = collectBootstrapManifest({ packageRoot, workspaceRoot });
    writeBootstrapManifest(manifest, undefined, workspaceRoot);
    return manifest;
}
