import { describe, it, expect } from 'vitest';
import { z } from 'zod';
import {
  ALL_TOOLS,
  TOOLS_BY_NAME,
  appendMcpTools,
  toolJsonSchemas,
  type ToolDef,
} from '../tools.js';

// Parity test for the register-through MCP-discovery contract (Phase 6 split).
// `ALL_TOOLS` / `TOOLS_BY_NAME` are `let`-bound in tools/registry.ts and
// reassigned by appendMcpTools() at startup; this barrel re-exports them.
// apps/coder relies on this exact behavior: it imports `appendMcpTools` + the
// live `ALL_TOOLS` binding from @boocode/server/tools, calls appendMcpTools()
// once, then reads ALL_TOOLS. ESM live bindings must carry the mutation
// through the barrel re-export — if the split ever snapshots the array instead
// of re-exporting the live binding, these assertions fail. Each test file gets
// an isolated module instance (vitest default), so mutating the registry here
// does not leak into tools.test.ts.
function makeFakeMcpTool(name: string): ToolDef<unknown> {
  return {
    name,
    description: `fake mcp tool ${name}`,
    inputSchema: z.object({}) as z.ZodType<unknown>,
    jsonSchema: {
      type: 'function',
      function: {
        name,
        description: `fake mcp tool ${name}`,
        parameters: { type: 'object', properties: {}, additionalProperties: false },
      },
    },
    async execute() {
      return { ok: true };
    },
  };
}

describe('appendMcpTools register-through contract', () => {
  it('is a no-op for an empty array', () => {
    const before = ALL_TOOLS.length;
    appendMcpTools([]);
    expect(ALL_TOOLS.length).toBe(before);
  });

  it('mutates the live ALL_TOOLS / TOOLS_BY_NAME bindings observable through the barrel', () => {
    const before = ALL_TOOLS.length;
    // Names chosen so insertion lands away from the array ends, proving the
    // re-sort runs (a naive concat would leave them at the tail).
    const a = makeFakeMcpTool('mcp__alpha__probe');
    const z2 = makeFakeMcpTool('mcp__zeta__probe');
    appendMcpTools([z2, a]);

    expect(ALL_TOOLS.length).toBe(before + 2);
    expect(TOOLS_BY_NAME['mcp__alpha__probe']).toBe(a);
    expect(TOOLS_BY_NAME['mcp__zeta__probe']).toBe(z2);

    // Still alpha-sorted after the append (prompt-cache stability invariant).
    const names = ALL_TOOLS.map((t) => t.name);
    expect(names).toEqual([...names].sort((x, y) => x.localeCompare(y)));

    // toolJsonSchemas() reads through the same live binding.
    const schemaNames = toolJsonSchemas().map((s) => s.function.name);
    expect(schemaNames).toContain('mcp__alpha__probe');
    expect(schemaNames).toContain('mcp__zeta__probe');
  });
});
