// Updated: CT display tweaks, don't clear State Receipt Total, avoid overwriting DB totals on initial render,
// include CT fee labels in computed-sum set, ensure Total Title & Registration Fee DOM is updated reliably,
// and include computed totals in save payload so backend persists them.

(function () {
  /* ----------------- Small helpers ----------------- */
  function qs(name) {
    const u = new URL(window.location.href);
    return u.searchParams.get(name) || "";
  }
  function setPdfSrc(date, filename) {
    const pdfUrl = `/file/pdf?date=${encodeURIComponent(date)}&filename=${encodeURIComponent(filename)}`;
    const f = document.getElementById("pdfFrame");
    if (f) f.src = pdfUrl;
  }
  function escapeHtml(s) {
    return String(s == null ? "" : s)
      .replace(/&/g, "&amp;").replace(/</g, "&lt;")
      .replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#39;");
  }

  /* ----------------- Display label sets ----------------- */
  const DISPLAY_DEFAULT = [
    "Delivery Date",
    "Titling State",
    "Transaction ID",
    "Process Date",
    "Taxable Selling Price / Purchase Price",
    "Sales Tax Credit Reciprocity",
    "Sales Tax",
    "Sales Tax Rate",
    "Total State Taxes",
    "Registration Fee",
    "Title Fee",
    "Plate Fee",
    "Lien Fee",
    "Transfer Fee",
    "Motor Vehicle Fees",
    "Local Use / County Fee",
    "Increase Fee",
    "Exchange Fee",
    "Specialty Plate Fee",
    "Additional Charge",
    "Total Title & Registration Fee",
    "Total Penalties",
    "Total Other Fees",
    "State Receipt Total",
    "Total Sales Taxes + Reg Fee + Penalties"
  ];

  // CT-specific display labels (removed duplicate "State")
  const DISPLAY_CT = [
    "Delivery Date",
    "Titling State",
    "Transaction ID",
    "Process Date",
    "Taxable Selling Price/ Purchase Price",
    "Trade in Value1",
    "DMV Sales Tax",
    "Sales Tax Rate",
    "Total State Taxes",
    "Registration Fee",
    "Title Fee",
    "Plate Change Fee",
    "Safety Plate Fee",
    "Standard Issue Base Plate Fee",
    "Lienholder Fee",
    "Greenhouse Gas Reduction Fee",
    "Emissions 4-Year Exempt Fee",
    "Admin Fee",
    "Clean Air Act Fee",
    "Passport to Parks Fee",
    "Vehicle Transfer Fee",
    "Motorcycle Education",
    "Record Transfer Fee",
    "Additional Charge",
    "Renewal Late Fee",
    "Total Penalties",
    "Total Other Fees",
    "State Receipt Total",
    "Total Title & Registration Fee",
    "Total Sales Taxes + Reg Fee + Penalties"
  ];

  // GA-specific display labels  
  const DISPLAY_GA = [
    "Delivery Date",
    "Titling State", 
    "Transaction ID",
    "Process Date",
    "Taxable Selling Price/ Purchase",
    "Trade in Value",
    "Sales Tax Credit Reciprocity",
    "Sales Tax Rate",
    "Title Ad Valorem Tax",
    "GA Sales Tax",
    "Annual Ad Valorem",
    "Title Ad Valorem Dealer Penalty",
    "Title Ad Valorem Casual Interest",
    "Total State Taxes",
    "Tag Fee",
    "Registration Fee",
    "Title Fee",
    "Plate Fee",
    "Alternative Fuel Vehicle Fee",
    "Mail Fee",
    "Highway Impact Fee",
    "Title Late Penalty",
    "Title Ad Valorem Casual Penalty",
    "Additional Charge",
    "Total Title & Registration Fee",
    "Total Penalties",
    "Total Other Fees",
    "State Receipt Total",
    "Total Sales Taxes + Reg Fee + Penalties"
  ];

  /* ----------------- Normalization + alias maps ----------------- */
  const norm = (s) =>
    String(s || "")
      .toLowerCase()
      .replace(/[\s/_\-\+&]+/g, " ")
      .replace(/\bfees\b/g, "fee")
      .replace(/\band\b/g, "")
      .trim();

  const ALIAS_TO_CANON = new Map([
    ["delivery date", "Delivery Date"],
    ["titling state", "Titling State"],
    ["state", "Titling State"],
    ["transaction id", "Transaction ID"],
    ["transaction_id", "Transaction ID"],
    ["txn id", "Transaction ID"],
    ["txn", "Transaction ID"],
    ["process date", "Process Date"],
    ["proc date", "Process Date"],
    ["process_date", "Process Date"],
    ["taxable selling price", "Taxable Selling Price / Purchase Price"],
    ["purchase price", "Taxable Selling Price / Purchase Price"],
    ["taxable selling price purchase price", "Taxable Selling Price / Purchase Price"],
    ["sales tax credit reciprocity", "Sales Tax Credit Reciprocity"],
    ["sales tax credit", "Sales Tax Credit Reciprocity"],
    ["sales tax", "Sales Tax"],
    ["salestax", "Sales Tax"],
    ["sales tax rate", "Sales Tax Rate"],
    ["tax rate", "Sales Tax Rate"],
    ["total state taxes", "Total State Taxes"],
    ["state taxes", "Total State Taxes"],
    ["registration fee", "Registration Fee"],
    ["reg fee", "Registration Fee"],
    ["title fee", "Title Fee"],
    ["plate fee", "Plate Fee"],
    ["platefee", "Plate Fee"],
    ["lien fee", "Lien Fee"],
    ["transfer fee", "Transfer Fee"],
    ["motor vehicle fee", "Motor Vehicle Fees"],
    ["motor vehicle fees", "Motor Vehicle Fees"],
    ["mv fee", "Motor Vehicle Fees"],
    ["mv fees", "Motor Vehicle Fees"],
    ["vehicle fee", "Motor Vehicle Fees"],
    ["vehicle fees", "Motor Vehicle Fees"],
    ["local use fee", "Local Use / County Fee"],
    ["local use county fee", "Local Use / County Fee"],
    ["local fee", "Local Use / County Fee"],
    ["county fee", "Local Use / County Fee"],
    ["increase fee", "Increase Fee"],
    ["exchange fee", "Exchange Fee"],
    ["specialty plate fee", "Specialty Plate Fee"],
    ["additional charge", "Additional Charge"],
    ["add charge", "Additional Charge"],
    ["total title registration fee", "Total Title & Registration Fee"],
    ["total title & registration fee", "Total Title & Registration Fee"],
    ["total title and registration fee", "Total Title & Registration Fee"],
    ["total title reg fee", "Total Title & Registration Fee"],
    ["total title registration", "Total Title & Registration Fee"],
    ["total penalties", "Total Penalties"],
    ["penalties total", "Total Penalties"],
    ["penalty", "Total Penalties"],
    ["total penalty", "Total Penalties"],
    ["total other fees", "Total Other Fees"],
    ["other fees total", "Total Other Fees"],
    ["total other fee", "Total Other Fees"],
    ["state receipt total", "State Receipt Total"],
    ["state receipts total", "State Receipt Total"],
    ["state receipt", "State Receipt Total"],
    ["total sales taxes reg fee penalties", "Total Sales Taxes + Reg Fee + Penalties"],
    ["total sales taxes reg fee penalty", "Total Sales Taxes + Reg Fee + Penalties"],
    ["total sales taxes + reg fee + penalties", "Total Sales Taxes + Reg Fee + Penalties"],
    ["sales taxes reg fee penalties", "Total Sales Taxes + Reg Fee + Penalties"]
  ]);

  // CT-specific alias mapping: normalized CT DB keys -> CT display labels (DISPLAY_CT items)
  const ALIAS_TO_CANON_CT = new Map([
    ["delivery date", "Delivery Date"],
    ["titling state", "Titling State"],
    ["transaction id", "Transaction ID"],
    ["process date", "Process Date"],
    ["taxable selling price/ purchase price", "Taxable Selling Price/ Purchase Price"],
    ["taxable selling price purchase price", "Taxable Selling Price/ Purchase Price"],
    ["taxable selling price", "Taxable Selling Price/ Purchase Price"],
    ["trade in value1", "Trade in Value1"],
    ["dmv sales tax", "DMV Sales Tax"],
    ["dmv sales", "DMV Sales Tax"],
    ["sales tax rate", "Sales Tax Rate"],
    ["total state taxes", "Total State Taxes"],
    ["registration fee", "Registration Fee"],
    ["title fee", "Title Fee"],
    ["plate change fee", "Plate Change Fee"],
    ["safety plate fee", "Safety Plate Fee"],
    ["standard issue base plate fee", "Standard Issue Base Plate Fee"],
    ["lienholder fee", "Lienholder Fee"],
    ["lien fee", "Lienholder Fee"],
    ["greenhouse gas reduction fee", "Greenhouse Gas Reduction Fee"],
    ["emissions 4-year exempt fee", "Emissions 4-Year Exempt Fee"],
    ["admin fee", "Admin Fee"],
    ["clean air act fee", "Clean Air Act Fee"],
    ["passport to parks fee", "Passport to Parks Fee"],
    ["vehicle transfer fee", "Vehicle Transfer Fee"],
    ["motorcycle education", "Motorcycle Education"],
    ["record transfer fee", "Record Transfer Fee"],
    ["additional charge", "Additional Charge"],
    ["renewal late fee", "Renewal Late Fee"],
    ["total penalties", "Total Penalties"],
    ["total other fees", "Total Other Fees"],
    ["state receipt total", "State Receipt Total"],
    ["total title & registration fee", "Total Title & Registration Fee"],
    ["total title registration fee", "Total Title & Registration Fee"],
    ["total title registration", "Total Title & Registration Fee"],
    ["total sales taxes + reg fee + penalties", "Total Sales Taxes + Reg Fee + Penalties"],
    ["total sales taxes reg fee penalties", "Total Sales Taxes + Reg Fee + Penalties"],

    // useful fallbacks
    ["taxable selling price", "Taxable Selling Price/ Purchase Price"],
    ["platefee", "Plate Change Fee"],
    ["motor vehicle fee", "Motor Vehicle Fees"],
    ["local use fee", "Local Use / County Fee"]
  ]);

  // GA-specific alias mapping: normalized GA DB keys -> GA display labels (DISPLAY_GA items)
  const ALIAS_TO_CANON_GA = new Map([
    ["delivery date", "Delivery Date"],
    ["titling state", "Titling State"],
    ["state", "Titling State"],
    ["transaction id", "Transaction ID"],
    ["transaction_id", "Transaction ID"],
    ["txn id", "Transaction ID"],
    ["txn", "Transaction ID"],
    ["process date", "Process Date"],
    ["proc date", "Process Date"],
    ["process_date", "Process Date"],
    ["taxable selling price/ purchase", "Taxable Selling Price/ Purchase"],
    ["taxable selling price purchase", "Taxable Selling Price/ Purchase"],
    ["taxable selling price", "Taxable Selling Price/ Purchase"],
    ["purchase price", "Taxable Selling Price/ Purchase"],
    ["trade in value", "Trade in Value"],
    ["trade in value1", "Trade in Value"],
    ["sales tax credit reciprocity", "Sales Tax Credit Reciprocity"],
    ["sales tax credit", "Sales Tax Credit Reciprocity"],
    ["sales tax rate", "Sales Tax Rate"],
    ["tax rate", "Sales Tax Rate"],
    ["title ad valorem tax", "Title Ad Valorem Tax"],
    ["ga sales tax", "GA Sales Tax"],
    ["sales tax", "GA Sales Tax"],
    ["annual ad valorem", "Annual Ad Valorem"],
    ["title ad valorem dealer penalty", "Title Ad Valorem Dealer Penalty"],
    ["title ad valorem dealer penalty", "Title Ad Valorem Dealer Penalty"],
    ["title ad valorem casual interest", "Title Ad Valorem Casual Interest"],
    ["total state taxes", "Total State Taxes"],
    ["state taxes", "Total State Taxes"],
    ["tag fee", "Tag Fee"],
    ["tag", "Tag Fee"],
    ["registration fee", "Registration Fee"],
    ["reg fee", "Registration Fee"],
    ["title fee", "Title Fee"],
    ["title", "Title Fee"],
    ["plate fee", "Plate Fee"],
    ["plate", "Plate Fee"],
    ["alternative fuel vehicle fee", "Alternative Fuel Vehicle Fee"],
    ["alternative fuel fee", "Alternative Fuel Vehicle Fee"],
    ["mail fee", "Mail Fee"],
    ["highway impact fee", "Highway Impact Fee"],
    ["title late penalty", "Title Late Penalty"],
    ["title late penalty", "Title Late Penalty"],
    ["title ad valorem casual penalty", "Title Ad Valorem Casual Penalty"],
    ["additional charge", "Additional Charge"],
    ["add charge", "Additional Charge"],
    ["total title & registration fee", "Total Title & Registration Fee"],
    ["total title registration fee", "Total Title & Registration Fee"],
    ["total title and registration fee", "Total Title & Registration Fee"],
    ["total title reg fee", "Total Title & Registration Fee"],
    ["total title registration", "Total Title & Registration Fee"],
    ["total penalties", "Total Penalties"],
    ["penalties total", "Total Penalties"],
    ["penalty", "Total Penalties"],
    ["total penalty", "Total Penalties"],
    ["total other fees", "Total Other Fees"],
    ["other fees total", "Total Other Fees"],
    ["total other fee", "Total Other Fees"],
    ["state receipt total", "State Receipt Total"],
    ["state receipts total", "State Receipt Total"],
    ["state receipt", "State Receipt Total"],
    ["total sales taxes + reg fee + penalties", "Total Sales Taxes + Reg Fee + Penalties"],
    ["total sales taxes reg fee penalties", "Total Sales Taxes + Reg Fee + Penalties"],
    ["sales taxes reg fee penalties", "Total Sales Taxes + Reg Fee + Penalties"]
  ]);

  // Active maps and display set (will be switched based on file state)
  let ACTIVE_ALIAS_MAP = ALIAS_TO_CANON; // maps normalized raw key -> display label
  let ACTIVE_DISPLAY = DISPLAY_DEFAULT;  // list of labels to render (in order)

  // Helper to map a backend key to the chosen display label (state-aware)
  function toCanonicalLabel(key) {
    const n = norm(key);
    const mapped = ACTIVE_ALIAS_MAP.get(n);
    if (mapped) return mapped;
    const defaultMapped = ALIAS_TO_CANON.get(n);
    if (defaultMapped) return defaultMapped;
    return null;
  }

  /* ---------- State detection and switching ---------- */
  function detectStateForFile(summaryRow, filename) {
    if (summaryRow && typeof summaryRow === "object") {
      const candidates = ["state", "state_code", "stateName", "location", "region", "stateShort", "titling_state", "title_state"];
      for (const k of candidates) {
        if (Object.prototype.hasOwnProperty.call(summaryRow, k) && summaryRow[k]) {
          return String(summaryRow[k]).trim().toUpperCase();
        }
        const found = Object.keys(summaryRow).find(kk => kk.toLowerCase() === k.toLowerCase());
        if (found && summaryRow[found]) return String(summaryRow[found]).trim().toUpperCase();
      }
    }
    if (filename) {
      const fn = String(filename).toUpperCase();
      const m = fn.match(/^([A-Z]{2})[-_]/);
      if (m && m[1]) return m[1].toUpperCase();
    }
    return "";
  }

  function setSchemaForState(stateCode) {
    const s = String(stateCode || "").trim().toUpperCase();
    if (s === "CT") {
      ACTIVE_ALIAS_MAP = ALIAS_TO_CANON_CT;
      ACTIVE_DISPLAY = DISPLAY_CT;
      console.debug("[view] using CT alias map AND CT display labels for state:", s);
    } else if (s === "GA") {
      ACTIVE_ALIAS_MAP = ALIAS_TO_CANON_GA;
      ACTIVE_DISPLAY = DISPLAY_GA;
      console.debug("[view] using GA alias map AND GA display labels for state:", s);
    } else {
      ACTIVE_ALIAS_MAP = ALIAS_TO_CANON;
      ACTIVE_DISPLAY = DISPLAY_DEFAULT;
      if (s) console.debug("[view] using default alias map and labels for state:", s);
    }
  }

  /* ----------------- Render & logic (mostly unchanged) ----------------- */
  let DIRTY = false;

  // Expand fee field candidates to include CT and GA variants so computed sums see all state-specific fields.
  const FEE_FIELDS_FOR_TITLE_REG = [
    "Registration Fee",
    "Title Fee",
    "Plate Fee",
    "Plate Change Fee",
    "Safety Plate Fee",
    "Standard Issue Base Plate Fee",
    "Lien Fee",
    "Lienholder Fee",
    "Transfer Fee",
    "Vehicle Transfer Fee",
    "Motor Vehicle Fees",
    "Motorcycle Education",
    "Local Use / County Fee",
    "Increase Fee",
    "Exchange Fee",
    "Specialty Plate Fee",
    "Additional Charge",
    "Record Transfer Fee",
    "Passport to Parks Fee",
    // CT-specific additions
    "Greenhouse Gas Reduction Fee",
    "Emissions 4-Year Exempt Fee",
    "Admin Fee",
    "Clean Air Act Fee",
    "Renewal Late Fee",
    // GA-specific additions (Tag Fee excluded from title & reg fee calculation)
    "Alternative Fuel Vehicle Fee",
    "Mail Fee", 
    "Highway Impact Fee"
  ];

  const PENALTY_FIELDS = [
  "Title Late Penalty",
  "Title Ad Valorem Casual Penalty",
  "Title Ad Valorem Dealer Penalty"
  ];

  function parseNumber(s) {
    if (s === null || s === undefined) return 0.0;
    const str = String(s).trim();
    if (str === "") return 0.0;
    const cleaned = str.replace(/[, $]/g, "");
    const n = Number(cleaned);
    return Number.isFinite(n) ? n : 0.0;
  }
  function fmtNumber(n) {
    return Number(n).toFixed(2);
  }
  function fmtNumberWithCommas(n) {
    const num = Number(n);
    if (!Number.isFinite(num)) return fmtNumber(n);
    return num.toLocaleString('en-US', { minimumFractionDigits: 2, maximumFractionDigits: 2 });
  }

  // recalcComputedFields now accepts (userInitiated, forceUpdate)
  // - userInitiated: when true, show save button and mark DIRTY on changes
  // - forceUpdate: when true, update totals regardless of server-provided total value,
  //                but DO NOT set DIRTY or reveal save button (safe overwrite on initial load)
  function recalcComputedFields(userInitiated, forceUpdate) {
    // sum fee fields (will include CT variants because FEE_FIELDS_FOR_TITLE_REG was expanded)
    let sumFees = 0.0;
    for (const f of FEE_FIELDS_FOR_TITLE_REG) {
      const inp = getInputByLabel(f);
      const val = inp ? inp.value : "";
      sumFees += parseNumber(val);
    }

    // detect if any fee input has been edited by the user in this session
    const anyFeeEdited = FEE_FIELDS_FOR_TITLE_REG.some(f => {
      const inp = getInputByLabel(f);
      return inp && inp.dataset.userEdited === "true";
    });

    const anyPenaltyEdited = PENALTY_FIELDS.some(f => {
      const inp = getInputByLabel(f);
      return inp && inp.dataset.userEdited === "true";
    });

    // Total Title & Registration Fee
    const totalTitleRegInput = getInputByLabel("Total Title & Registration Fee");
    if (totalTitleRegInput) {
      const formatted = fmtNumber(sumFees);
      const isUserEdited = totalTitleRegInput.dataset.userEdited === "true";
      const lastComputed = totalTitleRegInput.dataset.lastComputed || "";

      // Update if forced OR user initiated OR fee inputs were edited OR total is empty
      // Additionally, if computed value changed compared to lastComputed and the user didn't edit the total,
      // overwrite the DOM so UI shows latest computed total immediately.
      const shouldOverwriteBecauseComputedChanged = (!isUserEdited && lastComputed !== formatted);
      if (forceUpdate || userInitiated || anyFeeEdited || totalTitleRegInput.value === "" || shouldOverwriteBecauseComputedChanged) {
        if (!isUserEdited && totalTitleRegInput.value !== formatted) {
          totalTitleRegInput.value = formatted;
        }
        // update lastComputed marker so we know what we computed previously
        totalTitleRegInput.dataset.lastComputed = formatted;
        // Only mark DIRTY / show Save if user initiated change or fee edits (not on forced update)
        if (!forceUpdate && (userInitiated || anyFeeEdited)) {
          const saveBtn = document.getElementById("saveBtn");
          if (saveBtn) saveBtn.classList.remove("d-none");
          DIRTY = true;
        }
      }
    }

    // Total Penalties
    let sumPenalties = 0.0;

    for (const f of PENALTY_FIELDS) {
      const inp = getInputByLabel(f);
      const val = inp ? inp.value : "";
      sumPenalties += parseNumber(val);
    }

    const penaltiesInput = getInputByLabel("Total Penalties");

    if (penaltiesInput) {
      const formatted = fmtNumber(sumPenalties);
      const isUserEdited = penaltiesInput.dataset.userEdited === "true";
      const lastComputed = penaltiesInput.dataset.lastComputed || "";

      const shouldOverwrite = (!isUserEdited && lastComputed !== formatted);

      if (forceUpdate || userInitiated || sumPenalties > 0 || penaltiesInput.value === "" || shouldOverwrite) {
        if (!isUserEdited && penaltiesInput.value !== formatted) {
          penaltiesInput.value = formatted;
        }

        penaltiesInput.dataset.lastComputed = formatted;

        if (!forceUpdate && userInitiated) {
          document.getElementById("saveBtn")?.classList.remove("d-none");
          DIRTY = true;
        }
      }
    }

    // Total Other Fees
    const otherFeesInput = getInputByLabel("Total Other Fees");
    if (otherFeesInput) {
      const isUserEdited = otherFeesInput.dataset.userEdited === "true";
      const onum = parseNumber(otherFeesInput.value);
      const formatted = fmtNumber(onum);
      if (forceUpdate || userInitiated || otherFeesInput.value === "") {
        if (!isUserEdited && otherFeesInput.value !== formatted) {
          otherFeesInput.value = formatted;
          if (!forceUpdate && userInitiated) {
            const saveBtn = document.getElementById("saveBtn");
            if (saveBtn) saveBtn.classList.remove("d-none");
            DIRTY = true;
          }
        }
      }
    }

    // NOTE: removed previous logic that cleared State Receipt Total — we now preserve server-provided value.
    // Do not force-clear or overwrite the State Receipt Total on initial load.

    // GA-specific calculation: Total Sales Taxes + Reg Fee + Penalties = Total Title & Registration Fee + Total State Taxes
    const stateTaxesInput = getInputByLabel("Total State Taxes");
    const stateTaxesVal = (stateTaxesInput && stateTaxesInput.value) ? parseNumber(stateTaxesInput.value) : 0.0;
    const penaltiesVal = sumPenalties;
    const totalSalesPlus = sumFees + stateTaxesVal + penaltiesVal;

    const totalSalesInp = getInputByLabel("Total Sales Taxes + Reg Fee + Penalties");
    if (totalSalesInp) {
      const formatted = fmtNumber(totalSalesPlus);
      const isUserEdited = totalSalesInp.dataset.userEdited === "true";
      // similar overwrite rules as title total
      const lastComputedSales = totalSalesInp.dataset.lastComputed || "";
      const shouldOverwriteSales = (!isUserEdited && lastComputedSales !== formatted);
      if (forceUpdate || userInitiated || anyFeeEdited || anyPenaltyEdited || totalSalesInp.value === "" || shouldOverwriteSales) {
        if (!isUserEdited && totalSalesInp.value !== formatted) {
          totalSalesInp.value = formatted;
        }
        totalSalesInp.dataset.lastComputed = formatted;
        if (!forceUpdate && (userInitiated || anyFeeEdited || anyPenaltyEdited)) {
          const saveBtn = document.getElementById("saveBtn");
          if (saveBtn) saveBtn.classList.remove("d-none");
          DIRTY = true;
        }
      }
    }
  }

  function renderEditableTable(valuesByDisplayLabel, options = {}) {
    const readonly = !!options.readonly;
    const suspect = options.suspect || {};
    const suspect_values = options.suspect_values || {};

    const tbody = document.getElementById("extracted-data");
    const saveBtn = document.getElementById("saveBtn");
    if (!tbody) return;

    DIRTY = false;
    if (saveBtn) {
      if (readonly) saveBtn.classList.add("d-none");
      else saveBtn.classList.remove("d-none");
    }

    try {
      const extractedCardHeader = document.querySelector('.card .card-header');
      if (extractedCardHeader) {
        extractedCardHeader.querySelectorAll('.suspect-legend, .legend-item, .suspect-badge, .legend-swatch, .suspect-legend').forEach(n => n.remove());
        const titleEl = extractedCardHeader.querySelector('.card-title');
        if (titleEl) {
          titleEl.innerHTML = titleEl.innerHTML.replace(/\bSuspect(ed)?\b/ig, '').replace(/\bInvalid\b/ig, '').trim();
        }
      }
    } catch (e) {}

    const tooltip = document.getElementById("suspectTooltip");
    if (tooltip) { tooltip.style.display = "none"; tooltip.innerHTML = ""; }

    const SUSPECT_HIGHLIGHT_COLOR = "#e8aa2e";

    const numericLabels = new Set([
      "Taxable Selling Price / Purchase Price",
      "Sales Tax Credit Reciprocity",
      "Sales Tax",
      "Sales Tax Rate",
      "Total State Taxes",
      "Registration Fee",
      "Title Fee",
      "Plate Fee",
      "Plate Change Fee",
      "Safety Plate Fee",
      "Lien Fee",
      "Lienholder Fee",
      "Transfer Fee",
      "Vehicle Transfer Fee",
      "Motor Vehicle Fees",
      "Motorcycle Education",
      "Local Use / County Fee",
      "Increase Fee",
      "Exchange Fee",
      "Specialty Plate Fee",
      "Additional Charge",
      "Total Title & Registration Fee",
      "Total Penalties",
      "Total Other Fees",
      "Total Sales Taxes + Reg Fee + Penalties",
      "Taxable Selling Price/ Purchase Price",
      "DMV Sales Tax",
      "State Receipt Total"
    ]);

    let html = "";
    for (const label of ACTIVE_DISPLAY) {
      const keyAttr = encodeURIComponent(label);
      const val = valuesByDisplayLabel[label] ?? "";
      let displayVal = val;

      if (label === "Total Penalties" || label === "Total Other Fees") {
        displayVal = val === "" ? "0.00" : val;
      }
      if (label === "State Receipt Total" && (displayVal === null || displayVal === undefined)) {
        displayVal = "";
      }

      if (readonly && numericLabels.has(label) && (displayVal === "" || displayVal === null)) {
        displayVal = "0.00";
      }
      if (readonly && !numericLabels.has(label) && (displayVal === "" || displayVal === null)) {
        displayVal = "NULL";
      }

      const suspectRaw = (suspect_values && Object.prototype.hasOwnProperty.call(suspect_values, label)) ? suspect_values[label] : null;
      const hasSuspectRaw = typeof suspectRaw !== "undefined" && suspectRaw !== null && String(suspectRaw).trim() !== "";
      if (hasSuspectRaw) {
        displayVal = String(suspectRaw);
      } else {
        if (numericLabels.has(label) && displayVal !== "" && displayVal !== null) {
          const n = parseNumber(displayVal);
          if (label === "Taxable Selling Price / Purchase Price" || label === "Taxable Selling Price/ Purchase Price") {
            displayVal = fmtNumberWithCommas(n);
          } else {
            displayVal = fmtNumber(n);
          }
        }
      }

      const sReason = suspect[label];
      const isSuspect = typeof sReason !== "undefined" && sReason !== null && String(sReason).trim() !== "";

      const escVal = escapeHtml(displayVal);
      const inputStyle = isSuspect && escVal !== "" ? `style="background-color: ${SUSPECT_HIGHLIGHT_COLOR};"` : "";

      // Delivery Date should always be disabled/readonly for all states
      const isDeliveryDate = label === "Delivery Date";
      const shouldDisable = readonly || isDeliveryDate;

      html += `
        <tr>
          <td style="width:35%; white-space:nowrap;"><b>${escapeHtml(label)}</b></td>
          <td class="value-cell">
            <input type="text"
                   ${shouldDisable ? "disabled" : ""}
                   class="form-control form-control-sm field-input"
                   data-key="${keyAttr}"
                   value="${escVal}" ${inputStyle} />
          </td>
        </tr>`;
    }

    tbody.innerHTML = html;

    tbody.querySelectorAll(".field-input").forEach(inp => {
      inp.dataset.userEdited = "false";
      // clear lastComputed so first recalc will set it properly
      inp.dataset.lastComputed = "";
    });

    if (!readonly) {
      const inputs = Array.from(tbody.querySelectorAll(".field-input"));
      function setDirtyAndShowSave() {
        const saveBtnLocal = document.getElementById("saveBtn");
        if (saveBtnLocal) saveBtnLocal.classList.remove("d-none");
        DIRTY = true;
      }
      inputs.forEach(inp => {
        inp.addEventListener("input", (e) => {
          inp.dataset.userEdited = "true";
          setDirtyAndShowSave();
          recalcComputedFields(true, false);
        });
        inp.addEventListener("blur", () => {
          const label = decodeURIComponent(inp.getAttribute("data-key") || "");
          const numericLabelsLocal = new Set(Array.from(numericLabels));
          if (numericLabelsLocal.has(label)) {
            const num = parseNumber(inp.value);
            if (label === "Taxable Selling Price / Purchase Price" || label === "Taxable Selling Price/ Purchase Price") {
              inp.value = fmtNumberWithCommas(num);
            } else {
              inp.value = fmtNumber(num);
            }
          }
          recalcComputedFields(true, false);
        });
        inp.addEventListener("keydown", (e) => {
          if ((e.ctrlKey || e.metaKey) && e.key.toLowerCase() === "s") {
            e.preventDefault();
            document.getElementById("saveBtn")?.click();
          }
        });
      });
    } else {
      tbody.querySelectorAll("input").forEach(i => i.classList.add("readonly-field"));
    }

    // Decide whether to force a safe recompute on initial render:
    // Force if any fee input has a non-empty value OR State Receipt Total has a non-empty value.
    let shouldForce = false;
    for (const f of FEE_FIELDS_FOR_TITLE_REG) {
      const inp = getInputByLabel(f);
      if (inp && String(inp.value).trim() !== "") { shouldForce = true; break; }
    }
    const stateReceiptInp = getInputByLabel("State Receipt Total");
    if (!shouldForce && stateReceiptInp && String(stateReceiptInp.value).trim() !== "") shouldForce = true;

    // Call recalc: not user-initiated, but force if we determined it's necessary.
    recalcComputedFields(false, shouldForce);
  }

  function getInputByLabel(label) {
    const tbody = document.getElementById("extracted-data");
    if (!tbody) return null;
    const key = encodeURIComponent(label);
    return tbody.querySelector(`.field-input[data-key="${key}"]`);
  }

  // helper to pick the first available input value among candidate labels (used for sales tax)
  function getFirstInputNumber(labels) {
    for (const l of labels) {
      const inp = getInputByLabel(l);
      if (inp && inp.value !== "") return parseNumber(inp.value);
    }
    return 0.0;
  }

  /* ----------------- Loading and rejected handling ----------------- */
  async function fetchSummaryRowForFile(date, filename) {
    try {
      const resp = await fetch(`/api/files?date=${encodeURIComponent(date)}`, { credentials: "include" });
      if (!resp.ok) return null;
      const list = await resp.json();
      if (!Array.isArray(list)) return null;
      const target = list.find(r => String(r.filename || "").toLowerCase() === String(filename).toLowerCase());
      return target || null;
    } catch (e) {
      console.warn("Failed to fetch summary row:", e);
      return null;
    }
  }

  function isRejectedRow(row) {
    if (!row) return false;
    const s = String(row.status || "").toUpperCase();
    if (s === "DUPLICATE" || s === "FAILED") return true;
    if (row.rejection_reason || row.rejected_for) return true;
    return false;
  }

  function ensureRejectedNoticeContainer() {
    let container = document.getElementById("rejectedNotice");
    if (!container) {
      const card = document.querySelector(".card .card-body .flex-grow-1");
      if (card) {
        container = document.createElement("div");
        container.id = "rejectedNotice";
        container.className = "mb-2";
        card.insertBefore(container, card.firstChild);
      } else {
        container = document.createElement("div");
        container.id = "rejectedNotice";
        container.className = "mb-2";
        document.body.insertBefore(container, document.body.firstChild);
      }
    }
    return container;
  }

  async function loadExtracted(date, filename, summaryRow) {
    const tbody = document.getElementById("extracted-data");
    if (tbody) {
      tbody.innerHTML = `<tr><td colspan="2" class="text-center">Loading…</td></tr>`;
    }

    // Detect state and switch display+alias map BEFORE rendering
    const fileState = detectStateForFile(summaryRow, filename);
    setSchemaForState(fileState);
    console.debug("[view] detected fileState:", fileState);

    if (summaryRow && isRejectedRow(summaryRow)) {
      const pageTitle = document.querySelector("h1.mb-0");
      if (pageTitle) pageTitle.textContent = "Rejected Record View";

      const cardHeader = document.querySelector(".card-header .card-title");
      if (cardHeader) {
        const rr = summaryRow.rejection_reason || summaryRow.rejectionReason || "";
        cardHeader.textContent = rr ? rr : "Rejected Record - Details";
      }

      const saveBtn = document.getElementById("saveBtn");
      if (saveBtn) {
        saveBtn.classList.add("d-none");
        try { saveBtn.disabled = true; } catch (e) {}
      }

      const rejection_reason = summaryRow.rejection_reason || summaryRow.rejectionReason || "";
      const rejected_for = summaryRow.rejected_for || summaryRow.rejectedFor || "";
      let duplicateInfo = "";
      if (rejected_for) duplicateInfo = String(rejected_for);
      else if (rejection_reason) {
        const m = String(rejection_reason).match(/duplicate\s+of\s+([^\s)]+)/i) || String(rejection_reason).match(/duplicate[:\-]\s*([^\s)]+)/i);
        if (m && m[1]) duplicateInfo = m[1];
      }

      const noticeContainer = ensureRejectedNoticeContainer();
      if (noticeContainer) {
        let noteHtml = "";
        if (rejection_reason) noteHtml += `<div><strong>Reason:</strong> ${escapeHtml(rejection_reason)}</div>`;
        if (duplicateInfo) noteHtml += `<div><strong>Duplicate of:</strong> ${escapeHtml(duplicateInfo)}</div>`;
        if (!noteHtml) noteHtml = `<div>No explicit rejection reason available.</div>`;
        noticeContainer.innerHTML = noteHtml;
      }

      const numericLabels = new Set([
        "Taxable Selling Price / Purchase Price",
        "Sales Tax Credit Reciprocity",
        "Sales Tax",
        "Sales Tax Rate",
        "Total State Taxes",
        "Registration Fee",
        "Title Fee",
        "Plate Fee",
        "Lien Fee",
        "Transfer Fee",
        "Motor Vehicle Fees",
        "Local Use / County Fee",
        "Increase Fee",
        "Exchange Fee",
        "Specialty Plate Fee",
        "Additional Charge",
        "Total Title & Registration Fee",
        "Total Penalties",
        "Total Other Fees",
        "Total Sales Taxes + Reg Fee + Penalties"
      ]);

      const forced = Object.create(null);
      // Use ACTIVE_DISPLAY for order
      for (const label of ACTIVE_DISPLAY) {
        if (numericLabels.has(label)) forced[label] = "0.00";
        else forced[label] = "NULL";
      }

      renderEditableTable(forced, { readonly: true });
      return;
    }

    try {
      const res = await fetch(
        `/api/file?date=${encodeURIComponent(date)}&filename=${encodeURIComponent(filename)}`,
        { credentials: "include" }
      );
      if (!res.ok) throw new Error(`HTTP ${res.status}`);
      const data = await res.json();

      const raw = data?.fields || {};
      const suspect = data?.suspect || {};
      const suspect_values = data?.suspect_values || {};

      // Debug: show unmapped keys
      try {
        const rawKeys = Object.keys(raw || {});
        const unmapped = rawKeys.filter(k => !toCanonicalLabel(k));
        if (unmapped.length) console.debug("[view] unmapped raw keys:", unmapped, "state:", fileState);
      } catch (e) {}

      // Build valuesByDisplayLabel using ACTIVE_ALIAS_MAP => ACTIVE_DISPLAY
      const valuesByDisplayLabel = Object.create(null);

      // Map raw keys to display labels (state-aware)
      for (const [k, v] of Object.entries(raw)) {
        const mappedLabel = toCanonicalLabel(k);
        if (!mappedLabel) continue;
        const val = v == null ? "" : String(v);
        if (valuesByDisplayLabel[mappedLabel]) continue;
        if (val !== "") valuesByDisplayLabel[mappedLabel] = val;
      }

      // Try direct label matching (case-insensitive) for any remaining ACTIVE_DISPLAY labels
      const rawLower = {};
      for (const [k, v] of Object.entries(raw)) rawLower[norm(k)] = v;
      for (const label of ACTIVE_DISPLAY) {
        if (valuesByDisplayLabel[label]) continue;
        const normalizedLabel = norm(label);
        if (Object.prototype.hasOwnProperty.call(rawLower, normalizedLabel)) {
          const v = rawLower[normalizedLabel];
          valuesByDisplayLabel[label] = v == null ? "" : String(v);
        }
      }

      // Ensure every ACTIVE_DISPLAY label exists (default "")
      for (const label of ACTIVE_DISPLAY) {
        if (!(label in valuesByDisplayLabel)) valuesByDisplayLabel[label] = "";
      }

      // Render using ACTIVE_DISPLAY labels & values
      renderEditableTable(valuesByDisplayLabel, { readonly: false, suspect: suspect, suspect_values: suspect_values });
    } catch (err) {
      console.error("Failed to load extracted fields:", err);
      if (tbody) {
        tbody.innerHTML = `<tr><td colspan="2" class="text-danger text-center">Error loading extracted data.</td></tr>`;
      }
    }
  }

  /* ----------------- Save (improved mapping + include computed totals) ----------------- */
  async function saveEdits(date, filename) {
    const changes = {};
    // Build reverse map: displayLabel -> first matching normalized raw key from ACTIVE_ALIAS_MAP
    const displayToNorm = {};
    try {
      for (const [normKey, disp] of ACTIVE_ALIAS_MAP.entries()) {
        if (!displayToNorm[disp]) displayToNorm[disp] = normKey; // keep first mapping
      }
    } catch (e) { /* ignore */ }

    // Helper: compute current computed totals (so we include them in payload)
    function computeCurrentTotals() {
      let sumFees = 0.0;
      for (const f of FEE_FIELDS_FOR_TITLE_REG) {
        const inp = getInputByLabel(f);
        const val = inp ? inp.value : "";
        sumFees += parseNumber(val);
      }

      let sumPenalties = 0.0;
      for (const f of PENALTY_FIELDS) {
        const inp = getInputByLabel(f);
        const val = inp ? inp.value : "";
        sumPenalties += parseNumber(val);
      }

      const stateTaxesInput = getInputByLabel("Total State Taxes");
      const stateTaxesVal = stateTaxesInput && stateTaxesInput.value
        ? parseNumber(stateTaxesInput.value)
        : 0.0;

      const totalSalesPlus = sumFees + stateTaxesVal + sumPenalties;

      return {
        totalTitleAndReg: fmtNumber(sumFees),
        totalSalesPlus: fmtNumber(totalSalesPlus)
      };
    }

    // Collect user-edited fields (existing behavior)
    document.querySelectorAll(".field-input").forEach(inp => {
      const edited = String(inp.dataset.userEdited || "false") === "true";
      if (edited) {
        const displayKey = decodeURIComponent(inp.getAttribute("data-key") || "");
        const val = inp.value;
        // 1) keep display label key
        changes[displayKey] = val;

        // 2) if we can find a normalized raw key that maps TO this display label, include it too
        const mappedNorm = displayToNorm[displayKey];
        if (mappedNorm) {
          if (!Object.prototype.hasOwnProperty.call(changes, mappedNorm)) {
            changes[mappedNorm] = val;
          }
        }

        // 3) fallback: a snake_case version of the display label (useful if backend expects underscored label)
        const fallback = displayKey
          .toLowerCase()
          .replace(/[\/()+&\-]/g, " ")
          .replace(/\s+/g, "_")
          .replace(/__+/g, "_");
        if (fallback && !Object.prototype.hasOwnProperty.call(changes, fallback)) {
          changes[fallback] = val;
        }
      }
    });

    // Always include computed totals so backend gets persisted values
    const computed = computeCurrentTotals();

    // Force DOM totals to the computed values (ensures what's visible matches payload)
    const ttlInput = getInputByLabel("Total Title & Registration Fee");
    if (ttlInput) {
      // overwrite only if user didn't edit the title-total itself; if user has manually changed it, respect their edit.
      if (ttlInput.dataset.userEdited !== "true") {
        ttlInput.value = computed.totalTitleAndReg;
      }
      // ensure lastComputed marker is set so recalc logic knows what was last computed
      ttlInput.dataset.lastComputed = computed.totalTitleAndReg;
      // clear userEdited flag so save includes the computed total keys below
      ttlInput.dataset.userEdited = "false";
    }

    const tsumInput = getInputByLabel("Total Sales Taxes + Reg Fee + Penalties");
    if (tsumInput) {
      if (tsumInput.dataset.userEdited !== "true") {
        tsumInput.value = computed.totalSalesPlus;
      }
      tsumInput.dataset.lastComputed = computed.totalSalesPlus;
      tsumInput.dataset.userEdited = "false";
    }

    // --- START PATCH: always include State Receipt Total so backend doesn't clear it ---
    const srtInput = getInputByLabel("State Receipt Total");
    if (srtInput) {
      const isUserEdited = srtInput.dataset.userEdited === "true";
      const srtRaw = String(srtInput.value || "");
      let srtFormatted = "";
      if (srtRaw.trim() === "") {
        // keep empty string to avoid writing "0.00" if backend had empty
        srtFormatted = "";
      } else {
        const maybeNum = parseNumber(srtRaw);
        srtFormatted = fmtNumber(maybeNum);
      }
      if (!Object.prototype.hasOwnProperty.call(changes, "State Receipt Total")) {
        changes["State Receipt Total"] = srtFormatted;
      }
      const mappedSrt = displayToNorm["State Receipt Total"];
      if (mappedSrt && !Object.prototype.hasOwnProperty.call(changes, mappedSrt)) {
        changes[mappedSrt] = srtFormatted;
      }
      const srtSnake = "state_receipt_total";
      if (!Object.prototype.hasOwnProperty.call(changes, srtSnake)) {
        changes[srtSnake] = srtFormatted;
      }
      const srtCleaner = "state_receipt_total";
      if (!Object.prototype.hasOwnProperty.call(changes, srtCleaner)) {
        changes[srtCleaner] = srtFormatted;
      }
      // do not mark userEdited true if it wasn't — just ensure it's represented in payload
      if (!isUserEdited) srtInput.dataset.userEdited = "false";
    }
    // --- END PATCH ---

    // include Total Title & Registration Fee under many possible keys
    function addComputedKeys(displayLabel, value) {
      if (!Object.prototype.hasOwnProperty.call(changes, displayLabel)) changes[displayLabel] = value;
      const mapped = displayToNorm[displayLabel];
      if (mapped && !Object.prototype.hasOwnProperty.call(changes, mapped)) changes[mapped] = value;
      const snake = displayLabel.toLowerCase().replace(/[\/()+&\-]/g, " ").replace(/\s+/g, "_").replace(/__+/g, "_");
      if (!Object.prototype.hasOwnProperty.call(changes, snake)) changes[snake] = value;
      const cleaner = displayLabel.toLowerCase().replace(/[^\w]+/g, "_").replace(/__+/g, "_");
      if (!Object.prototype.hasOwnProperty.call(changes, cleaner)) changes[cleaner] = value;
    }

    addComputedKeys("Total Title & Registration Fee", computed.totalTitleAndReg);
    addComputedKeys("Total Sales Taxes + Reg Fee + Penalties", computed.totalSalesPlus);

    // Debug log to verify payload before sending
    console.debug("[view] saveEdits payload changes:", changes);

    // If nothing is changed (shouldn't happen because computed totals are added), still guard
    if (Object.keys(changes).length === 0) {
      alert("No changes to save.");
      return;
    }

    const payload = { date, filename, changes, qc: true };

    const res = await fetch("/api/file/update", {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      credentials: "include",
      body: JSON.stringify(payload)
    });

    if (!res.ok) {
      const txt = await res.text();
      throw new Error(`Save failed (${res.status}): ${txt}`);
    }

    // Mark edited inputs as saved (including totals)
    DIRTY = false;
    document.querySelectorAll(".field-input").forEach(inp => {
      const key = decodeURIComponent(inp.getAttribute("data-key") || "");
      const normKey = (function(k) {
        try { return displayToNorm[k] || ""; } catch (e) { return ""; }
      })(key);
      const fallbackKey = key.toLowerCase().replace(/[\/()+&\-]/g, " ").replace(/\s+/g, "_");
      if (Object.prototype.hasOwnProperty.call(changes, key) ||
          (normKey && Object.prototype.hasOwnProperty.call(changes, normKey)) ||
          Object.prototype.hasOwnProperty.call(changes, fallbackKey)) {
        inp.dataset.userEdited = "false";
      }
    });

    // also ensure totals inputs cleared
    if (ttlInput) ttlInput.dataset.userEdited = "false";
    if (tsumInput) tsumInput.dataset.userEdited = "false";

    document.getElementById("saveBtn")?.classList.add("d-none");
    alert("Saved!");

    try {
      const summaryRow = await fetchSummaryRowForFile(date, filename);
      await loadExtracted(date, filename, summaryRow);
    } catch (e) {
      console.warn("Failed to refresh after save:", e);
    }
  }

  /* ----------------- Boot (unchanged) ----------------- */
  document.addEventListener("DOMContentLoaded", async () => {
    const date = qs("date");
    const filename = qs("filename");

    const saveBtn = document.getElementById("saveBtn");
    if (saveBtn) {
      saveBtn.addEventListener("click", async () => {
        try {
          if (!date || !filename) throw new Error('Missing "date" or "filename" in URL.');
          await saveEdits(date, filename);
        } catch (e) {
          alert(String(e));
        }
      });
    }

    window.addEventListener("beforeunload", (e) => {
      if (DIRTY) {
        e.preventDefault();
        e.returnValue = "";
      }
    });

    const tbody = document.getElementById("extracted-data");
    if (!date || !filename) {
      if (tbody) {
        const labels = DISPLAY_DEFAULT;
        tbody.innerHTML = labels.map(n => `
          <tr>
            <td style="width:35%; white-space:nowrap;"><b>${escapeHtml(n)}</b></td>
            <td><input type="text" class="form-control form-control-sm" value="" disabled /></td>
          </tr>
        `).join("");
      }
      return;
    }

    setPdfSrc(date, filename);

    let summaryRow = null;
    try {
      summaryRow = await fetchSummaryRowForFile(date, filename);
    } catch (e) {
      console.warn("Summary row fetch failed:", e);
      summaryRow = null;
    }

    await loadExtracted(date, filename, summaryRow);
  });

  // expose some helpers for debugging
  window.detectStateForFile = detectStateForFile;
  window.setSchemaForState = setSchemaForState;
  window.toCanonicalLabel = toCanonicalLabel;

  window.ACTIVE_DISPLAY = ACTIVE_DISPLAY;


// --- Next/Previous Record Navigation (state-filtered) ---
async function getFileListForCurrentDate() {
  const url = new URL(window.location.href);
  const date = url.searchParams.get('date');
  if (!date) return [];
  const resp = await fetch(`/api/files?date=${encodeURIComponent(date)}`, { credentials: "include" });
  if (!resp.ok) return [];
  const list = await resp.json();
  if (!Array.isArray(list)) return [];
  list.sort((a, b) => {
    const af = (a.filename || '').toLowerCase();
    const bf = (b.filename || '').toLowerCase();
    if (af < bf) return -1;
    if (af > bf) return 1;
    return 0;
  });
  return list;
}

function extractStateFromFilename(filename) {
  // Extract state code from filename like "PA-12345.pdf" or "CT_file.pdf" or "GA-xxx.pdf"
  const match = String(filename || '').match(/^([A-Z]{2})[-_]/i);
  return match ? match[1].toUpperCase() : null;
}

window.goToNextRecord = async function goToNextRecord() {
  try {
    const url = new URL(window.location.href);
    const filename = url.searchParams.get('filename');
    const list = await getFileListForCurrentDate();
    if (!filename || list.length === 0) {
      alert('No file list available for this date.');
      return;
    }
    
    // Get current state from filename
    const currentState = extractStateFromFilename(filename);
    if (!currentState) {
      alert('Could not determine state from filename.');
      return;
    }
    
    // Filter files by same state
    const stateFiles = list.filter(r => extractStateFromFilename(r.filename) === currentState);
    if (stateFiles.length === 0) {
      alert('No files found for this state.');
      return;
    }
    
    const idx = stateFiles.findIndex(r => String(r.filename || '').toLowerCase() === String(filename).toLowerCase());
    if (idx === -1) {
      alert('Current record not found in file list.');
      return;
    }
    
    if (idx >= stateFiles.length - 1) {
      alert('This is the last record for ' + currentState + ' state.');
      return;
    }
    
    const nextFile = stateFiles[idx + 1];
    if (!nextFile || !nextFile.filename) {
      alert('Next record not found.');
      return;
    }
    
    url.searchParams.set('filename', nextFile.filename);
    window.location.href = url.toString();
  } catch (e) {
    alert('Failed to navigate to next record: ' + e);
  }
};

window.goToPrevRecord = async function goToPrevRecord() {
  try {
    const url = new URL(window.location.href);
    const filename = url.searchParams.get('filename');
    const list = await getFileListForCurrentDate();
    if (!filename || list.length === 0) {
      alert('No file list available for this date.');
      return;
    }
    
    // Get current state from filename
    const currentState = extractStateFromFilename(filename);
    if (!currentState) {
      alert('Could not determine state from filename.');
      return;
    }
    
    // Filter files by same state
    const stateFiles = list.filter(r => extractStateFromFilename(r.filename) === currentState);
    if (stateFiles.length === 0) {
      alert('No files found for this state.');
      return;
    }
    
    const idx = stateFiles.findIndex(r => String(r.filename || '').toLowerCase() === String(filename).toLowerCase());
    if (idx === -1) {
      alert('Current record not found in file list.');
      return;
    }
    
    if (idx <= 0) {
      alert('This is the first record for ' + currentState + ' state.');
      return;
    }
    
    const prevFile = stateFiles[idx - 1];
    if (!prevFile || !prevFile.filename) {
      alert('Previous record not found.');
      return;
    }
    
    url.searchParams.set('filename', prevFile.filename);
    window.location.href = url.toString();
  } catch (e) {
    alert('Failed to navigate to previous record: ' + e);
  }
};

})();

// Persist process-history filters across refresh / back navigation
(function () {
  const STORAGE_KEY = "processHistoryFilters";

  function getFilterElements() {
    // change these selectors to match your page's filter inputs
    return {
      fromDate: document.getElementById("fromDate"),
      toDate: document.getElementById("toDate"),
      state: document.getElementById("stateSelect"),
      other: document.getElementById("otherFilter") // example - remove if not used
    };
  }

  function readUIFilters() {
    const el = getFilterElements();
    return {
      fromDate: el.fromDate ? el.fromDate.value || "" : "",
      toDate:   el.toDate   ? el.toDate.value   || "" : "",
      state:    el.state    ? el.state.value    || "" : "",
      other:    el.other    ? el.other.value    || "" : ""
    };
  }

  function applyFiltersToUI(f) {
    if (!f) return;
    const el = getFilterElements();
    if (el.fromDate && typeof f.fromDate !== "undefined") el.fromDate.value = f.fromDate || "";
    if (el.toDate   && typeof f.toDate   !== "undefined") el.toDate.value   = f.toDate   || "";
    if (el.state    && typeof f.state    !== "undefined") el.state.value    = f.state    || "";
    if (el.other    && typeof f.other    !== "undefined") el.other.value    = f.other    || "";
  }

  function saveFiltersToStorage(f) {
    try {
      localStorage.setItem(STORAGE_KEY, JSON.stringify(f || {}));
    } catch (e) { console.warn("Failed to save filters", e); }
  }

  function readFiltersFromStorage() {
    try {
      return JSON.parse(localStorage.getItem(STORAGE_KEY) || "{}");
    } catch (e) { return {}; }
  }

  function clearFiltersFromStorage() {
    try { localStorage.removeItem(STORAGE_KEY); } catch (e) {}
  }

  // Attach events to save when inputs change (immediate) and when Apply / Clear clicked
  function installSaveOnChange() {
    const el = getFilterElements();
    Object.values(el).forEach(input => {
      if (!input) return;
      input.addEventListener("change", () => {
        saveFiltersToStorage(readUIFilters());
      }, { passive: true });
      // also on input for text fields
      input.addEventListener("input", () => {
        saveFiltersToStorage(readUIFilters());
      }, { passive: true });
    });

    // If you have Apply and Clear buttons, make sure to hook them:
    const applyBtn = document.getElementById("applyFilters");
    const clearBtn = document.getElementById("clearFilters");
    if (applyBtn) {
      applyBtn.addEventListener("click", () => {
        saveFiltersToStorage(readUIFilters());
        // call your apply logic here (if separate)
        if (typeof fetchAndRender === "function") fetchAndRender();
      });
    }
    if (clearBtn) {
      clearBtn.addEventListener("click", () => {
        clearFiltersFromStorage();
        // reset UI also if needed (some pages already do)
        applyFiltersToUI({ fromDate: "", toDate: "", state: "" });
      });
    }
  }

  // Restore filters on load / when page becomes visible (handles back button bfcache)
  function installRestoreOnShow() {
    // pageshow fires on normal load and when restoring from bfcache/back
    window.addEventListener("pageshow", (evt) => {
      try {
        const saved = readFiltersFromStorage();
        if (saved && Object.keys(saved).length) {
          applyFiltersToUI(saved);
        }
      } catch (e) { console.warn(e); }
    }, false);

    // Also run on initial DOMContentLoaded
    document.addEventListener("DOMContentLoaded", () => {
      try {
        const saved = readFiltersFromStorage();
        if (saved && Object.keys(saved).length) {
          applyFiltersToUI(saved);
        }
      } catch (e) { console.warn(e); }
    });
  }

  // Optional: update URL query params to reflect saved filters (so back/refresh retains via URL)
  function syncUrlWithFilters(f) {
    try {
      const u = new URL(window.location.href);
      if (f.fromDate) u.searchParams.set("from", f.fromDate); else u.searchParams.delete("from");
      if (f.toDate)   u.searchParams.set("to", f.toDate);     else u.searchParams.delete("to");
      if (f.state)    u.searchParams.set("state", f.state);   else u.searchParams.delete("state");
      // note: this changes the URL without reloading the page
      window.history.replaceState({}, "", u.toString());
    } catch (e) { /* ignore */ }
  }

  // You can optionally call syncUrlWithFilters when saving; kept commented so it's opt-in.
  // To enable, uncomment the call in saveFiltersToStorage() or in the change handler.

  // Install everything
  installRestoreOnShow();
  installSaveOnChange();

  // Expose helpers for debugging
  window._processHistoryFilters_read = readFiltersFromStorage;
  window._processHistoryFilters_clear = clearFiltersFromStorage;
})();
