declare const acquireVsCodeApi: () => {
  postMessage: (message: unknown) => void;
  getState: () => unknown;
  setState: (state: unknown) => void;
};

interface VSCodeAPI {
  postMessage: (message: unknown) => void;
}

let vscodeApi: VSCodeAPI | null = null;

function getVSCodeAPI(): VSCodeAPI {
  if (!vscodeApi) {
    vscodeApi = acquireVsCodeApi();
  }
  return vscodeApi;
}

interface BridgeRequest {
  id: string;
  type: string;
  payload?: unknown;
}

interface BridgeResponse {
  id: string;
  type: string;
  success: boolean;
  data?: unknown;
  error?: string;
}

const pendingRequests = new Map<string, {
  resolve: (value: unknown) => void;
  reject: (reason: Error) => void;
  timeout?: ReturnType<typeof setTimeout>;
  onAbort?: () => void;
}>();

let requestIdCounter = 0;

window.addEventListener('message', (event: MessageEvent<BridgeResponse>) => {
  const response = event.data;
  if (!response || typeof response.id !== 'string') return;

  const messageId = (response as BridgeResponse & { _msgId?: unknown })._msgId;
  if (typeof messageId === 'string' && messageId.length > 0) {
    getVSCodeAPI().postMessage({ type: 'bridge:ack', _msgId: messageId });
  }

  const pending = pendingRequests.get(response.id);
  if (pending) {
    pendingRequests.delete(response.id);
    if (pending.timeout) {
      clearTimeout(pending.timeout);
    }
    if (pending.onAbort) {
      pending.onAbort();
    }
    if (response.success) {
      pending.resolve(response.data);
    } else {
      pending.reject(new Error(response.error || 'Unknown error'));
    }
  }
});

export function sendBridgeMessage<T = unknown>(type: string, payload?: unknown): Promise<T> {
  return sendBridgeMessageWithOptions<T>(type, payload);
}

export function sendBridgeMessageWithOptions<T = unknown>(
  type: string,
  payload?: unknown,
  options?: { timeoutMs?: number; signal?: AbortSignal; onAbort?: (id: string) => void }
): Promise<T> {
  return new Promise((resolve, reject) => {
    const id = `req_${++requestIdCounter}_${Date.now()}`;
    const request: BridgeRequest = { id, type, payload };

    const pending: {
      resolve: (value: unknown) => void;
      reject: (reason: Error) => void;
      timeout?: ReturnType<typeof setTimeout>;
      onAbort?: () => void;
    } = {
      resolve: resolve as (value: unknown) => void,
      reject,
    };

    if (options?.signal) {
      const abort = () => {
        if (!pendingRequests.has(id)) return;
        pendingRequests.delete(id);
        if (pending.timeout) {
          clearTimeout(pending.timeout);
        }
        options.onAbort?.(id);
        reject(new DOMException('Aborted', 'AbortError'));
      };
      if (options.signal.aborted) {
        reject(new DOMException('Aborted', 'AbortError'));
        return;
      }
      options.signal.addEventListener('abort', abort, { once: true });
      pending.onAbort = () => options.signal?.removeEventListener('abort', abort);
    }

    pendingRequests.set(id, pending);

    const timeoutMs = typeof options?.timeoutMs === 'number' ? options.timeoutMs : 30000;
    if (Number.isFinite(timeoutMs) && timeoutMs > 0) {
      pending.timeout = setTimeout(() => {
        if (pendingRequests.has(id)) {
          pendingRequests.delete(id);
          if (pending.onAbort) {
            pending.onAbort();
          }
          reject(new Error(`Request ${type} timed out`));
        }
      }, timeoutMs);
    }

    getVSCodeAPI().postMessage(request);
  });
}

export type ProxiedApiResponse = {
  status: number;
  headers: Record<string, string>;
  bodyBase64?: string;
  bodyText?: string;
};

export async function proxyApiRequest(options: {
  method: string;
  path: string;
  headers?: Record<string, string>;
  bodyBase64?: string;
  signal?: AbortSignal;
}): Promise<ProxiedApiResponse> {
  // Do not impose a bridge-level timeout. Let the original fetch's AbortSignal
  // (or OpenCode server response timing) control the lifecycle.
  const { signal, ...payload } = options;
  return sendBridgeMessageWithOptions<ProxiedApiResponse>('api:proxy', payload, {
    timeoutMs: 0,
    signal,
    onAbort: (requestID) => getVSCodeAPI().postMessage({ id: `abort_${requestID}`, type: 'api:proxy:abort', payload: { requestID } }),
  });
}

export async function proxySessionMessageRequest(options: {
  path: string;
  headers?: Record<string, string>;
  bodyText: string;
  signal?: AbortSignal;
}): Promise<ProxiedApiResponse> {
  // Keep parity with server-side direct forwarder: let extension host control timeout.
  const { signal, ...payload } = options;
  return sendBridgeMessageWithOptions<ProxiedApiResponse>('api:session:message', payload, {
    timeoutMs: 0,
    signal,
    onAbort: (requestID) => getVSCodeAPI().postMessage({ id: `abort_${requestID}`, type: 'api:proxy:abort', payload: { requestID } }),
  });
}

export type ProxiedSseStartResponse = {
  status: number;
  headers: Record<string, string>;
  streamId: string | null;
  error?: string;
};

export async function startSseProxy(options: {
  path: string;
  headers?: Record<string, string>;
}): Promise<ProxiedSseStartResponse> {
  return sendBridgeMessage<ProxiedSseStartResponse>('api:sse:start', options);
}

export async function stopSseProxy(options: { streamId: string }): Promise<{ stopped: boolean }> {
  return sendBridgeMessage<{ stopped: boolean }>('api:sse:stop', options);
}

export async function executeVSCodeCommand(command: string, args?: unknown[]): Promise<{ result?: unknown }> {
  return sendBridgeMessage<{ result?: unknown }>('vscode:command', { command, args });
}

export async function openVSCodeExternalUrl(url: string): Promise<void> {
  await sendBridgeMessage('vscode:openExternalUrl', { url });
}

type CommandHandler = (payload: unknown) => void;
const commandHandlers = new Map<string, CommandHandler>();

export function onCommand(command: string, handler: CommandHandler): () => void {
  commandHandlers.set(command, handler);
  return () => commandHandlers.delete(command);
}

window.addEventListener('message', (event: MessageEvent) => {
  const message = event.data;
  if (message?.type === 'command' && message.command) {
    const handler = commandHandlers.get(message.command);
    if (handler) {
      handler(message.payload);
    }
  }
});

type ThemeChangePayload =
  | 'light'
  | 'dark'
  | {
      kind?: 'light' | 'dark' | 'high-contrast';
      shikiThemes?: { light?: Record<string, unknown>; dark?: Record<string, unknown> } | null;
    };
type ThemeChangeHandler = (theme: ThemeChangePayload) => void;
let themeChangeHandler: ThemeChangeHandler | null = null;

export function onThemeChange(handler: ThemeChangeHandler): () => void {
  themeChangeHandler = handler;
  return () => { themeChangeHandler = null; };
}

window.addEventListener('message', (event: MessageEvent) => {
  const message = event.data;
  if (message?.type === 'themeChange' && themeChangeHandler) {
    themeChangeHandler(message.theme);
  }
});
