/**
 * Web (client/server) adapter set — ticket #13, lot F1.
 *
 * `createHttpWsGateways()` is the third gateway implementation, alongside
 * `createTauriGateways()` (desktop) and `createMockGateways()` (tests/dev). It
 * wires every UI port to the shared backend core over HTTP request/response and a
 * single multiplexed WebSocket, exactly behind the *unchanged* ports — no
 * component is aware of it (selected in `app/di.tsx`).
 *
 * Scope reminder (F1): request/response gateways are fully implemented; the WS
 * PTY/chat streams are a contract-conformant **skeleton** (full round-trip lands
 * in F3/B5/B6 once a server exists — B3/B4). Desktop-only surfaces (pickFolder,
 * OS windows, focused-project, remote) fail with an explicit "unsupported on web"
 * error or an inert stub, never a Tauri call.
 */

import type { Gateways } from "@/ports";
import { LocalStorageUiPreferencesGateway } from "../uiPreferences";
import { HttpInvoker } from "./httpInvoker";
import { WsLiveClient } from "./wsLiveClient";
import { getWebSession } from "./webSession";
import { setWebLiveClient } from "./webLive";
import { HttpDeviceGateway } from "./deviceGateway";
import {
  HttpConversationGateway,
  HttpEmbedderGateway,
  HttpGitGateway,
  HttpInputGateway,
  HttpLayoutGateway,
  HttpMemoryGateway,
  HttpModelServerGateway,
  HttpPermissionGateway,
  HttpProfileGateway,
  HttpProjectGateway,
  HttpSkillGateway,
  HttpTemplateGateway,
  HttpWorkStateGateway,
} from "./requestResponseGateways";
import {
  HttpAgentGateway,
  HttpSystemGateway,
  HttpTerminalGateway,
  HttpTicketGateway,
} from "./streamGateways";
import {
  WebDesktopServerGateway,
  WebFocusedProjectGateway,
  WebRemoteGateway,
  WebWindowGateway,
} from "./unsupported";

/** Endpoint configuration for the web transport. */
export interface HttpWsGatewaysConfig {
  /** Backend HTTP base URL (no trailing slash). Defaults to the page origin. */
  baseUrl?: string;
  /** WebSocket base URL. Defaults to the page origin with `http(s)`→`ws(s)`. */
  wsUrl?: string;
  /** Bearer token from pairing (ticket #13 auth). */
  token?: string;
}

/** Derives default `{baseUrl, wsUrl}` from `window.location` when available. */
function resolveEndpoints(config: HttpWsGatewaysConfig): {
  baseUrl: string;
  wsUrl: string;
} {
  const origin =
    typeof window !== "undefined" && window.location
      ? window.location.origin
      : "http://localhost";
  const baseUrl = config.baseUrl ?? origin;
  const wsUrl =
    config.wsUrl ?? baseUrl.replace(/^http(s?):\/\//, (_m, s) => `ws${s}://`);
  return { baseUrl, wsUrl };
}

/**
 * Builds the full set of web (HTTP+WS) gateways. `config` is optional; endpoints
 * default to the current page origin so a browser build talks to its own server.
 * The {@link HttpInvoker} and {@link WsLiveClient} are shared across gateways.
 */
export function createHttpWsGateways(config: HttpWsGatewaysConfig = {}): Gateways {
  const { baseUrl, wsUrl } = resolveEndpoints(config);
  // Route 401s back to the pairing screen via the shared web session (F2/F6).
  const session = getWebSession();
  // `ws` is referenced by the invoker's 401 handler (declared below); the closure
  // only runs after both are constructed, so the forward reference is safe.
  let ws: WsLiveClient;
  const http = new HttpInvoker({
    baseUrl,
    token: config.token,
    onUnauthorized: () => {
      // F6: a 401 means the session cookie is gone/revoked. Route back to pairing
      // AND tear down the live socket so it stops its (now hopeless) reconnect
      // loop. Re-pairing brings the same client back up cleanly.
      session.notifyUnauthorized();
      ws?.disconnect();
    },
  });
  ws = new WsLiveClient({
    wsUrl,
    token: config.token,
    // F6: when a WS reconnect fails we cannot read its HTTP status, so probe with
    // a cheap `health` call. A revoked session answers `401` → the invoker's
    // `onUnauthorized` above bounces to pairing; a network error is swallowed and
    // the WS backoff keeps retrying.
    onReconnectFailed: () => {
      void http.invoke("health", { request: null }).catch(() => {});
    },
  });
  // Share the live client with the web feature so live surfaces can re-sync on
  // reconnect (F5). Inert on desktop (never called there).
  setWebLiveClient(ws);

  return {
    system: new HttpSystemGateway(http, ws),
    agent: new HttpAgentGateway(http, ws),
    input: new HttpInputGateway(http),
    terminal: new HttpTerminalGateway(ws),
    project: new HttpProjectGateway(http),
    layout: new HttpLayoutGateway(http),
    git: new HttpGitGateway(http),
    remote: new WebRemoteGateway(),
    profile: new HttpProfileGateway(http),
    modelServer: new HttpModelServerGateway(http),
    // Desktop-only (#68): the web client is served *by* the embedded server and
    // must not reconfigure it.
    desktopServer: new WebDesktopServerGateway(),
    template: new HttpTemplateGateway(http),
    skill: new HttpSkillGateway(http),
    memory: new HttpMemoryGateway(http),
    embedder: new HttpEmbedderGateway(http),
    device: new HttpDeviceGateway(http),
    permission: new HttpPermissionGateway(http),
    workState: new HttpWorkStateGateway(http),
    conversation: new HttpConversationGateway(http),
    ticket: new HttpTicketGateway(http, ws),
    window: new WebWindowGateway(),
    focusedProject: new WebFocusedProjectGateway(),
    // Frontend-owned UI prefs are transport-neutral (localStorage) — reuse as-is.
    uiPreferences: new LocalStorageUiPreferencesGateway(),
  };
}

export { HttpInvoker } from "./httpInvoker";
export { WsLiveClient } from "./wsLiveClient";
export { WebSession, getWebSession, setWebSessionForTests } from "./webSession";
export { getWebLiveClient, setWebLiveClient, disconnectWebLive } from "./webLive";
export type { ConnectionState } from "./wsLiveClient";
