import type { ServerWebSocket } from 'bun';
import { getMusicServerWsUrl } from '../api/router';

// Platform side of the two cliamp sockets. Both used to spawn processes here — the `cliamp` player and a
// `parec` capture — which put the whole local-audio pipeline inside the thin proxy. They now live in the
// music PLUGIN (`plugins/music/cliamp/cliamp-ws.ts`), and this is what is left of them: authenticate the browser
// (done before the upgrade, in server.tsx), then pass frames through in both directions without reading
// them. Text or binary, no inspection — same dumb-pipe shape as the vault notifications relay.

export type CliampWSData = {
  provider: 'cliamp' | 'cliamp-audio';
  search?: string; // the browser's query string, forwarded minus the platform token
};

type UpstreamState = {
  ws: WebSocket | null;
  queue: (string | Uint8Array<ArrayBuffer>)[];
  ready: boolean;
};

// Bun hands frames over as `string | Buffer`; a Buffer is a Uint8Array at runtime, so forward as-is
// rather than copying every PCM chunk.
const asPayload = (raw: string | Buffer): string | Uint8Array<ArrayBuffer> =>
  typeof raw === 'string' ? raw : (raw as Uint8Array<ArrayBuffer>);

// The sidecar has no use for the platform JWT and should not see it.
const forwardedQuery = (search: string | undefined): string => {
  const params = new URLSearchParams(search ?? '');
  params.delete('token');
  const qs = params.toString();
  return qs ? `?${qs}` : '';
};

function createCliampRelay(path: string) {
  const upstreams = new Map<ServerWebSocket<CliampWSData>, UpstreamState>();

  return {
    open(ws: ServerWebSocket<CliampWSData>) {
      const base = getMusicServerWsUrl();
      if (!base) {
        try {
          ws.close(1011, 'Music sidecar not available');
        } catch {
          /* already closed */
        }
        return;
      }

      const state: UpstreamState = { ws: null, queue: [], ready: false };
      upstreams.set(ws, state);

      const upstream = new WebSocket(`${base}${path}${forwardedQuery(ws.data.search)}`);
      upstream.binaryType = 'arraybuffer';
      state.ws = upstream;

      upstream.addEventListener('open', () => {
        state.ready = true;
        for (const m of state.queue) upstream.send(m);
        state.queue.length = 0;
      });
      upstream.addEventListener('message', (ev) => {
        try {
          ws.send(ev.data as string | ArrayBuffer);
        } catch {
          /* client gone */
        }
      });
      upstream.addEventListener('close', (ev) => {
        upstreams.delete(ws);
        try {
          ws.close(ev.code || 1000, ev.reason || '');
        } catch {
          /* already closed */
        }
      });
      upstream.addEventListener('error', () => {
        upstreams.delete(ws);
        try {
          ws.close(1011, 'upstream error');
        } catch {
          /* already closed */
        }
      });
    },

    message(ws: ServerWebSocket<CliampWSData>, raw: string | Buffer) {
      const state = upstreams.get(ws);
      if (!state) return;
      const payload = asPayload(raw);
      if (state.ready && state.ws) state.ws.send(payload);
      else state.queue.push(payload); // buffer until the upstream socket opens
    },

    close(ws: ServerWebSocket<CliampWSData>) {
      const state = upstreams.get(ws);
      if (!state) return;
      try {
        state.ws?.close();
      } catch {
        /* already closed */
      }
      upstreams.delete(ws);
    },

    drain() {},
  };
}

export const cliampWebsocket = createCliampRelay('/cliamp/ws');
export const cliampAudioWebsocket = createCliampRelay('/cliamp/audio/ws');
