// Hangar Browser Companion — background service worker (Chrome MV3)
// Also loaded as a background script for Firefox (MV2 compatible subset).

const isFirefox = typeof browser !== 'undefined';
const api = isFirefox ? browser : chrome;

// ─── Session state ────────────────────────────────────────────────────────────

let sessionToken = null;
let sessionPort  = null;
let initInFlight = false;   // deduplicate simultaneous init triggers

async function restoreSession() {
  try {
    const data = await api.storage.session.get(['hangarToken', 'hangarPort']);
    sessionToken = data.hangarToken || null;
    sessionPort  = data.hangarPort  || null;
  } catch (_) {
    // Firefox MV2 does not support storage.session
  }
}
restoreSession();

// ─── Shared init handler ──────────────────────────────────────────────────────
// Called from both the webNavigation listener and the INIT_URL message handler.
// The content script route is more reliable for cold-start service workers.

async function handleInitURL(href, tabId, windowId) {
  if (initInFlight) return;
  initInFlight = true;
  try {
    let url;
    try { url = new URL(href); } catch { return; }
    if (!url.pathname.startsWith('/init/')) return;

    const token = url.pathname.slice('/init/'.length);
    const port  = url.port;
    if (!token || !port) return;

    const res  = await fetch(`http://127.0.0.1:${port}/init/${token}`);
    const data = await res.json();

    sessionToken = token;
    sessionPort  = port;
    try {
      await api.storage.session.set({ hangarToken: token, hangarPort: port, ...data });
    } catch (_) {}   // Firefox MV2

    // Open the sidebar (Chrome 116+ only)
    if (!isFirefox && chrome.sidePanel) {
      try {
        await chrome.sidePanel.setOptions({ enabled: true, path: 'sidebar.html' });
        await chrome.sidePanel.open({ windowId });
      } catch (e) {
        console.warn('[Hangar] sidePanel.open failed:', e);
      }
    }

    // Navigate the init tab to the actual section URL
    if (data.sectionURL && tabId) {
      await api.tabs.update(tabId, { url: data.sectionURL });
    }
  } catch (e) {
    console.error('[Hangar] Session init failed:', e);
  } finally {
    initInFlight = false;
  }
}

// ─── Init URL interception via webNavigation ──────────────────────────────────
// Fires when the service worker is already awake. For cold starts, the
// content script sends INIT_URL instead (see below).

api.webNavigation.onCompleted.addListener(async (details) => {
  if (details.frameId !== 0) return;
  let url;
  try { url = new URL(details.url); } catch { return; }
  if (url.hostname !== '127.0.0.1') return;
  if (!url.pathname.startsWith('/init/')) return;

  let windowId;
  try { windowId = (await api.tabs.get(details.tabId))?.windowId; } catch (_) {}
  await handleInitURL(details.url, details.tabId, windowId);
}, { url: [{ hostEquals: '127.0.0.1' }] });

// ─── Messages from content scripts and sidebar ────────────────────────────────

api.runtime.onMessage.addListener((msg, sender, sendResponse) => {
  // Content script detected init URL — guaranteed to run even on cold start
  if (msg.type === 'INIT_URL') {
    handleInitURL(msg.href, sender.tab?.id, sender.tab?.windowId)
      .catch(e => console.error('[Hangar] INIT_URL failed:', e));
    return false;
  }

  if (msg.type === 'GET_SESSION') {
    sendResponse({ token: sessionToken, port: sessionPort });
    return true;
  }

  if (msg.type === 'FILL_FIELD') {
    injectInActiveTab(injectSingle, [msg.label, msg.value]);
    sendResponse({ ok: true });
    return true;
  }

  if (msg.type === 'FILL_ALL') {
    injectInActiveTab(injectAll, [msg.credentials]);
    sendResponse({ ok: true });
    return true;
  }

  return false;
});

// ─── Script injection helpers ─────────────────────────────────────────────────

async function injectInActiveTab(fn, args) {
  const [tab] = await api.tabs.query({ active: true, currentWindow: true });
  if (!tab) return;
  try {
    if (isFirefox) {
      await browser.tabs.executeScript(tab.id, {
        code: `(${fn.toString()})(${args.map(JSON.stringify).join(',')})`,
        runAt: 'document_idle'
      });
    } else {
      await chrome.scripting.executeScript({
        target: { tabId: tab.id },
        func: fn,
        args
      });
    }
  } catch (e) {
    console.warn('[Hangar] Injection failed:', e);
  }
}

// Injected into page context — fills a single label/value pair.
function injectSingle(label, value) {
  const setter = Object.getOwnPropertyDescriptor(HTMLInputElement.prototype, 'value').set;
  const inputs = Array.from(document.querySelectorAll('input:not([type=hidden]),textarea'));
  const isEmailCred = label.includes('@');

  for (const el of inputs) {
    const type = (el.getAttribute('type') || 'text').toLowerCase();
    const meta = [el.name, el.id, el.placeholder, el.autocomplete || ''].join(' ').toLowerCase();

    if (isEmailCred) {
      if (type === 'email' || meta.match(/\b(email|user|login|username)\b/)) {
        setter.call(el, label);
        el.dispatchEvent(new Event('input',  { bubbles: true }));
        el.dispatchEvent(new Event('change', { bubbles: true }));
      }
      if (type === 'password') {
        setter.call(el, value);
        el.dispatchEvent(new Event('input',  { bubbles: true }));
        el.dispatchEvent(new Event('change', { bubbles: true }));
      }
    } else {
      const labelLow = label.toLowerCase();
      if (meta.includes(labelLow) || type.includes(labelLow)) {
        setter.call(el, value);
        el.dispatchEvent(new Event('input',  { bubbles: true }));
        el.dispatchEvent(new Event('change', { bubbles: true }));
      }
    }
  }
}

// Injected into page context — fills all credentials at once.
function injectAll(credentials) {
  const setter = Object.getOwnPropertyDescriptor(HTMLInputElement.prototype, 'value').set;
  const inputs = Array.from(document.querySelectorAll('input:not([type=hidden]),textarea'));

  for (const { label, value } of credentials) {
    const isEmailCred = label.includes('@');
    for (const el of inputs) {
      const type = (el.getAttribute('type') || 'text').toLowerCase();
      const meta = [el.name, el.id, el.placeholder, el.autocomplete || ''].join(' ').toLowerCase();

      if (isEmailCred) {
        if (type === 'email' || meta.match(/\b(email|user|login|username)\b/)) {
          setter.call(el, label);
          el.dispatchEvent(new Event('input',  { bubbles: true }));
          el.dispatchEvent(new Event('change', { bubbles: true }));
        }
        if (type === 'password') {
          setter.call(el, value);
          el.dispatchEvent(new Event('input',  { bubbles: true }));
          el.dispatchEvent(new Event('change', { bubbles: true }));
        }
      } else {
        const labelLow = label.toLowerCase();
        if (meta.includes(labelLow)) {
          setter.call(el, value);
          el.dispatchEvent(new Event('input',  { bubbles: true }));
          el.dispatchEvent(new Event('change', { bubbles: true }));
        }
      }
    }
  }
}
