"use client";

import { use, useState } from "react";
import Link from "next/link";
import {
  BarChart,
  Bar,
  XAxis,
  YAxis,
  Tooltip,
  ReferenceLine,
  ResponsiveContainer,
} from "recharts";
import { useTripAnalytics, useTrip, useTransactions, useParticipantBalances, useTrips } from "@/lib/hooks";
import { CreateTripModal } from "@/components/create-trip-modal";
import { formatCategory } from "@/lib/categories";
import { CHART, TOOLTIP_STYLE } from "@/lib/category-colors";

function fmtDate(d: string | null) {
  if (!d) return null;
  return new Date(d).toLocaleDateString("en-AU", { day: "2-digit", month: "short", year: "numeric" });
}

function fmt(n: number) {
  return `$${n.toLocaleString("en-AU", { minimumFractionDigits: 2, maximumFractionDigits: 2 })}`;
}

/**
 * A labelled horizontal magnitude bar.
 *
 * One hue for every row, never a colour per category. The category name is right
 * there as a direct label, so a hue per row would double-encode identity the label
 * already carries — and the app's 27-colour CATEGORY_COLORS set fails CVD
 * separation on this surface anyway (validated: `other` vs `shopping` at ΔE 5.0
 * protan, below the floor). Length carries the magnitude; that is the whole job.
 */
function BarRow({
  label,
  amount,
  count,
  max,
  sub,
}: {
  label: string;
  amount: number;
  count: number;
  max: number;
  sub?: string;
}) {
  const pct = max > 0 ? Math.max((Math.abs(amount) / max) * 100, 0.6) : 0;
  return (
    <div className="group grid grid-cols-[minmax(0,1fr)_auto] gap-x-3 gap-y-1 items-baseline">
      <span className="text-sm text-zinc-300 truncate" title={label}>{label}</span>
      <span className="text-sm font-mono tabular-nums text-zinc-200">{fmt(amount)}</span>
      <div className="col-span-2 flex items-center gap-2">
        <div className="h-1.5 flex-1 bg-zinc-800/70 overflow-hidden rounded-sm">
          <div
            className="h-full transition-[width] duration-500 motion-reduce:transition-none"
            style={{ width: `${pct}%`, background: CHART.accent, borderRadius: "0 4px 4px 0" }}
          />
        </div>
        <span className="text-[11px] text-zinc-600 tabular-nums w-16 text-right shrink-0">
          {sub ?? `${count} ${count === 1 ? "charge" : "charges"}`}
        </span>
      </div>
    </div>
  );
}

/** A section heading that states what the section is FOR, not just what it holds. */
function SectionHead({ title, note }: { title: string; note: string }) {
  return (
    <div className="mb-4">
      <h3 className="text-sm font-display text-zinc-100">{title}</h3>
      <p className="text-xs text-zinc-500 mt-0.5 leading-relaxed">{note}</p>
    </div>
  );
}

function DailyTooltip({ active, payload, label }: { active?: boolean; payload?: { value: number }[]; label?: string }) {
  if (!active || !payload?.length) return null;
  return (
    <div style={TOOLTIP_STYLE} className="p-2.5 text-xs">
      <p className="text-zinc-400 mb-1">
        {label ? new Date(label).toLocaleDateString("en-AU", { day: "2-digit", month: "short", year: "numeric" }) : ""}
      </p>
      <p className="text-zinc-100 font-medium">${Number(payload[0].value).toFixed(2)}</p>
    </div>
  );
}

export default function TripDetailPage({ params }: { params: Promise<{ id: string }> }) {
  const { id } = use(params);
  const tripId = Number(id);

  const { data: analytics, isLoading } = useTripAnalytics(tripId);
  const { data: trip } = useTrip(tripId);
  const [tab, setTab] = useState<"overview" | "transactions">("overview");
  const [editModal, setEditModal] = useState(false);

  // A trip is all the expenses on one trip, so a participant sees every row on
  // it, not only their own. The server re-checks participation — this flag is a
  // request, not a grant.
  const { data: txData } = useTransactions({ trip_id: id, limit: 500, trip_all_rows: true });

  // Unscoped, deliberately: the trip figure alone cannot tell you whether to pay
  // anyone, because a trip whose payment over-covered it reads negative while the
  // payer is still in debt overall. This is the number to act on.
  const { data: balances = [] } = useParticipantBalances();
  // For the only cross-trip figure worth quoting: the daily rate.
  const { data: allTrips = [] } = useTrips();

  if (isLoading || !analytics) {
    return (
      <div className="space-y-6">
        <div className="h-32 bg-zinc-900 rounded-2xl animate-pulse" />
        <div className="grid grid-cols-2 sm:grid-cols-4 gap-4">
          {[...Array(4)].map((_, i) => <div key={i} className="h-24 bg-zinc-900 rounded-xl animate-pulse" />)}
        </div>
      </div>
    );
  }

  const {
    total_spend, transaction_count, num_days, daily_spend, tag_breakdown, participant_splits,
    phases, committed_merchants, on_ground_categories, on_ground_daily,
  } = analytics;
  const t = analytics.trip;

  const committed = Number(phases.committed);
  const onGround = Number(phases.on_ground);
  const total = committed + onGround;
  const committedPct = total > 0 ? (committed / total) * 100 : 0;
  const onGroundDaily = Number(on_ground_daily);
  // No start_date means no knowable departure, so there is no split to draw — the
  // query already folds everything into on-ground in that case.
  const hasPhases = Boolean(t.start_date);

  const maxCommitted = committed_merchants[0]?.amount ?? 1;
  const maxOnGround = on_ground_categories[0]?.amount ?? 1;
  const meanDaily = daily_spend.length
    ? daily_spend.reduce((s, d) => s + Number(d.amount), 0) / daily_spend.length
    : 0;

  // Where this trip's daily burn sits against the others. $677.88/day in Europe
  // against $83.39 in Auckland is the kind of thing a single trip page can never
  // say on its own, and it is the only figure here that is comparable at all —
  // totals are not, because trips differ in length.
  const dayRateRank = (() => {
    const rated = allTrips
      .filter((x) => x.start_date && x.end_date && Number(x.total_spend) > 0)
      .map((x) => {
        const days = Math.max(1, Math.round(
          (new Date(x.end_date!).getTime() - new Date(x.start_date!).getTime()) / 86400000
        ) + 1);
        return { id: x.id, rate: Number(x.total_spend) / days };
      })
      .sort((a, b) => b.rate - a.rate);
    if (rated.length < 2) return null;
    const idx = rated.findIndex((x) => x.id === t.id);
    if (idx === -1) return null;
    if (idx === 0) return `your priciest day-to-day of ${rated.length} trips`;
    if (idx === rated.length - 1) return `your cheapest day-to-day of ${rated.length} trips`;
    return `${idx + 1}${["st", "nd", "rd"][idx] ?? "th"} priciest of ${rated.length} trips`;
  })();

  const dateRange = t.start_date && t.end_date
    ? `${fmtDate(t.start_date)} – ${fmtDate(t.end_date)}`
    : t.start_date
    ? `From ${fmtDate(t.start_date)}`
    : null;

  return (
    <div className="space-y-6">
      {/* Hero */}
      <div
        className="relative rounded-2xl overflow-hidden p-6"
        style={{
          background: `linear-gradient(135deg, ${t.color}28 0%, #18181b 60%)`,
          borderLeft: `3px solid ${t.color}`,
        }}
      >
        <div className="flex items-start justify-between gap-4 flex-wrap">
          <div>
            <div className="flex items-center gap-2 mb-1">
              <Link href="/trips" className="text-xs text-zinc-500 hover:text-zinc-300 transition-colors">
                ← Trips
              </Link>
            </div>
            <h1 className="text-2xl font-bold">{t.name}</h1>
            {dateRange && <p className="text-sm text-zinc-400 mt-1">{dateRange}</p>}
            {t.description && <p className="text-sm text-zinc-500 mt-1">{t.description}</p>}
          </div>
          <button
            onClick={() => setEditModal(true)}
            className="px-3 py-1.5 bg-zinc-800/80 hover:bg-zinc-700 text-zinc-300 rounded-lg text-sm transition-colors flex-shrink-0"
          >
            Edit Trip
          </button>
        </div>
      </div>

      {/* ── Signature: the two economies of a trip ──
          The page's thesis, and the answer to "travel is 60% and tells me
          nothing". A trip is paid for twice — once in bookings locked in months
          ahead, once in daily spending on the ground — and every category except
          travel belongs wholly to the second. Showing the ratio first makes the
          rest of the page legible; showing a lone total never did. */}
      <div className="bg-zinc-900 border border-zinc-800 rounded-xl p-5 sm:p-6">
        <div className="flex flex-wrap items-end justify-between gap-x-8 gap-y-3">
          <div>
            <p className="text-[11px] uppercase tracking-[0.16em] text-zinc-500">What the trip cost</p>
            {/* Sans, not the display face, and proportional figures — a serif or
                tabular-nums hero reads as decoration at this size. */}
            <p className="text-4xl font-semibold text-zinc-50 mt-1 leading-none">{fmt(Number(total_spend))}</p>
            <p className="text-xs text-zinc-500 mt-1.5">
              all payers, net of refunds · {transaction_count} charges over {num_days} days
            </p>
          </div>
          {hasPhases && (
            <div className="text-right">
              <p className="text-[11px] uppercase tracking-[0.16em] text-zinc-500">On the ground</p>
              <p className="text-2xl font-semibold text-zinc-100 mt-1 leading-none">
                {fmt(onGroundDaily)}<span className="text-sm font-normal text-zinc-500"> / day</span>
              </p>
              {dayRateRank && <p className="text-xs text-zinc-500 mt-1.5">{dayRateRank}</p>}
            </div>
          )}
        </div>

        {hasPhases && total > 0 && (
          <div className="mt-6">
            {/* Two ordinal steps of one hue, validated against this surface, with a
                2px gap so the boundary is a real edge rather than a colour change.
                Both segments are direct-labelled, so no legend is needed. */}
            <div className="flex gap-[2px] h-2.5" role="img"
              aria-label={`${fmt(committed)} committed before departure, ${fmt(onGround)} spent on the ground`}>
              <div className="rounded-l-sm rounded-r-[1px]" style={{ width: `${committedPct}%`, background: "#7c4820" }} />
              <div className="rounded-r-sm rounded-l-[1px]" style={{ width: `${100 - committedPct}%`, background: "#d28a47" }} />
            </div>
            <div className="flex flex-wrap justify-between gap-x-6 gap-y-2 mt-3">
              <div>
                <p className="text-sm text-zinc-200">
                  <span className="inline-block w-2 h-2 rounded-sm mr-1.5 align-middle" style={{ background: "#7c4820" }} />
                  {fmt(committed)}
                  <span className="text-zinc-500"> committed{t.start_date ? ` before ${fmtDate(t.start_date)}` : ""}</span>
                </p>
                <p className="text-[11px] text-zinc-600 mt-0.5 ml-3.5">
                  {phases.committed_count} bookings · {committedPct.toFixed(0)}% of the trip
                </p>
              </div>
              <div className="sm:text-right">
                <p className="text-sm text-zinc-200">
                  <span className="inline-block w-2 h-2 rounded-sm mr-1.5 align-middle" style={{ background: "#d28a47" }} />
                  {fmt(onGround)}
                  <span className="text-zinc-500"> spent on the ground</span>
                </p>
                <p className="text-[11px] text-zinc-600 mt-0.5 ml-3.5 sm:ml-0">
                  {phases.on_ground_count} charges · {(100 - committedPct).toFixed(0)}% of the trip
                </p>
              </div>
            </div>
            {committed < 1 && (
              // Europe — Sonu + Sunny sits at $184.84 committed against Europe 2026's
              // $22,050.51, because the flights and stays for both legs were filed on
              // the first trip. Worth saying, or the ratio reads as missing data.
              <p className="text-[11px] text-zinc-600 mt-3 pt-3 border-t border-zinc-800/70">
                Almost nothing was booked before this trip started — its flights and
                stays are likely filed against another trip.
              </p>
            )}
          </div>
        )}
      </div>

      {/* Tab bar */}
      <div className="flex gap-0 border-b border-zinc-800">
        {(["overview", "transactions"] as const).map((tabName) => (
          <button
            key={tabName}
            onClick={() => setTab(tabName)}
            className={`px-5 py-2.5 text-sm capitalize transition-colors border-b-2 -mb-px ${
              tab === tabName
                ? "border-current text-white font-medium"
                : "border-transparent text-zinc-500 hover:text-zinc-300"
            }`}
            style={tab === tabName ? { borderColor: t.color } : {}}
          >
            {tabName}
          </button>
        ))}
      </div>

      {tab === "overview" && (
        <div className="space-y-5">
          {/* Daily spend */}
          {daily_spend.length > 0 && (
            <div className="bg-zinc-900 border border-zinc-800 rounded-xl p-5">
              <div className="flex items-start justify-between gap-4 flex-wrap mb-4">
                <SectionHead
                  title="Day by day"
                  note="Every day money moved, bookings included — the tall early bars are usually the flights."
                />
                <span className="text-[11px] text-zinc-500 shrink-0 flex items-center gap-1.5">
                  <span className="w-4 border-t border-dashed inline-block" style={{ borderColor: CHART.axis }} />
                  mean {fmt(meanDaily)}
                </span>
              </div>
              <ResponsiveContainer width="100%" height={200}>
                <BarChart data={daily_spend} margin={{ top: 4, right: 8, bottom: 0, left: 8 }}>
                  <XAxis
                    dataKey="date"
                    tick={{ fill: "#71717a", fontSize: 11 }}
                    axisLine={false}
                    tickLine={false}
                    tickFormatter={(v) => new Date(v).toLocaleDateString("en-AU", { day: "2-digit", month: "short" })}
                    interval="preserveStartEnd"
                  />
                  <YAxis
                    tick={{ fill: "#71717a", fontSize: 11 }}
                    axisLine={false}
                    tickLine={false}
                    tickFormatter={(v) => `$${v}`}
                    width={52}
                  />
                  <Tooltip content={<DailyTooltip />} cursor={{ fill: "#27272a" }} />
                  {/* Same axis, same unit — a mean line, not a second scale. */}
                  {meanDaily > 0 && (
                    <ReferenceLine y={meanDaily} stroke={CHART.axis} strokeDasharray="3 3" strokeWidth={1} />
                  )}
                  <Bar dataKey="amount" fill={CHART.accent} radius={[4, 4, 0, 0]} maxBarSize={40} />
                </BarChart>
              </ResponsiveContainer>
            </div>
          )}

          {/* ── The two phases, each on the axis that carries information ──
              This pairing is the fix for the travel problem. Before departure
              every row is a flight, a stay or a rail ticket, so `travel` is 99%
              of it and category says nothing — merchant is what distinguishes
              Agoda $4,490 from Air India $3,454. After departure travel drops to
              a peer among dining, transport and groceries, and category is
              finally worth charting. Same rows, two axes, chosen per phase. */}
          <div className="grid grid-cols-1 lg:grid-cols-2 gap-5 items-start">
            {hasPhases && committed_merchants.length > 0 && (
              <div className="bg-zinc-900 border border-zinc-800 rounded-xl p-5">
                <SectionHead
                  title="Booked ahead"
                  note={`Locked in before ${t.start_date ? fmtDate(t.start_date) : "departure"}. It is all flights and stays here, so the merchant is what tells them apart — not the category.`}
                />
                <div className="space-y-3.5">
                  {committed_merchants.map((m) => (
                    <BarRow
                      key={m.merchant}
                      label={m.merchant || "Unknown"}
                      amount={Number(m.amount)}
                      count={m.count}
                      max={maxCommitted}
                    />
                  ))}
                </div>
              </div>
            )}

            {on_ground_categories.length > 0 && (
              <div className="bg-zinc-900 border border-zinc-800 rounded-xl p-5">
                <SectionHead
                  title={hasPhases ? "On the ground" : "By category"}
                  note={
                    hasPhases
                      ? "Day-to-day spending once you arrived. With the bookings taken out, travel sits among its peers instead of swamping them."
                      : "This trip has no start date, so there is no departure to split on."
                  }
                />
                <div className="space-y-3.5">
                  {on_ground_categories.map((c) => (
                    <BarRow
                      key={c.category}
                      label={formatCategory(c.category)}
                      amount={Number(c.amount)}
                      count={c.count}
                      max={maxOnGround}
                      sub={onGround > 0 ? `${((Number(c.amount) / onGround) * 100).toFixed(0)}%` : undefined}
                    />
                  ))}
                </div>
              </div>
            )}
          </div>

          {/* Tag breakdown */}
          {tag_breakdown.length > 0 && (
            <div className="bg-zinc-900 border border-zinc-800 rounded-xl p-5">
              <h3 className="text-sm font-medium mb-3">By Tag</h3>
              <div className="flex flex-wrap gap-2">
                {tag_breakdown.map((tag) => (
                  <div
                    key={tag.tag_id}
                    className="flex items-center gap-2 px-3 py-2 rounded-lg border border-zinc-800 bg-zinc-800/50"
                  >
                    <span className="w-2.5 h-2.5 rounded-full flex-shrink-0" style={{ backgroundColor: tag.color }} />
                    <span className="text-sm font-medium">{tag.name}</span>
                    <span className="text-xs text-zinc-500">{tag.count} txns</span>
                    <span className="text-sm font-mono tabular-nums text-zinc-300">${Number(tag.amount).toFixed(2)}</span>
                  </div>
                ))}
              </div>
            </div>
          )}

          {/* Participant splits */}
          {participant_splits.length > 0 && (
            <div className="bg-zinc-900 border border-zinc-800 rounded-xl overflow-hidden">
              <div className="px-5 py-3 border-b border-zinc-800 flex items-center justify-between">
                <h3 className="text-sm font-medium">Participant Splits</h3>
                <Link href="/shared" className="text-xs text-zinc-500 hover:text-zinc-300">
                  View in Shared →
                </Link>
              </div>
              <table className="w-full text-sm">
                <thead>
                  <tr className="border-b border-zinc-800">
                    {["Person", "This trip", "How it adds up", "Overall balance"].map((h) => (
                      <th
                        key={h}
                        className={`px-5 py-2.5 text-xs text-zinc-500 font-medium ${h === "Person" || h === "How it adds up" ? "text-left" : "text-right"}`}
                      >
                        {h}
                      </th>
                    ))}
                  </tr>
                </thead>
                <tbody>
                  {/* One net figure per person — but a negative one is NOT a bill.
                      This is the distinction the page got wrong twice.

                      A payment is allocated to a scope as a lump sum, and the
                      grouped-payment allocation assigned each trip enough to clear
                      the payer's GROSS share. So when the other side of the trip is
                      netted off, a fully-paid trip goes negative by exactly the
                      amount the payer over-covered: Europe reads -$802.75 because
                      Sonu paid $8,004.04 against a net share of $7,201.30.

                      That surplus is not a debt the viewer must settle. It is
                      already carried in the overall balance — Sonu still owes
                      $5,313.38 overall — so "you owe them" was flatly wrong. Scope
                      nets sum to the overall figure; a negative here just means this
                      scope was over-covered and the excess sits in another.

                      So: negative WITH a payment into the scope is an overpayment,
                      and the overall column is where the actionable number lives.
                      Negative with NO payment is genuinely owed, because then the
                      viewer's share of the other person's spending simply exceeds
                      theirs. All three of today's negatives are the former.

                      Sign convention matches Shared: positive means they owe you. */}
                  {participant_splits.map((p) => {
                    const owedGross = Number(p.owed_gross);
                    const paidToMe = Number(p.paid_to_me);
                    const iOweGross = Number(p.i_owe_gross);
                    const paidByMe = Number(p.paid_by_me);
                    const net = Number(p.owed) - Number(p.i_owe);
                    const square = Math.abs(net) < 0.005;
                    const overpaid = net < -0.005 && paidToMe > 0.005;
                    const unconverted = p.unconverted_count + p.i_owe_unconverted_count;
                    const parts = [
                      owedGross > 0.005 ? `their share ${fmt(owedGross)}` : null,
                      paidToMe > 0.005 ? `they paid ${fmt(paidToMe)}` : null,
                      iOweGross > 0.005 ? `your share of their spend ${fmt(iOweGross)}` : null,
                      paidByMe > 0.005 ? `you paid ${fmt(paidByMe)}` : null,
                    ].filter(Boolean);
                    const overall = balances.find((b) => b.id === p.participant_id);
                    const overallNet = overall ? Number(overall.total_owed) : null;
                    return (
                      <tr key={p.participant_id} className="border-b border-zinc-800/50 last:border-0">
                        <td className="px-5 py-3 font-medium">{p.name}</td>
                        <td className="px-5 py-3 text-right tabular-nums font-mono whitespace-nowrap">
                          <span className={square ? "text-zinc-500" : net > 0 ? "text-amber-400" : overpaid ? "text-emerald-400" : "text-blue-400"}>
                            ${Math.abs(net).toFixed(2)}
                          </span>
                          <span className="block text-[11px] text-zinc-500 mt-0.5 font-sans">
                            {square
                              ? "all square"
                              : net > 0
                                ? "still owed"
                                : overpaid
                                  ? "covered — they paid over"
                                  : "you owe them"}
                          </span>
                          {unconverted > 0 && (
                            <span className="block text-[11px] text-amber-500/80 mt-0.5 font-sans">
                              approx · {unconverted} unconverted
                            </span>
                          )}
                        </td>
                        <td className="px-5 py-3 text-[11px] text-zinc-500 leading-relaxed">
                          {parts.length ? parts.join(" · ") : "no split activity on this trip"}
                          {overpaid && (
                            <span className="block text-emerald-500/80 mt-0.5">
                              trip covered; the {fmt(Math.abs(net))} surplus sits on the overall balance,
                              not owing to them
                            </span>
                          )}
                        </td>
                        {/* The only figure anyone should act on. Without it a
                            over-covered scope reads as "pay them" when they are
                            still in debt to you overall. */}
                        <td className="px-5 py-3 text-right tabular-nums font-mono whitespace-nowrap">
                          {overallNet === null ? (
                            <span className="text-zinc-600 text-[11px] font-sans">—</span>
                          ) : (
                            <>
                              <span className={Math.abs(overallNet) < 0.005 ? "text-zinc-500" : overallNet > 0 ? "text-amber-400" : "text-blue-400"}>
                                ${Math.abs(overallNet).toFixed(2)}
                              </span>
                              <span className="block text-[11px] text-zinc-500 mt-0.5 font-sans">
                                {Math.abs(overallNet) < 0.005 ? "all square" : overallNet > 0 ? "owes you" : "you owe them"}
                              </span>
                            </>
                          )}
                        </td>
                      </tr>
                    );
                  })}
                </tbody>
              </table>
              {/* This note used to say a per-trip figure could not be computed,
                  because payments carried no trip attribution. Migration 0022 added
                  split_payments.trip_id, so it can and now does — the figures above
                  are net of payments scoped to this trip. What the note has to say
                  instead is which payments are NOT in them. */}
              <p className="px-5 py-2.5 text-xs text-zinc-500 border-t border-zinc-800">
                <strong className="font-medium text-zinc-400">This trip</strong> is their
                share of what you paid, less what they paid you, less your share of what
                they paid. A payment is allocated to a trip as a lump sum, so one that
                covered someone&rsquo;s full share leaves this column negative by whatever
                it over-covered — that surplus is carried in{" "}
                <strong className="font-medium text-zinc-400">Overall balance</strong>, and
                is not money owed to them. Settle against the overall figure, never a
                single trip.
                <br />
                Only payments <em className="not-italic text-zinc-400">scoped to this
                trip</em> count here, so a debt settled by a payment left on the household
                tab still reads as outstanding — set the scope when recording one.
                See <Link href="/shared" className="text-zinc-400 hover:text-zinc-200 underline">Shared</Link> for
                the full picture.
              </p>
            </div>
          )}

          {on_ground_categories.length === 0 && committed_merchants.length === 0 && daily_spend.length === 0 && (
            <div className="text-center py-12 text-zinc-600">
              <p className="text-sm">No transactions assigned to this trip yet.</p>
              <Link href="/transactions" className="text-indigo-400 hover:text-indigo-300 text-sm mt-1 inline-block">
                Go to Transactions to assign some →
              </Link>
            </div>
          )}
        </div>
      )}

      {tab === "transactions" && (
        <div>
          {!txData?.data.length ? (
            <div className="text-center py-12 text-zinc-600">
              <p className="text-sm">No transactions assigned to this trip yet.</p>
              <Link href="/transactions" className="text-indigo-400 hover:text-indigo-300 text-sm mt-1 inline-block">
                Go to Transactions to assign some →
              </Link>
            </div>
          ) : (
            <div className="border border-zinc-800 rounded-xl overflow-hidden">
              <table className="w-full text-sm">
                <thead>
                  <tr className="border-b border-zinc-800 bg-zinc-900/60">
                    {["Date", "Description", "Merchant", "Category", "Amount"].map((h) => (
                      <th
                        key={h}
                        className={`px-4 py-2.5 text-xs text-zinc-500 font-medium ${h === "Amount" ? "text-right" : "text-left"}`}
                      >
                        {h}
                      </th>
                    ))}
                  </tr>
                </thead>
                <tbody>
                  {txData.data.map((tx) => (
                    <tr key={tx.id} className="border-b border-zinc-800/40 last:border-0 hover:bg-zinc-900/40 transition-colors">
                      <td className="px-4 py-2.5 text-xs text-zinc-400 whitespace-nowrap">
                        {new Date(tx.transaction_date).toLocaleDateString("en-AU", { day: "2-digit", month: "short" })}
                      </td>
                      <td className="px-4 py-2.5 max-w-xs truncate text-zinc-300">{tx.description}</td>
                      <td className="px-4 py-2.5 text-zinc-400 truncate">{tx.effective_merchant || "—"}</td>
                      <td className="px-4 py-2.5 text-zinc-500 text-xs">{formatCategory(tx.effective_category)}</td>
                      <td className="px-4 py-2.5 text-right tabular-nums font-mono text-red-400">
                        ${Number(tx.amount).toFixed(2)}
                      </td>
                    </tr>
                  ))}
                </tbody>
              </table>
            </div>
          )}
        </div>
      )}

      {editModal && trip && (
        <CreateTripModal trip={trip} onClose={() => setEditModal(false)} />
      )}
    </div>
  );
}
