import { z } from 'zod';
import { compactStringify } from "../../../presenters/json.js";
import ImmutableBytes from "../../../../src/domain/services/snapshot/ImmutableBytes.js";
import McpProtocolError from "./McpProtocolError.js";
const EMPTY_ARGS_SCHEMA = z.object({}).strict();
const NODE_ID_ARGS_SCHEMA = z.object({ nodeId: z.string().min(1) }).strict();
const EMPTY_INPUT_SCHEMA = Object.freeze({
    type: 'object',
    properties: Object.freeze({}),
    additionalProperties: false,
});
const NODE_ID_INPUT_SCHEMA = Object.freeze({
    type: 'object',
    properties: Object.freeze({
        nodeId: Object.freeze({ type: 'string', minLength: 1 }),
    }),
    required: Object.freeze(['nodeId']),
    additionalProperties: false,
});
const REGISTERED_TOOLS = Object.freeze([
    Object.freeze({
        name: 'warp_info',
        description: 'Return graph identity and MCP server posture.',
        inputSchema: EMPTY_INPUT_SCHEMA,
        handle: warpInfo,
    }),
    Object.freeze({
        name: 'warp_nodes',
        description: 'List visible node ids for the opened graph.',
        inputSchema: EMPTY_INPUT_SCHEMA,
        handle: warpNodes,
    }),
    Object.freeze({
        name: 'warp_node_props',
        description: 'Read visible properties for a single node id.',
        inputSchema: NODE_ID_INPUT_SCHEMA,
        handle: warpNodeProps,
    }),
    Object.freeze({
        name: 'warp_edges',
        description: 'List visible edges for the opened graph.',
        inputSchema: EMPTY_INPUT_SCHEMA,
        handle: warpEdges,
    }),
    Object.freeze({
        name: 'warp_has_node',
        description: 'Return whether a visible node id exists.',
        inputSchema: NODE_ID_INPUT_SCHEMA,
        handle: warpHasNode,
    }),
]);
const TOOLS_BY_NAME = new Map(REGISTERED_TOOLS.map((tool) => [tool.name, tool]));
export function listMcpTools() {
    return Object.freeze(REGISTERED_TOOLS.map((tool) => Object.freeze({
        name: tool.name,
        description: tool.description,
        inputSchema: tool.inputSchema,
    })));
}
export async function callMcpTool(graph, name, args) {
    const tool = TOOLS_BY_NAME.get(name);
    if (tool === undefined) {
        throw new McpProtocolError(-32602, `Unknown MCP tool: ${name}`);
    }
    return toolResponse(await tool.handle(graph, args));
}
function warpInfo(graph, args) {
    parseEmptyArgs(args);
    return Promise.resolve({
        graph: graph.graphName,
        writer: graph.writerId,
        posture: 'read-only',
    });
}
async function warpNodes(graph, args) {
    parseEmptyArgs(args);
    return { nodes: await graph.getNodes() };
}
async function warpNodeProps(graph, args) {
    const { nodeId } = parseNodeIdArgs(args);
    const props = await graph.getNodeProps(nodeId);
    return {
        nodeId,
        props: props === null ? null : propertyBagToJson(props),
    };
}
async function warpEdges(graph, args) {
    parseEmptyArgs(args);
    return { edges: (await graph.getEdges()).map(edgeToJson) };
}
async function warpHasNode(graph, args) {
    const { nodeId } = parseNodeIdArgs(args);
    return { nodeId, exists: await graph.hasNode(nodeId) };
}
function parseEmptyArgs(args) {
    const parsed = EMPTY_ARGS_SCHEMA.safeParse(args);
    if (!parsed.success) {
        throw invalidToolInput(parsed.error.issues.map((issue) => issue.message));
    }
}
function parseNodeIdArgs(args) {
    const parsed = NODE_ID_ARGS_SCHEMA.safeParse(args);
    if (parsed.success) {
        return parsed.data;
    }
    throw invalidToolInput(parsed.error.issues.map((issue) => issue.message));
}
function invalidToolInput(issues) {
    return new McpProtocolError(-32602, 'Invalid MCP tool input', { issues });
}
function edgeToJson(edge) {
    return {
        from: edge.from,
        to: edge.to,
        label: edge.label,
        props: propertyBagToJson(edge.props),
    };
}
function propertyBagToJson(props) {
    const bag = {};
    for (const [key, value] of Object.entries(props)) {
        bag[key] = propValueToJson(value);
    }
    return Object.freeze(bag);
}
function propValueToJson(value) {
    if (value instanceof ImmutableBytes) {
        return { type: 'bytes', value: value.toArray() };
    }
    if (isSnapshotPropArray(value)) {
        return Object.freeze(value.map(propValueToJson));
    }
    if (value !== null && typeof value === 'object') {
        return propObjectToJson(value);
    }
    return value;
}
function isSnapshotPropArray(value) {
    return Array.isArray(value);
}
function propObjectToJson(value) {
    const objectValue = {};
    for (const [key, entry] of Object.entries(value)) {
        objectValue[key] = propValueToJson(entry);
    }
    return Object.freeze(objectValue);
}
function toolResponse(payload) {
    return {
        content: [{ type: 'text', text: compactStringify(payload) }],
        structuredContent: payload,
    };
}
