// Voice tool surface.
//
// Design: voice IS the main agent. Same tool catalog, same gates. The Realtime
// model sees every tool the typed main agent sees — CORE_TOOLS (create_agent,
// upsert_schedule, competitor_report, generate_ui, send_to_hermes,
// whatsapp_send, find_artifacts, …) plus every enabled marketplace tool
// (gh_*, supabase_*, email_send, flight_search, voice_speak, …).
//
// Two invariants:
//
//   1. Every call routes through `executeTool` from lib/orchestrator-tools.js,
//      which itself delegates to `executeRegistryTool` for marketplace tools.
//      Trust-mode, approval gating, allowlists, rate limits, audit, egress —
//      same as typed chat. Voice gets no privileged path.
//
//   2. Schema shape conversion: typed agent uses Anthropic-style tool schemas
//      (`{ name, description, input_schema }`); OpenAI Realtime needs
//      (`{ type: 'function', name, description, parameters }`). The conversion
//      happens here so the rest of the stack stays untouched.
//
// Tools are returned in two shapes:
//   - `getVoiceToolsForRealtime()` — OpenAI Realtime `session.tools` shape.
//   - `getVoiceToolCatalog()` — UI-friendly { name, title, description,
//     risk_level } for the panel chip list.

import { executeTool, getTools } from '../orchestrator-tools.js';
import { listMarketplaceTools } from '../tools/registry.js';

function humanTitle(toolName) {
  return String(toolName || '')
    .replace(/[_-]+/g, ' ')
    .replace(/\b\w/g, c => c.toUpperCase());
}

function riskFor(toolName) {
  const m = listMarketplaceTools().find(t => t.name === toolName);
  if (m?.risk_level) return m.risk_level;
  // CORE_TOOLS heuristics — anything that writes externally or destructively
  // is flagged medium so the panel chip-list communicates blast radius. The
  // actual gate is enforced by executeTool / executeRegistryTool, not here.
  if (/^(delete_|send_|whatsapp_|email_|gh_pr_merge|supabase_(update|delete|sql|rpc)|stripe_)/.test(toolName)) {
    return 'medium';
  }
  return 'low';
}

function toRealtimeShape(tool) {
  // Anthropic shape → OpenAI Realtime shape. `input_schema` is JSON Schema,
  // which is exactly what Realtime expects under `parameters`. If a tool
  // forgot to define one, fall back to an empty object schema so Realtime
  // does not reject the tool entry.
  return {
    type: 'function',
    name: tool.name,
    description: tool.description || '',
    parameters: tool.input_schema || { type: 'object', properties: {} },
  };
}

function mainAgentToolList() {
  // getTools() = CORE_TOOLS (minus Hermes tools when Hermes is unavailable)
  // plus every enabled+available marketplace tool. Identical to what the
  // typed main agent receives when invoking Anthropic.
  return getTools();
}

export function getVoiceToolCatalog() {
  return mainAgentToolList().map(tool => ({
    name: tool.name,
    title: humanTitle(tool.name),
    description: tool.description || '',
    risk_level: riskFor(tool.name),
  }));
}

export function getVoiceToolsForRealtime() {
  return mainAgentToolList().map(toRealtimeShape);
}

export function isVoiceTool(name) {
  if (!name) return false;
  return mainAgentToolList().some(tool => tool.name === name);
}

/**
 * Execute a voice-side tool call. Always routes through `executeTool` so the
 * trust-mode, approval gate, allowlist, rate-limit, egress, and audit chains
 * fire just as they do for typed chat. Voice is NOT a privileged caller.
 *
 * @param {string} name
 * @param {object} args
 * @param {{ project_id?: string|null, agent_id?: string|null }} [context]
 */
export async function executeVoiceTool(name, args = {}, context = {}) {
  if (!name) throw new Error('voice tool name is required');
  return executeTool(name, args || {}, {
    project_id: context?.project_id ?? null,
    agent_id: context?.agent_id ?? null,
    triggered_by: 'voice',
  });
}
