// connectR Service Worker.
//
// Once a session is connected, every same-origin request (except the static
// files of this client itself) is routed through the iroh tunnel to the
// remote daemon, which proxies to the local web service. The browser's own
// network stack then handles the served pages — links, forms, fetch, POST,
// cookies and HTTP media all work as if the site were local.
//
// Known limitation: Service Workers cannot tunnel WebSocket *data*, so
// WebSocket-heavy apps are not supported yet (roadmap: loopback proxy client).
//
// Paths are derived from the registration scope so the client also works when
// deployed under a subpath (e.g. GitHub Pages project pages).

const VERSION = '2';
importScripts('./wasm/connectr_client_wasm.js?v=' + VERSION);

let wasmReady = null; // promise that resolves once the wasm module is initialized
let session = null;   // ClientSession

// The deployment prefix, e.g. '/' or '/repo/'. Static client files and
// forwarded paths are computed relative to it.
const scopePath = new URL('./', self.registration.scope).pathname;

function reply(event, data) {
  if (event.ports && event.ports.length > 0) {
    event.ports[0].postMessage(data);
  } else if (event.source) {
    event.source.postMessage(data);
  }
}

function ensureWasm() {
  if (!wasmReady) {
    // In wasm-bindgen's no-modules mode the exports are static properties on
    // the init function; the promise only signals that the module is ready.
    wasmReady = wasm_bindgen({ module_or_path: './wasm/connectr_client_wasm_bg.wasm?v=' + VERSION });
  }
  return wasmReady;
}

self.addEventListener('message', async (event) => {
  const msg = event.data || {};
  try {
    if (msg.type === 'connect') {
      await ensureWasm();
      const sess = await wasm_bindgen.ClientSession.connect(msg.alias || '', msg.password || '');
      session = sess;
      reply(event, { type: 'connected', endpointId: sess.endpoint_id() });
    } else if (msg.type === 'close') {
      if (session) { session.close(); session = null; }
      reply(event, { type: 'closed' });
    } else if (msg.type === 'status') {
      reply(event, session
        ? { type: 'connected', endpointId: session.endpoint_id() }
        : { type: 'idle' });
    }
  } catch (e) {
    reply(event, { type: 'error', error: String((e && e.message) || e) });
  }
});

self.addEventListener('activate', (event) => {
  // Take control immediately after a deploy so old SW versions don't linger.
  event.waitUntil(self.clients.claim());
});

// Static files of this client that must never be proxied. Note: index.html
// is deliberately NOT listed — while a session is active, '/' and
// '/index.html' belong to the remote service. The portal (connect/disconnect
// UI) lives at portal.html; the downloadable daemon binary and client zip
// must stay served by the static host even during an active session.
const STATIC_NAMES = new Set([
  'portal.html',
  'main.js',
  'sw.js',
  'style.css',
  'favicon.ico',
  'connectr-daemon.exe',
  'web-client.zip',
]);

function isStatic(url) {
  const p = url.pathname;
  if (!p.startsWith(scopePath)) return false;
  const rel = p.slice(scopePath.length);
  if (STATIC_NAMES.has(rel)) return true;
  return rel.startsWith('wasm/');
}

self.addEventListener('fetch', (event) => {
  const url = new URL(event.request.url);
  if (url.origin !== location.origin) return;
  if (isStatic(url)) return;
  if (!session) return; // normal browsing of the static host
  event.respondWith(proxyFetch(event.request, url));
});

async function proxyFetch(request, url) {
  try {
    const headers = [];
    request.headers.forEach((value, name) => headers.push([name, value]));

    let body = null;
    if (request.method !== 'GET' && request.method !== 'HEAD') {
      const buf = await request.arrayBuffer();
      if (buf.byteLength > 0) body = new Uint8Array(buf);
    }

    // Strip the deployment prefix so the remote service sees the bare path.
    let path = url.pathname + url.search;
    if (path.startsWith(scopePath)) {
      path = '/' + path.slice(scopePath.length);
    }
    const result = await session.fetch(path, request.method, headers, body);

    const hdrs = new Headers();
    for (const k of Object.keys(result.headers)) {
      hdrs.append(k, result.headers[k]);
    }
    return new Response(result.body, { status: result.status, headers: hdrs });
  } catch (e) {
    return new Response(
      'connectR proxy error: ' + String((e && e.message) || e),
      { status: 502, headers: { 'content-type': 'text/plain; charset=utf-8' } }
    );
  }
}
