(() => {
  "use strict";

  const qs = (sel) => document.querySelector(sel);

  const state = {
    index: null,
    rows: [],
    table: null,
    filters: {
      mode: "",
      episode: "",
      difficulty: "",
      section: "",
      search: "",
    },
    sort: {
      key: "source",
      dir: "asc",
    },
    page: 1,
    pageSize: 100,
  };

  const RARE_MODIFIER_VERSIONS = new Set(["v2", "bb"]);

  function esc(value) {
    return String(value ?? "")
      .replaceAll("&", "&amp;")
      .replaceAll("<", "&lt;")
      .replaceAll(">", "&gt;")
      .replaceAll('"', "&quot;")
      .replaceAll("'", "&#039;");
  }

  function labelValue(value) {
    return String(value || "")
      .replace(/^Episode(\d+)$/, "Episode $1")
      .replace(/^VeryHard$/, "Very Hard")
      .replaceAll("_", " ");
  }

  function setStatus(message, kind = "") {
    const box = qs("#drops-placeholder");
    if (!box) return;
    box.innerHTML = `<div class="drops-status ${kind ? `drops-status--${kind}` : ""}">${esc(message)}</div>`;
  }

  function currentVersion() {
    return qs("#drops-version")?.value || "v1";
  }

  function rareModifierEnabled() {
    const selectedMode = qs("#drops-mode")?.value || "peeps";
    return selectedMode === "peeps" && RARE_MODIFIER_VERSIONS.has(currentVersion());
  }

  function currentRareModifier() {
    const select = qs("#drops-rare-modifier");
    const pct = rareModifierEnabled() ? Number(select?.value || 0) : 0;
    const label = select?.selectedOptions?.[0]?.textContent || "No modifier";
    return { pct, label, multiplier: 1 + (pct / 100) };
  }

  function modifierMultiplierLabel(modifier) {
    return `x${modifier.multiplier.toFixed(3).replace(/0+$/, "").replace(/\.$/, "")}`;
  }

  function updateRareModifierControls() {
    const wrap = qs("#drops-rare-modifier-wrap");
    const v2Note = qs("#drops-v2-note");
    const select = qs("#drops-rare-modifier");
    const enabled = rareModifierEnabled();

    if (wrap) wrap.hidden = !enabled;
    if (v2Note) v2Note.hidden = currentVersion() !== "v2";

    if (!enabled && select) {
      select.value = "0";
    }
  }

  function formatOddsDenominator(value) {
    if (!Number.isFinite(value) || value <= 0) return "—";

    if (value >= 1000) {
      return Math.round(value).toLocaleString();
    }

    if (value >= 100) {
      return value.toFixed(1).replace(/\.0$/, "");
    }

    return value.toFixed(2).replace(/0+$/, "").replace(/\.$/, "");
  }

  function adjustedRate(rate) {
    const text = String(rate || "");
    const match = text.match(/^(\d+)\/(\d+)$/);
    if (!match) return text || "—";

    const num = Number(match[1]);
    const den = Number(match[2]);
    const modifier = currentRareModifier();

    if (!rareModifierEnabled() || modifier.pct <= 0) {
      return text;
    }

    const baseProbability = num / den;
    const adjustedProbability = Math.min(1, baseProbability * modifier.multiplier);

    if (adjustedProbability >= 1) {
      return "1/1";
    }

    return `1/${formatOddsDenominator(1 / adjustedProbability)}`;
  }

  function rateCellHtml(rate) {
    const base = String(rate || "—");
    const adjusted = adjustedRate(base);
    const modifier = currentRareModifier();

    if (!rareModifierEnabled() || modifier.pct <= 0 || adjusted === base) {
      return esc(base);
    }

    return esc(adjusted);
  }

  async function fetchJson(path) {
    const res = await fetch(path, { cache: "no-store" });
    if (!res.ok) throw new Error(`${path}: HTTP ${res.status}`);
    return res.json();
  }

  function uniqueSorted(rows, key) {
    return [...new Set(rows.map((row) => row[key]).filter(Boolean))]
      .sort((a, b) => String(a).localeCompare(String(b)));
  }

  function fillSelect(select, values, allLabel) {
    if (!select) return;
    const previous = select.value;
    select.innerHTML = `<option value="">${esc(allLabel)}</option>`;
    for (const value of values) {
      const opt = document.createElement("option");
      opt.value = value;
      opt.textContent = labelValue(value);
      select.appendChild(opt);
    }
    if ([...select.options].some((opt) => opt.value === previous)) {
      select.value = previous;
    }
  }

  function populateVersions(index) {
    const select = qs("#drops-version");
    if (!select) return;

    const previous = select.value || "v1";
    select.innerHTML = "";

    for (const table of index.tables || []) {
      const opt = document.createElement("option");
      opt.value = table.version;
      opt.textContent = table.label;
      select.appendChild(opt);
    }

    if ([...select.options].some((opt) => opt.value === previous)) {
      select.value = previous;
    }
  }

  function orderedDifficultyValues(rows) {
    const order = ["Normal", "Hard", "VeryHard", "Ultimate"];
    const present = new Set(rows.map((row) => row.difficulty).filter(Boolean));
    const ordered = order.filter((value) => present.has(value));
    const extras = [...present]
      .filter((value) => !order.includes(value))
      .sort((a, b) => String(a).localeCompare(String(b)));
    return [...ordered, ...extras];
  }

  function populateFilters(rows) {
    fillSelect(qs("#drops-rare-mode"), uniqueSorted(rows, "mode"), "All modes");
    fillSelect(qs("#drops-episode"), uniqueSorted(rows, "episode"), "All episodes");
    fillSelect(qs("#drops-difficulty"), orderedDifficultyValues(rows), "All difficulties");
    fillSelect(qs("#drops-section"), uniqueSorted(rows, "section_id"), "All Section IDs");
  }

  const SORT_COLUMNS = [
    ["mode", "Mode"],
    ["episode", "Episode"],
    ["difficulty", "Difficulty"],
    ["section_id", "SECID"],
    ["source", "Source"],
    ["item", "Item"],
    ["item_code", "Code"],
    ["rate", "Rate"],
  ];

  const DIFFICULTY_SORT_ORDER = {
    Normal: 0,
    Hard: 1,
    VeryHard: 2,
    Ultimate: 3,
  };

  function rateSortValue(rate) {
    const text = String(rate || "").replaceAll(",", "");
    const match = text.match(/^(\d+(?:\.\d+)?)\/(\d+(?:\.\d+)?)$/);
    if (!match) return Number.NEGATIVE_INFINITY;

    const num = Number(match[1]);
    const den = Number(match[2]);

    if (!Number.isFinite(num) || !Number.isFinite(den) || den <= 0) {
      return Number.NEGATIVE_INFINITY;
    }

    // Sort by actual probability, same as percentage conversion.
    // 5/8 => 0.625, 1/8192 => 0.000122...
    return num / den;
  }

  function sortValue(row, key) {
    if (key === "difficulty") {
      return DIFFICULTY_SORT_ORDER[row.difficulty] ?? 999;
    }

    if (key === "rate") {
      return rateSortValue(adjustedRate(row.rate));
    }

    if (key === "item") {
      return row.item || row.item_code || "";
    }

    return row[key] || "";
  }

  function sortedRows(rows) {
    const { key, dir } = state.sort;
    const factor = dir === "desc" ? -1 : 1;

    return [...rows].sort((a, b) => {
      const av = sortValue(a, key);
      const bv = sortValue(b, key);

      if (typeof av === "number" && typeof bv === "number") {
        return (av - bv) * factor;
      }

      return String(av).localeCompare(String(bv), undefined, {
        numeric: true,
        sensitivity: "base",
      }) * factor;
    });
  }

  function sortHeader(key, label) {
    const active = state.sort.key === key;
    const arrow = active ? (state.sort.dir === "asc" ? "▲" : "▼") : "";
    const ariaSort = active ? (state.sort.dir === "asc" ? "ascending" : "descending") : "none";

    return `<th aria-sort="${ariaSort}">
      <button class="drops-sort-button ${active ? "is-active" : ""}" type="button" data-drops-sort="${esc(key)}">
        <span>${esc(label)}</span>
        <span class="drops-sort-arrow" aria-hidden="true">${arrow}</span>
      </button>
    </th>`;
  }

  function visibleRows() {
    const search = state.filters.search.trim().toLowerCase();

    return state.rows.filter((row) => {
      if (state.filters.mode && row.mode !== state.filters.mode) return false;
      if (state.filters.episode && row.episode !== state.filters.episode) return false;
      if (state.filters.difficulty && row.difficulty !== state.filters.difficulty) return false;
      if (state.filters.section && row.section_id !== state.filters.section) return false;

      if (search) {
        const haystack = [
          row.mode,
          row.episode,
          row.difficulty,
          row.section_id,
          row.source,
          row.item,
          row.item_code,
          row.rate,
        ].join(" ").toLowerCase();

        if (!haystack.includes(search)) return false;
      }

      return true;
    });
  }

  function renderTable() {
    const box = qs("#drops-placeholder");
    if (!box) return;

    const rows = sortedRows(visibleRows());
    const tableLabel = state.table?.label || "BB";
    const groupLabel = state.table?.groupLabel || "Peeps";
    const totalPages = Math.max(1, Math.ceil(rows.length / state.pageSize));
    state.page = Math.min(Math.max(1, state.page), totalPages);

    const start = (state.page - 1) * state.pageSize;
    const shown = rows.slice(start, start + state.pageSize);
    const end = start + shown.length;

    const body = shown.map((row) => {
      const item = row.item || row.item_code || "—";
      const itemCode = row.item_code || "—";

      return `
        <tr>
          <td data-label="Mode">${esc(labelValue(row.mode))}</td>
          <td data-label="Episode">${esc(labelValue(row.episode))}</td>
          <td data-label="Difficulty">${esc(labelValue(row.difficulty))}</td>
          <td data-label="Section ID">${esc(row.section_id || "—")}</td>
          <td data-label="Source">${esc(labelValue(row.source || "—"))}</td>
          <td data-label="Item">${esc(item)}</td>
          <td data-label="Item Code">${esc(itemCode)}</td>
          <td data-label="Rate">${rateCellHtml(row.rate || "—")}</td>
        </tr>
      `;
    }).join("");

    const rangeText = rows.length
      ? ` Showing ${Number(start + 1).toLocaleString()}-${Number(end).toLocaleString()}.`
      : "";
    const modifier = currentRareModifier();
    const modifierNote = rareModifierEnabled() && modifier.pct > 0
      ? `<span>Rate modifier: ${esc(modifier.label)} / ${esc(modifierMultiplierLabel(modifier))}</span>`
      : "";

    box.innerHTML = `
      <div class="drops-summary">
        <div>
          <strong>${esc(groupLabel)} ${esc(tableLabel)} drop table</strong>
          <span>${rows.length.toLocaleString()} matching rows.${rangeText}</span>
          ${modifierNote}
        </div>
        <span>${state.rows.length.toLocaleString()} total rows</span>
      </div>
      <div class="drops-table-wrap">
        <table class="drops-table">
          <thead>
            <tr>
              ${SORT_COLUMNS.map(([key, label]) => sortHeader(key, label)).join("")}
            </tr>
          </thead>
          <tbody>${body || `<tr><td colspan="8">No drops match these filters.</td></tr>`}</tbody>
        </table>
      </div>
      <div class="leaderboard-pager drops-pager">
        <button type="button" data-drops-page="prev" ${state.page <= 1 ? "disabled" : ""}>Previous</button>
        <span>Page ${state.page} of ${totalPages}</span>
        <button type="button" data-drops-page="next" ${state.page >= totalPages ? "disabled" : ""}>Next</button>
      </div>
    `;
  }

  async function loadDropGroup() {
    const selectedMode = qs("#drops-mode")?.value || "peeps";
    const groupPath = selectedMode === "hardcore" ? "hardcore" : "peeps";
    const groupLabel = selectedMode === "hardcore" ? "Hardcore" : "Peeps";

    setStatus(`Loading ${groupLabel} drop tables...`);

    state.index = await fetchJson(`generated/drops/${groupPath}/index.json`);

    if (selectedMode === "peeps") {
      populateVersions(state.index);
    }

    const version = selectedMode === "hardcore" ? "bb" : currentVersion();
    const table = (state.index.tables || []).find((entry) => entry.version === version);

    if (!table) {
      setStatus(`No ${groupLabel} drop table is configured.`, "error");
      return;
    }

    state.table = { ...table, groupLabel };
    state.rows = await fetchJson(`generated/drops/${groupPath}/${table.path}`);

    state.filters.mode = "";
    state.filters.episode = "";
    state.filters.difficulty = "";
    state.filters.section = "";
    state.filters.search = "";
    state.page = 1;

    if (qs("#drops-search")) qs("#drops-search").value = "";

    populateFilters(state.rows);
    updateRareModifierControls();

    if (qs("#drops-rare-mode")) qs("#drops-rare-mode").value = "";
    if (qs("#drops-episode")) qs("#drops-episode").value = "";
    if (qs("#drops-difficulty")) qs("#drops-difficulty").value = "";
    if (qs("#drops-section")) qs("#drops-section").value = "";

    renderTable();
  }

  async function updateMode() {
    const mode = qs("#drops-mode")?.value || "peeps";
    const versionSelect = qs("#drops-version");
    const versionLabel = document.querySelector('label[for="drops-version"]');
    const v2Note = qs("#drops-v2-note");
    const hardcoreNote = qs("#drops-hardcore-note");

    if (versionSelect) versionSelect.hidden = mode === "hardcore";
    if (versionLabel) versionLabel.hidden = mode === "hardcore";
    if (v2Note) v2Note.hidden = true;
    if (hardcoreNote) hardcoreNote.hidden = mode !== "hardcore";

    try {
      await loadDropGroup();
    } catch (err) {
      setStatus(err?.message || "Unable to load drop table.", "error");
    }
  }

  document.addEventListener("DOMContentLoaded", () => {
    qs("#drops-mode")?.addEventListener("change", updateMode);
    qs("#drops-version")?.addEventListener("change", loadDropGroup);
    qs("#drops-rare-modifier")?.addEventListener("change", () => {
      state.page = 1;
      state.page = 1;
      renderTable();
    });

    qs("#drops-rare-mode")?.addEventListener("change", (event) => {
      state.filters.mode = event.target.value;
      state.page = 1;
      renderTable();
    });

    qs("#drops-episode")?.addEventListener("change", (event) => {
      state.filters.episode = event.target.value;
      state.page = 1;
      renderTable();
    });

    qs("#drops-difficulty")?.addEventListener("change", (event) => {
      state.filters.difficulty = event.target.value;
      state.page = 1;
      renderTable();
    });

    qs("#drops-section")?.addEventListener("change", (event) => {
      state.filters.section = event.target.value;
      state.page = 1;
      renderTable();
    });

    qs("#drops-search")?.addEventListener("input", (event) => {
      state.filters.search = event.target.value;
      state.page = 1;
      renderTable();
    });

    qs("#drops-placeholder")?.addEventListener("click", (event) => {
      const pageButton = event.target.closest("[data-drops-page]");
      if (pageButton) {
        state.page += pageButton.dataset.dropsPage === "next" ? 1 : -1;
        renderTable();
        return;
      }

      const button = event.target.closest("[data-drops-sort]");
      if (!button) return;

      const key = button.dataset.dropsSort;
      if (state.sort.key === key) {
        state.sort.dir = state.sort.dir === "asc" ? "desc" : "asc";
      } else {
        state.sort.key = key;
        state.sort.dir = "asc";
      }

      renderTable();
    });

    updateMode();
  });
})();
