// src/playwrightMcp.ts
import http, { IncomingMessage, ServerResponse } from 'http';
import { AddressInfo } from 'net';
import { SSEServerTransport } from '@modelcontextprotocol/sdk/server/sse.js';

/** Handle returned to testRunner so it can shut the server down. */
export interface PlaywrightMcpHandle {
  baseUrl: string; // host:port (NO path)
  shutdown(): Promise<void>;
  closeTransport(): void;
}

/**
 * Start an in-process Playwright-MCP server.
 *  • Binds on 127.0.0.1:0  (OS chooses a free port)
 *  • Exposes one route  /messages
 *      – GET  → opens SSE stream
 *      – POST → delivers JSON-RPC command via same transport
 */
export async function startPlaywrightMcp(): Promise<PlaywrightMcpHandle> {
  /* ── 1.  Create bare HTTP server on a free port ─────────────────── */
  const server = http.createServer();
  await new Promise<void>((ok) => server.listen(0, '127.0.0.1', ok));
  const { port } = server.address() as AddressInfo;
  const baseUrl = `http://127.0.0.1:${port}`;

  /* ── 2.  Dynamically import the ESM-only @playwright/mcp ─────────── */
  // Done via Function wrapper so ts-node (CommonJS) doesn’t rewrite to require().
  const { createConnection } = await (new Function('spec', 'return import(spec)')(
    '@playwright/mcp'
  ) as Promise<{ createConnection: any }>);

  /* ── 3.  Spin up the headless browser MCP connection ────────────── */
  const mcp = await createConnection({
    browser: {
      isolated: true, // no persistent profile
      launchOptions: { headless: true },
    },
    capabilities: ['tabs'],
  });

  /* ── 4.  One SSE transport shared for the whole session ─────────── */
  let transport: SSEServerTransport | undefined;

  server.on('request', async (req: IncomingMessage, res: ServerResponse) => {
    if (!req.url?.startsWith('/messages')) {
      res.writeHead(404).end('Not found');
      return;
    }

    if (req.method === 'GET') {
      // First GET establishes the SSE stream
      transport = new SSEServerTransport('/messages', res as any);
      await mcp.connect(transport); // handshake -> “endpoint” event
      return; // SSE handler keeps res open
    }

    if (req.method === 'POST') {
      if (!transport) {
        res.writeHead(503).end('SSE not established');
        return;
      }

      // Collect body (JSON-RPC message)
      const chunks: Buffer[] = [];
      req.on('data', (c) => chunks.push(c));
      req.on('end', async () => {
        try {
          await (transport as any).handlePostMessage(
            req,
            res,
            Buffer.concat(chunks).toString('utf8')
          );
        } catch (e: any) {
          res.writeHead(500).end(e.message ?? 'internal error');
        }
      });
      return;
    }

    res.writeHead(405).end('Method not allowed');
  });

  console.log(`[playwright-mcp] listening on ${baseUrl}/messages`);
  return {
    baseUrl, // testRunner adds /messages
    shutdown: () => new Promise<void>((ok) => server.close(() => ok())),
    closeTransport: () => {
      // sdk ≥0.7 provides transport.close();  fallback to res.end()
      try {
        (transport as any)?.close?.();
      } catch {
        try {
          (transport as any)?.res?.end();
        } catch {
          /* ignore */
        }
      }
    },
  };
}
