/**
 * @license
 * Copyright 2025 Google LLC
 * SPDX-License-Identifier: Apache-2.0
 */

import type { Transport } from '@modelcontextprotocol/sdk/shared/transport.js';
import type {
  JSONRPCMessage,
  JSONRPCResponse,
} from '@modelcontextprotocol/sdk/types.js';
import { EventEmitter } from 'node:events';

/**
 * A wrapper transport that intercepts messages from MCP servers and fixes
 * non-compliant responses.
 *
 * Issue: Some MCP servers (e.g., Xcode 26.3's mcpbridge) return tool results in
 * `content` but miss `structuredContent` when the tool has an output schema.
 *
 * Fix: Parse the text content as JSON and populate `structuredContent` if it's missing.
 */
export class McpComplianceTransport extends EventEmitter implements Transport {
  constructor(readonly transport: Transport) {
    super();

    // Forward messages from the underlying transport
    this.transport.onmessage = (message) => {
      this.handleMessage(message);
    };

    this.transport.onclose = () => {
      this.onclose?.();
    };

    this.transport.onerror = (error) => {
      this.onerror?.(error);
    };
  }

  // Transport interface implementation
  onclose?: () => void;
  onerror?: (error: Error) => void;
  onmessage?: (message: JSONRPCMessage) => void;

  async start(): Promise<void> {
    await this.transport.start();
  }

  async close(): Promise<void> {
    await this.transport.close();
  }

  async send(message: JSONRPCMessage): Promise<void> {
    await this.transport.send(message);
  }

  private handleMessage(message: JSONRPCMessage) {
    if (this.isJsonResponse(message)) {
      this.fixStructuredContent(message);
    }
    this.onmessage?.(message);
  }

  private isJsonResponse(message: JSONRPCMessage): message is JSONRPCResponse {
    return 'result' in message || 'error' in message;
  }

  private fixStructuredContent(response: JSONRPCResponse) {
    if (!('result' in response)) return;

    // We can cast because we verified 'result' is in response,
    // but TS might still be picky if the type is a strict union.
    // Let's treat it safely.
    // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-type-assertion, @typescript-eslint/no-unsafe-assignment
    const result = response.result as any;

    // Check if we have content but missing structuredContent
    if (
      result.content &&
      Array.isArray(result.content) &&
      result.content.length > 0 &&
      !result.structuredContent
    ) {
      // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
      const firstItem = result.content[0];
      if (firstItem.type === 'text' && typeof firstItem.text === 'string') {
        try {
          // Attempt to parse the text as JSON
          // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
          const parsed = JSON.parse(firstItem.text);
          // If successful, populate structuredContent
          // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
          result.structuredContent = parsed;
        } catch {
          // Ignored: Content is likely plain text, not JSON.
        }
      }
    }
  }
}
