type CdpCommandOptions = {
  relayPort: number;
  userToken: string;
  targetId?: string;
};

async function sendCdpCommand(opts: CdpCommandOptions, method: string, params?: unknown): Promise<unknown> {
  const { relayPort, userToken, targetId } = opts;
  const url = `ws://127.0.0.1:${relayPort}/cdp?token=${encodeURIComponent(userToken)}`;

  return await new Promise<unknown>((resolve, reject) => {
    const ws = new WebSocket(url);
    let settled = false;
    const timeout = setTimeout(() => {
      if (settled) return;
      settled = true;
      ws.close();
      reject(new Error(`CDP command timeout: ${method}`));
    }, 30_000);

    ws.addEventListener('open', () => {
      const cmd: Record<string, unknown> = { id: 1, method };
      if (params) cmd.params = params;
      if (targetId) cmd.sessionId = targetId;
      ws.send(JSON.stringify(cmd));
    });

    ws.addEventListener('message', (event) => {
      if (settled) return;
      try {
        const msg = JSON.parse(String(event.data)) as { id?: number; result?: unknown; error?: { message: string } };
        if (msg.id === 1) {
          settled = true;
          clearTimeout(timeout);
          ws.close();
          if (msg.error) reject(new Error(msg.error.message));
          else resolve(msg.result);
        }
      } catch {
        // ignore parse errors, wait for correct message
      }
    });

    ws.addEventListener('error', () => {
      if (settled) return;
      settled = true;
      clearTimeout(timeout);
      reject(new Error('CDP WebSocket connection failed'));
    });

    ws.addEventListener('close', () => {
      if (settled) return;
      settled = true;
      clearTimeout(timeout);
      reject(new Error('CDP WebSocket closed before response'));
    });
  });
}

export async function captureScreenshot(
  opts: CdpCommandOptions,
  format: 'png' | 'jpeg' = 'png',
  quality?: number,
): Promise<string> {
  const params: Record<string, unknown> = { format };
  if (quality !== undefined) params.quality = quality;
  const result = (await sendCdpCommand(opts, 'Page.captureScreenshot', params)) as { data: string };
  return result.data;
}

export async function evaluateJS(opts: CdpCommandOptions, expression: string): Promise<unknown> {
  const result = (await sendCdpCommand(opts, 'Runtime.evaluate', {
    expression,
    returnByValue: true,
    awaitPromise: true,
  })) as { result?: { value?: unknown; description?: string }; exceptionDetails?: { text?: string } };

  if (result.exceptionDetails) {
    throw new Error(result.exceptionDetails.text ?? 'Evaluation failed');
  }
  return result.result?.value;
}

export async function navigateTo(opts: CdpCommandOptions, url: string): Promise<void> {
  await sendCdpCommand(opts, 'Page.navigate', { url });
}

export async function getPageInfo(opts: CdpCommandOptions): Promise<{ title: string; url: string }> {
  const result = (await sendCdpCommand(opts, 'Runtime.evaluate', {
    expression: 'JSON.stringify({ title: document.title, url: location.href })',
    returnByValue: true,
  })) as { result?: { value?: string } };

  try {
    return JSON.parse(result.result?.value ?? '{}');
  } catch {
    return { title: '', url: '' };
  }
}
