const form = document.querySelector("#runForm");
const cancelBtn = document.querySelector("#cancelBtn");
const resumeBtn = document.querySelector("#resumeBtn");
const killBtn = document.querySelector("#killBtn");
const resetBtn = document.querySelector("#resetBtn");
const apiStatus = document.querySelector("#apiStatus");
const runStatus = document.querySelector("#runStatus");
const progressBar = document.querySelector("#progressBar");
const leadRows = document.querySelector("#leadRows");
const updates = document.querySelector("#updates");
const decisions = document.querySelector("#decisions");
const repairs = document.querySelector("#repairs");
const skills = document.querySelector("#skills");
const failures = document.querySelector("#failures");
const formError = document.querySelector("#formError");
const downloadBtn = document.querySelector("#downloadBtn");
const campaignDraftBtn = document.querySelector("#campaignDraftBtn");
const campaignSaveBtn = document.querySelector("#campaignSaveBtn");
const campaignSendBtn = document.querySelector("#campaignSendBtn");
const campaignSubject = document.querySelector("#campaignSubject");
const campaignFrom = document.querySelector("#campaignFrom");
const campaignBody = document.querySelector("#campaignBody");
const campaignStatus = document.querySelector("#campaignStatus");
const campaignList = document.querySelector("#campaignList");
const leadSummary = document.querySelector("#leadSummary");
const runSelect = document.querySelector("#runSelect");
const scoreFilter = document.querySelector("#scoreFilter");
const providerStatus = document.querySelector("#providerStatus");
const apiKeyInput = document.querySelector("#apiKeyInput");
const apiBaseInput = document.querySelector("#apiBaseInput");
const apiModelInput = document.querySelector("#apiModelInput");
const nicheInput = form.elements.niche;
const locationInput = form.elements.location;
const countryInput = form.elements.country;
const targetInput = form.elements.target;
const minScoreInput = form.elements.minScore;
const leadWorkerInput = form.elements.leadWorkerConcurrency;
const ownerAgentInput = form.elements.ownerAgentSwarmSize;
const maxPlannedLocationsInput = form.elements.maxPlannedLocations;
const ownerHuntTimeoutInput = form.elements.ownerHuntTimeoutMs;
const criteriaInput = form.elements.criteria;
const allUsaInput = document.querySelector("#allUsaInput");
let sourceInput = document.querySelector("#sourceInput");
const workflowSteps = document.querySelector("#workflowSteps");
const loopSteps = document.querySelector("#loopSteps");
const currentKeyword = document.querySelector("#currentKeyword");

let currentRunId = "";
let currentRun = null;
let runSummaries = [];
let events = null;
let portfolioDefaults = null;
let repairPlaybook = null;
const accessToken = getAccessToken();
const OWNER_FIRST_STRATEGY_VERSION = "owner-first-v4";
const LEAD_SCRAPER_STRATEGY_VERSION = "lead-scraper-v1";
const LEAD_SCRAPER_SOURCE_LABEL = "Google Maps \u2192 websites/directories/social \u2192 validated business contacts";
const ACTIVE_RUN_STATUSES = new Set(["queued", "running"]);
const RECOVERABLE_RUN_STATUSES = new Set(["queued", "running", "interrupted"]);

const HERMES_WORKFLOW = [
  ["seed_discovery", "Seed Discovery"],
  ["business_validation", "Business Validation"],
  ["owner_evidence_discovery", "Website/Social Scrape"],
  ["owner_identity_proof", "Contact Extraction"],
  ["owner_profile_rabbit_hole", "Validation"],
  ["owner_contact_extraction", "Campaign Readiness"],
  ["quality_gate", "Quality Gate"],
  ["csv_delivery", "CSV Delivery"]
];

const HERMES_LOOP = [
  ["detect", "Detect"],
  ["classify", "Classify"],
  ["retry", "Retry"],
  ["evolve", "Evolve"],
  ["self_repair", "Self-Repair"],
  ["resume", "Resume"]
];

document.querySelectorAll(".tab").forEach((button) => {
  if (["decisions", "repairs", "skills", "failures"].includes(button.dataset.tab)) button.classList.add("internal-tab");
  button.addEventListener("click", () => {
    document.querySelectorAll(".tab, .tab-pane").forEach((el) => el.classList.remove("active"));
    button.classList.add("active");
    document.querySelector(`#${button.dataset.tab}`).classList.add("active");
  });
});

form.addEventListener("submit", async (event) => {
  event.preventDefault();
  setError("");
  applyPortfolioDefaults({ onlyEmpty: true });
  syncAllUsaMode();
  await stopCurrentRunForNewQuery();
  const payload = Object.fromEntries(new FormData(form).entries());
  payload.source = "lead-scraper";
  payload.strategyVersion = LEAD_SCRAPER_STRATEGY_VERSION;
  runStatus.textContent = "starting";
  const response = await fetchApi("/api/runs", {
    method: "POST",
    headers: { "content-type": "application/json" },
    body: JSON.stringify(payload)
  });
  const run = await response.json();
  if (!response.ok) return setError(run.error || "Unable to start the run.");
  connect(run.id);
  render(run);
});

cancelBtn.addEventListener("click", async () => {
  if (!currentRunId) return;
  await fetchApi(`/api/runs/${currentRunId}/cancel`, { method: "POST" });
});

resumeBtn.addEventListener("click", async () => {
  if (!currentRunId) return;
  setError("");
  const response = await fetchApi(`/api/runs/${currentRunId}/resume`, { method: "POST" });
  const run = await response.json();
  if (!response.ok) return setError(run.error || "Unable to resume the run.");
  connect(run.id);
  render(run);
});

killBtn.addEventListener("click", async () => {
  if (!currentRunId) return;
  const response = await fetchApi(`/api/runs/${currentRunId}/kill`, { method: "POST" });
  const run = await response.json();
  if (!response.ok) return setError(run.error || "Unable to kill the run.");
  render(run);
});

resetBtn.addEventListener("click", () => resetCurrentWorkflow());

[scoreFilter].forEach((input) => {
  input.addEventListener("input", () => currentRun && render(currentRun));
});

downloadBtn.addEventListener("click", () => {
  if (!currentRunId || downloadBtn.disabled) return;
  window.location.href = apiUrl(`/api/runs/${encodeURIComponent(currentRunId)}/leads.csv?approved=1`);
});

campaignDraftBtn.addEventListener("click", () => {
  document.querySelectorAll(".tab, .tab-pane").forEach((el) => el.classList.remove("active"));
  document.querySelector('[data-tab="campaigns"]').classList.add("active");
  document.querySelector("#campaigns").classList.add("active");
  prefillCampaign();
});

campaignSaveBtn.addEventListener("click", async () => {
  if (!currentRunId) return;
  const campaign = await createCampaignDraft();
  if (campaign) renderCampaign(campaign);
});

campaignSendBtn.addEventListener("click", async () => {
  const campaignId = campaignSendBtn.dataset.campaignId;
  if (!campaignId) return;
  const response = await fetchApi(`/api/campaigns/${encodeURIComponent(campaignId)}/send`, { method: "POST" });
  const campaign = await response.json();
  if (!response.ok) return setError(campaign.error || "Campaign send failed.");
  renderCampaign(campaign);
});

document.querySelector("#saveApiBtn").addEventListener("click", async () => {
  const payload = cleanPayload({
    apiKey: apiKeyInput.value,
    baseUrl: apiBaseInput.value,
    model: apiModelInput.value
  });
  const response = await fetchApi("/api/settings/provider", {
    method: "POST",
    headers: { "content-type": "application/json" },
    body: JSON.stringify(payload)
  });
  const result = await response.json();
  if (!response.ok) return setError(result.error || "Provider update failed.");
  apiKeyInput.value = "";
  renderSettings(result);
  await health();
  setError("Provider settings updated.");
});

leadRows.addEventListener("click", async (event) => {
  const button = event.target.closest("button[data-action]");
  if (!button || !currentRunId) return;
  const index = button.dataset.index;
  const action = button.dataset.action;
  const body = action === "approve"
    ? { approved: button.dataset.approved !== "true" }
    : { excluded: true };
  const response = await fetchApi(`/api/runs/${currentRunId}/leads/${index}`, {
    method: "PATCH",
    headers: { "content-type": "application/json" },
    body: JSON.stringify(body)
  });
  const run = await response.json();
  if (!response.ok) return setError(run.error || "Unable to update lead.");
  render(run);
});

async function health() {
  const response = await fetchApi("/api/health");
  const data = await response.json();
  const toolReady = data.tools?.googleMapsScrapper && data.tools?.scraplingEnricher && data.tools?.ownerProfileTools?.ok;
  apiStatus.textContent = data.minimaxConfigured && toolReady ? "Ready" : "Needs setup";
}

async function loadSettings() {
  const response = await fetchApi("/api/settings");
  if (!response.ok) return;
  renderSettings(await response.json());
}

async function loadPortfolioDefaults() {
  const response = await fetchApi("/api/portfolio-defaults");
  if (!response.ok) return;
  portfolioDefaults = await response.json();
  applyPortfolioDefaults({ onlyEmpty: true });
}

async function loadRepairPlaybook() {
  const response = await fetchApi("/api/repair-playbook");
  if (!response.ok) return;
  repairPlaybook = await response.json();
  if (currentRun) render(currentRun);
  else renderRepairs(null);
}

function applyPortfolioDefaults({ onlyEmpty = false } = {}) {
  if (!portfolioDefaults) return;
  const assign = (element, value) => {
    if (!element || value === undefined || value === null) return;
    if (onlyEmpty && String(element.value || "").trim()) return;
    element.value = value;
  };
  assign(nicheInput, portfolioDefaults.niche);
  assign(locationInput, portfolioDefaults.location || portfolioDefaults.cities);
  assign(countryInput, portfolioDefaults.country);
  assign(targetInput, portfolioDefaults.target);
  assign(minScoreInput, portfolioDefaults.minScore);
  assign(maxPlannedLocationsInput, portfolioDefaults.maxPlannedLocations);
  assign(ownerHuntTimeoutInput, portfolioDefaults.ownerHuntTimeoutMs);
  assign(leadWorkerInput, portfolioDefaults.leadWorkerConcurrency);
  assign(ownerAgentInput, portfolioDefaults.ownerAgentSwarmSize);
  assign(criteriaInput, portfolioDefaults.criteria);
}

function renderSettings(settings) {
  const queue = settings.provider?.minimaxQueue;
  const queueText = queue ? ` · queue ${queue.active}/${queue.pending} · limit ${queue.maxConcurrency}` : "";
  providerStatus.textContent = settings.provider?.minimaxConfigured ? `Connected (${settings.provider.model})${queueText}` : "Missing key";
}

async function stopCurrentRunForNewQuery() {
  if (events) events.close();
  events = null;

  const summaries = await loadRuns();
  const activeRuns = summaries.filter((run) => ACTIVE_RUN_STATUSES.has(run.status));
  await Promise.all(activeRuns.map((run) => fetchApi(`/api/runs/${encodeURIComponent(run.id)}/kill`, { method: "POST" }).catch(() => {})));

  resetView();
}

async function resetCurrentWorkflow() {
  setError("");
  resetBtn.disabled = true;
  const runId = currentRunId;
  const status = currentRun?.status;
  if (events) events.close();
  events = null;

  try {
    if (runId && ACTIVE_RUN_STATUSES.has(status)) {
      await fetchApi(`/api/runs/${encodeURIComponent(runId)}/kill`, { method: "POST" });
    } else {
      const summaries = await loadRuns();
      const activeRuns = summaries.filter((run) => ACTIVE_RUN_STATUSES.has(run.status));
      await Promise.all(activeRuns.map((run) => fetchApi(`/api/runs/${encodeURIComponent(run.id)}/kill`, { method: "POST" }).catch(() => {})));
    }
  } finally {
    resetView();
    resetBtn.disabled = false;
  }
}

async function loadRuns(preferredId = "") {
  const response = await fetchApi("/api/runs");
  if (!response.ok) return [];
  runSummaries = await response.json();
  if (!runSelect) return runSummaries;
  runSelect.innerHTML = '<option value="">No run selected</option>';
  for (const run of runSummaries) {
    const option = document.createElement("option");
    option.value = run.id;
    const ownerYield = run.ownerHunts ? ` · owner ${run.ownerContacts || 0}/${run.ownerHunts}` : "";
    const pending = run.pendingDiscovered ? ` · pending ${run.pendingDiscovered}` : "";
    option.textContent = `${run.niche || "run"} · ${run.status} · ${run.qualifiedCount}/${run.target || "?"}${ownerYield}${pending}`;
    runSelect.appendChild(option);
  }
  if (preferredId) runSelect.value = preferredId;
  return runSummaries;
}

async function loadRun(id, { connectLive = false } = {}) {
  const response = await fetchApi(`/api/runs/${encodeURIComponent(id)}`);
  if (!response.ok) return;
  const run = await response.json();
  render(run);
  if (connectLive && ACTIVE_RUN_STATUSES.has(run.status)) connect(run.id);
}

async function loadActiveRun() {
  const summaries = await loadRuns();
  const activeRuns = summaries.filter((run) => ACTIVE_RUN_STATUSES.has(run.status));
  const active = mostRecentlyStarted(activeRuns)
    || mostRecentlyStarted(summaries.filter((run) => RECOVERABLE_RUN_STATUSES.has(run.status)));
  await Promise.all(activeRuns
    .filter((run) => run.id !== active?.id)
    .map((run) => fetchApi(`/api/runs/${encodeURIComponent(run.id)}/kill`, { method: "POST" }).catch(() => {})));
  const selected = active || mostRecentlyStarted(summaries);
  if (!selected?.id) return null;
  await loadRun(selected.id, { connectLive: ACTIVE_RUN_STATUSES.has(selected.status) });
  return selected;
}

function mostRecentlyStarted(runs) {
  return runs
    .slice()
    .sort((a, b) => String(b.createdAt || b.updatedAt).localeCompare(String(a.createdAt || a.updatedAt)))[0] || null;
}

function connect(id) {
  if (events) events.close();
  events = new EventSource(apiUrl(`/api/runs/${id}/events`));
  events.addEventListener("snapshot", (event) => render(JSON.parse(event.data)));
  events.addEventListener("update", (event) => render(JSON.parse(event.data)));
  events.addEventListener("error", () => {
    if (runStatus) runStatus.textContent = "reconnecting";
  });
}

function render(run) {
  currentRun = run;
  currentRunId = run.id || currentRunId;
  if (runSelect) runSelect.value = currentRunId;
  runStatus.textContent = run.status;
  syncCurrentRunInputs(run);
  if (run.input?.source && sourceInput.value !== run.input.source) {
    sourceInput.value = run.input.source;
    syncSourceMode();
  }
  renderWorkflow(run);
  document.querySelector("#qualifiedCount").textContent = run.counters?.qualified || 0;
  document.querySelector("#discoveredCount").textContent = run.counters?.discovered || 0;
  document.querySelector("#rejectedCount").textContent = run.counters?.rejected || 0;
  document.querySelector("#ownerRabbitHoleCount").textContent = run.counters?.ownerRabbitHoles || 0;
  document.querySelector("#ownerProfileCount").textContent = run.counters?.ownerProfilesFound || 0;
  document.querySelector("#locationCount").textContent = run.input?.cities?.length || 0;
  document.querySelector("#retryCount").textContent = run.counters?.retries || 0;
  document.querySelector("#failureCount").textContent = run.failures?.length || 0;
  const target = Number(run.input?.target || 1);
  const qualified = Number(run.qualifiedLeads?.filter((lead) => !lead.excluded).length || 0);
  progressBar.style.width = `${Math.min(100, Math.round((qualified / target) * 100))}%`;
  cancelBtn.disabled = !["queued", "running"].includes(run.status);
  killBtn.disabled = !["queued", "running"].includes(run.status);
  resumeBtn.disabled = !["partial", "failed", "cancelled", "killed", "interrupted"].includes(run.status);

  const leads = visibleLeads();
  const activeCount = (run.qualifiedLeads || []).filter((lead) => !lead.excluded).length;
  const emailCount = leads.filter(({ lead }) => leadContactEmail(lead)).length;
  const phoneCount = leads.filter(({ lead }) => leadContactPhone(lead)).length;
  const websiteCount = leads.filter(({ lead }) => lead.website).length;
  const ownerAgents = Number(run.scaleAudit?.ownerAgentsDeployed || run.counters?.ownerAgentsDeployed || 0);
  const pendingDiscovered = Number(run.scaleAudit?.pendingDiscovered || 0);
  const exportEnabled = currentRunId && activeCount > 0;
  downloadBtn.disabled = !exportEnabled;
  campaignDraftBtn.disabled = !exportEnabled || emailCount === 0;
  leadSummary.textContent = `${leads.length}/${activeCount} visible · contacts ${leads.length}${ownerAgents ? ` · agents ${ownerAgents}` : ""}${pendingDiscovered ? ` · pending ${pendingDiscovered}` : ""} · email ${emailCount} · phone ${phoneCount} · website ${websiteCount}`;

  leadRows.innerHTML = "";
  for (const { lead, index } of leads) {
    const tr = document.createElement("tr");
    if (lead.excluded) tr.className = "excluded";
    const approved = lead.approved !== false;
    tr.innerHTML = `
      <td><strong>${escapeHtml(lead.name)}</strong><br><span class="muted">${escapeHtml(ownerLabel(lead) || lead.category || "")}</span></td>
      <td>${escapeHtml(lead.city || "")}</td>
      <td>${formatContact(lead)}</td>
      <td>${escapeHtml(sourceLabel(lead))}</td>
      <td>${escapeHtml(String(lead.quality?.score ?? ""))}</td>
      <td><div class="lead-actions">
        <button type="button" class="secondary" data-action="approve" data-index="${index}" data-approved="${approved}">${approved ? "Unapprove" : "Approve"}</button>
        <button type="button" class="secondary" data-action="exclude" data-index="${index}">Hide</button>
      </div></td>
    `;
    leadRows.appendChild(tr);
  }

  renderList(updates, (run.steps || []).slice(-120).reverse().map(formatStep));
  renderList(decisions, (run.agentDecisions || []).slice(-120).reverse().map(formatDecision));
  renderRepairs(run);
  renderList(skills, (run.skillUpdates || []).map((skill) => `${skill.title}: ${skill.do}`));
  renderList(failures, (run.failures || []).slice().reverse().map((failure) => failure.message), true);
}

function visibleLeads() {
  const minScore = Number(scoreFilter.value || 0);
  return (currentRun?.qualifiedLeads || [])
    .map((lead, index) => ({ lead, index }))
    .filter(({ lead }) => !lead.excluded)
    .filter(({ lead }) => (lead.quality?.score ?? 0) >= minScore);
}

function resetView() {
  currentRunId = "";
  currentRun = null;
  if (events) events.close();
  events = null;
  if (runSelect) runSelect.value = "";
  runStatus.textContent = "idle";
  if (currentKeyword) currentKeyword.textContent = "No run loaded";
  progressBar.style.width = "0";
  ["qualifiedCount", "discoveredCount", "rejectedCount", "ownerRabbitHoleCount", "ownerProfileCount", "locationCount", "retryCount", "failureCount"].forEach((id) => {
    document.querySelector(`#${id}`).textContent = "0";
  });
  leadSummary.textContent = "No run loaded";
  leadRows.innerHTML = "";
  [updates, decisions, repairs, skills, failures].forEach((node) => { node.innerHTML = ""; });
  [cancelBtn, resumeBtn, killBtn, downloadBtn, campaignDraftBtn, campaignSaveBtn, campaignSendBtn].forEach((button) => { button.disabled = true; });
  renderWorkflow(null);
  setError("");
}

function syncCurrentRunInputs(run) {
  const keyword = String(run.input?.niche || "").trim();
  if (currentKeyword) currentKeyword.textContent = keyword || "No keyword";
  if (keyword && document.activeElement !== nicheInput) nicheInput.value = keyword;
  if (run.input?.locationScope && document.activeElement !== locationInput) locationInput.value = run.input.locationScope;
  if (run.input?.countryScope && document.activeElement !== countryInput) countryInput.value = run.input.countryScope;
  if (run.input?.target && document.activeElement !== targetInput) targetInput.value = run.input.target;
}

function sourceLabel(lead) {
  if (currentRun?.input?.source === "lead-scraper") return lead.discoverySource || lead.sourceQuery ? "Lead scraper" : "Business contact";
  if (lead.sourceQuery && lead.enrichmentMode) return "Maps + owner rabbit-hole";
  if (lead.sourceQuery) return "Google Maps";
  if (lead.enrichmentMode) return "Owner rabbit-hole";
  return "Qualified";
}

function renderWorkflow(run) {
  const source = run?.input?.source || sourceInput.value || "multi-source-owner-evidence";
  const phases = (run?.steps || []).map((step) => step.phase);
  const completedStages = new Set(phases.map(workflowStageForPhase).filter(Boolean));
  const completedLoops = new Set(phases.filter((phase) => HERMES_LOOP.some(([id]) => id === phase)));
  const lastPhase = [...phases].reverse().find((phase) => phase !== "status") || "";
  const activeStage = workflowStageForPhase(lastPhase);
  const activeLoop = HERMES_LOOP.some(([id]) => id === lastPhase) ? lastPhase : "";
  const terminal = ["complete", "partial", "failed", "cancelled", "killed", "interrupted"].includes(run?.status);
  if (terminal && phases.includes("deliver")) completedStages.add("csv_delivery");

  workflowSteps.innerHTML = HERMES_WORKFLOW.map(([id, label]) => {
    const state = id === activeStage ? "active" : completedStages.has(id) ? "complete" : "";
    return `<li class="${state}"><span>${escapeHtml(labelForWorkflowStep(id, label, source))}</span></li>`;
  }).join("");

  loopSteps.innerHTML = HERMES_LOOP.map(([id, label]) => {
    const state = id === activeLoop ? "active" : completedLoops.has(id) ? "complete" : "";
    return `<li class="${state}"><span>${escapeHtml(label)}</span></li>`;
  }).join("");
}

function workflowStageForPhase(phase) {
  return ({
    client_input: "seed_discovery",
    plan: "seed_discovery",
    maps_discovery: "seed_discovery",
    business_validation: "business_validation",
    website_enrichment: "owner_evidence_discovery",
    lead_extraction: "owner_identity_proof",
    business_lead_qualification: "owner_profile_rabbit_hole",
    owner_profile_rabbit_hole: "owner_profile_rabbit_hole",
    owner_contact_extraction: "owner_contact_extraction",
    quality_gate: "quality_gate",
    deliver: "csv_delivery",
    delivery: "csv_delivery"
  })[phase] || "";
}

function labelForWorkflowStep(id, label, source) {
  return label;
}

function phaseLabel(phase) {
  const workflow = HERMES_WORKFLOW.find(([id]) => id === phase);
  if (workflow) return workflow[1];
  const loop = HERMES_LOOP.find(([id]) => id === phase);
  if (loop) return loop[1];
  return String(phase || "").replace(/[_-]+/g, " ").replace(/\b\w/g, (char) => char.toUpperCase());
}

function renderList(node, items, bad = false) {
  node.innerHTML = "";
  for (const item of items) {
    const li = document.createElement("li");
    if (item && typeof item === "object") {
      if (item.time) {
        const time = document.createElement("span");
        time.className = "log-time";
        time.textContent = item.time;
        li.append(time, document.createTextNode(" "));
      }
      li.append(document.createTextNode(item.text || ""));
    } else {
      li.textContent = item;
    }
    if (bad) li.className = "bad";
    node.appendChild(li);
  }
}

function formatStep(step) {
  return {
    time: formatTimestamp(step.at),
    text: `${step.phaseLabel || phaseLabel(step.phase)}: ${step.message}`
  };
}

function formatTimestamp(value) {
  const date = new Date(value);
  if (Number.isNaN(date.getTime())) return "";
  return date.toLocaleTimeString([], {
    hour: "2-digit",
    minute: "2-digit",
    second: "2-digit"
  });
}

function renderRepairs(run) {
  const items = [];
  const activeRepairs = run?.flowWatchdog?.repairs || [];
  const selfHealing = run?.selfHealing || [];

  if (activeRepairs.length) {
    items.push("FlowWatchdog repair actions");
    for (const repair of activeRepairs.slice(0, 25)) {
      items.push(formatRepair(repair));
    }
  }

  if (selfHealing.length) {
    items.push("Checkpoint self-healing");
    for (const heal of selfHealing.slice(-10).reverse()) {
      items.push(`${heal.reason || "self-heal"}: ${formatRepair(heal.repair || {})}`);
    }
  }

  if (repairPlaybook) {
    items.push(`Training kit: ${repairPlaybook.codingTools?.length || 0} coding tools, ${repairPlaybook.skills?.length || 0} repair skills, ${repairPlaybook.plugins?.length || 0} plugins, ${repairPlaybook.diagnosticRunbooks?.length || 0} drills, ${repairPlaybook.patchTemplates?.length || 0} patch templates, ${repairPlaybook.guardrails?.length || 0} guardrails`);
    for (const tool of repairPlaybook.codingTools || []) {
      items.push(`Tool ${tool.id}: ${tool.command} - ${tool.useFor}${tool.safeToRunAutomatically ? " (auto)" : " (plan/review)"}`);
    }
    for (const plugin of repairPlaybook.plugins || []) {
      items.push(`Plugin ${plugin.id}: ${plugin.useFor}`);
    }
    for (const skill of repairPlaybook.skills || []) {
      items.push(`Skill ${skill.id}: when ${skill.trigger}; repair ${skill.repair}`);
    }
    for (const runbook of repairPlaybook.diagnosticRunbooks || []) {
      items.push(`Drill ${runbook.id}: ${runbook.trigger} → ${(runbook.steps || []).join(" | ")}`);
    }
    for (const template of repairPlaybook.patchTemplates || []) {
      items.push(`Patch template ${template.id}: ${template.symptom} → ${template.recipe}`);
    }
    for (const patch of repairPlaybook.patchIdeas || []) {
      items.push(`Patch idea: ${patch.symptom} → ${patch.idea}`);
    }
    for (const guardrail of repairPlaybook.guardrails || []) {
      items.push(`Guardrail: ${guardrail}`);
    }
  }

  renderList(repairs, items.length ? items : ["No repair training loaded yet."]);
}

function formatDecision(decision) {
  const parts = [`${decision.type}: ${decision.action}`];
  if (decision.variable) parts.push(`Variable: ${decision.variable}`);
  if (decision.tool) parts.push(`Tool: ${decision.tool}`);
  if (decision.runbook) parts.push(`Runbook: ${decision.runbook}`);
  if (decision.patchTemplate) parts.push(`Patch: ${decision.patchTemplate}`);
  if (decision.validationCommand) parts.push(`Validate: ${decision.validationCommand}`);
  if (decision.patchIdea) parts.push(`Patch idea: ${decision.patchIdea}`);
  if (decision.reason) parts.push(`Reason: ${decision.reason}`);
  return parts.join(" ");
}

function formatRepair(repair) {
  const parts = [repair.action || "repair action pending"];
  if (repair.variable) parts.push(`variable=${repair.variable}`);
  if (repair.tool) parts.push(`tool=${repair.tool}`);
  if (repair.runbook) parts.push(`runbook=${repair.runbook}`);
  if (repair.patchTemplate) parts.push(`patchTemplate=${repair.patchTemplate}`);
  if (repair.validationCommand) parts.push(`validate=${repair.validationCommand}`);
  if (repair.patchIdea) parts.push(`patch=${repair.patchIdea}`);
  if (repair.reason) parts.push(`reason=${repair.reason}`);
  return parts.join(" · ");
}

function formatContact(lead) {
  const parts = [];
  if (currentRun?.input?.source === "lead-scraper") {
    if (lead.contactConfidence) parts.push(`<span class="muted">${escapeHtml(businessContactLabel(lead))}</span>`);
    if (leadContactEmail(lead)) parts.push(`<span class="muted">Email: ${escapeHtml(leadContactEmail(lead))}</span>`);
    if (leadContactPhone(lead)) parts.push(`<span class="muted">Phone: ${escapeHtml(leadContactPhone(lead))}</span>`);
    for (const form of (lead.contactForms || []).slice(0, 2)) parts.push(`<a href="${escapeAttr(form)}" target="_blank" rel="noreferrer">Contact form</a>`);
    for (const social of (lead.socialLinks || lead.ownerSocialProfiles || []).slice(0, 2)) parts.push(`<a href="${escapeAttr(social)}" target="_blank" rel="noreferrer">Social</a>`);
    if (lead.website) parts.push(`<a href="${escapeAttr(lead.website)}" target="_blank" rel="noreferrer">Website</a>`);
    return parts.join("<br>") || '<span class="muted">Pending</span>';
  }
  if (lead.contactConfidence) parts.push(`<span class="muted">${escapeHtml(contactConfidenceLabel(lead))}</span>`);
  if (lead.ownerName) parts.push(`<span class="muted">Owner: ${escapeHtml(lead.ownerName)}</span>`);
  const email = leadContactEmail(lead);
  const phone = leadContactPhone(lead);
  if (email) parts.push(`<span class="muted">Reach owner by email: ${escapeHtml(email)}</span>`);
  if (phone) parts.push(`<span class="muted">Reach owner by phone: ${escapeHtml(phone)}</span>`);
  if (lead.ownerLinkedIn) parts.push(`<a href="${escapeAttr(lead.ownerLinkedIn)}" target="_blank" rel="noreferrer">Owner LinkedIn</a>`);
  if (lead.ownerAgentSwarm) parts.push(`<span class="muted">Agents: ${escapeHtml(swarmLabel(lead.ownerAgentSwarm))}</span>`);
  const toolLabelText = profileToolLabel(lead);
  if (toolLabelText) parts.push(`<span class="muted">Profile tools: ${escapeHtml(toolLabelText)}</span>`);
  if (lead.website) parts.push(`<a href="${escapeAttr(lead.website)}" target="_blank" rel="noreferrer">Website</a>`);
  return parts.join("<br>") || '<span class="muted">Pending</span>';
}

function leadContactEmail(lead) {
  return lead?.ownerEmail || lead?.email || (lead?.contactConfidence === "owner-associated" ? lead?.businessEmail : "") || "";
}

function leadContactPhone(lead) {
  return lead?.ownerPhone || lead?.phone || (lead?.contactConfidence === "owner-associated" ? lead?.businessPhone : "") || "";
}

function contactConfidenceLabel(lead) {
  return ({
    "owner-direct": "Owner reachable",
    "owner-associated": "Owner reachable",
    "business-only": "Not qualified",
    "no-contact": "No owner contact"
  })[lead?.contactConfidence] || lead?.contactConfidence || "";
}

function businessContactLabel(lead) {
  return ({
    "business-direct": "Business contact",
    "contact-form": "Contact form",
    "website-only": "Website only",
    "no-contact": "No contact"
  })[lead?.contactConfidence] || lead?.contactConfidence || "";
}

function prefillCampaign() {
  const niche = currentRun?.input?.niche || nicheInput.value || "your business";
  const location = currentRun?.input?.locationScope || locationInput.value || "";
  campaignSubject.value ||= `Quick question about ${niche}`;
  campaignBody.value ||= `Hi {{name}},

I found {{business}} while researching ${niche}${location ? ` in ${location}` : ""}.

Would you be open to a quick conversation this week?

Reply here or unsubscribe: {{unsubscribe_url}}`;
  campaignSaveBtn.disabled = !currentRunId;
  campaignStatus.textContent = "Draft ready. Save it before sending.";
}

async function createCampaignDraft() {
  const response = await fetchApi("/api/campaigns", {
    method: "POST",
    headers: { "content-type": "application/json" },
    body: JSON.stringify(cleanPayload({
      runId: currentRunId,
      subject: campaignSubject.value,
      body: campaignBody.value,
      fromEmail: campaignFrom.value
    }))
  });
  const campaign = await response.json();
  if (!response.ok) {
    setError(campaign.error || "Could not create campaign draft.");
    return null;
  }
  return campaign;
}

function renderCampaign(campaign) {
  campaignStatus.textContent = `${campaign.status} · ${campaign.recipients?.length || 0} recipients · ${campaign.backend}`;
  campaignSendBtn.disabled = !campaign.id;
  campaignSendBtn.dataset.campaignId = campaign.id;
  const li = document.createElement("li");
  li.textContent = `${campaign.name}: ${campaign.status}, ${campaign.recipients?.length || 0} recipients, sent ${campaign.tracking?.sent || 0}, opened ${campaign.tracking?.opened || 0}, clicked ${campaign.tracking?.clicked || 0}, bounced ${campaign.tracking?.bounced || 0}`;
  campaignList.prepend(li);
}

function ownerLabel(lead) {
  if (!lead.ownerName) return "";
  return [lead.ownerTitle, lead.rabbitHoleStatus].filter(Boolean).join(" · ");
}

function swarmLabel(swarm) {
  const traces = Array.isArray(swarm.traces) ? swarm.traces : [];
  return `${swarm.deployedTotal || swarm.deployed || traces.length} total / ${swarm.requestedPerPass || swarm.requested || traces.length} per pass`;
}

function profileToolLabel(lead) {
  const attempts = (lead.socialEvidence || []).flatMap((entry) => Array.isArray(entry.toolAttempts) ? entry.toolAttempts : []);
  if (!attempts.length) return "";
  const used = attempts.filter((attempt) => attempt.status === "used").map((attempt) => attempt.tool.split("/").pop());
  const installed = attempts.filter((attempt) => /^installed|configured/.test(attempt.status)).map((attempt) => attempt.tool.split("/").pop());
  return [...used, ...installed].slice(0, 5).join(", ");
}

function fetchApi(path, options = {}) {
  const headers = new Headers(options.headers || {});
  if (accessToken) headers.set("x-hermes-token", accessToken);
  return fetch(apiUrl(path), { ...options, headers });
}

function apiUrl(path) {
  if (!accessToken) return path;
  const url = new URL(path, window.location.origin);
  url.searchParams.set("token", accessToken);
  return `${url.pathname}${url.search}${url.hash}`;
}

function getAccessToken() {
  const params = new URLSearchParams(window.location.search);
  const token = params.get("token");
  if (token) {
    try {
      window.sessionStorage.setItem("hermesToken", token);
    } catch {
      // Session storage can be unavailable in restrictive browser modes.
    }
    return token;
  }
  try {
    return window.sessionStorage.getItem("hermesToken") || "";
  } catch {
    return "";
  }
}

function cleanPayload(payload) {
  return Object.fromEntries(Object.entries(payload).filter(([, value]) => String(value || "").trim()));
}

function setError(message) {
  formError.textContent = message;
}

function syncSourceMode() {
  if (sourceInput.tagName === "SELECT") {
    replaceLegacySourceSelector();
  }
  sourceInput.value = "lead-scraper";
}

function replaceLegacySourceSelector() {
  const replacement = document.createElement("input");
  replacement.id = sourceInput.id;
  replacement.name = sourceInput.name;
  replacement.type = "hidden";
  replacement.value = "lead-scraper";

  const sourceLock = document.createElement("div");
  sourceLock.className = "source-lock";

  const title = document.createElement("strong");
  title.textContent = "Source";

  const label = document.createElement("span");
  label.textContent = LEAD_SCRAPER_SOURCE_LABEL;

  sourceLock.append(replacement, title, label);
  const wrapper = sourceInput.closest("label");
  if (wrapper) {
    wrapper.replaceWith(sourceLock);
  } else {
    sourceInput.replaceWith(sourceLock);
  }
  sourceInput = replacement;
}

function syncAllUsaMode() {
  if (allUsaInput) allUsaInput.value = countryInput?.value === "United States" ? "true" : "false";
}

function escapeHtml(value) {
  return String(value).replace(/[&<>"']/g, (char) => ({
    "&": "&amp;",
    "<": "&lt;",
    ">": "&gt;",
    '"': "&quot;",
    "'": "&#039;"
  }[char]));
}

function escapeAttr(value) {
  return escapeHtml(value).replace(/"/g, "&quot;");
}

async function boot() {
  syncSourceMode();
  syncAllUsaMode();
  renderWorkflow(null);
  await Promise.allSettled([health(), loadSettings(), loadPortfolioDefaults(), loadRepairPlaybook()]);
  await loadActiveRun();
}

boot().catch(() => {
  apiStatus.textContent = "Server offline";
});
