// Sidekick side panel UI controller.

const $status = document.getElementById("status-text");
const $statusDot = document.getElementById("status-dot");
const $session = document.getElementById("session-text");
const $error = document.getElementById("error-text");
const $pin = document.getElementById("pin-button");
const $viewport = document.getElementById("viewport-button");
const $debuggerToggle = document.getElementById("debugger-toggle");
const $debuggerTitle = document.getElementById("debugger-title");
const $recent = document.getElementById("recent-list");
const $reconnect = document.getElementById("reconnect-button");
const $modeSelect = document.getElementById("mode-select");
const $modeBlurb = document.getElementById("mode-blurb");
const $undock = document.getElementById("undock-button");
const $dock = document.getElementById("dock-button");

let debuggerAttached = false;
let lastPassiveCaptureEnabled = null;
let idleRefreshTimer = null;
let idleRefreshInFlight = false;

const MODE_BLURBS = {
  "debug": "Capture issues — console errors, failed requests, surrounding DOM.",
  "developer": "Capture code-change context — selectors, DOM, computed styles, component hints.",
  "ux-ui": "Capture design feedback — screenshots, bounds, viewport, typography, spacing.",
  "cro": "Capture conversion feedback — CTA / form / hero snapshots and above-fold copy."
};

const NO_SESSION_REASON = /no active .*feedback session/i;
const IDLE_REFRESH_INTERVAL_MS = 3000;
const EXTENSION_ID = chrome.runtime.id;

// Chrome's native messaging failures are terse and each one points at a
// different broken link in the chain. Translate them into next steps.
function connectionHint(error) {
  if (!error) return null;
  if (/native messaging host not found/i.test(error)) {
    return "Open the Sidekick app once — it registers the Chrome helper automatically at launch. Then click Reconnect.";
  }
  if (/forbidden/i.test(error)) {
    return `This extension's ID (${EXTENSION_ID}) doesn't match the one registered with Sidekick. In Sidekick, open Browser → Install Chrome Helper and reinstall with this ID.`;
  }
  if (/failed to start native messaging host/i.test(error)) {
    return "macOS blocked Sidekick's helper. Move Sidekick.app into /Applications, reopen it, then use Browser → Diagnose in Sidekick for the exact fix.";
  }
  if (/native host has exited/i.test(error)) {
    return "Sidekick isn't running (or just quit). Launch Sidekick, then click Reconnect.";
  }
  return null;
}

function renderState({ state, error, lastSessionID, recentCaptures, mode }) {
  if (mode && $modeSelect && $modeSelect.value !== mode) {
    $modeSelect.value = mode;
  }
  if ($modeBlurb) {
    $modeBlurb.textContent = MODE_BLURBS[$modeSelect ? $modeSelect.value : "developer"] || "";
  }
  // The native bridge handshakes successfully even when Sidekick has no
  // Feedback session armed. Don't lie about being "connected" in that
  // case — distinguish bridge-up vs. ready-to-capture so the user knows
  // they need to click "Feedback" in Sidekick.
  const noSessionError = state === "rejected" && error && NO_SESSION_REASON.test(error);
  const armed = state === "connected" && !!lastSessionID;
  const bridgeUpButIdle = (state === "connected" && !lastSessionID) || noSessionError;
  updatePassiveCapture(armed);
  updateIdleRefresh(armed || bridgeUpButIdle);

  let effective;
  if (armed) {
    effective = { dot: "green", text: "Ready to capture", session: `Active session: ${lastSessionID}`, error: null };
  } else if (bridgeUpButIdle) {
    effective = {
      dot: "yellow",
      text: "Waiting on Sidekick",
      session: "Click “Feedback” in Sidekick to start a browser-aware feedback session.",
      error: null
    };
  } else {
    const map = {
      stopped: { dot: "gray", text: "Disconnected" },
      connecting: { dot: "yellow", text: "Connecting to Sidekick…" },
      connected: { dot: "yellow", text: "Waiting on Sidekick" },
      rejected: { dot: "yellow", text: "Sidekick rejected the message" },
      failed: { dot: "red", text: "Connection failed" },
      disconnected: { dot: "red", text: "Disconnected from Sidekick" }
    };
    const base = map[state] || { dot: "gray", text: state || "Unknown" };
    effective = {
      dot: base.dot,
      text: base.text,
      session: lastSessionID ? `Last session: ${lastSessionID}` : "",
      error: error || null
    };
  }

  $status.textContent = effective.text;
  $statusDot.dataset.color = effective.dot;
  $session.textContent = effective.session;
  if (effective.error) {
    $error.hidden = false;
    const hint = connectionHint(effective.error);
    $error.textContent = hint ? `${effective.error}\n${hint}` : effective.error;
  } else {
    $error.hidden = true;
    $error.textContent = "";
  }

  // Disable capture actions when the bridge isn't armed for a Feedback session.
  const captureDisabled = !armed;
  $pin.disabled = captureDisabled;
  $viewport.disabled = captureDisabled;
  $debuggerToggle.disabled = captureDisabled && !debuggerAttached;
  if ($modeSelect) {
    // The picker remains usable when idle so the user can stage a mode before
    // arming a Sidekick session; once armed the mode is sent on the next push.
    $modeSelect.disabled = state === "failed" || state === "disconnected";
  }

  renderRecent(recentCaptures || []);
}

function clearChildren(node) {
  while (node.firstChild) node.removeChild(node.firstChild);
}

function renderRecent(items) {
  clearChildren($recent);
  if (!items.length) {
    const empty = document.createElement("li");
    empty.className = "recent__empty";
    empty.textContent = "Nothing captured yet.";
    $recent.appendChild(empty);
    return;
  }
  for (const item of items) {
    const li = document.createElement("li");
    li.className = "recent__item";
    const title = document.createElement("p");
    title.className = "recent__title";
    if (item.kind === "pin") {
      title.textContent = `Marked section #${item.index}`;
    } else if (item.kind === "browser-click") {
      title.textContent = `Browser click #${item.index}`;
    } else if (item.kind === "viewport") {
      title.textContent = item.label || "Viewport";
    } else {
      title.textContent = item.kind;
    }
    const meta = document.createElement("p");
    meta.className = "recent__meta";
    const url = (() => {
      try { return new URL(item.url).hostname; } catch (_) { return item.url || ""; }
    })();
    meta.textContent = `${url}${item.selector ? " · " + item.selector : ""}`;
    li.appendChild(title);
    li.appendChild(meta);
    $recent.appendChild(li);
  }
}

async function send(message) {
  return new Promise((resolve) => {
    chrome.runtime.sendMessage(message, (response) => {
      if (chrome.runtime.lastError) {
        console.warn("sidekick: sendMessage failed", chrome.runtime.lastError);
        resolve(null);
      } else {
        resolve(response);
      }
    });
  });
}

function updatePassiveCapture(enabled) {
  if (lastPassiveCaptureEnabled === enabled) return;
  lastPassiveCaptureEnabled = enabled;
  send({ type: "sidekick.setPassiveCapture", enabled });
}

function updateIdleRefresh(shouldPoll) {
  if (!shouldPoll) {
    if (idleRefreshTimer) {
      clearInterval(idleRefreshTimer);
      idleRefreshTimer = null;
    }
    return;
  }
  if (idleRefreshTimer) return;
  idleRefreshTimer = setInterval(async () => {
    if (idleRefreshInFlight) return;
    idleRefreshInFlight = true;
    try {
      await send({ type: "sidekick.refreshState" });
      await refresh();
    } finally {
      idleRefreshInFlight = false;
    }
  }, IDLE_REFRESH_INTERVAL_MS);
}

async function refresh() {
  const state = await send({ type: "sidekick.getState" });
  if (state) renderState(state);
}

$pin.addEventListener("click", async () => {
  $pin.disabled = true;
  try {
    await send({ type: "sidekick.startSelection" });
  } finally {
    setTimeout(() => { $pin.disabled = false; }, 250);
  }
});

$viewport.addEventListener("click", async () => {
  $viewport.disabled = true;
  try {
    await send({ type: "sidekick.captureViewport", label: "Viewport" });
  } finally {
    $viewport.disabled = false;
  }
});

$debuggerToggle.addEventListener("click", async () => {
  $debuggerToggle.disabled = true;
  try {
    if (!debuggerAttached) {
      const r = await send({ type: "sidekick.attachDebugger" });
      if (r && r.ok) {
        debuggerAttached = true;
        $debuggerTitle.textContent = "Detach Console + Network";
      }
    } else {
      await send({ type: "sidekick.detachDebugger" });
      debuggerAttached = false;
      $debuggerTitle.textContent = "Attach Console + Network";
    }
  } finally {
    $debuggerToggle.disabled = false;
  }
});

$reconnect.addEventListener("click", async () => {
  await send({ type: "sidekick.connect" });
  await refresh();
});

if ($modeSelect) {
  $modeSelect.addEventListener("change", async () => {
    const mode = $modeSelect.value;
    if ($modeBlurb) $modeBlurb.textContent = MODE_BLURBS[mode] || "";
    await send({ type: "sidekick.setMode", mode });
  });
}

if ($undock) {
  $undock.addEventListener("click", async () => {
    try {
      await chrome.windows.create({
        url: chrome.runtime.getURL("sidepanel.html"),
        type: "popup",
        width: 360,
        height: 600
      });
      window.close();
    } catch (err) {
      console.error("sidekick: undocking failed", err);
    }
  });
}

if ($dock) {
  $dock.addEventListener("click", async () => {
    try {
      const lastFocused = await chrome.windows.getLastFocused({ windowTypes: ["normal"] });
      if (lastFocused) {
        await chrome.sidePanel.open({ windowId: lastFocused.id });
      } else {
        await chrome.sidePanel.open();
      }
      window.close();
    } catch (err) {
      console.error("sidekick: docking failed", err);
    }
  });
}

chrome.runtime.onMessage.addListener((msg) => {
  if (!msg) return;
  if (msg.type === "state") {
    renderState(msg);
  } else if (msg.type === "response") {
    // Optional: surface decode/accept feedback. The full state-broadcast
    // already covers this via the previous setState() call.
  }
});

(async () => {
  try {
    const win = await chrome.windows.getCurrent();
    const isUndocked = win && win.type === "popup";
    if ($undock) $undock.hidden = isUndocked;
    if ($dock) $dock.hidden = !isUndocked;
  } catch (err) {
    console.warn("sidekick: error checking window type", err);
  }

  await send({ type: "sidekick.connect" });
  await refresh();
})();
