/**
 * MCP App SDK Shim
 * Bridges the app ↔ host communication via postMessage.
 * Compatible with Claude Desktop, Goose, VS Code, and standalone browser.
 */

type EventHandler = (data: any) => void;

interface McpApp {
  on(event: string, handler: EventHandler): void;
  off(event: string, handler: EventHandler): void;
  callServerTool(name: string, args: Record<string, unknown>): Promise<any>;
  updateModelContext(data: { text: string }): void;
  sendMessage(data: { text: string }): void;
  connect(): Promise<void>;
  getHostCapabilities(): { serverTools?: boolean } | null;
}

let requestId = 0;
const pendingRequests = new Map<number, { resolve: (v: any) => void; reject: (e: Error) => void }>();

export function createMcpApp(): McpApp {
  const handlers = new Map<string, Set<EventHandler>>();
  let hostSource: MessageEventSource | null = null;
  let hostCapabilities: { serverTools?: boolean } | null = null;

  function emit(event: string, data: any) {
    const set = handlers.get(event);
    if (set) {
      for (const fn of set) {
        try { fn(data); } catch (e) { console.error(`[mcp-app] handler error for ${event}:`, e); }
      }
    }
  }

  // Listen for messages from host
  window.addEventListener('message', (event: MessageEvent) => {
    const msg = event.data;
    if (!msg || typeof msg !== 'object') return;

    // Track host source for replies
    if (event.source && !hostSource) {
      hostSource = event.source;
    }

    // JSON-RPC response (for callServerTool)
    if (msg.jsonrpc === '2.0' && msg.id != null && pendingRequests.has(msg.id)) {
      const pending = pendingRequests.get(msg.id)!;
      pendingRequests.delete(msg.id);
      if (msg.error) {
        pending.reject(new Error(msg.error.message || 'Server tool error'));
      } else {
        pending.resolve(msg.result);
      }
      return;
    }

    // Host initialization
    if (msg.jsonrpc === '2.0' && msg.method === 'ui/initialize') {
      hostCapabilities = msg.params?.hostCapabilities || null;
      // Reply with init response
      const reply = { jsonrpc: '2.0', id: msg.id, result: { capabilities: {} } };
      if (event.source) {
        (event.source as Window).postMessage(reply, '*');
      }
      emit('init', msg.params);
      return;
    }

    // Tool result
    if (msg.jsonrpc === '2.0' && msg.method === 'tool/result') {
      emit('toolresult', msg.params);
      return;
    }

    // Tool input
    if (msg.jsonrpc === '2.0' && msg.method === 'tool/input') {
      emit('toolinput', msg.params);
      return;
    }

    // Host context changed
    if (msg.jsonrpc === '2.0' && msg.method === 'host/contextChanged') {
      emit('hostcontextchanged', msg.params);
      return;
    }

    // Legacy/custom message formats
    if (msg.type === 'mcp-app-data' || msg.type === 'mcp-app-init') {
      emit('toolresult', msg.data);
      return;
    }

    // structuredContent passed directly
    if (msg.structuredContent) {
      emit('toolresult', msg.structuredContent);
      return;
    }
  });

  // Also check for inline data (injected by server)
  if ((window as any).__MCP_APP_DATA__) {
    setTimeout(() => emit('toolresult', (window as any).__MCP_APP_DATA__), 0);
  }

  return {
    on(event: string, handler: EventHandler) {
      if (!handlers.has(event)) handlers.set(event, new Set());
      handlers.get(event)!.add(handler);
    },

    off(event: string, handler: EventHandler) {
      handlers.get(event)?.delete(handler);
    },

    async callServerTool(name: string, args: Record<string, unknown>): Promise<any> {
      const id = ++requestId;
      const message = {
        jsonrpc: '2.0',
        id,
        method: 'tools/call',
        params: { name, arguments: args },
      };

      return new Promise((resolve, reject) => {
        pendingRequests.set(id, { resolve, reject });

        // Try host window first, then parent
        const target = hostSource as Window || window.parent;
        if (target && target !== window) {
          target.postMessage(message, '*');
        } else {
          // Standalone mode — simulate async
          pendingRequests.delete(id);
          reject(new Error('No host connected. Running in standalone mode.'));
        }

        // Timeout after 30s
        setTimeout(() => {
          if (pendingRequests.has(id)) {
            pendingRequests.delete(id);
            reject(new Error('callServerTool timed out'));
          }
        }, 30000);
      });
    },

    updateModelContext(data: { text: string }) {
      const message = {
        jsonrpc: '2.0',
        method: 'context/update',
        params: data,
      };
      const target = hostSource as Window || window.parent;
      if (target && target !== window) {
        target.postMessage(message, '*');
      }
    },

    sendMessage(data: { text: string }) {
      const message = {
        jsonrpc: '2.0',
        method: 'message/send',
        params: data,
      };
      const target = hostSource as Window || window.parent;
      if (target && target !== window) {
        target.postMessage(message, '*');
      }
    },

    async connect() {
      // Signal readiness
      const target = window.parent;
      if (target && target !== window) {
        target.postMessage({ jsonrpc: '2.0', method: 'ui/ready', params: {} }, '*');
      }
    },

    getHostCapabilities() {
      return hostCapabilities;
    },
  };
}
