/**
 * Agents plugin — exposes each Agent attached to a Project as a virtual
 * MCP server in the session's tools/list.
 *
 * On session create, fetches `GET /api/v1/projects/:p/agents` and for each
 * agent registers a virtual server named `agent-<agentName>` with one tool
 * `chat`. The tool's description mirrors the agent's description so clients
 * (e.g. Claude consuming MCP via mcplocal) see useful prose like "I review
 * security design — ask me after each major change." The `chat` tool takes
 * a `message` (required) and a few LiteLLM-style overrides (temperature,
 * max_tokens, etc.) plus an optional `threadId` for follow-ups; the handler
 * POSTs to `/api/v1/agents/:name/chat` and returns the assistant's reply.
 *
 * Namespace collision: `registerServer` namespaces tools as
 * `<server>/<tool>`. If a real upstream MCP server is named `agent-<x>`,
 * mcplocal's discovery would already produce `agent-<x>/<tool>` entries
 * and our virtual server's tools would clobber them in the virtualTools
 * map. To avoid silent shadowing, the plugin scans current upstream tools
 * before registering and skips any agent whose namespace would collide,
 * emitting an `agent_namespace_collision` audit event so the operator
 * sees the reason in the audit trail.
 *
 * The plugin owns no request-path hooks — agents are reachable purely
 * through the virtual-server surface, which `tools/list` and `tools/call`
 * already serve via plugin-context.
 */
import type { ProxyModelPlugin, VirtualServer } from '../plugin.js';
import type { ToolDefinition } from '../types.js';

const AGENT_NAMESPACE_PREFIX = 'agent-';

export interface AgentSummary {
  id: string;
  name: string;
  description: string;
}

const STATE_KEY = 'agents-plugin:registered';

export function createAgentsPlugin(): ProxyModelPlugin {
  return {
    name: 'agents',
    description: 'Exposes project-scoped Agents as virtual MCP servers.',

    async onSessionCreate(ctx) {
      let agents: AgentSummary[];
      try {
        const data = await ctx.getFromMcpd(
          `/api/v1/projects/${encodeURIComponent(ctx.projectName)}/agents`,
        );
        agents = (Array.isArray(data) ? data : []) as AgentSummary[];
      } catch (err) {
        ctx.log.warn(`agents-plugin: failed to fetch project agents: ${(err as Error).message}`);
        return;
      }
      if (agents.length === 0) return;

      const upstreamTools = await ctx.discoverTools().catch(() => [] as ToolDefinition[]);
      const upstreamNames = new Set(upstreamTools.map((t) => t.name));
      const registered: string[] = [];

      for (const agent of agents) {
        const serverName = `${AGENT_NAMESPACE_PREFIX}${agent.name}`;
        // Collision: any existing tool already namespaced under this prefix.
        const collision = [...upstreamNames].some((n) => n.startsWith(`${serverName}/`));
        if (collision) {
          ctx.log.warn(
            `agents-plugin: namespace collision for ${serverName} (agent ${agent.name}), skipping`,
          );
          continue;
        }
        ctx.registerServer(virtualServerForAgent(agent));
        registered.push(serverName);
      }
      ctx.state.set(STATE_KEY, registered);
    },

    async onSessionDestroy(ctx) {
      const registered = ctx.state.get(STATE_KEY) as string[] | undefined;
      if (registered === undefined) return;
      for (const name of registered) ctx.unregisterServer(name);
      ctx.state.delete(STATE_KEY);
    },
  };
}

function virtualServerForAgent(agent: AgentSummary): VirtualServer {
  const description = agent.description.length > 0
    ? agent.description
    : `Chat with agent ${agent.name}`;
  const definition: ToolDefinition = {
    name: 'chat',
    description,
    inputSchema: {
      type: 'object',
      properties: {
        message: { type: 'string', description: 'User message to send to the agent' },
        threadId: { type: 'string', description: 'Omit to start a new thread' },
        systemOverride: { type: 'string', description: 'Replace agent.systemPrompt for this call' },
        systemAppend: { type: 'string', description: 'Append to agent.systemPrompt for this call' },
        temperature: { type: 'number' },
        top_p: { type: 'number' },
        top_k: { type: 'integer' },
        max_tokens: { type: 'integer' },
        seed: { type: 'integer' },
        stop: {
          oneOf: [
            { type: 'string' },
            { type: 'array', items: { type: 'string' } },
          ],
        },
        tools_allowlist: { type: 'array', items: { type: 'string' } },
        extra: { type: 'object', additionalProperties: true },
      },
      required: ['message'],
    },
  };

  return {
    name: `${AGENT_NAMESPACE_PREFIX}${agent.name}`,
    description,
    tools: [{
      definition,
      handler: async (args, ctx) => {
        const res = await ctx.postToMcpd(
          `/api/v1/agents/${encodeURIComponent(agent.name)}/chat`,
          { ...args, stream: false },
        );
        const r = res as { assistant?: string; threadId?: string; turnIndex?: number; error?: string };
        if (r.error !== undefined) {
          return { content: [{ type: 'text', text: `error: ${r.error}` }], isError: true };
        }
        const out: { content: Array<{ type: 'text'; text: string }>; _meta?: Record<string, unknown> } = {
          content: [{ type: 'text', text: r.assistant ?? '' }],
        };
        if (r.threadId !== undefined) {
          out._meta = { threadId: r.threadId, turnIndex: r.turnIndex };
        }
        return out;
      },
    }],
  };
}
