import { Chain, type LLTV, type Maturity, Mempool, Offer, Router } from "@morpho-dev/router";
import { createWalletClient, defineChain, http } from "viem";

type OfferStatus = "valid" | "invalid" | "consumed" | "expired" | "unknown";

type Mode = "get" | "match" | "create";

const envEl = document.getElementById("env")!;
envEl.textContent = `API: ${location.origin}`;

const filtersEl = document.getElementById("filters")!;
const tableTitleEl = document.getElementById("tableTitle")!;

let currentMode: Mode = "get";

function createInput(id: string, label: string, placeholder = "") {
  const wrap = document.createElement("div");
  wrap.className = "col-md-3 col-lg-2 mb-3";
  const l = document.createElement("label");
  l.className = "form-label";
  l.htmlFor = id;
  l.textContent = label;
  const i = document.createElement("input");
  i.className = "form-control form-control-sm";
  i.id = id;
  i.placeholder = placeholder;
  wrap.append(l, i);
  return { wrap, input: i };
}

function createSelect<K extends string>(
  id: string,
  label: string,
  options: K[],
  allowEmpty = true,
) {
  const wrap = document.createElement("div");
  wrap.className = "col-md-3 col-lg-2 mb-3";
  const l = document.createElement("label");
  l.className = "form-label";
  l.htmlFor = id;
  l.textContent = label;
  const s = document.createElement("select");
  s.className = "form-select form-select-sm";
  s.id = id;
  if (allowEmpty) {
    const opt = document.createElement("option");
    opt.value = "";
    opt.text = "(any)";
    s.append(opt);
  }
  for (const v of options) {
    const opt = document.createElement("option");
    opt.value = v;
    opt.text = v;
    s.append(opt);
  }
  wrap.append(l, s);
  return { wrap, select: s };
}

// Create filter rows using Bootstrap grid for GET mode
const getRow1 = document.createElement("div");
getRow1.className = "row g-2";
const creators = createInput("creators", "Creators (comma)");
const side = createSelect("side", "Side", ["buy", "sell"] as const);
const chains = createInput("chains", "Chains (comma)");
const loanTokens = createInput("loanTokens", "Loan Tokens (comma)");
const statusFilter = createInput("status", "Status (comma)");
getRow1.append(creators.wrap, side.wrap, chains.wrap, loanTokens.wrap, statusFilter.wrap);

const getRow2 = document.createElement("div");
getRow2.className = "row g-2";
const callbackAddresses = createInput("callbackAddresses", "Callback Addresses (comma)");
const minAmount = createInput("minAmount", "Min Amount (wei)");
const maxAmount = createInput("maxAmount", "Max Amount (wei)");
const minRate = createInput("minRate", "Min Rate (wei)");
const maxRate = createInput("maxRate", "Max Rate (wei)");
getRow2.append(callbackAddresses.wrap, minAmount.wrap, maxAmount.wrap, minRate.wrap, maxRate.wrap);

const getRow3 = document.createElement("div");
getRow3.className = "row g-2";
const minMaturity = createInput("minMaturity", "Min Maturity (sec)");
const maxMaturity = createInput("maxMaturity", "Max Maturity (sec)");
const minExpiry = createInput("minExpiry", "Min Expiry (sec)");
const maxExpiry = createInput("maxExpiry", "Max Expiry (sec)");
getRow3.append(minMaturity.wrap, maxMaturity.wrap, minExpiry.wrap, maxExpiry.wrap);

const getRow4 = document.createElement("div");
getRow4.className = "row g-2";
const collateralAssets = createInput("collateralAssets", "Collateral Assets (comma)");
const collateralOracles = createInput("collateralOracles", "Collateral Oracles (comma)");
const collateralTuple = createInput(
  "collateralTuple",
  "Collateral Tuple asset[:oracle][:lltv] (# separated)",
);
getRow4.append(collateralAssets.wrap, collateralOracles.wrap, collateralTuple.wrap);

const getRow5 = document.createElement("div");
getRow5.className = "row g-2";
const minLltv = createInput("minLltv", "Min LLTV (decimal, e.g. 0.8)");
const maxLltv = createInput("maxLltv", "Max LLTV (decimal, e.g. 0.9)");
const sortBy = createSelect("sortBy", "Sort By", ["rate", "maturity", "expiry", "amount"] as const);
const sortOrder = createSelect("sortOrder", "Sort Order", ["asc", "desc"] as const);
getRow5.append(minLltv.wrap, maxLltv.wrap, sortBy.wrap, sortOrder.wrap);

// Create filter rows for MATCH mode
const matchRow1 = document.createElement("div");
matchRow1.className = "row g-2";
const matchSide = createSelect("matchSide", "Side", ["buy", "sell"] as const, false);
const matchChainId = createInput("matchChainId", "Chain ID");
const matchRate = createInput("matchRate", "Rate (wei)");
const matchLoanToken = createInput("matchLoanToken", "Loan Token");
// Default chainId to 1 for match mode
matchChainId.input.value = "1";
matchRow1.append(matchSide.wrap, matchChainId.wrap, matchRate.wrap, matchLoanToken.wrap);

const matchRow2 = document.createElement("div");
matchRow2.className = "row g-2";
const matchCreator = createInput("matchCreator", "Creator");
const matchStatus = createInput("matchStatus", "Status (comma)");
const matchMaturity = createInput("matchMaturity", "Exact Maturity (sec)");
const matchMinMaturity = createInput("matchMinMaturity", "Min Maturity (sec)");
matchRow2.append(matchCreator.wrap, matchStatus.wrap, matchMaturity.wrap, matchMinMaturity.wrap);

const matchRow3 = document.createElement("div");
matchRow3.className = "row g-2";
const matchMaxMaturity = createInput("matchMaxMaturity", "Max Maturity (sec)");
const matchCollateralAsset = createInput("matchCollateralAsset", "Collateral Asset");
const matchCollateralOracle = createInput("matchCollateralOracle", "Collateral Oracle");
const matchCollateralLltv = createInput("matchCollateralLltv", "Collateral LLTV (decimal)");
matchRow3.append(
  matchMaxMaturity.wrap,
  matchCollateralAsset.wrap,
  matchCollateralOracle.wrap,
  matchCollateralLltv.wrap,
);

const createRow1 = document.createElement("div");
createRow1.className = "row g-2";
const createChainId = createInput(
  "createChainId",
  "Chain ID",
  Chain.chains["ethereum-virtual-testnet"].id.toString(),
);
const createLoanToken = createInput(
  "createLoanToken",
  "Loan Token",
  "0x6B175474E89094C44Da98b954EedeAC495271d0F",
);
const createAssets = createInput("createAssets", "Assets (wei)", "1000000000000000000");
const createRate = createInput("createRate", "Rate (wei)", "100000000000000000");
createRow1.append(createChainId.wrap, createLoanToken.wrap, createAssets.wrap, createRate.wrap);

const createRow2 = document.createElement("div");
createRow2.className = "row g-2";
const createMaturity = createInput("createMaturity", "Maturity (sec)", "1735689600");
const createExpiry = createInput("createExpiry", "Expiry (sec)", "1735689600");
const createOffering = createInput(
  "createOffering",
  "Offering Address",
  "0x0000000000000000000000000000000000000000",
);
const createCallbackAddress = createInput(
  "createCallbackAddress",
  "Callback Address",
  "0x0000000000000000000000000000000000000000",
);
createRow2.append(
  createMaturity.wrap,
  createExpiry.wrap,
  createOffering.wrap,
  createCallbackAddress.wrap,
);

const createRow3 = document.createElement("div");
createRow3.className = "row g-2";
const createCollateralAsset = createInput(
  "createCollateralAsset",
  "Collateral Asset",
  "0x0000000000000000000000000000000000000000",
);
const createCollateralOracle = createInput(
  "createCollateralOracle",
  "Collateral Oracle",
  "0x0000000000000000000000000000000000000000",
);
const createCollateralLltv = createInput(
  "createCollateralLltv",
  "Collateral LLTV (decimal)",
  "0.8",
);
const createBuy = createSelect(
  "createBuy",
  "Buy/Sell",
  ["Buy(true)", "Sell(false)"] as const,
  false,
);
createRow3.append(
  createCollateralAsset.wrap,
  createCollateralOracle.wrap,
  createCollateralLltv.wrap,
  createBuy.wrap,
);

// Helpers to create accordion items for filters
function createAccordionItem(id: string, title: string, children: HTMLElement[], expanded = false) {
  const item = document.createElement("div");
  item.className = "accordion-item";

  const header = document.createElement("h2");
  header.className = "accordion-header";
  header.id = `hdr-${id}`;

  const button = document.createElement("button");
  button.type = "button";
  button.className = `accordion-button ${expanded ? "" : "collapsed"}`;
  button.setAttribute("data-bs-toggle", "collapse");
  button.setAttribute("data-bs-target", `#col-${id}`);
  button.setAttribute("aria-expanded", expanded ? "true" : "false");
  button.setAttribute("aria-controls", `col-${id}`);
  button.textContent = title;

  header.append(button);

  const collapse = document.createElement("div");
  collapse.id = `col-${id}`;
  collapse.className = `accordion-collapse collapse ${expanded ? "show" : ""}`;
  collapse.setAttribute("aria-labelledby", `hdr-${id}`);

  const body = document.createElement("div");
  body.className = "accordion-body";
  body.append(...children);

  collapse.append(body);
  item.append(header, collapse);

  return item;
}

// Build filters accordion for GET mode
const getAccGeneral = createAccordionItem("getGeneral", "General", [getRow1], true);
const getAccAmounts = createAccordionItem("getAmounts", "Amounts & Rates", [getRow2]);
const getAccTime = createAccordionItem("getTime", "Time", [getRow3]);
const getAccCollateral = createAccordionItem("getCollateral", "Collateral", [getRow4]);
const getAccLltvSorting = createAccordionItem("getLltv", "LLTV & Sorting", [getRow5]);

// Build filters accordion for MATCH mode
const matchAccGeneral = createAccordionItem("matchGeneral", "General", [matchRow1], true);
const matchAccFilters = createAccordionItem("matchFilters", "Filters", [matchRow2]);
const matchAccCollateral = createAccordionItem("matchCollateral", "Collateral", [matchRow3]);

const createAccGeneral = createAccordionItem("createGeneral", "General", [createRow1], true);
const createAccDetails = createAccordionItem("createDetails", "Details", [createRow2]);
const createAccCollateral = createAccordionItem("createCollateral", "Collateral & Type", [
  createRow3,
]);

// Get DOM elements
const offersTBody = document.querySelector<HTMLTableSectionElement>("#offersTable tbody")!;
const btnGetMode = document.getElementById("btnGetMode") as HTMLButtonElement;
const btnMatchMode = document.getElementById("btnMatchMode") as HTMLButtonElement;
const btnCreateMode = document.getElementById("btnCreateMode") as HTMLButtonElement;
const btnFetch = document.getElementById("btnFetch") as HTMLButtonElement;
const btnPrev = document.getElementById("btnPrev") as HTMLButtonElement;
const btnNext = document.getElementById("btnNext") as HTMLButtonElement;
const btnSubmit = document.getElementById("btnSubmit") as HTMLButtonElement;
const btnRandomize = document.getElementById("btnRandomize") as HTMLButtonElement;
const createControls = document.getElementById("createControls") as HTMLDivElement;
const filtersTitle = document.getElementById("filtersTitle") as HTMLSpanElement;
const tableSection = document.getElementById("tableSection") as HTMLDivElement;
const limitEl = document.getElementById("limit") as HTMLInputElement;
const statusEl = document.getElementById("status");

// Initialize state variables
let currentCursor: string | null = null;
const cursorStack: string[] = [];

function switchMode(mode: Mode) {
  currentMode = mode;

  // Update button states
  if (mode === "get") {
    btnGetMode.className = "btn btn-primary";
    btnMatchMode.className = "btn btn-outline-primary";
    btnCreateMode.className = "btn btn-outline-primary";
    tableTitleEl.textContent = "Offers";
    filtersTitle.textContent = "Filters";
    createControls.classList.add("d-none");
    tableSection.classList.remove("d-none");
  } else if (mode === "match") {
    btnGetMode.className = "btn btn-outline-primary";
    btnMatchMode.className = "btn btn-primary";
    btnCreateMode.className = "btn btn-outline-primary";
    tableTitleEl.textContent = "Matched Offers";
    filtersTitle.textContent = "Filters";
    createControls.classList.add("d-none");
    tableSection.classList.remove("d-none");
  } else {
    btnGetMode.className = "btn btn-outline-primary";
    btnMatchMode.className = "btn btn-outline-primary";
    btnCreateMode.className = "btn btn-primary";
    tableTitleEl.textContent = "Create New Offer";
    filtersTitle.textContent = "Parameters";
    createControls.classList.remove("d-none");
    tableSection.classList.add("d-none");
  }

  // Clear filters and table
  filtersEl.innerHTML = "";
  offersTBody.innerHTML = "";

  // Show appropriate filters
  if (mode === "get") {
    filtersEl.append(getAccGeneral, getAccAmounts, getAccTime, getAccCollateral, getAccLltvSorting);
  } else if (mode === "match") {
    filtersEl.append(matchAccGeneral, matchAccFilters, matchAccCollateral);
    // Ensure defaults and auto-fetch for match mode
    const chainInput = document.getElementById("matchChainId") as HTMLInputElement | null;
    if (chainInput && !chainInput.value) chainInput.value = "1";
    const sideSel = document.getElementById("matchSide") as HTMLSelectElement | null;
    if (sideSel && !sideSel.value) sideSel.value = "buy";
    // Trigger initial fetch when switching to match
    fetchOffers(undefined, true);
  } else {
    filtersEl.append(createAccGeneral, createAccDetails, createAccCollateral);
    // Populate with random offer data
    populateRandomOffer();
  }

  // Reset pagination
  currentCursor = null;
  cursorStack.length = 0;
  btnNext.disabled = true;
  btnPrev.disabled = true;
  if (statusEl) statusEl.textContent = "";
}

// Initialize with GET mode
switchMode("get");

btnGetMode.addEventListener("click", () => switchMode("get"));
btnMatchMode.addEventListener("click", () => switchMode("match"));
btnCreateMode.addEventListener("click", () => switchMode("create"));
btnRandomize.addEventListener("click", () => populateRandomOffer());
btnSubmit.addEventListener("click", () => submitOffer());

// Expand/Collapse all controls
const btnCollapseAll = document.getElementById("collapseAll");
const btnExpandAll = document.getElementById("expandAll");

function setAllAccordions(expand: boolean) {
  const collapses = Array.from(
    document.querySelectorAll<HTMLElement>("#filters .accordion-collapse"),
  );
  for (const el of collapses) {
    const bs = (window as any).bootstrap;
    const inst = bs?.Collapse?.getOrCreateInstance
      ? bs.Collapse.getOrCreateInstance(el, { toggle: false })
      : new bs.Collapse(el, { toggle: false });
    if (expand) inst.show();
    else inst.hide();
  }
}

btnCollapseAll?.addEventListener("click", () => setAllAccordions(false));
btnExpandAll?.addEventListener("click", () => setAllAccordions(true));

const client = Router.connect({ url: window.location.origin });

const MEMPOOL_RPC_URL = import.meta.env.RPC_URL;
const MEMPOOL_CONTRACT_ADDRESS = Chain.chains["ethereum-virtual-testnet"].mempool!.address;

function populateRandomOffer() {
  const randomOffer = Offer.random();

  // Populate form fields with random offer data
  const chainIdInput = document.getElementById("createChainId") as HTMLInputElement;
  const loanTokenInput = document.getElementById("createLoanToken") as HTMLInputElement;
  const assetsInput = document.getElementById("createAssets") as HTMLInputElement;
  const rateInput = document.getElementById("createRate") as HTMLInputElement;
  const maturityInput = document.getElementById("createMaturity") as HTMLInputElement;
  const expiryInput = document.getElementById("createExpiry") as HTMLInputElement;
  const offeringInput = document.getElementById("createOffering") as HTMLInputElement;
  const callbackInput = document.getElementById("createCallbackAddress") as HTMLInputElement;
  const collateralAssetInput = document.getElementById("createCollateralAsset") as HTMLInputElement;
  const collateralOracleInput = document.getElementById(
    "createCollateralOracle",
  ) as HTMLInputElement;
  const collateralLltvInput = document.getElementById("createCollateralLltv") as HTMLInputElement;
  const buySelect = document.getElementById("createBuy") as HTMLSelectElement;

  if (chainIdInput) chainIdInput.value = Chain.chains["ethereum-virtual-testnet"].id.toString();
  if (loanTokenInput) loanTokenInput.value = "0x6B175474E89094C44Da98b954EedeAC495271d0F"; // DAI address
  if (assetsInput) assetsInput.value = String(randomOffer.assets);
  if (rateInput) rateInput.value = String(randomOffer.rate);
  if (maturityInput) maturityInput.value = String(randomOffer.maturity);
  if (expiryInput) expiryInput.value = String(randomOffer.expiry);
  if (offeringInput) offeringInput.value = randomOffer.offering;
  if (callbackInput) callbackInput.value = randomOffer.callback.address;
  if (collateralAssetInput && randomOffer.collaterals.length > 0) {
    collateralAssetInput.value = randomOffer.collaterals[0].asset;
  }
  if (collateralOracleInput && randomOffer.collaterals.length > 0) {
    collateralOracleInput.value = randomOffer.collaterals[0].oracle;
  }
  if (collateralLltvInput && randomOffer.collaterals.length > 0) {
    // Convert LLTV from bigint to decimal
    const lltvDecimal = Number(randomOffer.collaterals[0].lltv) / 1e16;
    collateralLltvInput.value = lltvDecimal.toString();
  }
  if (buySelect) buySelect.value = randomOffer.buy ? "Buy(true)" : "Sell(false)";
}

async function submitOffer() {
  hideAlert();
  btnSubmit.disabled = true;
  btnSubmit.innerHTML = '<i class="bi bi-hourglass-split"></i> Submitting...';

  try {
    const chainIdInput = document.getElementById("createChainId") as HTMLInputElement;
    const loanTokenInput = document.getElementById("createLoanToken") as HTMLInputElement;
    const assetsInput = document.getElementById("createAssets") as HTMLInputElement;
    const rateInput = document.getElementById("createRate") as HTMLInputElement;
    const maturityInput = document.getElementById("createMaturity") as HTMLInputElement;
    const expiryInput = document.getElementById("createExpiry") as HTMLInputElement;
    const offeringInput = document.getElementById("createOffering") as HTMLInputElement;
    const callbackInput = document.getElementById("createCallbackAddress") as HTMLInputElement;
    const collateralAssetInput = document.getElementById(
      "createCollateralAsset",
    ) as HTMLInputElement;
    const collateralOracleInput = document.getElementById(
      "createCollateralOracle",
    ) as HTMLInputElement;
    const collateralLltvInput = document.getElementById("createCollateralLltv") as HTMLInputElement;
    const buySelect = document.getElementById("createBuy") as HTMLSelectElement;

    // Validate required fields
    if (!chainIdInput.value || !loanTokenInput.value || !assetsInput.value || !rateInput.value) {
      showAlert("Please fill in all required fields (Chain ID, Loan Token, Assets, Rate)");
      return;
    }

    const tenderlyChain = defineChain({
      id: Number(Chain.chains["ethereum-virtual-testnet"].id),
      name: "Tenderly Mainnet Fork",
      network: "mainnet",
      nativeCurrency: {
        decimals: 18,
        name: "Ether",
        symbol: "ETH",
      },
      rpcUrls: {
        default: {
          http: [MEMPOOL_RPC_URL],
        },
        public: {
          http: [MEMPOOL_RPC_URL],
        },
      },
    });

    const client = createWalletClient({
      account: offeringInput.value as `0x${string}`,
      transport: http(MEMPOOL_RPC_URL),
      chain: tenderlyChain,
    });

    const mempool = Mempool.connect({
      client,
      mempoolAddress: MEMPOOL_CONTRACT_ADDRESS as `0x${string}`,
    });

    // Build offer object using Offer.random() as base and override specific fields
    const baseOffer = Offer.random();
    const offerData = {
      ...baseOffer,
      chainId: BigInt(chainIdInput.value),
      loanToken: loanTokenInput.value as `0x${string}`,
      assets: BigInt(assetsInput.value),
      rate: BigInt(rateInput.value),
      maturity: Number(maturityInput.value) as unknown as Maturity.Maturity,
      expiry: Number(expiryInput.value),
      offering: offeringInput.value as `0x${string}`,
      callback: {
        address: callbackInput.value as `0x${string}`,
        data: "0x" as `0x${string}`,
        gasLimit: 100000n,
      },
      buy: buySelect.value === "Buy(true)",
    };

    // Add collateral if provided
    if (collateralAssetInput.value && collateralOracleInput.value && collateralLltvInput.value) {
      const lltvBigInt = BigInt(Math.round(parseFloat(collateralLltvInput.value) * 1e16));
      offerData.collaterals = [
        {
          asset: collateralAssetInput.value as `0x${string}`,
          oracle: collateralOracleInput.value as `0x${string}`,
          lltv: lltvBigInt as unknown as LLTV.LLTV,
        },
      ];
    } else {
      offerData.collaterals = [];
    }

    await mempool.add({ offer: offerData });

    showAlert("Offer submitted successfully!", "success");

    populateRandomOffer();
  } catch (error: any) {
    showAlert(`Failed to submit offer: ${error.message || error}`);
  } finally {
    btnSubmit.disabled = false;
    btnSubmit.innerHTML = '<i class="bi bi-check-circle"></i> Submit Offer';
  }
}

// Error alert helpers
const alertEl = document.getElementById("alert");
const alertMsgEl = document.getElementById("alertMessage");
function hideAlert() {
  if (!alertEl) return;
  alertEl.classList.add("d-none");
  alertEl.classList.remove("show");
  if (alertMsgEl) alertMsgEl.textContent = "";
}
function showAlert(message: string, type: "danger" | "success" = "danger") {
  if (!alertEl) return;
  if (alertMsgEl) alertMsgEl.textContent = message;
  alertEl.classList.remove("d-none", "alert-danger", "alert-success");
  alertEl.classList.add("show", `alert-${type}`);
}

function parseCommaList(s: string): string[] | undefined {
  const arr = s
    .split(",")
    .map((x) => x.trim())
    .filter(Boolean);
  return arr.length ? arr : undefined;
}

function toBigIntOrUndefined(s: string): bigint | undefined {
  if (!s) return undefined;
  try {
    return BigInt(s);
  } catch {
    return undefined;
  }
}

function toNumberOrUndefined(s: string): number | undefined {
  if (!s) return undefined;
  const n = Number(s);
  return Number.isFinite(n) ? n : undefined;
}

function toLltvOrUndefined(s: string): LLTV.LLTV | undefined {
  if (!s) return undefined;
  // Expect decimal string -> convert to 16-decimal fixed (handled by backend), keep as decimal here
  try {
    const asBig = BigInt(Math.round(parseFloat(s) * 1e16));
    return asBig as unknown as LLTV.LLTV;
  } catch {
    return undefined;
  }
}

function collectGetParams(cursor?: string) {
  const params: any = {};
  const creatorsVal = (document.getElementById("creators") as HTMLInputElement).value;
  const chainsVal = (document.getElementById("chains") as HTMLInputElement).value;
  const loanTokensVal = (document.getElementById("loanTokens") as HTMLInputElement).value;
  const statusVal = (document.getElementById("status") as HTMLInputElement).value;
  const callbackAddressesVal = (document.getElementById("callbackAddresses") as HTMLInputElement)
    .value;

  const sideSel = (document.getElementById("side") as HTMLSelectElement).value as
    | ""
    | "buy"
    | "sell";
  const sortBySel = (document.getElementById("sortBy") as HTMLSelectElement).value as
    | ""
    | "rate"
    | "maturity"
    | "expiry"
    | "amount";
  const sortOrderSel = (document.getElementById("sortOrder") as HTMLSelectElement).value as
    | ""
    | "asc"
    | "desc";

  const minAmountVal = (document.getElementById("minAmount") as HTMLInputElement).value;
  const maxAmountVal = (document.getElementById("maxAmount") as HTMLInputElement).value;
  const minRateVal = (document.getElementById("minRate") as HTMLInputElement).value;
  const maxRateVal = (document.getElementById("maxRate") as HTMLInputElement).value;
  const minMaturityVal = (document.getElementById("minMaturity") as HTMLInputElement).value;
  const maxMaturityVal = (document.getElementById("maxMaturity") as HTMLInputElement).value;
  const minExpiryVal = (document.getElementById("minExpiry") as HTMLInputElement).value;
  const maxExpiryVal = (document.getElementById("maxExpiry") as HTMLInputElement).value;
  const collateralAssetsVal = (document.getElementById("collateralAssets") as HTMLInputElement)
    .value;
  const collateralOraclesVal = (document.getElementById("collateralOracles") as HTMLInputElement)
    .value;
  const collateralTupleVal = (document.getElementById("collateralTuple") as HTMLInputElement).value;
  const minLltvVal = (document.getElementById("minLltv") as HTMLInputElement).value;
  const maxLltvVal = (document.getElementById("maxLltv") as HTMLInputElement).value;

  Object.assign(params, {
    creators: parseCommaList(creatorsVal),
    side: sideSel || undefined,
    chains: parseCommaList(chainsVal)?.map((x) => Number(x)),
    loanTokens: parseCommaList(loanTokensVal),
    status: parseCommaList(statusVal) as OfferStatus[] | undefined,
    callbackAddresses: parseCommaList(callbackAddressesVal),
    minAmount: toBigIntOrUndefined(minAmountVal),
    maxAmount: toBigIntOrUndefined(maxAmountVal),
    minRate: toBigIntOrUndefined(minRateVal),
    maxRate: toBigIntOrUndefined(maxRateVal),
    minMaturity: toNumberOrUndefined(minMaturityVal) as unknown as Maturity.Maturity,
    maxMaturity: toNumberOrUndefined(maxMaturityVal) as unknown as Maturity.Maturity,
    minExpiry: toNumberOrUndefined(minExpiryVal),
    maxExpiry: toNumberOrUndefined(maxExpiryVal),
    collateralAssets: parseCommaList(collateralAssetsVal),
    collateralOracles: parseCommaList(collateralOraclesVal),
    collateralTuple: collateralTupleVal
      ? collateralTupleVal.split("#").map((tuple) => {
          const [asset, oracle, lltvStr] = tuple.split(":");
          const lltv = toLltvOrUndefined(ltvSafe(ltvNormalize(lltvStr ?? "")));
          return { asset, oracle: oracle || undefined, lltv };
        })
      : undefined,
    minLltv: toLltvOrUndefined(minLltvVal),
    maxLltv: toLltvOrUndefined(maxLltvVal),
    sortBy: sortBySel || undefined,
    sortOrder: sortOrderSel || undefined,
    limit: toNumberOrUndefined(limitEl.value) ?? 20,
  });

  // Only add cursor if it has a valid value
  if (cursor && cursor.trim() !== "") {
    params.cursor = cursor;
  }

  return params;
}

function collectMatchParams(cursor?: string) {
  const params: any = {};

  const sideVal = (document.getElementById("matchSide") as HTMLSelectElement).value;
  const chainIdVal = (document.getElementById("matchChainId") as HTMLInputElement).value;
  const rateVal = (document.getElementById("matchRate") as HTMLInputElement).value;
  const loanTokenVal = (document.getElementById("matchLoanToken") as HTMLInputElement).value;
  const creatorVal = (document.getElementById("matchCreator") as HTMLInputElement).value;
  const statusVal = (document.getElementById("matchStatus") as HTMLInputElement).value;
  const maturityVal = (document.getElementById("matchMaturity") as HTMLInputElement).value;
  const minMaturityVal = (document.getElementById("matchMinMaturity") as HTMLInputElement).value;
  const maxMaturityVal = (document.getElementById("matchMaxMaturity") as HTMLInputElement).value;
  const collateralAssetVal = (document.getElementById("matchCollateralAsset") as HTMLInputElement)
    .value;
  const collateralOracleVal = (document.getElementById("matchCollateralOracle") as HTMLInputElement)
    .value;
  const collateralLltvVal = (document.getElementById("matchCollateralLltv") as HTMLInputElement)
    .value;

  Object.assign(params, {
    side: sideVal || undefined,
    chainId: toNumberOrUndefined(chainIdVal),
    rate: toBigIntOrUndefined(rateVal),
    loanToken: loanTokenVal || undefined,
    creator: creatorVal || undefined,
    status: parseCommaList(statusVal) as OfferStatus[] | undefined,
    maturity: toNumberOrUndefined(maturityVal) as unknown as Maturity.Maturity,
    minMaturity: toNumberOrUndefined(minMaturityVal) as unknown as Maturity.Maturity,
    maxMaturity: toNumberOrUndefined(maxMaturityVal) as unknown as Maturity.Maturity,
    limit: toNumberOrUndefined(limitEl.value) ?? 20,
  });

  // Add collateral if provided
  if (collateralAssetVal || collateralOracleVal || collateralLltvVal) {
    params.collaterals = [
      {
        asset: collateralAssetVal || "",
        oracle: collateralOracleVal || "",
        lltv: toLltvOrUndefined(collateralLltvVal) || (0n as unknown as LLTV.LLTV),
      },
    ];
  }

  // Only add cursor if it has a valid value
  if (cursor && cursor.trim() !== "") {
    params.cursor = cursor;
  }

  return params;
}

function ltvNormalize(s: string) {
  return s.trim();
}
function ltvSafe(s: string) {
  return s;
}

function formatAddress(a?: string, len = 10) {
  if (!a) return "";
  if (a.length <= 2 * len) return a;
  return `${a.slice(0, len)}…${a.slice(-len)}`;
}

function formatNumber(n?: bigint | number | string) {
  if (n === undefined || n === null) return "";
  const str = String(n);
  if (str.length <= 12) return str;
  return `${str.slice(0, 8)}…${str.slice(-4)}`;
}

function formatTimestamp(timestamp?: number | string) {
  if (!timestamp) return "";
  const date = new Date(Number(timestamp) * 1000);
  return `${date.toLocaleDateString()} ${date.toLocaleTimeString()}`;
}

function createTooltip(element: HTMLElement, content: string) {
  if (content.length > 20) {
    element.setAttribute("data-bs-toggle", "tooltip");
    element.setAttribute("data-bs-placement", "top");
    element.setAttribute("data-bs-title", content);
    element.classList.add("expandable");
  }
}

async function copyToClipboard(text: string) {
  try {
    await navigator.clipboard.writeText(text);
  } catch {
    const ta = document.createElement("textarea");
    ta.value = text;
    ta.style.position = "fixed";
    ta.style.opacity = "0";
    document.body.appendChild(ta);
    ta.focus();
    ta.select();
    try {
      document.execCommand("copy");
    } finally {
      document.body.removeChild(ta);
    }
  }
}

function makeCopyableCell(td: HTMLTableCellElement, fullText: string) {
  if (!fullText) return;
  td.classList.add("copyable");
  const icon = document.createElement("i");
  icon.className = "bi bi-clipboard copy-btn";
  icon.title = "Copy";
  icon.setAttribute("role", "button");
  icon.addEventListener("click", async (e) => {
    e.stopPropagation();
    await copyToClipboard(fullText);
    icon.classList.remove("bi-clipboard");
    icon.classList.add("bi-clipboard-check", "text-success");
    icon.title = "Copied";
    setTimeout(() => {
      icon.classList.remove("bi-clipboard-check", "text-success");
      icon.classList.add("bi-clipboard");
      icon.title = "Copy";
    }, 1200);
  });
  td.append(icon);
}

function getStatusBadgeClass(status: string) {
  switch (status) {
    case "valid":
      return "bg-success";
    case "invalid":
      return "bg-danger";
    case "consumed":
      return "bg-warning";
    case "expired":
      return "bg-secondary";
    default:
      return "bg-light text-dark";
  }
}

function renderOffers(offers: any[]) {
  offersTBody.innerHTML = "";

  offers.forEach((o, idx) => {
    const tr = document.createElement("tr");

    // Row number
    const td1 = document.createElement("td");
    td1.className = "number-cell";
    td1.textContent = String(idx + 1);
    tr.append(td1);

    // Status
    const td2 = document.createElement("td");
    const statusText = String(o.status ?? "");
    const statusBadge = document.createElement("span");
    statusBadge.className = `badge status-badge ${getStatusBadgeClass(statusText)}`;
    statusBadge.textContent = statusText;
    td2.append(statusBadge);
    makeCopyableCell(td2, statusText);
    tr.append(td2);

    // Consumed
    const td3 = document.createElement("td");
    td3.className = "number-cell";
    const consumedText = String(o.consumed ?? 0n);
    td3.textContent = formatNumber(consumedText);
    makeCopyableCell(td3, consumedText);
    createTooltip(td3, consumedText);
    tr.append(td3);

    // Type (buy/sell)
    const td4 = document.createElement("td");
    const typeText = o.offer?.buy ? "buy" : "sell";
    td4.textContent = typeText;
    makeCopyableCell(td4, typeText);
    tr.append(td4);

    // Chain ID
    const td5 = document.createElement("td");
    td5.className = "number-cell";
    const chainText = String(o.offer?.chainId ?? "");
    td5.textContent = chainText;
    makeCopyableCell(td5, chainText);
    tr.append(td5);

    // Loan Token
    const td6 = document.createElement("td");
    td6.className = "address-cell";
    const loanTokenText = formatAddress(o.offer?.loanToken);
    td6.textContent = loanTokenText;
    makeCopyableCell(td6, o.offer?.loanToken || "");
    createTooltip(td6, o.offer?.loanToken || "");
    tr.append(td6);

    // Assets
    const td7 = document.createElement("td");
    td7.className = "number-cell";
    const assetsText = String(o.offer?.assets ?? "");
    td7.textContent = formatNumber(assetsText);
    makeCopyableCell(td7, assetsText);
    createTooltip(td7, assetsText);
    tr.append(td7);

    // Rate
    const td8 = document.createElement("td");
    td8.className = "number-cell";
    const rateText = String(o.offer?.rate ?? "");
    td8.textContent = formatNumber(rateText);
    makeCopyableCell(td8, rateText);
    createTooltip(td8, rateText);
    tr.append(td8);

    // Maturity
    const td9 = document.createElement("td");
    td9.className = "number-cell";
    const maturityText = String(o.offer?.maturity ?? "");
    td9.textContent = formatNumber(maturityText);
    makeCopyableCell(td9, maturityText);
    createTooltip(td9, maturityText);
    tr.append(td9);

    // Expiry
    const td10 = document.createElement("td");
    td10.className = "number-cell";
    const expiryText = formatTimestamp(o.offer?.expiry);
    td10.textContent = expiryText;
    makeCopyableCell(td10, String(o.offer?.expiry ?? ""));
    tr.append(td10);

    // Offering
    const td11 = document.createElement("td");
    td11.className = "address-cell";
    const offeringText = formatAddress(o.offer?.offering);
    td11.textContent = offeringText;
    makeCopyableCell(td11, o.offer?.offering || "");
    createTooltip(td11, o.offer?.offering || "");
    tr.append(td11);

    // Callback Address
    const td12 = document.createElement("td");
    td12.className = "address-cell";
    const callbackText = formatAddress(o.offer?.callback?.address);
    td12.textContent = callbackText;
    makeCopyableCell(td12, o.offer?.callback?.address || "");
    createTooltip(td12, o.offer?.callback?.address || "");
    tr.append(td12);

    // Collaterals
    const td13 = document.createElement("td");
    const collateralsText = (o.offer?.collaterals || [])
      .map(
        (c: any) => `${formatAddress(c.asset, 6)}:${formatAddress(c.oracle, 6)}:${String(c.lltv)}`,
      )
      .join("\n");
    td13.textContent = collateralsText;
    if (collateralsText.length > 20) {
      createTooltip(td13, collateralsText);
    }
    makeCopyableCell(td13, collateralsText);
    tr.append(td13);

    // Hash
    const td14 = document.createElement("td");
    td14.className = "address-cell";
    const hashText = formatAddress(o.offer?.hash, 12);
    td14.textContent = hashText;
    makeCopyableCell(td14, o.offer?.hash || "");
    createTooltip(td14, o.offer?.hash || "");
    tr.append(td14);

    offersTBody.append(tr);
  });

  // Initialize tooltips
  const tooltipTriggerList = document.querySelectorAll('[data-bs-toggle="tooltip"]');
  tooltipTriggerList.forEach((el) => {
    new (window as any).bootstrap.Tooltip(el);
  });
}

async function fetchOffers(cursor?: string, pushHistory = true) {
  if (statusEl) statusEl.textContent = "";
  hideAlert();
  btnFetch.classList.add("loading");

  try {
    if (cursor) {
      try {
        const decoded = JSON.parse(atob(cursor.replace(/-/g, "+").replace(/_/g, "/")));
        if (decoded.sort === "expiry" && decoded.expiry) {
          // no-op: retained parsing in case future logic needs it
        }
      } catch (_e) {
        // ignore
      }
    }

    let offers: any[];
    let nextCursor: string | null = null;

    if (currentMode === "get") {
      const params = collectGetParams(cursor);
      const result = await client.get(params);
      offers = result.offers;
      nextCursor = result.cursor;
    } else {
      const params = collectMatchParams(cursor);
      const result = await client.match(params);
      offers = result.offers;
      nextCursor = result.cursor;
    }

    renderOffers(offers);
    hideAlert();

    if (pushHistory && cursor && cursor.trim() !== "") {
      cursorStack.push(cursor);
    } else if (pushHistory && cursorStack.length === 0) {
      cursorStack.push("initial");
    }

    currentCursor = nextCursor;
    btnNext.disabled = !currentCursor;
    btnPrev.disabled = cursorStack.length <= 1;
    if (statusEl) statusEl.textContent = "";
  } catch (_e: any) {
    if (statusEl) statusEl.textContent = "";
    showAlert(String(_e?.message ?? _e ?? "Unknown error"));
  } finally {
    btnFetch.classList.remove("loading");
  }
}

btnFetch.addEventListener("click", async () => {
  await fetchOffers(undefined, true);
});

btnNext.addEventListener("click", async () => {
  if (!currentCursor) {
    return;
  }
  await fetchOffers(currentCursor, true);
});

btnPrev.addEventListener("click", async () => {
  if (cursorStack.length <= 1) {
    return;
  }
  cursorStack.pop();
  const prev = cursorStack[cursorStack.length - 1];
  await fetchOffers(prev === "initial" ? undefined : prev, false);
});

// Auto-load first page
fetchOffers();
