import { ExternalE2EEKeyProvider } from 'livekit-client';
// Vite-native Worker import. `?worker` triggers a dedicated build chunk
// shipped as a classic/module worker. The default export is the Worker
// constructor; we instantiate once per tab and reuse.
import LivekitE2EEWorker from 'livekit-client/e2ee-worker?worker';

const STORAGE_KEY = 'chatapp.e2ee';

export interface CallE2EESettings {
  enabled: boolean;
}

// Default ON — the whole point of this project is zero-knowledge, so honour
// that for the SFU path too. Can be turned off for debugging or if a user's
// browser lacks RTCRtpScriptTransform / Insertable Streams support.
const DEFAULTS: CallE2EESettings = { enabled: true };

type Listener = (s: CallE2EESettings) => void;
const listeners = new Set<Listener>();

let cached: CallE2EESettings | null = null;

function read(): CallE2EESettings {
  if (cached) return cached;
  try {
    const raw = window.localStorage.getItem(STORAGE_KEY);
    if (!raw) {
      cached = DEFAULTS;
      return cached;
    }
    const parsed = JSON.parse(raw) as Partial<CallE2EESettings>;
    cached = {
      enabled: typeof parsed.enabled === 'boolean' ? parsed.enabled : DEFAULTS.enabled,
    };
    return cached;
  } catch {
    cached = DEFAULTS;
    return cached;
  }
}

function write(s: CallE2EESettings): void {
  cached = s;
  try {
    window.localStorage.setItem(STORAGE_KEY, JSON.stringify(s));
  } catch {
    /* quota / private mode */
  }
  for (const l of listeners) l(s);
}

export function getCallE2EESettings(): CallE2EESettings {
  return read();
}

export function updateCallE2EESettings(patch: Partial<CallE2EESettings>): CallE2EESettings {
  const next = { ...read(), ...patch };
  write(next);
  return next;
}

export function subscribeCallE2EESettings(listener: Listener): () => void {
  listeners.add(listener);
  return () => listeners.delete(listener);
}

// Shared single Worker instance — LiveKit supports reusing it across rooms.
let workerInstance: Worker | null = null;
function getWorker(): Worker {
  if (!workerInstance) {
    workerInstance = new LivekitE2EEWorker();
  }
  return workerInstance;
}

// Feature detection — Insertable Streams (RTCRtpScriptTransform or the older
// encodedStreams API) is required for LiveKit E2EE. Returns false on browsers
// that can't encrypt the media path (the caller then skips the `e2ee` option).
export function isE2EESupported(): boolean {
  if (typeof window === 'undefined') return false;
  const hasScriptTransform =
    typeof (window as unknown as { RTCRtpScriptTransform?: unknown }).RTCRtpScriptTransform !==
    'undefined';
  const sender = (window as unknown as { RTCRtpSender?: { prototype?: unknown } }).RTCRtpSender;
  const hasEncodedStreams =
    !!sender?.prototype &&
    'createEncodedStreams' in (sender.prototype as Record<string, unknown>);
  return hasScriptTransform || hasEncodedStreams;
}

interface E2EEBundle {
  keyProvider: ExternalE2EEKeyProvider;
  worker: Worker;
}

// Derives a stable per-conversation passphrase entirely client-side. The
// passphrase is never transmitted anywhere; each member computes it locally
// from the conversation id they already hold via RLS-protected Supabase data.
// A stronger variant would ship a random per-conversation secret through the
// existing E2E envelope system — deferred to M3.
export async function createCallE2EE(conversationId: string): Promise<E2EEBundle> {
  const keyProvider = new ExternalE2EEKeyProvider();
  const salt = 'chat-app-voice-e2ee-v1';
  const enc = new TextEncoder();
  const buf = await crypto.subtle.digest('SHA-256', enc.encode(salt + ':' + conversationId));
  const hex = Array.from(new Uint8Array(buf))
    .map((b) => b.toString(16).padStart(2, '0'))
    .join('');
  await keyProvider.setKey(hex);
  return { keyProvider, worker: getWorker() };
}
