const state = {
  profile: null,
  package: null,
  jobMode: "url",
  resumeView: "clean",
  editorAvailable: false,
  evidenceMode: "strict",
  evidenceGuard: true,
};

const $ = (selector) => document.querySelector(selector);
const $$ = (selector) => [...document.querySelectorAll(selector)];

document.addEventListener("DOMContentLoaded", async () => {
  bindNavigation();
  bindUploads();
  bindProfileForm();
  bindJobForm();
  bindResultTabs();
  bindOhMyCvImport();
  bindRevisionChat();
  bindGlobalEvidenceGuard();
  await loadStatus();
});

async function api(path, options = {}) {
  const response = await fetch(path, options);
  if (!response.ok) {
    let message = "Something went wrong.";
    try {
      const body = await response.json();
      message = body.detail || message;
    } catch {
      message = response.statusText || message;
    }
    throw new Error(message);
  }
  return response.json();
}

async function apiTask(path, options = {}) {
  const started = await api(`${path}/start`, options);
  let consecutiveNetworkErrors = 0;

  while (true) {
    await new Promise((resolve) => window.setTimeout(resolve, 1200));
    try {
      const task = await api(`/api/tasks/${started.task_id}`);
      consecutiveNetworkErrors = 0;
      if (task.status === "succeeded") return task.result;
      if (task.status === "failed") {
        throw new Error(task.error || "The background model task failed.");
      }
    } catch (error) {
      if (!error.message.includes("NetworkError") && !error.message.includes("Failed to fetch")) {
        throw error;
      }
      consecutiveNetworkErrors += 1;
      if (consecutiveNetworkErrors >= 50) {
        throw new Error(
          `Lost contact with the server while task ${started.task_id} was running. ` +
            "Check the server stdout logs; the model task may still be active.",
        );
      }
    }
  }
}

async function loadStatus() {
  try {
    const status = await api("/api/status");
    const pill = $("#providerPill");
    const providerLabel = status.model
      ? `${status.provider} · ${status.model}`
      : status.provider;
    $("#providerText").textContent = status.configured
      ? providerLabel
      : "Provider setup needed";
    pill.classList.add(status.configured ? "ready" : "warning");
    $("#tailorButton").disabled = !status.configured;
    state.editorAvailable = status.editor_available;
    state.evidenceGuard = status.evidence_guard;
    renderGlobalEvidenceGuard();
    document.dispatchEvent(new Event("evidenceguardchange"));
    $("#cvEditorLink").classList.toggle("hidden", !status.editor_available);
    $("#openOhMyCvButton").classList.toggle("hidden", !status.editor_available);

    if (status.profile_exists) {
      const profile = await api("/api/profile");
      showProfile(profile);
    }
  } catch (error) {
    showToast(error.message, true);
  }
}

function bindGlobalEvidenceGuard() {
  $("#globalEvidenceGuard").addEventListener("click", async () => {
    const previous = state.evidenceGuard;
    state.evidenceGuard = !previous;
    renderGlobalEvidenceGuard();
    document.dispatchEvent(new Event("evidenceguardchange"));
    try {
      await api("/api/settings", {
        method: "PUT",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({ evidence_guard: state.evidenceGuard }),
      });
      showToast(
        state.evidenceGuard
          ? "Evidence guard enabled across the app."
          : "Evidence guard disabled. Profile-based rules now apply across the app.",
      );
    } catch (error) {
      state.evidenceGuard = previous;
      renderGlobalEvidenceGuard();
      document.dispatchEvent(new Event("evidenceguardchange"));
      showToast(error.message, true);
    }
  });
}

function renderGlobalEvidenceGuard() {
  const button = $("#globalEvidenceGuard");
  button.classList.toggle("off", !state.evidenceGuard);
  button.setAttribute("aria-pressed", String(state.evidenceGuard));
  $("#globalEvidenceGuardText").textContent = state.evidenceGuard
    ? "Evidence guard on"
    : "Evidence guard off";
  $("#revisionGuardStatus").classList.toggle("off", !state.evidenceGuard);
  $("#revisionGuardStatusText").textContent = state.evidenceGuard
    ? "Evidence guard on"
    : "Profile rules active";
  $("#revisionGuardDescription").textContent = state.evidenceGuard
    ? "Ask for changes to tone, detail, ordering, emphasis, or length. Every revision is checked against your evidence profile."
    : "Ask for changes to tone, detail, ordering, emphasis, or length. Revisions use your complete profile without claim-level evidence IDs.";
}

function bindNavigation() {
  $$(".step").forEach((button) => {
    button.addEventListener("click", () => {
      const section = document.getElementById(button.dataset.section);
      if (section && !section.classList.contains("hidden")) {
        section.scrollIntoView({ behavior: "smooth", block: "start" });
        activateStep(button.dataset.section);
      }
    });
  });

  $("#replaceProfile").addEventListener("click", () => {
    $("#profileReady").classList.add("hidden");
    $("#profileEmpty").classList.remove("hidden");
    $("#replaceProfile").classList.add("hidden");
  });
}

function activateStep(sectionId) {
  $$(".step").forEach((step) => {
    step.classList.toggle("active", step.dataset.section === sectionId);
  });
}

function bindUploads() {
  const input = $("#resumeFile");
  const zone = $("#dropzone");

  input.addEventListener("change", () => updateResumeFilename(input.files[0]));
  ["dragenter", "dragover"].forEach((eventName) => {
    zone.addEventListener(eventName, (event) => {
      event.preventDefault();
      zone.classList.add("dragging");
    });
  });
  ["dragleave", "drop"].forEach((eventName) => {
    zone.addEventListener(eventName, (event) => {
      event.preventDefault();
      zone.classList.remove("dragging");
    });
  });
  zone.addEventListener("drop", (event) => {
    const files = event.dataTransfer.files;
    if (files.length) {
      input.files = files;
      updateResumeFilename(files[0]);
    }
  });

  $("#notesFile").addEventListener("change", (event) => {
    $("#notesFilename").textContent = event.target.files[0]?.name || "";
  });
}

function updateResumeFilename(file) {
  if (!file) return;
  $("#dropTitle").textContent = file.name;
  $("#dropHint").textContent = `${formatBytes(file.size)} · Ready to analyze`;
}

function formatBytes(bytes) {
  if (bytes < 1024) return `${bytes} bytes`;
  if (bytes < 1024 * 1024) return `${Math.round(bytes / 1024)} KB`;
  return `${(bytes / 1024 / 1024).toFixed(1)} MB`;
}

function bindProfileForm() {
  $("#profileForm").addEventListener("submit", async (event) => {
    event.preventDefault();
    const resume = $("#resumeFile").files[0];
    if (!resume) {
      showToast("Choose your master resume first.", true);
      return;
    }

    const data = new FormData();
    data.append("resume", resume);
    data.append("about", $("#about").value);
    const notes = $("#notesFile").files[0];
    if (notes) data.append("notes", notes);

    const stopProgress = showProcessing("profile");
    try {
      const profile = await apiTask("/api/profile", { method: "POST", body: data });
      stopProgress();
      showProfile(profile);
      showToast("Career profile built and evidence mapped.");
      $("#tailorSection").scrollIntoView({ behavior: "smooth", block: "start" });
      activateStep("tailorSection");
    } catch (error) {
      stopProgress();
      showToast(error.message, true);
    }
  });
}

function showProfile(profile) {
  state.profile = profile;
  $("#profileEmpty").classList.add("hidden");
  $("#profileReady").classList.remove("hidden");
  $("#replaceProfile").classList.remove("hidden");

  $("#profileName").textContent = profile.contact.full_name;
  $("#profileIdentity").textContent = profile.professional_identity;
  $("#identityMonogram").textContent = initials(profile.contact.full_name);
  $("#factCount").textContent = profile.facts.length;
  $("#skillCount").textContent = profile.skills.length;
  $("#questionCount").textContent = profile.unanswered_questions.length;
  $("#ledgerSummary").textContent = `${profile.facts.length} source-backed claims`;

  renderChips($("#strengthList"), profile.differentiators);
  renderChips($("#skillList"), profile.skills);
  renderEvidenceLedger(profile.facts);
}

function initials(name) {
  return name
    .split(/\s+/)
    .slice(0, 2)
    .map((part) => part[0])
    .join("")
    .toUpperCase();
}

function renderChips(container, items) {
  container.replaceChildren();
  if (!items.length) {
    container.append(textNode("No items documented yet.", "chip"));
    return;
  }
  items.forEach((item) => container.append(textNode(item, "chip")));
}

function renderEvidenceLedger(facts) {
  const ledger = $("#evidenceLedger");
  ledger.replaceChildren();
  facts.forEach((fact) => {
    const row = document.createElement("div");
    row.className = "ledger-row";
    row.append(textNode(fact.id, "fact-id"));

    const body = document.createElement("div");
    body.append(textNode(fact.statement, "ledger-statement", "p"));
    const source = document.createElement("small");
    source.textContent = `${fact.category} · ${fact.source_name} · “${fact.source_excerpt}”`;
    body.append(source);
    row.append(body);
    ledger.append(row);
  });
}

function bindJobForm() {
  $$(".mode").forEach((button) => {
    button.addEventListener("click", () => {
      state.jobMode = button.dataset.mode;
      $$(".mode").forEach((item) => item.classList.toggle("active", item === button));
      $("#urlPane").classList.toggle("hidden", state.jobMode !== "url");
      $("#textPane").classList.toggle("hidden", state.jobMode !== "text");
    });
  });

  $("#jobText").addEventListener("input", (event) => {
    $("#characterCount").textContent = `${event.target.value.length.toLocaleString()} characters`;
  });

  const strength = $("#tailoringStrength");
  const evidenceModeButton = $("#evidenceModeButton");
  const updateStrength = () => {
    const value = Number(strength.value);
    $("#strengthValue").textContent = value;
    $("#strengthDescription").textContent =
      value <= 20
        ? "Minimal edits that stay very close to your current wording and structure."
        : value <= 70
          ? state.evidenceMode === "strict"
            ? "Balanced rewriting with claim-level evidence from your career profile."
            : "Balanced rewriting from your complete profile without claim-level IDs."
          : "Fuller supported detail and stronger job-aligned wording without invention.";
  };
  const updateEvidenceMode = () => {
    if (!state.evidenceGuard) state.evidenceMode = "profile";
    const flexible = state.evidenceMode === "profile";
    evidenceModeButton.disabled = !state.evidenceGuard;
    evidenceModeButton.classList.toggle("active", flexible);
    evidenceModeButton.setAttribute("aria-pressed", String(flexible));
    $("#evidenceModeTitle").textContent = flexible
      ? "Flexible profile-based rewrite"
      : "Strict source evidence";
    $("#evidenceModeDescription").textContent = flexible
      ? state.evidenceGuard
        ? "Claims use your full profile; evidence IDs are optional."
        : "App-wide guard is off; profile-based rules are enforced."
      : "Every claim must cite a profile evidence ID.";
    updateStrength();
  };
  evidenceModeButton.addEventListener("click", () => {
    state.evidenceMode = state.evidenceMode === "strict" ? "profile" : "strict";
    updateEvidenceMode();
  });
  document.addEventListener("evidenceguardchange", updateEvidenceMode);
  strength.addEventListener("input", updateStrength);
  updateEvidenceMode();

  $("#tailorForm").addEventListener("submit", async (event) => {
    event.preventDefault();
    if (!state.profile) {
      showToast("Build your career profile first.", true);
      $("#profileSection").scrollIntoView({ behavior: "smooth" });
      return;
    }

    const body =
      state.jobMode === "url"
        ? {
            job_url: $("#jobUrl").value.trim(),
            job_text: null,
            tailoring_strength: Number(strength.value),
            evidence_mode: state.evidenceMode,
          }
        : {
            job_url: null,
            job_text: $("#jobText").value.trim(),
            tailoring_strength: Number(strength.value),
            evidence_mode: state.evidenceMode,
          };

    if (!(body.job_url || body.job_text)) {
      showToast("Add a job URL or paste the full description.", true);
      return;
    }

    const stopProgress = showProcessing("tailor");
    try {
      const packageData = await apiTask("/api/tailor", {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify(body),
      });
      stopProgress();
      showResults(packageData);
      prepareOhMyCvImport(packageData);
      showToast("Tailored resume created and audited.");
    } catch (error) {
      stopProgress();
      showToast(error.message, true);
    }
  });
}

async function prepareOhMyCvImport(packageData) {
  const button = $("#openOhMyCvButton");
  button.disabled = true;
  if (!state.editorAvailable) return;

  try {
    const response = await fetch("/api/download/resume-ohmycv.md");
    if (!response.ok) throw new Error("Could not prepare the Oh My CV export.");
    const markdown = await response.text();
    const role = packageData.job.role_title || "Tailored resume";
    const company = packageData.job.company ? ` · ${packageData.job.company}` : "";
    localStorage.setItem(
      "signal:pending-ohmycv",
      JSON.stringify({
        name: `${role}${company}`,
        markdown,
        createdAt: new Date().toISOString(),
      }),
    );
    button.disabled = false;
  } catch (error) {
    showToast(error.message, true);
  }
}

function bindOhMyCvImport() {
  $("#openOhMyCvButton").addEventListener("click", () => {
    window.open("/cv/signal-import", "_blank", "noopener");
  });
}

function bindRevisionChat() {
  const form = $("#revisionForm");
  const input = $("#revisionMessage");
  const button = $("#revisionSend");

  $$("[data-revision-prompt]").forEach((suggestion) => {
    suggestion.addEventListener("click", () => {
      input.value = suggestion.dataset.revisionPrompt;
      input.focus();
    });
  });

  input.addEventListener("keydown", (event) => {
    if (event.key === "Enter" && (event.ctrlKey || event.metaKey)) {
      event.preventDefault();
      form.requestSubmit();
    }
  });

  form.addEventListener("submit", async (event) => {
    event.preventDefault();
    const message = input.value.trim();
    if (!state.package || message.length < 3) return;

    appendChatMessage("user", "You", message);
    input.value = "";
    input.disabled = true;
    button.disabled = true;
    const thinking = appendChatMessage(
      "assistant thinking",
      "Signal",
      "Rewriting and checking every claim…",
    );

    try {
      const result = await apiTask("/api/revise", {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({ message }),
      });
      thinking.remove();
      showResults(result.package, false);
      prepareOhMyCvImport(result.package);
      appendChatMessage("assistant", "Signal", result.reply);
      showToast("Resume revised, audited, and downloads refreshed.");
    } catch (error) {
      thinking.remove();
      appendChatMessage("assistant error", "Signal", error.message);
    } finally {
      input.disabled = false;
      button.disabled = false;
      input.focus();
    }
  });
}

function appendChatMessage(className, author, message) {
  const node = document.createElement("div");
  node.className = `chat-message ${className}`;
  node.append(textNode(author));
  node.append(textNode(message, "", "p"));
  $("#revisionMessages").append(node);
  node.scrollIntoView({ behavior: "smooth", block: "nearest" });
  return node;
}

function bindResultTabs() {
  $$(".preview-tab").forEach((button) => {
    button.addEventListener("click", () => {
      state.resumeView = button.dataset.view;
      $$(".preview-tab").forEach((item) =>
        item.classList.toggle("active", item === button),
      );
      if (state.package) renderResume(state.package.resume);
    });
  });
}

function showResults(packageData, shouldScroll = true) {
  state.package = packageData;
  $("#resultsSection").classList.remove("hidden");
  $("#targetRole").textContent = packageData.job.role_title || "Target role";
  $("#targetCompany").textContent = packageData.job.company || "Company";
  $("#strongMatchCount").textContent = packageData.match.strong_matches.length;
  $("#gapCount").textContent = packageData.match.genuine_gaps.length;
  $("#auditSealText").textContent =
    state.evidenceMode === "strict"
      ? "Factuality audit passed"
      : "Profile-based review passed";

  const worthDiscussing = [
    ...new Set([
      ...packageData.match.partial_matches,
      ...packageData.match.genuine_gaps,
    ]),
  ];
  renderList($("#strongMatches"), packageData.match.strong_matches, "No direct matches mapped.");
  renderList($("#genuineGaps"), worthDiscussing, "No material gaps identified.");
  renderList($("#changesList"), packageData.changes_made, "No changes recorded.");
  renderList(
    $("#questionsList"),
    packageData.questions_for_candidate,
    "No follow-up questions.",
  );
  renderResume(packageData.resume);

  if (shouldScroll) {
    $("#resultsSection").scrollIntoView({ behavior: "smooth", block: "start" });
  }
  activateStep("resultsSection");
}

function renderResume(resume) {
  const paper = $("#resumePaper");
  paper.replaceChildren();

  paper.append(textNode(resume.contact.full_name, "", "h1"));
  paper.append(textNode(resume.headline, "resume-headline"));

  const contactValues = [
    resume.contact.location,
    resume.contact.email,
    resume.contact.phone,
    resume.contact.linkedin,
    resume.contact.website,
  ].filter(Boolean);
  if (contactValues.length) {
    paper.append(textNode(contactValues.join(" · "), "resume-contact"));
  }

  addResumeSection(paper, "Professional Summary", resume.summary);
  resume.sections.forEach((section) => addResumeSection(paper, section.title, section.items));
}

function addResumeSection(paper, title, items) {
  paper.append(textNode(title, "", "h2"));
  const list = document.createElement("ul");
  items.forEach((item) => {
    const li = document.createElement("li");
    li.textContent = item.text;
    if (state.resumeView === "evidence") {
      const tags = document.createElement("div");
      tags.className = "evidence-tags";
      item.evidence_ids.forEach((id) => tags.append(textNode(id)));
      li.append(tags);
    }
    list.append(li);
  });
  paper.append(list);
}

function renderList(container, items, fallback) {
  container.replaceChildren();
  const values = items.length ? items : [fallback];
  values.forEach((item) => container.append(textNode(item, "", "li")));
}

function textNode(text, className = "", tag = "span") {
  const node = document.createElement(tag);
  if (className) node.className = className;
  node.textContent = text;
  return node;
}

function showProcessing(type) {
  const modal = $("#processing");
  const profileMode = type === "profile";
  const steps = profileMode
    ? ["Reading source", "Mapping facts", "Building profile"]
    : ["Reading role", "Drafting match", "Auditing claims"];
  const titles = profileMode
    ? ["Reading your experience", "Mapping the evidence", "Building your profile"]
    : ["Reading the opportunity", "Shaping your narrative", "Auditing every claim"];
  const copies = profileMode
    ? [
        "Extracting the full story from your source material…",
        "Linking each professional claim to direct evidence…",
        "Organizing strengths, skills, and open questions…",
      ]
    : [
        "Identifying the role’s real priorities and language…",
        "Selecting your strongest supported evidence…",
        "Removing anything that cannot be proven…",
      ];

  const stepContainer = $("#processingSteps");
  stepContainer.replaceChildren(...steps.map((step) => textNode(step)));
  modal.classList.remove("hidden");

  let index = 0;
  const update = () => {
    $("#processingTitle").textContent = titles[index];
    $("#processingCopy").textContent = copies[index];
    $("#progressBar").style.width = `${22 + index * 32}%`;
    [...stepContainer.children].forEach((item, itemIndex) =>
      item.classList.toggle("active", itemIndex <= index),
    );
  };
  update();
  const timer = window.setInterval(() => {
    index = Math.min(index + 1, steps.length - 1);
    update();
  }, 2800);

  return () => {
    window.clearInterval(timer);
    $("#progressBar").style.width = "100%";
    window.setTimeout(() => modal.classList.add("hidden"), 220);
  };
}

let toastTimer;
function showToast(message, error = false) {
  const toast = $("#toast");
  toast.textContent = message;
  toast.classList.toggle("error", error);
  toast.classList.add("show");
  window.clearTimeout(toastTimer);
  toastTimer = window.setTimeout(() => toast.classList.remove("show"), 4400);
}
