/**
 * RHA Provider Vault — popup UI logic
 * All vault operations go through the background service worker via chrome.runtime.sendMessage.
 */

const bg = msg => new Promise((res, rej) =>
  chrome.runtime.sendMessage(msg, r => {
    if (chrome.runtime.lastError) return rej(new Error(chrome.runtime.lastError.message));
    if (r?.error) return rej(new Error(r.error));
    res(r);
  })
);

// If any vault op fails because Chrome killed the service worker, go back to
// the lock screen so the user can re-enter their password.
async function safeBg(msg) {
  try {
    return await bg(msg);
  } catch (e) {
    if (e.message.includes('locked') || e.message.includes('Vault') || e.message.includes('context')) {
      show('screen-lock');
      document.getElementById('pw-error').textContent = 'Session expired — please unlock again.';
      document.getElementById('pw-error').style.display = '';
      throw e;
    }
    throw e;
  }
}

// ── Screen routing ─────────────────────────────────────────────────────────────

function show(id) {
  document.querySelectorAll('.screen').forEach(s => s.classList.remove('active'));
  document.getElementById(id).classList.add('active');
}

function showTab(name) {
  document.querySelectorAll('.tab').forEach(t => t.classList.toggle('active', t.dataset.tab === name));
  document.querySelectorAll('.tab-content').forEach(c => {
    c.style.display = c.id === `tab-${name}` ? 'flex' : 'none';
  });
  if (name === 'audit') loadAudit();
}

// ── Init ──────────────────────────────────────────────────────────────────────

/**
 * A freshly-woken MV3 service worker can miss the popup's very first message
 * (the "receiving end does not exist" race). Never let that transient failure
 * be mistaken for "a vault exists, enter its password" — retry once, and only
 * show an explicit error (never a guessed screen) if it still fails.
 */
async function statusWithRetry() {
  try {
    return await bg({ action: 'vault:status' });
  } catch {
    await new Promise(r => setTimeout(r, 250));
    return await bg({ action: 'vault:status' });
  }
}

async function init() {
  let status;
  try {
    status = await statusWithRetry();
  } catch (e) {
    show('screen-lock');
    document.getElementById('lock-title').textContent = 'Extension error';
    document.getElementById('lock-desc').textContent = '';
    document.getElementById('lock-icon').textContent = '⚠️';
    document.getElementById('pw-input').style.display = 'none';
    document.getElementById('pw-submit').style.display = 'none';
    document.getElementById('create-vault-link').style.display = 'none';
    const err = document.getElementById('pw-error');
    err.textContent = `Could not reach the extension background — ${e.message}. Try closing this popup and reopening it.`;
    err.style.display = '';
    return;
  }
  const { exists, unlocked } = status;
  if (!exists) {
    showCreateVault();
  } else if (unlocked) {
    show('screen-main');
    await loadProviders();
  } else {
    show('screen-lock');
    document.getElementById('pw-input').style.display = '';
    document.getElementById('pw-submit').style.display = '';
    document.getElementById('lock-title').textContent = 'Unlock Vault';
    document.getElementById('lock-desc').textContent = 'Enter your vault password to access your provider keys.';
    document.getElementById('lock-icon').textContent = '🔒';
    document.getElementById('create-vault-link').style.display = '';
  }
}

function showCreateVault() {
  show('screen-create');
  document.getElementById('create-vault-link').style.display = 'none';
}

// ── Lock screen ───────────────────────────────────────────────────────────────

document.getElementById('pw-submit').addEventListener('click', async () => {
  const pw = document.getElementById('pw-input').value;
  const err = document.getElementById('pw-error');
  try {
    await bg({ action: 'vault:unlock', password: pw });
    document.getElementById('pw-input').value = '';
    err.style.display = 'none';
    show('screen-main');
    await loadProviders();
  } catch (e) {
    err.textContent = e.message;
    err.style.display = '';
  }
});

document.getElementById('pw-input').addEventListener('keydown', e => {
  if (e.key === 'Enter') document.getElementById('pw-submit').click();
});

document.getElementById('create-vault-link').addEventListener('click', showCreateVault);

// ── Create vault ──────────────────────────────────────────────────────────────

document.getElementById('back-to-unlock').addEventListener('click', () => show('screen-lock'));

document.getElementById('create-submit').addEventListener('click', async () => {
  const pw1 = document.getElementById('new-pw').value;
  const pw2 = document.getElementById('new-pw2').value;
  const err = document.getElementById('create-error');
  if (pw1.length < 8) { err.textContent = 'Password must be at least 8 characters'; err.style.display = ''; return; }
  if (pw1 !== pw2)    { err.textContent = 'Passwords do not match'; err.style.display = ''; return; }
  try {
    await bg({ action: 'vault:create', password: pw1 });
    document.getElementById('new-pw').value = '';
    document.getElementById('new-pw2').value = '';
    err.style.display = 'none';
    show('screen-main');
    await loadProviders();
  } catch (e) {
    err.textContent = e.message; err.style.display = '';
  }
});

// ── Main screen tabs ──────────────────────────────────────────────────────────

document.querySelectorAll('.tab').forEach(t =>
  t.addEventListener('click', () => showTab(t.dataset.tab))
);

document.getElementById('lock-btn').addEventListener('click', async () => {
  await bg({ action: 'vault:lock' });
  show('screen-lock');
});

// ── Provider list ─────────────────────────────────────────────────────────────

let providers = [];
let activeProviderId = null;

async function loadProviders() {
  const r = await safeBg({ action: 'providers:list' });
  providers = r.providers || [];
  activeProviderId = r.activeProviderId;
  renderList();
}

const TYPE_LABEL = {
  'openai-compat': 'OpenAI-Compatible',
  'claude':        'Anthropic Claude',
  'mock':          'Mock (fixture)',
};

/** Preset fills type + baseURL + model placeholder; fields stay editable. */
const PRESETS = {
  claude: {
    label: 'Claude (Anthropic)',
    type: 'claude',
    baseURL: '',
    modelOverride: 'claude-opus-4-8',
    needsKey: true,
    keyUrl: 'https://console.anthropic.com/settings/keys',
  },
  openai: {
    label: 'OpenAI',
    type: 'openai-compat',
    baseURL: 'https://api.openai.com/v1',
    modelOverride: 'gpt-5',
    needsKey: true,
    keyUrl: 'https://platform.openai.com/api-keys',
  },
  gemini: {
    label: 'Gemini (Google)',
    type: 'openai-compat',
    baseURL: 'https://generativelanguage.googleapis.com/v1beta/openai/',
    modelOverride: 'gemini-2.5-pro',
    needsKey: true,
    keyUrl: 'https://aistudio.google.com/apikey',
  },
  glm: {
    label: 'GLM (Zhipu)',
    type: 'openai-compat',
    baseURL: 'https://open.bigmodel.cn/api/paas/v4/',
    modelOverride: 'glm-4.5',
    needsKey: true,
    keyUrl: 'https://open.bigmodel.cn/usercenter/proj-mgmt/apikeys',
  },
  kimi: {
    label: 'Kimi (Moonshot)',
    type: 'openai-compat',
    baseURL: 'https://api.moonshot.cn/v1',
    modelOverride: 'moonshot-v1-128k',
    needsKey: true,
    keyUrl: 'https://platform.moonshot.cn/console/api-keys',
  },
  openrouter: {
    label: 'OpenRouter',
    type: 'openai-compat',
    baseURL: 'https://openrouter.ai/api/v1',
    modelOverride: '',
    needsKey: true,
    keyUrl: 'https://openrouter.ai/keys',
  },
  local: {
    label: 'Local (LM Studio / Ollama)',
    type: 'openai-compat',
    baseURL: 'http://localhost:1234/v1',
    modelOverride: '',
    needsKey: false,
    keyUrl: '',
  },
};

function updateKeyDashboard(key) {
  const a = document.getElementById('key-dashboard');
  if (!a) return;
  const url = PRESETS[key]?.keyUrl;
  if (url) {
    a.href = url;
    a.style.display = '';
  } else {
    a.style.display = 'none';
  }
}

function applyPreset(key) {
  const p = PRESETS[key];
  if (!p) { updateKeyDashboard(''); return; }
  document.getElementById('f-type').value = p.type;
  document.getElementById('f-baseurl').value = p.baseURL;
  document.getElementById('f-model').value = p.modelOverride;
  document.getElementById('f-model').placeholder = p.modelOverride || 'auto';
  if (!document.getElementById('f-name').value.trim()) {
    document.getElementById('f-name').value = p.label;
  }
  updateFieldVisibility(p.type, { keyOptional: !p.needsKey });
  updateKeyDashboard(key);
}

document.getElementById('f-preset').addEventListener('change', e => {
  const key = e.target.value;
  if (key) applyPreset(key);
  else updateKeyDashboard('');
});

document.getElementById('rhaurl-reset')?.addEventListener('click', () => {
  document.getElementById('f-rhaurl').value = 'http://localhost:4100';
});

// View-loss locking REMOVED: closing the popup no longer locks the vault.
// Policy: the vault locks on browser close (the session key lives in
// chrome.storage.session, which dies with the browser) or after 30 idle
// minutes with no job running — never because a popup lost focus.

document.getElementById('test-conn-btn')?.addEventListener('click', async () => {
  const p = buildProviderFromForm();
  const err = document.getElementById('edit-error');
  err.style.display = 'none';
  // Web runtime: verify the provider endpoint directly — there is no RHA
  // server in this mode, and POSTing at the static site is a guaranteed 405.
  if (p.rhaMode !== 'local') {
    try {
      await safeBg({ action: 'providers:verify', provider: p });
      err.style.display = '';
      err.style.color = 'var(--ok,#3fb96f)';
      err.textContent = `✓ verified — ${p.modelOverride || p.baseURL || p.type}`;
    } catch (e) {
      showEditError(e.message || String(e));
    }
    return;
  }
  const rhaUrl = (p.rhaUrl || 'http://localhost:4100').replace(/\/$/, '');
  try {
    const res = await fetch(`${rhaUrl}/api/provider`, {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({
        type: p.type,
        baseURL: p.baseURL || '',
        modelOverride: p.modelOverride || '',
        apiKey: p.apiKey || '',
        role: 'operator',
        index: 0,
      }),
    });
    const j = await res.json().catch(() => ({}));
    if (!res.ok) throw new Error(j.error || `HTTP ${res.status}`);
    err.style.display = '';
    err.style.color = 'var(--ok,#3fb96f)';
    err.textContent = `✓ connected — ${j.model || p.modelOverride || p.type}`;
  } catch (e) {
    showEditError(e.message || String(e));
  }
});

document.getElementById('reveal-key-btn')?.addEventListener('click', () => {
  const input = document.getElementById('f-apikey');
  const btn = document.getElementById('reveal-key-btn');
  if (input.type === 'password') {
    input.type = 'text';
    btn.textContent = 'Mask key';
  } else {
    input.type = 'password';
    btn.textContent = 'Reveal key';
  }
});

function renderList() {
  const el = document.getElementById('provider-list');
  if (!providers.length) {
    el.innerHTML = '<div class="empty">No providers yet. Add one below.</div>';
    return;
  }
  el.innerHTML = providers.map(p => `
    <div class="provider-item" data-id="${p.id}">
      <div class="dot ${p.id === activeProviderId ? 'active' : ''}"></div>
      <div>
        <div class="pname">${esc(p.name)}</div>
        <div class="ptype">${TYPE_LABEL[p.type] || p.type}${p.baseURL ? ' · ' + esc(p.baseURL) : ''}</div>
      </div>
      ${p.id === activeProviderId ? '<span class="badge ok">active</span>' : ''}
    </div>
  `).join('');
  el.querySelectorAll('.provider-item').forEach(row =>
    row.addEventListener('click', () => openEdit(providers.find(p => p.id === row.dataset.id)))
  );
}

// ── Add provider ──────────────────────────────────────────────────────────────

document.getElementById('add-provider-btn').addEventListener('click', () => {
  openEdit({
    id: crypto.randomUUID(),
    name: '',
    type: 'openai-compat',
    baseURL: 'http://localhost:1234/v1',
    modelOverride: '',
    rhaMode: 'web',
    rhaUrl: 'http://localhost:4100',
    apiKey: '',
    _isNew: true,
  });
});

// ── RHA runtime mode (web default / local engine) ─────────────────────────────

function setRhaModeUi(mode) {
  document.getElementById('f-rhamode').value = mode;
  document.getElementById('rhaurl-row').style.display = mode === 'local' ? '' : 'none';
  document.getElementById('rhamode-hint').textContent = mode === 'local'
    ? 'Pushes provider config to your locally running RHA Wrapper server.'
    : 'Web: jobs run in this extension via the Run tab; nothing to install.';
}

document.getElementById('f-rhamode').addEventListener('change', e => setRhaModeUi(e.target.value));

// ── Edit / detail view ────────────────────────────────────────────────────────

let currentProvider = null;

function openEdit(p) {
  currentProvider = { ...p };
  document.getElementById('view-list').style.display = 'none';
  const ev = document.getElementById('view-edit');
  ev.style.display = 'flex';

  document.getElementById('edit-title').textContent = p._isNew ? 'New Provider' : p.name;
  document.getElementById('f-preset').value = '';
  document.getElementById('f-name').value        = p.name        || '';
  document.getElementById('f-type').value        = p.type        || 'openai-compat';
  document.getElementById('f-baseurl').value     = p.baseURL     || 'http://localhost:1234/v1';
  document.getElementById('f-model').value       = p.modelOverride || '';
  // Migration guard: providers saved before the runtime dropdown may carry
  // rha.software as a "server URL" — never a valid local engine address.
  const savedUrl = p.rhaUrl && !/\/\/(www\.)?rha\.software/.test(p.rhaUrl)
    ? p.rhaUrl : 'http://localhost:4100';
  document.getElementById('f-rhaurl').value      = savedUrl;
  setRhaModeUi(p.rhaMode === 'local' ? 'local' : 'web');
  document.getElementById('f-apikey').value      = '';
  document.getElementById('edit-error').style.display = 'none';

  const hasKey = !!p.apiKey;
  const ks = document.getElementById('key-status');
  ks.textContent = hasKey ? '✓ Key stored in vault' : '⬡ No key stored';
  ks.className = `key-status ${hasKey ? 'has-key' : 'no-key'}`;

  updateFieldVisibility(p.type);

  const ind = document.getElementById('active-indicator');
  if (p.id === activeProviderId) {
    ind.innerHTML = '<div class="status-active">✓ Currently active</div>';
    ind.style.display = '';
  } else { ind.style.display = 'none'; }

  document.getElementById('activate-btn').textContent =
    p.id === activeProviderId ? 'Assigned ✓' : 'Assign role';
}

document.getElementById('f-type').addEventListener('change', e => {
  document.getElementById('f-preset').value = '';
  updateFieldVisibility(e.target.value);
});

function updateFieldVisibility(type, opts = {}) {
  document.getElementById('field-baseurl').style.display = type === 'openai-compat' ? '' : 'none';
  document.getElementById('field-model').style.display   = type === 'mock'           ? 'none' : '';
  // The key field is ALWAYS available except for mock — local servers rarely
  // need one, but commercial endpoints behind the same preset shape do, and
  // hiding the field ever proved sticky across preset switches.
  document.getElementById('field-apikey').style.display  = type === 'mock' ? 'none' : '';
  document.getElementById('f-apikey').placeholder = opts.keyOptional
    ? 'Optional — most local servers need no key'
    : 'Paste key to store / replace';
}

document.getElementById('back-btn').addEventListener('click', backToList);

function backToList() {
  document.getElementById('view-edit').style.display = 'none';
  document.getElementById('view-list').style.display = 'flex';
  currentProvider = null;
}

document.getElementById('save-key-btn').addEventListener('click', () => {
  const key = document.getElementById('f-apikey').value.trim();
  if (!key) return;
  if (currentProvider) currentProvider.apiKey = key;
  const ks = document.getElementById('key-status');
  ks.textContent = '✓ Key stored in vault (save to persist)';
  ks.className = 'key-status has-key';
  document.getElementById('f-apikey').value = '';
});

document.getElementById('save-btn').addEventListener('click', saveProvider);
document.getElementById('activate-btn').addEventListener('click', activateProvider);

document.getElementById('delete-btn').addEventListener('click', async () => {
  if (!currentProvider || currentProvider._isNew) { backToList(); return; }
  if (!confirm(`Delete "${currentProvider.name}"?`)) return;
  try {
    await safeBg({ action: 'providers:delete', id: currentProvider.id });
    await loadProviders();
    backToList();
  } catch (e) { showEditError(e.message); }
});

async function saveProvider() {
  const p = buildProviderFromForm();
  if (!p.name) { showEditError('Name is required'); return; }
  try {
    await safeBg({ action: 'providers:save', provider: p });
    await loadProviders();
    currentProvider = { ...p };
    document.getElementById('edit-title').textContent = p.name;
    document.getElementById('edit-error').style.display = 'none';
  } catch (e) { showEditError(e.message); }
}

async function activateProvider() {
  const p = buildProviderFromForm();
  if (!p.name) { showEditError('Name is required'); return; }
  const roleRaw = document.getElementById('f-role')?.value || 'operator';
  if (roleRaw === 'orchestrator') {
    console.warn('[vault] stored orchestrator role ignored — use Operator 2');
  }
  const role = roleRaw.startsWith('operator') ? 'operator'
    : roleRaw === 'supervisor' ? 'supervisor'
    : 'operator';
  const index = roleRaw === 'operator:1' ? 1 : 0;
  try {
    await safeBg({ action: 'providers:save', provider: p });
    await safeBg({
      action: 'providers:activate',
      id: p.id,
      rhaMode: p.rhaMode,
      rhaUrl: p.rhaUrl || 'http://localhost:4100',
      role,
      index,
    });
    activeProviderId = p.id;
    await loadProviders();
    document.getElementById('activate-btn').textContent = 'Active ✓';
    const ind = document.getElementById('active-indicator');
    ind.innerHTML = p.rhaMode === 'local'
      ? '<div class="status-active">✓ Provider activated — local RHA server updated</div>'
      : '<div class="status-active">✓ Provider verified &amp; active — use the Run tab to compile</div>';
    ind.style.display = '';
    document.getElementById('edit-error').style.display = 'none';
  } catch (e) { showEditError(e.message); }
}

function buildProviderFromForm() {
  const keyInput = document.getElementById('f-apikey').value.trim();
  return {
    ...currentProvider,
    _isNew: undefined,
    name:          document.getElementById('f-name').value.trim(),
    type:          document.getElementById('f-type').value,
    baseURL:       document.getElementById('f-baseurl').value.trim(),
    modelOverride: document.getElementById('f-model').value.trim(),
    rhaMode:       document.getElementById('f-rhamode').value === 'local' ? 'local' : 'web',
    rhaUrl:        document.getElementById('f-rhaurl').value.trim(),
    apiKey:        keyInput || currentProvider?.apiKey || '',
  };
}

function showEditError(msg) {
  const e = document.getElementById('edit-error');
  e.textContent = msg; e.style.display = '';
}

// ── Audit log ─────────────────────────────────────────────────────────────────

async function loadAudit() {
  const { log } = await safeBg({ action: 'audit:get' });
  const el = document.getElementById('audit-log');
  if (!log.length) { el.innerHTML = '<div class="empty">No operations yet.</div>'; return; }
  el.innerHTML = log.map(e => `
    <div class="audit-entry">
      <span class="audit-ts">${new Date(e.ts).toLocaleTimeString()}</span>
      <span class="audit-op">${esc(e.op)}</span>
      <span>${esc(e.provider || '')}</span>
      ${e.ok !== undefined
        ? `<span class="${e.ok ? 'audit-ok' : 'audit-fail'}">${e.ok ? '✓' : '✗'}</span>`
        : ''}
      ${e.ms ? `<span style="color:var(--dim);margin-left:auto">${e.ms}ms</span>` : ''}
    </div>
  `).join('');
}

// ── Tier 1 runner (no local server) ───────────────────────────────────────────

let t1Poll = null;

function t1Error(msg) {
  const el = document.getElementById('t1-error');
  el.textContent = msg || '';
  el.style.display = msg ? 'block' : 'none';
}

/** Tone per pipeline event — mirrors the web app's engine-log palette. */
function t1Tone(e) {
  const m = e.message || '';
  switch (e.phase) {
    case 'encode': return 'accent';
    case 'gate': return m.includes('clean') ? 'ok' : 'warn';
    case 'ir': return m.includes('frozen') ? 'ok' : m.includes('abandoned') ? 'bad' : 'warn';
    case 'build': return m.includes('boundary') ? 'warn' : 'ok';
    case 'auth': {
      if (m.includes('crashed')) return 'bad';
      if (m.includes('converged')) return 'ok';
      const f = /(\d+)\/(\d+) passing/.exec(m);
      if (f) return f[1] === f[2] ? 'ok' : f[1] === '0' ? 'bad' : 'warn';
      return 'neutral';
    }
    case 'rha': return m.includes('failed') ? 'bad' : 'warn';
    case 'done': return 'accent';
    default: return 'neutral';
  }
}

function renderTier1(st) {
  const log = document.getElementById('t1-log');
  const events = st.events || [];
  const t0 = events.length ? new Date(events[0].ts).getTime() : Date.now();
  log.innerHTML = events.map(e => {
    const secs = Math.max(0, Math.round((new Date(e.ts).getTime() - t0) / 1000));
    const ts = secs >= 60 ? `${Math.floor(secs / 60)}m${String(secs % 60).padStart(2, '0')}s` : `${secs}s`;
    return `<div class="t1row t1-${t1Tone(e)}">`
      + `<span class="t1ts">${ts}</span>`
      + `<span class="t1msg">${e.featureId ? esc(e.featureId) + ' · ' : ''}${esc(e.message)}</span>`
      + `</div>`;
  }).join('');
  log.scrollTop = log.scrollHeight;

  const corpusEl = document.getElementById('t1-corpus');
  if (st.corpus?.offline) {
    corpusEl.textContent = 'corpus: offline (bundled defaults in use)';
  } else if (st.corpus) {
    corpusEl.textContent = `corpus: rha.software ${st.corpus.appVersion} (${st.corpus.generatedAt?.slice(0, 10) || ''})`;
  }

  document.getElementById('t1-run').disabled = !!st.running;
  document.getElementById('t1-export').disabled = !st.summary;
  if (st.error) t1Error(st.error);
  if (st.summary && !st.running) {
    const s = st.summary;
    const probs = s.problems?.length ? ` — ${s.problems.length} unresolved problems` : '';
    log.insertAdjacentHTML('beforeend',
      `<div class="t1row t1-accent"><span class="t1ts">—</span><span class="t1msg">${esc(s.title)}: ${s.features} features, ${s.suites} frozen suites${esc(probs)}</span></div>`);
  }
  if (!st.running && t1Poll) { clearInterval(t1Poll); t1Poll = null; }
}

async function pollTier1() {
  const st = await safeBg({ action: 'tier1:status' });
  if (st && !st.error) renderTier1(st);
  else if (st?.error) renderTier1({ running: false, events: [], error: st.error });
}

document.getElementById('t1-run').addEventListener('click', async () => {
  t1Error('');
  const prompt = document.getElementById('t1-prompt').value.trim();
  if (!prompt) { t1Error('Describe the product first.'); return; }
  const vanilla = !!document.getElementById('t1-vanilla')?.checked;
  const res = await safeBg({
    action: 'tier1:run',
    payload: { prompt, mode: vanilla ? 'vanilla' : 'rha' },
  });
  if (res?.error) { t1Error(res.error); return; }
  document.getElementById('t1-export').disabled = true;
  if (!t1Poll) t1Poll = setInterval(pollTier1, 1000);
  pollTier1();
});

document.getElementById('t1-export').addEventListener('click', async () => {
  t1Error('');
  const res = await safeBg({ action: 'tier1:export' });
  if (res?.error) t1Error(res.error);
});

// Resume display if a job is running/finished while popup was closed.
pollTier1().then(() => {
  // keep polling only if a run is in flight
  safeBg({ action: 'tier1:status' }).then(st => {
    if (st?.running && !t1Poll) t1Poll = setInterval(pollTier1, 1000);
  });
});

// ── Helpers ───────────────────────────────────────────────────────────────────

function esc(s) {
  return String(s).replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;').replace(/"/g,'&quot;');
}

// ── Boot ──────────────────────────────────────────────────────────────────────
// Self-reported version in the footer: what THIS loaded copy actually is —
// the ground truth when debugging "which build am I running?"
try {
  document.getElementById('ext-version').textContent = `v${chrome.runtime.getManifest().version}`;
} catch { /* non-extension context */ }
init();
