// Pinned server fingerprints (trust-on-first-use), keyed by host:port.
// Fingerprints are not secrets — localStorage is fine; the credential store is
// reserved for the password (see ConnectionContext).
//
// getPin() is called SYNCHRONOUSLY from platform.js's pinnedApiFetch (every
// API request needs its pin at call time, with no await available there), so
// this stays a plain localStorage-backed module — no async store can serve
// that call site. On the desktop, localStorage is a mirror of the shared
// Cambium pins.json (native-backed mirror pattern, see data/nativeMirror.js):
// module init hydrates from pins_all (migrating any pre-existing localStorage
// pins up if native has none yet), and setPin/removePin also invoke
// pins_set/pins_remove so the CLI sees the same trust decisions. This module
// intentionally does NOT import platform.js (which itself imports getPin from
// here for pinnedApiFetch) — it does its own minimal Tauri detection to avoid
// a module cycle.
import { hydrate } from './nativeMirror';

const PINS_KEY = 'cbm-server-pins-v1';

const isTauri =
  typeof window !== 'undefined' && Boolean(window.__TAURI_INTERNALS__);

// Resolved once at module init, same rationale as platform.js's top-level
// await: the bridge must be ready before any pin read/write. Stays undefined
// on web, so every invoke(...) call below is naturally skipped there.
let invoke;
if (isTauri) {
  ({ invoke } = await import('@tauri-apps/api/core'));
}

function loadPins() {
  try {
    return JSON.parse(localStorage.getItem(PINS_KEY)) ?? {};
  } catch {
    return {};
  }
}

function savePins(pins) {
  try {
    localStorage.setItem(PINS_KEY, JSON.stringify(pins));
  } catch {
    /* quota/private mode: pins just won't persist */
  }
}

/** "host:port" from a connection-card ip or a full URL. */
export function hostFromIp(ip) {
  return String(ip ?? '').trim().replace(/^https?:\/\//, '').replace(/\/.*$/, '');
}

export function getPin(host) {
  return loadPins()[hostFromIp(host)] ?? null;
}

export function setPin(host, fingerprint) {
  const pins = loadPins();
  pins[hostFromIp(host)] = fingerprint;
  savePins(pins);
  invoke?.('pins_set', { host: hostFromIp(host), fingerprint }).catch(() => {});
}

export function removePin(host) {
  const pins = loadPins();
  delete pins[hostFromIp(host)];
  savePins(pins);
  invoke?.('pins_remove', { host: hostFromIp(host) }).catch(() => {});
}

// Native loader for the mirror: pins_all() always resolves to a map (never
// rejects on "no pins yet" — an empty map IS the "never saved" state here,
// since pins.json has no separate existence marker). Returning null for that
// case lets hydrate() attempt the one-time upward migration; returning
// undefined on any failure/no-bridge leaves localStorage as sole authority.
async function nativePinsLoad() {
  if (!invoke) return undefined;
  try {
    const pins = await invoke('pins_all');
    const entries = Object.entries(pins ?? {});
    return entries.length ? JSON.stringify(Object.fromEntries(entries)) : null;
  } catch {
    return undefined;
  }
}

// One-time migration: push every pre-existing localStorage pin up to native,
// one pins_set call per host (there's no bulk-set command).
async function nativePinsPush(value) {
  if (!invoke) return;
  let pins;
  try {
    pins = JSON.parse(value) ?? {};
  } catch {
    return;
  }
  await Promise.all(
    Object.entries(pins).map(([host, fingerprint]) =>
      invoke('pins_set', { host, fingerprint }).catch(() => {})
    )
  );
}

await hydrate(PINS_KEY, nativePinsLoad, nativePinsPush);
