'use strict';

class ToolRegistry {
  constructor({
    builtinTools = [],
    middleware = null,
    localTools = null,
    mcpClient = null,
    skillRunner = null,
    agentCatalog = null,
    taskTool = null,
    taskRunner = null,
    agentRuntime = null,
    brain = null,
    transcript = null,
    lifecycleHooks = null,
    projectRoot = process.cwd(),
    enableTaskTool = false,
    sessionIndex = null,
    enableSessionTools = false,
    specialHandlers = {},
    unknownToolHandler = null,
  } = {}) {
    this.middleware = middleware || null;
    this.localTools = localTools || null;
    this.mcpClient = mcpClient || null;
    this.skillRunner = skillRunner || null;
    this.taskTool = taskTool || null;
    this.sessionIndex = sessionIndex || (enableSessionTools ? createDefaultSessionIndex() : null);
    this.bridgeToolTargets = new Map();
    if (!this.taskTool && enableTaskTool) {
      const { AgentCatalog } = require('./agent-catalog');
      const { TaskTool } = require('./task-tool');
      this.taskTool = new TaskTool({
        agentCatalog: agentCatalog || new AgentCatalog({ projectRoot }),
        runner: taskRunner,
        agentRuntime,
        brain,
        transcript,
        lifecycleHooks,
      });
    }
    this.specialHandlers = { ...specialHandlers };
    this.unknownToolHandler = typeof unknownToolHandler === 'function' ? unknownToolHandler : null;
    this.tools = new Map();
    for (const tool of builtinTools || []) {
      this.register(tool.name, tool);
    }
  }

  register(name, definition = {}) {
    if (!name) throw new Error('tool name is required');
    const normalized = normalizeDefinition({ name, ...definition });
    this.tools.set(name, normalized);
    return normalized;
  }

  async getDefinitions(ctx = {}) {
    let definitions = (await this._collectDefinitions(ctx)).map(publicDefinition);
    if (this.middleware?.run) {
      definitions = await this.middleware.run('tool.definitions', ctx, definitions);
    }
    return definitions
      .map((definition) => publicDefinition(normalizeDefinition(definition)))
      .sort(compareToolNames);
  }

  async getToolSpecs(ctx = {}) {
    let specs = (await this._collectDefinitions(ctx)).map(toolSpec);
    if (this.middleware?.run) {
      specs = await this.middleware.run('tool.specs', ctx, specs);
    }
    return specs
      .map((spec) => toolSpec(normalizeDefinition(spec)))
      .sort(compareToolNames);
  }

  async _collectDefinitions(ctx = {}) {
    const merged = new Map();
    for (const [name, def] of this.tools) {
      const normalized = normalizeDefinition(def);
      if (await shouldExposeDefinition(normalized, ctx)) merged.set(name, normalized);
    }
    if (this.middleware?.getRegisteredTools) {
      for (const [name, def] of Object.entries(this.middleware.getRegisteredTools())) {
        const normalized = normalizeDefinition({ name, ...def });
        if (await shouldExposeDefinition(normalized, ctx)) merged.set(name, normalized);
      }
    }
    for (const bridge of await this._bridgeDefinitions(ctx)) {
      if (!merged.has(bridge.name) && await shouldExposeDefinition(bridge, ctx)) merged.set(bridge.name, bridge);
    }
    return [...merged.values()].sort(compareToolNames);
  }

  async execute(name, args = {}, ctx = {}) {
    if (this.specialHandlers[name]) {
      return this.specialHandlers[name](args, ctx);
    }

    const middlewareTool = this.middleware?.getRegisteredTools?.()[name];
    if (middlewareTool) {
      if (typeof middlewareTool.execute !== 'function') {
        return invalidTool(name, 'Registered tool has no execute function');
      }
      const prepared = await prepareExecution(name, { name, ...middlewareTool }, args, ctx);
      if (prepared.invalid) return prepared;
      return middlewareTool.execute(prepared.args, ctx);
    }

    const registered = this.tools.get(name);
    if (registered?.execute) {
      const prepared = await prepareExecution(name, registered, args, ctx);
      if (prepared.invalid) return prepared;
      return registered.execute(prepared.args, ctx);
    }

    if (name === 'mcp_call' && this.mcpClient?.callTool) {
      const prepared = await prepareExecution(name, genericMcpDefinition(), args, ctx);
      if (prepared.invalid) return prepared;
      return this._callMcpTool(prepared.args.server || '', prepared.args.tool, prepared.args.arguments || {}, ctx);
    }
    const bridgeTarget = this.bridgeToolTargets.get(name);
    if (bridgeTarget?.type === 'mcp' && this.mcpClient?.callTool) {
      const prepared = prepareBridgeArgs(name, args, ctx);
      if (prepared.invalid) return prepared;
      return this._callMcpTool(bridgeTarget.server, bridgeTarget.tool, prepared.args, ctx);
    }
    if (bridgeTarget?.type === 'skill') {
      const prepared = prepareBridgeArgs(name, args, ctx);
      if (prepared.invalid) return prepared;
      return this._runSkill(bridgeTarget.name, prepared.args, ctx);
    }
    if (name === 'load_skill' && this.skillRunner) {
      const prepared = await prepareExecution(name, loadSkillDefinition(), args, ctx);
      if (prepared.invalid) return prepared;
      return this._loadSkill(prepared.args.name, prepared.args, ctx);
    }
    if (name === 'skill' && this.skillRunner) {
      const prepared = await prepareExecution(name, genericSkillDefinition(), args, ctx);
      if (prepared.invalid) return prepared;
      return this._runSkill(prepared.args.name, prepared.args.arguments || {}, ctx);
    }
    if (name === 'task' && this.taskTool) {
      const prepared = await prepareExecution(name, this.taskTool.definition(), args, ctx);
      if (prepared.invalid) return prepared;
      return this.taskTool.execute(prepared.args, ctx);
    }
    if (name === 'session_search' && this.sessionIndex) {
      const prepared = await prepareExecution(name, sessionSearchDefinition(), args, ctx);
      if (prepared.invalid) return prepared;
      return this.sessionIndex.searchSessions(prepared.args);
    }
    if (name === 'session_load' && this.sessionIndex) {
      const prepared = await prepareExecution(name, sessionLoadDefinition(), args, ctx);
      if (prepared.invalid) return prepared;
      const result = this.sessionIndex.getSession(prepared.args.sessionId, prepared.args);
      return result || invalidTool(name, `Unknown Wall-E session: ${prepared.args.sessionId}`);
    }
    if (this.unknownToolHandler) {
      return this.unknownToolHandler(name, args, ctx);
    }

    const local = this.localTools || safeLocalTools();
    if (local?.executeLocalTool) {
      const result = await local.executeLocalTool(name, args);
      if (result !== null && result !== undefined) return result;
    }
    return invalidTool(name, `Unknown tool: ${name}`);
  }

  async _bridgeDefinitions() {
    this.bridgeToolTargets.clear();
    const definitions = [];
    if (this.mcpClient) {
      definitions.push(genericMcpDefinition());
      const tools = await this._listMcpTools();
      for (const tool of tools) {
        const publicName = uniqueBridgeName('mcp', tool.server, tool.name, definitions, this.bridgeToolTargets);
        this.bridgeToolTargets.set(publicName, {
          type: 'mcp',
          server: tool.server || '',
          tool: tool.name,
        });
        definitions.push(normalizeDefinition({
          name: publicName,
          description: bridgeDescription('MCP', tool.server, tool.name, tool.description),
          input_schema: tool.input_schema,
          source: 'mcp',
          sideEffect: 'external',
          approval: 'required',
          supportsParallel: false,
        }));
      }
    }
    if (this.skillRunner) {
      definitions.push(loadSkillDefinition());
      definitions.push(genericSkillDefinition());
      const skills = await this._listSkills();
      for (const skill of skills) {
        const publicName = uniqueBridgeName('skill', '', skill.name, definitions, this.bridgeToolTargets);
        this.bridgeToolTargets.set(publicName, {
          type: 'skill',
          name: skill.name,
        });
        definitions.push(normalizeDefinition({
          name: publicName,
          description: bridgeDescription('Skill', '', skill.name, skill.description),
          input_schema: skill.input_schema,
          source: 'skill',
          sideEffect: 'external',
          approval: 'required',
          supportsParallel: false,
        }));
      }
    }
    if (this.taskTool) {
      definitions.push(normalizeDefinition(this.taskTool.definition()));
    }
    if (this.sessionIndex) {
      definitions.push(sessionSearchDefinition());
      definitions.push(sessionLoadDefinition());
    }
    return definitions;
  }

  async _listMcpTools() {
    const client = this.mcpClient;
    if (!client) return [];
    try {
      if (typeof client.listTools === 'function') {
        return normalizeMcpToolList(await client.listTools());
      }
      if (typeof client.getTools === 'function') {
        return normalizeMcpToolList(await client.getTools());
      }
    } catch {}
    return [];
  }

  async _listSkills() {
    const runner = this.skillRunner;
    if (!runner) return [];
    try {
      const raw = typeof runner.listSkills === 'function'
        ? await runner.listSkills()
        : typeof runner.getSkills === 'function'
          ? await runner.getSkills()
          : Array.isArray(runner.skills)
            ? runner.skills
            : [];
      return normalizeSkillList(raw);
    } catch {}
    return [];
  }

  _callMcpTool(server, tool, args, ctx) {
    if (!this.mcpClient?.callTool) return invalidTool(tool, 'MCP client has no callTool function');
    if (server) return this.mcpClient.callTool(server, tool, args || {}, ctx);
    return this.mcpClient.callTool(tool, args || {}, ctx);
  }

  _runSkill(name, args, ctx) {
    if (!this.skillRunner) return invalidTool(name, 'Skill runner is not configured');
    if (typeof this.skillRunner === 'function') {
      return this.skillRunner({ name, arguments: args || {} }, ctx);
    }
    if (typeof this.skillRunner.run === 'function') {
      return this.skillRunner.run(name, args || {}, ctx);
    }
    if (typeof this.skillRunner.execute === 'function') {
      return this.skillRunner.execute({ name, arguments: args || {} }, ctx);
    }
    return invalidTool(name, 'Skill runner has no execute function');
  }

  async _loadSkill(name, args, ctx) {
    if (!this.skillRunner) return invalidTool(name, 'Skill runner is not configured');
    if (typeof this.skillRunner.loadSkill === 'function') {
      return this.skillRunner.loadSkill(name, args || {}, ctx);
    }
    if (typeof this.skillRunner.getSkill === 'function') {
      const skill = await this.skillRunner.getSkill(name, args || {}, ctx);
      return skill || invalidTool(name, `Skill "${name}" not found`);
    }
    const skills = await this._listSkills();
    const found = skills.find((skill) => skill.name === name);
    if (!found) return invalidTool(name, `Skill "${name}" not found`);
    return { success: true, skill: found };
  }
}

function normalizeDefinition(def = {}) {
  const inputSchema = sanitizeInputSchema(def.input_schema || def.parameters);
  const sideEffect = normalizeSideEffect(def.sideEffect || def.side_effect || inferSideEffect(def.name));
  const readOnly = Boolean(def.readOnly ?? def.read_only ?? sideEffect === 'read');
  const approval = normalizeApproval(def.approval || def.approvalPolicy || (readOnly ? 'never' : 'required'));
  const supportsParallel = Boolean(def.supportsParallel ?? def.supports_parallel ?? (readOnly && approval === 'never'));
  const interruptBehavior = normalizeInterruptBehavior(def.interruptBehavior || def.interrupt_behavior || (readOnly ? 'cancel' : 'block'));
  const resultBudgetBytes = Number.isFinite(Number(def.resultBudgetBytes || def.result_budget_bytes))
    ? Number(def.resultBudgetBytes || def.result_budget_bytes)
    : undefined;
  return {
    ...def,
    name: def.name,
    description: def.description || '',
    input_schema: inputSchema,
    readOnly,
    sideEffect,
    approval,
    supportsParallel,
    interruptBehavior,
    outputPolicy: normalizeOutputPolicy(def.outputPolicy || def.output_policy || 'inline'),
    resultBudgetBytes,
    telemetryPolicy: def.telemetryPolicy || def.telemetry_policy || 'safe-summary',
    renderHint: def.renderHint || def.render_hint || (readOnly ? 'collapsed-read' : 'expanded-action'),
    sandboxProfile: def.sandboxProfile || def.sandbox_profile || (sideEffect === 'read' ? 'readonly' : 'workspace'),
    source: String(def.source || def.kind || ''),
  };
}

function publicDefinition(def = {}) {
  return {
    name: def.name,
    description: def.description || '',
    input_schema: sanitizeInputSchema(def.input_schema || def.parameters),
  };
}

function toolSpec(def = {}) {
  const normalized = normalizeDefinition(def);
  return {
    name: normalized.name,
    description: normalized.description || '',
    input_schema: sanitizeInputSchema(normalized.input_schema || normalized.parameters),
    readOnly: normalized.readOnly,
    sideEffect: normalized.sideEffect,
    approval: normalized.approval,
    supportsParallel: normalized.supportsParallel,
    interruptBehavior: normalized.interruptBehavior,
    outputPolicy: normalized.outputPolicy,
    resultBudgetBytes: normalized.resultBudgetBytes,
    telemetryPolicy: normalized.telemetryPolicy,
    renderHint: normalized.renderHint,
    sandboxProfile: normalized.sandboxProfile,
    source: normalized.source || '',
    toolsets: nameList(normalized.toolsets || normalized.toolset),
  };
}

function invalidTool(name, message) {
  return {
    error: message,
    invalid: true,
    tool: name,
  };
}

function normalizeMcpToolList(raw) {
  const out = [];
  for (const item of flattenToolList(raw)) {
    const name = item.tool || item.name;
    if (!name) continue;
    out.push({
      server: item.server || item.serverName || '',
      name,
      description: item.description || '',
      input_schema: schemaFrom(item),
    });
  }
  return out;
}

function normalizeSkillList(raw) {
  return flattenToolList(raw).map((item) => ({
    name: item.name,
    description: item.description || '',
    input_schema: schemaFrom(item),
  })).filter((item) => item.name);
}

function flattenToolList(raw, inheritedServer = '') {
  if (!raw) return [];
  if (Array.isArray(raw)) {
    return raw.flatMap((item) => flattenToolList(item, inheritedServer));
  }
  if (raw.tools && Array.isArray(raw.tools)) {
    const server = raw.server || raw.serverName || raw.name || inheritedServer;
    return raw.tools.flatMap((tool) => flattenToolList({ ...tool, server: tool.server || server }, server));
  }
  if (raw.result?.tools) return flattenToolList(raw.result.tools, inheritedServer);
  if (raw.servers && typeof raw.servers === 'object') return flattenToolList(raw.servers, inheritedServer);
  if (typeof raw === 'object' && !raw.name && !raw.tool && !raw.inputSchema && !raw.input_schema && !raw.parameters) {
    const rows = [];
    for (const [server, value] of Object.entries(raw)) {
      rows.push(...flattenToolList(value, server));
    }
    return rows;
  }
  return [{ ...raw, server: raw.server || inheritedServer }];
}

function schemaFrom(item = {}) {
  return sanitizeInputSchema(item.input_schema || item.inputSchema || item.parameters);
}

function genericMcpDefinition() {
  return normalizeDefinition({
    name: 'mcp_call',
    description: 'Call an MCP tool by server and tool name.',
    toolsets: ['mcp', 'actions'],
    source: 'mcp',
    sideEffect: 'external',
    approval: 'required',
    supportsParallel: false,
    input_schema: {
      type: 'object',
      properties: {
        server: { type: 'string' },
        tool: { type: 'string' },
        arguments: { type: 'object' },
      },
      required: ['server', 'tool'],
    },
  });
}

function genericSkillDefinition() {
  return normalizeDefinition({
    name: 'skill',
    description: 'Run a Wall-E skill by name with JSON arguments.',
    toolsets: ['skills', 'actions'],
    source: 'skill',
    sideEffect: 'external',
    approval: 'required',
    supportsParallel: false,
    input_schema: {
      type: 'object',
      properties: {
        name: { type: 'string' },
        arguments: { type: 'object' },
      },
      required: ['name'],
    },
  });
}

function loadSkillDefinition() {
  return normalizeDefinition({
    name: 'load_skill',
    description: 'Load a Wall-E skill\'s instructions and metadata without executing it. Use for instruction-only skills such as workflow, review, or debugging guidance.',
    toolsets: ['skills', 'context'],
    source: 'skill',
    sideEffect: 'read',
    approval: 'never',
    supportsParallel: true,
    input_schema: {
      type: 'object',
      properties: {
        name: { type: 'string' },
        max_bytes: { type: 'number', default: 60000 },
      },
      required: ['name'],
    },
  });
}

function sessionSearchDefinition() {
  return normalizeDefinition({
    name: 'session_search',
    description: 'Search Wall-E coding session JSONL transcripts. Uses Wall-E-owned session files, not the CTM or brain database.',
    toolsets: ['sessions', 'memory'],
    source: 'session',
    sideEffect: 'read',
    approval: 'never',
    supportsParallel: true,
    input_schema: {
      type: 'object',
      properties: {
        query: { type: 'string' },
        cwd: { type: 'string' },
        limit: { type: 'number', default: 10 },
        cursor: { type: 'string' },
      },
    },
  });
}

function sessionLoadDefinition() {
  return normalizeDefinition({
    name: 'session_load',
    description: 'Load a Wall-E coding session from its JSONL transcript with messages, tool events, permissions, and workspace status.',
    toolsets: ['sessions', 'memory'],
    source: 'session',
    sideEffect: 'read',
    approval: 'never',
    supportsParallel: true,
    input_schema: {
      type: 'object',
      properties: {
        sessionId: { type: 'string' },
        limit: { type: 'number', default: 200 },
      },
      required: ['sessionId'],
    },
  });
}

async function shouldExposeDefinition(def, ctx = {}) {
  if (!matchesToolsets(def, ctx)) return false;
  const availability = await resolveAvailability(def, ctx);
  return availability.available || Boolean(ctx.includeUnavailable);
}

async function prepareExecution(name, definition, args, ctx = {}) {
  const def = normalizeDefinition(definition);
  const availability = await resolveAvailability(def, ctx);
  if (!availability.available) {
    return invalidTool(name, availability.reason || `Tool is unavailable: ${name}`);
  }
  let preparedArgs = args;
  if (typeof def.prepareArgs === 'function') {
    try {
      const transformed = await def.prepareArgs(args, ctx);
      if (transformed?.invalid) return transformed;
      preparedArgs = transformed && Object.prototype.hasOwnProperty.call(transformed, 'args')
        ? transformed.args
        : transformed;
    } catch (error) {
      return invalidTool(name, error?.message || 'Tool argument normalization failed');
    }
  }
  return validateArguments(name, preparedArgs, def.input_schema);
}

function prepareBridgeArgs(name, args) {
  if (args == null) return { args: {} };
  if (typeof args !== 'object' || Array.isArray(args)) {
    return invalidTool(name, 'Tool arguments must be an object');
  }
  return { args };
}

function validateArguments(name, args, schema = {}) {
  if (args == null) args = {};
  if (typeof args !== 'object' || Array.isArray(args)) {
    return invalidTool(name, 'Tool arguments must be an object');
  }

  const normalizedSchema = sanitizeInputSchema(schema);
  const prepared = { ...args };
  const properties = normalizedSchema.properties || {};
  for (const [key, property] of Object.entries(properties)) {
    if (prepared[key] === undefined && Object.prototype.hasOwnProperty.call(property, 'default')) {
      prepared[key] = property.default;
    }
  }

  const missing = [];
  for (const key of normalizedSchema.required || []) {
    if (prepared[key] === undefined || prepared[key] === null) missing.push(key);
  }
  if (missing.length) {
    return invalidTool(name, `Missing required argument${missing.length === 1 ? '' : 's'}: ${missing.join(', ')}`);
  }
  return { args: prepared };
}

async function resolveAvailability(def, ctx = {}) {
  if (def.enabled === false) return { available: false, reason: 'Tool is disabled' };
  const checker = def.available || def.availability;
  try {
    if (typeof checker === 'function') {
      const result = await checker(ctx);
      return normalizeAvailability(result);
    }
    if (checker && typeof checker === 'object') {
      return normalizeAvailability(checker);
    }
  } catch (error) {
    return { available: false, reason: error?.message || 'Tool availability check failed' };
  }
  return { available: true, reason: '' };
}

function normalizeAvailability(value) {
  if (value === false) return { available: false, reason: 'Tool is unavailable' };
  if (value === true || value == null) return { available: true, reason: '' };
  if (typeof value === 'object') {
    return {
      available: value.available !== false,
      reason: value.reason || '',
    };
  }
  return { available: Boolean(value), reason: '' };
}

function matchesToolsets(def, ctx = {}) {
  const requested = nameList(ctx.toolsets || ctx.toolset);
  if (!requested.length) return true;
  const toolsets = nameList(def.toolsets || def.toolset);
  if (!toolsets.length) return true;
  return toolsets.some((toolset) => requested.includes(toolset));
}

function nameList(value) {
  if (!value) return [];
  const values = Array.isArray(value) ? value : String(value).split(',');
  return values
    .map((item) => String(item || '').trim().toLowerCase())
    .filter(Boolean);
}

function sanitizeInputSchema(schema) {
  if (!schema || typeof schema !== 'object' || Array.isArray(schema)) {
    return { type: 'object', properties: {} };
  }
  const cloned = sanitizeSchemaNode(schema);
  if (!cloned.type) cloned.type = cloned.properties ? 'object' : 'object';
  if (cloned.type !== 'object') cloned.type = 'object';
  if (!cloned.properties || typeof cloned.properties !== 'object' || Array.isArray(cloned.properties)) {
    cloned.properties = {};
  }
  cloned.required = Array.isArray(cloned.required)
    ? cloned.required.filter((key) => typeof key === 'string' && Object.prototype.hasOwnProperty.call(cloned.properties, key))
    : [];
  return cloned;
}

function inferSideEffect(name) {
  const toolName = String(name || '');
  if (/^(think|read_file|read_project_file|list_directory|search_files|search_project|ripgrep|grep|grep_files|glob|find_files|lsp_|session_search|session_load|load_skill|ctm_context|ctm_remote_access_status|ctm_session_search|lookup_person|list_mcp_tools|list_tasks|calendar_events|calendar_list|mail_messages|mail_read|mail_search|slack_search|slack_read_channel|system_info|clipboard_read|check_url|web_fetch|web_search|bg_output)$/.test(toolName)) {
    return 'read';
  }
  if (/^(write_file|edit_file|multi_edit|apply_patch|delete_file|move_file|remember_fact|mail_reply|slack_send_message|calendar_create|create_task|start_coding|notification|open_url|open_app|applescript|reminder_create)$/.test(toolName)) {
    return 'write';
  }
  if (/^(run_shell|shell|bash|terminal|start_static_server|stop_static_server)$/.test(toolName)) {
    return 'process';
  }
  if (/^(web_|fetch|http_|check_url|mail_|slack_|calendar_)/.test(toolName)) {
    return 'network';
  }
  if (/^(mcp_call|mcp__|skill|skill__|task)$/.test(toolName)) {
    return 'external';
  }
  return 'external';
}

function normalizeSideEffect(value) {
  const text = String(value || '').trim();
  return ['read', 'write', 'network', 'process', 'external'].includes(text) ? text : 'external';
}

function normalizeApproval(value) {
  const text = String(value || '').trim();
  return ['never', 'suggest', 'required'].includes(text) ? text : 'required';
}

function normalizeInterruptBehavior(value) {
  const text = String(value || '').trim();
  return text === 'cancel' ? 'cancel' : 'block';
}

function normalizeOutputPolicy(value) {
  const text = String(value || '').trim();
  return ['inline', 'artifact', 'synthesize'].includes(text) ? text : 'inline';
}

function compareToolNames(a, b) {
  return String(a?.name || '').localeCompare(String(b?.name || ''));
}

function sanitizeSchemaNode(node) {
  if (!node || typeof node !== 'object' || Array.isArray(node)) return {};
  const out = {};
  for (const [key, value] of Object.entries(node)) {
    if (typeof value === 'function' || value === undefined) continue;
    if (key === 'properties' && value && typeof value === 'object' && !Array.isArray(value)) {
      out.properties = {};
      for (const [propName, propSchema] of Object.entries(value)) {
        out.properties[propName] = sanitizeSchemaNode(propSchema);
      }
      continue;
    }
    if (key === 'items') {
      out.items = sanitizeSchemaNode(value);
      continue;
    }
    if ((key === 'oneOf' || key === 'anyOf' || key === 'allOf') && Array.isArray(value)) {
      out[key] = value.map(sanitizeSchemaNode);
      continue;
    }
    try {
      out[key] = JSON.parse(JSON.stringify(value));
    } catch {
      out[key] = String(value);
    }
  }
  return out;
}

function bridgeDescription(kind, server, name, description) {
  const prefix = server ? `${kind} ${server}.${name}` : `${kind} ${name}`;
  return description ? `${prefix}: ${description}` : prefix;
}

function uniqueBridgeName(prefix, server, name, definitions, targets) {
  const base = compactToolName(`${prefix}${server ? `__${server}` : ''}__${name}`);
  const used = new Set([
    ...definitions.map((definition) => definition.name),
    ...targets.keys(),
  ]);
  if (!used.has(base)) return base;
  const hash = shortHash(`${prefix}:${server}:${name}`);
  return compactToolName(`${base}__${hash}`);
}

function compactToolName(value) {
  const cleaned = String(value || '')
    .replace(/[^A-Za-z0-9_]+/g, '_')
    .replace(/^_+|_+$/g, '')
    .replace(/_{3,}/g, '__')
    || 'tool';
  if (cleaned.length <= 64) return cleaned;
  const hash = shortHash(cleaned);
  return `${cleaned.slice(0, 55)}_${hash}`;
}

function shortHash(value) {
  let hash = 5381;
  const text = String(value || '');
  for (let i = 0; i < text.length; i++) {
    hash = ((hash << 5) + hash) ^ text.charCodeAt(i);
  }
  return (hash >>> 0).toString(36).slice(0, 8);
}

function safeLocalTools() {
  try {
    return require('../tools/local-tools');
  } catch {
    return null;
  }
}

function createDefaultSessionIndex() {
  const { CodingSessionIndex } = require('./session-index');
  return new CodingSessionIndex();
}

module.exports = {
  ToolRegistry,
  normalizeDefinition,
  publicDefinition,
  toolSpec,
  invalidTool,
  normalizeMcpToolList,
  normalizeSkillList,
  compactToolName,
  sanitizeInputSchema,
  validateArguments,
  matchesToolsets,
  compareToolNames,
  sessionSearchDefinition,
  sessionLoadDefinition,
  loadSkillDefinition,
};
