'use strict';

const fs = require('fs');
const http = require('http');
const path = require('path');
const { spawn } = require('child_process');

const WALLE_SERVER_NAME = 'wall-e';
const AGENT_INSTRUCTIONS_BEGIN = '<!-- wall-e-memory-routing:start -->';
const AGENT_INSTRUCTIONS_END = '<!-- wall-e-memory-routing:end -->';
const WALLE_MCP_TRANSPORTS = Object.freeze({
  STDIO: 'stdio',
  HTTP: 'http',
});

const MCP_TARGETS = Object.freeze([
  { id: 'claude-code-user', tool: 'Claude Code User', configPath: '.claude.json', detectPath: '.claude.json', kind: 'json' },
  { id: 'claude-code-project', tool: 'Claude Code Project', configPath: '.mcp.json', detectPath: '.', kind: 'json', base: 'project', explicit: true },
  { id: 'codex', tool: 'Codex', configPath: '.codex/config.toml', detectPath: '.codex', kind: 'codex-toml' },
  { id: 'cursor', tool: 'Cursor', configPath: '.cursor/mcp.json', detectPath: '.cursor', kind: 'json' },
  { id: 'windsurf', tool: 'Windsurf', configPath: '.codeium/windsurf/mcp_config.json', detectPath: '.codeium/windsurf', kind: 'json' },
  { id: 'claude-desktop', tool: 'Claude Desktop', configPath: 'Library/Application Support/Claude/claude_desktop_config.json', detectPath: 'Library/Application Support/Claude', kind: 'json' },
]);

const AGENT_INSTRUCTION_TARGETS = Object.freeze([
  {
    id: 'claude-code-instructions',
    tool: 'Claude Code Memory Instructions',
    instructionPath: '.claude/CLAUDE.md',
    detectPaths: ['.claude', '.claude.json'],
  },
  {
    id: 'codex-instructions',
    tool: 'Codex Memory Instructions',
    instructionPath: '.codex/AGENTS.md',
    detectPaths: ['.codex'],
  },
]);

function wallEMcpUrl(wallePort) {
  return `http://localhost:${Number(wallePort) || 3457}/mcp`;
}

function wallEMcpStdioScript() {
  return path.resolve(__dirname, '..', 'bin', 'walle-mcp-stdio.js');
}

function _requestedMcpTransport(opts = {}) {
  const raw = String(opts.transport || process.env.WALLE_MCP_TRANSPORT || '').trim().toLowerCase();
  if (raw === WALLE_MCP_TRANSPORTS.HTTP || raw === 'streamable-http' || raw === 'streamable_http') return WALLE_MCP_TRANSPORTS.HTTP;
  return WALLE_MCP_TRANSPORTS.STDIO;
}

function wallEMcpStdioConfig() {
  return {
    type: 'stdio',
    command: process.execPath,
    args: [wallEMcpStdioScript()],
  };
}

function wallEMcpConfig(wallePort, opts = {}) {
  if (_requestedMcpTransport(opts) === WALLE_MCP_TRANSPORTS.HTTP) {
    return { type: 'http', url: wallEMcpUrl(wallePort) };
  }
  return wallEMcpStdioConfig();
}

function _entryTransport(entry) {
  if (!entry || typeof entry !== 'object') return '';
  const type = String(entry.type || '').toLowerCase();
  if (type === 'stdio' || entry.command) return WALLE_MCP_TRANSPORTS.STDIO;
  if (type === 'http' || type === 'streamable-http' || type === 'streamable_http' || entry.url) return WALLE_MCP_TRANSPORTS.HTTP;
  return '';
}

function _argsEqual(a, b) {
  const left = Array.isArray(a) ? a : [];
  const right = Array.isArray(b) ? b : [];
  return left.length === right.length && left.every((value, index) => String(value) === String(right[index]));
}

function _commandsEquivalent(actual, expected) {
  const a = String(actual || '');
  const e = String(expected || '');
  if (a === e) return true;
  return a === 'node' && path.basename(e) === 'node';
}

function _jsonEntryMatchesExpected(entry, expected) {
  if (!entry || typeof entry !== 'object') return false;
  if (expected.type === 'http') return _entryTransport(entry) === WALLE_MCP_TRANSPORTS.HTTP && entry.url === expected.url;
  return _entryTransport(entry) === WALLE_MCP_TRANSPORTS.STDIO
    && _commandsEquivalent(entry.command, expected.command)
    && _argsEqual(entry.args, expected.args);
}

function _mcpAutoConfigFlag(env = process.env) {
  return String(env.WALLE_MCP_AUTO_CONFIG || '').trim().toLowerCase();
}

function _isExplicitMcpAutoConfigEnabled(env = process.env) {
  const value = _mcpAutoConfigFlag(env);
  return value === '1' || value === 'true' || value === 'yes' || value === 'on' || value === 'force';
}

function _isExplicitMcpAutoConfigDisabled(env = process.env) {
  const value = _mcpAutoConfigFlag(env);
  return value === '0' || value === 'false' || value === 'no' || value === 'off';
}

function _looksLikeTransientInstance(env = process.env) {
  const tag = String(env.CTM_INSTANCE_TAG || '').trim().toLowerCase();
  if (/^(dev|render-test|test|e2e|ctm-dev)(?:[-_]|$)/.test(tag)) return true;

  const paths = [
    env.CTM_DATA_DIR,
    env.WALL_E_DATA_DIR,
    env.WALLE_SESSIONS_DIR,
    env.WALL_E_SESSIONS_DIR,
  ].map(v => String(v || '').replace(/\\/g, '/'));

  return paths.some(p => /(?:^|\/)(?:tmp|private\/tmp)\/(?:walle-dev|ctm-render-test|walle-e2e|ctm-dev)(?:[-/]|$)/.test(p));
}

function getMcpAutoConfigSkipReason(env = process.env) {
  if (_isExplicitMcpAutoConfigDisabled(env)) return 'disabled';
  if (_isExplicitMcpAutoConfigEnabled(env)) return '';
  if (_looksLikeTransientInstance(env)) return 'transient_instance';
  return '';
}

function _home(homeDir) {
  return homeDir || process.env.HOME || process.env.USERPROFILE || '';
}

function _projectDir(opts = {}) {
  return opts.projectDir || opts.cwd || '';
}

function _targetBase(target, opts = {}) {
  return target.base === 'project' ? _projectDir(opts) : _home(opts.homeDir);
}

function _targetPaths(target, opts = {}) {
  const base = _targetBase(target, opts);
  if (!base) return { detectPath: '', configPath: '' };
  return {
    detectPath: path.join(base, target.detectPath),
    configPath: path.join(base, target.configPath),
  };
}

function _legacyClaudeProjectPath(homeDir) {
  const home = _home(homeDir);
  return home ? path.join(home, '.claude', 'mcp.json') : '';
}

function _isDetected(target, opts = {}) {
  if (target.explicit && !opts.includeProjectConfigs) return false;
  const paths = _targetPaths(target, opts);
  if (!paths.detectPath) return false;
  return fs.existsSync(paths.detectPath);
}

function _isInstructionTargetDetected(target, homeDir) {
  const home = _home(homeDir);
  return target.detectPaths.some(detectPath => fs.existsSync(path.join(home, detectPath)));
}

function _readJsonConfig(configPath) {
  if (!fs.existsSync(configPath)) return { config: {}, exists: false };
  try {
    return { config: JSON.parse(fs.readFileSync(configPath, 'utf8')), exists: true };
  } catch (err) {
    return { config: null, exists: true, error: err };
  }
}

function _writeJsonConfig(configPath, config) {
  fs.mkdirSync(path.dirname(configPath), { recursive: true });
  fs.writeFileSync(configPath, JSON.stringify(config, null, 2) + '\n');
}

function _jsonStatus(target, configPath, wallePort, opts = {}) {
  const loaded = _readJsonConfig(configPath);
  if (loaded.error) {
    return { kind: 'mcp_config', tool: target.tool, status: 'invalid_config', configPath, error: loaded.error.message };
  }
  const entry = loaded.config?.mcpServers?.[WALLE_SERVER_NAME];
  const expected = wallEMcpConfig(wallePort, opts);
  if (entry && _jsonEntryMatchesExpected(entry, expected)) {
    return { kind: 'mcp_config', tool: target.tool, status: 'configured', configPath, transport: expected.type };
  }
  if (entry) {
    const transport = _entryTransport(entry);
    if (transport && transport !== expected.type) {
      return { kind: 'mcp_config', tool: target.tool, status: 'wrong_transport', configPath, transport, expectedTransport: expected.type, url: entry.url || '' };
    }
    if (expected.type === 'http') {
      return { kind: 'mcp_config', tool: target.tool, status: 'wrong_port', configPath, transport, url: entry.url || '' };
    }
    return { kind: 'mcp_config', tool: target.tool, status: 'wrong_command', configPath, transport, command: entry.command || '' };
  }
  return { kind: 'mcp_config', tool: target.tool, status: 'not_configured', configPath };
}

function _ensureJsonTarget(target, configPath, wallePort, opts = {}) {
  const loaded = _readJsonConfig(configPath);
  if (loaded.error) {
    return { kind: 'mcp_config', tool: target.tool, action: 'error', configPath, error: loaded.error.message };
  }
  const config = loaded.config || {};
  if (!config.mcpServers || typeof config.mcpServers !== 'object' || Array.isArray(config.mcpServers)) {
    config.mcpServers = {};
  }
  const existing = config.mcpServers[WALLE_SERVER_NAME];
  const expected = wallEMcpConfig(wallePort, opts);
  if (existing && _jsonEntryMatchesExpected(existing, expected)) {
    return { kind: 'mcp_config', tool: target.tool, action: 'already_configured', configPath, transport: expected.type };
  }
  const action = existing ? 'updated' : 'added';
  config.mcpServers[WALLE_SERVER_NAME] = expected;
  _writeJsonConfig(configPath, config);
  return { kind: 'mcp_config', tool: target.tool, action, configPath, transport: expected.type };
}

function _codexBlockRegex() {
  // Match the wall-e MCP block from its header to the next top-level
  // [section] or end-of-string. The /m flag was previously set, which made
  // `$` mean end-of-LINE; combined with `\s*$` the non-greedy `[\s\S]*?`
  // bailed out after the first body line (typically `command = ...`),
  // leaving `args = ...` / `enabled = true` orphaned in the file. Every
  // upsert then left another pair behind, eventually producing a
  // duplicate-key TOML parse error.
  return /(?:^|\n)\[mcp_servers\.(?:"wall-e"|wall-e)\]\n[\s\S]*?(?=\n\[[^\n]+\]|$)/;
}

// Defence-in-depth cleanup for files corrupted by earlier buggy versions:
// strip any `args = ["...walle-mcp-stdio.js"]` + `enabled = true` pair that
// is NOT part of a [mcp_servers."wall-e"] block.
function _stripOrphanWalleFragments(text) {
  return String(text || '').replace(
    /\n+args\s*=\s*\[\s*"[^"]*walle-mcp-stdio\.js"\s*\]\s*\nenabled\s*=\s*true\s*(?=\n|$)/g,
    '',
  );
}

// Detect whether the file has orphan args/enabled pairs pointing at
// walle-mcp-stdio that are NOT inside the canonical [mcp_servers."wall-e"]
// block — i.e., the canonical block contains exactly one such args line,
// so anything beyond one is an orphan.
function _hasOrphanWalleFragments(text) {
  const argsLines = (String(text || '').match(/^args\s*=\s*\[[^\]]*walle-mcp-stdio[^\]]*\]/gm) || []).length;
  return argsLines > 1;
}

function _extractCodexWallEBlock(text) {
  const match = String(text || '').match(_codexBlockRegex());
  return match ? match[0].replace(/^\n/, '') : '';
}

function _extractTomlStringValue(block, key) {
  const re = new RegExp(`^\\s*${key}\\s*=\\s*"([^"\\\\]*(?:\\\\.[^"\\\\]*)*)"\\s*$`, 'm');
  const match = String(block || '').match(re);
  if (!match) return '';
  return match[1].replace(/\\"/g, '"').replace(/\\\\/g, '\\');
}

function _extractTomlStringArrayValue(block, key) {
  const re = new RegExp(`^\\s*${key}\\s*=\\s*\\[([^\\]]*)\\]\\s*$`, 'm');
  const match = String(block || '').match(re);
  if (!match) return [];
  const values = [];
  const itemRe = /"([^"\\]*(?:\\.[^"\\]*)*)"/g;
  let item;
  while ((item = itemRe.exec(match[1]))) {
    values.push(item[1].replace(/\\"/g, '"').replace(/\\\\/g, '\\'));
  }
  return values;
}

function _tomlString(value) {
  return `"${String(value || '').replace(/\\/g, '\\\\').replace(/"/g, '\\"')}"`;
}

function _codexConfigMatchesExpected(block, expected) {
  if (expected.type === 'http') {
    return _extractTomlStringValue(block, 'url') === expected.url;
  }
  const command = _extractTomlStringValue(block, 'command');
  const args = _extractTomlStringArrayValue(block, 'args');
  return _commandsEquivalent(command, expected.command) && _argsEqual(args, expected.args);
}

function _codexBlockTransport(block) {
  if (_extractTomlStringValue(block, 'command')) return WALLE_MCP_TRANSPORTS.STDIO;
  if (_extractTomlStringValue(block, 'url')) return WALLE_MCP_TRANSPORTS.HTTP;
  return '';
}

function _codexStatus(target, configPath, wallePort, opts = {}) {
  const text = fs.existsSync(configPath) ? fs.readFileSync(configPath, 'utf8') : '';
  const block = _extractCodexWallEBlock(text);
  if (!block) return { kind: 'mcp_config', tool: target.tool, status: 'not_configured', configPath };
  const expected = wallEMcpConfig(wallePort, opts);
  if (_codexConfigMatchesExpected(block, expected)) {
    return { kind: 'mcp_config', tool: target.tool, status: 'configured', configPath, transport: expected.type };
  }
  const transport = _codexBlockTransport(block);
  if (transport && transport !== expected.type) {
    return { kind: 'mcp_config', tool: target.tool, status: 'wrong_transport', configPath, transport, expectedTransport: expected.type, url: _extractTomlStringValue(block, 'url') };
  }
  if (expected.type === 'http') {
    return { kind: 'mcp_config', tool: target.tool, status: 'wrong_port', configPath, transport, url: _extractTomlStringValue(block, 'url') };
  }
  return { kind: 'mcp_config', tool: target.tool, status: 'wrong_command', configPath, transport, command: _extractTomlStringValue(block, 'command') };
}

function _upsertCodexWallEBlock(text, wallePort, opts = {}) {
  const cleaned = _stripOrphanWalleFragments(String(text || ''));
  const body = cleaned.replace(_codexBlockRegex(), '').replace(/\s+$/g, '');
  const expected = wallEMcpConfig(wallePort, opts);
  const block = expected.type === 'http'
    ? `[mcp_servers."wall-e"]\nurl = ${_tomlString(expected.url)}\nenabled = true\n`
    : `[mcp_servers."wall-e"]\ncommand = ${_tomlString(expected.command)}\nargs = [${expected.args.map(_tomlString).join(', ')}]\nenabled = true\n`;
  return (body ? `${body}\n\n` : '') + block;
}

function _ensureCodexTarget(target, configPath, wallePort, opts = {}) {
  const existing = fs.existsSync(configPath) ? fs.readFileSync(configPath, 'utf8') : '';
  const status = _codexStatus(target, configPath, wallePort, opts);
  const expected = wallEMcpConfig(wallePort, opts);
  const hasOrphans = _hasOrphanWalleFragments(existing);
  if (status.status === 'configured' && !hasOrphans) {
    return { kind: 'mcp_config', tool: target.tool, action: 'already_configured', configPath, transport: expected.type };
  }
  fs.mkdirSync(path.dirname(configPath), { recursive: true });
  fs.writeFileSync(configPath, _upsertCodexWallEBlock(existing, wallePort, opts));
  const action = hasOrphans && status.status === 'configured'
    ? 'cleaned_orphans'
    : (status.status === 'not_configured' ? 'added' : 'updated');
  return { kind: 'mcp_config', tool: target.tool, action, configPath, transport: expected.type };
}

function wallEAgentMemoryInstructions(wallePort) {
  return `${AGENT_INSTRUCTIONS_BEGIN}
## Wall-E MCP Memory Routing

Wall-E MCP is the user's private memory, knowledge graph, and CTM coding-session context. Local coding clients should use the provider-native \`wall-e\` MCP server, normally configured as stdio at ${wallEMcpStdioScript()}. The local HTTP endpoint remains available at ${wallEMcpUrl(wallePort)} for explicit JSON-RPC fallback when the host process can reach it.

Use Wall-E memory before public web search or guesswork for any private, remembered, or work-context question, including:
- prior sessions, previous attempts, branch/task history, regressions, decisions, preferences, and "last time" / "remember" / "what did we discuss" prompts;
- people, teams, projects, tools, documents, Slack/email/calendar/work context, and "do you know" / "who is" / "what is" prompts;
- questions about the user's own writing style, communication style, tone, personality, leadership style, work patterns, or "am I too ..." self-analysis;
- requests that explicitly say to use the user's context, Wall-E memory, CTM sessions, or past agent work.

Recommended tools:
- \`walle_context_hint\` for cheap preflight routing and compact context before an agent answers.
- \`walle_lookup_context\` for one-shot route + private memory/entity/CTM session context lookup.
- \`walle_route_query\` when you are unsure whether the request should use Wall-E, live source tools, or public web.
- \`walle_memory_status\` when connection health is uncertain.
- \`walle_live_capabilities\` when you need to know whether Wall-E has Slack, Gmail/Google Workspace, calendar, or macOS live-source access without reading private content.
- \`walle_user_profile\` for writing style, communication style, preferences, leadership style, and work-pattern questions.
- \`walle_postflight_check\` after an answer when you need to verify whether Wall-E context was wrongly skipped.
- \`walle_context_pack\`, \`walle_search_sessions\`, and \`walle_get_session\` for prior coding-agent context.
- \`search_memories\`, \`ask_walle\`, \`entity_search\`, and \`knowledge_query\` for user/work memory and knowledge graph lookup.

For current-session lookups from inside Codex, prefer \`CODEX_THREAD_ID\` over \`CTM_SESSION_ID\` when both exist and differ. \`CTM_SESSION_ID\` can be stale in long-lived resumed Codex processes, while \`CODEX_THREAD_ID\` names the provider transcript that Wall-E/CTM can map back to the owning tab.

If native MCP tool wrappers are unavailable or not mounted, first check whether the host exposes a configured \`wall-e\` MCP server. If no wrapper is available and local HTTP is reachable from this process, call the Wall-E HTTP endpoint directly with JSON-RPC: use \`tools/list\` to verify the server, then \`tools/call\` with \`walle_lookup_context\`. If localhost HTTP is refused or blocked, report that Wall-E MCP is unreachable from this provider rather than silently guessing.

Do not use Wall-E as a reflex for every request. Use live host tools for explicit current inbox/calendar/Slack reads or side-effecting actions such as send, post, create, schedule, update, or delete. Use public web search for public/current facts, or after Wall-E returns no useful memory. If Wall-E memory is unavailable, say that explicitly. Keep private details minimal; do not expose private links, emails, or full transcripts unless the user needs them.
${AGENT_INSTRUCTIONS_END}
`;
}

function _upsertMarkedBlock(text, block) {
  const existing = String(text || '').replace(/\r\n/g, '\n');
  const nextBlock = String(block || '').trimEnd() + '\n';
  const start = existing.indexOf(AGENT_INSTRUCTIONS_BEGIN);
  const end = existing.indexOf(AGENT_INSTRUCTIONS_END);
  if (start >= 0 && end > start) {
    const afterEnd = end + AGENT_INSTRUCTIONS_END.length;
    return `${existing.slice(0, start)}${nextBlock}${existing.slice(afterEnd).replace(/^\n+/, '\n')}`;
  }
  const prefix = existing.trimEnd();
  return prefix ? `${prefix}\n\n${nextBlock}` : nextBlock;
}

function _instructionStatus(target, instructionPath, wallePort) {
  if (!fs.existsSync(instructionPath)) {
    return { kind: 'agent_instructions', tool: target.tool, status: 'not_configured', configPath: instructionPath };
  }
  const existing = fs.readFileSync(instructionPath, 'utf8');
  const expected = wallEAgentMemoryInstructions(wallePort).trimEnd();
  if (existing.includes(expected)) {
    return { kind: 'agent_instructions', tool: target.tool, status: 'configured', configPath: instructionPath };
  }
  if (existing.includes(AGENT_INSTRUCTIONS_BEGIN) && existing.includes(AGENT_INSTRUCTIONS_END)) {
    return { kind: 'agent_instructions', tool: target.tool, status: 'stale', configPath: instructionPath };
  }
  return { kind: 'agent_instructions', tool: target.tool, status: 'not_configured', configPath: instructionPath };
}

function _ensureInstructionTarget(target, homeDir, wallePort) {
  if (!_isInstructionTargetDetected(target, homeDir)) {
    return { kind: 'agent_instructions', tool: target.tool, action: 'not_installed' };
  }
  const instructionPath = path.join(_home(homeDir), target.instructionPath);
  const status = _instructionStatus(target, instructionPath, wallePort);
  if (status.status === 'configured') {
    return { kind: 'agent_instructions', tool: target.tool, action: 'already_configured', configPath: instructionPath };
  }
  const existing = fs.existsSync(instructionPath) ? fs.readFileSync(instructionPath, 'utf8') : '';
  fs.mkdirSync(path.dirname(instructionPath), { recursive: true });
  fs.writeFileSync(instructionPath, _upsertMarkedBlock(existing, wallEAgentMemoryInstructions(wallePort)));
  return {
    kind: 'agent_instructions',
    tool: target.tool,
    action: status.status === 'stale' ? 'updated' : 'added',
    configPath: instructionPath,
  };
}

function detectAgentMemoryInstructions(wallePort, opts = {}) {
  const homeDir = opts.homeDir;
  return AGENT_INSTRUCTION_TARGETS.map((target) => {
    if (!_isInstructionTargetDetected(target, homeDir)) {
      return { kind: 'agent_instructions', tool: target.tool, status: 'not_installed' };
    }
    return _instructionStatus(target, path.join(_home(homeDir), target.instructionPath), wallePort);
  });
}

function ensureAgentMemoryInstructions(wallePort, opts = {}) {
  const homeDir = opts.homeDir;
  return AGENT_INSTRUCTION_TARGETS.map(target => _ensureInstructionTarget(target, homeDir, wallePort));
}

function detectMcpIntegrations(wallePort, opts = {}) {
  const results = MCP_TARGETS.map((target) => {
    const paths = _targetPaths(target, opts);
    if (!_isDetected(target, opts)) return { kind: 'mcp_config', tool: target.tool, status: 'not_installed' };
    if (target.kind === 'codex-toml') return _codexStatus(target, paths.configPath, wallePort, opts);
    return _jsonStatus(target, paths.configPath, wallePort, opts);
  });
  const legacyPath = _legacyClaudeProjectPath(opts.homeDir);
  if (legacyPath && fs.existsSync(legacyPath)) {
    results.push({
      kind: 'mcp_config',
      tool: 'Claude Code Legacy',
      status: 'unsupported_config_path',
      configPath: legacyPath,
      expectedConfigPath: opts.includeProjectConfigs && _projectDir(opts)
        ? path.join(_projectDir(opts), '.mcp.json')
        : path.join(_home(opts.homeDir), '.claude.json'),
    });
  }
  return results;
}

function ensureMcpIntegrations(wallePort, opts = {}) {
  const mcpResults = MCP_TARGETS.map((target) => {
    const paths = _targetPaths(target, opts);
    if (!_isDetected(target, opts)) return { kind: 'mcp_config', tool: target.tool, action: 'not_installed' };
    if (target.kind === 'codex-toml') return _ensureCodexTarget(target, paths.configPath, wallePort, opts);
    return _ensureJsonTarget(target, paths.configPath, wallePort, opts);
  });
  if (opts.includeAgentInstructions === false) return mcpResults;
  return mcpResults.concat(ensureAgentMemoryInstructions(wallePort, { homeDir: opts.homeDir }));
}

function injectMcpConfigs(wallePort, homeDir, opts = {}) {
  return ensureMcpIntegrations(wallePort, { ...opts, homeDir });
}

function _postMcp(port, payload, timeoutMs) {
  return new Promise((resolve, reject) => {
    const sessionId = payload.sessionId || '';
    const wirePayload = { ...payload };
    delete wirePayload.sessionId;
    const body = JSON.stringify(wirePayload);
    const headers = {
      'Accept': 'application/json, text/event-stream',
      'Content-Type': 'application/json',
      'Content-Length': Buffer.byteLength(body),
    };
    if (sessionId) headers['Mcp-Session-Id'] = sessionId;
    const req = http.request({
      host: '127.0.0.1',
      port: Number(port) || 3457,
      path: '/mcp',
      method: 'POST',
      timeout: timeoutMs,
      headers,
    }, (res) => {
      const chunks = [];
      res.on('data', chunk => chunks.push(chunk));
      res.on('end', () => {
        const raw = Buffer.concat(chunks).toString();
        let json = null;
        try { json = raw ? JSON.parse(raw) : null; } catch {}
        resolve({ status: res.statusCode || 0, json, body: raw, headers: res.headers });
      });
    });
    req.on('timeout', () => req.destroy(new Error('Timed out testing Wall-E MCP')));
    req.on('error', reject);
    req.write(body);
    req.end();
  });
}

async function testWallEMcpEndpoint(wallePort, opts = {}) {
  const timeoutMs = opts.timeoutMs || 2000;
  try {
    const init = await _postMcp(wallePort, {
      jsonrpc: '2.0',
      id: 1,
      method: 'initialize',
      params: { protocolVersion: '2025-03-26', capabilities: {}, clientInfo: { name: 'wall-e-self-test', version: '1.0.0' } },
    }, timeoutMs);
    if (init.status !== 200 || !init.json?.result?.serverInfo) {
      return { ok: false, status: init.status, error: 'initialize failed' };
    }
    const sessionId = init.headers?.['mcp-session-id'] || '';
    const tools = await _postMcp(wallePort, {
      jsonrpc: '2.0',
      id: 2,
      method: 'tools/list',
      params: {},
      sessionId,
    }, timeoutMs);
    const list = tools.json?.result?.tools;
    if (tools.status !== 200 || !Array.isArray(list)) {
      return { ok: false, status: tools.status, error: 'tools/list failed' };
    }
    return { ok: true, server: init.json.result.serverInfo, toolCount: list.length };
  } catch (err) {
    return { ok: false, error: err.message };
  }
}

function _requestStdio(processRef, pending, payload, timeoutMs, stderrRef) {
  return new Promise((resolve, reject) => {
    const id = payload.id;
    const timer = setTimeout(() => {
      pending.delete(id);
      reject(new Error(`Timed out waiting for stdio MCP response ${id}; stderr=${stderrRef.value}`));
    }, timeoutMs);
    pending.set(id, {
      resolve: (message) => {
        clearTimeout(timer);
        resolve(message);
      },
      reject: (err) => {
        clearTimeout(timer);
        reject(err);
      },
    });
    processRef.stdin.write(`${JSON.stringify(payload)}\n`);
  });
}

async function testWallEMcpStdio(opts = {}) {
  const timeoutMs = opts.timeoutMs || 3000;
  const config = wallEMcpStdioConfig();
  const child = spawn(config.command, config.args, {
    cwd: path.resolve(__dirname, '..'),
    env: { ...process.env, ...(opts.env || {}) },
    stdio: ['pipe', 'pipe', 'pipe'],
  });
  let stdout = '';
  const stderrRef = { value: '' };
  const pending = new Map();

  child.stdout.on('data', (data) => {
    stdout += data.toString('utf8');
    const lines = stdout.split('\n');
    stdout = lines.pop() || '';
    for (const line of lines) {
      if (!line.trim()) continue;
      let message = null;
      try { message = JSON.parse(line); } catch {
        continue;
      }
      const waiter = pending.get(message.id);
      if (waiter) {
        pending.delete(message.id);
        if (message.error) waiter.reject(new Error(message.error.message || JSON.stringify(message.error)));
        else waiter.resolve(message);
      }
    }
  });
  child.stderr.on('data', (data) => { stderrRef.value += data.toString('utf8'); });

  try {
    const init = await _requestStdio(child, pending, {
      jsonrpc: '2.0',
      id: 1,
      method: 'initialize',
      params: { protocolVersion: '2025-06-18', capabilities: {}, clientInfo: { name: 'wall-e-stdio-self-test', version: '1.0.0' } },
    }, timeoutMs, stderrRef);
    child.stdin.write(`${JSON.stringify({ jsonrpc: '2.0', method: 'notifications/initialized', params: {} })}\n`);
    const tools = await _requestStdio(child, pending, {
      jsonrpc: '2.0',
      id: 2,
      method: 'tools/list',
      params: {},
    }, timeoutMs, stderrRef);
    return {
      ok: true,
      server: init.result?.serverInfo || null,
      toolCount: Array.isArray(tools.result?.tools) ? tools.result.tools.length : 0,
      transport: 'stdio',
    };
  } catch (err) {
    return { ok: false, error: err.message, stderr: stderrRef.value, transport: 'stdio' };
  } finally {
    for (const waiter of pending.values()) waiter.reject(new Error('stdio MCP self-test ended'));
    pending.clear();
    if (child.stdin.writable) child.stdin.end();
    await new Promise((resolve) => {
      const timer = setTimeout(() => {
        if (!child.killed) child.kill('SIGTERM');
        resolve();
      }, 500);
      timer.unref?.();
      child.once('exit', () => {
        clearTimeout(timer);
        resolve();
      });
    });
  }
}

module.exports = {
  AGENT_INSTRUCTION_TARGETS,
  AGENT_INSTRUCTIONS_BEGIN,
  AGENT_INSTRUCTIONS_END,
  MCP_TARGETS,
  WALLE_SERVER_NAME,
  detectAgentMemoryInstructions,
  detectMcpIntegrations,
  ensureAgentMemoryInstructions,
  ensureMcpIntegrations,
  getMcpAutoConfigSkipReason,
  injectMcpConfigs,
  testWallEMcpEndpoint,
  testWallEMcpStdio,
  wallEAgentMemoryInstructions,
  wallEMcpConfig,
  wallEMcpStdioConfig,
  wallEMcpStdioScript,
  wallEMcpUrl,
};
