const { useState, useEffect, useCallback, useRef } = React;
const API = ""; // Same origin when served from FastAPI
window.API = API; // Expose to other JSX files

// ─── Constants ──────────────────────────────────────────────────────────────
// Z-index layers (ordered lowest → highest)
const Z_STICKY_HEADER    = 2;
const Z_TABLE_CELL       = 1;
const Z_SEARCH_FORM      = 100;
const Z_DROPDOWN         = 100;
const Z_MODAL_OVERLAY    = 1000;
const Z_MODAL            = 1000;
const Z_TOOLTIP          = 9999;
const Z_SUGGESTIONS      = 9999;
const Z_DATE_PICKER      = 9998;
const Z_MODAL_TOP        = 10000;
const Z_TOAST            = 99999;

// Timing (ms)
const TOAST_DISMISS_MS          = 5000;
const MAP_INVALIDATE_DELAY_MS   = 300;
const SEARCH_DEBOUNCE_MS        = 250;
const INPUT_FOCUS_DELAY_MS      = 50;
const CONFIRMATION_MSG_MS       = 1500;

// Layout / sizing (px)
const TOAST_MAX_WIDTH            = 380;
const TOAST_OFFSET               = 20;
const SUGGESTIONS_MAX_HEIGHT     = 300;
const DATE_PICKER_MAX_HEIGHT     = 220;
const ALLIANCE_INFO_MAX_WIDTH    = 520;
const MAX_TOASTS                 = 5;

// ─── Error Boundary ─────────────────────────────────────────────────────────
class ErrorBoundary extends React.Component {
  constructor(props) { super(props); this.state = { hasError: false, error: null }; }
  static getDerivedStateFromError(error) { return { hasError: true, error }; }
  componentDidCatch(error, info) { console.error("ErrorBoundary caught:", error, info); }
  render() {
    if (this.state.hasError) {
      return React.createElement("div", { style: { background: "rgba(239,68,68,0.1)", border: "1px solid rgba(239,68,68,0.3)", borderRadius: 12, padding: 20, margin: "12px 0", textAlign: "center" } },
        React.createElement("div", { style: { fontSize: 14, color: "#ef4444", marginBottom: 8 } }, "This section failed to load"),
        React.createElement("div", { style: { fontSize: 12, color: theme.textMuted } }, String(this.state.error?.message || "Unknown error")),
        React.createElement("button", { onClick: () => this.setState({ hasError: false, error: null }), style: { marginTop: 10, padding: "6px 16px", background: "rgba(59,130,246,0.15)", color: "#3b82f6", border: "1px solid rgba(59,130,246,0.3)", borderRadius: 8, cursor: "pointer", fontSize: 12, fontFamily: "inherit" } }, "Retry")
      );
    }
    return this.props.children;
  }
}

// ─── Toast Notifications ─────────────────────────────────────────────────────
// Lightweight global toast system for surfacing errors (replaces silent .catch(() => {}))
const _toastListeners = new Set();
let _toastId = 0;
window.showToast = (msg, type = "error") => {
  const t = { id: ++_toastId, msg, type, ts: Date.now() };
  _toastListeners.forEach(fn => fn(t));
};
// Screen-reader announcements via live region
window.announce = (msg) => {
  const el = document.getElementById("a11y-announcer");
  if (el) { el.textContent = ""; setTimeout(() => { el.textContent = msg; }, 100); }
};
// Shorthand for catch blocks — logs to console AND shows toast
window.silentErr = (context) => (err) => {
  console.warn(`[${context}]`, err);
  window.showToast(`${context}: ${err.message || "Network error"}`);
};

function ToastContainer() {
  const [toasts, setToasts] = useState([]);
  const theme = window.activeTheme || darkTheme;
  useEffect(() => {
    const add = (t) => setToasts(prev => [...prev.slice(-(MAX_TOASTS - 1)), t]);
    _toastListeners.add(add);
    return () => _toastListeners.delete(add);
  }, []);
  useEffect(() => {
    if (toasts.length === 0) return;
    const timer = setTimeout(() => setToasts(prev => prev.slice(1)), TOAST_DISMISS_MS);
    return () => clearTimeout(timer);
  }, [toasts]);
  if (toasts.length === 0) return null;
  return ReactDOM.createPortal(
    React.createElement("div", { role: "status", "aria-live": "polite", "aria-atomic": "false", style: { position: "fixed", bottom: TOAST_OFFSET, right: TOAST_OFFSET, zIndex: Z_TOAST, display: "flex", flexDirection: "column", gap: 8, maxWidth: TOAST_MAX_WIDTH } },
      toasts.map(t => React.createElement("div", { key: t.id, role: t.type === "error" ? "alert" : "status", style: {
        background: t.type === "error" ? "rgba(239,68,68,0.95)" : t.type === "success" ? "rgba(16,185,129,0.95)" : "rgba(59,130,246,0.95)",
        color: "#fff", padding: "10px 16px", borderRadius: 10, fontSize: 13, fontFamily: "inherit",
        boxShadow: "0 4px 20px rgba(0,0,0,0.3)", display: "flex", alignItems: "center", gap: 8,
        animation: "fadeIn 0.2s ease"
      } },
        React.createElement("span", { style: { flex: 1 } }, t.msg),
        React.createElement("button", { onClick: () => setToasts(prev => prev.filter(x => x.id !== t.id)), "aria-label": "Dismiss notification",
          style: { background: "none", border: "none", color: "#fff", cursor: "pointer", fontSize: 16, padding: "0 4px", opacity: 0.7 } }, "×")
      ))
    ), document.body
  );
}

// ─── Help Guide ─────────────────────────────────────────────────────────────
// In-app guide for new users — Quick Start + detailed feature walkthroughs
function HelpGuide({ onClose }) {
  const theme = window.activeTheme || darkTheme;
  const [activeSection, setActiveSection] = useState("quickstart");

  const sections = [
    { id: "quickstart", icon: "🚀", label: "Quick Start" },
    { id: "search", icon: "🔍", label: "Searching Flights" },
    { id: "results", icon: "📋", label: "Reading Results" },
    { id: "programs", icon: "🏛", label: "Programs & Banks" },
    { id: "dashboard", icon: "📊", label: "Dashboard" },
    { id: "alerts", icon: "📧", label: "Alerts" },
    { id: "profile", icon: "👤", label: "Your Profile" },
    { id: "tips", icon: "💡", label: "Pro Tips" },
  ];

  const Section = ({ title, children }) => (
    <div style={{ marginBottom: 28 }}>
      <h3 style={{ fontSize: 18, fontWeight: 700, color: theme.text, marginBottom: 10, letterSpacing: -0.3 }}>{title}</h3>
      <div style={{ color: theme.textMuted, fontSize: 14, lineHeight: 1.75 }}>{children}</div>
    </div>
  );

  const Step = ({ num, title, children }) => (
    <div style={{ display: "flex", gap: 14, marginBottom: 16 }}>
      <div style={{ width: 32, height: 32, borderRadius: "50%", background: theme.accent, color: "#fff", display: "flex", alignItems: "center", justifyContent: "center", fontSize: 14, fontWeight: 700, flexShrink: 0, marginTop: 2 }}>{num}</div>
      <div>
        <div style={{ fontWeight: 600, color: theme.text, fontSize: 14, marginBottom: 4 }}>{title}</div>
        <div style={{ color: theme.textMuted, fontSize: 13, lineHeight: 1.7 }}>{children}</div>
      </div>
    </div>
  );

  const Kbd = ({ children }) => (
    <span style={{ display: "inline-block", background: theme.surface, border: `1px solid ${theme.border}`, borderRadius: 6, padding: "2px 8px", fontSize: 12, fontWeight: 600, color: theme.accent, fontFamily: "'JetBrains Mono', monospace" }}>{children}</span>
  );

  const Tip = ({ children }) => (
    <div style={{ background: theme.accent + "10", border: `1px solid ${theme.accent}30`, borderRadius: 10, padding: "10px 14px", marginTop: 8, marginBottom: 8, fontSize: 13, color: theme.textMuted, display: "flex", gap: 8, alignItems: "flex-start" }}>
      <span style={{ color: theme.accent, fontSize: 14, flexShrink: 0 }}>💡</span>
      <span>{children}</span>
    </div>
  );

  const content = {
    quickstart: (
      <>
        <Section title="Find Award Flights in 60 Seconds">
          <p style={{ marginBottom: 16, fontSize: 15, color: theme.text }}>Award Flight DB tracks over 6 million award flight seats across 25 airline loyalty programs and 7 credit card transfer partners. Here's how to find your next flight:</p>
          <Step num={1} title="Enter Your Route">
            Type a city name, country, or airport code in the <strong style={{ color: theme.text }}>FROM</strong> and <strong style={{ color: theme.text }}>TO</strong> fields. Select from the dropdown suggestions. You can type <Kbd>Tokyo</Kbd> to search all Tokyo airports at once, or <Kbd>NRT</Kbd> for a specific one. You can also search with <strong style={{ color: theme.accent }}>just an origin</strong> (to see everywhere you can fly from that airport) or <strong style={{ color: theme.accent }}>just a destination</strong> (to find all routes into a city).
          </Step>
          <Step num={2} title="Set Your Dates">
            Pick a <strong style={{ color: theme.text }}>Date From</strong> and <strong style={{ color: theme.text }}>Date To</strong> range. The database tracks availability up to 11 months ahead, so cast a wide net.
          </Step>
          <Step num={3} title="Choose Your Cabin">
            Select <strong style={{ color: theme.first }}>First</strong>, <strong style={{ color: theme.business }}>Business</strong>, <strong style={{ color: theme.premium }}>Premium Economy</strong>, or <strong style={{ color: theme.economy }}>Economy</strong>. Business is selected by default — that's where the best award value usually is.
          </Step>
          <Step num={4} title="Hit Search">
            Click <strong style={{ color: theme.accent }}>Search Flights</strong> and results will appear ranked by mileage cost. The best deals show up first.
          </Step>
          <Tip>Create a free account to save your home airports, set up price alerts, and track your loyalty program balances — all in one place.</Tip>
        </Section>
      </>
    ),

    search: (
      <>
        <Section title="Flexible Route Search">
          <p style={{ marginBottom: 16 }}>You don't need both an origin and destination to search. Use any combination:</p>
          <div style={{ marginBottom: 12 }}>
            <strong style={{ color: theme.accent }}>Origin + Destination</strong> — Find flights on a specific route (e.g., MEM → NRT).
          </div>
          <div style={{ marginBottom: 12 }}>
            <strong style={{ color: theme.accent }}>Origin only</strong> — Leave the TO field empty to discover everywhere you can fly from your airport. Great for "where can I go?" inspiration.
          </div>
          <div style={{ marginBottom: 12 }}>
            <strong style={{ color: theme.accent }}>Destination only</strong> — Leave the FROM field empty to see all routes into a city. Perfect for finding the cheapest way to reach a destination.
          </div>
          <Tip>Combine a partial route with cabin and bank filters to answer questions like "Where can I fly Business class from MEM using Chase points?"</Tip>
        </Section>

        <Section title="Search Options Explained">
          <div style={{ marginBottom: 16 }}>
            <strong style={{ color: theme.text, display: "block", marginBottom: 4 }}>Trip Type</strong>
            <strong style={{ color: theme.accent }}>One Way</strong> finds single-direction flights. <strong style={{ color: theme.accent }}>Round Trip</strong> finds paired outbound + return itineraries with smart layover scoring. <strong style={{ color: theme.accent }}>Multi City</strong> lets you search flexible city pairings.
          </div>
          <div style={{ marginBottom: 16 }}>
            <strong style={{ color: theme.text, display: "block", marginBottom: 4 }}>Cabin Class</strong>
            Select one or more cabin classes: <strong style={{ color: theme.first }}>First</strong>, <strong style={{ color: theme.business }}>Business</strong>, <strong style={{ color: theme.premium }}>Premium Economy</strong>, or <strong style={{ color: theme.economy }}>Economy</strong>. Each is color-coded throughout the app so you can spot them at a glance.
          </div>
          <div style={{ marginBottom: 16 }}>
            <strong style={{ color: theme.text, display: "block", marginBottom: 4 }}>Bank & Airline Filters</strong>
            Filter by a specific credit card program (like Chase, Amex, or Capital One) to see only flights bookable with those points. Or filter by airline to see a specific carrier's availability. These filters work with partial route searches too.
          </div>
          <div style={{ marginBottom: 16 }}>
            <strong style={{ color: theme.text, display: "block", marginBottom: 4 }}>Max Miles</strong>
            Set a ceiling on the number of miles/points you're willing to spend. Default is 120,000 — lower it to find the real sweet spots.
          </div>
          <div style={{ marginBottom: 16 }}>
            <strong style={{ color: theme.text, display: "block", marginBottom: 4 }}>Max Stops</strong>
            Choose <strong style={{ color: theme.text }}>Nonstop</strong> for direct flights only, <strong style={{ color: theme.text }}>1 Stop</strong> (default) for connections through hubs, or <strong style={{ color: theme.text }}>2 Stops</strong> to see all options. Connections are automatically built by matching flights through hub airports.
          </div>
          <div style={{ marginBottom: 16 }}>
            <strong style={{ color: theme.text, display: "block", marginBottom: 4 }}>Passengers</strong>
            Search for up to 9 passengers. Results will only show flights with enough available seats for your group.
          </div>
          <Tip>Use the <strong style={{ color: theme.text }}>⇌ swap button</strong> between FROM and TO to quickly reverse your route.</Tip>
        </Section>
      </>
    ),

    results: (
      <>
        <Section title="Understanding Your Results">
          <p style={{ marginBottom: 16 }}>Results are shown in a table sorted by mileage cost (lowest first). Here's what each column means:</p>
          <div style={{ marginBottom: 12 }}>
            <strong style={{ color: theme.text }}>Route</strong> — Origin → Destination airport codes. For connections, you'll see the connecting airport in between.
          </div>
          <div style={{ marginBottom: 12 }}>
            <strong style={{ color: theme.text }}>Date</strong> — The travel date. Hover over dates to see a sparkline chart showing how availability changes over time.
          </div>
          <div style={{ marginBottom: 12 }}>
            <strong style={{ color: theme.text }}>Airline</strong> — The operating carrier with their logo. The airline name links to their program details.
          </div>
          <div style={{ marginBottom: 12 }}>
            <strong style={{ color: theme.text }}>Cabin</strong> — Color-coded by class: <span style={{ color: theme.first }}>First</span>, <span style={{ color: theme.business }}>Business</span>, <span style={{ color: theme.premium }}>Premium Economy</span>, <span style={{ color: theme.economy }}>Economy</span>.
          </div>
          <div style={{ marginBottom: 12 }}>
            <strong style={{ color: theme.text }}>Miles</strong> — The number of miles/points required to book. Actual currency depends on which program you book through.
          </div>
          <div style={{ marginBottom: 12 }}>
            <strong style={{ color: theme.text }}>Program</strong> — Which loyalty program this availability was found through. This is who you'd book with.
          </div>
          <div style={{ marginBottom: 12 }}>
            <strong style={{ color: theme.text }}>Seats</strong> — How many award seats are available on that flight. Book quickly if you see only 1-2 seats!
          </div>
          <Tip>Click the <strong style={{ color: theme.text }}>sort headers</strong> to re-sort by any column. Click a program name to see which credit card points can transfer to it.</Tip>
        </Section>

        <Section title="Direct vs Connection Results">
          <p>Direct flights appear first in a clean table. Below that, connection itineraries are shown with a connecting city indicator. Connections are automatically scored for quality — shorter layovers and better cabin matches rank higher.</p>
        </Section>
      </>
    ),

    programs: (
      <>
        <Section title="Loyalty Programs & Transfer Partners">
          <p style={{ marginBottom: 16 }}>The <strong style={{ color: theme.text }}>Programs</strong> tab is your reference for the 25 airline programs and 7 bank transfer partners tracked in the database.</p>

          <div style={{ marginBottom: 20 }}>
            <strong style={{ color: theme.text, display: "block", marginBottom: 6 }}>🔥 Active Transfer Bonuses</strong>
            At the top, you'll see current transfer bonus promotions from credit card programs. For example, if Chase is offering a +20% bonus to British Airways, transferring 100,000 points gives you 120,000 Avios. These bonuses change frequently — check before you transfer!
          </div>

          <div style={{ marginBottom: 20 }}>
            <strong style={{ color: theme.text, display: "block", marginBottom: 6 }}>Alliance Groups</strong>
            Programs are organized by airline alliance — <strong>Star Alliance</strong>, <strong>Oneworld</strong>, <strong>SkyTeam</strong>, and <strong>Independent</strong>. Partners within the same alliance can often book each other's flights.
          </div>

          <div style={{ marginBottom: 20 }}>
            <strong style={{ color: theme.text, display: "block", marginBottom: 6 }}>Bank Transfer Partners</strong>
            Below the airlines, you'll see each bank's transfer partners with their transfer ratios. A "1:1" ratio means 1,000 bank points becomes 1,000 airline miles. Some have better ratios — look for these to maximize value.
          </div>

          <Tip>Sign in to track your point balances for each program and bank. They'll show right on the cards so you can quickly see what you can afford.</Tip>
        </Section>
      </>
    ),

    dashboard: (
      <>
        <Section title="Database Overview">
          <p style={{ marginBottom: 16 }}>The <strong style={{ color: theme.text }}>Dashboard</strong> gives you a bird's-eye view of the entire database with five powerful tools for exploring award flight data.</p>

          <div style={{ marginBottom: 16 }}>
            <strong style={{ color: theme.text, display: "block", marginBottom: 4 }}>Stats at a Glance</strong>
            Four stat cards show <strong style={{ color: theme.accent }}>Total Flights</strong> tracked, <strong style={{ color: theme.accent }}>Programs</strong> monitored, unique <strong style={{ color: theme.accent }}>Routes</strong>, and <strong style={{ color: theme.accent }}>Airports</strong> covered. The database is updated daily with fresh award availability.
          </div>

          <div style={{ marginBottom: 16 }}>
            <strong style={{ color: theme.text, display: "block", marginBottom: 4 }}>System Status</strong>
            Shows database connection health, the last ingestion timestamp, the date range of tracked availability, and API version. If the last ingestion is more than a day old, data might be stale.
          </div>
        </Section>

        <Section title="1. Airport Coverage Map">
          <p style={{ marginBottom: 16 }}>An interactive world map plotting every airport with tracked award flights. Airports are color-coded by activity level:</p>
          <div style={{ marginBottom: 8 }}>
            <span style={{ color: theme.gold }}>● Gold</span> — Top 50 busiest airports (major hubs like JFK, LHR, NRT)
          </div>
          <div style={{ marginBottom: 8 }}>
            <span style={{ color: theme.accent }}>● Blue</span> — Top 200 airports
          </div>
          <div style={{ marginBottom: 8 }}>
            <span style={{ color: theme.success }}>● Green</span> — All other active airports
          </div>
          <p style={{ marginTop: 8 }}>Zoom and pan the map to explore different regions. The counter shows total airports and countries covered.</p>
        </Section>

        <Section title="2. Route Density Map">
          <p style={{ marginBottom: 16 }}>A visual explorer that draws animated arcs across a world map showing every route in the database. Thicker, brighter arcs mean more award availability on that route.</p>

          <div style={{ marginBottom: 16 }}>
            <strong style={{ color: theme.text, display: "block", marginBottom: 4 }}>Route Limit</strong>
            Control how many routes are loaded: <strong style={{ color: theme.accent }}>800</strong> for a quick overview, <strong style={{ color: theme.accent }}>2K</strong> for broader coverage, <strong style={{ color: theme.accent }}>5K</strong> (default) for most routes, or <strong style={{ color: theme.accent }}>All</strong> to load the entire network.
          </div>

          <div style={{ marginBottom: 16 }}>
            <strong style={{ color: theme.text, display: "block", marginBottom: 4 }}>Cabin Filter</strong>
            Switch between <strong style={{ color: theme.text }}>All</strong> cabins, <strong style={{ color: theme.business }}>Business</strong> only, or <strong style={{ color: theme.economy }}>Economy</strong> only. This instantly hides routes without availability in your chosen cabin.
          </div>

          <div style={{ marginBottom: 16 }}>
            <strong style={{ color: theme.text, display: "block", marginBottom: 4 }}>Region Toggles</strong>
            Color-coded region tags like <span style={{ color: "#f472b6" }}>NA↔EU</span>, <span style={{ color: "#60a5fa" }}>NA↔Asia</span>, <span style={{ color: "#fbbf24" }}>NA↔SA</span>, and 14 more. Click any tag to toggle that region's routes on or off. Use <strong style={{ color: theme.text }}>Hide All</strong> to start blank, then enable only the corridors you care about. <strong style={{ color: theme.accent }}>Show All</strong> brings everything back.
          </div>

          <div style={{ marginBottom: 16 }}>
            <strong style={{ color: theme.text, display: "block", marginBottom: 4 }}>Origin & Destination Search</strong>
            Filter routes by airport, city, country, or region. Enter just an origin to see everywhere you can fly from, or just a destination to find all routes into a city. Use the type selector (Any, IATA, City, Country, Region) to control match scope.
          </div>

          <div style={{ marginBottom: 16 }}>
            <strong style={{ color: theme.text, display: "block", marginBottom: 4 }}>Click Any Arc</strong>
            Click a route arc to open a detail panel showing origin, destination, available cabins, mileage costs, and which programs have availability.
          </div>

          <Tip>Power combo: set cabin to <strong style={{ color: theme.business }}>Business</strong>, click <strong style={{ color: theme.text }}>Hide All</strong>, turn on just <span style={{ color: "#60a5fa" }}>NA↔Asia</span>, then type your home airport in the origin field. You'll instantly see every business-class route to Asia from your city.</Tip>
        </Section>

        <Section title="3. Sweet Spot Finder">
          <p style={{ marginBottom: 16 }}>A color-coded heatmap showing the <strong style={{ color: theme.text }}>lowest mileage price</strong> for every program across every month. This is the fastest way to find which programs offer the cheapest redemptions and when.</p>

          <div style={{ marginBottom: 16 }}>
            <strong style={{ color: theme.text, display: "block", marginBottom: 4 }}>Cabin Toggle</strong>
            Switch between <strong style={{ color: theme.business }}>Business</strong> and <strong style={{ color: theme.economy }}>Economy</strong> to see the best prices in each cabin class. Each shows completely different programs and pricing.
          </div>

          <div style={{ marginBottom: 16 }}>
            <strong style={{ color: theme.text, display: "block", marginBottom: 4 }}>Cheapest vs Average</strong>
            <strong style={{ color: theme.accent }}>Cheapest</strong> shows the absolute lowest mileage cost found that month — the best possible deal. <strong style={{ color: theme.accent }}>Average</strong> shows the typical cost, which is more realistic for planning. Compare both to gauge how rare the sweet spots are.
          </div>

          <div style={{ marginBottom: 16 }}>
            <strong style={{ color: theme.text, display: "block", marginBottom: 4 }}>Reading the Heatmap</strong>
            Each cell shows the mileage cost (in thousands, e.g., "10k" = 10,000 miles) for a program in a given month. Cells are color-coded from <span style={{ color: theme.success }}>green</span> (cheap) through <span style={{ color: theme.warning }}>yellow</span> to <span style={{ color: theme.danger }}>red</span> (expensive). Look for the greenest cells — those are your sweet spots.
          </div>

          <div style={{ marginBottom: 16 }}>
            <strong style={{ color: theme.text, display: "block", marginBottom: 4 }}>Route & Program Filters</strong>
            Use the origin and destination search to narrow results to specific routes. The program filter lets you search and hide specific programs. Click any program name to temporarily hide it and focus on the ones you care about.
          </div>

          <div style={{ marginBottom: 16 }}>
            <strong style={{ color: theme.text, display: "block", marginBottom: 4 }}>Cell Details</strong>
            Click any cell to <strong style={{ color: theme.text }}>pin</strong> a detail panel showing that program's total flights and routes for that month, plus the best business and economy deals with exact route, date, and mileage cost. Hover over other cells to preview without unpinning. Use <strong style={{ color: theme.accent }}>Compact</strong> mode to fit more programs on screen at once.
          </div>

          <Tip>Search for your home airport as origin, then look across the row for the greenest cells. That tells you which programs offer the cheapest flights from your city and which months have the best availability.</Tip>
        </Section>

        <Section title="4. Cabin Class Calendar">
          <p style={{ marginBottom: 16 }}>A monthly calendar view that shows <strong style={{ color: theme.text }}>daily award availability by cabin class</strong> for any route. Perfect for finding exact dates to book.</p>

          <div style={{ marginBottom: 16 }}>
            <strong style={{ color: theme.text, display: "block", marginBottom: 4 }}>How to Use It</strong>
            Enter an origin, destination, or both in the search fields, then click Search. The calendar fills in with color-coded bars for each day showing which cabin classes have award space available.
          </div>

          <div style={{ marginBottom: 16 }}>
            <strong style={{ color: theme.text, display: "block", marginBottom: 4 }}>Reading the Colors</strong>
            Each day shows up to four bars: <span style={{ color: theme.economy }}>■ Economy</span>, <span style={{ color: theme.premium }}>■ Premium Economy</span>, <span style={{ color: theme.business }}>■ Business</span>, and <span style={{ color: theme.first }}>■ First</span>. If a bar shows a mileage number (like "10k"), award space is available in that cabin for that date. Empty or dim bars mean no availability.
          </div>

          <div style={{ marginBottom: 16 }}>
            <strong style={{ color: theme.text, display: "block", marginBottom: 4 }}>Month Navigation</strong>
            Use the <strong style={{ color: theme.text }}>◀</strong> and <strong style={{ color: theme.text }}>▶</strong> arrows to move between months. The database tracks availability up to 11 months ahead, so explore future months for the best selection.
          </div>

          <div style={{ marginBottom: 16 }}>
            <strong style={{ color: theme.text, display: "block", marginBottom: 4 }}>Day Details</strong>
            Click any day in the calendar to see a breakdown of available flights, programs, mileage costs, and seat counts for that specific date. This is the final step before you go book your flight.
          </div>

          <Tip>Found a sweet spot in the heatmap above? Plug that same route into the Cabin Class Calendar to see the exact dates with availability. Together, these two tools take you from "what's cheap?" all the way to "when can I fly?"</Tip>
        </Section>
      </>
    ),

    alerts: (
      <>
        <Section title="Price Alerts">
          <p style={{ marginBottom: 16, fontSize: 14 }}><em style={{ color: theme.textDim }}>Requires a free account — sign in to access.</em></p>

          <p style={{ marginBottom: 16 }}>Alerts let you monitor specific routes and get notified when award seats become available at the mileage price you want.</p>

          <Step num={1} title="Create an Alert">
            Go to the <strong style={{ color: theme.text }}>Alerts</strong> tab and set your origin, destination, cabin class, and the maximum miles you'd pay. Give it a friendly name so you remember what it's tracking.
          </Step>
          <Step num={2} title="Set Duration">
            Choose how long to monitor — 7 days, 30 days, 90 days, 6 months, or a full year. Longer windows catch seasonal availability that opens up.
          </Step>
          <Step num={3} title="Get Notified">
            When matching award seats are found, you'll receive an email with the flight details, mileage cost, and a direct link to book.
          </Step>
          <Tip>Set alerts for popular routes like the US to Japan in business class. Availability opens up unpredictably — alerts catch the moment it appears.</Tip>
        </Section>
      </>
    ),

    profile: (
      <>
        <Section title="Your Profile Settings">
          <p style={{ marginBottom: 16, fontSize: 14 }}><em style={{ color: theme.textDim }}>Requires a free account — sign in to access.</em></p>

          <div style={{ marginBottom: 16 }}>
            <strong style={{ color: theme.text, display: "block", marginBottom: 4 }}>Home Airports</strong>
            Set your home airports (e.g., MEM, JFK) and they'll automatically fill in the "From" field when you search. Great if you always fly out of the same cities.
          </div>

          <div style={{ marginBottom: 16 }}>
            <strong style={{ color: theme.text, display: "block", marginBottom: 4 }}>Preferred Programs & Banks</strong>
            Select the airline programs and credit card programs you have. This helps personalize which results are most relevant to you.
          </div>

          <div style={{ marginBottom: 16 }}>
            <strong style={{ color: theme.text, display: "block", marginBottom: 4 }}>Cabin & Booking Preferences</strong>
            Set your default cabin class, trip type, and whether you prefer award bookings or cash fares. These become your search defaults.
          </div>

          <div style={{ marginBottom: 16 }}>
            <strong style={{ color: theme.text, display: "block", marginBottom: 4 }}>Theme</strong>
            Switch between dark mode and light mode based on your preference.
          </div>

          <Tip>Your settings are synced to the cloud, so they persist even if you clear your browser. Sign in on any device to pick up right where you left off.</Tip>
        </Section>
      </>
    ),

    tips: (
      <>
        <Section title="Pro Tips for Finding Award Flights">
          <div style={{ marginBottom: 20 }}>
            <strong style={{ color: theme.gold, display: "block", marginBottom: 6 }}>🗺️ Explore with Partial Routes</strong>
            <span>Leave the destination empty and search from your home airport to discover all the places you can fly with points. Or leave the origin empty to find every route into your dream destination. It's the best way to find deals you didn't know existed.</span>
          </div>

          <div style={{ marginBottom: 20 }}>
            <strong style={{ color: theme.gold, display: "block", marginBottom: 6 }}>🎯 Search Wide, Then Narrow</strong>
            <span>Start with a broad date range (2-3 months) and all cabins to see what's available. Once you spot patterns, narrow down to specific dates and classes.</span>
          </div>

          <div style={{ marginBottom: 20 }}>
            <strong style={{ color: theme.gold, display: "block", marginBottom: 6 }}>💰 Check Transfer Bonuses First</strong>
            <span>Before transferring points, check the Programs tab for active transfer bonuses. A 20% bonus can save you thousands of points on a single booking.</span>
          </div>

          <div style={{ marginBottom: 20 }}>
            <strong style={{ color: theme.gold, display: "block", marginBottom: 6 }}>🔗 Connections Can Be Gold</strong>
            <span>Don't skip connection results! Sometimes routing through a hub unlocks availability that doesn't exist on direct flights. A business class connection through Doha or Istanbul can be a fantastic experience.</span>
          </div>

          <div style={{ marginBottom: 20 }}>
            <strong style={{ color: theme.gold, display: "block", marginBottom: 6 }}>📅 Timing Matters</strong>
            <span>Airlines release award seats on specific schedules — some 330 days out, others 90 days. New availability also opens up close-in (2-3 weeks before departure) when airlines release unsold seats.</span>
          </div>

          <div style={{ marginBottom: 20 }}>
            <strong style={{ color: theme.gold, display: "block", marginBottom: 6 }}>🏦 Know Your Transfer Partners</strong>
            <span>The same flight might be bookable through multiple programs at different prices. Check the program column in results — the same SFO→NRT flight could cost 60k through one program and 85k through another.</span>
          </div>

          <div style={{ marginBottom: 20 }}>
            <strong style={{ color: theme.gold, display: "block", marginBottom: 6 }}>⚡ Act Fast on Low Availability</strong>
            <span>If you see 1-2 seats available, book quickly. Award seats can disappear within hours, especially on premium cabins to popular destinations.</span>
          </div>
        </Section>
      </>
    ),
  };

  // Focus trap and ESC key for modal accessibility
  const modalRef = React.useRef(null);
  React.useEffect(() => {
    const handleKeyDown = (e) => {
      if (e.key === "Escape") { onClose(); return; }
      if (e.key === "Tab" && modalRef.current) {
        const focusable = modalRef.current.querySelectorAll('button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])');
        if (focusable.length === 0) return;
        const first = focusable[0], last = focusable[focusable.length - 1];
        if (e.shiftKey) { if (document.activeElement === first) { e.preventDefault(); last.focus(); } }
        else { if (document.activeElement === last) { e.preventDefault(); first.focus(); } }
      }
    };
    document.addEventListener("keydown", handleKeyDown);
    // Focus first element on mount
    setTimeout(() => { if (modalRef.current) { const first = modalRef.current.querySelector('button'); if (first) first.focus(); } }, 50);
    return () => document.removeEventListener("keydown", handleKeyDown);
  }, [onClose]);

  return ReactDOM.createPortal(
    <div style={{ position: "fixed", inset: 0, zIndex: Z_MODAL_TOP, display: "flex", alignItems: "center", justifyContent: "center", background: "rgba(0,0,0,0.75)", backdropFilter: "blur(4px)" }} onClick={onClose}>
      <div ref={modalRef} role="dialog" aria-modal="true" aria-labelledby="help-guide-title" className="help-guide-modal" style={{ width: "90%", maxWidth: 860, maxHeight: "85vh", display: "flex", background: theme.bg, border: `1px solid ${theme.border}`, borderRadius: 16, boxShadow: "0 24px 80px rgba(0,0,0,0.5)", overflow: "hidden" }} onClick={e => e.stopPropagation()}>

        {/* Sidebar nav */}
        <div className="help-guide-sidebar" style={{ width: 200, minWidth: 200, borderRight: `1px solid ${theme.border}`, background: theme.surface, padding: "20px 0", display: "flex", flexDirection: "column" }}>
          <div style={{ padding: "0 16px 16px", borderBottom: `1px solid ${theme.border}`, marginBottom: 8 }}>
            <div style={{ fontSize: 16, fontWeight: 700, color: theme.text }}>📖 User Guide</div>
            <div style={{ fontSize: 11, color: theme.textDim, marginTop: 2 }}>Learn how to use Award Flight DB</div>
          </div>
          {sections.map(s => (
            <button key={s.id} onClick={() => setActiveSection(s.id)} style={{
              display: "flex", alignItems: "center", gap: 8, width: "100%",
              padding: "10px 16px", border: "none", cursor: "pointer", fontFamily: "inherit",
              fontSize: 13, fontWeight: activeSection === s.id ? 600 : 400,
              color: activeSection === s.id ? theme.accent : theme.textMuted,
              background: activeSection === s.id ? theme.accent + "12" : "transparent",
              borderLeft: `3px solid ${activeSection === s.id ? theme.accent : "transparent"}`,
              transition: "all 0.15s"
            }}>
              <span>{s.icon}</span> {s.label}
            </button>
          ))}
        </div>

        {/* Content area */}
        <div className="help-guide-content" style={{ flex: 1, overflow: "auto", padding: "28px 32px" }}>
          <div style={{ display: "flex", justifyContent: "space-between", alignItems: "flex-start", marginBottom: 20 }}>
            <div>
              <h2 id="help-guide-title" style={{ fontSize: 22, fontWeight: 700, color: theme.text, margin: 0 }}>
                {sections.find(s => s.id === activeSection)?.icon} {sections.find(s => s.id === activeSection)?.label}
              </h2>
            </div>
            <button onClick={onClose} aria-label="Close help guide" style={{ background: "none", border: `1px solid ${theme.border}`, borderRadius: 8, padding: "6px 12px", color: theme.textMuted, cursor: "pointer", fontSize: 13, fontFamily: "inherit" }}>✕ Close</button>
          </div>
          {content[activeSection]}
        </div>
      </div>
    </div>,
    document.body
  );
}

// ─── Theme ──────────────────────────────────────────────────────────────────
const darkTheme = {
  bg: "#0a0e1a", surface: "#111827", surfaceHover: "#1a2235",
  card: "#151d2e", cardBg: "#151d2e", border: "#1e293b", borderLight: "#2a3a52",
  text: "#e2e8f0", textMuted: "#94a3b8", textDim: "#8b9ab5",
  accent: "#3b82f6", accentGlow: "rgba(59, 130, 246, 0.15)", accentSoft: "#1e3a5f",
  success: "#10b981", warning: "#f59e0b", danger: "#ef4444", gold: "#fbbf24",
  first: "#c084fc", business: "#3b82f6", premium: "#10b981", economy: "#94a3b8",
  hover: "#1a2235",
};
const lightTheme = {
  bg: "#f8fafc", surface: "#f1f5f9", surfaceHover: "#e2e8f0",
  card: "#ffffff", cardBg: "#ffffff", border: "#cbd5e1", borderLight: "#e2e8f0",
  text: "#1e293b", textMuted: "#64748b", textDim: "#94a3b8",
  accent: "#2563eb", accentGlow: "rgba(37, 99, 235, 0.12)", accentSoft: "#dbeafe",
  success: "#059669", warning: "#d97706", danger: "#dc2626", gold: "#d97706",
  first: "#9333ea", business: "#2563eb", premium: "#059669", economy: "#64748b",
  hover: "#e2e8f0",
};
// Mutable theme object — all code references theme.X, we swap properties in-place on toggle
const theme = { ...darkTheme };
function applyTheme(mode) {
  const src = mode === "light" ? lightTheme : darkTheme;
  Object.keys(src).forEach(k => { theme[k] = src[k]; });
  window.theme = theme;
  window.activeTheme = theme;
  document.body.style.backgroundColor = theme.bg;
  document.body.style.color = theme.text;
  if (mode === "light") { document.body.classList.add("light-maps"); } else { document.body.classList.remove("light-maps"); }
}
// Initialize from localStorage immediately to prevent flash
applyTheme(localStorage.getItem("theme_mode") || "dark");

const cabinInfo = {
  F: { label: "First", color: theme.first, icon: "👑" },
  J: { label: "Business", color: theme.business, icon: "💎" },
  W: { label: "Premium Econ", color: theme.premium, icon: "✦" },
  Y: { label: "Economy", color: theme.economy, icon: "✈" },
};

const allianceColors = {
  "Star Alliance": "#c9a227", Oneworld: "#c53030", SkyTeam: "#2563eb", Independent: "#6b7280",
};

// Logo helpers — square icon/tail logos
// favicone.com: high-quality 256×256 square icons (best quality where available)
const fav = (domain) => `https://favicone.com/${domain}?s=256`;
// Google favicon at 128px (reliable fallback for domains favicone doesn't cover)
const gfav = (domain) => `https://www.google.com/s2/favicons?domain=${domain}&sz=128`;

// Alliance logos (icon + full member team image for hover tooltip)
const ALLIANCE_LOGOS = {
  "Star Alliance": { src: gfav("staralliance.com"), dark: false, teamImg: "/static/star_alliance.png" },
  "Oneworld": { src: gfav("oneworld.com"), dark: false, teamImg: "/static/oneworld.png" },
  "SkyTeam": { src: "/static/logo_skyteam.png", dark: false, teamImg: "/static/skyteam.png" },
  "Independent": null,
};

const PROGRAMS = {
  aeroplan: { name: "Air Canada Aeroplan", alliance: "Star Alliance", currency: "Aeroplan points", url: "https://www.aircanada.com/ca/en/aco/home/aeroplan.html", logo: fav("aircanada.com"), darkLogo: false },
  flyingblue: { name: "Air France/KLM Flying Blue", alliance: "SkyTeam", currency: "Flying Blue miles", url: "https://www.flyingblue.com/en", logo: gfav("airfrance.com"), darkLogo: false },
  alaska: { name: "Alaska Atmos Rewards", alliance: "Oneworld", currency: "Atmos Rewards", url: "https://www.alaskaair.com/en/mileage-plan", logo: "/static/logo_alaska.png", darkLogo: false },
  american: { name: "American AAdvantage", alliance: "Oneworld", currency: "AAdvantage miles", url: "https://www.aa.com/i18n/aadvantage-program/aadvantage-program.jsp", logo: fav("aa.com"), darkLogo: false },
  azul: { name: "Azul Fidelidade", alliance: "Independent", currency: "Fidelidade points", url: "https://www.voeazul.com.br/en/loyalty", logo: "/static/logo_azul.png", darkLogo: false },
  connectmiles: { name: "Copa ConnectMiles", alliance: "Star Alliance", currency: "ConnectMiles", url: "https://www.copaair.com/en-us/connectmiles/", logo: gfav("copaair.com"), darkLogo: false },
  delta: { name: "Delta SkyMiles", alliance: "SkyTeam", currency: "SkyMiles", url: "https://www.delta.com/us/en/skymiles/overview", logo: fav("delta.com"), darkLogo: true },
  emirates: { name: "Emirates Skywards", alliance: "Independent", currency: "Skywards miles", url: "https://www.emirates.com/us/english/skywards/", logo: "/static/logo_emirates.png", darkLogo: false },
  ethiopian: { name: "Ethiopian ShebaMiles", alliance: "Star Alliance", currency: "ShebaMiles", url: "https://www.ethiopianairlines.com/en/shebamiles", logo: gfav("ethiopianairlines.com"), darkLogo: false },
  etihad: { name: "Etihad Guest", alliance: "Independent", currency: "Etihad Guest miles", url: "https://www.etihad.com/en-us/etihad-guest", logo: "/static/logo_etihad.png", darkLogo: false },
  finnair: { name: "Finnair Plus", alliance: "Oneworld", currency: "Avios", url: "https://www.finnair.com/en/finnair-plus", logo: fav("finnair.com"), darkLogo: true },
  smiles: { name: "GOL Smiles", alliance: "Independent", currency: "Smiles miles", url: "https://www.smiles.com.br/home", logo: gfav("voegol.com.br"), darkLogo: false },
  jetblue: { name: "JetBlue TrueBlue", alliance: "Independent", currency: "TrueBlue points", url: "https://trueblue.jetblue.com", logo: fav("jetblue.com"), darkLogo: true },
  lufthansa: { name: "Lufthansa Miles & More", alliance: "Star Alliance", currency: "Miles & More miles", url: "https://www.miles-and-more.com/row/en.html", logo: "/static/logo_lufthansa.png", darkLogo: false },
  qantas: { name: "Qantas Frequent Flyer", alliance: "Oneworld", currency: "Qantas Points", url: "https://www.qantas.com/au/en/frequent-flyer.html", logo: fav("qantas.com"), darkLogo: false },
  qatar: { name: "Qatar Privilege Club", alliance: "Oneworld", currency: "Avios", url: "https://www.qatarairways.com/en-us/Privilege-Club.html", logo: "/static/logo_qatar.png", darkLogo: false },
  eurobonus: { name: "SAS EuroBonus", alliance: "Star Alliance", currency: "EuroBonus points", url: "https://www.flysas.com/en/eurobonus/", logo: fav("flysas.com"), darkLogo: false },
  saudia: { name: "Saudia AlFursan", alliance: "SkyTeam", currency: "AlFursan miles", url: "https://www.saudia.com/en/alfursan", logo: gfav("saudia.com"), darkLogo: false },
  singapore: { name: "Singapore KrisFlyer", alliance: "Star Alliance", currency: "KrisFlyer miles", url: "https://www.singaporeair.com/en_UK/us/ppsclub-krisflyer/krisflyer/", logo: gfav("singaporeair.com"), darkLogo: false },
  spirit: { name: "Spirit Free Spirit", alliance: "Independent", currency: "Free Spirit points", url: "https://www.spirit.com/free-spirit", logo: fav("spirit.com"), darkLogo: false },
  turkish: { name: "Turkish Miles&Smiles", alliance: "Star Alliance", currency: "Miles&Smiles", url: "https://www.turkishairlines.com/en-us/miles-and-smiles/", logo: gfav("turkishairlines.com"), darkLogo: false },
  united: { name: "United MileagePlus", alliance: "Star Alliance", currency: "MileagePlus miles", url: "https://www.united.com/en/us/fly/mileageplus.html", logo: fav("united.com"), darkLogo: true },
  virginatlantic: { name: "Virgin Atlantic Flying Club", alliance: "SkyTeam", currency: "Flying Club miles", url: "https://www.virginatlantic.com/en-us/flying-club", logo: gfav("virginatlantic.com"), darkLogo: false },
  velocity: { name: "Virgin Australia Velocity", alliance: "Independent", currency: "Velocity Points", url: "https://www.velocityfrequentflyer.com", logo: gfav("virginaustralia.com"), darkLogo: false },
  aeromexico: { name: "Aeromexico Club Premier", alliance: "SkyTeam", currency: "Club Premier points", url: "https://www.aeromexico.com/en-us/club-premier", logo: fav("aeromexico.com"), darkLogo: false, whiteBg: true },
};

// ─── Award Availability Release Schedule ────────────────────────────────────
// How far in advance each airline releases award seats (in days).
// Sources: roame.travel/guides/award-availability-release, upgradedpoints.com
// "bookable_via" = programs that can book partner awards on this airline's metal
const RELEASE_SCHEDULE = {
  // ── Seats.aero tracked programs (we have direct award data) ──
  aeromexico:     { days: 330, notes: "" },
  aeroplan:       { days: 355, notes: "" },
  flyingblue:     { days: 359, notes: "" },
  alaska:         { days: 331, notes: "" },
  american:       { days: 331, notes: "" },
  azul:           { days: 330, notes: "Estimate" },
  connectmiles:   { days: 330, notes: "Estimate" },
  delta:          { days: 331, notes: "" },
  emirates:       { days: 328, notes: "" },
  ethiopian:      { days: 330, notes: "Estimate" },
  etihad:         { days: 330, notes: "" },
  finnair:        { days: 360, notes: "" },
  smiles:         { days: 330, notes: "Estimate" },
  jetblue:        { days: 331, notes: "" },
  lufthansa:      { days: 360, notes: "" },
  qantas:         { days: 353, notes: "" },
  qatar:          { days: 361, notes: "" },
  eurobonus:      { days: 330, notes: "Estimate" },
  saudia:         { days: 330, notes: "Estimate" },
  singapore:      { days: 355, notes: "" },
  spirit:         { days: 140, notes: "Limited window" },
  turkish:        { days: 355, notes: "" },
  united:         { days: 337, notes: "" },
  virginatlantic: { days: 331, notes: "" },
  velocity:       { days: 330, notes: "Estimate" },
  // ── Transfer-only programs (bookable via partner programs) ──
  aerlingus:      { days: 330, notes: "Avios shared", iata: "EI", name: "Aer Lingus AerClub (Avios)", alliance: "Independent", transferOnly: true, logo: gfav("aerlingus.com") },
  ana:            { days: 355, notes: "", iata: "NH", name: "ANA Mileage Club", alliance: "Star Alliance", transferOnly: true, logo: gfav("ana.co.jp") },
  avianca:        { days: 355, notes: "", iata: "AV", name: "Avianca LifeMiles", alliance: "Star Alliance", transferOnly: true, logo: gfav("avianca.com") },
  britishairways: { days: 354, notes: "Avios shared", iata: "BA", name: "British Airways Executive Club (Avios)", alliance: "Oneworld", transferOnly: true, logo: gfav("britishairways.com") },
  cathay:         { days: 360, notes: "", iata: "CX", name: "Cathay Pacific Asia Miles", alliance: "Oneworld", transferOnly: true, logo: gfav("cathaypacific.com") },
  evaair:         { days: 360, notes: "", iata: "BR", name: "EVA Air Infinity MileageLands", alliance: "Star Alliance", transferOnly: true, logo: gfav("evaair.com") },
  hawaiian:       { days: 330, notes: "", iata: "HA", name: "Hawaiian Airlines HawaiianMiles", alliance: "Independent", transferOnly: true, logo: gfav("hawaiianairlines.com") },
  iberia:         { days: 360, notes: "Avios shared", iata: "IB", name: "Iberia Plus (Avios)", alliance: "Oneworld", transferOnly: true, logo: gfav("iberia.com") },
  jal:            { days: 360, notes: "", iata: "JL", name: "Japan Airlines Mileage Bank", alliance: "Oneworld", transferOnly: true, logo: gfav("jal.co.jp") },
  southwest:      { days: 0, notes: "Batch releases every 4-10 weeks", iata: "WN", name: "Southwest Rapid Rewards", alliance: "Independent", transferOnly: true, logo: gfav("southwest.com") },
  tap:            { days: 361, notes: "", iata: "TP", name: "TAP Air Portugal Miles&Go", alliance: "Star Alliance", transferOnly: true, logo: gfav("flytap.com") },
  thai:           { days: 339, notes: "", iata: "TG", name: "Thai Airways Royal Orchid Plus", alliance: "Star Alliance", transferOnly: true, logo: gfav("thaiairways.com") },
  airindia:       { days: 330, notes: "Estimate", iata: "AI", name: "Air India Maharaja Club", alliance: "Star Alliance", transferOnly: true, logo: gfav("airindia.com") },
  korean:         { days: 361, notes: "", iata: "KE", name: "Korean Air SkyPass", alliance: "SkyTeam", transferOnly: true, logo: gfav("koreanair.com") },
  asiana:         { days: 361, notes: "", iata: "OZ", name: "Asiana Club", alliance: "Star Alliance", transferOnly: true, logo: gfav("flyasiana.com") },
};

const BANKS = {
  chase_ur: { name: "Chase Ultimate Rewards", shortName: "Chase UR", color: "#1a73e8", currency: "UR", url: "https://ultimaterewardspoints.chase.com", logo: gfav("chase.com"), darkLogo: false },
  amex_mr: { name: "Amex Membership Rewards", shortName: "Amex MR", color: "#006fcf", currency: "MR", url: "https://global.americanexpress.com/rewards", logo: gfav("americanexpress.com"), darkLogo: false },
  capital_one: { name: "Capital One Miles", shortName: "Capital One", color: "#d03027", currency: "miles", url: "https://www.capitalone.com/credit-cards/rewards", logo: "https://www.capitalone.com/apple-touch-icon.png", darkLogo: false },
  bilt: { name: "Bilt Rewards", shortName: "Bilt", color: "#e8927c", currency: "points", url: "https://www.biltrewards.com", logo: gfav("biltrewards.com"), darkLogo: true },
  citi_typ: { name: "Citi ThankYou Points", shortName: "Citi TYP", color: "#0066b2", currency: "TYP", url: "https://www.thankyou.com", logo: gfav("citi.com"), darkLogo: false },
  wells_fargo: { name: "Wells Fargo Rewards", shortName: "Wells Fargo", color: "#cd1309", currency: "points", url: "https://www.wellsfargo.com/rewards", logo: gfav("wellsfargo.com"), darkLogo: false },
  rove: { name: "Rove Miles", shortName: "Rove", color: "#7c3aed", currency: "miles", url: "https://www.rovemiles.com", logo: gfav("rovemiles.com"), darkLogo: true },
};

// ─── Formatters ─────────────────────────────────────────────────────────────
const fmt = {
  miles: (n) => (n ? `${(n / 1000).toFixed(1)}k` : "—"),
  milesFull: (n) => (n ? n.toLocaleString() : "—"),
  taxes: (n) => (n != null ? `$${Math.round(n)}` : "—"),
  date: (d) => {
    if (!d) return "—";
    const dt = new Date(d + "T00:00:00");
    return dt.toLocaleDateString("en-US", { month: "short", day: "numeric", year: "numeric", weekday: "short" });
  },
  dateFull: (d) => {
    if (!d) return "—";
    const dt = new Date(d + "T00:00:00");
    return dt.toLocaleDateString("en-US", { month: "short", day: "numeric", year: "numeric", weekday: "short" });
  },
  ago: (ts) => {
    if (!ts) return "never";
    const diff = Date.now() - new Date(ts).getTime();
    const mins = Math.floor(diff / 60000);
    if (mins < 60) return `${mins}m ago`;
    const hrs = Math.floor(mins / 60);
    if (hrs < 24) return `${hrs}h ago`;
    return `${Math.floor(hrs / 24)}d ago`;
  },
};

// ─── GlowButton ─────────────────────────────────────────────────────────────
function GlowButton({ children, onClick, active, small, disabled, style, color }) {
  const bg = color || theme.accent;
  return (
    <button onClick={onClick} disabled={disabled} style={{
      background: active ? bg : "transparent",
      color: active ? "#fff" : theme.textMuted,
      border: `1px solid ${active ? bg : theme.border}`,
      borderRadius: 8, padding: small ? "6px 12px" : "10px 20px",
      fontSize: small ? 13 : 14, fontWeight: 600,
      cursor: disabled ? "not-allowed" : "pointer",
      transition: "all 0.2s", opacity: disabled ? 0.5 : 1,
      fontFamily: "inherit",
      boxShadow: active ? `0 0 20px ${bg}30` : "none",
      ...style,
    }}>{children}</button>
  );
}

// ─── Input ───────────────────────────────────────────────────────────────────
function Input({ value, onChange, placeholder, style: extraStyle, type = "text", min, max, "aria-label": ariaLabel, ...rest }) {
  return (
    <input type={type} value={value} onChange={(e) => onChange(e.target.value)}
      placeholder={placeholder} min={min} max={max} aria-label={ariaLabel || placeholder} {...rest}
      style={{
        background: theme.surface, color: theme.text,
        border: `1px solid ${theme.border}`, borderRadius: 8,
        padding: "10px 14px", fontSize: 14, fontFamily: "inherit",
        width: "100%", boxSizing: "border-box",
        transition: "border-color 0.2s", colorScheme: "dark", ...extraStyle,
      }}
      onFocus={(e) => (e.target.style.borderColor = theme.accent)}
      onBlur={(e) => (e.target.style.borderColor = theme.border)}
    />
  );
}

// ─── Section Card ───────────────────────────────────────────────────────────
function Section({ title, icon, children, actions, collapsed, onToggle }) {
  return (
    <div style={{ background: theme.surface, border: `1px solid ${theme.border}`, borderRadius: 12, marginBottom: 16, overflow: "hidden" }}>
      <div onClick={onToggle} role={onToggle ? "button" : undefined} tabIndex={onToggle ? 0 : undefined} aria-expanded={onToggle ? !collapsed : undefined} onKeyDown={onToggle ? (e) => { if (e.key === "Enter" || e.key === " ") { e.preventDefault(); onToggle(); } } : undefined} style={{
        padding: "14px 20px", display: "flex", justifyContent: "space-between",
        alignItems: "center", cursor: onToggle ? "pointer" : "default",
        borderBottom: collapsed ? "none" : `1px solid ${theme.border}`,
      }}>
        <div style={{ display: "flex", alignItems: "center", gap: 8 }}>
          <span style={{ fontSize: 16 }}>{icon}</span>
          <span style={{ fontSize: 15, fontWeight: 600 }}>{title}</span>
        </div>
        <div style={{ display: "flex", alignItems: "center", gap: 8 }}>
          {actions}
          {onToggle && <span style={{ color: theme.textDim, fontSize: 12 }}>{collapsed ? "▸" : "▾"}</span>}
        </div>
      </div>
      {!collapsed && <div style={{ padding: 20 }}>{children}</div>}
    </div>
  );
}

// ─── StatusDot ──────────────────────────────────────────────────────────────
function StatusDot({ status }) {
  const colors = { healthy: theme.success, warning: theme.warning, error: theme.danger, stale: theme.warning, offline: theme.textDim };
  return (
    <span style={{
      display: "inline-block", width: 8, height: 8, borderRadius: "50%",
      background: colors[status] || theme.textDim, marginRight: 6,
      boxShadow: status === "healthy" ? `0 0 6px ${theme.success}60` : "none",
    }} />
  );
}

// ─── Toggle ─────────────────────────────────────────────────────────────────
function Toggle({ checked, onChange, label }) {
  return (
    <label style={{ display: "flex", alignItems: "center", gap: 10, cursor: "pointer" }}>
      <div onClick={() => onChange(!checked)} style={{
        width: 40, height: 22, borderRadius: 11,
        background: checked ? theme.accent : theme.border,
        position: "relative", transition: "background 0.2s", cursor: "pointer",
      }}>
        <div style={{
          width: 18, height: 18, borderRadius: 9, background: "#fff",
          position: "absolute", top: 2, left: checked ? 20 : 2,
          transition: "left 0.2s", boxShadow: "0 1px 3px rgba(0,0,0,0.3)",
        }} />
      </div>
      {label && <span style={{ fontSize: 14, color: theme.text }}>{label}</span>}
    </label>
  );
}

// ─── TripDetailsPanel (Seats.aero-style flight detail) ─────────────────────
function TripDetailsPanel({ availabilityId, flight, authToken, onClose }) {
  const [data, setData] = useState(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);
  const [selectedCabin, setSelectedCabin] = useState(null);

  useEffect(() => {
    if (!availabilityId) return;
    setLoading(true);
    setError(null);
    const headers = {};
    if (authToken) headers["Authorization"] = `Bearer ${authToken}`;
    fetch(`${API}/api/trips/${availabilityId}`, { headers })
      .then(r => {
        if (r.status === 429) return r.json().then(d => { throw new Error(d.error || "Rate limit reached"); });
        if (!r.ok) return r.json().then(d => { throw new Error(d.error || `Error ${r.status}`); });
        return r.json();
      })
      .then(d => {
        setData(d);
        setLoading(false);
      })
      .catch(e => { setError(e.message); setLoading(false); });
  }, [availabilityId]);

  if (loading) return (
    <div style={{ padding: 20, textAlign: "center", color: theme.textDim, background: theme.surface, borderRadius: "0 0 12px 12px", border: `1px solid ${theme.accent}33`, borderTop: "none" }}>
      <span style={{ animation: "spin 1s linear infinite", display: "inline-block", fontSize: 18 }}>✈</span>
      <div style={{ fontSize: 12, marginTop: 6 }}>Loading flight details...</div>
    </div>
  );
  if (error) return (
    <div style={{ padding: 16, textAlign: "center", color: theme.warning, background: theme.surface, borderRadius: "0 0 12px 12px", border: `1px solid ${theme.warning}33`, borderTop: "none", fontSize: 13 }}>
      ⚠ {error}
    </div>
  );
  if (!data || !data.trips || data.trips.length === 0) return (
    <div style={{ padding: 16, textAlign: "center", color: theme.textDim, background: theme.surface, borderRadius: "0 0 12px 12px", border: `1px solid ${theme.border}`, borderTop: "none", fontSize: 13 }}>
      No trip details available for this flight.
    </div>
  );

  const trips = data.trips;
  const meta = data.meta || {};

  // Group trips by cabin
  const cabinGroups = {};
  trips.forEach(t => {
    const cab = (t.Cabin || "unknown").toLowerCase();
    if (!cabinGroups[cab]) cabinGroups[cab] = [];
    cabinGroups[cab].push(t);
  });
  const availCabins = Object.keys(cabinGroups);
  const activeCabin = selectedCabin || availCabins[0] || "economy";
  const activeTrips = cabinGroups[activeCabin] || trips;
  const cabinDisplayMap = { economy: "Economy", premium_economy: "Premium", business: "Business", first: "First" };

  const formatTime = (iso) => {
    if (!iso) return "";
    try {
      const d = new Date(iso);
      return d.toLocaleTimeString("en-US", { hour: "2-digit", minute: "2-digit", hour12: false });
    } catch { return iso.slice(11, 16); }
  };
  const formatDuration = (mins) => {
    if (!mins) return "";
    const h = Math.floor(mins / 60);
    const m = mins % 60;
    return `${h}h ${m}m`;
  };

  return (
    <div style={{ background: theme.surface, border: `1px solid ${theme.accent}33`, borderTop: "none", borderRadius: "0 0 12px 12px", overflow: "hidden", animation: "fadeIn 0.2s ease" }}>
      <style>{`@keyframes fadeIn { from { opacity: 0; transform: translateY(-8px); } to { opacity: 1; transform: translateY(0); } }`}</style>
      {/* Header */}
      <div style={{ padding: "12px 16px", borderBottom: `1px solid ${theme.border}33`, display: "flex", justifyContent: "space-between", alignItems: "center" }}>
        <div style={{ display: "flex", alignItems: "center", gap: 10 }}>
          <span style={{ fontSize: 14, fontWeight: 700 }}>{meta.origin} ✈ {meta.destination}</span>
          <span style={{ fontSize: 11, color: theme.textDim }}>{meta.source ? (PROGRAMS[meta.source]?.program_name || meta.source) : ""}</span>
          {data.cached && <span style={{ fontSize: 9, color: theme.textDim, background: theme.card, padding: "2px 6px", borderRadius: 4 }}>cached</span>}
        </div>
        <div style={{ display: "flex", alignItems: "center", gap: 8 }}>
          {data.used_today != null && <span style={{ fontSize: 10, color: theme.textDim }}>{data.used_today}/{data.daily_limit} lookups today</span>}
          <button onClick={onClose} style={{ background: "none", border: "none", color: theme.textDim, cursor: "pointer", fontSize: 16, padding: "2px 6px", fontFamily: "inherit" }}>✕</button>
        </div>
      </div>
      {/* Cabin tabs */}
      {availCabins.length > 1 && (
        <div style={{ display: "flex", gap: 2, padding: "8px 16px 0", borderBottom: `1px solid ${theme.border}22` }}>
          {availCabins.map(c => (
            <button key={c} onClick={() => setSelectedCabin(c)} style={{
              padding: "6px 12px", borderRadius: "6px 6px 0 0", fontSize: 12, fontWeight: 600, cursor: "pointer",
              border: activeCabin === c ? `1px solid ${theme.accent}` : `1px solid transparent`,
              borderBottom: "none",
              background: activeCabin === c ? theme.accent + "20" : "transparent",
              color: activeCabin === c ? theme.accent : theme.textMuted, fontFamily: "inherit",
            }}>{cabinDisplayMap[c] || c} ({cabinGroups[c].length})</button>
          ))}
        </div>
      )}
      {/* Trip list */}
      <div style={{ maxHeight: 400, overflowY: "auto" }}>
        {activeTrips.map((trip, ti) => {
          const segs = trip.AvailabilitySegments || trip.segments || [];
          segs.sort((a, b) => (a.Order || 0) - (b.Order || 0));
          const stops = trip.Stops ?? (segs.length - 1);
          const dur = trip.TotalDuration;
          const miles = trip.MileageCost;
          const taxes = trip.TotalTaxes;
          const taxCur = trip.TaxesCurrencySymbol || trip.TaxesCurrency || "";
          const fltNums = trip.FlightNumbers || segs.map(s => s.FlightNumber).filter(Boolean).join(", ");
          const routing = segs.map(s => s.OriginAirport).concat(segs.length > 0 ? [segs[segs.length - 1].DestinationAirport] : []).join(" → ");
          const fareClass = segs.map(s => s.FareClass).filter(Boolean).join(", ");
          const depTime = trip.DepartsAt || (segs[0] && segs[0].DepartsAt) || "";
          const arrTime = trip.ArrivesAt || (segs.length > 0 && segs[segs.length - 1].ArrivesAt) || "";
          const stopsColor = stops === 0 ? theme.success : stops === 1 ? "#fbbf24" : "#ef4444";
          const stopsLabel = stops === 0 ? "Direct" : `${stops} stop${stops > 1 ? "s" : ""}`;

          return (
            <div key={ti} style={{ padding: "12px 16px", borderBottom: `1px solid ${theme.border}22`, transition: "background 0.15s" }}
              onMouseEnter={e => e.currentTarget.style.background = theme.card}
              onMouseLeave={e => e.currentTarget.style.background = "transparent"}>
              {/* Main row: time, routing, duration, stops */}
              <div style={{ display: "flex", alignItems: "center", gap: 12, marginBottom: 6, flexWrap: "wrap" }}>
                {depTime && <span style={{ fontSize: 16, fontWeight: 700, fontFamily: "monospace" }}>{formatTime(depTime)}</span>}
                <span style={{ fontSize: 12, color: theme.textMuted, flex: 1, minWidth: 120 }}>{routing}</span>
                {arrTime && <span style={{ fontSize: 12, color: theme.textDim, fontFamily: "monospace" }}>{formatTime(arrTime)}</span>}
                {dur > 0 && <span style={{ fontSize: 11, color: theme.textDim }}>⏱ {formatDuration(dur)}</span>}
                <span style={{ fontSize: 10, fontWeight: 700, color: "#fff", background: stopsColor, padding: "2px 8px", borderRadius: 4 }}>{stopsLabel}</span>
              </div>
              {/* Second row: flight numbers, fare class, miles, taxes */}
              <div style={{ display: "flex", alignItems: "center", gap: 12, flexWrap: "wrap" }}>
                {fltNums && <span style={{ fontSize: 11, color: theme.accent, fontFamily: "monospace" }}>{fltNums}</span>}
                {fareClass && <span style={{ fontSize: 11, color: theme.textDim }}>Class: {fareClass}</span>}
                {miles > 0 && <span style={{ fontSize: 13, fontWeight: 700, color: cabinInfo.J?.color || theme.accent }}>{miles.toLocaleString()} pts</span>}
                {taxes > 0 && <span style={{ fontSize: 11, color: theme.textDim }}>+ {taxCur}{(taxes / 100).toFixed(2)}</span>}
                {trip.RemainingSeats > 0 && <span style={{ fontSize: 11, color: trip.RemainingSeats <= 2 ? theme.warning : theme.textDim }}>{trip.RemainingSeats} seat{trip.RemainingSeats !== 1 ? "s" : ""}</span>}
              </div>
              {/* Segment detail rows */}
              {segs.length > 1 && (
                <div style={{ marginTop: 8, paddingLeft: 8, borderLeft: `2px solid ${theme.border}33` }}>
                  {segs.map((seg, si) => (
                    <div key={si} style={{ display: "flex", gap: 10, alignItems: "center", padding: "3px 0", fontSize: 11, color: theme.textDim }}>
                      <span style={{ fontFamily: "monospace", color: theme.textMuted, minWidth: 50 }}>{seg.FlightNumber || ""}</span>
                      <span>{seg.OriginAirport} → {seg.DestinationAirport}</span>
                      {seg.DepartsAt && <span style={{ fontFamily: "monospace" }}>{formatTime(seg.DepartsAt)}-{formatTime(seg.ArrivesAt)}</span>}
                      {seg.AircraftName && <span style={{ color: theme.textDim + "88" }}>{seg.AircraftName} ({seg.AircraftCode})</span>}
                      {seg.FareClass && <span>({seg.FareClass})</span>}
                    </div>
                  ))}
                </div>
              )}
            </div>
          );
        })}
      </div>
      {/* Footer with booking links */}
      {trips[0]?.booking_links && trips[0].booking_links.length > 0 && (
        <div style={{ padding: "10px 16px", borderTop: `1px solid ${theme.border}33`, display: "flex", gap: 8, flexWrap: "wrap" }}>
          {trips[0].booking_links.map((bl, i) => (
            <a key={i} href={bl.url || bl.Url} target="_blank" rel="noopener noreferrer" style={{
              fontSize: 12, fontWeight: 600, color: "#fff", background: theme.accent, padding: "6px 14px", borderRadius: 6,
              textDecoration: "none", display: "inline-flex", alignItems: "center", gap: 4, transition: "opacity 0.2s",
            }} onMouseEnter={e => e.target.style.opacity = 0.8} onMouseLeave={e => e.target.style.opacity = 1}>
              🔗 {bl.label || bl.Label || "Book"}
            </a>
          ))}
        </div>
      )}
    </div>
  );
}

// ─── FlightCard ─────────────────────────────────────────────────────────────
function FlightCard({ flight, cabin, airportMap, expanded, onToggle, optionsData, optionsLoading, programCount, tripExpanded, onTripToggle, authToken }) {
  const am = airportMap || {};
  const info = cabinInfo[cabin] || cabinInfo.J;
  const prog = PROGRAMS[flight.source] || { name: flight.source, alliance: "Independent" };
  const allianceColor = allianceColors[prog.alliance] || "#6b7280";
  return (
    <div>
      <div style={{
        background: theme.card, border: `1px solid ${expanded ? theme.accent + "66" : theme.border}`, borderRadius: expanded ? "12px 12px 0 0" : 12,
        padding: 16, display: "grid", gridTemplateColumns: "1fr auto", gap: 12,
        transition: "all 0.2s", cursor: "pointer",
      }}
        onClick={onToggle}
        onMouseEnter={(e) => { e.currentTarget.style.borderColor = expanded ? theme.accent + "66" : theme.borderLight; e.currentTarget.style.transform = "translateY(-1px)"; }}
        onMouseLeave={(e) => { e.currentTarget.style.borderColor = expanded ? theme.accent + "66" : theme.border; e.currentTarget.style.transform = "translateY(0)"; }}
      >
        <div>
          <div style={{ display: "flex", alignItems: "center", gap: 8, marginBottom: 6 }}>
            <span style={{ display: "inline-flex", flexDirection: "column", alignItems: "center" }}>
              <span style={{ fontSize: 22, fontWeight: 700, letterSpacing: 1 }}>{flight.origin}</span>
              {am[flight.origin] && <span style={{ fontSize: 10, color: theme.textDim, fontWeight: 400 }}>{am[flight.origin]}</span>}
            </span>
            <span style={{ color: theme.textDim }}>{flight.direct ? "——✈——" : "---◆---"}</span>
            <span style={{ display: "inline-flex", flexDirection: "column", alignItems: "center" }}>
              <span style={{ fontSize: 22, fontWeight: 700, letterSpacing: 1 }}>{flight.destination}</span>
              {am[flight.destination] && <span style={{ fontSize: 10, color: theme.textDim, fontWeight: 400 }}>{am[flight.destination]}</span>}
            </span>
            {flight.direct && <span style={{ fontSize: 10, color: theme.success, fontWeight: 600, textTransform: "uppercase", letterSpacing: 1, marginLeft: 4 }}>Direct</span>}
          </div>
          <div style={{ display: "flex", gap: 12, flexWrap: "wrap", alignItems: "center" }}>
            <span style={{ color: theme.textMuted, fontSize: 13 }}>{fmt.date(flight.date)}</span>
            <span style={{ fontSize: 11, fontWeight: 600, color: allianceColor, background: `${allianceColor}15`, padding: "2px 8px", borderRadius: 4 }}>{prog.name}</span>
            {programCount > 1 && <span style={{ fontSize: 10, fontWeight: 600, color: theme.accent, background: `${theme.accent}15`, padding: "2px 8px", borderRadius: 4 }}>{programCount} programs</span>}
            {flight.airlines && <span style={{ color: theme.textDim, fontSize: 12 }}>{flight.airlines}</span>}
            {flight.flight_numbers && <span style={{ color: theme.textDim, fontSize: 11, fontFamily: "monospace", opacity: 0.7 }}>{flight.flight_numbers}</span>}
          </div>
        </div>
        <div style={{ textAlign: "right", display: "flex", flexDirection: "column", justifyContent: "center", alignItems: "flex-end" }}>
          <div style={{ fontSize: 20, fontWeight: 700, color: info.color }}>{programCount > 1 ? "from " : ""}{fmt.milesFull(flight.mileage)}</div>
          <div style={{ fontSize: 12, color: theme.textDim }}>miles + {fmt.taxes(flight.taxes)}</div>
          {flight.seats > 0 && <div style={{ fontSize: 11, color: flight.seats <= 2 ? theme.warning : theme.textDim, marginTop: 2 }}>{flight.seats} seat{flight.seats !== 1 ? "s" : ""}</div>}
          <div style={{ display: "flex", gap: 8, marginTop: 4 }}>
            <div style={{ fontSize: 10, color: theme.accent }}>{expanded ? "▲ Hide options" : "▼ Compare programs"}</div>
            {flight.id && <button onClick={(e) => { e.stopPropagation(); if (onTripToggle) onTripToggle(); }} aria-expanded={tripExpanded} aria-label={tripExpanded ? "Hide flight details" : "Show flight details"} style={{ fontSize: 10, color: tripExpanded ? theme.warning : "#10b981", cursor: "pointer", fontWeight: 600, background: "none", border: "none", padding: 0, fontFamily: "inherit" }}>{tripExpanded ? "▲ Hide details" : "ℹ Flight Details"}</button>}
          </div>
        </div>
      </div>
      {expanded && !tripExpanded && (
        optionsLoading ? (
          <div style={{ background: theme.surface, border: `1px solid ${theme.accent}66`, borderTop: "none", borderRadius: "0 0 12px 12px", padding: 24, textAlign: "center", color: theme.textDim }}>
            <span style={{ animation: "spin 1s linear infinite", display: "inline-block" }}>✈</span> Loading booking options...
          </div>
        ) : optionsData ? (
          <div style={{ border: `1px solid ${theme.accent}66`, borderTop: "none", borderRadius: "0 0 12px 12px", overflow: "hidden" }}>
            <FlightDetailsPanel options={optionsData} searchCabin={cabin} />
          </div>
        ) : null
      )}
      {tripExpanded && (
        <TripDetailsPanel availabilityId={flight.id} flight={flight} authToken={authToken} onClose={() => { if (onTripToggle) onTripToggle(); }} />
      )}
    </div>
  );
}

// ─── ConnectionFlightCard ───────────────────────────────────────────────────
function ConnectionFlightCard({ conn, cabin, airportMap, expandedLeg, onToggleLeg, getOptionsData, getOptionsLoading }) {
  const am = airportMap || {};
  const info = cabinInfo[cabin] || cabinInfo.J;
  const legs = conn.legs || [];
  const numStops = conn.stops || legs.length - 1;
  const l1 = legs[0], lLast = legs[legs.length - 1];
  const hubList = conn.hub ? conn.hub.split(",") : [];
  const progs = legs.map(l => PROGRAMS[l.source] || { name: l.source, alliance: "Independent" });
  return (
    <div style={{
      background: theme.card, border: `1px solid ${numStops >= 2 ? theme.accent + "30" : theme.border}`, borderRadius: 12,
      padding: 16, transition: "all 0.2s", cursor: "default",
    }}
      onMouseEnter={(e) => { e.currentTarget.style.borderColor = theme.borderLight; e.currentTarget.style.transform = "translateY(-1px)"; }}
      onMouseLeave={(e) => { e.currentTarget.style.borderColor = theme.border; e.currentTarget.style.transform = "translateY(0)"; }}
    >
      {/* Header: route + total */}
      <div style={{ display: "grid", gridTemplateColumns: "1fr auto", gap: 12, marginBottom: 12 }}>
        <div>
          <div style={{ display: "flex", alignItems: "center", gap: 8, marginBottom: 4, flexWrap: "wrap" }}>
            <span style={{ fontSize: 22, fontWeight: 700, letterSpacing: 1 }}>{l1.origin}</span>
            {hubList.map((hub, i) => (
              <React.Fragment key={i}>
                <span style={{ color: theme.textDim }}>---◆---</span>
                <span style={{ fontSize: 16, fontWeight: 600, color: theme.accent }}>{hub}</span>
              </React.Fragment>
            ))}
            <span style={{ color: theme.textDim }}>---◆---</span>
            <span style={{ fontSize: 22, fontWeight: 700, letterSpacing: 1 }}>{lLast.destination}</span>
          </div>
          <div style={{ display: "flex", gap: 8, alignItems: "center", flexWrap: "wrap" }}>
            <span style={{ fontSize: 11, color: theme.accent, fontWeight: 600, background: `${theme.accent}15`, padding: "2px 8px", borderRadius: 4 }}>
              {numStops} stop{numStops !== 1 ? "s" : ""} · via {hubList.map(h => `${h}${am[h] ? ` (${am[h]})` : ""}`).join(", ")}
            </span>
            {conn.same_program && <span style={{ fontSize: 10, color: theme.success, fontWeight: 600, background: `${theme.success}15`, padding: "2px 6px", borderRadius: 4 }}>Same Program</span>}
          </div>
        </div>
        <div style={{ textAlign: "right", display: "flex", flexDirection: "column", justifyContent: "center" }}>
          <div style={{ fontSize: 20, fontWeight: 700, color: info.color }}>{fmt.milesFull(conn.total_mileage)}</div>
          <div style={{ fontSize: 12, color: theme.textDim }}>total + {fmt.taxes(conn.total_taxes)}</div>
        </div>
      </div>
      {/* Leg details */}
      <div style={{ borderTop: `1px solid ${theme.border}`, paddingTop: 10, display: "flex", flexDirection: "column", gap: 8 }}>
        {legs.map((leg, li) => {
          const prog = progs[li];
          const ac = allianceColors[prog.alliance] || "#6b7280";
          const isExpanded = expandedLeg === li;
          const legKey = `${leg.origin}|${leg.destination}|${leg.date}|${cabin || "J"}|${"one_way"}`;
          const optData = getOptionsData ? getOptionsData(legKey) : null;
          const optLoading = getOptionsLoading ? getOptionsLoading(legKey) : false;
          return (
            <div key={li}>
              <div style={{ display: "grid", gridTemplateColumns: "1fr auto", gap: 12, paddingBottom: li < legs.length - 1 && !isExpanded ? 8 : 0, borderBottom: li < legs.length - 1 && !isExpanded ? `1px dashed ${theme.border}` : "none" }}>
                <div>
                  <div style={{ display: "flex", alignItems: "center", gap: 6, marginBottom: 2 }}>
                    <span style={{ fontSize: 14, fontWeight: 600 }}>{leg.origin} → {leg.destination}</span>
                    <span style={{ fontSize: 10, color: theme.textDim }}>Leg {li + 1}</span>
                  </div>
                  <div style={{ display: "flex", gap: 8, flexWrap: "wrap", alignItems: "center" }}>
                    <span style={{ color: theme.textMuted, fontSize: 12 }}>{fmt.date(leg.date)}</span>
                    <span style={{ fontSize: 11, fontWeight: 600, color: ac, background: `${ac}15`, padding: "2px 6px", borderRadius: 4 }}>{prog.name}</span>
                    {leg.airlines && <span style={{ color: theme.textDim, fontSize: 12 }}>{leg.airlines}</span>}
                    {leg.flight_numbers && <span style={{ color: theme.textDim, fontSize: 11, fontFamily: "monospace", opacity: 0.7 }}>{leg.flight_numbers}</span>}
                  </div>
                </div>
                <div style={{ textAlign: "right" }}>
                  <div style={{ fontSize: 14, fontWeight: 600, color: info.color }}>{fmt.milesFull(leg.mileage)}</div>
                  <div style={{ fontSize: 11, color: theme.textDim }}>+ {fmt.taxes(leg.taxes)}</div>
                  {leg.seats > 0 && <div style={{ fontSize: 10, color: leg.seats <= 2 ? theme.warning : theme.textDim }}>{leg.seats} seat{leg.seats !== 1 ? "s" : ""}</div>}
                  <div onClick={(e) => { e.stopPropagation(); onToggleLeg && onToggleLeg(li); }} style={{ fontSize: 10, color: theme.accent, cursor: "pointer", marginTop: 2 }}>
                    {isExpanded ? "▲ Hide" : "▼ Options"}
                  </div>
                </div>
              </div>
              {isExpanded && (
                optLoading ? (
                  <div style={{ padding: 16, textAlign: "center", color: theme.textDim, fontSize: 12 }}>
                    <span style={{ animation: "spin 1s linear infinite", display: "inline-block" }}>✈</span> Loading options...
                  </div>
                ) : optData ? (
                  <FlightDetailsPanel options={optData} searchCabin={cabin} />
                ) : null
              )}
              {li === 0 && isExpanded && <div style={{ borderBottom: `1px dashed ${theme.border}`, marginTop: 8, marginBottom: 8 }} />}
            </div>
          );
        })}
      </div>
    </div>
  );
}

// ─── FlightDetailsPanel ─────────────────────────────────────────────────────
function FlightDetailsPanel({ options, searchCabin }) {
  const [selectedCabin, setSelectedCabin] = useState(searchCabin || "J");
  const [sortBy, setSortBy] = useState("miles"); // "miles" | "taxes"
  const [sortDir, setSortDir] = useState("asc");

  if (!options || !options.programs) return null;
  const programs = options.programs;
  const cheapest = options.cheapest_by_cabin || {};

  // Filter to programs that have availability in selected cabin
  const available = programs.filter(p => p.cabins[selectedCabin]?.available);
  const unavailable = programs.filter(p => !p.cabins[selectedCabin]?.available);

  // Sort
  const sorted = [...available].sort((a, b) => {
    const cabA = a.cabins[selectedCabin], cabB = b.cabins[selectedCabin];
    let va, vb;
    if (sortBy === "miles") { va = cabA.mileage ?? 999999; vb = cabB.mileage ?? 999999; }
    else { va = cabA.taxes ?? 999999; vb = cabB.taxes ?? 999999; }
    return sortDir === "asc" ? va - vb : vb - va;
  });

  const toggleSort = (col) => {
    if (sortBy === col) setSortDir(d => d === "asc" ? "desc" : "asc");
    else { setSortBy(col); setSortDir("asc"); }
  };

  const cabins = ["Y", "W", "J", "F"];
  const cabinLabels = { Y: "Economy", W: "Premium", J: "Business", F: "First" };

  const route = options.route || {};
  const tripType = options.trip_type || "one_way";
  const tripLabel = tripType === "round_trip" ? "Round Trip" : "One Way";

  return (
    <div style={{ background: theme.surface, border: `1px solid ${theme.border}`, borderRadius: 10, padding: 16, marginTop: 8, animation: "fadeIn 0.2s ease" }}>
      <style>{`@keyframes fadeIn { from { opacity: 0; transform: translateY(-8px); } to { opacity: 1; transform: translateY(0); } }`}</style>
      {/* Route header */}
      {route.origin && (
        <div style={{ display: "flex", alignItems: "center", gap: 10, marginBottom: 10, paddingBottom: 8, borderBottom: `1px solid ${theme.border}33`, flexWrap: "wrap" }}>
          <span style={{ fontSize: 14, fontWeight: 700, letterSpacing: 0.5 }}>{route.origin} → {route.destination}</span>
          <span style={{ fontSize: 12, color: theme.textMuted }}>{fmt.date(route.date)}</span>
          <span style={{ fontSize: 10, fontWeight: 600, color: tripType === "round_trip" ? theme.accent : theme.textDim, background: tripType === "round_trip" ? (theme.accent + "15") : (theme.textDim + "15"), padding: "2px 8px", borderRadius: 4 }}>{tripLabel}</span>
          <span style={{ fontSize: 11, color: theme.textDim }}>{sorted.length} program{sorted.length !== 1 ? "s" : ""} available</span>
        </div>
      )}
      {/* Cabin tabs */}
      <div style={{ display: "flex", gap: 4, marginBottom: 12 }}>
        {cabins.map(c => {
          const hasCab = programs.some(p => p.cabins[c]?.available);
          return (
            <button key={c} onClick={() => hasCab && setSelectedCabin(c)} style={{
              padding: "6px 14px", borderRadius: 6, fontSize: 12, fontWeight: 600, cursor: hasCab ? "pointer" : "default",
              border: selectedCabin === c ? `1px solid ${theme.accent}` : `1px solid ${theme.border}`,
              background: selectedCabin === c ? theme.accent : "transparent",
              color: selectedCabin === c ? "#fff" : hasCab ? theme.textMuted : theme.textDim + "44",
              opacity: hasCab ? 1 : 0.4, fontFamily: "inherit",
            }}>
              {cabinInfo[c]?.icon} {cabinLabels[c]}
            </button>
          );
        })}
      </div>
      {/* Sort controls */}
      <div style={{ display: "flex", gap: 12, marginBottom: 8 }}>
        <span style={{ fontSize: 11, color: theme.textDim }}>Sort by:</span>
        {["miles", "taxes"].map(col => (
          <button key={col} onClick={() => toggleSort(col)} style={{
            background: "none", border: "none", color: sortBy === col ? theme.accent : theme.textDim,
            fontSize: 11, cursor: "pointer", fontWeight: sortBy === col ? 700 : 400, fontFamily: "inherit", padding: 0,
          }}>
            {col === "miles" ? "Miles" : "Taxes"} {sortBy === col ? (sortDir === "asc" ? "↑" : "↓") : ""}
          </button>
        ))}
      </div>
      {/* Program table */}
      <div style={{ overflowX: "auto" }}>
        <table style={{ width: "100%", borderCollapse: "collapse", fontSize: 13 }}>
          <thead>
            <tr style={{ borderBottom: `1px solid ${theme.border}` }}>
              <th style={{ textAlign: "left", padding: "6px 8px", color: theme.textDim, fontWeight: 600, fontSize: 11 }}>Airline / Program</th>
              <th style={{ textAlign: "left", padding: "6px 8px", color: theme.textDim, fontWeight: 600, fontSize: 11 }}>Alliance</th>
              <th style={{ textAlign: "left", padding: "6px 8px", color: theme.textDim, fontWeight: 600, fontSize: 11 }}>Flight #</th>
              <th style={{ textAlign: "right", padding: "6px 8px", color: theme.textDim, fontWeight: 600, fontSize: 11, cursor: "pointer" }} onClick={() => toggleSort("miles")}>Miles {sortBy === "miles" ? (sortDir === "asc" ? "↑" : "↓") : ""}</th>
              <th style={{ textAlign: "right", padding: "6px 8px", color: theme.textDim, fontWeight: 600, fontSize: 11, cursor: "pointer" }} onClick={() => toggleSort("taxes")}>Taxes {sortBy === "taxes" ? (sortDir === "asc" ? "↑" : "↓") : ""}</th>
              <th style={{ textAlign: "right", padding: "6px 8px", color: theme.textDim, fontWeight: 600, fontSize: 11 }}>Seats</th>
              <th style={{ textAlign: "left", padding: "6px 8px", color: theme.textDim, fontWeight: 600, fontSize: 11 }}>Transfer From</th>
              <th style={{ textAlign: "center", padding: "6px 8px", color: theme.textDim, fontWeight: 600, fontSize: 11 }}>Book</th>
            </tr>
          </thead>
          <tbody>
            {sorted.map((p, i) => {
              const cab = p.cabins[selectedCabin];
              const isCheapest = cab.cheapest;
              const ac = allianceColors[p.alliance] || "#6b7280";
              return (
                <tr key={p.source} style={{ borderBottom: `1px solid ${theme.border}22`, background: isCheapest ? `${theme.success}08` : "transparent" }}>
                  <td style={{ padding: "8px 8px" }}>
                    <div style={{ display: "flex", alignItems: "center", gap: 8 }}>
                      <img src={`https://pics.avs.io/60/60/${p.iata || p.airlines || ""}.png`} alt={`${p.name || p.airlines || p.iata || "Airline"} logo`} style={{ width: 40, height: 40, borderRadius: 8, objectFit: "contain", background: theme.bg === darkTheme.bg ? "#ffffff" : "#e2e8f0", flexShrink: 0, padding: 2 }} onError={(e) => { e.target.style.display = "none"; }} />
                      <div>
                        <div style={{ display: "flex", alignItems: "center", gap: 5 }}>
                          {isCheapest && <span style={{ fontSize: 10, color: theme.success, fontWeight: 700, background: `${theme.success}15`, padding: "1px 5px", borderRadius: 3 }}>BEST</span>}
                          <span style={{ fontWeight: 600, fontSize: 13 }}>{p.airline || p.airlines || "—"}</span>
                        </div>
                        <div style={{ fontSize: 11, color: theme.textMuted, marginTop: 1 }}>{p.program_name}</div>
                      </div>
                    </div>
                  </td>
                  <td style={{ padding: "8px 8px" }}><span style={{ fontSize: 10, fontWeight: 600, color: ac, background: `${ac}15`, padding: "2px 6px", borderRadius: 4 }}>{p.alliance}</span></td>
                  <td style={{ padding: "8px 8px", color: theme.textDim, fontSize: 11, fontFamily: "monospace" }}>{p.flight_numbers || "—"}</td>
                  <td style={{ padding: "8px 8px", textAlign: "right", fontWeight: 700, color: isCheapest ? theme.success : cabinInfo[selectedCabin]?.color || theme.text }}>{fmt.milesFull(cab.mileage)}</td>
                  <td style={{ padding: "8px 8px", textAlign: "right", color: theme.textMuted }}>{fmt.taxes(cab.taxes)}</td>
                  <td style={{ padding: "8px 8px", textAlign: "right", color: cab.seats > 0 ? (cab.seats <= 2 ? theme.warning : theme.textDim) : theme.textDim, fontSize: 12 }}>{cab.seats > 0 ? `${cab.seats} seat${cab.seats !== 1 ? "s" : ""}` : "—"}</td>
                  <td style={{ padding: "8px 8px" }}>
                    <div style={{ display: "flex", gap: 4, flexWrap: "wrap" }}>
                      {p.transfer_partners.map(tp => (
                        <span key={tp.bank} style={{ fontSize: 9, padding: "2px 5px", borderRadius: 3, background: `${theme.accent}15`, color: theme.accent, whiteSpace: "nowrap" }} title={`${tp.bank_name}: ${tp.ratio} (${tp.speed})`}>
                          {tp.bank_name} {tp.ratio !== "1:1" ? tp.ratio : ""} {tp.speed === "Instant" ? "⚡" : ""}
                        </span>
                      ))}
                      {p.transfer_partners.length === 0 && <span style={{ fontSize: 10, color: theme.textDim }}>Direct only</span>}
                    </div>
                  </td>
                  <td style={{ padding: "8px 8px", textAlign: "center" }}>
                    {p.booking_url ? (
                      <a href={p.booking_url} target="_blank" rel="noopener noreferrer" style={{ color: theme.accent, fontSize: 11, textDecoration: "none", fontWeight: 600, display: "inline-flex", alignItems: "center", gap: 4, padding: "4px 10px", borderRadius: 6, background: `${theme.accent}10`, border: `1px solid ${theme.accent}33` }}
                        title={`Book ${route.origin}→${route.destination} ${fmt.date(route.date)} ${cabinLabels[selectedCabin]} ${tripLabel} via ${p.program_name} — ${cab.seats} seat${cab.seats !== 1 ? "s" : ""}`}>
                        Book ↗
                      </a>
                    ) : <span style={{ color: theme.textDim, fontSize: 11 }}>—</span>}
                  </td>
                </tr>
              );
            })}
            {unavailable.map(p => (
              <tr key={p.source} style={{ borderBottom: `1px solid ${theme.border}11`, opacity: 0.35 }}>
                <td style={{ padding: "6px 8px" }}>
                  <div style={{ display: "flex", alignItems: "center", gap: 6 }}>
                    <img src={`https://pics.avs.io/60/60/${p.iata || p.airlines || ""}.png`} alt={`${p.name || p.airlines || p.iata || "Airline"} logo`} style={{ width: 40, height: 40, borderRadius: 8, objectFit: "contain", background: theme.bg === darkTheme.bg ? "#ffffff" : "#e2e8f0", flexShrink: 0, padding: 2 }} onError={(e) => { e.target.style.display = "none"; }} />
                    <span style={{ fontSize: 12 }}>{p.airline || p.program_name}</span>
                  </div>
                </td>
                <td style={{ padding: "6px 8px" }}><span style={{ fontSize: 10, color: allianceColors[p.alliance] || "#6b7280" }}>{p.alliance}</span></td>
                <td colSpan={6} style={{ padding: "6px 8px", color: theme.textDim, fontSize: 11, textAlign: "center" }}>No {cabinLabels[selectedCabin]} availability</td>
              </tr>
            ))}
          </tbody>
        </table>
      </div>
      {sorted.length === 0 && <div style={{ textAlign: "center", padding: 16, color: theme.textDim, fontSize: 13 }}>No programs with {cabinLabels[selectedCabin]} availability on this route</div>}
    </div>
  );
}

// ─── EditableBalance ────────────────────────────────────────────────────────
function EditableBalance({ value, onSave, label, color, readOnly }) {
  const [editing, setEditing] = useState(false);
  const [draft, setDraft] = useState(value || "");

  useEffect(() => { setDraft(value || ""); }, [value]);

  const save = () => {
    const num = parseInt(draft.toString().replace(/,/g, ""), 10);
    onSave(isNaN(num) ? 0 : num);
    setEditing(false);
  };

  if (editing && !readOnly) {
    return (
      <div style={{ display: "flex", alignItems: "center", gap: 4 }}>
        <input
          autoFocus
          type="text"
          value={draft}
          onChange={e => setDraft(e.target.value)}
          onKeyDown={e => { if (e.key === "Enter") save(); if (e.key === "Escape") setEditing(false); }}
          onBlur={save}
          style={{ width: 90, padding: "3px 6px", fontSize: 13, background: theme.surface, border: `1px solid ${theme.accent}`, borderRadius: 4, color: theme.text, textAlign: "right" }}
          aria-label="Edit balance" placeholder="0"
        />
      </div>
    );
  }

  const display = value ? Number(value).toLocaleString() : "—";
  return (
    <div
      onClick={() => !readOnly && setEditing(true)}
      title={readOnly ? "Sign in to edit balance" : "Click to edit balance"}
      style={{ cursor: readOnly ? "default" : "pointer", textAlign: "right", minWidth: 80 }}
    >
      <div style={{ fontSize: 15, fontWeight: 700, color: value ? (color || theme.accent) : theme.textDim }}>{display}</div>
      <div style={{ fontSize: 10, color: theme.textDim, marginTop: 1 }}>{label || "miles"}</div>
    </div>
  );
}

// ─── ProgramCard ────────────────────────────────────────────────────────────
function ProgramCard({ slug, program, balance, onBalanceChange, readOnly }) {
  const allianceColor = allianceColors[program.alliance] || "#6b7280";
  return (
    <div style={{ background: theme.card, border: `1px solid ${theme.border}`, borderRadius: 10, padding: "14px 16px", display: "flex", alignItems: "center", justifyContent: "space-between" }}>
      {program.logo && <img src={program.logo} alt={`${program.name || "Program"} logo`} style={{ width: 52, height: 52, borderRadius: 8, marginRight: 10, objectFit: "contain", background: program.whiteBg ? "#fff" : program.darkLogo ? "rgba(255,255,255,0.12)" : "transparent", padding: 3, flexShrink: 0 }} onError={e => { e.target.style.display = "none"; }} />}
      <div style={{ flex: 1, minWidth: 0 }}>
        <div style={{ display: "flex", alignItems: "center", gap: 6 }}>
          <a href={program.url} target="_blank" rel="noopener noreferrer" style={{ fontWeight: 600, fontSize: 14, color: theme.text, textDecoration: "none" }}
            onMouseEnter={e => { e.target.style.color = theme.accent; e.target.style.textDecoration = "underline"; }}
            onMouseLeave={e => { e.target.style.color = theme.text; e.target.style.textDecoration = "none"; }}>
            {program.name}
          </a>
          <span style={{ fontSize: 10, color: theme.textDim }}>↗</span>
        </div>
        <span style={{ fontSize: 11, fontWeight: 600, color: allianceColor, background: `${allianceColor}15`, padding: "2px 8px", borderRadius: 4 }}>{program.alliance}</span>
      </div>
      <EditableBalance value={balance} onSave={val => onBalanceChange(slug, val)} label={program.currency || "miles"} color={allianceColor} readOnly={readOnly} />
    </div>
  );
}

// ─── StatCard ───────────────────────────────────────────────────────────────
function StatCard({ label, value, icon, color }) {
  return (
    <div style={{ background: theme.card, border: `1px solid ${theme.border}`, borderRadius: 12, padding: "20px 16px", textAlign: "center" }}>
      <div style={{ fontSize: 28, marginBottom: 4 }}>{icon}</div>
      <div style={{ fontSize: 24, fontWeight: 700, color: color || theme.text }}>{value}</div>
      <div style={{ fontSize: 12, color: theme.textDim, marginTop: 4, textTransform: "uppercase", letterSpacing: 1 }}>{label}</div>
    </div>
  );
}

// ─── ProgramRadar ──────────────────────────────────────────────────────────
function ProgramRadar() {
  const [radarData, setRadarData] = useState(null);
  const [selected, setSelected] = useState([]);
  const [hovered, setHovered] = useState(null);

  const AXES = [
    { key: "coverage", label: "Coverage", tip: "% of 365-day range with data" },
    { key: "volume", label: "Volume", tip: "Flight count relative to largest program" },
    { key: "routes", label: "Routes", tip: "Unique route count relative to most" },
    { key: "biz_value", label: "Biz Value", tip: "Business class pricing (higher = cheaper)" },
    { key: "econ_value", label: "Econ Value", tip: "Economy pricing (higher = cheaper)" },
    { key: "direct_pct", label: "Direct %", tip: "Percentage of direct flights" },
  ];

  const COLORS = [
    "#3b82f6", "#fbbf24", "#10b981", "#ef4444", "#a855f7",
    "#f97316", "#06b6d4", "#ec4899", "#84cc16", "#6366f1"
  ];

  useEffect(() => {
    (window.authFetch || fetch)(`${API}/api/admin/program-radar`)
      .then(r => { if (!r.ok) throw new Error("Not authorized"); return r.json(); })
      .then(d => {
        setRadarData(d.programs || []);
        // Auto-select top 3 by volume
        const top3 = (d.programs || []).slice(0, 3).map(p => p.source);
        setSelected(top3);
      })
      .catch(() => setRadarData(null));
  }, []);

  const toggleProgram = (source) => {
    setSelected(prev => {
      if (prev.includes(source)) {
        if (hovered === source) setHovered(null);
        return prev.filter(s => s !== source);
      }
      if (prev.length >= 10) return prev; // cap at 10
      return [...prev, source];
    });
  };

  if (!radarData) return null;

  // SVG radar chart geometry
  const SIZE = 340, CX = SIZE / 2, CY = SIZE / 2, R = 130;
  const numAxes = AXES.length;

  // Compute angle for each axis (starting from top, clockwise)
  const angleFor = (i) => (Math.PI * 2 * i / numAxes) - Math.PI / 2;

  // Point on radar at given axis index and value (0-100)
  const pointAt = (axisIdx, value) => {
    const a = angleFor(axisIdx);
    const r = (value / 100) * R;
    return { x: CX + r * Math.cos(a), y: CY + r * Math.sin(a) };
  };

  // Build polygon path for a program
  const buildPath = (metrics) => {
    return AXES.map((ax, i) => {
      const p = pointAt(i, metrics[ax.key] || 0);
      return `${i === 0 ? "M" : "L"}${p.x.toFixed(1)},${p.y.toFixed(1)}`;
    }).join(" ") + " Z";
  };

  // Grid rings
  const rings = [20, 40, 60, 80, 100];

  // Selected programs data
  const selectedPrograms = selected.map(s => radarData.find(p => p.source === s)).filter(Boolean);

  return (
    <div style={{ marginTop: 32, background: theme.surface, border: `1px solid ${theme.border}`, borderRadius: 12, padding: 24 }}>
      <div style={{ display: "flex", justifyContent: "space-between", alignItems: "center", marginBottom: 16 }}>
        <h3 style={{ fontSize: 16, fontWeight: 700, margin: 0 }}>Program Comparison Radar</h3>
        <span style={{ fontSize: 11, color: theme.textDim }}>{selected.length} selected · click to add/remove</span>
      </div>

      <div style={{ display: "flex", gap: 24 }}>
        {/* Radar Chart */}
        <div style={{ flexShrink: 0 }}>
          <svg width={SIZE} height={SIZE} style={{ display: "block" }}>
            {/* Background */}
            <rect x={0} y={0} width={SIZE} height={SIZE} rx={8} fill={theme.bg} />

            {/* Grid rings */}
            {rings.map(v => {
              const pts = AXES.map((_, i) => pointAt(i, v));
              const d = pts.map((p, i) => `${i === 0 ? "M" : "L"}${p.x.toFixed(1)},${p.y.toFixed(1)}`).join(" ") + " Z";
              return <path key={v} d={d} fill="none" stroke={theme.border} strokeWidth={0.5} />;
            })}

            {/* Axis lines */}
            {AXES.map((_, i) => {
              const p = pointAt(i, 100);
              return <line key={i} x1={CX} y1={CY} x2={p.x} y2={p.y} stroke={theme.border} strokeWidth={0.5} />;
            })}

            {/* Axis labels */}
            {AXES.map((ax, i) => {
              const p = pointAt(i, 118);
              return (
                <text key={ax.key} x={p.x} y={p.y} textAnchor="middle" dominantBaseline="middle"
                  fill={theme.textDim} fontSize={10} fontFamily="Arial" fontWeight={500}>
                  {ax.label}
                </text>
              );
            })}

            {/* Ring value labels on first axis */}
            {rings.map(v => {
              const p = pointAt(0, v);
              return (
                <text key={"rv" + v} x={p.x + 3} y={p.y - 3} fill={theme.textDim} fontSize={7} fontFamily="Arial">
                  {v}
                </text>
              );
            })}

            {/* Program polygons */}
            {selectedPrograms.map((prog, idx) => {
              const color = COLORS[idx % COLORS.length];
              const isHov = hovered === prog.source;
              return (
                <g key={prog.source}>
                  <path d={buildPath(prog.metrics)} fill={color} fillOpacity={isHov ? 0.25 : 0.12}
                    stroke={color} strokeWidth={isHov ? 2.5 : 1.5} strokeLinejoin="round" />
                  {/* Dots at each vertex */}
                  {AXES.map((ax, i) => {
                    const p = pointAt(i, prog.metrics[ax.key] || 0);
                    return <circle key={i} cx={p.x} cy={p.y} r={isHov ? 3.5 : 2} fill={color} opacity={isHov ? 1 : 0.7} />;
                  })}
                </g>
              );
            })}
          </svg>
        </div>

        {/* Program selector + stats */}
        <div style={{ flex: 1, minWidth: 0, maxHeight: SIZE, overflowY: "auto" }}>
          {/* Selected programs legend */}
          {selectedPrograms.length > 0 && (
            <div style={{ marginBottom: 12, paddingBottom: 12, borderBottom: `1px solid ${theme.border}` }}>
              {selectedPrograms.map((prog, idx) => {
                const color = COLORS[idx % COLORS.length];
                const pInfo = PROGRAMS[prog.source] || {};
                const isHov = hovered === prog.source;
                return (
                  <div key={prog.source}
                    onMouseEnter={() => setHovered(prog.source)}
                    onMouseLeave={() => setHovered(null)}
                    style={{ display: "flex", alignItems: "center", gap: 8, padding: "5px 8px", marginBottom: 2,
                      borderRadius: 6, cursor: "pointer", background: isHov ? theme.surfaceHover : "transparent",
                      transition: "background 0.15s" }}
                    onClick={() => toggleProgram(prog.source)}>
                    <div style={{ width: 10, height: 10, borderRadius: 2, background: color, flexShrink: 0 }} />
                    <span style={{ fontSize: 12, fontWeight: 600, color: theme.text, flex: 1 }}>
                      {pInfo.name || prog.source}
                    </span>
                    <span style={{ fontSize: 10, color: theme.textDim }}>✕</span>
                  </div>
                );
              })}
            </div>
          )}

          {/* Hovering: show raw stats */}
          {hovered && (() => {
            const prog = radarData.find(p => p.source === hovered);
            if (!prog) return null;
            const raw = prog.raw;
            return (
              <div style={{ padding: 10, background: theme.card, borderRadius: 8, border: `1px solid ${theme.border}`, marginBottom: 12, fontSize: 11 }}>
                <div style={{ fontWeight: 700, marginBottom: 6, color: theme.text }}>
                  {(PROGRAMS[hovered] || {}).name || hovered}
                </div>
                <div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: "4px 12px", color: theme.textMuted }}>
                  <span>Coverage: <b style={{ color: theme.text }}>{raw.coverage_pct}%</b></span>
                  <span>Days: <b style={{ color: theme.text }}>{raw.days_with_data}/366</b></span>
                  <span>Routes: <b style={{ color: theme.text }}>{raw.route_count?.toLocaleString()}</b></span>
                  <span>Flights: <b style={{ color: theme.text }}>{raw.total_rows?.toLocaleString()}</b></span>
                  <span>Avg Biz: <b style={{ color: "#fbbf24" }}>{raw.avg_j_miles ? raw.avg_j_miles.toLocaleString() : "—"}</b></span>
                  <span>Avg Econ: <b style={{ color: "#10b981" }}>{raw.avg_y_miles ? raw.avg_y_miles.toLocaleString() : "—"}</b></span>
                  <span>Direct: <b style={{ color: theme.text }}>{raw.direct_pct}%</b></span>
                  <span>Latest: <b style={{ color: theme.text }}>{raw.latest_date || "—"}</b></span>
                </div>
              </div>
            );
          })()}

          {/* All programs list */}
          <div style={{ display: "flex", flexWrap: "wrap", gap: 4 }}>
            {radarData.map(prog => {
              const isSelected = selected.includes(prog.source);
              const pInfo = PROGRAMS[prog.source] || {};
              return (
                <button key={prog.source}
                  onClick={() => toggleProgram(prog.source)}
                  onMouseEnter={() => isSelected && setHovered(prog.source)}
                  onMouseLeave={() => isSelected && setHovered(null)}
                  style={{ padding: "4px 10px", fontSize: 11, borderRadius: 6, cursor: "pointer",
                    border: `1px solid ${isSelected ? theme.accent + "60" : theme.border}`,
                    background: isSelected ? theme.accent + "20" : "transparent",
                    color: isSelected ? theme.accent : theme.textMuted,
                    fontWeight: isSelected ? 600 : 400,
                    transition: "all 0.15s" }}>
                  {pInfo.name ? pInfo.name.split(" ").pop() : prog.source}
                </button>
              );
            })}
          </div>
        </div>
      </div>
    </div>
  );
}

// ─── AirportMap ─────────────────────────────────────────────────────────────
function AirportMap() {
  const mapRef = useRef(null);
  const mapInstance = useRef(null);
  const markersLayer = useRef(null);
  const tileLayerRef = useRef(null);
  const [airportData, setAirportData] = useState(null);
  const [hoveredAirport, setHoveredAirport] = useState(null);
  const [loading, setLoading] = useState(true);

  // Fetch airport data
  useEffect(() => {
    fetch(`${API}/api/airports/map`)
      .then(r => r.json())
      .then(data => { setAirportData(data); setLoading(false); })
      .catch(() => setLoading(false));
  }, []);

  // Initialize map
  useEffect(() => {
    if (!mapRef.current || mapInstance.current) return;
    if (typeof L === "undefined") { console.error("Leaflet not loaded"); return; }
    try {
      const map = L.map(mapRef.current, {
        zoomControl: false,
        attributionControl: true,
        worldCopyJump: true,
      }).setView([30, 0], 3.5);

      L.control.zoom({ position: "bottomright" }).addTo(map);

      const isDark = (localStorage.getItem("theme_mode") || "dark") === "dark";
      const tileUrl = isDark ? "https://{s}.basemaps.cartocdn.com/dark_all/{z}/{x}/{y}{r}.png" : "https://{s}.basemaps.cartocdn.com/light_all/{z}/{x}/{y}{r}.png";
      tileLayerRef.current = L.tileLayer(tileUrl, {
        attribution: '&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> &copy; <a href="https://carto.com/">CARTO</a>',
        maxZoom: 18,
        subdomains: "abcd",
      }).addTo(map);

      mapInstance.current = map;
      markersLayer.current = L.layerGroup().addTo(map);

      // Force Leaflet to recalculate container size after render
      const timer = setTimeout(() => { try { if (mapInstance.current) mapInstance.current.invalidateSize(); } catch(e) {} }, MAP_INVALIDATE_DELAY_MS);

      return () => { clearTimeout(timer); try { map.remove(); } catch(e) {} mapInstance.current = null; };
    } catch (e) { console.error("AirportMap init error:", e); }
  }, []);

  // Swap tile layer on theme change
  useEffect(() => {
    const handler = () => {
      if (!mapInstance.current || !tileLayerRef.current) return;
      const isDark = (localStorage.getItem("theme_mode") || "dark") === "dark";
      const newUrl = isDark ? "https://{s}.basemaps.cartocdn.com/dark_all/{z}/{x}/{y}{r}.png" : "https://{s}.basemaps.cartocdn.com/light_all/{z}/{x}/{y}{r}.png";
      mapInstance.current.removeLayer(tileLayerRef.current);
      tileLayerRef.current = L.tileLayer(newUrl, { maxZoom: 18, subdomains: "abcd" }).addTo(mapInstance.current);
    };
    window.addEventListener("theme-change", handler);
    return () => window.removeEventListener("theme-change", handler);
  }, []);

  // Render airport markers (zoom-responsive)
  useEffect(() => {
    if (!mapInstance.current || !airportData || !markersLayer.current) return;
    markersLayer.current.clearLayers();

    const map = mapInstance.current;
    const airports = airportData.airports || [];
    const maxFlights = airportData.max_flights || 1;

    // Rank airports by flight volume for tiered coloring
    const sorted = [...airports].filter(a => a.flights > 0).sort((a, b) => b.flights - a.flights);
    const top50Set = new Set(sorted.slice(0, 50).map(a => a.iata));
    const top200Set = new Set(sorted.slice(0, 200).map(a => a.iata));

    // Build markers array so we can resize on zoom
    const markers = [];
    airports.forEach(apt => {
      if (!apt.lat || !apt.lon) return;
      const flights = apt.flights || 0;
      const pct = Math.log(flights + 1) / Math.log(maxFlights + 1);
      const isTop50 = top50Set.has(apt.iata);
      const isTop200 = top200Set.has(apt.iata);

      // Color by tier: gold for top 50, blue for top 200, green for active, dim for zero
      let color, fillColor, fillOpacity;
      if (isTop50) {
        color = "#fbbf24"; fillColor = "#fbbf24"; fillOpacity = 0.9;
      } else if (isTop200) {
        color = "#3b82f6"; fillColor = "#60a5fa"; fillOpacity = 0.85;
      } else if (flights > 0) {
        color = "#10b981"; fillColor = "#34d399"; fillOpacity = 0.7;
      } else {
        color = "#475569"; fillColor = "#64748b"; fillOpacity = 0.4;
      }

      const marker = L.circleMarker([apt.lat, apt.lon], {
        radius: 3,
        color,
        fillColor,
        fillOpacity,
        weight: isTop50 ? 2 : 1,
        className: "airport-dot",
      });

      // Rich tooltip
      const flightsLabel = flights > 0 ? flights.toLocaleString() : "No data";
      const tierLabel = isTop50 ? ' <span style="color:#fbbf24;font-weight:700">★ Top 50</span>' :
                        isTop200 ? ' <span style="color:#60a5fa;font-weight:700">Top 200</span>' : '';
      marker.bindTooltip(
        `<div style="font-family:-apple-system,BlinkMacSystemFont,sans-serif;font-size:13px;line-height:1.5">` +
        `<div style="font-weight:700;font-size:15px;margin-bottom:2px">${apt.iata}${tierLabel}</div>` +
        `<div style="color:#94a3b8">${apt.name}</div>` +
        `<div style="color:#64748b;font-size:11px">${apt.city}, ${apt.country}</div>` +
        `<div style="margin-top:4px;font-size:12px;color:#e2e8f0">✈ <strong>${flightsLabel}</strong> award flights tracked</div>` +
        `</div>`,
        { direction: "top", offset: [0, -4], className: "airport-tooltip" }
      );

      marker.addTo(markersLayer.current);
      markers.push({ marker, pct, flights });
    });

    // Zoom-responsive sizing: dot size purely based on flight volume (pct) + zoom
    function updateRadii() {
      const zoom = map.getZoom();
      const scale = Math.min(1, Math.max(0.15, (zoom - 1) / 6));
      markers.forEach(({ marker, pct, flights }) => {
        // Size driven entirely by flight count — 1.5px min up to 12px max
        const baseRadius = flights === 0 ? 1 : Math.max(1.5, 1.5 + pct * 10.5);
        marker.setRadius(baseRadius * scale);
      });
    }

    updateRadii();
    map.on("zoomend", updateRadii);

    // Fit bounds to airports that have data
    const withData = airports.filter(a => a.flights > 0 && a.lat && a.lon);
    if (withData.length > 0) {
      const bounds = L.latLngBounds(withData.map(a => [a.lat, a.lon]));
      map.fitBounds(bounds, { padding: [30, 30], maxZoom: 5 });
    }

    return () => { map.off("zoomend", updateRadii); };
  }, [airportData]);

  const totalTracked = airportData ? airportData.airports.filter(a => a.flights > 0).length : 0;
  const totalCountries = airportData ? new Set(airportData.airports.filter(a => a.flights > 0).map(a => a.country)).size : 0;

  return (
    <div style={{ background: theme.surface, border: `1px solid ${theme.border}`, borderRadius: 12, overflow: "hidden", marginTop: 24 }}>
      <div style={{ display: "flex", justifyContent: "space-between", alignItems: "center", padding: "16px 20px 12px" }}>
        <div>
          <h3 style={{ fontSize: 16, fontWeight: 700, marginBottom: 4, color: theme.text }}>Airport Coverage Map</h3>
          <p style={{ fontSize: 12, color: theme.textDim, margin: 0 }}>
            {loading ? "Loading airports..." : `${totalTracked} airports tracked across ${totalCountries} countries`}
          </p>
        </div>
        <div style={{ display: "flex", gap: 16, fontSize: 11, color: theme.textDim }}>
          <span><span style={{ display: "inline-block", width: 10, height: 10, borderRadius: "50%", background: "#fbbf24", marginRight: 4, verticalAlign: "middle" }}></span>Top 50</span>
          <span><span style={{ display: "inline-block", width: 8, height: 8, borderRadius: "50%", background: "#60a5fa", marginRight: 4, verticalAlign: "middle" }}></span>Top 200</span>
          <span><span style={{ display: "inline-block", width: 6, height: 6, borderRadius: "50%", background: "#34d399", marginRight: 4, verticalAlign: "middle" }}></span>Active</span>
          <span><span style={{ display: "inline-block", width: 4, height: 4, borderRadius: "50%", background: "#64748b", marginRight: 4, verticalAlign: "middle" }}></span>No data</span>
        </div>
      </div>
      <div ref={mapRef} style={{ width: "100%", height: 480, position: "relative" }}>
        {loading && (
          <div style={{ position: "absolute", inset: 0, display: "flex", alignItems: "center", justifyContent: "center", background: theme.bg + "cc", zIndex: Z_MODAL_OVERLAY }}>
            <div style={{ color: theme.textMuted, fontSize: 16 }}>Loading airport data...</div>
          </div>
        )}
      </div>
    </div>
  );
}

// ─── RouteDensityMap ────────────────────────────────────────────────────────
function RouteDensityMap() {
  const mapRef = useRef(null);
  const mapInstance = useRef(null);
  const arcsLayer = useRef(null);
  const tileLayerRef = useRef(null);
  const [routeData, setRouteData] = useState(null);
  const [loading, setLoading] = useState(true);
  const [cabinFilter, setCabinFilter] = useState("all"); // all, biz, econ
  const [originSearch, setOriginSearch] = useState("");
  const [destSearch, setDestSearch] = useState("");
  const [appliedOrigin, setAppliedOrigin] = useState(""); // only updates on Search click
  const [appliedDest, setAppliedDest] = useState("");
  const [originType, setOriginType] = useState("any"); // any, iata, city, country, region
  const [destType, setDestType] = useState("any");
  const [showOriginDrop, setShowOriginDrop] = useState(false);
  const [showDestDrop, setShowDestDrop] = useState(false);
  const [routeLimit, setRouteLimit] = useState(5000);
  const [hiddenRegions, setHiddenRegions] = useState(new Set());
  const [selectedRoute, setSelectedRoute] = useState(null); // {origin, destination, ...}
  const [routeDetail, setRouteDetail] = useState(null);
  const [detailLoading, setDetailLoading] = useState(false);
  const originRef = useRef(null);
  const destRef = useRef(null);

  // Apply search filters (called by Search button or Enter key)
  const applySearch = () => {
    setAppliedOrigin(originSearch);
    setAppliedDest(destSearch);
  };

  // Fetch route detail when a route is clicked
  useEffect(() => {
    if (!selectedRoute) { setRouteDetail(null); return; }
    setDetailLoading(true);
    fetch(`${API}/api/dashboard/route-detail?origin=${selectedRoute.origin}&destination=${selectedRoute.destination}`)
      .then(r => r.json())
      .then(d => { setRouteDetail(d); setDetailLoading(false); })
      .catch(() => setDetailLoading(false));
  }, [selectedRoute]);

  // Fetch route data (re-fetch when limit changes)
  useEffect(() => {
    setLoading(true);
    const limitParam = routeLimit === 0 ? "0" : String(routeLimit);
    fetch(`${API}/api/dashboard/route-density?limit=${limitParam}`)
      .then(r => r.json())
      .then(d => { setRouteData(d); setLoading(false); })
      .catch(() => setLoading(false));
  }, [routeLimit]);

  // Close dropdowns on outside click
  useEffect(() => {
    const handler = (e) => {
      if (originRef.current && !originRef.current.contains(e.target)) setShowOriginDrop(false);
      if (destRef.current && !destRef.current.contains(e.target)) setShowDestDrop(false);
    };
    document.addEventListener("mousedown", handler);
    return () => document.removeEventListener("mousedown", handler);
  }, []);

  // Initialize map
  useEffect(() => {
    if (!mapRef.current || mapInstance.current) return;
    if (typeof L === "undefined") return;
    try {
      const map = L.map(mapRef.current, {
        zoomControl: false,
        attributionControl: true,
        worldCopyJump: true,
        minZoom: 1.5,
      }).setView([20, 0], 2);

      L.control.zoom({ position: "bottomright" }).addTo(map);

      const isDark = (localStorage.getItem("theme_mode") || "dark") === "dark";
      const tileUrl = isDark ? "https://{s}.basemaps.cartocdn.com/dark_all/{z}/{x}/{y}{r}.png" : "https://{s}.basemaps.cartocdn.com/light_all/{z}/{x}/{y}{r}.png";
      tileLayerRef.current = L.tileLayer(tileUrl, {
        attribution: '&copy; <a href="https://www.openstreetmap.org/copyright">OSM</a> &copy; <a href="https://carto.com/">CARTO</a>',
        maxZoom: 18,
        subdomains: "abcd",
      }).addTo(map);

      mapInstance.current = map;
      arcsLayer.current = L.layerGroup().addTo(map);
      const timer = setTimeout(() => { try { if (mapInstance.current) mapInstance.current.invalidateSize(); } catch(e) {} }, MAP_INVALIDATE_DELAY_MS);

      return () => { clearTimeout(timer); try { map.remove(); } catch(e) {} mapInstance.current = null; };
    } catch (e) { console.error("RouteDensityMap init error:", e); }
  }, []);

  // Swap tile layer on theme change
  useEffect(() => {
    const handler = () => {
      if (!mapInstance.current || !tileLayerRef.current) return;
      const isDark = (localStorage.getItem("theme_mode") || "dark") === "dark";
      const newUrl = isDark ? "https://{s}.basemaps.cartocdn.com/dark_all/{z}/{x}/{y}{r}.png" : "https://{s}.basemaps.cartocdn.com/light_all/{z}/{x}/{y}{r}.png";
      mapInstance.current.removeLayer(tileLayerRef.current);
      tileLayerRef.current = L.tileLayer(newUrl, { maxZoom: 18, subdomains: "abcd" }).addTo(mapInstance.current);
    };
    window.addEventListener("theme-change", handler);
    return () => window.removeEventListener("theme-change", handler);
  }, []);

  // Region classification: separate NA/SA, ME/Africa, proper corridors
  const regionOf = (lat, lon, country) => {
    // North America: US, Canada, Mexico, Caribbean, Central America
    if (lon >= -170 && lon <= -30 && lat >= 10) return "na";
    // South America
    if (lon >= -90 && lon <= -30 && lat < 10) return "sa";
    // Europe
    if (lon >= -30 && lon <= 45 && lat >= 35) return "eu";
    // Middle East (lon 25-60, lat 10-40)
    if (lon >= 25 && lon <= 65 && lat >= 10 && lat < 40) return "me";
    // Africa
    if (lon >= -20 && lon <= 55 && lat < 35 && lat >= -40) return "af";
    // Asia (East/SE/South)
    if (lon > 60 && lat >= 0) return "as";
    if (lon > 45 && lon <= 150 && lat >= 0) return "as";
    // Oceania
    if (lon > 100 && lat < 0) return "oc";
    if (lon > 60 && lat < 0) return "oc";
    return "ot";
  };

  // Route corridor index mapping
  const ROUTE_CORRIDORS = {
    "eu-na":  0,   // NA ↔ Europe
    "as-na":  1,   // NA ↔ Asia
    "na-sa":  2,   // NA ↔ S. America
    "me-na":  3,   // NA ↔ Middle East
    "af-na":  4,   // NA ↔ Africa
    "na-oc":  5,   // NA ↔ Oceania
    "as-eu":  6,   // Europe ↔ Asia
    "eu-me":  7,   // Europe ↔ Middle East
    "af-eu":  8,   // Europe ↔ Africa
    "eu-eu":  9,   // Intra-Europe
    "as-as": 10,   // Intra-Asia
    "na-na": 11,   // Intra-N. America
    "sa-sa": 11,   // Intra-S. America (same color)
    "af-as": 12,   // Africa/ME ↔ Asia
    "as-me": 12,
    "as-oc": 13,   // Oceania connections
    "eu-oc": 13,
    "oc-sa": 13,
    "af-me": 14,   // Intra-Africa/ME
    "af-af": 14,
    "me-me": 14,
    "eu-sa": 15,   // Europe ↔ S. America
    "as-sa": 15,   // Asia ↔ S. America (same color)
  };

  const REGION_COLORS = [
    "#f472b6", // 0  NA↔EU (pink)
    "#60a5fa", // 1  NA↔Asia (blue)
    "#fbbf24", // 2  NA↔SA (gold)
    "#c084fc", // 3  NA↔ME (purple)
    "#f87171", // 4  NA↔Africa (red)
    "#22d3ee", // 5  NA↔Oceania (cyan)
    "#34d399", // 6  EU↔Asia (green)
    "#fb923c", // 7  EU↔ME (orange)
    "#a78bfa", // 8  EU↔Africa (violet)
    "#e879f9", // 9  Intra-EU (magenta)
    "#2dd4bf", // 10 Intra-Asia (teal)
    "#facc15", // 11 Intra-Americas (yellow)
    "#f97316", // 12 AF/ME↔Asia (deep orange)
    "#a3e635", // 13 Oceania (lime)
    "#fb7185", // 14 Intra-AF/ME (rose)
    "#38bdf8", // 15 EU/Asia↔SA (sky)
    "#94a3b8", // 16 Other (gray)
  ];

  const classifyRoute = (route) => {
    const oR = regionOf(route.olat, route.olon);
    const dR = regionOf(route.dlat, route.dlon);
    const pair = [oR, dR].sort().join("-");
    return ROUTE_CORRIDORS[pair] ?? 16;
  };

  // Match a search term against a route endpoint
  const matchesEndpoint = (search, type, route, side) => {
    if (!search.trim()) return true;
    const q = search.trim().toLowerCase();
    const iata = side === "o" ? route.origin : route.destination;
    const city = side === "o" ? (route.ocity || "") : (route.dcity || "");
    const country = side === "o" ? (route.ocountry || "") : (route.dcountry || "");
    const continent = side === "o" ? (route.ocontinent || "") : (route.dcontinent || "");

    if (type === "iata") return iata.toLowerCase() === q;
    if (type === "city") return city.toLowerCase().includes(q);
    if (type === "country") return country.toLowerCase().includes(q);
    if (type === "region") return continent.toLowerCase().includes(q);
    // "any" — match against all fields
    return iata.toLowerCase().includes(q) || city.toLowerCase().includes(q) ||
           country.toLowerCase().includes(q) || continent.toLowerCase().includes(q);
  };

  // Build autocomplete suggestions from route data
  const getSuggestions = (search, type, side) => {
    if (!routeData || !search.trim() || search.length < 1) return [];
    const q = search.trim().toLowerCase();
    const seen = new Set();
    const results = [];
    const routes = routeData.routes || [];

    routes.forEach(r => {
      const iata = side === "o" ? r.origin : r.destination;
      const city = side === "o" ? (r.ocity || "") : (r.dcity || "");
      const country = side === "o" ? (r.ocountry || "") : (r.dcountry || "");
      const continent = side === "o" ? (r.ocontinent || "") : (r.dcontinent || "");

      // Determine if this matches
      let matches = false;
      if (type === "iata") matches = iata.toLowerCase().startsWith(q);
      else if (type === "city") matches = city.toLowerCase().includes(q);
      else if (type === "country") matches = country.toLowerCase().includes(q);
      else if (type === "region") matches = continent.toLowerCase().includes(q);
      else matches = iata.toLowerCase().includes(q) || city.toLowerCase().includes(q) ||
                     country.toLowerCase().includes(q) || continent.toLowerCase().includes(q);

      if (matches) {
        // Deduplicate by what we'll show
        let key, label;
        if (type === "country") { key = country; label = country; }
        else if (type === "region") { key = continent; label = continent; }
        else { key = iata; label = `${iata} — ${city}, ${country}`; }

        if (!seen.has(key)) {
          seen.add(key);
          results.push({ key, label, iata, city, country, continent });
        }
      }
    });
    return results.slice(0, 12);
  };

  // Render arcs
  useEffect(() => {
    if (!mapInstance.current || !routeData || !arcsLayer.current) return;
    arcsLayer.current.clearLayers();

    const map = mapInstance.current;
    const routes = routeData.routes || [];

    // Apply all filters
    const filtered = routes.filter(r => {
      if (cabinFilter === "biz" && r.best_j == null) return false;
      if (cabinFilter === "econ" && r.best_y == null) return false;
      if (!matchesEndpoint(appliedOrigin, originType, r, "o")) return false;
      if (!matchesEndpoint(appliedDest, destType, r, "d")) return false;
      const regionIdx = classifyRoute(r);
      if (hiddenRegions.has(regionIdx)) return false;
      return true;
    });

    // Compute max within filtered set for relative scaling
    const maxF = filtered.reduce((mx, r) => Math.max(mx, r.flights), 1);

    // Draw arcs — thinnest first
    const sorted = [...filtered].sort((a, b) => a.flights - b.flights);
    const arcRefs = [];

    sorted.forEach(route => {
      const pct = Math.log(route.flights + 1) / Math.log(maxF + 1);
      const baseWeight = 0.6 + pct * 5;
      const baseOpacity = 0.25 + pct * 0.55;

      const regionIdx = classifyRoute(route);
      const color = REGION_COLORS[regionIdx];

      const midLat = (route.olat + route.dlat) / 2;
      const midLon = (route.olon + route.dlon) / 2;
      const dxLat = route.dlat - route.olat;
      const dxLon = route.dlon - route.olon;
      const dist = Math.sqrt(dxLat * dxLat + dxLon * dxLon);
      const curveFactor = Math.min(0.2, dist * 0.05);
      const cLat = midLat + dxLon * curveFactor;
      const cLon = midLon - dxLat * curveFactor;

      const pts = [];
      for (let t = 0; t <= 1; t += 1/12) {
        const t1 = 1 - t;
        const lat = t1*t1*route.olat + 2*t1*t*cLat + t*t*route.dlat;
        const lon = t1*t1*route.olon + 2*t1*t*cLon + t*t*route.dlon;
        pts.push([lat, lon]);
      }

      const arc = L.polyline(pts, {
        color,
        weight: baseWeight,
        opacity: baseOpacity,
        smoothFactor: 1,
        lineCap: "round",
      });

      const bizStr = route.best_j ? `${Math.round(route.best_j/1000)}k` : "—";
      const econStr = route.best_y ? `${Math.round(route.best_y/1000)}k` : "—";
      arc.bindTooltip(
        `<div style="font-family:-apple-system,BlinkMacSystemFont,sans-serif;font-size:12px;line-height:1.4">` +
        `<div style="font-weight:700;font-size:14px;margin-bottom:2px">${route.origin} → ${route.destination}</div>` +
        `<div style="color:#94a3b8">${route.ocity} → ${route.dcity}</div>` +
        `<div style="margin-top:4px;display:grid;grid-template-columns:1fr 1fr;gap:2px 12px;font-size:11px">` +
        `<span>Flights: <b style="color:#e2e8f0">${route.flights.toLocaleString()}</b></span>` +
        `<span>Programs: <b style="color:#e2e8f0">${route.programs}</b></span>` +
        `<span>Best Biz: <b style="color:#fbbf24">${bizStr}</b></span>` +
        `<span>Best Econ: <b style="color:#10b981">${econStr}</b></span>` +
        `<span>Days: <b style="color:#e2e8f0">${route.days_available}</b></span>` +
        `</div><div style="margin-top:4px;font-size:10px;color:#60a5fa">Click for details</div></div>`,
        { direction: "top", sticky: true, className: "route-tooltip" }
      );

      // Click to drill down into route detail
      arc.on("click", () => {
        setSelectedRoute({ origin: route.origin, destination: route.destination,
          ocity: route.ocity, dcity: route.dcity, ocountry: route.ocountry, dcountry: route.dcountry,
          flights: route.flights, best_j: route.best_j, best_y: route.best_y });
      });
      arc.setStyle({ ...arc.options, className: "route-arc-clickable" });

      arc.addTo(arcsLayer.current);
      arcRefs.push({ arc, baseWeight, baseOpacity });
    });

    // Zoom-responsive thickness
    function updateArcs() {
      const zoom = map.getZoom();
      const scale = Math.min(1.2, Math.max(0.15, (zoom - 1) / 4));
      arcRefs.forEach(({ arc, baseWeight, baseOpacity }) => {
        arc.setStyle({
          weight: Math.max(0.3, baseWeight * scale),
          opacity: Math.min(0.9, baseOpacity * (0.5 + scale * 0.5)),
        });
      });
    }

    updateArcs();
    map.on("zoomend", updateArcs);

    // Auto-fit bounds when filtering
    if ((appliedOrigin || appliedDest) && filtered.length > 0 && filtered.length < routes.length) {
      const allPts = [];
      filtered.forEach(r => { allPts.push([r.olat, r.olon]); allPts.push([r.dlat, r.dlon]); });
      map.fitBounds(L.latLngBounds(allPts), { padding: [40, 40], maxZoom: 6 });
    }

    return () => { map.off("zoomend", updateArcs); };
  }, [routeData, cabinFilter, appliedOrigin, appliedDest, originType, destType, hiddenRegions]);

  const allRoutes = routeData?.routes || [];
  const visibleCount = allRoutes.filter(r => {
    if (cabinFilter === "biz" && r.best_j == null) return false;
    if (cabinFilter === "econ" && r.best_y == null) return false;
    if (!matchesEndpoint(appliedOrigin, originType, r, "o")) return false;
    if (!matchesEndpoint(appliedDest, destType, r, "d")) return false;
    const regionIdx = classifyRoute(r);
    if (hiddenRegions.has(regionIdx)) return false;
    return true;
  }).length;

  const inputStyle = {
    flex: 1, padding: "6px 10px", fontSize: 12, borderRadius: 6,
    border: `1px solid ${theme.border}`, background: theme.card, color: theme.text,
    minWidth: 0,
  };

  const typeStyle = (active) => ({
    padding: "3px 8px", fontSize: 10, borderRadius: 4, cursor: "pointer",
    border: `1px solid ${active ? theme.accent + "60" : theme.border}`,
    background: active ? theme.accent + "20" : "transparent",
    color: active ? theme.accent : theme.textDim,
    fontWeight: active ? 600 : 400, whiteSpace: "nowrap",
  });

  const dropStyle = {
    position: "absolute", top: "100%", left: 0, right: 0, zIndex: Z_DROPDOWN,
    background: theme.surface, border: `1px solid ${theme.border}`, borderRadius: 8,
    boxShadow: "0 8px 24px rgba(0,0,0,0.4)", maxHeight: 200, overflowY: "auto",
    marginTop: 4,
  };

  // Render a search input with type selector and autocomplete
  const SearchInput = ({ label, value, setValue, type, setType, suggestions, showDrop, setShowDrop, inputRef }) => (
    <div ref={inputRef} style={{ flex: 1, minWidth: 0 }}>
      <div style={{ fontSize: 10, color: theme.textDim, marginBottom: 4, fontWeight: 600, textTransform: "uppercase", letterSpacing: 0.5 }}>{label}</div>
      <div style={{ display: "flex", gap: 4, marginBottom: 4 }}>
        {[
          { key: "any", label: "Any" },
          { key: "iata", label: "IATA" },
          { key: "city", label: "City" },
          { key: "country", label: "Country" },
          { key: "region", label: "Region" },
        ].map(t => (
          <button key={t.key} onClick={() => setType(t.key)} style={typeStyle(type === t.key)}>{t.label}</button>
        ))}
      </div>
      <div style={{ position: "relative" }}>
        <input
          type="text"
          value={value}
          placeholder={type === "any" ? "IATA, city, country, or region..." : type === "iata" ? "e.g. JFK, LAX, LHR" : type === "city" ? "e.g. New York, London" : type === "country" ? "e.g. United States, Japan" : "e.g. Europe, Asia"}
          onChange={e => { setValue(e.target.value); setShowDrop(true); }}
          onFocus={() => { if (value.trim()) setShowDrop(true); }}
          onKeyDown={e => { if (e.key === "Enter") { setShowDrop(false); applySearch(); } }}
          style={inputStyle}
        />
        {value && (
          <button onClick={() => { setValue(""); setShowDrop(false); }}
            style={{ position: "absolute", right: 6, top: "50%", transform: "translateY(-50%)",
              background: "none", border: "none", color: theme.textDim, cursor: "pointer",
              fontSize: 14, padding: 0, lineHeight: 1 }}>✕</button>
        )}
        {showDrop && suggestions.length > 0 && (
          <div style={dropStyle}>
            {suggestions.map(s => (
              <div key={s.key}
                onClick={() => { setValue(s.key); setShowDrop(false); }}
                style={{ padding: "8px 12px", fontSize: 12, cursor: "pointer", color: theme.text,
                  borderBottom: `1px solid ${theme.border}22` }}
                onMouseEnter={e => e.currentTarget.style.background = theme.surfaceHover}
                onMouseLeave={e => e.currentTarget.style.background = "transparent"}>
                {s.label}
              </div>
            ))}
          </div>
        )}
      </div>
    </div>
  );

  const originSuggestions = getSuggestions(originSearch, originType, "o");
  const destSuggestions = getSuggestions(destSearch, destType, "d");

  return (
    <div style={{ background: theme.surface, border: `1px solid ${theme.border}`, borderRadius: 12, overflow: "hidden", marginTop: 24 }}>
      <div style={{ display: "flex", justifyContent: "space-between", alignItems: "center", padding: "16px 20px 8px", flexWrap: "wrap", gap: 8 }}>
        <div>
          <h3 style={{ fontSize: 16, fontWeight: 700, marginBottom: 4, color: theme.text }}>Route Density Map</h3>
          <p style={{ fontSize: 12, color: theme.textDim, margin: 0 }}>
            {loading ? "Loading routes..." : `Showing ${visibleCount} of ${allRoutes.length} routes · Thicker arcs = more availability`}
          </p>
        </div>
        <div style={{ display: "flex", gap: 12, alignItems: "center", flexWrap: "wrap" }}>
          <div style={{ display: "flex", gap: 4, alignItems: "center" }}>
            <span style={{ fontSize: 10, color: theme.textDim, fontWeight: 600, textTransform: "uppercase", letterSpacing: 0.5, marginRight: 2 }}>Routes</span>
            {[
              { key: 800, label: "800" },
              { key: 2000, label: "2K" },
              { key: 5000, label: "5K" },
              { key: 0, label: "All" },
            ].map(f => (
              <button key={f.key} onClick={() => setRouteLimit(f.key)}
                style={{ padding: "4px 10px", fontSize: 11, borderRadius: 6, cursor: "pointer",
                  border: `1px solid ${routeLimit === f.key ? theme.accent + "60" : theme.border}`,
                  background: routeLimit === f.key ? theme.accent + "20" : "transparent",
                  color: routeLimit === f.key ? theme.accent : theme.textDim,
                  fontWeight: routeLimit === f.key ? 600 : 400 }}>
                {f.label}
              </button>
            ))}
          </div>
          <div style={{ display: "flex", gap: 4, alignItems: "center" }}>
            <span style={{ fontSize: 10, color: theme.textDim, fontWeight: 600, textTransform: "uppercase", letterSpacing: 0.5, marginRight: 2 }}>Cabin</span>
            {[
              { key: "all", label: "All" },
              { key: "biz", label: "Business" },
              { key: "econ", label: "Economy" },
            ].map(f => (
              <button key={f.key} onClick={() => setCabinFilter(f.key)}
                style={{ padding: "4px 10px", fontSize: 11, borderRadius: 6, cursor: "pointer",
                  border: `1px solid ${cabinFilter === f.key ? theme.accent + "60" : theme.border}`,
                  background: cabinFilter === f.key ? theme.accent + "20" : "transparent",
                  color: cabinFilter === f.key ? theme.accent : theme.textDim,
                  fontWeight: cabinFilter === f.key ? 600 : 400 }}>
                {f.label}
              </button>
            ))}
          </div>
        </div>
      </div>

      {/* Search filters */}
      <div style={{ display: "flex", gap: 16, padding: "8px 20px 12px", alignItems: "flex-start" }}>
        <SearchInput label="Origin" value={originSearch} setValue={setOriginSearch}
          type={originType} setType={setOriginType}
          suggestions={originSuggestions} showDrop={showOriginDrop} setShowDrop={setShowOriginDrop}
          inputRef={originRef} />
        <div style={{ paddingTop: 28, color: theme.textDim, fontSize: 16, flexShrink: 0 }}>→</div>
        <SearchInput label="Destination" value={destSearch} setValue={setDestSearch}
          type={destType} setType={setDestType}
          suggestions={destSuggestions} showDrop={showDestDrop} setShowDrop={setShowDestDrop}
          inputRef={destRef} />
        <div style={{ display: "flex", flexDirection: "column", gap: 4, paddingTop: 22, flexShrink: 0 }}>
          <button onClick={applySearch}
            style={{ padding: "6px 18px", fontSize: 12, borderRadius: 6, cursor: "pointer",
              border: "none", background: theme.accent, color: "#fff",
              fontWeight: 600, whiteSpace: "nowrap" }}>
            Search
          </button>
          {(originSearch || destSearch || appliedOrigin || appliedDest) && (
            <button onClick={() => { setOriginSearch(""); setDestSearch(""); setAppliedOrigin(""); setAppliedDest(""); }}
              style={{ padding: "4px 14px", fontSize: 10, borderRadius: 6, cursor: "pointer",
                border: `1px solid ${theme.border}`, background: theme.card, color: theme.textMuted,
                whiteSpace: "nowrap" }}>
              Clear
            </button>
          )}
        </div>
      </div>

      {/* Region color legend — click to toggle */}
      {!loading && (
        <div style={{ display: "flex", flexWrap: "wrap", gap: "4px 6px", padding: "0 20px 10px", fontSize: 10, color: theme.textDim, alignItems: "center" }}>
          {[
            { color: "#f472b6", label: "NA↔EU", idx: 0 },
            { color: "#60a5fa", label: "NA↔Asia", idx: 1 },
            { color: "#fbbf24", label: "NA↔SA", idx: 2 },
            { color: "#c084fc", label: "NA↔ME", idx: 3 },
            { color: "#f87171", label: "NA↔Africa", idx: 4 },
            { color: "#22d3ee", label: "NA↔Oceania", idx: 5 },
            { color: "#34d399", label: "EU↔Asia", idx: 6 },
            { color: "#fb923c", label: "EU↔ME", idx: 7 },
            { color: "#a78bfa", label: "EU↔Africa", idx: 8 },
            { color: "#e879f9", label: "Intra-EU", idx: 9 },
            { color: "#2dd4bf", label: "Intra-Asia", idx: 10 },
            { color: "#facc15", label: "Intra-Amer", idx: 11 },
            { color: "#f97316", label: "AF/ME↔Asia", idx: 12 },
            { color: "#a3e635", label: "Oceania", idx: 13 },
            { color: "#fb7185", label: "Intra-AF/ME", idx: 14 },
            { color: "#38bdf8", label: "EU/AS↔SA", idx: 15 },
            { color: "#94a3b8", label: "Other", idx: 16 },
          ].map(r => {
            const hidden = hiddenRegions.has(r.idx);
            return (
              <button key={r.label} onClick={() => {
                setHiddenRegions(prev => {
                  const next = new Set(prev);
                  if (next.has(r.idx)) next.delete(r.idx); else next.add(r.idx);
                  return next;
                });
              }}
                style={{ display: "inline-flex", alignItems: "center", gap: 3, padding: "2px 8px",
                  borderRadius: 4, cursor: "pointer", border: `1px solid ${hidden ? theme.border : r.color + "60"}`,
                  background: hidden ? "transparent" : r.color + "15",
                  opacity: hidden ? 0.4 : 1, transition: "all 0.15s ease",
                  fontSize: 10, color: hidden ? theme.textDim : theme.text, fontWeight: hidden ? 400 : 500,
                  lineHeight: 1.4 }}>
                <span style={{ display: "inline-block", width: 14, height: 3, background: r.color, borderRadius: 2, opacity: hidden ? 0.3 : 1 }}></span>
                {r.label}
              </button>
            );
          })}
          <button onClick={() => setHiddenRegions(new Set([0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16]))}
            style={{ padding: "2px 8px", fontSize: 10, borderRadius: 4, cursor: "pointer",
              border: `1px solid ${theme.border}`, background: "transparent", color: theme.textDim,
              fontWeight: 500, lineHeight: 1.4 }}>
            Hide All
          </button>
          {hiddenRegions.size > 0 && (
            <button onClick={() => setHiddenRegions(new Set())}
              style={{ padding: "2px 8px", fontSize: 10, borderRadius: 4, cursor: "pointer",
                border: `1px solid ${theme.border}`, background: "transparent", color: theme.accent,
                fontWeight: 500, lineHeight: 1.4 }}>
              Show All
            </button>
          )}
        </div>
      )}

      <div ref={mapRef} style={{ width: "100%", height: 500, position: "relative" }}>
        {loading && (
          <div style={{ position: "absolute", inset: 0, display: "flex", alignItems: "center", justifyContent: "center", background: theme.bg + "cc", zIndex: Z_MODAL_OVERLAY }}>
            <div style={{ color: theme.textMuted, fontSize: 16 }}>Loading route data...</div>
          </div>
        )}
      </div>

      {/* Route detail panel — shown when user clicks an arc */}
      {selectedRoute && (
        <div style={{ borderTop: `1px solid ${theme.border}`, padding: "16px 20px" }}>
          <div style={{ display: "flex", justifyContent: "space-between", alignItems: "center", marginBottom: 12 }}>
            <div>
              <h4 style={{ fontSize: 16, fontWeight: 700, color: theme.text, margin: 0 }}>
                {selectedRoute.origin} → {selectedRoute.destination}
              </h4>
              <p style={{ fontSize: 12, color: theme.textDim, margin: "2px 0 0" }}>
                {selectedRoute.ocity}, {selectedRoute.ocountry} → {selectedRoute.dcity}, {selectedRoute.dcountry}
              </p>
            </div>
            <button onClick={() => setSelectedRoute(null)}
              style={{ padding: "4px 12px", fontSize: 12, borderRadius: 6, cursor: "pointer",
                border: `1px solid ${theme.border}`, background: theme.card, color: theme.textMuted }}>
              Close
            </button>
          </div>

          {detailLoading && <div style={{ color: theme.textDim, fontSize: 13, padding: "20px 0", textAlign: "center" }}>Loading availability...</div>}

          {routeDetail && !detailLoading && (
            <div style={{ overflowX: "auto" }}>
              {routeDetail.count === 0 ? (
                <div style={{ color: theme.textDim, fontSize: 13, padding: "20px 0", textAlign: "center" }}>No availability found for this route.</div>
              ) : (
                <table style={{ width: "100%", borderCollapse: "collapse", fontSize: 12 }}>
                  <thead>
                    <tr style={{ borderBottom: `2px solid ${theme.border}` }}>
                      {["Date", "Source", "Airline", "Direct",
                        "Biz Miles", "Biz Tax", "Biz Seats",
                        "Econ Miles", "Econ Tax", "Econ Seats",
                        "Prem Miles", "Prem Tax", "Prem Seats",
                        "First Miles", "First Tax", "First Seats"].map(h => (
                        <th key={h} style={{ padding: "8px 6px", textAlign: "left", color: theme.textDim, fontWeight: 600,
                          fontSize: 10, textTransform: "uppercase", letterSpacing: 0.5, whiteSpace: "nowrap" }}>{h}</th>
                      ))}
                    </tr>
                  </thead>
                  <tbody>
                    {routeDetail.results.map((r, i) => {
                      const fmtMiles = (miles, avail) => avail && miles > 0 ? miles.toLocaleString() : "—";
                      const fmtTax = (tax) => tax != null && tax > 0 ? "$" + Math.round(tax) : "—";
                      const fmtSeats = (seats, avail) => avail ? (seats > 0 ? seats : "?") : "—";
                      const airlineName = r.airlines || r.source || "—";
                      return (
                      <tr key={i} style={{ borderBottom: `1px solid ${theme.border}40` }}
                        onMouseEnter={e => e.currentTarget.style.background = theme.surfaceHover}
                        onMouseLeave={e => e.currentTarget.style.background = "transparent"}>
                        <td style={{ padding: "6px", color: theme.text, whiteSpace: "nowrap", fontWeight: 500 }}>{r.date}</td>
                        <td style={{ padding: "6px", color: theme.accent, fontWeight: 500, textTransform: "uppercase", fontSize: 11 }}>{r.source}</td>
                        <td style={{ padding: "6px", color: theme.text, fontWeight: 500 }}>{airlineName}</td>
                        <td style={{ padding: "6px", color: r.direct ? theme.success : theme.textDim }}>{r.direct ? "Yes" : "No"}</td>
                        <td style={{ padding: "6px", color: r.j_avail ? "#fbbf24" : theme.textDim, fontWeight: r.j_avail ? 600 : 400 }}>
                          {fmtMiles(r.j_miles, r.j_avail)}</td>
                        <td style={{ padding: "6px", color: r.j_avail ? theme.text : theme.textDim }}>{r.j_avail ? fmtTax(r.j_taxes) : "—"}</td>
                        <td style={{ padding: "6px", color: r.j_avail ? theme.text : theme.textDim }}>{fmtSeats(r.j_seats, r.j_avail)}</td>
                        <td style={{ padding: "6px", color: r.y_avail ? "#10b981" : theme.textDim, fontWeight: r.y_avail ? 600 : 400 }}>
                          {fmtMiles(r.y_miles, r.y_avail)}</td>
                        <td style={{ padding: "6px", color: r.y_avail ? theme.text : theme.textDim }}>{r.y_avail ? fmtTax(r.y_taxes) : "—"}</td>
                        <td style={{ padding: "6px", color: r.y_avail ? theme.text : theme.textDim }}>{fmtSeats(r.y_seats, r.y_avail)}</td>
                        <td style={{ padding: "6px", color: r.w_avail ? "#a78bfa" : theme.textDim, fontWeight: r.w_avail ? 600 : 400 }}>
                          {fmtMiles(r.w_miles, r.w_avail)}</td>
                        <td style={{ padding: "6px", color: r.w_avail ? theme.text : theme.textDim }}>{r.w_avail ? fmtTax(r.w_taxes) : "—"}</td>
                        <td style={{ padding: "6px", color: r.w_avail ? theme.text : theme.textDim }}>{fmtSeats(r.w_seats, r.w_avail)}</td>
                        <td style={{ padding: "6px", color: r.f_avail ? "#c084fc" : theme.textDim, fontWeight: r.f_avail ? 600 : 400 }}>
                          {fmtMiles(r.f_miles, r.f_avail)}</td>
                        <td style={{ padding: "6px", color: r.f_avail ? theme.text : theme.textDim }}>{r.f_avail ? fmtTax(r.f_taxes) : "—"}</td>
                        <td style={{ padding: "6px", color: r.f_avail ? theme.text : theme.textDim }}>{fmtSeats(r.f_seats, r.f_avail)}</td>
                      </tr>
                      );
                    })}
                  </tbody>
                </table>
              )}
              {routeDetail.count > 0 && (
                <div style={{ fontSize: 11, color: theme.textDim, padding: "8px 0 0", textAlign: "right" }}>
                  {routeDetail.count} record{routeDetail.count !== 1 ? "s" : ""} found
                </div>
              )}
            </div>
          )}
        </div>
      )}
    </div>
  );
}

// ─── DB Progress Widget ────────────────────────────────────────────────────
function DBProgressWidget() {
  const [data, setData] = useState(null);
  const [loading, setLoading] = useState(true);
  const [expanded, setExpanded] = useState(false);
  const [animPct, setAnimPct] = useState(0);
  const canvasRef = useRef(null);
  const animRef = useRef(null);

  useEffect(() => {
    (window.authFetch || fetch)(`${API}/api/admin/db-progress`)
      .then(r => { if (!r.ok) throw new Error("Not authorized"); return r.json(); })
      .then(d => { if (d.overall_pct != null) setData(d); setLoading(false); })
      .catch(() => setLoading(false));
  }, []);

  // Animate percentage counter
  useEffect(() => {
    if (!data) return;
    const target = data.overall_pct;
    const duration = 2000;
    const start = performance.now();
    const ease = (t) => t < 0.5 ? 4 * t * t * t : 1 - Math.pow(-2 * t + 2, 3) / 2;
    const animate = (now) => {
      const elapsed = now - start;
      const progress = Math.min(elapsed / duration, 1);
      setAnimPct(ease(progress) * target);
      if (progress < 1) animRef.current = requestAnimationFrame(animate);
    };
    animRef.current = requestAnimationFrame(animate);
    return () => cancelAnimationFrame(animRef.current);
  }, [data]);

  // Particle canvas animation
  useEffect(() => {
    const canvas = canvasRef.current;
    if (!canvas || !data) return;
    const ctx = canvas.getContext("2d");
    const W = canvas.width = 320;
    const H = canvas.height = 320;
    const pct = (data.overall_pct || 0) / 100;
    const particles = [];
    const numParticles = 60;

    // Create particles
    for (let i = 0; i < numParticles; i++) {
      const angle = Math.random() * Math.PI * 2;
      const radius = 100 + Math.random() * 40;
      particles.push({
        x: W/2 + Math.cos(angle) * radius,
        y: H/2 + Math.sin(angle) * radius,
        vx: (Math.random() - 0.5) * 0.3,
        vy: (Math.random() - 0.5) * 0.3,
        radius: 1 + Math.random() * 2,
        alpha: 0.2 + Math.random() * 0.5,
        color: Math.random() > 0.5 ? "#3b82f6" : "#60a5fa",
        orbitSpeed: 0.002 + Math.random() * 0.004,
        orbitRadius: radius,
        angle: angle,
      });
    }

    let frame;
    const draw = () => {
      ctx.clearRect(0, 0, W, H);

      // Background glow
      const grd = ctx.createRadialGradient(W/2, H/2, 0, W/2, H/2, 150);
      grd.addColorStop(0, `rgba(59, 130, 246, ${0.06 + pct * 0.08})`);
      grd.addColorStop(1, "transparent");
      ctx.fillStyle = grd;
      ctx.fillRect(0, 0, W, H);

      // Orbit ring (background)
      ctx.beginPath();
      ctx.arc(W/2, H/2, 110, 0, Math.PI * 2);
      ctx.strokeStyle = "rgba(59, 130, 246, 0.08)";
      ctx.lineWidth = 24;
      ctx.stroke();

      // Progress arc
      const startAngle = -Math.PI / 2;
      const endAngle = startAngle + (Math.PI * 2 * Math.min(animPct / 100, 1));
      ctx.beginPath();
      ctx.arc(W/2, H/2, 110, startAngle, endAngle);
      const arcGrd = ctx.createLinearGradient(W/2 - 110, 0, W/2 + 110, H);
      arcGrd.addColorStop(0, "#3b82f6");
      arcGrd.addColorStop(0.5, "#60a5fa");
      arcGrd.addColorStop(1, "#93c5fd");
      ctx.strokeStyle = arcGrd;
      ctx.lineWidth = 24;
      ctx.lineCap = "round";
      ctx.stroke();

      // Glow on the arc tip
      if (animPct > 0) {
        const tipX = W/2 + Math.cos(endAngle) * 110;
        const tipY = H/2 + Math.sin(endAngle) * 110;
        const tipGlow = ctx.createRadialGradient(tipX, tipY, 0, tipX, tipY, 20);
        tipGlow.addColorStop(0, "rgba(96, 165, 250, 0.6)");
        tipGlow.addColorStop(1, "transparent");
        ctx.fillStyle = tipGlow;
        ctx.fillRect(tipX - 20, tipY - 20, 40, 40);
      }

      // Animated particles
      particles.forEach(p => {
        p.angle += p.orbitSpeed;
        p.x = W/2 + Math.cos(p.angle) * p.orbitRadius + Math.sin(Date.now() / 2000 + p.angle) * 3;
        p.y = H/2 + Math.sin(p.angle) * p.orbitRadius + Math.cos(Date.now() / 2000 + p.angle) * 3;

        // Only show particles within the completed arc section
        const particleAngle = Math.atan2(p.y - H/2, p.x - W/2);
        let normAngle = particleAngle - startAngle;
        if (normAngle < 0) normAngle += Math.PI * 2;
        const arcSpan = (animPct / 100) * Math.PI * 2;

        if (normAngle <= arcSpan + 0.3) {
          ctx.beginPath();
          ctx.arc(p.x, p.y, p.radius, 0, Math.PI * 2);
          ctx.fillStyle = p.color.replace(")", `, ${p.alpha * (0.6 + 0.4 * Math.sin(Date.now() / 1000 + p.angle))})`).replace("rgb", "rgba");
          ctx.fill();
        }
      });

      frame = requestAnimationFrame(draw);
    };
    draw();
    return () => cancelAnimationFrame(frame);
  }, [data, animPct]);

  if (loading) return React.createElement("div", { style: { textAlign: "center", padding: 40, color: theme.textDim } }, "Loading progress...");
  if (!data) return null;

  const completedPrograms = (data.programs || []).filter(p => p.fully_scanned).length;
  const totalPrograms = (data.programs || []).length;

  return React.createElement("div", {
    style: {
      background: `linear-gradient(135deg, ${theme.surface} 0%, rgba(59, 130, 246, 0.03) 100%)`,
      border: `1px solid ${theme.border}`,
      borderRadius: 16,
      padding: 24,
      marginBottom: 24,
      position: "relative",
      overflow: "hidden",
    }
  },
    // Header
    React.createElement("div", { style: { display: "flex", justifyContent: "space-between", alignItems: "center", marginBottom: 8 } },
      React.createElement("div", null,
        React.createElement("h3", { style: { fontSize: 16, fontWeight: 700, margin: 0, color: theme.text } }, "Database Build Progress"),
        React.createElement("p", { style: { fontSize: 12, color: theme.textDim, margin: "4px 0 0" } }, "Coverage based on per-airline release windows")
      ),
      React.createElement("div", { style: { textAlign: "right" } },
        React.createElement("div", { style: { fontSize: 11, color: theme.textDim, textTransform: "uppercase", letterSpacing: 1 } }, "Est. Completion"),
        React.createElement("div", { style: { fontSize: 14, fontWeight: 600, color: theme.accent } }, data.est_completion || "Calculating...")
      )
    ),

    // Main content: canvas + stats
    React.createElement("div", { style: { display: "flex", alignItems: "center", gap: 32, flexWrap: "wrap" } },
      // Left: animated ring
      React.createElement("div", { style: { position: "relative", width: 280, height: 280, flexShrink: 0 } },
        React.createElement("canvas", {
          ref: canvasRef,
          style: { width: 280, height: 280, position: "absolute", top: 0, left: 0 }
        }),
        // Center text overlay
        React.createElement("div", {
          style: {
            position: "absolute", top: "50%", left: "50%", transform: "translate(-50%, -50%)",
            textAlign: "center", zIndex: 1,
          }
        },
          React.createElement("div", {
            style: {
              fontSize: 48, fontWeight: 800, letterSpacing: -2,
              background: "linear-gradient(135deg, #3b82f6, #93c5fd)",
              WebkitBackgroundClip: "text", WebkitTextFillColor: "transparent",
              lineHeight: 1,
            }
          }, `${animPct.toFixed(1)}%`),
          React.createElement("div", { style: { fontSize: 11, color: theme.textDim, marginTop: 4, textTransform: "uppercase", letterSpacing: 1 } }, "Coverage"),
          data.overall_reach != null && React.createElement("div", {
            style: { fontSize: 18, fontWeight: 700, color: theme.warning, marginTop: 8 }
          }, `${data.overall_reach}%`),
          data.overall_reach != null && React.createElement("div", { style: { fontSize: 10, color: theme.textDim, textTransform: "uppercase", letterSpacing: 1 } }, "Reach"),
          React.createElement("div", {
            style: { fontSize: 14, fontWeight: 600, color: theme.textDim, marginTop: 6 }
          }, `${Math.ceil(data.days_to_full)}d remaining`)
        )
      ),

      // Right: stats grid
      React.createElement("div", { style: { flex: 1, minWidth: 260 } },
        // Key metrics
        React.createElement("div", { style: { display: "grid", gridTemplateColumns: "1fr 1fr", gap: 12, marginBottom: 16 } },
          ...[
            { label: "Total Flights", value: data.total_rows?.toLocaleString() || "0", icon: "✈️", color: theme.accent },
            { label: "Pages Ingested", value: `${data.total_pages_current?.toLocaleString()} / ${data.total_pages_est?.toLocaleString()}`, icon: "📄", color: theme.text },
            { label: "Daily Throughput", value: `${data.pages_per_day?.toLocaleString()} pages/day`, icon: "⚡", color: theme.warning },
            { label: "Programs Complete", value: `${completedPrograms} / ${totalPrograms}`, icon: "✅", color: theme.success },
          ].map((s, i) =>
            React.createElement("div", {
              key: i,
              style: { background: theme.card, border: `1px solid ${theme.border}`, borderRadius: 10, padding: "12px 14px" }
            },
              React.createElement("div", { style: { fontSize: 10, color: theme.textDim, textTransform: "uppercase", letterSpacing: 1, marginBottom: 4 } }, `${s.icon} ${s.label}`),
              React.createElement("div", { style: { fontSize: 16, fontWeight: 700, color: s.color } }, s.value)
            )
          )
        ),

        // Expand/collapse per-program
        React.createElement("button", {
          onClick: () => setExpanded(!expanded),
          style: {
            background: "none", border: `1px solid ${theme.border}`, borderRadius: 8,
            color: theme.textMuted, fontSize: 12, padding: "6px 14px", cursor: "pointer",
            fontFamily: "inherit", width: "100%", textAlign: "center",
          }
        }, expanded ? "▲ Hide Program Breakdown" : "▼ Show Program Breakdown")
      )
    ),

    // Per-program breakdown (collapsible)
    expanded && React.createElement("div", {
      style: { marginTop: 20, display: "grid", gridTemplateColumns: "repeat(auto-fill, minmax(240px, 1fr))", gap: 8 }
    },
      ...(data.programs || []).sort((a, b) => b.pct - a.pct).map(p => {
        const prog = PROGRAMS[p.source];
        const barColor = p.fully_scanned ? theme.success : p.pct > 75 ? "#60a5fa" : p.pct > 30 ? "#3b82f6" : theme.accent;
        return React.createElement("div", {
          key: p.source,
          style: {
            background: theme.card, border: `1px solid ${theme.border}`, borderRadius: 10,
            padding: "10px 12px", display: "flex", alignItems: "center", gap: 10,
          }
        },
          // Logo
          prog?.logo && React.createElement("img", {
            src: prog.logo, alt: "",
            style: {
              width: 28, height: 28, borderRadius: 6, objectFit: "contain", flexShrink: 0,
              background: prog.darkLogo ? "rgba(255,255,255,0.12)" : "transparent", padding: 2,
            },
            onError: (e) => { e.target.style.display = "none"; }
          }),

          // Info + bar
          React.createElement("div", { style: { flex: 1, minWidth: 0 } },
            React.createElement("div", { style: { display: "flex", justifyContent: "space-between", alignItems: "center", marginBottom: 4 } },
              React.createElement("span", { style: { fontSize: 12, fontWeight: 600, color: theme.text, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" } }, prog?.name?.split(" ").slice(-1)[0] || p.source),
              React.createElement("span", { style: { fontSize: 11, fontWeight: 700, color: barColor, flexShrink: 0, marginLeft: 6 } },
                p.fully_scanned ? "✓ Full" : `${p.pct}% cov`
              )
            ),
            // Progress bar
            React.createElement("div", {
              style: { height: 6, borderRadius: 3, background: theme.border + "40", overflow: "hidden" }
            },
              React.createElement("div", {
                style: {
                  height: "100%", borderRadius: 3, width: `${Math.max(2, p.pct)}%`,
                  background: p.fully_scanned
                    ? `linear-gradient(90deg, ${theme.success}, #34d399)`
                    : `linear-gradient(90deg, #3b82f6, #60a5fa)`,
                  transition: "width 1.5s ease-out",
                }
              })
            ),
            React.createElement("div", {
              style: { display: "flex", justifyContent: "space-between", marginTop: 3, fontSize: 10, color: theme.textDim }
            },
              React.createElement("span", null, `${p.date_count || 0}d / ${(p.release_days || 365) + 1}d` + (p.gap_days > 0 ? ` (${p.gap_days} gaps)` : "")),
              React.createElement("span", null, p.days_ahead >= 0 ? `reach: ${p.pct_reach || 0}%` : "no data")
            )
          )
        );
      })
    )
  );
}

// ─── SweetSpotFinder ────────────────────────────────────────────────────────
function SweetSpotFinder() {
  const [data, setData] = useState(null);
  const [loading, setLoading] = useState(true);
  const [cabin, setCabin] = useState("j"); // j = business, y = economy
  const [metric, setMetric] = useState("min"); // min or avg
  const [origin, setOrigin] = useState("");
  const [dest, setDest] = useState("");
  const [originType, setOriginType] = useState("any"); // any, iata, city, country, region
  const [destType, setDestType] = useState("any");
  const [appliedOrigin, setAppliedOrigin] = useState("");
  const [appliedDest, setAppliedDest] = useState("");
  const [appliedOriginType, setAppliedOriginType] = useState("any");
  const [appliedDestType, setAppliedDestType] = useState("any");
  const [hovered, setHovered] = useState(null); // {source, month}
  const [selected, setSelected] = useState(null); // {source, month} - pinned detail
  const [compact, setCompact] = useState(false);
  const [programFilter, setProgramFilter] = useState(""); // search text for programs
  const [hiddenPrograms, setHiddenPrograms] = useState(new Set());
  const [originSugg, setOriginSugg] = useState([]);
  const [destSugg, setDestSugg] = useState([]);
  const [showOriginDrop, setShowOriginDrop] = useState(false);
  const [showDestDrop, setShowDestDrop] = useState(false);
  const ssOriginRef = useRef(null);
  const ssDestRef = useRef(null);

  // Close dropdowns on outside click
  useEffect(() => {
    const handler = (e) => {
      if (ssOriginRef.current && !ssOriginRef.current.contains(e.target)) setShowOriginDrop(false);
      if (ssDestRef.current && !ssDestRef.current.contains(e.target)) setShowDestDrop(false);
    };
    document.addEventListener("mousedown", handler);
    return () => document.removeEventListener("mousedown", handler);
  }, []);

  // Fetch autocomplete suggestions
  const fetchSuggestions = useCallback((query, type, setter) => {
    if (!query || query.length < 1) { setter([]); return; }
    fetch(`${API}/api/airports?search=${encodeURIComponent(query)}&active_only=true`)
      .then(r => r.json())
      .then(d => {
        const airports = d.airports || [];
        const seen = new Set();
        const results = [];
        airports.forEach(a => {
          let key, label;
          if (type === "country") { key = a.country; label = a.country; }
          else if (type === "region") { key = a.continent; label = a.continent; }
          else if (type === "city") { key = a.city; label = `${a.city}, ${a.country}`; }
          else { key = a.iata_code; label = `${a.iata_code} — ${a.city}, ${a.country}`; }
          if (key && !seen.has(key)) { seen.add(key); results.push({ key, label }); }
        });
        setter(results.slice(0, 12));
      })
      .catch(() => setter([]));
  }, []);

  const fetchData = (o, d, oType, dType) => {
    setLoading(true);
    const params = new URLSearchParams();
    if (o) { params.set("origin", o); params.set("origin_type", oType || "iata"); }
    if (d) { params.set("destination", d); params.set("dest_type", dType || "iata"); }
    fetch(`${API}/api/dashboard/sweet-spots?${params}`)
      .then(r => r.json())
      .then(d => { setData(d); setLoading(false); })
      .catch(() => setLoading(false));
  };

  useEffect(() => { fetchData("", ""); }, []);

  const applyFilter = () => {
    const o = origin.trim();
    const d = dest.trim();
    setAppliedOrigin(o);
    setAppliedDest(d);
    setAppliedOriginType(originType);
    setAppliedDestType(destType);
    fetchData(o, d, originType, destType);
  };

  const clearFilter = () => {
    setOrigin(""); setDest("");
    setAppliedOrigin(""); setAppliedDest("");
    setOriginType("any"); setDestType("any");
    fetchData("", "");
  };

  if (!data && !loading) return null;

  // Build grid: programs (rows) × months (columns)
  const cells = data?.cells || [];
  const scale = data?.scale || {};

  // Extract unique months and programs
  const monthSet = new Set();
  const programSet = new Set();
  cells.forEach(c => { monthSet.add(c.month); programSet.add(c.source); });
  const months = [...monthSet].sort();
  const programs = [...programSet].sort((a, b) => {
    const aFlights = cells.filter(c => c.source === a).reduce((s, c) => s + c.flights, 0);
    const bFlights = cells.filter(c => c.source === b).reduce((s, c) => s + c.flights, 0);
    return bFlights - aFlights;
  });

  // Build lookup
  const lookup = {};
  cells.forEach(c => { lookup[`${c.source}|${c.month}`] = c; });

  const getValue = (source, month) => {
    const cell = lookup[`${source}|${month}`];
    if (!cell) return null;
    return cell[metric === "min" ? `min_${cabin}` : `avg_${cabin}`];
  };

  // Color scale
  const scaleKey = cabin;
  const globalMin = scale[`${scaleKey}_min`] || 1;
  const globalMax = scale[`${scaleKey}_max`] || 200000;

  const cellColor = (value) => {
    if (value == null) return "transparent";
    const logMin = Math.log(globalMin);
    const logMax = Math.log(globalMax);
    const logVal = Math.log(Math.max(value, globalMin));
    const pct = Math.min(1, Math.max(0, (logVal - logMin) / (logMax - logMin)));

    let r, g, b;
    if (pct < 0.5) {
      const t = pct * 2;
      r = Math.round(16 + t * 234);
      g = Math.round(185 - t * 5);
      b = Math.round(129 - t * 93);
    } else {
      const t = (pct - 0.5) * 2;
      r = Math.round(250 - t * 11);
      g = Math.round(180 - t * 112);
      b = Math.round(36 + t * 35);
    }
    return `rgb(${r},${g},${b})`;
  };

  const monthLabel = (m) => {
    const [y, mo] = m.split("-");
    const names = ["", "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
    return `${names[parseInt(mo)]} '${y.slice(2)}`;
  };

  // Active cell = selected (pinned) takes priority, else hovered
  const activeRef = selected || hovered;
  const activeCell = activeRef ? lookup[`${activeRef.source}|${activeRef.month}`] : null;

  // Filter programs by search text and hidden set
  const filteredPrograms = programs.filter(source => {
    if (hiddenPrograms.has(source)) return false;
    if (!programFilter) return true;
    const pf = programFilter.toLowerCase();
    const pInfo = PROGRAMS[source] || {};
    return source.toLowerCase().includes(pf) || (pInfo.name || "").toLowerCase().includes(pf);
  });

  const toggleHideProgram = (source) => {
    setHiddenPrograms(prev => {
      const next = new Set(prev);
      if (next.has(source)) next.delete(source); else next.add(source);
      return next;
    });
  };

  const handleCellClick = (source, month) => {
    if (selected && selected.source === source && selected.month === month) {
      setSelected(null); // deselect on re-click
    } else {
      setSelected({ source, month });
    }
  };

  const inputStyle = {
    padding: "5px 10px", fontSize: 12, borderRadius: 6, width: 80,
    border: `1px solid ${theme.border}`, background: theme.card, color: theme.text,
  };

  const pillStyle = (active) => ({
    padding: "4px 10px", fontSize: 11, borderRadius: 6, cursor: "pointer",
    border: `1px solid ${active ? theme.accent + "60" : theme.border}`,
    background: active ? theme.accent + "20" : "transparent",
    color: active ? theme.accent : theme.textDim,
    fontWeight: active ? 600 : 400,
  });

  // Compact cell sizing
  const cellPad = compact ? "3px 1px" : "6px 2px";
  const cellMin = compact ? 36 : 48;
  const cellFont = compact ? 9 : 10;

  return (
    <div style={{ marginTop: 24, background: theme.surface, border: `1px solid ${theme.border}`, borderRadius: 12, padding: 20 }}>
      <div style={{ display: "flex", justifyContent: "space-between", alignItems: "flex-start", marginBottom: 12 }}>
        <div>
          <h3 style={{ fontSize: 16, fontWeight: 700, marginBottom: 4, color: theme.text }}>Sweet Spot Finder</h3>
          <p style={{ fontSize: 12, color: theme.textDim, margin: 0 }}>
            {loading ? "Loading..." : `${cabin === "j" ? "Business" : "Economy"} class ${metric === "min" ? "lowest" : "average"} mileage by program × month`}
            {appliedOrigin || appliedDest ? ` · ${appliedOrigin || "Any"} (${appliedOriginType})→${appliedDest || "Any"} (${appliedDestType})` : " · All routes"}
          </p>
        </div>
        <div style={{ display: "flex", gap: 8, alignItems: "center", flexWrap: "wrap" }}>
          <div style={{ display: "flex", gap: 3 }}>
            <button onClick={() => setCabin("j")} style={pillStyle(cabin === "j")}>Business</button>
            <button onClick={() => setCabin("y")} style={pillStyle(cabin === "y")}>Economy</button>
          </div>
          <div style={{ width: 1, height: 20, background: theme.border }}></div>
          <div style={{ display: "flex", gap: 3 }}>
            <button onClick={() => setMetric("min")} style={pillStyle(metric === "min")}>Cheapest</button>
            <button onClick={() => setMetric("avg")} style={pillStyle(metric === "avg")}>Average</button>
          </div>
          <div style={{ width: 1, height: 20, background: theme.border }}></div>
          <button onClick={() => setCompact(!compact)} style={pillStyle(compact)}>
            {compact ? "▦ Compact" : "▦ Compact"}
          </button>
        </div>
      </div>

      {/* Route filter + program filter */}
      {(() => {
        const ssTypeStyle = (active) => ({
          padding: "3px 7px", fontSize: 10, borderRadius: 4, cursor: "pointer",
          border: `1px solid ${active ? theme.accent + "60" : theme.border}`,
          background: active ? theme.accent + "20" : "transparent",
          color: active ? theme.accent : theme.textDim,
          fontWeight: active ? 600 : 400, whiteSpace: "nowrap",
        });
        const ssDropStyle = {
          position: "absolute", top: "100%", left: 0, right: 0, zIndex: Z_DROPDOWN,
          background: theme.surface, border: `1px solid ${theme.border}`, borderRadius: 8,
          boxShadow: "0 8px 24px rgba(0,0,0,0.4)", maxHeight: 180, overflowY: "auto", marginTop: 4,
        };
        const placeholders = {
          any: "IATA, city, country, or region...",
          iata: "e.g. JFK, LAX, LHR",
          city: "e.g. New York, London",
          country: "e.g. United States, Japan",
          region: "e.g. Europe, Asia",
        };
        const SSInput = ({ label, value, setValue, type, setType, suggestions, showDrop, setShowDrop, inputRef, fetchSugg }) => (
          <div ref={inputRef} style={{ flex: 1, minWidth: 0 }}>
            <div style={{ fontSize: 10, color: theme.textDim, marginBottom: 3, fontWeight: 600, textTransform: "uppercase", letterSpacing: 0.5 }}>{label}</div>
            <div style={{ display: "flex", gap: 3, marginBottom: 4 }}>
              {["any", "iata", "city", "country", "region"].map(t => (
                <button key={t} onClick={() => setType(t)} style={ssTypeStyle(type === t)}>{t === "any" ? "Any" : t.charAt(0).toUpperCase() + t.slice(1)}</button>
              ))}
            </div>
            <div style={{ position: "relative" }}>
              <input type="text" value={value}
                placeholder={placeholders[type]}
                onChange={e => { setValue(e.target.value); setShowDrop(true); fetchSugg(e.target.value, type); }}
                onFocus={() => { if (value.trim()) setShowDrop(true); }}
                onKeyDown={e => { if (e.key === "Enter") { setShowDrop(false); applyFilter(); } }}
                style={{ ...inputStyle, width: "100%" }} />
              {value && (
                <button onClick={() => { setValue(""); setShowDrop(false); }}
                  style={{ position: "absolute", right: 6, top: "50%", transform: "translateY(-50%)",
                    background: "none", border: "none", color: theme.textDim, cursor: "pointer",
                    fontSize: 14, padding: 0, lineHeight: 1 }}>✕</button>
              )}
              {showDrop && suggestions.length > 0 && (
                <div style={ssDropStyle}>
                  {suggestions.map(s => (
                    <div key={s.key}
                      onClick={() => { setValue(s.key); setShowDrop(false); }}
                      style={{ padding: "7px 12px", fontSize: 12, cursor: "pointer", color: theme.text,
                        borderBottom: `1px solid ${theme.border}22` }}
                      onMouseEnter={e => e.currentTarget.style.background = theme.surfaceHover}
                      onMouseLeave={e => e.currentTarget.style.background = "transparent"}>
                      {s.label}
                    </div>
                  ))}
                </div>
              )}
            </div>
          </div>
        );
        return (
          <div style={{ display: "flex", gap: 12, alignItems: "flex-start", marginBottom: 8 }}>
            <SSInput label="Origin" value={origin} setValue={setOrigin}
              type={originType} setType={setOriginType}
              suggestions={originSugg} showDrop={showOriginDrop} setShowDrop={setShowOriginDrop}
              inputRef={ssOriginRef} fetchSugg={(q, t) => fetchSuggestions(q, t, setOriginSugg)} />
            <div style={{ paddingTop: 30, color: theme.textDim, fontSize: 16, flexShrink: 0 }}>→</div>
            <SSInput label="Destination" value={dest} setValue={setDest}
              type={destType} setType={setDestType}
              suggestions={destSugg} showDrop={showDestDrop} setShowDrop={setShowDestDrop}
              inputRef={ssDestRef} fetchSugg={(q, t) => fetchSuggestions(q, t, setDestSugg)} />
            <div style={{ paddingTop: 24, display: "flex", gap: 6, flexShrink: 0 }}>
              <button onClick={applyFilter}
                style={{ padding: "6px 14px", fontSize: 11, borderRadius: 6, cursor: "pointer",
                  border: `1px solid ${theme.accent}40`, background: theme.accent + "20", color: theme.accent, fontWeight: 600 }}>
                Search
              </button>
              {(appliedOrigin || appliedDest) && (
                <button onClick={clearFilter}
                  style={{ padding: "6px 12px", fontSize: 11, borderRadius: 6, cursor: "pointer",
                    border: `1px solid ${theme.border}`, background: theme.card, color: theme.textDim }}>
                  Clear
                </button>
              )}
            </div>
          </div>
        );
      })()}

      {/* Program filter row */}
      <div style={{ display: "flex", gap: 8, alignItems: "center", marginBottom: 16, fontSize: 12 }}>
        <span style={{ color: theme.textDim, fontSize: 11 }}>Programs:</span>
        <input type="text" value={programFilter} onChange={e => setProgramFilter(e.target.value)}
          placeholder="Filter programs..." style={{ ...inputStyle, width: 140 }} />
        {hiddenPrograms.size > 0 && (
          <button onClick={() => setHiddenPrograms(new Set())}
            style={{ padding: "4px 10px", fontSize: 10, borderRadius: 6, cursor: "pointer",
              border: `1px solid ${theme.border}`, background: theme.card, color: theme.textDim }}>
            Show all ({hiddenPrograms.size} hidden)
          </button>
        )}
        <span style={{ fontSize: 10, color: theme.textDim }}>{filteredPrograms.length} of {programs.length} shown</span>
        <div style={{ marginLeft: "auto", display: "flex", alignItems: "center", gap: 6, fontSize: 10, color: theme.textDim }}>
          <span>Cheap</span>
          <div style={{ width: 80, height: 8, borderRadius: 4, background: "linear-gradient(to right, rgb(16,185,129), rgb(250,180,36), rgb(239,68,68))" }}></div>
          <span>Expensive</span>
        </div>
      </div>

      {/* Heatmap grid */}
      {!loading && filteredPrograms.length > 0 && (
        <div style={{ overflowX: "auto" }}>
          <table style={{ borderCollapse: "collapse", width: "100%", fontSize: cellFont }}>
            <thead>
              <tr>
                <th style={{ padding: compact ? "4px 6px" : "6px 10px", textAlign: "left", color: theme.textDim, fontWeight: 600,
                  position: "sticky", left: 0, background: theme.surface, zIndex: 2, minWidth: compact ? 70 : 100 }}>Program</th>
                {months.map(m => (
                  <th key={m} style={{ padding: compact ? "4px 2px" : "6px 4px", textAlign: "center", color: theme.textDim, fontWeight: 500, whiteSpace: "nowrap", fontSize: compact ? 9 : 11 }}>
                    {monthLabel(m)}
                  </th>
                ))}
              </tr>
            </thead>
            <tbody>
              {filteredPrograms.map(source => {
                const pInfo = PROGRAMS[source] || {};
                return (
                  <tr key={source}>
                    <td style={{ padding: compact ? "2px 6px" : "4px 10px", fontWeight: 600, color: theme.text, whiteSpace: "nowrap",
                      position: "sticky", left: 0, background: theme.surface, zIndex: 1, borderBottom: `1px solid ${theme.border}22`,
                      fontSize: compact ? 9 : 11, cursor: "pointer" }}
                      title="Click to hide this program"
                      onClick={() => toggleHideProgram(source)}>
                      {pInfo.name ? pInfo.name.split(" ").slice(-1)[0] : source}
                    </td>
                    {months.map(m => {
                      const val = getValue(source, m);
                      const isHov = hovered && hovered.source === source && hovered.month === m;
                      const isSel = selected && selected.source === source && selected.month === m;
                      return (
                        <td key={m}
                          onMouseEnter={() => setHovered({ source, month: m })}
                          onMouseLeave={() => setHovered(null)}
                          onClick={() => handleCellClick(source, m)}
                          style={{ padding: compact ? 1 : 2, borderBottom: `1px solid ${theme.border}22`, cursor: "pointer" }}>
                          <div style={{
                            background: cellColor(val),
                            opacity: val != null ? 0.75 : 0,
                            borderRadius: compact ? 3 : 4,
                            padding: cellPad,
                            textAlign: "center",
                            color: val ? "#000" : theme.textDim + "40",
                            fontWeight: val ? 700 : 400,
                            fontSize: cellFont,
                            minWidth: cellMin,
                            border: isSel ? "2px solid #60a5fa" : isHov ? "2px solid #fff" : "2px solid transparent",
                            transition: "border 0.1s",
                          }}>
                            {val ? `${Math.round(val / 1000)}k` : "—"}
                          </div>
                        </td>
                      );
                    })}
                  </tr>
                );
              })}
            </tbody>
          </table>
        </div>
      )}

      {/* Detail panel — shows selected (pinned) or hovered cell */}
      {activeCell && (
        <div style={{ marginTop: 12, padding: 14, background: theme.card, borderRadius: 8,
          border: `1px solid ${selected ? "#60a5fa40" : theme.border}`, fontSize: 12 }}>
          <div style={{ display: "flex", justifyContent: "space-between", alignItems: "center", marginBottom: 10 }}>
            <div>
              <b style={{ color: theme.text, fontSize: 14 }}>{(PROGRAMS[activeRef.source] || {}).name || activeRef.source}</b>
              <span style={{ color: theme.textDim, marginLeft: 8 }}>{monthLabel(activeRef.month)}</span>
              {selected && (
                <button onClick={() => setSelected(null)}
                  style={{ marginLeft: 10, padding: "2px 8px", fontSize: 10, borderRadius: 4, cursor: "pointer",
                    border: `1px solid ${theme.border}`, background: theme.surface, color: theme.textDim }}>✕ Unpin</button>
              )}
            </div>
            <div style={{ fontSize: 11, color: theme.textDim }}>
              {activeCell.flights?.toLocaleString()} flights · {activeCell.routes} routes
            </div>
          </div>
          <div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: 10 }}>
            {/* Business class deal */}
            <div style={{ padding: 10, background: theme.surface, borderRadius: 6, border: `1px solid #fbbf2430` }}>
              <div style={{ fontSize: 10, color: "#fbbf24", fontWeight: 600, textTransform: "uppercase", letterSpacing: 0.5, marginBottom: 6 }}>Best Business Class</div>
              {activeCell.min_j ? (
                <>
                  <div style={{ fontSize: 20, fontWeight: 800, color: "#fbbf24", marginBottom: 4 }}>
                    {activeCell.min_j.toLocaleString()} <span style={{ fontSize: 12, fontWeight: 500 }}>miles</span>
                  </div>
                  {activeCell.best_j_route && (
                    <div style={{ marginBottom: 2 }}>
                      <div style={{ fontSize: 12, color: theme.text, fontWeight: 600 }}>
                        ✈ {activeCell.best_j_route.replace("-", " → ")}
                      </div>
                      {(activeCell.best_j_ocity || activeCell.best_j_dcity) && (
                        <div style={{ fontSize: 10, color: theme.textDim, marginTop: 1 }}>
                          {activeCell.best_j_ocity}{activeCell.best_j_ocountry ? `, ${activeCell.best_j_ocountry}` : ""} → {activeCell.best_j_dcity}{activeCell.best_j_dcountry ? `, ${activeCell.best_j_dcountry}` : ""}
                        </div>
                      )}
                    </div>
                  )}
                  {activeCell.best_j_date && (
                    <div style={{ fontSize: 11, color: theme.textDim }}>
                      📅 {activeCell.best_j_date}
                    </div>
                  )}
                  <div style={{ fontSize: 10, color: theme.textDim, marginTop: 4 }}>
                    Avg: {activeCell.avg_j ? `${activeCell.avg_j.toLocaleString()} mi` : "—"}
                  </div>
                </>
              ) : (
                <div style={{ color: theme.textDim, fontSize: 11 }}>No business availability</div>
              )}
            </div>
            {/* Economy class deal */}
            <div style={{ padding: 10, background: theme.surface, borderRadius: 6, border: `1px solid #10b98130` }}>
              <div style={{ fontSize: 10, color: "#10b981", fontWeight: 600, textTransform: "uppercase", letterSpacing: 0.5, marginBottom: 6 }}>Best Economy Class</div>
              {activeCell.min_y ? (
                <>
                  <div style={{ fontSize: 20, fontWeight: 800, color: "#10b981", marginBottom: 4 }}>
                    {activeCell.min_y.toLocaleString()} <span style={{ fontSize: 12, fontWeight: 500 }}>miles</span>
                  </div>
                  {activeCell.best_y_route && (
                    <div style={{ marginBottom: 2 }}>
                      <div style={{ fontSize: 12, color: theme.text, fontWeight: 600 }}>
                        ✈ {activeCell.best_y_route.replace("-", " → ")}
                      </div>
                      {(activeCell.best_y_ocity || activeCell.best_y_dcity) && (
                        <div style={{ fontSize: 10, color: theme.textDim, marginTop: 1 }}>
                          {activeCell.best_y_ocity}{activeCell.best_y_ocountry ? `, ${activeCell.best_y_ocountry}` : ""} → {activeCell.best_y_dcity}{activeCell.best_y_dcountry ? `, ${activeCell.best_y_dcountry}` : ""}
                        </div>
                      )}
                    </div>
                  )}
                  {activeCell.best_y_date && (
                    <div style={{ fontSize: 11, color: theme.textDim }}>
                      📅 {activeCell.best_y_date}
                    </div>
                  )}
                  <div style={{ fontSize: 10, color: theme.textDim, marginTop: 4 }}>
                    Avg: {activeCell.avg_y ? `${activeCell.avg_y.toLocaleString()} mi` : "—"}
                  </div>
                </>
              ) : (
                <div style={{ color: theme.textDim, fontSize: 11 }}>No economy availability</div>
              )}
            </div>
          </div>
        </div>
      )}

      {!loading && filteredPrograms.length === 0 && programs.length > 0 && (
        <div style={{ textAlign: "center", padding: 30, color: theme.textDim }}>
          No programs match filter.{" "}
          <button onClick={() => { setProgramFilter(""); setHiddenPrograms(new Set()); }}
            style={{ color: theme.accent, background: "none", border: "none", cursor: "pointer", textDecoration: "underline" }}>Reset</button>
        </div>
      )}

      {!loading && programs.length === 0 && (
        <div style={{ textAlign: "center", padding: 40, color: theme.textDim }}>
          No data found{appliedOrigin || appliedDest ? ` for ${appliedOrigin || "Any"}→${appliedDest || "Any"}` : ""}. Try different airports.
        </div>
      )}
    </div>
  );
}

// ─── CabinCalendar ──────────────────────────────────────────────────────────
function CabinCalendar() {
  const [data, setData] = useState(null);
  const [loading, setLoading] = useState(false);
  const [origin, setOrigin] = useState("");
  const [dest, setDest] = useState("");
  const [originType, setOriginType] = useState("any");
  const [destType, setDestType] = useState("any");
  const [appliedOrigin, setAppliedOrigin] = useState("");
  const [appliedDest, setAppliedDest] = useState("");
  const [appliedOriginType, setAppliedOriginType] = useState("any");
  const [appliedDestType, setAppliedDestType] = useState("any");
  const [currentMonth, setCurrentMonth] = useState(() => {
    const d = new Date(); return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, "0")}`;
  });
  const [selectedDay, setSelectedDay] = useState(null);
  const [hoveredDay, setHoveredDay] = useState(null);
  const [originSugg, setOriginSugg] = useState([]);
  const [destSugg, setDestSugg] = useState([]);
  const [showOriginDrop, setShowOriginDrop] = useState(false);
  const [showDestDrop, setShowDestDrop] = useState(false);
  const ccOriginRef = useRef(null);
  const ccDestRef = useRef(null);

  useEffect(() => {
    const handler = (e) => {
      if (ccOriginRef.current && !ccOriginRef.current.contains(e.target)) setShowOriginDrop(false);
      if (ccDestRef.current && !ccDestRef.current.contains(e.target)) setShowDestDrop(false);
    };
    document.addEventListener("mousedown", handler);
    return () => document.removeEventListener("mousedown", handler);
  }, []);

  const fetchSugg = useCallback((query, type, setter) => {
    if (!query || query.length < 1) { setter([]); return; }
    fetch(`${API}/api/airports?search=${encodeURIComponent(query)}&active_only=true`)
      .then(r => r.json())
      .then(d => {
        const airports = d.airports || [];
        const seen = new Set();
        const results = [];
        airports.forEach(a => {
          let key, label;
          if (type === "country") { key = a.country; label = a.country; }
          else if (type === "region") { key = a.continent; label = a.continent; }
          else if (type === "city") { key = a.city; label = `${a.city}, ${a.country}`; }
          else { key = a.iata_code; label = `${a.iata_code} — ${a.city}, ${a.country}`; }
          if (key && !seen.has(key)) { seen.add(key); results.push({ key, label }); }
        });
        setter(results.slice(0, 12));
      }).catch(() => setter([]));
  }, []);

  const fetchData = useCallback((o, d, oType, dType, mo) => {
    setLoading(true);
    const params = new URLSearchParams();
    if (o) { params.set("origin", o); params.set("origin_type", oType || "iata"); }
    if (d) { params.set("destination", d); params.set("dest_type", dType || "iata"); }
    params.set("month", mo);
    fetch(`${API}/api/dashboard/calendar-availability?${params}`)
      .then(r => r.json())
      .then(d => { setData(d); setLoading(false); })
      .catch(() => setLoading(false));
  }, []);

  const applyFilter = () => {
    const o = origin.trim(), d = dest.trim();
    setAppliedOrigin(o); setAppliedDest(d);
    setAppliedOriginType(originType); setAppliedDestType(destType);
    setSelectedDay(null); setHoveredDay(null);
    fetchData(o, d, originType, destType, currentMonth);
  };

  const clearFilter = () => {
    setOrigin(""); setDest("");
    setAppliedOrigin(""); setAppliedDest("");
    setOriginType("any"); setDestType("any");
    setData(null); setSelectedDay(null);
  };

  // Don't allow navigating to months entirely in the past
  const todayStr = new Date().toISOString().slice(0, 7); // "YYYY-MM"

  const shiftMonth = (dir) => {
    const [y, m] = currentMonth.split("-").map(Number);
    let ny = y, nm = m + dir;
    if (nm > 12) { nm = 1; ny++; }
    if (nm < 1) { nm = 12; ny--; }
    const next = `${ny}-${String(nm).padStart(2, "0")}`;
    // Block going before the current month
    if (next < todayStr) return;
    setCurrentMonth(next);
    setSelectedDay(null); setHoveredDay(null);
    if (appliedOrigin || appliedDest) fetchData(appliedOrigin, appliedDest, appliedOriginType, appliedDestType, next);
  };

  // Build calendar grid
  const buildCalendarDays = () => {
    const [y, m] = currentMonth.split("-").map(Number);
    const firstDay = new Date(y, m - 1, 1).getDay(); // 0=Sun
    const daysInMonth = new Date(y, m, 0).getDate();
    const grid = [];
    // Leading empties
    for (let i = 0; i < firstDay; i++) grid.push(null);
    // Actual days
    for (let d = 1; d <= daysInMonth; d++) {
      grid.push(`${y}-${String(m).padStart(2, "0")}-${String(d).padStart(2, "0")}`);
    }
    return grid;
  };

  const calDays = buildCalendarDays();
  const days = data?.days || {};
  const scale = data?.scale || {};

  const cabinColors = { y: "#3b82f6", w: "#8b5cf6", j: "#f59e0b", f: "#ef4444" };
  const cabinLabels = { y: "Economy", w: "Premium", j: "Business", f: "First" };

  const formatMiles = (v) => {
    if (v == null) return "—";
    if (v >= 1000) return `${Math.round(v / 1000)}k`;
    return String(v);
  };

  const monthNames = ["", "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"];
  const [curY, curM] = currentMonth.split("-").map(Number);
  const monthTitle = `${monthNames[curM]} ${curY}`;

  const activeDay = selectedDay || hoveredDay;
  const activeDayData = activeDay ? days[activeDay] : null;

  const inputStyle = {
    padding: "5px 10px", fontSize: 12, borderRadius: 6, width: "100%",
    border: `1px solid ${theme.border}`, background: theme.card, color: theme.text,
  };
  const pillStyle = (active) => ({
    padding: "4px 10px", fontSize: 11, borderRadius: 6, cursor: "pointer",
    border: `1px solid ${active ? theme.accent + "60" : theme.border}`,
    background: active ? theme.accent + "20" : "transparent",
    color: active ? theme.accent : theme.textDim, fontWeight: active ? 600 : 400,
  });
  const ssTypeStyle = (active) => ({
    padding: "3px 7px", fontSize: 10, borderRadius: 4, cursor: "pointer",
    border: `1px solid ${active ? theme.accent + "60" : theme.border}`,
    background: active ? theme.accent + "20" : "transparent",
    color: active ? theme.accent : theme.textDim, fontWeight: active ? 600 : 400, whiteSpace: "nowrap",
  });
  const ssDropStyle = {
    position: "absolute", top: "100%", left: 0, right: 0, zIndex: Z_DROPDOWN,
    background: theme.surface, border: `1px solid ${theme.border}`, borderRadius: 8,
    boxShadow: "0 8px 24px rgba(0,0,0,0.4)", maxHeight: 180, overflowY: "auto", marginTop: 4,
  };
  const placeholders = {
    any: "IATA, city, country, or region...", iata: "e.g. JFK, LAX, LHR",
    city: "e.g. New York, London", country: "e.g. United States, Japan", region: "e.g. Europe, Asia",
  };

  const CCInput = ({ label, value, setValue, type, setType, suggestions, showDrop, setShowDrop, inputRef, fetchS }) => (
    <div ref={inputRef} style={{ flex: 1, minWidth: 0 }}>
      <div style={{ fontSize: 10, color: theme.textDim, marginBottom: 3, fontWeight: 600, textTransform: "uppercase", letterSpacing: 0.5 }}>{label}</div>
      <div style={{ display: "flex", gap: 3, marginBottom: 4 }}>
        {["any", "iata", "city", "country", "region"].map(t => (
          <button key={t} onClick={() => setType(t)} style={ssTypeStyle(type === t)}>{t === "any" ? "Any" : t.charAt(0).toUpperCase() + t.slice(1)}</button>
        ))}
      </div>
      <div style={{ position: "relative" }}>
        <input type="text" value={value}
          placeholder={placeholders[type]}
          onChange={e => { setValue(e.target.value); setShowDrop(true); fetchS(e.target.value, type); }}
          onFocus={() => { if (value.trim()) setShowDrop(true); }}
          onKeyDown={e => { if (e.key === "Enter") { setShowDrop(false); applyFilter(); } }}
          style={inputStyle} />
        {value && (
          <button onClick={() => { setValue(""); setShowDrop(false); }}
            style={{ position: "absolute", right: 6, top: "50%", transform: "translateY(-50%)",
              background: "none", border: "none", color: theme.textDim, cursor: "pointer", fontSize: 14, padding: 0, lineHeight: 1 }}>✕</button>
        )}
        {showDrop && suggestions.length > 0 && (
          <div style={ssDropStyle}>
            {suggestions.map(s => (
              <div key={s.key} onClick={() => { setValue(s.key); setShowDrop(false); }}
                style={{ padding: "7px 12px", fontSize: 12, cursor: "pointer", color: theme.text, borderBottom: `1px solid ${theme.border}22` }}
                onMouseEnter={e => e.currentTarget.style.background = theme.surfaceHover}
                onMouseLeave={e => e.currentTarget.style.background = "transparent"}>
                {s.label}
              </div>
            ))}
          </div>
        )}
      </div>
    </div>
  );

  const dayOfWeek = (dateStr) => {
    const d = new Date(dateStr + "T12:00:00");
    return d.toLocaleDateString("en-US", { weekday: "long" });
  };

  return (
    <div style={{ marginTop: 24, background: theme.surface, border: `1px solid ${theme.border}`, borderRadius: 12, padding: 20 }}>
      <h3 style={{ fontSize: 16, fontWeight: 700, marginBottom: 4, color: theme.text }}>Cabin Class Calendar</h3>
      <p style={{ fontSize: 12, color: theme.textDim, margin: "0 0 12px 0" }}>
        Daily availability by cabin class — pick a route to see which dates have award space
        {appliedOrigin || appliedDest ? ` · ${appliedOrigin || "Any"} (${appliedOriginType}) → ${appliedDest || "Any"} (${appliedDestType})` : ""}
      </p>

      {/* Filters */}
      <div style={{ display: "flex", gap: 12, marginBottom: 16, flexWrap: "wrap", alignItems: "flex-end" }}>
        <CCInput label="Origin" value={origin} setValue={setOrigin} type={originType} setType={setOriginType}
          suggestions={originSugg} showDrop={showOriginDrop} setShowDrop={setShowOriginDrop}
          inputRef={ccOriginRef} fetchS={(q, t) => fetchSugg(q, t, setOriginSugg)} />
        <div style={{ color: theme.textDim, fontSize: 16, paddingBottom: 6 }}>→</div>
        <CCInput label="Destination" value={dest} setValue={setDest} type={destType} setType={setDestType}
          suggestions={destSugg} showDrop={showDestDrop} setShowDrop={setShowDestDrop}
          inputRef={ccDestRef} fetchS={(q, t) => fetchSugg(q, t, setDestSugg)} />
        <div style={{ display: "flex", gap: 6, paddingBottom: 2 }}>
          <button onClick={applyFilter} style={{ ...pillStyle(true), padding: "6px 16px", fontSize: 12 }}>Search</button>
          {(appliedOrigin || appliedDest) && (
            <button onClick={clearFilter} style={{ ...pillStyle(false), padding: "6px 12px", fontSize: 12 }}>Clear</button>
          )}
        </div>
      </div>

      {/* Month navigation */}
      <div style={{ display: "flex", alignItems: "center", justifyContent: "center", gap: 20, marginBottom: 16 }}>
        <button onClick={() => shiftMonth(-1)} disabled={currentMonth <= todayStr}
          style={{ background: "none", border: `1px solid ${theme.border}`, borderRadius: 6, color: currentMonth <= todayStr ? theme.border : theme.text, cursor: currentMonth <= todayStr ? "not-allowed" : "pointer", padding: "4px 12px", fontSize: 16, opacity: currentMonth <= todayStr ? 0.4 : 1 }}>◀</button>
        <span style={{ fontSize: 16, fontWeight: 700, color: theme.text, minWidth: 180, textAlign: "center" }}>{monthTitle}</span>
        <button onClick={() => shiftMonth(1)}
          style={{ background: "none", border: `1px solid ${theme.border}`, borderRadius: 6, color: theme.text, cursor: "pointer", padding: "4px 12px", fontSize: 16 }}>▶</button>
      </div>

      {/* Legend — 4 cabin bars with labels */}
      <div style={{ display: "flex", gap: 16, justifyContent: "center", marginBottom: 12, fontSize: 11, flexWrap: "wrap" }}>
        {["y", "w", "j", "f"].map(c => (
          <div key={c} style={{ display: "flex", alignItems: "center", gap: 5 }}>
            <div style={{ width: 24, height: 8, borderRadius: 2, background: cabinColors[c] }}></div>
            <span style={{ color: theme.textDim }}>{cabinLabels[c]}</span>
          </div>
        ))}
        <div style={{ display: "flex", alignItems: "center", gap: 5 }}>
          <div style={{ width: 24, height: 8, borderRadius: 2, background: theme.border + "30" }}></div>
          <span style={{ color: theme.textDim }}>No Availability</span>
        </div>
      </div>

      {loading && (
        <div style={{ textAlign: "center", padding: 40, color: theme.textDim }}>Loading calendar...</div>
      )}

      {!loading && !data && (
        <div style={{ textAlign: "center", padding: 40, color: theme.textDim }}>
          Search for a route to see cabin availability by date.
        </div>
      )}

      {!loading && data && (
        <>
          {/* Calendar grid */}
          <div style={{ display: "grid", gridTemplateColumns: "repeat(7, 1fr)", gap: 2 }}>
            {/* Day headers */}
            {["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"].map(d => (
              <div key={d} style={{ textAlign: "center", fontSize: 11, fontWeight: 600, color: theme.textDim, padding: "4px 0" }}>{d}</div>
            ))}

            {/* Day cells */}
            {calDays.map((dateStr, i) => {
              if (!dateStr) return <div key={`empty-${i}`} style={{ minHeight: 70 }}></div>;
              const dayNum = parseInt(dateStr.split("-")[2]);
              const dayData = days[dateStr];
              const isSelected = selectedDay === dateStr;
              const isHovered = hoveredDay === dateStr;
              const hasData = dayData && ["y", "w", "j", "f"].some(c => dayData[c]?.min);

              return (
                <div key={dateStr}
                  onClick={() => setSelectedDay(isSelected ? null : dateStr)}
                  onMouseEnter={() => setHoveredDay(dateStr)}
                  onMouseLeave={() => setHoveredDay(null)}
                  style={{
                    minHeight: 70, padding: "4px 5px", borderRadius: 6, cursor: "pointer",
                    background: isSelected ? theme.accent + "15" : isHovered ? theme.surfaceHover : theme.card,
                    border: `1px solid ${isSelected ? theme.accent + "60" : isHovered ? theme.border : theme.border + "40"}`,
                    transition: "all 0.15s ease",
                  }}>
                  <div style={{ fontSize: 12, fontWeight: hasData ? 700 : 400, color: hasData ? theme.text : theme.textDim, marginBottom: 3 }}>{dayNum}</div>
                  <div style={{ display: "flex", flexDirection: "column", gap: 2 }}>
                    {["y", "w", "j", "f"].map(c => {
                      const val = dayData && dayData[c]?.min;
                      const hasAvail = val != null && val > 0;
                      return (
                        <div key={c} style={{
                          height: 11, borderRadius: 2, display: "flex", alignItems: "center",
                          paddingLeft: 3, fontSize: 8, fontWeight: 600,
                          background: hasAvail ? cabinColors[c] : theme.border + "20",
                          color: hasAvail ? "#fff" : "transparent",
                          textShadow: hasAvail ? "0 1px 2px rgba(0,0,0,0.5)" : "none",
                          opacity: hasAvail ? 1 : 0.5,
                        }}>
                          {hasAvail ? formatMiles(val) : ""}
                        </div>
                      );
                    })}
                  </div>
                </div>
              );
            })}
          </div>

          {/* Detail panel */}
          {activeDay && activeDayData && (
            <div style={{ marginTop: 12, padding: 16, background: theme.card, border: `1px solid ${theme.border}`, borderRadius: 10 }}>
              <div style={{ display: "flex", justifyContent: "space-between", alignItems: "center", marginBottom: 12 }}>
                <div>
                  <span style={{ fontSize: 14, fontWeight: 700, color: theme.text }}>{dayOfWeek(activeDay)}, {activeDay}</span>
                  {activeDayData.programs && (
                    <span style={{ fontSize: 11, color: theme.textDim, marginLeft: 8 }}>
                      {activeDayData.programs} program{activeDayData.programs !== 1 ? "s" : ""} · {activeDayData.routes} route{activeDayData.routes !== 1 ? "s" : ""}
                    </span>
                  )}
                </div>
                {selectedDay && (
                  <button onClick={() => setSelectedDay(null)}
                    style={{ background: "none", border: "none", color: theme.textDim, cursor: "pointer", fontSize: 16 }}>✕</button>
                )}
              </div>
              <div className="cabin-type-grid" style={{ display: "grid", gridTemplateColumns: "repeat(4, 1fr)", gap: 12 }}>
                {["y", "w", "j", "f"].map(c => {
                  const cd = activeDayData[c];
                  const hasAvail = cd && cd.min;
                  return (
                    <div key={c} style={{
                      padding: 12, borderRadius: 8,
                      background: hasAvail ? cabinColors[c] + "15" : theme.surface,
                      border: `1px solid ${hasAvail ? cabinColors[c] + "40" : theme.border + "40"}`,
                    }}>
                      <div style={{ fontSize: 11, fontWeight: 700, color: cabinColors[c], marginBottom: 6, textTransform: "uppercase", letterSpacing: 0.5 }}>
                        {c.toUpperCase()} {cabinLabels[c]}
                      </div>
                      {hasAvail ? (<>
                        <div style={{ fontSize: 18, fontWeight: 800, color: theme.text, marginBottom: 4 }}>
                          {cd.min.toLocaleString()} mi
                        </div>
                        {cd.route && (
                          <div style={{ fontSize: 12, color: theme.textMuted, marginBottom: 2 }}>
                            {cd.ocity || cd.route.split("-")[0]} → {cd.dcity || cd.route.split("-")[1]}
                          </div>
                        )}
                        {cd.source && (
                          <div style={{ fontSize: 11, color: theme.textDim }}>
                            via {(PROGRAMS[cd.source]?.name || cd.source)}
                          </div>
                        )}
                        <div style={{ fontSize: 11, color: theme.textDim, marginTop: 4 }}>
                          {cd.count} flight{cd.count !== 1 ? "s" : ""}
                        </div>
                      </>) : (
                        <div style={{ fontSize: 13, color: theme.textDim }}>No availability</div>
                      )}
                    </div>
                  );
                })}
              </div>
            </div>
          )}

          {/* Empty state */}
          {Object.keys(days).length === 0 && (
            <div style={{ textAlign: "center", padding: 30, color: theme.textDim }}>
              No availability found for this route in {monthTitle}. Try a different month or route.
            </div>
          )}
        </>
      )}
    </div>
  );
}

// ─── IngestionHealth ────────────────────────────────────────────────────────
function IngestionHealth({ stats, history }) {
  const programHealth = history?.per_source || {};
  const programs = Object.entries(PROGRAMS);
  return (
    <Section title="Ingestion Health" icon="🔄">
      <div className="dash-4col" style={{ display: "grid", gridTemplateColumns: "repeat(4, 1fr)", gap: 12, marginBottom: 20 }}>
        {[
          { label: "Last Run", value: fmt.ago(stats?.last_ingestion) },
          { label: "API Calls", value: `${stats?.api_calls_used || "—"} / 1000` },
          { label: "Total Rows", value: stats?.total_availability?.toLocaleString() || "—", color: theme.accent },
          { label: "DB Size", value: stats?.db_size || "—" },
        ].map((item, i) => (
          <div key={i} style={{ background: theme.card, border: `1px solid ${theme.border}`, borderRadius: 10, padding: 16, textAlign: "center" }}>
            <div style={{ fontSize: 11, color: theme.textDim, textTransform: "uppercase", letterSpacing: 1, marginBottom: 4 }}>{item.label}</div>
            <div style={{ fontSize: 16, fontWeight: 700, color: item.color || theme.text }}>{item.value}</div>
          </div>
        ))}
      </div>
      <div style={{ overflowX: "auto" }}>
        <table style={{ width: "100%", borderCollapse: "collapse", fontSize: 13 }}>
          <thead>
            <tr style={{ borderBottom: `1px solid ${theme.border}` }}>
              {["Program", "Status", "Rows", "New", "Pages", "Dates / Target", "Last Run"].map((h, i) => (
                <th key={i} style={{ padding: "8px 12px", textAlign: i >= 2 && i !== 5 ? "right" : i === 5 ? "center" : "left", color: theme.textDim, fontSize: 11, textTransform: "uppercase", letterSpacing: 0.5 }}>{h}</th>
              ))}
            </tr>
          </thead>
          <tbody>
            {programs.map(([slug, prog], i) => {
              const h = programHealth[slug];
              const status = h?.errors?.length ? "error" : h?.total_rows > 0 ? "healthy" : "stale";
              return (
                <tr key={slug} style={{ borderBottom: `1px solid ${theme.border}`, background: i % 2 === 0 ? "transparent" : theme.card + "40" }}>
                  <td style={{ padding: "10px 12px" }}><span style={{ fontWeight: 600 }}>{prog.name}</span><span style={{ color: theme.textDim, fontSize: 11, marginLeft: 6 }}>{slug}</span></td>
                  <td style={{ padding: "10px 12px" }}><StatusDot status={status} /><span style={{ fontSize: 12, color: status === "healthy" ? theme.success : status === "error" ? theme.danger : theme.textDim }}>{status === "healthy" ? "OK" : status === "error" ? "Error" : "No data"}</span></td>
                  <td style={{ padding: "10px 12px", textAlign: "right", fontFamily: "monospace" }}>{h?.total_rows?.toLocaleString() || "—"}</td>
                  <td style={{ padding: "10px 12px", textAlign: "right", fontFamily: "monospace", color: h?.inserted > 0 ? theme.success : theme.textDim }}>{h?.inserted?.toLocaleString() || "—"}</td>
                  <td style={{ padding: "10px 12px", textAlign: "right", fontFamily: "monospace" }}>{h?.pages || "—"}</td>
                  <td style={{ padding: "10px 12px", textAlign: "center", fontFamily: "monospace", fontSize: 12 }}>
                    {h?.min_date ? (<div><div>{h.min_date.slice(5)} — {h.max_date.slice(5)}</div><div style={{ fontSize: 10, color: theme.textDim }}>{h.date_count} days</div></div>) : "—"}
                  </td>
                  <td style={{ padding: "10px 12px", textAlign: "right", color: theme.textDim, fontSize: 12 }}>{h?.duration_s ? `${h.duration_s}s` : "—"}</td>
                </tr>
              );
            })}
          </tbody>
        </table>
      </div>
    </Section>
  );
}

// ─── HighScores ─────────────────────────────────────────────────────────────
function HighScores({ highScores }) {
  if (!highScores || Object.keys(highScores).length === 0) {
    return (<Section title="🏆 Program High Scores" icon=""><div style={{ padding: 24, textAlign: "center", color: theme.textDim }}>No high scores yet — data populates after first rolling ingestion run.</div></Section>);
  }
  const sorted = Object.entries(highScores).sort((a, b) => (b[1].max_rows || 0) - (a[1].max_rows || 0));
  const totalRows = sorted.reduce((sum, [, v]) => sum + (v.max_rows || 0), 0);
  const totalPages = sorted.reduce((sum, [, v]) => sum + (v.max_pages || 0), 0);
  const incomplete = sorted.filter(([, v]) => v.has_more_pages).length;
  return (
    <Section title="🏆 Program High Scores" icon="">
      <div className="dash-4col" style={{ display: "grid", gridTemplateColumns: "repeat(4, 1fr)", gap: 12, marginBottom: 20 }}>
        {[
          { label: "Programs Tracked", value: `${sorted.length} / 25` },
          { label: "Total Max Rows", value: totalRows.toLocaleString(), color: theme.gold },
          { label: "Total Max Pages", value: totalPages.toLocaleString() },
          { label: "Fully Scanned", value: `${sorted.length - incomplete} / ${sorted.length}`, color: incomplete === 0 ? theme.success : theme.warning },
        ].map((item, i) => (
          <div key={i} style={{ background: theme.card, border: `1px solid ${theme.border}`, borderRadius: 10, padding: 16, textAlign: "center" }}>
            <div style={{ fontSize: 11, color: theme.textDim, textTransform: "uppercase", letterSpacing: 1, marginBottom: 4 }}>{item.label}</div>
            <div style={{ fontSize: 16, fontWeight: 700, color: item.color || theme.text }}>{item.value}</div>
          </div>
        ))}
      </div>
      <div style={{ overflowX: "auto" }}>
        <table style={{ width: "100%", borderCollapse: "collapse", fontSize: 13 }}>
          <thead><tr style={{ borderBottom: `1px solid ${theme.border}` }}>
            {["Program", "Max Rows", "Pages", "Complete?", "Last Updated"].map((h, i) => (
              <th key={i} style={{ padding: "8px 12px", textAlign: i === 0 ? "left" : i === 3 ? "center" : "right", color: theme.textDim, fontSize: 11, textTransform: "uppercase", letterSpacing: 0.5 }}>{h}</th>
            ))}
          </tr></thead>
          <tbody>
            {sorted.map(([slug, info], i) => {
              const maxRows = info.max_rows || 0;
              const pct = totalRows > 0 ? (maxRows / sorted[0][1].max_rows) * 100 : 0;
              const prog = PROGRAMS[slug];
              const complete = !info.has_more_pages;
              return (
                <tr key={slug} style={{ borderBottom: `1px solid ${theme.border}`, background: i % 2 === 0 ? "transparent" : theme.card + "40" }}>
                  <td style={{ padding: "10px 12px" }}><span style={{ fontWeight: 600 }}>{prog?.name || slug}</span>{i === 0 && <span style={{ marginLeft: 6, fontSize: 11 }}>👑</span>}</td>
                  <td style={{ padding: "10px 12px" }}>
                    <div style={{ display: "flex", alignItems: "center", gap: 10 }}>
                      <div style={{ flex: 1, height: 8, background: theme.border, borderRadius: 4, overflow: "hidden" }}>
                        <div style={{ width: `${pct}%`, height: "100%", background: complete ? `linear-gradient(90deg, ${theme.success}, ${theme.success}88)` : `linear-gradient(90deg, ${theme.accent}, ${theme.gold})`, borderRadius: 4, transition: "width 0.5s ease" }} />
                      </div>
                      <span style={{ fontFamily: "monospace", fontSize: 13, fontWeight: 600, minWidth: 70, textAlign: "right" }}>{maxRows.toLocaleString()}</span>
                    </div>
                  </td>
                  <td style={{ padding: "10px 12px", textAlign: "right", fontFamily: "monospace" }}>{info.max_pages || 0}</td>
                  <td style={{ padding: "10px 12px", textAlign: "center" }}>
                    <span style={{ fontSize: 11, padding: "2px 8px", borderRadius: 8, background: complete ? theme.success + "20" : theme.warning + "20", color: complete ? theme.success : theme.warning }}>{complete ? "✅ Yes" : "📄 More"}</span>
                  </td>
                  <td style={{ padding: "10px 12px", textAlign: "right", color: theme.textDim, fontSize: 12 }}>{(info.last_updated || "").slice(0, 10) || "—"}</td>
                </tr>
              );
            })}
          </tbody>
        </table>
      </div>
    </Section>
  );
}

// ─── TransferBonuses ────────────────────────────────────────────────────────
function TransferBonuses({ bonuses, onRefresh }) {
  const BANK_NAMES = { chase_ur: 'Chase Ultimate Rewards', amex_mr: 'Amex Membership Rewards', capital_one: 'Capital One Miles', bilt: 'Bilt Rewards', citi_typ: 'Citi ThankYou Points', wells_fargo: 'Wells Fargo Rewards', rove: 'Rove' };
  const PROGRAM_NAMES = { aeroplan: 'Air Canada Aeroplan', flyingblue: 'Air France/KLM Flying Blue', united: 'United MileagePlus', virginatlantic: 'Virgin Atlantic Flying Club', singapore: 'Singapore KrisFlyer', avios: 'British Airways Avios', marriott: 'Marriott Bonvoy', jal: 'JAL Mileage Bank', delta: 'Delta SkyMiles', emirates: 'Emirates Skywards' };
  const activeBonuses = (bonuses || []).filter(b => b.bonus_pct > 0);
  const grouped = {};
  activeBonuses.forEach(b => { if (!grouped[b.bank]) grouped[b.bank] = []; grouped[b.bank].push(b); });
  return (
    <Section title="Active Transfer Bonuses" icon="🔥">
      <div style={{ display: "flex", justifyContent: "space-between", alignItems: "center", marginBottom: 16 }}>
        <p style={{ color: theme.textMuted, fontSize: 13, margin: 0 }}>Current point transfer bonuses from credit card programs</p>
        <button onClick={onRefresh} style={{ background: theme.accent, color: "#fff", border: "none", borderRadius: 6, padding: "6px 16px", cursor: "pointer", fontSize: 13, fontWeight: 600 }}>Refresh</button>
      </div>
      {activeBonuses.length === 0 ? (
        <div style={{ textAlign: "center", padding: 32, color: theme.textDim }}>
          <div style={{ fontSize: 24, marginBottom: 8 }}>No active transfer bonuses</div>
          <div style={{ fontSize: 13 }}>Click Refresh to check for current bonuses</div>
        </div>
      ) : (
        Object.entries(grouped).map(([bank, items]) => (
          <div key={bank} style={{ marginBottom: 20 }}>
            <div style={{ fontSize: 14, fontWeight: 700, color: theme.textMuted, textTransform: "uppercase", letterSpacing: 1, marginBottom: 10, paddingBottom: 6, borderBottom: "1px solid " + theme.border }}>{BANK_NAMES[bank] || bank}</div>
            {items.map((b, i) => (
              <div key={i} style={{ display: "flex", justifyContent: "space-between", alignItems: "center", padding: "12px 16px", marginBottom: 6, borderRadius: 8, background: theme.card, border: "1px solid " + theme.border }}>
                <div>
                  <div style={{ fontSize: 15, fontWeight: 600, color: theme.text }}>{PROGRAM_NAMES[b.program] || b.program}</div>
                  <div style={{ fontSize: 12, color: theme.textMuted, marginTop: 2 }}>{b.ratio} transfer ratio{b.speed ? " · " + b.speed : ""}{b.expires ? " · Expires " + b.expires : ""}</div>
                </div>
                <div style={{ fontSize: 22, fontWeight: 800, color: b.bonus_pct >= 40 ? "#f59e0b" : b.bonus_pct >= 25 ? "#10b981" : "#3b82f6", background: b.bonus_pct >= 40 ? "rgba(245,158,11,0.1)" : b.bonus_pct >= 25 ? "rgba(16,185,129,0.1)" : "rgba(59,130,246,0.1)", padding: "4px 12px", borderRadius: 8 }}>+{b.bonus_pct}%</div>
              </div>
            ))}
          </div>
        ))
      )}
    </Section>
  );
}

// ─── MyCards ─────────────────────────────────────────────────────────────────
function MyCards({ cards, onToggle }) {
  const [expandedCards, setExpandedCards] = useState({});
  const [transferData, setTransferData] = useState(null);
  useEffect(() => { fetch("/api/transfer-partners").then(r => r.json()).then(data => setTransferData(data)).catch(() => setTransferData(null)); }, []);
  const CARD_NAMES = { chase_ur: "Chase Ultimate Rewards (UR)", amex_mr: "Amex Membership Rewards (MR)", capital_one: "Capital One Miles", bilt: "Bilt Rewards", citi_typ: "Citi ThankYou Points (TYP)", wells_fargo: "Wells Fargo Rewards", rove: "Rove" };
  const CARD_TO_BANK = { chase_ur: "chase", amex_mr: "amex", capital_one: "capital_one", bilt: "bilt", citi_typ: "citi", wells_fargo: "wells_fargo", rove: "rove" };
  const ALLIANCE_COLORS = { star_alliance: "#c9a227", oneworld: "#c53030", skyteam: "#2563eb", none: "#6b7280" };
  const ALLIANCE_LABELS = { star_alliance: "★", oneworld: "OW", skyteam: "ST", none: "" };

  return React.createElement("div", { style: { display: "grid", gridTemplateColumns: "repeat(auto-fill, minmax(320px, 1fr))", gap: "12px" } },
    Object.entries(cards).map(([cardId, cardEnabled]) => {
      const isExpanded = expandedCards[cardId];
      const bankKey = CARD_TO_BANK[cardId];
      const bankData = transferData && bankKey ? transferData[bankKey] : null;
      const partners = bankData ? bankData.partners : [];
      const airlines = partners.filter(p => p.type === "airline");
      const hotels = partners.filter(p => p.type === "hotel");
      return React.createElement("div", { key: cardId, style: { background: theme.accentGlow, border: "1px solid " + theme.border, borderRadius: "10px", padding: "14px", transition: "all 0.2s" } },
        React.createElement("div", { style: { display: "flex", justifyContent: "space-between", alignItems: "center" } },
          React.createElement("div", { style: { display: "flex", alignItems: "center", gap: "8px", cursor: "pointer", flex: 1 }, onClick: () => setExpandedCards(prev => ({ ...prev, [cardId]: !prev[cardId] })) },
            React.createElement("span", { style: { fontSize: 11, color: theme.textDim, transition: "transform 0.2s", transform: isExpanded ? "rotate(90deg)" : "rotate(0deg)" } }, "▶"),
            React.createElement("span", { style: { fontSize: 14, fontWeight: 500, color: theme.text } }, CARD_NAMES[cardId] || cardId),
            partners.length > 0 && React.createElement("span", { style: { fontSize: 10, color: theme.textDim, marginLeft: 4 } }, "(" + partners.length + ")")
          ),
          React.createElement("label", { style: { display: "flex", alignItems: "center", gap: "6px", cursor: "pointer" } },
            React.createElement("input", { type: "checkbox", checked: cardEnabled, onChange: () => onToggle(cardId), style: { accentColor: "#22c55e" } }),
            React.createElement("span", { style: { fontSize: 11, color: cardEnabled ? "#22c55e" : "#666" } }, cardEnabled ? "Active" : "Off")
          )
        ),
        isExpanded && React.createElement("div", { style: { marginTop: "10px", borderTop: "1px solid " + theme.border, paddingTop: "10px" } },
          !transferData ? React.createElement("div", { style: { color: theme.textDim, fontSize: 12, fontStyle: "italic" } }, "Loading...") :
          partners.length === 0 ? React.createElement("div", { style: { color: theme.textDim, fontSize: 12, fontStyle: "italic" } }, "No transfer partners found") :
          React.createElement("div", null,
            airlines.length > 0 && React.createElement("div", null,
              React.createElement("div", { style: { fontSize: 10, fontWeight: 600, color: theme.textDim, textTransform: "uppercase", letterSpacing: "0.05em", marginBottom: 6 } }, "✈ Airlines (" + airlines.length + ")"),
              React.createElement("table", { style: { width: "100%", borderCollapse: "collapse", fontSize: 12, marginBottom: 10 } },
                React.createElement("thead", null, React.createElement("tr", null,
                  React.createElement("th", { style: { textAlign: "left", padding: "4px 0", color: theme.textDim, fontWeight: 500, borderBottom: "1px solid " + theme.border, fontSize: 11 } }, "Program"),
                  React.createElement("th", { style: { textAlign: "center", padding: "4px 0", color: theme.textDim, fontWeight: 500, borderBottom: "1px solid " + theme.border, fontSize: 11 } }, "Speed"),
                  React.createElement("th", { style: { textAlign: "right", padding: "4px 0", color: theme.textDim, fontWeight: 500, borderBottom: "1px solid " + theme.border, fontSize: 11 } }, "Ratio")
                )),
                React.createElement("tbody", null, airlines.map((p, i) => React.createElement("tr", { key: i },
                  React.createElement("td", { style: { padding: "5px 0", color: theme.text } },
                    React.createElement("div", { style: { display: "flex", alignItems: "center", gap: 4 } },
                      p.alliance && p.alliance !== "none" && React.createElement("span", { style: { fontSize: 9, color: ALLIANCE_COLORS[p.alliance] || "#666", fontWeight: 700, minWidth: 16 }, title: p.alliance.replace("_", " ") }, ALLIANCE_LABELS[p.alliance] || ""),
                      React.createElement("span", null, p.name || p.airline),
                      p.iata && React.createElement("span", { style: { fontSize: 10, color: theme.textDim, marginLeft: 2 } }, "(" + p.iata + ")")
                    )
                  ),
                  React.createElement("td", { style: { textAlign: "center", padding: "5px 0" } },
                    React.createElement("span", { style: { fontSize: 10, color: p.speed === "Instant" ? "#22c55e" : theme.textMuted } }, p.speed || "—")
                  ),
                  React.createElement("td", { style: { textAlign: "right", padding: "5px 0", display: "flex", alignItems: "center", justifyContent: "flex-end", gap: 4 } },
                    p.bonus_pct > 0 && React.createElement("span", { style: { fontSize: 9, fontWeight: 700, color: "#f59e0b", background: "rgba(245,158,11,0.15)", padding: "1px 5px", borderRadius: 4 } }, "+" + p.bonus_pct + "%"),
                    React.createElement("span", { style: { color: p.ratio === "1:1" ? "#22c55e" : "#f59e0b", fontWeight: 600, fontFamily: "monospace", fontSize: 11 } }, p.ratio)
                  )
                )))
              )
            ),
            hotels.length > 0 && React.createElement("div", null,
              React.createElement("div", { style: { fontSize: 10, fontWeight: 600, color: theme.textDim, textTransform: "uppercase", letterSpacing: "0.05em", marginBottom: 6, marginTop: 4 } }, "🏨 Hotels (" + hotels.length + ")"),
              React.createElement("table", { style: { width: "100%", borderCollapse: "collapse", fontSize: 12 } },
                React.createElement("thead", null, React.createElement("tr", null,
                  React.createElement("th", { style: { textAlign: "left", padding: "4px 0", color: theme.textDim, fontWeight: 500, borderBottom: "1px solid " + theme.border, fontSize: 11 } }, "Program"),
                  React.createElement("th", { style: { textAlign: "right", padding: "4px 0", color: theme.textDim, fontWeight: 500, borderBottom: "1px solid " + theme.border, fontSize: 11 } }, "Ratio")
                )),
                React.createElement("tbody", null, hotels.map((p, i) => React.createElement("tr", { key: i },
                  React.createElement("td", { style: { padding: "5px 0", color: theme.text } }, p.airline),
                  React.createElement("td", { style: { textAlign: "right", padding: "5px 0", color: p.ratio === "1:1" ? "#22c55e" : "#f59e0b", fontWeight: 600, fontFamily: "monospace", fontSize: 11 } }, p.ratio)
                )))
              )
            )
          )
        )
      );
    })
  );
}

// ─── AlertManager ───────────────────────────────────────────────────────────
function AlertManager({ alerts, onSave, onDelete, onToggle }) {
  const [editing, setEditing] = useState(null);
  const [form, setForm] = useState({ name: "", origin: "", destination: "", cabin: "J", date_start: "", date_end: "", min_days: 7, max_days: 21, open_jaw: "same", max_miles: "", email: "", duration: "none", max_stops: 1, notify_below_miles: "" });
  const startNew = () => { setForm({ name: "", origin: "", destination: "", cabin: "J", date_start: "", date_end: "", min_days: 7, max_days: 21, open_jaw: "same", max_miles: "", email: "", duration: "none", max_stops: 1, notify_below_miles: "" }); setEditing("new"); };
  return (
    <Section title="Alert Manager" icon="🔔" actions={<GlowButton small onClick={startNew} active>+ New Alert</GlowButton>}>
      {alerts && alerts.length > 0 && (
        <div style={{ display: "flex", flexDirection: "column", gap: 8, marginBottom: editing ? 20 : 0 }}>
          {alerts.map((a) => (
            <div key={a.id} style={{ background: theme.card, border: `1px solid ${a.active ? theme.accent + "40" : theme.border}`, borderRadius: 10, padding: "12px 16px", display: "grid", gridTemplateColumns: "auto 1fr auto", gap: 12, alignItems: "center", overflow: "hidden" }}>
              <Toggle checked={a.active} onChange={() => onToggle(a.id)} />
              <div style={{ minWidth: 0, overflow: "hidden" }}>
                <div style={{ fontWeight: 600, fontSize: 14, whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis" }}>{a.name}</div>
                <div style={{ color: theme.textMuted, fontSize: 12, marginTop: 2, whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis" }}>{(() => { const fmt = (s) => { if (!s) return "—"; const arr = s.split(",").map(c => c.trim()).filter(Boolean); return arr.length <= 4 ? arr.join(",") : arr.slice(0,3).join(",") + "… (" + arr.length + ")"; }; return fmt(a.origin) + " → " + fmt(a.destination); })()} · {cabinInfo[a.cabin]?.label || a.cabin} · {a.min_days}-{a.max_days} days{a.max_stops != null && a.max_stops !== 1 ? ` · ${a.max_stops === 0 ? "Nonstop" : a.max_stops + " stops"}` : ""}{a.notify_below_miles ? ` · <${Number(a.notify_below_miles).toLocaleString()}mi` : ""}{a.email ? ` · ${a.email}` : ""}{a.expires_at ? ` · expires ${new Date(a.expires_at).toLocaleDateString()}` : ""}</div>
              </div>
              <div style={{ display: "flex", gap: 6, flexShrink: 0 }}>
                <GlowButton small onClick={() => { setForm({ ...a }); setEditing(a.id); }}>Edit</GlowButton>
                <GlowButton small onClick={() => onDelete(a.id)} style={{ color: theme.danger, borderColor: theme.danger + "40" }}>×</GlowButton>
              </div>
            </div>
          ))}
        </div>
      )}
      {!alerts?.length && !editing && (
        <div style={{ textAlign: "center", padding: 32, color: theme.textDim }}>
          <div style={{ fontSize: 32, marginBottom: 8 }}>🔔</div><div>No alerts configured</div>
          <div style={{ fontSize: 12, marginTop: 4 }}>Click + New Alert to create your first alert</div>
        </div>
      )}
      {editing && (
        <div style={{ background: theme.card, border: `1px solid ${theme.accent}40`, borderRadius: 12, padding: 20 }}>
          <div style={{ fontSize: 15, fontWeight: 600, marginBottom: 16 }}>{editing === "new" ? "Create Alert" : "Edit Alert"}</div>
          <div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: 12, marginBottom: 12 }}>
            <div><label style={{ fontSize: 11, color: theme.textDim, textTransform: "uppercase", letterSpacing: 1, display: "block", marginBottom: 4 }}>Name</label><Input value={form.name} onChange={(v) => setForm(f => ({ ...f, name: v }))} placeholder="NYC to Europe J" /></div>
            <div><label style={{ fontSize: 11, color: theme.textDim, textTransform: "uppercase", letterSpacing: 1, display: "block", marginBottom: 4 }}>Cabin</label>
              <div style={{ display: "flex", gap: 4 }}>{["F", "J", "W", "Y"].map(c => (<GlowButton key={c} small active={form.cabin === c} onClick={() => setForm(f => ({ ...f, cabin: c }))}>{cabinInfo[c].icon} {cabinInfo[c].label}</GlowButton>))}</div>
            </div>
          </div>
          <div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: 12, marginBottom: 12 }}>
            <div><label style={{ fontSize: 11, color: theme.textDim, textTransform: "uppercase", letterSpacing: 1, display: "block", marginBottom: 4 }}>Origin</label><Input value={form.origin} onChange={(v) => setForm(f => ({ ...f, origin: v }))} placeholder="MEM, ATL, JFK" /></div>
            <div><label style={{ fontSize: 11, color: theme.textDim, textTransform: "uppercase", letterSpacing: 1, display: "block", marginBottom: 4 }}>Destination</label><Input value={form.destination} onChange={(v) => setForm(f => ({ ...f, destination: v }))} placeholder="LHR, CDG, FCO" /></div>
          </div>
          <div className="alert-form-grid" style={{ display: "grid", gridTemplateColumns: "1fr 1fr 1fr 1fr", gap: 12, marginBottom: 12 }}>
            <div><label style={{ fontSize: 11, color: theme.textDim, textTransform: "uppercase", letterSpacing: 1, display: "block", marginBottom: 4 }}>From Date</label><Input type="date" value={form.date_start} onChange={(v) => setForm(f => ({ ...f, date_start: v }))} /></div>
            <div><label style={{ fontSize: 11, color: theme.textDim, textTransform: "uppercase", letterSpacing: 1, display: "block", marginBottom: 4 }}>To Date</label><Input type="date" value={form.date_end} onChange={(v) => setForm(f => ({ ...f, date_end: v }))} /></div>
            <div><label style={{ fontSize: 11, color: theme.textDim, textTransform: "uppercase", letterSpacing: 1, display: "block", marginBottom: 4 }}>Min Days</label><Input type="number" value={form.min_days} onChange={(v) => setForm(f => ({ ...f, min_days: parseInt(v) || 3 }))} /></div>
            <div><label style={{ fontSize: 11, color: theme.textDim, textTransform: "uppercase", letterSpacing: 1, display: "block", marginBottom: 4 }}>Max Days</label><Input type="number" value={form.max_days} onChange={(v) => setForm(f => ({ ...f, max_days: parseInt(v) || 21 }))} /></div>
          </div>
          <div className="admin-3col" style={{ display: "grid", gridTemplateColumns: "1fr 1fr 1fr", gap: 12, marginBottom: 16 }}>
            <div><label style={{ fontSize: 11, color: theme.textDim, textTransform: "uppercase", letterSpacing: 1, display: "block", marginBottom: 4 }}>Open Jaw</label>
              <div style={{ display: "flex", gap: 4 }}>{[{ id: "same", label: "Round Trip" }, { id: "country", label: "Same Country" }, { id: "continent", label: "Same Continent" }].map(o => (<GlowButton key={o.id} small active={form.open_jaw === o.id} onClick={() => setForm(f => ({ ...f, open_jaw: o.id }))}>{o.label}</GlowButton>))}</div>
            </div>
            <div><label style={{ fontSize: 11, color: theme.textDim, textTransform: "uppercase", letterSpacing: 1, display: "block", marginBottom: 4 }}>Max Stops</label>
              <div style={{ display: "flex", gap: 4 }}>{[{ id: 0, label: "Nonstop" }, { id: 1, label: "1 Stop" }, { id: 2, label: "2 Stops" }].map(s => (<GlowButton key={s.id} small active={(form.max_stops ?? 1) === s.id} onClick={() => setForm(f => ({ ...f, max_stops: s.id }))}>{s.label}</GlowButton>))}</div>
            </div>
            <div><label style={{ fontSize: 11, color: theme.textDim, textTransform: "uppercase", letterSpacing: 1, display: "block", marginBottom: 4 }}>Max Miles</label><Input value={form.max_miles} onChange={(v) => setForm(f => ({ ...f, max_miles: v }))} placeholder="120000" /></div>
          </div>
          <div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: 12, marginBottom: 16 }}>
            <div><label style={{ fontSize: 11, color: theme.textDim, textTransform: "uppercase", letterSpacing: 1, display: "block", marginBottom: 4 }}>Notify Below Miles</label><Input value={form.notify_below_miles || ""} onChange={(v) => setForm(f => ({ ...f, notify_below_miles: v }))} placeholder="e.g. 56000 (blank = always)" /></div>
            <div><label style={{ fontSize: 11, color: theme.textDim, textTransform: "uppercase", letterSpacing: 1, display: "block", marginBottom: 4 }}>Email</label><Input value={form.email || ""} onChange={(v) => setForm(f => ({ ...f, email: v }))} placeholder="your@email.com" /></div>
            <div><label style={{ fontSize: 11, color: theme.textDim, textTransform: "uppercase", letterSpacing: 1, display: "block", marginBottom: 4 }}>Duration</label>
              <div style={{ display: "flex", gap: 4, flexWrap: "wrap" }}>{[{ id: "none", label: "No Expiry" }, { id: "1w", label: "1 Week" }, { id: "1m", label: "1 Month" }, { id: "3m", label: "3 Months" }, { id: "6m", label: "6 Months" }, { id: "1y", label: "1 Year" }].map(d => (<GlowButton key={d.id} small active={form.duration === d.id} onClick={() => setForm(f => ({ ...f, duration: d.id }))}>{d.label}</GlowButton>))}</div>
            </div>
          </div>
          <div style={{ display: "flex", gap: 8, justifyContent: "flex-end" }}>
            <GlowButton small onClick={() => setEditing(null)}>Cancel</GlowButton>
            <GlowButton small active onClick={() => { onSave(form, editing); setEditing(null); }}>{editing === "new" ? "Create Alert" : "Save Changes"}</GlowButton>
          </div>
        </div>
      )}
    </Section>
  );
}

// ─── RunHistory ─────────────────────────────────────────────────────────────
function RunHistory({ runs }) {
  return (
    <Section title="Run History" icon="📜">
      {runs && runs.length > 0 ? (
        <div style={{ overflowX: "auto" }}>
          <table style={{ width: "100%", borderCollapse: "collapse", fontSize: 13 }}>
            <thead><tr style={{ borderBottom: `1px solid ${theme.border}` }}>
              {["Time (UTC)", "Programs", "Rows", "New", "Updated", "Errors", "API Left"].map((h, i) => (
                <th key={i} style={{ padding: "8px 12px", textAlign: i === 0 ? "left" : "right", color: theme.textDim, fontSize: 11, textTransform: "uppercase", letterSpacing: 0.5 }}>{h}</th>
              ))}
            </tr></thead>
            <tbody>{runs.map((r, i) => (
              <tr key={i} style={{ borderBottom: `1px solid ${theme.border}`, background: i % 2 === 0 ? "transparent" : theme.card + "40" }}>
                <td style={{ padding: "10px 12px", fontFamily: "monospace", fontSize: 12 }}>{r.run_time}</td>
                <td style={{ padding: "10px 12px", textAlign: "right" }}>{r.programs}</td>
                <td style={{ padding: "10px 12px", textAlign: "right", fontFamily: "monospace" }}>{r.total_rows?.toLocaleString()}</td>
                <td style={{ padding: "10px 12px", textAlign: "right", fontFamily: "monospace", color: r.new_rows > 0 ? theme.success : theme.textDim }}>{r.new_rows?.toLocaleString()}</td>
                <td style={{ padding: "10px 12px", textAlign: "right", fontFamily: "monospace" }}>{r.updated_rows?.toLocaleString()}</td>
                <td style={{ padding: "10px 12px", textAlign: "right", color: r.errors > 0 ? theme.danger : theme.textDim }}>{r.errors}</td>
                <td style={{ padding: "10px 12px", textAlign: "right", fontFamily: "monospace", color: r.api_remaining < 100 ? theme.warning : theme.textDim }}>{r.api_remaining ?? "?"}</td>
              </tr>
            ))}</tbody>
          </table>
        </div>
      ) : (
        <div style={{ textAlign: "center", padding: 32, color: theme.textDim }}><div style={{ fontSize: 32, marginBottom: 8 }}>📜</div><div>No run history available</div></div>
      )}
    </Section>
  );
}

// ─── SystemInfo ─────────────────────────────────────────────────────────────
function SystemInfo({ stats }) {
  const rows = [
    { label: "Database", value: "DuckDB", status: "healthy" },
    { label: "Total Flights", value: stats?.total_availability?.toLocaleString() || "—" },
    { label: "Active Programs", value: `${stats?.programs_with_data || "—"} / 25` },
    { label: "Date Range", value: stats ? `${stats.date_min || "—"} → ${stats.date_max || "—"}` : "—" },
    { label: "Last Ingestion", value: stats?.last_ingestion || "never" },
    { label: "DB Size", value: stats?.db_size || "—" },
    { label: "API Version", value: "v2.0.0" },
    { label: "Max Budget", value: "900 calls / day" },
  ];
  return (
    <Section title="System Info" icon="🖥">
      <div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: 0 }}>
        {rows.map((r, i) => (
          <div key={i} style={{ padding: "10px 0", borderBottom: `1px solid ${theme.border}`, display: "flex", justifyContent: "space-between", paddingRight: i % 2 === 0 ? 24 : 0, paddingLeft: i % 2 === 1 ? 24 : 0 }}>
            <span style={{ color: theme.textDim, fontSize: 14 }}>{r.status && <StatusDot status={r.status} />}{r.label}</span>
            <span style={{ fontSize: 14, fontWeight: 500 }}>{r.value}</span>
          </div>
        ))}
      </div>
    </Section>
  );
}

// ─── AnalyticsDashboard ─────────────────────────────────────────────────────
function AnalyticsDashboard({ token }) {
  const [data, setData] = useState(null);
  const [period, setPeriod] = useState("all");
  const [loading, setLoading] = useState(false);

  const fetchData = useCallback((p) => {
    setLoading(true);
    fetch(`${API}/api/admin/analytics?period=${p}`, { headers: { Authorization: `Bearer ${token}` } })
      .then(r => r.json())
      .then(d => { setData(d); setLoading(false); })
      .catch(() => setLoading(false));
  }, [token]);

  useEffect(() => { if (token) fetchData(period); }, [token, period]);

  // ── Hover Line Chart (sparkline popup) ──
  const HoverLineChart = ({ timeline, color, title, width = 320, height = 160 }) => {
    if (!timeline || timeline.length === 0) return null;
    const pad = { top: 24, right: 12, bottom: 28, left: 36 };
    const w = width - pad.left - pad.right;
    const h = height - pad.top - pad.bottom;
    const maxVal = Math.max(...timeline.map(d => d.count), 1);
    const total = timeline.reduce((s, d) => s + d.count, 0);
    const points = timeline.map((d, i) => ({
      x: pad.left + (timeline.length === 1 ? w / 2 : (i / (timeline.length - 1)) * w),
      y: pad.top + h - (d.count / maxVal) * h,
      date: d.date, count: d.count
    }));
    const line = points.map((p, i) => `${i === 0 ? "M" : "L"}${p.x.toFixed(1)},${p.y.toFixed(1)}`).join(" ");
    const area = line + ` L${points[points.length - 1].x.toFixed(1)},${pad.top + h} L${points[0].x.toFixed(1)},${pad.top + h} Z`;
    // Y-axis labels
    const yTicks = [0, Math.round(maxVal / 2), maxVal];
    return (
      <div style={{ width, padding: 0 }}>
        {title && <div style={{ fontSize: 11, fontWeight: 700, color: theme.text, marginBottom: 4, textAlign: "center" }}>{title} <span style={{ color: theme.textDim, fontWeight: 400 }}>({total} total)</span></div>}
        <svg width={width} height={height} style={{ display: "block" }}>
          {/* Grid lines */}
          {yTicks.map((v, i) => {
            const y = pad.top + h - (v / maxVal) * h;
            return <React.Fragment key={i}>
              <line x1={pad.left} y1={y} x2={pad.left + w} y2={y} stroke={theme.border} strokeWidth={0.5} />
              <text x={pad.left - 4} y={y + 3} fill={theme.textDim} fontSize={9} textAnchor="end">{v}</text>
            </React.Fragment>;
          })}
          {/* Area fill */}
          <path d={area} fill={color + "18"} />
          {/* Line */}
          <path d={line} fill="none" stroke={color} strokeWidth={2} strokeLinecap="round" strokeLinejoin="round" />
          {/* Dots */}
          {points.map((p, i) => (
            <React.Fragment key={i}>
              <circle cx={p.x} cy={p.y} r={3} fill={color} stroke={theme.card} strokeWidth={1.5} />
              {(timeline.length <= 14 || i % Math.ceil(timeline.length / 7) === 0 || i === timeline.length - 1) && (
                <text x={p.x} y={pad.top + h + 14} fill={theme.textDim} fontSize={8} textAnchor="middle">{p.date.slice(5)}</text>
              )}
            </React.Fragment>
          ))}
          {/* Value labels on dots (when few enough) */}
          {timeline.length <= 14 && points.map((p, i) => (
            <text key={`v${i}`} x={p.x} y={p.y - 8} fill={color} fontSize={9} fontWeight={600} textAnchor="middle">{p.count}</text>
          ))}
        </svg>
      </div>
    );
  };

  // ── Hoverable wrapper that shows a sparkline popup ──
  const HoverSparkline = ({ children, timeline, color, title, style }) => {
    const [show, setShow] = useState(false);
    const [pos, setPos] = useState({ x: 0, y: 0 });
    const ref = React.useRef(null);

    const handleEnter = (e) => {
      if (!timeline || timeline.length === 0) return;
      const rect = ref.current?.getBoundingClientRect();
      if (rect) {
        const popupW = 340, popupH = 200;
        let x = rect.left + rect.width / 2 - popupW / 2;
        if (x < 8) x = 8;
        if (x + popupW > window.innerWidth - 8) x = window.innerWidth - popupW - 8;
        let y = rect.top - popupH - 8;
        if (y < 8) y = rect.bottom + 8;
        if (y + popupH > window.innerHeight - 8) y = window.innerHeight - popupH - 8;
        setPos({ x, y });
      }
      setShow(true);
    };

    return (
      <div ref={ref} onMouseEnter={handleEnter} onMouseLeave={() => setShow(false)} style={{ position: "relative", cursor: timeline && timeline.length > 0 ? "pointer" : "default", ...style }}>
        {children}
        {show && timeline && timeline.length > 0 && ReactDOM.createPortal(
          <div style={{
            position: "fixed", left: pos.x, top: pos.y, zIndex: Z_TOOLTIP,
            background: theme.surface, border: `1px solid ${theme.border}`, borderRadius: 12,
            boxShadow: `0 8px 32px ${theme.bg}cc`, padding: 12,
            pointerEvents: "none", transition: "opacity 0.15s", opacity: show ? 1 : 0,
          }}>
            <HoverLineChart timeline={timeline} color={color || theme.accent} title={title} />
          </div>,
          document.body
        )}
      </div>
    );
  };

  const StatCard = ({ label, value, color, icon, timeline }) => (
    <HoverSparkline timeline={timeline} color={color} title={label}>
      <div style={{ background: theme.card, border: `1px solid ${theme.border}`, borderRadius: 10, padding: 16, textAlign: "center", flex: 1, minWidth: 120, transition: "border-color 0.2s" }}
        onMouseEnter={e => e.currentTarget.style.borderColor = color || theme.accent}
        onMouseLeave={e => e.currentTarget.style.borderColor = theme.border}>
        {icon && <div style={{ fontSize: 20, marginBottom: 4 }}>{icon}</div>}
        <div style={{ fontSize: 11, color: theme.textDim, textTransform: "uppercase", letterSpacing: 1, marginBottom: 4 }}>{label}</div>
        <div style={{ fontSize: 22, fontWeight: 700, color: color || theme.text }}>{value ?? "—"}</div>
        {timeline && timeline.length > 0 && <div style={{ fontSize: 9, color: theme.textDim, marginTop: 4 }}>hover for daily trend ↗</div>}
      </div>
    </HoverSparkline>
  );

  const HBar = ({ label, value, max, color, suffix }) => {
    const pct = max > 0 ? (value / max) * 100 : 0;
    return (
      <div style={{ display: "flex", alignItems: "center", gap: 10, marginBottom: 6 }}>
        <div style={{ width: 100, fontSize: 12, color: theme.textMuted, textAlign: "right", flexShrink: 0, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}>{label}</div>
        <div style={{ flex: 1, background: theme.card, borderRadius: 4, height: 18, overflow: "hidden" }}>
          <div style={{ width: `${pct}%`, height: "100%", background: color || theme.accent, borderRadius: 4, transition: "width 0.3s" }} />
        </div>
        <div style={{ width: 60, fontSize: 12, fontWeight: 600, color: theme.text, textAlign: "right" }}>{value}{suffix || ""}</div>
      </div>
    );
  };

  const SimpleBarChart = ({ dataPoints, color, height = 120 }) => {
    if (!dataPoints || dataPoints.length === 0) return <div style={{ color: theme.textDim, fontSize: 13, padding: 16 }}>No data yet</div>;
    const maxVal = Math.max(...dataPoints.map(d => d.value), 1);
    const barW = Math.max(4, Math.min(20, (600 / dataPoints.length) - 2));
    return (
      <div style={{ display: "flex", alignItems: "flex-end", gap: 2, height, padding: "0 4px" }}>
        {dataPoints.map((d, i) => {
          const h = (d.value / maxVal) * (height - 20);
          return (
            <div key={i} style={{ display: "flex", flexDirection: "column", alignItems: "center", flex: 1 }} title={`${d.label}: ${d.value}`}>
              <div style={{ fontSize: 9, color: theme.textDim, marginBottom: 2 }}>{d.value > 0 ? d.value : ""}</div>
              <div style={{ width: barW, height: Math.max(2, h), background: color || theme.accent, borderRadius: 2, transition: "height 0.3s" }} />
              {i % Math.ceil(dataPoints.length / 8) === 0 && (
                <div style={{ fontSize: 8, color: theme.textDim, marginTop: 2, whiteSpace: "nowrap" }}>{d.label.slice(5)}</div>
              )}
            </div>
          );
        })}
      </div>
    );
  };

  if (!data && loading) return <Section title="Analytics Dashboard" icon="📊"><div style={{ padding: 20, textAlign: "center", color: theme.textDim }}>Loading analytics...</div></Section>;
  if (!data) return null;

  const cabinColors = { Y: "#10b981", W: "#3b82f6", J: "#a855f7", F: "#fbbf24" };
  const cabinLabels = { Y: "Economy", W: "Premium Econ", J: "Business", F: "First" };
  const maxCabin = Math.max(...(data.cabin_distribution || []).map(c => c.count), 1);
  const maxRoute = Math.max(...(data.popular_routes || []).map(r => r.count), 1);
  const tabEntries = data.tab_usage || [];
  const maxTab = Math.max(...tabEntries.map(t => t.count), 1);
  const daily = data.daily || { searches: [], users: [], events: [], cabin: {}, tabs: {}, filters: {} };

  return (
    <Section title="Analytics Dashboard" icon="📊">
      {/* Period selector */}
      <div style={{ display: "flex", gap: 6, marginBottom: 16 }}>
        {[{ id: "today", label: "Today" }, { id: "week", label: "This Week" }, { id: "all", label: "All Time" }].map(p => (
          <button key={p.id} onClick={() => setPeriod(p.id)} style={{
            padding: "6px 14px", borderRadius: 8, border: `1px solid ${period === p.id ? theme.accent : theme.border}`,
            background: period === p.id ? theme.accent + "20" : "transparent", color: period === p.id ? theme.accent : theme.textMuted,
            fontSize: 12, fontWeight: 600, cursor: "pointer"
          }}>{p.label}</button>
        ))}
        <button onClick={() => fetchData(period)} style={{ marginLeft: "auto", padding: "6px 12px", borderRadius: 8, border: `1px solid ${theme.border}`, background: "transparent", color: theme.textDim, fontSize: 12, cursor: "pointer" }}>↻ Refresh</button>
      </div>

      {/* Summary cards */}
      <div style={{ display: "flex", gap: 12, marginBottom: 20, flexWrap: "wrap" }}>
        <StatCard label="Total Searches" value={data.total_searches} color={theme.accent} icon="🔍" timeline={daily.searches} />
        <StatCard label="Unique Users" value={data.unique_users} color="#a855f7" icon="👥" timeline={daily.users} />
        <StatCard label="Total Events" value={data.total_events} color="#10b981" icon="📈" timeline={daily.events} />
        <StatCard label="Today's Searches" value={data.searches_today} color="#fbbf24" icon="📅" timeline={daily.searches} />
      </div>

      {/* Two column layout */}
      <div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: 16, marginBottom: 16 }}>
        {/* Cabin Distribution */}
        <div style={{ background: theme.card, border: `1px solid ${theme.border}`, borderRadius: 10, padding: 16 }}>
          <div style={{ fontSize: 13, fontWeight: 600, marginBottom: 12, color: theme.text }}>Cabin Distribution <span style={{ fontSize: 9, color: theme.textDim, fontWeight: 400 }}>hover bars for daily</span></div>
          {(data.cabin_distribution || []).length === 0 ? <div style={{ color: theme.textDim, fontSize: 13 }}>No data yet</div> :
            (data.cabin_distribution || []).map(c => (
              <HoverSparkline key={c.cabin} timeline={daily.cabin?.[c.cabin]} color={cabinColors[c.cabin] || theme.accent} title={`${cabinLabels[c.cabin] || c.cabin} / Day`}>
                <HBar label={cabinLabels[c.cabin] || c.cabin} value={c.count} max={maxCabin} color={cabinColors[c.cabin] || theme.accent} suffix={` (${c.pct}%)`} />
              </HoverSparkline>
            ))
          }
        </div>

        {/* Most Active Users */}
        <HoverSparkline timeline={daily.users} color="#a855f7" title="Active Users / Day" style={{ background: theme.card, border: `1px solid ${theme.border}`, borderRadius: 10, padding: 16 }}>
          <div style={{ fontSize: 13, fontWeight: 600, marginBottom: 12, color: theme.text }}>Most Active Users <span style={{ fontSize: 9, color: theme.textDim, fontWeight: 400 }}>hover for daily</span></div>
          <div style={{ maxHeight: 200, overflowY: "auto" }}>
            {(data.active_users || []).length === 0 ? <div style={{ color: theme.textDim, fontSize: 13 }}>No data yet</div> :
              (data.active_users || []).map((u, i) => (
                <div key={i} style={{ display: "flex", justifyContent: "space-between", padding: "6px 0", borderBottom: `1px solid ${theme.border}`, fontSize: 13 }}>
                  <span style={{ color: theme.text, fontWeight: 500 }}>{u.name || u.user_id}</span>
                  <span style={{ color: theme.textDim }}>{u.events} events · {u.searches} searches</span>
                </div>
              ))
            }
          </div>
        </HoverSparkline>
      </div>

      {/* Popular Routes */}
      <HoverSparkline timeline={daily.searches} color={theme.accent} title="Searches / Day" style={{ background: theme.card, border: `1px solid ${theme.border}`, borderRadius: 10, padding: 16, marginBottom: 16 }}>
        <div style={{ fontSize: 13, fontWeight: 600, marginBottom: 12, color: theme.text }}>Popular Routes <span style={{ fontSize: 9, color: theme.textDim, fontWeight: 400 }}>hover for daily</span></div>
        {(data.popular_routes || []).length === 0 ? <div style={{ color: theme.textDim, fontSize: 13 }}>No searches yet</div> :
          <div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: 4 }}>
            {(data.popular_routes || []).slice(0, 20).map((r, i) => (
              <HBar key={i} label={`${r.origin} → ${r.destination}`} value={r.count} max={maxRoute} color={i < 3 ? theme.accent : theme.textDim + "60"} />
            ))}
          </div>
        }
      </HoverSparkline>

      {/* Tab Usage */}
      <HoverSparkline timeline={daily.searches} color="#3b82f6" title="Tab Switches / Day" style={{ background: theme.card, border: `1px solid ${theme.border}`, borderRadius: 10, padding: 16, marginBottom: 16 }}>
        <div style={{ fontSize: 13, fontWeight: 600, marginBottom: 12, color: theme.text }}>Tab Usage <span style={{ fontSize: 9, color: theme.textDim, fontWeight: 400 }}>hover for daily</span></div>
        {tabEntries.length === 0 ? <div style={{ color: theme.textDim, fontSize: 13 }}>No data yet</div> :
          tabEntries.map(t => (
            <HoverSparkline key={t.tab} timeline={daily.tabs?.[t.tab]} color="#3b82f6" title={`${t.tab} / Day`}>
              <HBar label={t.tab} value={t.count} max={maxTab} color="#3b82f6" />
            </HoverSparkline>
          ))
        }
      </HoverSparkline>

      {/* Filter Stats */}
      <HoverSparkline timeline={daily.events} color="#f59e0b" title="Filter Events / Day" style={{ background: theme.card, border: `1px solid ${theme.border}`, borderRadius: 10, padding: 16, marginBottom: 16 }}>
        <div style={{ fontSize: 13, fontWeight: 600, marginBottom: 12, color: theme.text }}>Filter Interactions <span style={{ fontSize: 9, color: theme.textDim, fontWeight: 400 }}>hover for daily</span></div>
        <div style={{ display: "flex", gap: 12, flexWrap: "wrap" }}>
          {Object.entries(data.filter_stats || {}).map(([key, val]) => (
            <HoverSparkline key={key} timeline={daily.filters?.[key]} color="#f59e0b" title={`${key.replace(/_/g, " ")} / Day`}>
              <div style={{ background: theme.surface, border: `1px solid ${theme.border}`, borderRadius: 8, padding: "10px 16px", textAlign: "center", minWidth: 100 }}>
                <div style={{ fontSize: 10, color: theme.textDim, textTransform: "uppercase", letterSpacing: 0.5, marginBottom: 2 }}>{key.replace(/_/g, " ")}</div>
                <div style={{ fontSize: 18, fontWeight: 700, color: theme.text }}>{val}</div>
              </div>
            </HoverSparkline>
          ))}
        </div>
      </HoverSparkline>

      {/* Trip Type Distribution */}
      {(data.trip_distribution || []).length > 0 && (
        <HoverSparkline timeline={daily.searches} color="#06b6d4" title="Searches / Day" style={{ background: theme.card, border: `1px solid ${theme.border}`, borderRadius: 10, padding: 16, marginBottom: 16 }}>
          <div style={{ fontSize: 13, fontWeight: 600, marginBottom: 12, color: theme.text }}>Trip Type Distribution <span style={{ fontSize: 9, color: theme.textDim, fontWeight: 400 }}>hover for daily</span></div>
          {data.trip_distribution.map(t => (
            <HBar key={t.trip_type} label={t.trip_type || "unknown"} value={t.count} max={Math.max(...data.trip_distribution.map(x => x.count), 1)} color="#06b6d4" suffix={` (${t.pct}%)`} />
          ))}
        </HoverSparkline>
      )}

      {/* Searches Over Time */}
      <div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: 16, marginBottom: 16 }}>
        <div style={{ background: theme.card, border: `1px solid ${theme.border}`, borderRadius: 10, padding: 16 }}>
          <div style={{ fontSize: 13, fontWeight: 600, marginBottom: 12, color: theme.text }}>Searches / Day</div>
          <SimpleBarChart dataPoints={(data.searches_timeline || []).map(d => ({ label: d.date, value: d.count }))} color={theme.accent} />
        </div>
        <div style={{ background: theme.card, border: `1px solid ${theme.border}`, borderRadius: 10, padding: 16 }}>
          <div style={{ fontSize: 13, fontWeight: 600, marginBottom: 12, color: theme.text }}>Active Users / Day</div>
          <SimpleBarChart dataPoints={(data.users_timeline || []).map(d => ({ label: d.date, value: d.count }))} color="#a855f7" />
        </div>
      </div>

      {/* ═══ Seats.aero Trips API Usage ═══ */}
      {data.trips_api && (
        <div style={{ background: theme.card, border: `1px solid ${theme.border}`, borderRadius: 10, padding: 16, marginBottom: 16 }}>
          <div style={{ fontSize: 14, fontWeight: 700, marginBottom: 12, color: theme.text, display: "flex", alignItems: "center", gap: 8 }}>
            ✈️ Seats.aero Trips API Usage
            <span style={{ fontSize: 10, fontWeight: 400, color: theme.textDim }}>On-demand flight detail lookups</span>
          </div>
          {/* Summary stats */}
          <div style={{ display: "flex", gap: 12, marginBottom: 16, flexWrap: "wrap" }}>
            <div style={{ background: theme.surface, border: `1px solid ${theme.border}`, borderRadius: 8, padding: "10px 16px", textAlign: "center", flex: 1, minWidth: 100 }}>
              <div style={{ fontSize: 10, color: theme.textDim, textTransform: "uppercase", letterSpacing: 0.5, marginBottom: 2 }}>Today API Calls</div>
              <div style={{ fontSize: 22, fontWeight: 700, color: "#ef4444" }}>{data.trips_api.today_api || 0}</div>
              <div style={{ fontSize: 9, color: theme.textDim }}>{data.trips_api.today || 0} total (inc. cache)</div>
            </div>
            <div style={{ background: theme.surface, border: `1px solid ${theme.border}`, borderRadius: 8, padding: "10px 16px", textAlign: "center", flex: 1, minWidth: 100 }}>
              <div style={{ fontSize: 10, color: theme.textDim, textTransform: "uppercase", letterSpacing: 0.5, marginBottom: 2 }}>Period API Calls</div>
              <div style={{ fontSize: 22, fontWeight: 700, color: "#f59e0b" }}>{data.trips_api.api_calls || 0}</div>
              <div style={{ fontSize: 9, color: theme.textDim }}>{data.trips_api.total || 0} total requests</div>
            </div>
            <div style={{ background: theme.surface, border: `1px solid ${theme.border}`, borderRadius: 8, padding: "10px 16px", textAlign: "center", flex: 1, minWidth: 100 }}>
              <div style={{ fontSize: 10, color: theme.textDim, textTransform: "uppercase", letterSpacing: 0.5, marginBottom: 2 }}>Cache Hit Rate</div>
              <div style={{ fontSize: 22, fontWeight: 700, color: "#10b981" }}>{data.trips_api.total > 0 ? Math.round((data.trips_api.cache_hits / data.trips_api.total) * 100) : 0}%</div>
              <div style={{ fontSize: 9, color: theme.textDim }}>{data.trips_api.cache_hits || 0} cache hits</div>
            </div>
            <div style={{ background: theme.surface, border: `1px solid ${theme.border}`, borderRadius: 8, padding: "10px 16px", textAlign: "center", flex: 1, minWidth: 100 }}>
              <div style={{ fontSize: 10, color: theme.textDim, textTransform: "uppercase", letterSpacing: 0.5, marginBottom: 2 }}>Cache Size</div>
              <div style={{ fontSize: 22, fontWeight: 700, color: "#3b82f6" }}>{data.trips_api.cache_size || 0}</div>
              <div style={{ fontSize: 9, color: theme.textDim }}>cached routes</div>
            </div>
          </div>
          {/* API calls by day chart */}
          {(data.trips_api.by_day || []).length > 0 && (
            <div style={{ marginBottom: 12 }}>
              <div style={{ fontSize: 12, fontWeight: 600, marginBottom: 8, color: theme.textMuted }}>API Calls / Day</div>
              <SimpleBarChart dataPoints={(data.trips_api.by_day).map(d => ({ label: d.date, value: d.api_calls }))} color="#ef4444" />
            </div>
          )}
          {/* By user and by source in two columns */}
          <div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: 12 }}>
            {/* By user */}
            {(data.trips_api.by_user || []).length > 0 && (
              <div>
                <div style={{ fontSize: 12, fontWeight: 600, marginBottom: 6, color: theme.textMuted }}>Top Users</div>
                {data.trips_api.by_user.map((u, i) => (
                  <div key={i} style={{ display: "flex", justifyContent: "space-between", padding: "4px 0", borderBottom: `1px solid ${theme.border}22`, fontSize: 12 }}>
                    <span style={{ color: theme.text }}>{u.user}</span>
                    <span style={{ color: theme.textDim }}>{u.api_calls} API / {u.total} total</span>
                  </div>
                ))}
              </div>
            )}
            {/* By source program */}
            {(data.trips_api.by_source || []).length > 0 && (
              <div>
                <div style={{ fontSize: 12, fontWeight: 600, marginBottom: 6, color: theme.textMuted }}>By Program</div>
                {data.trips_api.by_source.slice(0, 8).map((s, i) => (
                  <div key={i} style={{ display: "flex", justifyContent: "space-between", padding: "4px 0", borderBottom: `1px solid ${theme.border}22`, fontSize: 12 }}>
                    <span style={{ color: theme.text }}>{PROGRAMS[s.source]?.program_name || s.source}</span>
                    <span style={{ color: theme.textDim }}>{s.count}</span>
                  </div>
                ))}
              </div>
            )}
          </div>
        </div>
      )}
    </Section>
  );
}

// ─── AutocompleteInput ──────────────────────────────────────────────────────
function AutocompleteInput({ value, onChange, placeholder, theme: acTheme }) {
  const t = acTheme || theme;
  const [tags, setTags] = React.useState([]);
  const [query, setQuery] = React.useState("");
  const [suggestions, setSuggestions] = React.useState([]);
  const [showDD, setShowDD] = React.useState(false);
  const debRef = React.useRef(null);
  const wRef = React.useRef(null);

  React.useEffect(() => {
    if (value) {
      const codes = value.split(",").map(s => s.trim()).filter(Boolean);
      const currentCodes = tags.map(t => t.code);
      if (JSON.stringify(codes) !== JSON.stringify(currentCodes)) {
        setTags(codes.map(code => ({ code, label: code })));
        setQuery("");
      }
    } else if (!value && tags.length > 0) { setTags([]); }
  }, [value]);

  React.useEffect(() => {
    function h(e) { if (wRef.current && !wRef.current.contains(e.target)) setShowDD(false); }
    document.addEventListener("mousedown", h);
    return () => document.removeEventListener("mousedown", h);
  }, []);

  const doSearch = (q) => {
    if (q.length < 1) { setSuggestions([]); return; }
    fetch(`${API}/api/resolve-location?q=${encodeURIComponent(q)}`)
      .then(r => r.json()).then(d => { setSuggestions(d); setShowDD(true); }).catch(window.silentErr("Request failed"));
  };

  const updateParentValue = (newTags) => { onChange(newTags.map(t => t.code).join(",")); };

  const addTag = (item) => {
    if (tags.find(t => t.code === item.codes)) return;
    const newTags = [...tags, { code: item.codes, label: item.label }];
    setTags(newTags); setQuery(""); setSuggestions([]); setShowDD(false);
    updateParentValue(newTags);
    setTimeout(() => { if (wRef.current) { const inp = wRef.current.querySelector("input"); if (inp) inp.focus(); } }, INPUT_FOCUS_DELAY_MS);
  };

  const removeTag = (code) => { const newTags = tags.filter(t => t.code !== code); setTags(newTags); updateParentValue(newTags); };

  const onInput = (e) => {
    const v = e.target.value;
    if (v.includes(",")) {
      const parts = v.split(",");
      const before = parts[0].trim();
      const after = parts.slice(1).join(",").trim();
      if (before.length > 0) {
        fetch(`${API}/api/resolve-location?q=${encodeURIComponent(before)}`)
          .then(r => r.json()).then(d => {
            if (d.length > 0 && !tags.find(t => t.code === d[0].codes)) {
              const newTags = [...tags, { code: d[0].codes, label: d[0].label }];
              setTags(newTags); updateParentValue(newTags);
            }
            setQuery(after);
            if (after.length > 0) { clearTimeout(debRef.current); debRef.current = setTimeout(() => doSearch(after), SEARCH_DEBOUNCE_MS); } else { setSuggestions([]); }
          }).catch(() => {});
      } else { setQuery(after); if (after.length > 0) { clearTimeout(debRef.current); debRef.current = setTimeout(() => doSearch(after), SEARCH_DEBOUNCE_MS); } }
      return;
    }
    setQuery(v);
    clearTimeout(debRef.current);
    debRef.current = setTimeout(() => doSearch(v), SEARCH_DEBOUNCE_MS);
  };

  const onKeyDown = (e) => {
    if (e.key === "Backspace" && query === "" && tags.length > 0) { const newTags = tags.slice(0, -1); setTags(newTags); updateParentValue(newTags); }
    if (e.key === "Enter") {
      e.preventDefault();
      if (suggestions.length > 0) { addTag(suggestions[0]); }
      else if (query.trim().length > 0) { fetch(`${API}/api/resolve-location?q=${encodeURIComponent(query.trim())}`).then(r => r.json()).then(d => { if (d.length > 0) addTag(d[0]); }).catch(() => {}); }
    }
  };

  const ico = { region: "🌍", country: "🇳🇳", city: "🏙", airport: "✈️" };

  return React.createElement("div", { ref: wRef, style: { position: "relative" } },
    React.createElement("div", { style: { display: "flex", flexWrap: "wrap", alignItems: "center", gap: 4, width: "100%", padding: "4px 28px 4px 8px", borderRadius: 8, minHeight: 40, border: "1px solid " + (t.border || "#333"), background: t.card || "#1a1a2e", cursor: "text", boxSizing: "border-box" }, onClick: () => { if (wRef.current) { const inp = wRef.current.querySelector("input"); if (inp) inp.focus(); } } },
      tags.map((tag) => React.createElement("span", { key: tag.code, style: { display: "inline-flex", alignItems: "center", gap: 4, background: t.accent || "#3b82f6", color: "#fff", borderRadius: 4, padding: "2px 8px", fontSize: 12, fontWeight: 600, whiteSpace: "nowrap" } },
        React.createElement("span", null, tag.code),
        React.createElement("button", { onClick: (e) => { e.stopPropagation(); removeTag(tag.code); }, "aria-label": `Remove ${tag.code}`, style: { cursor: "pointer", marginLeft: 2, fontWeight: "bold", opacity: 0.7, fontSize: 14, background: "none", border: "none", color: "inherit", padding: 0 } }, "×")
      )),
      React.createElement("input", { type: "text", role: "combobox", "aria-expanded": showDD && suggestions.length > 0, "aria-autocomplete": "list", "aria-label": placeholder, value: query, onChange: onInput, onKeyDown: onKeyDown, onFocus: () => { if (suggestions.length) setShowDD(true); }, placeholder: tags.length === 0 ? placeholder : "Add more...", style: { flex: 1, minWidth: 80, border: "none", background: "transparent", color: t.text || "#e2e8f0", fontSize: 14, padding: "4px 0", boxSizing: "border-box" } })
    ),
    (tags.length > 0 || query) && React.createElement("button", { onClick: (e) => { e.stopPropagation(); setTags([]); setQuery(""); setSuggestions([]); onChange(""); }, "aria-label": "Clear all locations", style: { cursor: "pointer", padding: "2px 8px", fontSize: 18, color: t.textDim || "#888", fontWeight: "bold", position: "absolute", right: 4, top: 8, zIndex: 2, background: "none", border: "none" }, title: "Clear all" }, "×"),
    tags.length > 0 && React.createElement("div", { style: { fontSize: 11, color: t.textDim || "#888", marginTop: 2, paddingLeft: 4, whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis" } }, tags.map(t => t.label).join(" | ")),
    showDD && suggestions.length > 0 && React.createElement("div", { role: "listbox", "aria-label": "Location suggestions", style: { position: "absolute", top: "100%", left: 0, right: 0, zIndex: Z_SUGGESTIONS, boxShadow: "0 8px 32px rgba(0,0,0,0.3)", border: "1px solid " + (t.border || "#333"), background: t.card || "#1a1a2e", borderRadius: 8, maxHeight: SUGGESTIONS_MAX_HEIGHT, overflowY: "auto" } },
      suggestions.map((item, i) => React.createElement("div", { key: i, role: "option", onClick: () => addTag(item), style: { padding: "10px 14px", cursor: "pointer", display: "flex", alignItems: "center", gap: 8, background: t.card || "#1a1a2e", borderBottom: i < suggestions.length - 1 ? "1px solid " + (t.border || "#333") : "none", fontSize: 13, color: t.text || "#fff" }, onMouseEnter: (e) => { e.currentTarget.style.background = t.hover || "#2a2a4a"; }, onMouseLeave: (e) => { e.currentTarget.style.background = t.card || "#1a1a2e"; } },
        React.createElement("span", null, ico[item.type] || ""),
        React.createElement("span", { style: { flex: 1 } }, item.label),
        item.count > 1 && React.createElement("span", { style: { fontSize: 11, color: t.textDim || "#888", background: t.bg || "#111", padding: "2px 8px", borderRadius: 12 } }, item.count + " airports")
      ))
    )
  );
}

// Expose shared globals to other JSX files (email_preview_tab, ingestion_tab)
window.AutocompleteInput = AutocompleteInput;
window.PROGRAMS = PROGRAMS;
window.theme = theme;
window.activeTheme = theme;

// Auth-aware fetch helper for external JSX tabs
window.authFetch = (url, opts = {}) => {
  const token = window.authToken || localStorage.getItem("auth_token") || "";
  const headers = { ...(opts.headers || {}) };
  if (token) headers["Authorization"] = `Bearer ${token}`;
  return fetch(url, { ...opts, headers });
};

// ─── MultiSelectDropdown ────────────────────────────────────────────────────
function MultiSelectDropdown({ label, options, selected, onToggle, onClear, onSelectAll, theme: ddTheme }) {
  const t = ddTheme || theme;
  const [open, setOpen] = React.useState(false);
  const ref = React.useRef(null);
  React.useEffect(() => { function h(e) { if (ref.current && !ref.current.contains(e.target)) setOpen(false); } document.addEventListener("mousedown", h); return () => document.removeEventListener("mousedown", h); }, []);
  const cnt = selected.length;
  const allIds = options.map(([id]) => id);
  const allSelected = cnt === allIds.length && cnt > 0;
  return React.createElement("div", { ref, style: { position: "relative" } },
    React.createElement("button", { onClick: () => setOpen(!open), style: { width: "100%", padding: "8px 12px", borderRadius: 8, border: "1px solid " + (t.border || "#333"), background: t.card || "#1a1a2e", color: t.text || "#fff", fontSize: 13, cursor: "pointer", textAlign: "left", display: "flex", justifyContent: "space-between", alignItems: "center" } },
      React.createElement("span", null, cnt > 0 ? cnt + " selected" : label),
      React.createElement("span", { style: { fontSize: 10 } }, open ? "▲" : "▼")
    ),
    open && React.createElement("div", { style: { position: "absolute", top: "100%", left: 0, right: 0, zIndex: Z_DATE_PICKER, background: t.card || "#1a1a2e", border: "1px solid " + (t.border || "#333"), borderRadius: 8, maxHeight: DATE_PICKER_MAX_HEIGHT, overflowY: "auto", boxShadow: "0 8px 24px rgba(0,0,0,0.3)" } },
      React.createElement("div", { style: { display: "flex", gap: 0, borderBottom: "1px solid " + (t.border || "#333") } },
        React.createElement("div", { onClick: () => { if (onSelectAll) onSelectAll(allIds); }, style: { flex: 1, padding: "8px 14px", cursor: "pointer", fontSize: 12, fontWeight: 600, color: allSelected ? (t.textDim || "#888") : (t.accent || "#60a5fa"), textAlign: "center", borderRight: "1px solid " + (t.border || "#333") } }, "✓ All"),
        React.createElement("div", { onClick: () => { if (onClear) onClear(); else selected.forEach(s => onToggle(s)); }, style: { flex: 1, padding: "8px 14px", cursor: "pointer", fontSize: 12, fontWeight: 600, color: cnt === 0 ? (t.textDim || "#888") : (t.accent || "#60a5fa"), textAlign: "center" } }, "✕ None")
      ),
      options.map(([id, item]) => React.createElement("label", { key: id, style: { display: "flex", alignItems: "center", gap: 8, padding: "8px 14px", cursor: "pointer", fontSize: 13, color: t.text || "#fff", borderBottom: "1px solid " + (t.border || "#333") },
        onMouseEnter: (e) => { e.currentTarget.style.background = t.hover || "#2a2a4a"; },
        onMouseLeave: (e) => { e.currentTarget.style.background = t.card || "#1a1a2e"; } },
        React.createElement("input", { type: "checkbox", checked: selected.includes(id), onChange: () => onToggle(id), style: { accentColor: t.accent || "#60a5fa" } }),
        item.color && React.createElement("span", { style: { width: 12, height: 12, borderRadius: "50%", background: item.color, flexShrink: 0 } }),
        React.createElement("span", null, item.name)
      ))
    )
  );
}

// ═══════════════════════════════════════════════════════════════════════════
// ─── Main App ─────────────────────────────────────────────────────────────
// ─── Auth Modal ─────────────────────────────────────────────────────────────
function AuthModal({ onClose, onAuth, initialResetToken }) {
  const [mode, setMode] = useState(initialResetToken ? "reset" : "login"); // "login", "register", "forgot", "reset"
  const [email, setEmail] = useState("");
  const [password, setPassword] = useState("");
  const [confirmPassword, setConfirmPassword] = useState("");
  const [displayName, setDisplayName] = useState("");
  const [error, setError] = useState("");
  const [success, setSuccess] = useState("");
  const [loading, setLoading] = useState(false);
  const [resetToken, setResetToken] = useState(initialResetToken || "");

  const handleSubmit = async (e) => {
    e.preventDefault();
    setError("");
    setSuccess("");

    // Forgot password flow
    if (mode === "forgot") {
      setLoading(true);
      try {
        const res = await fetch(`${API}/api/auth/forgot-password`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ email }) });
        const data = await res.json();
        if (!res.ok) throw new Error(data.detail || "Request failed");
        setSuccess(data.message || "If that email exists, a reset link has been sent.");
      } catch (err) { setError(err.message); }
      setLoading(false);
      return;
    }

    // Reset password flow
    if (mode === "reset") {
      if (password !== confirmPassword) { setError("Passwords don't match"); return; }
      setLoading(true);
      try {
        const res = await fetch(`${API}/api/auth/reset-password`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ token: resetToken, new_password: password }) });
        const data = await res.json();
        if (!res.ok) throw new Error(data.detail || "Reset failed");
        setSuccess(data.message || "Password has been reset. You can now sign in.");
        setTimeout(() => { setMode("login"); setPassword(""); setConfirmPassword(""); setSuccess(""); }, 2000);
      } catch (err) { setError(err.message); }
      setLoading(false);
      return;
    }

    if (mode === "register" && password !== confirmPassword) { setError("Passwords don't match"); return; }
    setLoading(true);
    try {
      const endpoint = mode === "login" ? "/api/auth/login" : "/api/auth/register";
      const body = mode === "login" ? { email, password } : { email, password, display_name: displayName };
      const res = await fetch(`${API}${endpoint}`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(body) });
      const data = await res.json();
      if (!res.ok) throw new Error(data.detail || "Auth failed");
      localStorage.setItem("auth_token", data.token);
      onAuth(data.user, data.token);
      onClose();
    } catch (err) { setError(err.message); }
    setLoading(false);
  };

  const [googleClientId, setGoogleClientId] = useState("");

  // Fetch Google client ID and initialize Google Sign-In
  useEffect(() => {
    fetch(`${API}/api/auth/config`).then(r => r.json()).then(cfg => {
      if (cfg.google_client_id) setGoogleClientId(cfg.google_client_id);
    }).catch(() => {});
  }, []);

  useEffect(() => {
    if (!googleClientId || !window.google?.accounts?.id) return;
    try {
      window.google.accounts.id.initialize({
        client_id: googleClientId,
        callback: async (response) => {
          setError("");
          setLoading(true);
          try {
            const res = await fetch(`${API}/api/auth/google`, {
              method: "POST",
              headers: { "Content-Type": "application/json" },
              body: JSON.stringify({ credential: response.credential }),
            });
            const data = await res.json();
            if (!res.ok) throw new Error(data.detail || "Google auth failed");
            localStorage.setItem("auth_token", data.token);
            onAuth(data.user, data.token);
            onClose();
          } catch (err) { setError(err.message); }
          setLoading(false);
        },
      });
      const btnEl = document.getElementById("google-signin-btn");
      if (btnEl) {
        window.google.accounts.id.renderButton(btnEl, {
          theme: "filled_black",
          size: "large",
          width: "100%",
          text: mode === "login" ? "signin_with" : "signup_with",
          shape: "rectangular",
        });
      }
    } catch (e) { console.warn("Google Sign-In init error:", e); }
  }, [googleClientId, mode]);

  const inputStyle = { width: "100%", padding: "10px 14px", background: theme.surface, border: `1px solid ${theme.border}`, borderRadius: 8, color: theme.text, fontSize: 14, fontFamily: "inherit", boxSizing: "border-box" };
  const labelStyle = { display: "block", fontSize: 12, color: theme.textDim, fontWeight: 600, textTransform: "uppercase", letterSpacing: 1, marginBottom: 6 };

  // Only show Google section if client ID is configured
  const showGoogle = !!googleClientId;

  return (
    <div style={{ position: "fixed", inset: 0, background: "rgba(0,0,0,0.7)", display: "flex", alignItems: "center", justifyContent: "center", zIndex: Z_MODAL_TOP }} onClick={onClose} onKeyDown={e => { if (e.key === "Escape") onClose(); }}>
      <div role="dialog" aria-modal="true" aria-labelledby="auth-modal-title" style={{ background: theme.card, border: `1px solid ${theme.border}`, borderRadius: 16, padding: 32, width: 400, maxWidth: "90vw" }} onClick={e => e.stopPropagation()}>
        <div style={{ display: "flex", justifyContent: "space-between", alignItems: "center", marginBottom: 24 }}>
          <h2 id="auth-modal-title" style={{ fontSize: 20, fontWeight: 700, margin: 0 }}>{mode === "login" ? "Sign In" : mode === "register" ? "Create Account" : mode === "forgot" ? "Reset Password" : "Set New Password"}</h2>
          <button onClick={onClose} aria-label="Close sign in dialog" style={{ background: "none", border: "none", color: theme.textDim, fontSize: 20, cursor: "pointer" }}>×</button>
        </div>

        {/* Google Sign-In button container — only shown if configured */}
        {showGoogle && <div id="google-signin-btn" style={{ marginBottom: 16 }}></div>}

        {showGoogle && (
          <div style={{ display: "flex", alignItems: "center", gap: 12, marginBottom: 16 }}>
            <div style={{ flex: 1, height: 1, background: theme.border }} />
            <span style={{ fontSize: 12, color: theme.textDim }}>or</span>
            <div style={{ flex: 1, height: 1, background: theme.border }} />
          </div>
        )}

        <form onSubmit={handleSubmit}>
          {mode === "forgot" && (
            <>
              <p style={{ fontSize: 13, color: theme.textDim, marginBottom: 16 }}>Enter your email and we'll send you a link to reset your password.</p>
              <div style={{ marginBottom: 12 }}>
                <label htmlFor="auth-email" style={labelStyle}>Email</label>
                <input id="auth-email" type="email" value={email} onChange={e => setEmail(e.target.value)} placeholder="you@example.com" required style={inputStyle} autoComplete="email" />
              </div>
            </>
          )}
          {mode === "reset" && (
            <>
              <p style={{ fontSize: 13, color: theme.textDim, marginBottom: 16 }}>Enter your new password (min 8 characters, must include uppercase, lowercase, and a number).</p>
              <div style={{ marginBottom: 12 }}>
                <label htmlFor="auth-password" style={labelStyle}>New Password</label>
                <input id="auth-password" type="password" value={password} onChange={e => setPassword(e.target.value)} placeholder="••••••••" required minLength={8} style={inputStyle} autoComplete="new-password" />
              </div>
              <div style={{ marginBottom: 12 }}>
                <label htmlFor="auth-confirm-pw" style={labelStyle}>Confirm New Password</label>
                <input id="auth-confirm-pw" type="password" value={confirmPassword} onChange={e => setConfirmPassword(e.target.value)} placeholder="••••••••" required style={inputStyle} autoComplete="new-password" />
              </div>
            </>
          )}
          {(mode === "login" || mode === "register") && (
            <>
              {mode === "register" && (
                <div style={{ marginBottom: 12 }}>
                  <label htmlFor="auth-name" style={labelStyle}>Name</label>
                  <input id="auth-name" type="text" value={displayName} onChange={e => setDisplayName(e.target.value)} placeholder="Your name" style={inputStyle} />
                </div>
              )}
              <div style={{ marginBottom: 12 }}>
                <label htmlFor="auth-email" style={labelStyle}>Email</label>
                <input id="auth-email" type="email" value={email} onChange={e => setEmail(e.target.value)} placeholder="you@example.com" required style={inputStyle} autoComplete="email" />
              </div>
              <div style={{ marginBottom: 12 }}>
                <label htmlFor="auth-password" style={labelStyle}>Password</label>
                <input id="auth-password" type="password" value={password} onChange={e => setPassword(e.target.value)} placeholder="••••••••" required minLength={8} style={inputStyle} autoComplete={mode === "login" ? "current-password" : "new-password"} />
                {mode === "register" && <p style={{ fontSize: 11, color: theme.textDim, marginTop: 4, marginBottom: 0 }}>Min 8 chars, with uppercase, lowercase, and a number</p>}
              </div>
              {mode === "register" && (
                <div style={{ marginBottom: 12 }}>
                  <label htmlFor="auth-confirm-pw" style={labelStyle}>Confirm Password</label>
                  <input id="auth-confirm-pw" type="password" value={confirmPassword} onChange={e => setConfirmPassword(e.target.value)} placeholder="••••••••" required style={inputStyle} autoComplete="new-password" />
                </div>
              )}
            </>
          )}
          {error && <div role="alert" style={{ color: theme.danger, fontSize: 13, marginBottom: 12, padding: "8px 12px", background: "rgba(239,68,68,0.1)", borderRadius: 8 }}>{error}</div>}
          {success && <div style={{ color: "#34d399", fontSize: 13, marginBottom: 12, padding: "8px 12px", background: "rgba(52,211,153,0.1)", borderRadius: 8 }}>{success}</div>}
          <button type="submit" disabled={loading} style={{ width: "100%", padding: "12px", background: theme.accent, color: "#fff", border: "none", borderRadius: 8, fontSize: 14, fontWeight: 600, cursor: loading ? "wait" : "pointer", opacity: loading ? 0.7 : 1, fontFamily: "inherit" }}>
            {loading ? "..." : mode === "login" ? "Sign In" : mode === "register" ? "Create Account" : mode === "forgot" ? "Send Reset Link" : "Reset Password"}
          </button>
        </form>

        <div style={{ textAlign: "center", marginTop: 16, fontSize: 13, color: theme.textDim }}>
          {mode === "login" && (
            <>
              <button onClick={() => { setMode("forgot"); setError(""); setSuccess(""); }} style={{ background: "none", border: "none", color: theme.textDim, cursor: "pointer", fontFamily: "inherit", fontSize: 12, marginBottom: 8, display: "block", width: "100%" }}>Forgot password?</button>
              <span>Don't have an account? <button onClick={() => { setMode("register"); setError(""); setSuccess(""); }} style={{ background: "none", border: "none", color: theme.accent, cursor: "pointer", fontFamily: "inherit", fontSize: 13 }}>Sign Up</button></span>
            </>
          )}
          {mode === "register" && (
            <span>Already have an account? <button onClick={() => { setMode("login"); setError(""); setSuccess(""); }} style={{ background: "none", border: "none", color: theme.accent, cursor: "pointer", fontFamily: "inherit", fontSize: 13 }}>Sign In</button></span>
          )}
          {(mode === "forgot" || mode === "reset") && (
            <span>Back to <button onClick={() => { setMode("login"); setError(""); setSuccess(""); }} style={{ background: "none", border: "none", color: theme.accent, cursor: "pointer", fontFamily: "inherit", fontSize: 13 }}>Sign In</button></span>
          )}
        </div>
      </div>
    </div>
  );
}


// ─── Profile Tab ────────────────────────────────────────────────────────────
function ProfileTab({ user, token, onUserUpdate, themeMode, onThemeChange, onHomeAirportsChange, onSearchDefaultsChange }) {
  const [prefs, setPrefs] = useState(null);
  const [saving, setSaving] = useState(false);
  const [changePwOpen, setChangePwOpen] = useState(false);
  const [pwForm, setPwForm] = useState({ current: "", new_pw: "", confirm: "" });
  const [pwMsg, setPwMsg] = useState("");
  const [editName, setEditName] = useState(false);
  const [nameVal, setNameVal] = useState(user?.display_name || "");
  const saveTimer = useRef(null);

  const authHeaders = { "Authorization": `Bearer ${token}`, "Content-Type": "application/json" };

  useEffect(() => {
    if (!token) return;
    fetch(`${API}/api/user/preferences`, { headers: { Authorization: `Bearer ${token}` } })
      .then(r => r.json()).then(setPrefs).catch(e => console.warn("Prefs load:", e));
  }, [token]);

  const savePrefs = (updated) => {
    setPrefs(updated);
    if (saveTimer.current) clearTimeout(saveTimer.current);
    saveTimer.current = setTimeout(() => {
      fetch(`${API}/api/user/preferences`, { method: "PUT", headers: authHeaders, body: JSON.stringify(updated) }).catch(window.silentErr("Save preferences"));
    }, 800);
  };

  const SEARCH_DEFAULT_KEYS = ["trip_type_preference", "cabin_preferences", "max_stops", "max_taxes", "default_sort", "booking_preference"];
  const updatePref = (key, val) => {
    const updated = { ...prefs, [key]: val };
    savePrefs(updated);
    if (key === "home_airports" && onHomeAirportsChange) onHomeAirportsChange(val);
    if (SEARCH_DEFAULT_KEYS.includes(key) && onSearchDefaultsChange) onSearchDefaultsChange(updated);
  };

  const handleChangePw = async () => {
    setPwMsg("");
    if (pwForm.new_pw !== pwForm.confirm) { setPwMsg("Passwords don't match"); return; }
    try {
      const res = await fetch(`${API}/api/auth/change-password`, { method: "POST", headers: authHeaders, body: JSON.stringify({ current_password: pwForm.current, new_password: pwForm.new_pw }) });
      const data = await res.json();
      if (!res.ok) throw new Error(data.detail || "Failed");
      setPwMsg("Password changed successfully!");
      setPwForm({ current: "", new_pw: "", confirm: "" });
      setTimeout(() => setChangePwOpen(false), CONFIRMATION_MSG_MS);
    } catch (e) { setPwMsg(e.message); }
  };

  const handleSaveName = async () => {
    await fetch(`${API}/api/user/profile`, { method: "PUT", headers: authHeaders, body: JSON.stringify({ display_name: nameVal }) });
    onUserUpdate({ ...user, display_name: nameVal });
    setEditName(false);
  };

  if (!prefs) return <div style={{ color: theme.textDim, textAlign: "center", padding: 40 }}>Loading preferences...</div>;

  const sectionStyle = { background: theme.surface, border: `1px solid ${theme.border}`, borderRadius: 16, padding: 24, marginBottom: 20 };
  const labelStyle = { fontSize: 12, color: theme.textDim, fontWeight: 600, textTransform: "uppercase", letterSpacing: 1, display: "block", marginBottom: 8 };
  const inputStyle = { padding: "10px 14px", background: theme.bg, border: `1px solid ${theme.border}`, borderRadius: 8, color: theme.text, fontSize: 14, fontFamily: "inherit", width: "100%", boxSizing: "border-box" };
  const chipActive = { padding: "6px 16px", borderRadius: 20, border: `1px solid ${theme.accent}`, background: theme.accentGlow, color: theme.accent, fontSize: 13, fontWeight: 600, cursor: "pointer", fontFamily: "inherit" };
  const chipInactive = { padding: "6px 16px", borderRadius: 20, border: `1px solid ${theme.border}`, background: "transparent", color: theme.textDim, fontSize: 13, fontWeight: 500, cursor: "pointer", fontFamily: "inherit" };

  return (
    <div>
      <h2 style={{ fontSize: 20, fontWeight: 700, marginBottom: 20 }}>My Profile</h2>

      {/* Account Info */}
      <div style={sectionStyle}>
        <h3 style={{ fontSize: 16, fontWeight: 600, marginBottom: 16, color: theme.text }}>Account</h3>
        <div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: 16 }}>
          <div>
            <label style={labelStyle}>Display Name</label>
            {editName ? (
              <div style={{ display: "flex", gap: 8 }}>
                <input value={nameVal} onChange={e => setNameVal(e.target.value)} style={{ ...inputStyle, flex: 1 }} />
                <button onClick={handleSaveName} style={{ ...chipActive, borderRadius: 8 }}>Save</button>
                <button onClick={() => setEditName(false)} style={{ ...chipInactive, borderRadius: 8 }}>Cancel</button>
              </div>
            ) : (
              <div style={{ display: "flex", alignItems: "center", gap: 8 }}>
                <span style={{ fontSize: 14 }}>{user.display_name}</span>
                <button onClick={() => { setNameVal(user.display_name); setEditName(true); }} style={{ background: "none", border: "none", color: theme.accent, cursor: "pointer", fontSize: 12 }}>Edit</button>
              </div>
            )}
          </div>
          <div>
            <label style={labelStyle}>Email</label>
            <span style={{ fontSize: 14, color: theme.textMuted }}>{user.email}</span>
          </div>
        </div>
        <div style={{ marginTop: 16 }}>
          <label style={labelStyle}>Role</label>
          <span style={{ padding: "4px 12px", borderRadius: 12, fontSize: 12, fontWeight: 600, background: user.role === "master_admin" ? "rgba(168,85,247,0.15)" : user.role === "admin" ? "rgba(239,68,68,0.15)" : user.role === "premium" ? "rgba(251,191,36,0.15)" : "rgba(59,130,246,0.15)", color: user.role === "master_admin" ? "#a855f7" : user.role === "admin" ? theme.danger : user.role === "premium" ? theme.gold : theme.accent }}>{user.role === "master_admin" ? "MASTER ADMIN" : user.role.toUpperCase()}</span>
        </div>
        <div style={{ marginTop: 16 }}>
          {changePwOpen ? (
            <div style={{ background: theme.bg, borderRadius: 12, padding: 16, border: `1px solid ${theme.border}` }}>
              <div style={{ display: "grid", gap: 12 }}>
                {user.auth_provider !== "google" && <div><label style={labelStyle}>Current Password</label><input type="password" value={pwForm.current} onChange={e => setPwForm({ ...pwForm, current: e.target.value })} style={inputStyle} /></div>}
                <div><label style={labelStyle}>New Password</label><input type="password" value={pwForm.new_pw} onChange={e => setPwForm({ ...pwForm, new_pw: e.target.value })} style={inputStyle} /></div>
                <div><label style={labelStyle}>Confirm New Password</label><input type="password" value={pwForm.confirm} onChange={e => setPwForm({ ...pwForm, confirm: e.target.value })} style={inputStyle} /></div>
              </div>
              {pwMsg && <div style={{ fontSize: 13, marginTop: 8, color: pwMsg.includes("success") ? theme.success : theme.danger }}>{pwMsg}</div>}
              <div style={{ display: "flex", gap: 8, marginTop: 12 }}>
                <button onClick={handleChangePw} style={{ ...chipActive, borderRadius: 8 }}>Change Password</button>
                <button onClick={() => setChangePwOpen(false)} style={{ ...chipInactive, borderRadius: 8 }}>Cancel</button>
              </div>
            </div>
          ) : (
            <button onClick={() => setChangePwOpen(true)} style={{ ...chipInactive, borderRadius: 8 }}>{user.auth_provider === "google" ? "Set Password" : "Change Password"}</button>
          )}
        </div>
      </div>

      {/* Appearance */}
      <div style={sectionStyle}>
        <h3 style={{ fontSize: 16, fontWeight: 600, marginBottom: 16, color: theme.text }}>Appearance</h3>
        <label style={labelStyle}>Theme</label>
        <div style={{ display: "flex", gap: 8 }}>
          <button onClick={() => { onThemeChange("light"); updatePref("theme_mode", "light"); }}
            style={themeMode === "light" ? chipActive : chipInactive}>Light</button>
          <button onClick={() => { onThemeChange("dark"); updatePref("theme_mode", "dark"); }}
            style={themeMode === "dark" ? chipActive : chipInactive}>Dark</button>
        </div>
      </div>

      {/* Home Airports */}
      <div style={sectionStyle}>
        <h3 style={{ fontSize: 16, fontWeight: 600, marginBottom: 16 }}>Home Airports</h3>
        <p style={{ fontSize: 13, color: theme.textDim, marginBottom: 12 }}>Your default departure airports. All will auto-fill in search.</p>
        <AutocompleteInput value={(prefs.home_airports || []).join(",")} onChange={(v) => updatePref("home_airports", v ? v.split(",").map(s => s.trim()).filter(Boolean) : [])} placeholder="Add airports (e.g. MEM, JFK, LAX)" theme={theme} />
      </div>

      {/* Search Defaults */}
      <div style={sectionStyle}>
        <h3 style={{ fontSize: 16, fontWeight: 600, marginBottom: 16 }}>Search Defaults</h3>
        <div className="admin-3col" style={{ display: "grid", gridTemplateColumns: "1fr 1fr 1fr", gap: 20 }}>
          <div>
            <label style={labelStyle}>Trip Type</label>
            <div style={{ display: "flex", gap: 8 }}>
              {[["one_way", "One Way"], ["round_trip", "Round Trip"], ["both", "Both"]].map(([val, label]) => (
                <button key={val} onClick={() => updatePref("trip_type_preference", val)} style={prefs.trip_type_preference === val ? chipActive : chipInactive}>{label}</button>
              ))}
            </div>
          </div>
          <div>
            <label style={labelStyle}>Booking Type</label>
            <div style={{ display: "flex", gap: 8 }}>
              {[["awards", "Awards"], ["cash", "Cash"], ["both", "Both"]].map(([val, label]) => (
                <button key={val} onClick={() => updatePref("booking_preference", val)} style={prefs.booking_preference === val ? chipActive : chipInactive}>{label}</button>
              ))}
            </div>
          </div>
          <div>
            <label style={labelStyle}>Default Sort</label>
            <select value={prefs.default_sort || "points_low"} onChange={e => updatePref("default_sort", e.target.value)} style={{ ...inputStyle, cursor: "pointer" }}>
              <option value="points_low">Points: Low to High</option>
              <option value="points_high">Points: High to Low</option>
              <option value="taxes_low">Taxes: Low to High</option>
              <option value="date_asc">Date: Earliest</option>
            </select>
          </div>
        </div>

        <div style={{ marginTop: 16 }}>
          <label style={labelStyle}>Default Cabin Classes</label>
          <div style={{ display: "flex", gap: 8 }}>
            {[["economy", "Economy", "Y"], ["premium_economy", "Premium Economy", "W"], ["business", "Business", "J"], ["first", "First", "F"]].map(([key, label, code]) => {
              const cabins = prefs.cabin_preferences || {};
              const active = cabins[key];
              return <button key={key} onClick={() => updatePref("cabin_preferences", { ...cabins, [key]: !active })} style={active ? chipActive : chipInactive}>{cabinInfo[code]?.icon} {label}</button>;
            })}
          </div>
        </div>

        <div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: 16, marginTop: 16 }}>
          <div>
            <label style={labelStyle}>Max Stops</label>
            <select value={prefs.max_stops ?? ""} onChange={e => updatePref("max_stops", e.target.value === "" ? null : parseInt(e.target.value))} style={{ ...inputStyle, cursor: "pointer" }}>
              <option value="">No limit</option>
              <option value="0">Direct only</option>
              <option value="1">1 stop max</option>
              <option value="2">2 stops max</option>
            </select>
          </div>
          <div>
            <label style={labelStyle}>Max Taxes ($)</label>
            <input type="number" value={prefs.max_taxes || ""} onChange={e => updatePref("max_taxes", e.target.value ? parseInt(e.target.value) : null)} placeholder="No limit" style={inputStyle} />
          </div>
        </div>
      </div>

      {/* Bank Programs */}
      <div style={sectionStyle}>
        <h3 style={{ fontSize: 16, fontWeight: 600, marginBottom: 16 }}>My Bank Programs</h3>
        <p style={{ fontSize: 13, color: theme.textDim, marginBottom: 12 }}>Select the bank point programs you have and enter your balances.</p>
        <div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: 12 }}>
          {Object.entries(BANKS).map(([id, bank]) => {
            const bProgs = prefs.bank_programs || {};
            const bpEntry = Array.isArray(bProgs) ? bProgs.find(b => b.id === id) : null;
            const enabled = bpEntry ? bpEntry.enabled : (bProgs[id]?.enabled ?? false);
            const balances = prefs.bank_balances || {};
            const balance = balances[id] || 0;
            return (
              <div key={id} style={{ display: "flex", alignItems: "center", gap: 12, padding: "10px 14px", background: enabled ? theme.accentGlow : theme.bg, border: `1px solid ${enabled ? theme.accent + "40" : theme.border}`, borderRadius: 10, cursor: "pointer" }} onClick={() => {
                if (Array.isArray(bProgs)) {
                  const updated = bProgs.map(b => b.id === id ? { ...b, enabled: !b.enabled } : b);
                  if (!bpEntry) updated.push({ id, name: bank.name, enabled: true });
                  updatePref("bank_programs", updated);
                } else {
                  updatePref("bank_programs", { ...bProgs, [id]: { ...bProgs[id], enabled: !enabled, name: bank.name } });
                }
              }}>
                <img src={bank.logo} alt={`${bank.name || "Bank"} logo`} style={{ width: 28, height: 28, borderRadius: 6 }} onError={e => e.target.style.display = "none"} />
                <div style={{ flex: 1 }}>
                  <div style={{ fontSize: 13, fontWeight: 600, color: enabled ? theme.text : theme.textDim }}>{bank.shortName}</div>
                  {enabled && <div style={{ fontSize: 11, color: theme.textDim }}>{(balance || 0).toLocaleString()} {bank.currency}</div>}
                </div>
                <div style={{ width: 18, height: 18, borderRadius: 4, border: `2px solid ${enabled ? theme.accent : theme.border}`, background: enabled ? theme.accent : "transparent", display: "flex", alignItems: "center", justifyContent: "center", fontSize: 12, color: "#fff" }}>{enabled ? "✓" : ""}</div>
              </div>
            );
          })}
        </div>
      </div>

      {/* Favorite Airports */}
      <div style={sectionStyle}>
        <h3 style={{ fontSize: 16, fontWeight: 600, marginBottom: 16 }}>Favorite Airports</h3>
        <p style={{ fontSize: 13, color: theme.textDim, marginBottom: 12 }}>Quick-access airports for search.</p>
        <AutocompleteInput value={(prefs.favorite_airports || []).join(",")} onChange={(v) => updatePref("favorite_airports", v ? v.split(",").map(s => s.trim()).filter(Boolean) : [])} placeholder="Add airports (e.g. NRT, LHR, SIN)" theme={theme} />
      </div>

      {/* Alert Email */}
      <div style={sectionStyle}>
        <h3 style={{ fontSize: 16, fontWeight: 600, marginBottom: 16 }}>Notifications</h3>
        <div>
          <label style={labelStyle}>Alert Email</label>
          <input type="email" value={prefs.alerts_email || ""} onChange={e => updatePref("alerts_email", e.target.value)} placeholder="you@example.com" style={inputStyle} />
        </div>
      </div>
    </div>
  );
}


// ─── Admin User Management Section ──────────────────────────────────────────
function AdminUsersSection({ token, currentUserRole }) {
  const [users, setUsers] = useState([]);
  const [loading, setLoading] = useState(true);
  const authHeaders = { "Authorization": `Bearer ${token}`, "Content-Type": "application/json" };
  const isMasterAdmin = currentUserRole === "master_admin";

  const roleColors = {
    master_admin: { bg: "rgba(239,68,68,0.15)", color: "#ef4444", label: "MASTER ADMIN" },
    admin: { bg: "rgba(251,191,36,0.15)", color: "#fbbf24", label: "ADMIN" },
    premium: { bg: "rgba(168,85,247,0.15)", color: "#a855f7", label: "PREMIUM" },
    user: { bg: "rgba(59,130,246,0.15)", color: "#3b82f6", label: "USER" },
  };

  const loadUsers = () => {
    fetch(`${API}/api/admin/users`, { headers: { Authorization: `Bearer ${token}` } })
      .then(r => r.json()).then(d => { setUsers(d.users || []); setLoading(false); }).catch(() => setLoading(false));
  };
  useEffect(loadUsers, []);

  const changeRole = async (userId, newRole) => {
    const res = await fetch(`${API}/api/admin/users/${userId}/role`, { method: "PUT", headers: authHeaders, body: JSON.stringify({ role: newRole }) });
    if (!res.ok) { const d = await res.json(); alert(d.detail || "Failed to change role"); }
    loadUsers();
  };

  const deleteUser = async (userId, email) => {
    if (!confirm(`Delete user ${email}? This cannot be undone.`)) return;
    const res = await fetch(`${API}/api/admin/users/${userId}`, { method: "DELETE", headers: { Authorization: `Bearer ${token}` } });
    if (!res.ok) { const d = await res.json(); alert(d.detail || "Failed to delete user"); }
    loadUsers();
  };

  if (loading) return <div style={{ color: theme.textDim }}>Loading users...</div>;

  return (
    <div>
      <h3 style={{ fontSize: 16, fontWeight: 600, marginBottom: 6, display: "flex", alignItems: "center", gap: 8 }}>
        User Management <span style={{ fontSize: 12, fontWeight: 400, color: theme.textDim }}>({users.length} users)</span>
      </h3>
      <p style={{ fontSize: 12, color: theme.textDim, marginBottom: 16 }}>
        Role hierarchy: <span style={{ color: roleColors.master_admin.color }}>Master Admin</span> &gt; <span style={{ color: roleColors.admin.color }}>Admin</span> &gt; <span style={{ color: roleColors.premium.color }}>Premium</span> &gt; <span style={{ color: roleColors.user.color }}>User</span>
      </p>
      <div style={{ overflowX: "auto" }}>
        <table style={{ width: "100%", borderCollapse: "collapse", fontSize: 13 }}>
          <thead>
            <tr style={{ borderBottom: `1px solid ${theme.border}` }}>
              <th style={{ textAlign: "left", padding: "8px 12px", color: theme.textDim, fontWeight: 600, fontSize: 11, textTransform: "uppercase", letterSpacing: 1 }}>Name</th>
              <th style={{ textAlign: "left", padding: "8px 12px", color: theme.textDim, fontWeight: 600, fontSize: 11, textTransform: "uppercase", letterSpacing: 1 }}>Email</th>
              <th style={{ textAlign: "left", padding: "8px 12px", color: theme.textDim, fontWeight: 600, fontSize: 11, textTransform: "uppercase", letterSpacing: 1 }}>Role</th>
              <th style={{ textAlign: "left", padding: "8px 12px", color: theme.textDim, fontWeight: 600, fontSize: 11, textTransform: "uppercase", letterSpacing: 1 }}>Provider</th>
              <th style={{ textAlign: "left", padding: "8px 12px", color: theme.textDim, fontWeight: 600, fontSize: 11, textTransform: "uppercase", letterSpacing: 1 }}>Last Login</th>
              <th style={{ textAlign: "right", padding: "8px 12px", color: theme.textDim, fontWeight: 600, fontSize: 11, textTransform: "uppercase", letterSpacing: 1 }}>Actions</th>
            </tr>
          </thead>
          <tbody>
            {users.map(u => {
              const isMaster = u.role === "master_admin";
              const rc = roleColors[u.role] || roleColors.user;
              return (
              <tr key={u.id} style={{ borderBottom: `1px solid ${theme.border}22` }}>
                <td style={{ padding: "10px 12px" }}>{u.display_name}</td>
                <td style={{ padding: "10px 12px", color: theme.textMuted }}>{u.email}</td>
                <td style={{ padding: "10px 12px" }}>
                  {isMaster ? (
                    <span style={{ padding: "4px 10px", borderRadius: 12, fontSize: 11, fontWeight: 700, background: rc.bg, color: rc.color }}>{rc.label}</span>
                  ) : (
                    <select value={u.role} onChange={e => changeRole(u.id, e.target.value)} disabled={!isMasterAdmin && u.role === "admin"} style={{ background: theme.bg, color: rc.color, border: `1px solid ${rc.color}44`, borderRadius: 6, padding: "4px 8px", fontSize: 12, fontFamily: "inherit", cursor: (!isMasterAdmin && u.role === "admin") ? "not-allowed" : "pointer", opacity: (!isMasterAdmin && u.role === "admin") ? 0.6 : 1 }}>
                      <option value="user">User</option>
                      <option value="premium">Premium</option>
                      <option value="admin">Admin</option>
                    </select>
                  )}
                </td>
                <td style={{ padding: "10px 12px", color: theme.textDim }}>{u.auth_provider || "local"}</td>
                <td style={{ padding: "10px 12px", color: theme.textDim }}>{u.last_login ? new Date(u.last_login).toLocaleDateString() : "Never"}</td>
                <td style={{ padding: "10px 12px", textAlign: "right" }}>
                  {isMaster ? (
                    <span style={{ fontSize: 11, color: theme.textDim }}>Protected</span>
                  ) : (
                    <button onClick={() => deleteUser(u.id, u.email)} style={{ background: "rgba(239,68,68,0.1)", color: theme.danger, border: "none", borderRadius: 6, padding: "4px 10px", fontSize: 12, cursor: "pointer", fontFamily: "inherit" }}>Delete</button>
                  )}
                </td>
              </tr>
              );
            })}
          </tbody>
        </table>
      </div>
    </div>
  );
}


// ═══════════════════════════════════════════════════════════════════════════
// ─── Role Permissions Panel ──────────────────────────────────────────────
function RolePermissionsPanel({ token, currentUserRole }) {
  const isMasterAdmin = currentUserRole === "master_admin";
  const roleColors = {
    master_admin: { bg: "rgba(239,68,68,0.15)", color: "#ef4444", label: "Master Admin" },
    admin: { bg: "rgba(251,191,36,0.15)", color: "#fbbf24", label: "Admin" },
    premium: { bg: "rgba(168,85,247,0.15)", color: "#a855f7", label: "Premium" },
    user: { bg: "rgba(59,130,246,0.15)", color: "#3b82f6", label: "User" },
  };

  // Default permissions matrix — defines what each role can access
  const defaultPerms = {
    master_admin: { search: true, programs: true, dashboard: true, admin: true, email_preview: true, ingestion: true, profile: true, manage_users: true, manage_roles: true, run_ingestion: true, view_as: true },
    admin: { search: true, programs: true, dashboard: true, admin: true, email_preview: true, ingestion: true, profile: true, manage_users: true, manage_roles: false, run_ingestion: true, view_as: true },
    premium: { search: true, programs: true, dashboard: true, admin: false, email_preview: true, ingestion: false, profile: true, manage_users: false, manage_roles: false, run_ingestion: false, view_as: false },
    user: { search: true, programs: true, dashboard: true, admin: false, email_preview: true, ingestion: false, profile: true, manage_users: false, manage_roles: false, run_ingestion: false, view_as: false },
  };

  const permLabels = {
    search: "Search Flights",
    programs: "Programs Tab",
    dashboard: "Dashboard Tab",
    admin: "Admin Panel",
    email_preview: "Alerts & Preview",
    ingestion: "Ingestion Panel",
    profile: "Profile / Preferences",
    manage_users: "Manage Users",
    manage_roles: "Promote to Admin",
    run_ingestion: "Run Ingestion Jobs",
    view_as: "View As Other Roles",
  };

  const permOrder = ["search", "programs", "dashboard", "profile", "admin", "email_preview", "ingestion", "manage_users", "manage_roles", "run_ingestion", "view_as"];
  const roles = ["master_admin", "admin", "premium", "user"];

  const checkIcon = "\u2713";
  const xIcon = "\u2717";

  return (
    <div>
      <h3 style={{ fontSize: 16, fontWeight: 600, marginBottom: 6, display: "flex", alignItems: "center", gap: 8 }}>
        Role Permissions <span style={{ fontSize: 12, fontWeight: 400, color: theme.textDim }}>(Reference)</span>
      </h3>
      <p style={{ fontSize: 12, color: theme.textDim, marginBottom: 16 }}>
        Overview of what each role can access. {!isMasterAdmin && "Only master admins can modify roles."}
      </p>
      <div style={{ overflowX: "auto" }}>
        <table style={{ width: "100%", borderCollapse: "collapse", fontSize: 13 }}>
          <thead>
            <tr style={{ borderBottom: `1px solid ${theme.border}` }}>
              <th style={{ textAlign: "left", padding: "8px 12px", color: theme.textDim, fontWeight: 600, fontSize: 11, textTransform: "uppercase", letterSpacing: 1, minWidth: 160 }}>Permission</th>
              {roles.map(r => {
                const rc = roleColors[r];
                return <th key={r} style={{ textAlign: "center", padding: "8px 12px", minWidth: 100 }}><span style={{ padding: "3px 10px", borderRadius: 10, fontSize: 11, fontWeight: 700, background: rc.bg, color: rc.color }}>{rc.label}</span></th>;
              })}
            </tr>
          </thead>
          <tbody>
            {permOrder.map(perm => (
              <tr key={perm} style={{ borderBottom: `1px solid ${theme.border}22` }}>
                <td style={{ padding: "8px 12px", color: theme.text, fontWeight: 500 }}>{permLabels[perm]}</td>
                {roles.map(r => {
                  const has = defaultPerms[r][perm];
                  return (
                    <td key={r} style={{ textAlign: "center", padding: "8px 12px" }}>
                      <span style={{ fontSize: 16, fontWeight: 700, color: has ? "#22c55e" : "rgba(239,68,68,0.4)" }}>{has ? checkIcon : xIcon}</span>
                    </td>
                  );
                })}
              </tr>
            ))}
          </tbody>
        </table>
      </div>
    </div>
  );
}

// ═══════════════════════════════════════════════════════════════════════════
// ─── Per-program carrier breakdowns (which carriers you can book via each program) ──
// Days = typical advance release window for that carrier's awards through this program
// Sources: seats.aero, roame.travel, upgradedpoints.com
const PROGRAM_CARRIERS = {
  aeroplan: [
    { iata: "AC", name: "Air Canada", days: 355 },
    { iata: "NH", name: "ANA", days: 355 },
    { iata: "AI", name: "Air India", days: 359 },
    { iata: "CA", name: "Air China", days: 358 },
    { iata: "NZ", name: "Air New Zealand", days: 355 },
    { iata: "OZ", name: "Asiana Airlines", days: 355 },
    { iata: "BR", name: "EVA Air", days: 358 },
    { iata: "ET", name: "Ethiopian Airlines", days: 355 },
    { iata: "LH", name: "Lufthansa", days: 356 },
    { iata: "MS", name: "EgyptAir", days: 357 },
    { iata: "SK", name: "SAS", days: 350 },
    { iata: "SQ", name: "Singapore Airlines", days: 355 },
    { iata: "TK", name: "Turkish Airlines", days: 355 },
    { iata: "UA", name: "United Airlines", days: 337 },
    { iata: "TP", name: "TAP Air Portugal", days: 357 },
    { iata: "LX", name: "SWISS", days: 356 },
    { iata: "OS", name: "Austrian Airlines", days: 356 },
    { iata: "SA", name: "South African Airways", days: 357 },
    { iata: "ZH", name: "Shenzhen Airlines", days: 358 },
    { iata: "AZ", name: "ITA Airways", days: 358 },
    { iata: "EY", name: "Etihad Airways", days: 357 },
    { iata: "A3", name: "Aegean Airlines", days: 357 },
    { iata: "OU", name: "Croatia Airlines", days: 357 },
  ],
  flyingblue: [
    { iata: "AF", name: "Air France", days: 359 },
    { iata: "KL", name: "KLM", days: 359 },
    { iata: "DL", name: "Delta Air Lines", days: 331 },
    { iata: "KE", name: "Korean Air", days: 361 },
    { iata: "SV", name: "Saudia", days: 330 },
    { iata: "VS", name: "Virgin Atlantic", days: 331 },
    { iata: "AM", name: "Aeromexico", days: 330 },
    { iata: "MU", name: "China Eastern", days: 355 },
    { iata: "GA", name: "Garuda Indonesia", days: 330 },
    { iata: "ME", name: "Middle East Airlines", days: 330 },
    { iata: "CI", name: "China Airlines", days: 360 },
    { iata: "VN", name: "Vietnam Airlines", days: 340 },
  ],
  alaska: [
    { iata: "AS", name: "Alaska Airlines", days: 331 },
    { iata: "AA", name: "American Airlines", days: 331 },
    { iata: "BA", name: "British Airways", days: 354 },
    { iata: "CX", name: "Cathay Pacific", days: 360 },
    { iata: "EK", name: "Emirates", days: 328 },
    { iata: "FJ", name: "Fiji Airways", days: 330 },
    { iata: "JL", name: "Japan Airlines", days: 360 },
    { iata: "QF", name: "Qantas", days: 353 },
    { iata: "QR", name: "Qatar Airways", days: 361 },
    { iata: "SQ", name: "Singapore Airlines", days: 355 },
    { iata: "AY", name: "Finnair", days: 360 },
    { iata: "IB", name: "Iberia", days: 360 },
  ],
  american: [
    { iata: "AA", name: "American Airlines", days: 331 },
    { iata: "BA", name: "British Airways", days: 354 },
    { iata: "CX", name: "Cathay Pacific", days: 360 },
    { iata: "EK", name: "Emirates", days: 328 },
    { iata: "EY", name: "Etihad Airways", days: 330 },
    { iata: "JL", name: "Japan Airlines", days: 360 },
    { iata: "QF", name: "Qantas", days: 353 },
    { iata: "QR", name: "Qatar Airways", days: 361 },
    { iata: "AY", name: "Finnair", days: 360 },
    { iata: "IB", name: "Iberia", days: 360 },
    { iata: "MH", name: "Malaysia Airlines", days: 355 },
    { iata: "SQ", name: "Singapore Airlines", days: 355 },
  ],
  delta: [
    { iata: "DL", name: "Delta Air Lines", days: 331 },
    { iata: "AF", name: "Air France", days: 359 },
    { iata: "KL", name: "KLM", days: 359 },
    { iata: "VS", name: "Virgin Atlantic", days: 331 },
    { iata: "KE", name: "Korean Air", days: 361 },
    { iata: "AM", name: "Aeromexico", days: 330 },
    { iata: "LA", name: "LATAM Airlines", days: 330 },
  ],
  united: [
    { iata: "UA", name: "United Airlines", days: 337 },
    { iata: "AC", name: "Air Canada", days: 355 },
    { iata: "NH", name: "ANA", days: 355 },
    { iata: "LH", name: "Lufthansa", days: 360 },
    { iata: "SQ", name: "Singapore Airlines", days: 355 },
    { iata: "TK", name: "Turkish Airlines", days: 355 },
    { iata: "BR", name: "EVA Air", days: 360 },
    { iata: "SK", name: "SAS", days: 350 },
    { iata: "TP", name: "TAP Air Portugal", days: 361 },
    { iata: "ET", name: "Ethiopian Airlines", days: 330 },
  ],
  emirates: [
    { iata: "EK", name: "Emirates", days: 328 },
    { iata: "QF", name: "Qantas", days: 353 },
    { iata: "JL", name: "Japan Airlines", days: 360 },
  ],
  qatar: [
    { iata: "QR", name: "Qatar Airways", days: 361 },
    { iata: "AA", name: "American Airlines", days: 331 },
    { iata: "BA", name: "British Airways", days: 354 },
    { iata: "CX", name: "Cathay Pacific", days: 360 },
    { iata: "AY", name: "Finnair", days: 360 },
    { iata: "IB", name: "Iberia", days: 360 },
    { iata: "JL", name: "Japan Airlines", days: 360 },
    { iata: "MH", name: "Malaysia Airlines", days: 355 },
    { iata: "RJ", name: "Royal Jordanian", days: 330 },
    { iata: "SL", name: "SriLankan Airlines", days: 330 },
  ],
  singapore: [
    { iata: "SQ", name: "Singapore Airlines", days: 355 },
    { iata: "AC", name: "Air Canada", days: 355 },
    { iata: "NH", name: "ANA", days: 355 },
    { iata: "LH", name: "Lufthansa", days: 360 },
    { iata: "TK", name: "Turkish Airlines", days: 355 },
    { iata: "UA", name: "United Airlines", days: 337 },
    { iata: "NZ", name: "Air New Zealand", days: 355 },
    { iata: "VA", name: "Virgin Australia", days: 330 },
  ],
  turkish: [
    { iata: "TK", name: "Turkish Airlines", days: 355 },
    { iata: "LH", name: "Lufthansa", days: 360 },
    { iata: "AC", name: "Air Canada", days: 355 },
    { iata: "UA", name: "United Airlines", days: 337 },
    { iata: "SQ", name: "Singapore Airlines", days: 355 },
    { iata: "NH", name: "ANA", days: 355 },
    { iata: "TG", name: "Thai Airways", days: 339 },
    { iata: "ET", name: "Ethiopian Airlines", days: 330 },
  ],
  lufthansa: [
    { iata: "LH", name: "Lufthansa", days: 360 },
    { iata: "LX", name: "SWISS", days: 360 },
    { iata: "OS", name: "Austrian", days: 360 },
    { iata: "SN", name: "Brussels Airlines", days: 360 },
    { iata: "AC", name: "Air Canada", days: 355 },
    { iata: "UA", name: "United Airlines", days: 337 },
    { iata: "SQ", name: "Singapore Airlines", days: 355 },
    { iata: "NH", name: "ANA", days: 355 },
    { iata: "TK", name: "Turkish Airlines", days: 355 },
    { iata: "TG", name: "Thai Airways", days: 339 },
  ],
  qantas: [
    { iata: "QF", name: "Qantas", days: 353 },
    { iata: "EK", name: "Emirates", days: 328 },
    { iata: "AA", name: "American Airlines", days: 331 },
    { iata: "BA", name: "British Airways", days: 354 },
    { iata: "CX", name: "Cathay Pacific", days: 360 },
    { iata: "JL", name: "Japan Airlines", days: 360 },
    { iata: "QR", name: "Qatar Airways", days: 361 },
    { iata: "AY", name: "Finnair", days: 360 },
    { iata: "SL", name: "SriLankan Airlines", days: 330 },
  ],
  finnair: [
    { iata: "AY", name: "Finnair", days: 360 },
    { iata: "AA", name: "American Airlines", days: 331 },
    { iata: "BA", name: "British Airways", days: 354 },
    { iata: "CX", name: "Cathay Pacific", days: 360 },
    { iata: "IB", name: "Iberia", days: 360 },
    { iata: "JL", name: "Japan Airlines", days: 360 },
    { iata: "QF", name: "Qantas", days: 353 },
    { iata: "QR", name: "Qatar Airways", days: 361 },
  ],
  eurobonus: [
    { iata: "SK", name: "SAS", days: 330 },
    { iata: "LH", name: "Lufthansa", days: 360 },
    { iata: "TK", name: "Turkish Airlines", days: 355 },
    { iata: "NH", name: "ANA", days: 355 },
    { iata: "TG", name: "Thai Airways", days: 339 },
  ],
  etihad: [
    { iata: "EY", name: "Etihad Airways", days: 330 },
    { iata: "AA", name: "American Airlines", days: 331 },
    { iata: "KE", name: "Korean Air", days: 361 },
    { iata: "SQ", name: "Singapore Airlines", days: 355 },
  ],
  virginatlantic: [
    { iata: "VS", name: "Virgin Atlantic", days: 331 },
    { iata: "AF", name: "Air France", days: 359 },
    { iata: "KL", name: "KLM", days: 359 },
    { iata: "DL", name: "Delta Air Lines", days: 331 },
    { iata: "ANA", name: "ANA", days: 355 },
  ],
  jetblue: [
    { iata: "B6", name: "JetBlue", days: 331 },
    { iata: "HA", name: "Hawaiian Airlines", days: 330 },
  ],
  spirit: [
    { iata: "NK", name: "Spirit Airlines", days: 140 },
  ],
  velocity: [
    { iata: "VA", name: "Virgin Australia", days: 330 },
    { iata: "SQ", name: "Singapore Airlines", days: 355 },
    { iata: "DL", name: "Delta Air Lines", days: 331 },
    { iata: "EY", name: "Etihad Airways", days: 330 },
  ],
  saudia: [
    { iata: "SV", name: "Saudia", days: 330 },
    { iata: "DL", name: "Delta Air Lines", days: 331 },
    { iata: "KE", name: "Korean Air", days: 361 },
    { iata: "AF", name: "Air France", days: 359 },
  ],
  smiles: [
    { iata: "G3", name: "GOL Airlines", days: 330 },
    { iata: "AF", name: "Air France", days: 359 },
    { iata: "KL", name: "KLM", days: 359 },
    { iata: "EK", name: "Emirates", days: 328 },
    { iata: "EY", name: "Etihad Airways", days: 330 },
    { iata: "TK", name: "Turkish Airlines", days: 355 },
  ],
  aeromexico: [
    { iata: "AM", name: "Aeromexico", days: 330 },
    { iata: "DL", name: "Delta Air Lines", days: 331 },
    { iata: "AF", name: "Air France", days: 359 },
    { iata: "KL", name: "KLM", days: 359 },
    { iata: "KE", name: "Korean Air", days: 361 },
  ],
  azul: [
    { iata: "AD", name: "Azul", days: 330 },
    { iata: "UA", name: "United Airlines", days: 337 },
  ],
  connectmiles: [
    { iata: "CM", name: "Copa Airlines", days: 330 },
    { iata: "UA", name: "United Airlines", days: 337 },
    { iata: "AC", name: "Air Canada", days: 355 },
  ],
  ethiopian: [
    { iata: "ET", name: "Ethiopian Airlines", days: 330 },
    { iata: "LH", name: "Lufthansa", days: 360 },
    { iata: "AC", name: "Air Canada", days: 355 },
    { iata: "UA", name: "United Airlines", days: 337 },
  ],
};

// Helper: compute the latest bookable date given days out from today
function daysOutDate(days) {
  const d = new Date();
  d.setDate(d.getDate() + days);
  return d.toISOString().split("T")[0];
}

// ─── Release Schedule Table ──────────────────────────────────────────────
function ReleaseScheduleTable() {
  const [selectedSource, setSelectedSource] = useState("");
  const [sortField, setSortField] = useState("days");
  const [sortDir, setSortDir] = useState("desc");

  const allAllianceColors = { "Star Alliance": "#c8a951", Oneworld: "#e74c3c", SkyTeam: "#3498db", Independent: "#6b7280" };

  // Build summary rows: merge PROGRAMS (seats.aero tracked) + transfer-only from RELEASE_SCHEDULE
  const summaryRows = React.useMemo(() => {
    const rows = [];
    // Seats.aero tracked programs
    Object.entries(PROGRAMS).forEach(([slug, prog]) => {
      const sched = RELEASE_SCHEDULE[slug];
      if (!sched) return;
      rows.push({
        slug, name: prog.name, alliance: prog.alliance, logo: prog.logo,
        darkLogo: prog.darkLogo, whiteBg: prog.whiteBg,
        days: sched.days, latestDate: daysOutDate(sched.days),
        notes: sched.notes || "", tracked: true,
        hasCarriers: !!PROGRAM_CARRIERS[slug],
      });
    });
    // Transfer-only programs from RELEASE_SCHEDULE
    Object.entries(RELEASE_SCHEDULE).forEach(([slug, sched]) => {
      if (PROGRAMS[slug]) return;
      if (!sched.transferOnly) return;
      rows.push({
        slug, name: sched.name || slug, alliance: sched.alliance || "Independent",
        logo: sched.logo || null, darkLogo: false, whiteBg: false,
        days: sched.days, latestDate: daysOutDate(sched.days),
        notes: sched.notes || "", tracked: false, hasCarriers: false,
      });
    });
    // Sort
    rows.sort((a, b) => {
      let cmp = 0;
      if (sortField === "days") cmp = a.days - b.days;
      else if (sortField === "name") cmp = a.name.localeCompare(b.name);
      else if (sortField === "alliance") cmp = (a.alliance || "").localeCompare(b.alliance || "");
      return sortDir === "desc" ? -cmp : cmp;
    });
    return rows;
  }, [sortField, sortDir]);

  // Compute "book earlier via" — for each program, find the alliance partner that releases further out
  const earlierPartners = React.useMemo(() => {
    const map = {};
    const allRows = [];
    // Build a full list of all programs with days + alliance
    Object.entries(PROGRAMS).forEach(([slug, prog]) => {
      const sched = RELEASE_SCHEDULE[slug];
      if (sched) allRows.push({ slug, name: prog.name, alliance: prog.alliance, days: sched.days });
    });
    Object.entries(RELEASE_SCHEDULE).forEach(([slug, sched]) => {
      if (PROGRAMS[slug]) return;
      if (sched.transferOnly) allRows.push({ slug, name: sched.name || slug, alliance: sched.alliance || "Independent", days: sched.days });
    });
    // For each program, find the best alliance partner with more days
    allRows.forEach(row => {
      if (!row.alliance || row.alliance === "Independent") { map[row.slug] = null; return; }
      const partners = allRows.filter(p => p.alliance === row.alliance && p.slug !== row.slug && p.days > row.days);
      if (partners.length === 0) { map[row.slug] = null; return; }
      partners.sort((a, b) => b.days - a.days);
      const best = partners[0];
      map[row.slug] = { name: best.name, slug: best.slug, days: best.days, extra: best.days - row.days };
    });
    return map;
  }, []);

  // Per-program carrier rows
  const carrierRows = React.useMemo(() => {
    if (!selectedSource || !PROGRAM_CARRIERS[selectedSource]) return [];
    const carriers = [...PROGRAM_CARRIERS[selectedSource]];
    carriers.sort((a, b) => sortDir === "desc" ? b.days - a.days : a.days - b.days);
    return carriers.map(c => ({ ...c, latestDate: daysOutDate(c.days) }));
  }, [selectedSource, sortDir]);

  const toggleSort = (field) => {
    if (sortField === field) setSortDir(d => d === "desc" ? "asc" : "desc");
    else { setSortField(field); setSortDir("desc"); }
  };

  const sortArrow = (field) => sortField === field ? (sortDir === "desc" ? " ↓" : " ↑") : "";

  return (
    <div style={{ marginTop: 40 }}>
      <div style={{ display: "flex", justifyContent: "space-between", alignItems: "flex-start", marginBottom: 20, flexWrap: "wrap", gap: 12 }}>
        <div>
          <h2 style={{ fontSize: 20, fontWeight: 700, marginBottom: 8 }}>📅 Award Release Dates</h2>
          <p style={{ color: theme.textMuted, fontSize: 14, margin: 0 }}>
            {selectedSource
              ? "Carriers bookable through this program and how far out each releases award seats."
              : "How far in advance each airline program releases award seats. Alliance partners that release earlier are shown so you can book further out."}
          </p>
        </div>
        <div style={{ display: "flex", alignItems: "center", gap: 8 }}>
          <select value={selectedSource} onChange={e => setSelectedSource(e.target.value)}
            style={{ background: theme.surface, color: theme.text, border: `1px solid ${theme.border}`, borderRadius: 8, padding: "8px 12px", fontSize: 13, fontFamily: "inherit", cursor: "pointer", minWidth: 200 }}>
            <option value="">All Programs</option>
            {Object.entries(PROGRAMS).filter(([s]) => PROGRAM_CARRIERS[s]).sort((a, b) => a[1].name.localeCompare(b[1].name)).map(([slug, prog]) => (
              <option key={slug} value={slug}>{prog.name}</option>
            ))}
          </select>
          {selectedSource && <button onClick={() => setSelectedSource("")} style={{ background: "transparent", border: `1px solid ${theme.border}`, borderRadius: 8, padding: "8px 12px", color: theme.textMuted, cursor: "pointer", fontSize: 12, fontFamily: "inherit" }}>← All Programs</button>}
        </div>
      </div>

      {/* ── Summary view: all programs ── */}
      {!selectedSource && (
        <div style={{ background: theme.surface, border: `1px solid ${theme.border}`, borderRadius: 16, overflow: "hidden" }}>
          <table style={{ width: "100%", borderCollapse: "collapse", fontSize: 13 }}>
            <thead>
              <tr style={{ borderBottom: `2px solid ${theme.border}` }}>
                {[
                  { label: "Program", field: "name", align: "left" },
                  { label: "Alliance", field: "alliance", align: "left" },
                  { label: "Days Out", field: "days", align: "center" },
                  { label: "Latest Bookable", field: null, align: "center" },
                  { label: "Book Earlier Via", field: null, align: "left" },
                ].map((h, i) => (
                  <th key={i} onClick={h.field ? () => toggleSort(h.field) : undefined}
                    style={{ padding: "12px 14px", textAlign: h.align, fontSize: 11, fontWeight: 700, color: theme.textDim, textTransform: "uppercase", letterSpacing: 1, background: theme.card, cursor: h.field ? "pointer" : "default", userSelect: "none" }}>
                    {h.label}{h.field ? sortArrow(h.field) : ""}
                  </th>
                ))}
              </tr>
            </thead>
            <tbody>
              {summaryRows.map((r, idx) => {
                const earlier = earlierPartners[r.slug];
                return (
                  <tr key={r.slug} style={{ borderBottom: `1px solid ${theme.border}`, background: idx % 2 === 0 ? "transparent" : theme.card + "40", cursor: r.hasCarriers ? "pointer" : "default", transition: "background 0.15s" }}
                    onClick={() => r.hasCarriers && setSelectedSource(r.slug)}
                    onMouseEnter={e => e.currentTarget.style.background = r.hasCarriers ? theme.accent + "10" : (idx % 2 === 0 ? "transparent" : theme.card + "40")}
                    onMouseLeave={e => e.currentTarget.style.background = idx % 2 === 0 ? "transparent" : theme.card + "40"}>
                    <td style={{ padding: "10px 14px" }}>
                      <div style={{ display: "flex", alignItems: "center", gap: 10 }}>
                        {r.logo && <img src={r.logo} alt={`${r.name || "Source"} logo`} style={{ width: 22, height: 22, borderRadius: 4, objectFit: "contain", flexShrink: 0, background: r.darkLogo ? "rgba(255,255,255,0.12)" : r.whiteBg ? "rgba(255,255,255,0.9)" : "transparent", padding: 1 }} onError={e => { e.target.style.display = "none"; }} />}
                        <span style={{ fontWeight: 600, color: theme.text }}>{r.name}</span>
                        {r.hasCarriers && <span style={{ fontSize: 10, color: theme.accent }}>→</span>}
                        {!r.tracked && <span style={{ fontSize: 9, color: theme.textDim, background: theme.border, padding: "1px 6px", borderRadius: 4, marginLeft: 4 }}>Transfer Only</span>}
                      </div>
                    </td>
                    <td style={{ padding: "10px 14px" }}>
                      <span style={{ fontSize: 11, fontWeight: 600, color: allAllianceColors[r.alliance] || theme.textDim, background: (allAllianceColors[r.alliance] || theme.textDim) + "18", padding: "2px 8px", borderRadius: 6 }}>{r.alliance || "—"}</span>
                    </td>
                    <td style={{ padding: "10px 14px", textAlign: "center" }}>
                      <span style={{ fontWeight: 700, fontSize: 15, color: r.days >= 355 ? theme.success : r.days >= 330 ? theme.accent : r.days >= 300 ? "#f59e0b" : "#ef4444" }}>
                        {r.days > 0 ? r.days : "—"}
                      </span>
                    </td>
                    <td style={{ padding: "10px 14px", textAlign: "center", fontSize: 13, fontWeight: 600, color: theme.text, fontFamily: "monospace" }}>
                      {r.days > 0 ? r.latestDate : "—"}
                    </td>
                    <td style={{ padding: "10px 14px" }}>
                      {earlier ? (
                        <div style={{ display: "flex", alignItems: "center", gap: 6, flexWrap: "wrap" }}>
                          <span style={{ fontSize: 12, fontWeight: 600, color: theme.success }}>{earlier.name}</span>
                          <span style={{ fontSize: 10, color: theme.success, background: theme.success + "15", padding: "1px 6px", borderRadius: 4, fontWeight: 700 }}>+{earlier.extra}d</span>
                        </div>
                      ) : (
                        <span style={{ fontSize: 11, color: theme.textDim }}>{r.alliance === "Independent" ? "—" : "Earliest in alliance"}</span>
                      )}
                    </td>
                  </tr>
                );
              })}
            </tbody>
          </table>
          <div style={{ padding: "12px 16px", borderTop: `1px solid ${theme.border}`, fontSize: 11, color: theme.textDim, display: "flex", justifyContent: "space-between", alignItems: "center", background: theme.card }}>
            <span>{summaryRows.length} programs · Click any tracked program for carrier breakdown</span>
            <span>Sources: seats.aero, roame.travel, upgradedpoints.com</span>
          </div>
        </div>
      )}

      {/* ── Per-program view: carriers bookable through this program ── */}
      {selectedSource && (
        <div style={{ background: theme.surface, border: `1px solid ${theme.border}`, borderRadius: 16, overflow: "hidden" }}>
          <div style={{ padding: "16px 20px", borderBottom: `1px solid ${theme.border}`, background: theme.card, display: "flex", alignItems: "center", gap: 12 }}>
            {PROGRAMS[selectedSource]?.logo && <img src={PROGRAMS[selectedSource].logo} alt={`${PROGRAMS[selectedSource]?.name || selectedSource} logo`} style={{ width: 28, height: 28, borderRadius: 6, objectFit: "contain", background: PROGRAMS[selectedSource]?.darkLogo ? "rgba(255,255,255,0.12)" : "transparent", padding: 2 }} onError={e => { e.target.style.display = "none"; }} />}
            <div>
              <div style={{ fontSize: 16, fontWeight: 700, color: theme.text }}>Carrier release schedule for {PROGRAMS[selectedSource]?.name || selectedSource}</div>
              <div style={{ fontSize: 12, color: theme.textDim }}>Carriers with award availability bookable via this program · Sorted by {sortDir === "desc" ? "highest" : "lowest"} days out</div>
            </div>
          </div>
          {carrierRows.length > 0 ? (
            <table style={{ width: "100%", borderCollapse: "collapse", fontSize: 13 }}>
              <thead>
                <tr style={{ borderBottom: `2px solid ${theme.border}` }}>
                  {["Carrier", "Days Out", "Latest Bookable"].map((h, i) => (
                    <th key={i} onClick={i === 1 ? () => setSortDir(d => d === "desc" ? "asc" : "desc") : undefined}
                      style={{ padding: "12px 14px", textAlign: i >= 1 ? "center" : "left", fontSize: 11, fontWeight: 700, color: theme.textDim, textTransform: "uppercase", letterSpacing: 1, background: theme.card, cursor: i === 1 ? "pointer" : "default" }}>
                      {h}{i === 1 ? (sortDir === "desc" ? " ↓" : " ↑") : ""}
                    </th>
                  ))}
                </tr>
              </thead>
              <tbody>
                {carrierRows.map((c, idx) => (
                  <tr key={c.iata} style={{ borderBottom: `1px solid ${theme.border}`, background: idx % 2 === 0 ? "transparent" : theme.card + "40", transition: "background 0.15s" }}
                    onMouseEnter={e => e.currentTarget.style.background = theme.accent + "10"}
                    onMouseLeave={e => e.currentTarget.style.background = idx % 2 === 0 ? "transparent" : theme.card + "40"}>
                    <td style={{ padding: "10px 14px" }}>
                      <div style={{ display: "flex", alignItems: "center", gap: 10 }}>
                        <span style={{ background: theme.accent + "20", color: theme.accent, fontWeight: 700, fontSize: 11, padding: "2px 6px", borderRadius: 4, fontFamily: "monospace", minWidth: 28, textAlign: "center" }}>{c.iata}</span>
                        <span style={{ fontWeight: 600, color: theme.text }}>{c.name}</span>
                      </div>
                    </td>
                    <td style={{ padding: "10px 14px", textAlign: "center" }}>
                      <span style={{ fontWeight: 700, fontSize: 15, color: c.days >= 355 ? theme.success : c.days >= 330 ? theme.accent : c.days >= 300 ? "#f59e0b" : "#ef4444" }}>{c.days} days</span>
                    </td>
                    <td style={{ padding: "10px 14px", textAlign: "center", fontSize: 13, fontWeight: 600, color: theme.text, fontFamily: "monospace" }}>{c.latestDate}</td>
                  </tr>
                ))}
              </tbody>
            </table>
          ) : (
            <div style={{ padding: 30, textAlign: "center", color: theme.textDim }}>No carrier breakdown available for this program yet.</div>
          )}
          <div style={{ padding: "12px 16px", borderTop: `1px solid ${theme.border}`, fontSize: 11, color: theme.textDim, display: "flex", justifyContent: "space-between", alignItems: "center", background: theme.card }}>
            <span>{carrierRows.length} carriers · Dates computed from today</span>
            <span>Sources: seats.aero, roame.travel</span>
          </div>
        </div>
      )}
    </div>
  );
}

// ═══════════════════════════════════════════════════════════════════════════
// ─── Main App ─────────────────────────────────────────────────────────────
function AwardFlightDashboard() {
  const [tab, setTab] = useState("search");
  const todayStr = new Date().toISOString().split("T")[0];
  const [searchParams, setSearchParams] = useState({ origin: "", destination: "", dateFrom: "", dateTo: "", returnDateFrom: "", returnDateTo: "", cabin: "J", directOnly: false, includeConnections: true, maxStops: 1, minSeats: "", airlines: "", source: "", maxMiles: "", tripType: "one_way", passengers: 1, selectedBanks: [], selectedAirlines: [] });
  const [results, setResults] = useState([]);
  const [connectionResults, setConnectionResults] = useState([]);
  const [connectionHubs, setConnectionHubs] = useState([]);
  const [returnResults, setReturnResults] = useState([]);
  const [returnConnectionResults, setReturnConnectionResults] = useState([]);
  const [returnConnectionHubs, setReturnConnectionHubs] = useState([]);
  const [airportMap, setAirportMap] = useState({});
  const [loading, setLoading] = useState(false);
  const [searched, setSearched] = useState(false);
  const [expandedCard, setExpandedCard] = useState(null); // "direct-0", "conn-1", "conn-1-leg-0", etc.
  const [tripExpandedCard, setTripExpandedCard] = useState(null); // availability_id being shown in TripDetailsPanel
  const [flightOptionsCache, setFlightOptionsCache] = useState({}); // key: "LAX|NRT|2026-04-15" => data
  const [loadingOptions, setLoadingOptions] = useState(null); // card key currently loading
  const [resultSort, setResultSort] = useState("miles"); // "miles" | "date"
  const [stats, setStats] = useState(null);
  const [transferData, setTransferData] = useState(null);
  const [adminHistory, setAdminHistory] = useState(null);
  const [adminBonuses, setAdminBonuses] = useState([]);
  const [adminAlerts, setAdminAlerts] = useState([]);
  const [adminRuns, setAdminRuns] = useState([]);
  const [adminHighScores, setAdminHighScores] = useState({});
  const [adminCards, setAdminCards] = useState({ chase_ur: true, amex_mr: true, capital_one: true, bilt: true, citi_typ: false, wells_fargo: false, rove: false });
  const [programBalances, setProgramBalances] = useState({});
  const [bankBalances, setBankBalances] = useState({});
  const [transferBonuses, setTransferBonuses] = useState([]);

  // ── Theme State ──
  const [themeMode, setThemeMode] = useState(() => localStorage.getItem("theme_mode") || "dark");
  const [, forceRender] = useState(0);
  const changeTheme = useCallback((mode) => {
    setThemeMode(mode);
    applyTheme(mode);
    localStorage.setItem("theme_mode", mode);
    forceRender(n => n + 1); // force re-render so all inline styles pick up new theme values
    window.dispatchEvent(new CustomEvent("theme-change"));
  }, []);

  // ── Auth State ──
  const [authUser, setAuthUser] = useState(null);  // null = not logged in
  const [authToken, setAuthToken] = useState(localStorage.getItem("auth_token") || "");
  const [showAuthModal, setShowAuthModal] = useState(false);
  const [resetToken, setResetToken] = useState("");

  // Check for password reset token in URL
  useEffect(() => {
    const params = new URLSearchParams(window.location.search);
    const token = params.get("reset_token");
    if (token) {
      setResetToken(token);
      setShowAuthModal(true);
      // Clean URL
      window.history.replaceState({}, document.title, window.location.pathname);
    }
  }, []);
  const [showHelpGuide, setShowHelpGuide] = useState(false);
  const [authLoading, setAuthLoading] = useState(true);
  const [viewAsRole, setViewAsRole] = useState(null); // null = normal view, "user"/"premium"/"admin" = preview mode

  // Expose auth token to other JSX files (ingestion_tab, email_preview_tab)
  useEffect(() => { window.authToken = authToken; }, [authToken]);

  // ── Analytics Tracking (fire-and-forget) ──
  const trackEvent = useCallback((eventType, properties = {}) => {
    try {
      fetch(`${API}/api/analytics/event`, {
        method: "POST",
        headers: { "Content-Type": "application/json", ...(authToken ? { Authorization: `Bearer ${authToken}` } : {}) },
        body: JSON.stringify({ event_type: eventType, properties })
      }).catch(() => {});
    } catch (e) {}
  }, [authToken]);

  // Effective role for UI rendering (actual role or the "view as" override)
  const actualRole = authUser?.role || null;
  const isRealAdmin = actualRole === "admin" || actualRole === "master_admin";
  const effectiveRole = viewAsRole || actualRole;
  const isEffectiveAdmin = effectiveRole === "admin" || effectiveRole === "master_admin";

  // Check for existing token on mount
  useEffect(() => {
    const token = localStorage.getItem("auth_token");
    if (token) {
      fetch(`${API}/api/auth/me`, { headers: { Authorization: `Bearer ${token}` } })
        .then(r => { if (!r.ok) throw new Error(); return r.json(); })
        .then(data => { setAuthUser(data.user); setAuthToken(token); })
        .catch(() => { localStorage.removeItem("auth_token"); setAuthToken(""); })
        .finally(() => setAuthLoading(false));
    } else {
      setAuthLoading(false);
    }
  }, []);

  // Helper to get auth headers for API calls
  const authHeaders = authToken ? { Authorization: `Bearer ${authToken}` } : {};

  const handleAuth = (user, token) => {
    setAuthUser(user);
    setAuthToken(token);
  };

  const handleLogout = () => {
    localStorage.removeItem("auth_token");
    setAuthUser(null);
    setAuthToken("");
    setTab("search");
  };

  // Apply saved search defaults from user preferences to the search form
  const applySearchDefaults = (prefs) => {
    setSearchParams(p => {
      const updates = { ...p };
      // Home airports → origin
      if (prefs.home_airports?.length > 0) updates.origin = prefs.home_airports.join(",");
      // Cabin preference → pick highest enabled cabin (F > J > W > Y)
      if (prefs.cabin_preferences) {
        const c = prefs.cabin_preferences;
        if (c.first) updates.cabin = "F";
        else if (c.business) updates.cabin = "J";
        else if (c.premium_economy) updates.cabin = "W";
        else if (c.economy) updates.cabin = "Y";
      }
      // Trip type preference → tripType (skip if "both" — leave user's current choice)
      if (prefs.trip_type_preference && prefs.trip_type_preference !== "both") {
        updates.tripType = prefs.trip_type_preference;
      }
      // Max stops → maxStops
      if (prefs.max_stops !== undefined && prefs.max_stops !== null) {
        updates.maxStops = prefs.max_stops;
      }
      // Max taxes → maxTaxes (add to search params for filtering)
      if (prefs.max_taxes !== undefined) {
        updates.maxTaxes = prefs.max_taxes;
      }
      return updates;
    });
  };

  // Load user preferences into search defaults when logged in
  useEffect(() => {
    if (!authToken) return;
    fetch(`${API}/api/user/preferences`, { headers: { Authorization: `Bearer ${authToken}` } })
      .then(r => r.json()).then(prefs => {
        applySearchDefaults(prefs);
        if (prefs.program_balances) setProgramBalances(prefs.program_balances);
        if (prefs.bank_balances) setBankBalances(prefs.bank_balances);
        // Sync theme from server preference
        if (prefs.theme_mode && prefs.theme_mode !== themeMode) changeTheme(prefs.theme_mode);
      }).catch(e => console.warn("Load preferences:", e));
  }, [authToken]);

  // Clear balances when logged out — balances are per-user only
  useEffect(() => {
    if (!authToken) {
      setProgramBalances({});
      setBankBalances({});
    }
  }, [authToken]);

  const saveProgramBalance = (slug, val) => {
    if (!authToken) return; // Must be logged in to save balances
    const updated = { ...programBalances, [slug]: val };
    setProgramBalances(updated);
    fetch(`${API}/api/user/preferences`, { method: "PUT", headers: { "Content-Type": "application/json", Authorization: `Bearer ${authToken}` }, body: JSON.stringify({ program_balances: updated }) }).catch(window.silentErr("Save program balance"));
  };

  const saveBankBalance = (id, val) => {
    if (!authToken) return; // Must be logged in to save balances
    const updated = { ...bankBalances, [id]: val };
    setBankBalances(updated);
    fetch(`${API}/api/user/preferences`, { method: "PUT", headers: { "Content-Type": "application/json", Authorization: `Bearer ${authToken}` }, body: JSON.stringify({ bank_balances: updated }) }).catch(window.silentErr("Save bank balance"));
  };

  useEffect(() => { fetch(`${API}/api/airports`).then(r => r.json()).then(data => { const m = {}; (data.airports || []).forEach(a => { m[a.iata_code] = a.city || a.name; }); setAirportMap(m); }).catch(window.silentErr("Load airports")); }, []);
  useEffect(() => { fetch(`${API}/api/stats`).then(r => r.json()).then(setStats).catch(() => setStats(null)); }, []);
  useEffect(() => { fetch(`${API}/api/transfer-partners`).then(r => r.json()).then(setTransferData).catch(window.silentErr("Load transfer partners")); }, []);
  useEffect(() => { fetch(`${API}/api/transfer-bonuses`).then(r => r.json()).then(d => setTransferBonuses(Array.isArray(d) ? d : [])).catch(window.silentErr("Load transfer bonuses")); }, []);

  useEffect(() => {
    if (tab !== "admin" || !authToken) return;
    const ah = { headers: { Authorization: `Bearer ${authToken}` } };
    fetch(`${API}/api/admin/history`, ah).then(r => r.json()).then(d => { setAdminHistory(d.last_run); setAdminRuns(d.runs || []); }).catch(window.silentErr("Load admin history"));
    fetch(`${API}/api/admin/bonuses`, ah).then(r => r.json()).then(d => setAdminBonuses(d.bonuses || [])).catch(window.silentErr("Load bonuses"));
    fetch(`${API}/api/admin/alerts`, ah).then(r => r.json()).then(d => setAdminAlerts(d.alerts || [])).catch(window.silentErr("Load alerts"));
    fetch(`${API}/api/admin/cards`, ah).then(r => r.json()).then(d => d.cards && setAdminCards(d.cards)).catch(window.silentErr("Load cards"));
    fetch(`${API}/api/admin/high-scores`, ah).then(r => r.json()).then(d => setAdminHighScores(d.high_scores || {})).catch(window.silentErr("Load high scores"));
  }, [tab, authToken]);

  const updateParam = (key, val) => setSearchParams(p => ({ ...p, [key]: val }));

  const doSearch = useCallback(async () => {
    const effectiveSource = (searchParams.selectedAirlines && searchParams.selectedAirlines.length > 0) ? searchParams.selectedAirlines.join(",") : searchParams.source;
    setLoading(true); setSearched(true); setConnectionResults([]); setConnectionHubs([]);
    setReturnResults([]); setReturnConnectionResults([]); setReturnConnectionHubs([]);
    setExpandedCard(null); setTripExpandedCard(null); setFlightOptionsCache({}); setLoadingOptions(null);

    const applyFilters = (directArr, connsArr) => {
      let df = directArr, cf = connsArr;
      if (searchParams.selectedAirlines?.length > 0) {
        df = df.filter(r => searchParams.selectedAirlines.includes(r.source));
        cf = cf.filter(c => c.legs.every(l => searchParams.selectedAirlines.includes(l.source)));
      }
      if (searchParams.selectedBanks?.length > 0 && transferData) {
        const bankPrograms = new Set();
        searchParams.selectedBanks.forEach(bankKey => {
          const bankInfo = transferData[bankKey];
          if (bankInfo?.partners) bankInfo.partners.filter(p => p.type === "airline").forEach(p => {
            Object.entries(PROGRAMS).forEach(([progKey, prog]) => { if (prog.name && p.name && (prog.name.toLowerCase().includes(p.name.toLowerCase()) || p.name.toLowerCase().includes(prog.name.toLowerCase()))) bankPrograms.add(progKey); });
          });
        });
        if (bankPrograms.size > 0) { df = df.filter(r => bankPrograms.has(r.source)); cf = cf.filter(c => c.legs.every(l => bankPrograms.has(l.source))); }
      }
      return { direct: df, conns: cf };
    };

    try {
      const p = searchParams;
      const maxStops = p.maxStops != null ? p.maxStops : (p.directOnly ? 0 : p.includeConnections ? 1 : 0);
      const useConnections = maxStops >= 1;

      const doOneLeg = async (orig, dst, df, dt) => {
        if (useConnections) {
          const params = new URLSearchParams();
          if (orig) params.set("origin", orig.toUpperCase());
          if (dst) params.set("destination", dst.toUpperCase());
          if (df) params.set("date_from", df);
          if (dt) params.set("date_to", dt);
          params.set("cabin", p.cabin);
          params.set("max_stops", String(maxStops));
          if (p.maxMiles) params.set("max_miles", p.maxMiles);
          params.set("sort", "miles");
          if (effectiveSource) params.set("source", effectiveSource);
          const res = await fetch(`${API}/api/search-connections?${params}`);
          const data = await res.json();
          const { direct, conns } = applyFilters(data.direct_results || [], data.connections || []);
          return { direct, conns, hubs: data.hubs || [] };
        } else {
          const params = new URLSearchParams();
          if (orig) params.set("origin", orig.toUpperCase());
          if (dst) params.set("destination", dst.toUpperCase());
          if (df) params.set("date_from", df);
          if (dt) params.set("date_to", dt);
          params.set("cabin", p.cabin);
          params.set("direct_only", "true");
          if (p.maxMiles) params.set("max_miles", p.maxMiles);
          params.set("sort", "miles");
          const url = `${API}/api/search?${params}${p.minSeats ? `&min_seats=${p.minSeats}` : (p.passengers > 1) ? `&min_seats=${p.passengers}` : ""}${p.airlines ? `&airlines=${p.airlines}` : ""}${p.source ? `&source=${effectiveSource}` : ""}`;
          const res = await fetch(url);
          const data = await res.json();
          const { direct } = applyFilters(data.results || [], []);
          return { direct, conns: [], hubs: [] };
        }
      };

      // Outbound
      const out = await doOneLeg(p.origin, p.destination, p.dateFrom, p.dateTo);
      setResults(out.direct);
      setConnectionResults(out.conns);
      setConnectionHubs(out.hubs);

      // Return (if round trip)
      if (p.tripType === "round_trip") {
        const rdf = p.returnDateFrom || p.dateTo || "";
        const rdt = p.returnDateTo || "";
        const ret = await doOneLeg(p.destination, p.origin, rdf, rdt);
        setReturnResults(ret.direct);
        setReturnConnectionResults(ret.conns);
        setReturnConnectionHubs(ret.hubs);
      }

      // ── Analytics: track search ──
      trackEvent('search', { origin_cities: (p.origin || '').toUpperCase(), destination_cities: (p.destination || '').toUpperCase(), date_from: p.dateFrom, date_to: p.dateTo, cabin: p.cabin, max_stops: maxStops, trip_type: p.tripType, result_count: out.direct.length + out.conns.length });
    } catch (e) { setResults([]); setConnectionResults([]); }
    setLoading(false);
  }, [searchParams, transferData]);

  // Auto-re-search when cabin, stops, or trip type change while results are showing
  const prevFilterRef = React.useRef(null);
  useEffect(() => {
    if (!searched || loading) return;
    const key = `${searchParams.cabin}|${searchParams.maxStops}|${searchParams.tripType}`;
    if (prevFilterRef.current === null) { prevFilterRef.current = key; return; }
    if (prevFilterRef.current !== key) {
      prevFilterRef.current = key;
      doSearch();
    }
  }, [searchParams.cabin, searchParams.maxStops, searchParams.tripType, searched, loading, doSearch]);

  const adminFetchOpts = (method, body) => ({ method: method || "GET", headers: { "Content-Type": "application/json", ...(authToken ? { Authorization: `Bearer ${authToken}` } : {}) }, ...(body ? { body: JSON.stringify(body) } : {}) });
  const handleCardToggle = (id) => { const updated = { ...adminCards, [id]: !adminCards[id] }; setAdminCards(updated); fetch(`${API}/api/admin/cards`, adminFetchOpts("POST", { cards: updated })).catch(window.silentErr("Save card toggle")); };
  const handleAlertSave = (form, editingId) => { if (editingId === "new") { trackEvent('alert_create', { origin: form.origin, destination: form.destination, cabin: form.cabin }); } const method = editingId === "new" ? "POST" : "PUT"; const url = editingId === "new" ? `${API}/api/admin/alerts` : `${API}/api/admin/alerts/${editingId}`; fetch(url, adminFetchOpts(method, form)).then(r => r.json()).then(() => { fetch(`${API}/api/admin/alerts`, { headers: authHeaders }).then(r => r.json()).then(d => setAdminAlerts(d.alerts || [])); }).catch(window.silentErr("Save alert")); };
  const handleAlertDelete = (id) => { fetch(`${API}/api/admin/alerts/${id}`, adminFetchOpts("DELETE")).then(() => setAdminAlerts(prev => prev.filter(a => a.id !== id))).catch(window.silentErr("Delete alert")); };
  const handleAlertToggle = (id) => { fetch(`${API}/api/admin/alerts/${id}/toggle`, adminFetchOpts("POST")).then(r => r.json()).then(() => { fetch(`${API}/api/admin/alerts`, { headers: authHeaders }).then(r => r.json()).then(d => setAdminAlerts(d.alerts || [])); }).catch(window.silentErr("Toggle alert")); };
  const handleRefreshBonuses = () => { fetch(`${API}/api/admin/bonuses/refresh`, adminFetchOpts("POST")).then(r => r.json()).then(d => setAdminBonuses(d.bonuses || [])).catch(window.silentErr("Refresh bonuses")); };

  return (
    <div style={{ minHeight: "100vh", background: theme.bg, color: theme.text, fontFamily: "'DM Sans', 'Helvetica Neue', sans-serif" }}>
      <link href="https://fonts.googleapis.com/css2?family=DM+Sans:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet" />
      <style dangerouslySetInnerHTML={{ __html: `
        @media (max-width: 768px) {
          /* --- Header --- */
          .header-top { flex-wrap: wrap !important; gap: 8px !important; }
          .header-brand { min-width: 0; }
          .header-brand-text .brand-title { font-size: 15px !important; }
          .header-brand-text .brand-subtitle { font-size: 10px !important; }
          .header-brand img { width: 34px !important; height: 34px !important; }
          .header-right { gap: 8px !important; }
          .header-stats { display: none !important; }
          .header-viewas { display: none !important; }
          .header-username { display: none !important; }
          .header-role { display: none !important; }
          .header-avatar { width: 28px !important; height: 28px !important; font-size: 12px !important; }
          .header-signout { padding: 3px 8px !important; font-size: 10px !important; }
          .header-signin { padding: 6px 12px !important; font-size: 12px !important; }
          .header-help { width: 26px !important; height: 26px !important; font-size: 12px !important; }
          .header-tabs { overflow-x: auto; -webkit-overflow-scrolling: touch; scrollbar-width: none; }
          .header-tabs::-webkit-scrollbar { display: none; }
          .header-tabs button { padding: 8px 12px !important; font-size: 12px !important; white-space: nowrap; }
          /* --- Main content padding --- */
          .main-content { padding: 16px 12px 40px !important; }
          .app-header { padding: 12px 12px 0 !important; }
          /* --- Search form --- */
          .search-route-grid { grid-template-columns: 1fr auto 1fr !important; gap: 8px !important; }
          .search-options-grid { grid-template-columns: 1fr 1fr !important; gap: 8px !important; }
          .search-dates-grid { grid-template-columns: 1fr 1fr !important; gap: 8px !important; }
          .search-cabin-row { flex-wrap: wrap !important; gap: 8px !important; }
          .search-cabin-row > div:first-child { display: flex; flex-wrap: wrap; gap: 4px; }
          .search-card { padding: 16px !important; }
          /* --- Results / cards --- */
          .results-grid { grid-template-columns: 1fr !important; }
          /* --- Dashboard stats --- */
          .dash-stats-grid { grid-template-columns: repeat(2, 1fr) !important; gap: 8px !important; }
          /* --- Dashboard 4-col grids --- */
          .dash-4col { grid-template-columns: repeat(2, 1fr) !important; gap: 8px !important; }
          /* --- Profile grids --- */
          .profile-programs-grid { grid-template-columns: 1fr !important; }
          .profile-banks-grid { grid-template-columns: 1fr !important; }
          /* --- Help guide modal --- */
          .help-guide-modal { width: 95% !important; max-width: none !important; flex-direction: column !important; }
          .help-guide-sidebar { width: 100% !important; min-width: 0 !important; flex-direction: row !important; overflow-x: auto; padding: 12px !important; gap: 4px !important; border-right: none !important; border-bottom: 1px solid rgba(255,255,255,0.1); }
          .help-guide-sidebar button { white-space: nowrap; font-size: 11px !important; padding: 6px 10px !important; }
          .help-guide-content { padding: 16px !important; max-height: 60vh !important; }
          /* --- Admin forms --- */
          .admin-3col { grid-template-columns: 1fr !important; gap: 12px !important; }
          .admin-4col { grid-template-columns: 1fr 1fr !important; gap: 8px !important; }
          /* --- Alert form --- */
          .alert-form-grid { grid-template-columns: 1fr 1fr !important; gap: 8px !important; }
          /* --- Calendar --- */
          .cabin-calendar-grid { grid-template-columns: repeat(7, 1fr) !important; font-size: 10px !important; }
          .cabin-type-grid { grid-template-columns: repeat(2, 1fr) !important; gap: 6px !important; }
        }
        @media (max-width: 480px) {
          .header-brand-text .brand-title { font-size: 13px !important; }
          .header-brand-text .brand-subtitle { display: none !important; }
          .header-brand img { width: 30px !important; height: 30px !important; }
          .header-tabs button { padding: 6px 8px !important; font-size: 11px !important; }
          .search-options-grid { grid-template-columns: 1fr !important; }
          .search-dates-grid { grid-template-columns: 1fr !important; }
          .dash-stats-grid { grid-template-columns: 1fr !important; }
          .dash-4col { grid-template-columns: 1fr !important; }
        }
      `}} />

      {/* Header */}
      <header className="app-header" style={{ borderBottom: `1px solid ${theme.border}`, background: `linear-gradient(180deg, ${theme.surface} 0%, ${theme.bg} 100%)`, padding: "20px 24px 0" }}>
        <div style={{ maxWidth: 1100, margin: "0 auto" }}>
          <div className="header-top" style={{ display: "flex", alignItems: "center", justifyContent: "space-between", marginBottom: 16 }}>
            <div className="header-brand" style={{ display: "flex", alignItems: "center", gap: 12 }}>
              <img src="/static/digitalwinglogo_transparent.png" alt="Award Flight DB" style={{ width: 42, height: 42, objectFit: "contain" }} />
              <div className="header-brand-text">
                <h1 className="brand-title" style={{ fontSize: 18, fontWeight: 700, letterSpacing: -0.5, margin: 0 }}>Award Flight DB</h1>
                <div className="brand-subtitle" style={{ fontSize: 11, color: theme.textDim }}>25 programs · 7 banks</div>
              </div>
            </div>
            <div className="header-right" style={{ display: "flex", alignItems: "center", gap: 12 }}>
              <button className="header-help" onClick={() => setShowHelpGuide(true)} title="Help & Guide" aria-label="Open help guide" style={{ width: 30, height: 30, borderRadius: "50%", border: `1px solid ${theme.border}`, background: "transparent", color: theme.textDim, fontSize: 14, fontWeight: 700, cursor: "pointer", display: "flex", alignItems: "center", justifyContent: "center", fontFamily: "inherit", transition: "all 0.2s" }} onMouseEnter={e => { e.target.style.borderColor = theme.accent; e.target.style.color = theme.accent; }} onMouseLeave={e => { e.target.style.borderColor = theme.border; e.target.style.color = theme.textDim; }}>?</button>
              {stats && <div className="header-stats" style={{ fontSize: 12, color: theme.textDim, textAlign: "right" }}><span style={{ color: theme.success }}>●</span> {stats.total_availability?.toLocaleString() || "—"} flights tracked</div>}
              {authUser ? (
                <div style={{ display: "flex", alignItems: "center", gap: 10 }}>
                  {/* View As dropdown — only for real admins */}
                  {isRealAdmin && (
                    <div className="header-viewas" style={{ display: "flex", alignItems: "center", gap: 4 }}>
                      <span style={{ fontSize: 10, color: theme.textDim }}>View as:</span>
                      <select value={viewAsRole || ""} onChange={e => { setViewAsRole(e.target.value || null); if (e.target.value && !["admin","master_admin"].includes(e.target.value) && ["admin","email","ingestion"].includes(tab)) setTab("search"); }} style={{ background: viewAsRole ? theme.warning + "22" : theme.bg, color: viewAsRole ? theme.warning : theme.textMuted, border: `1px solid ${viewAsRole ? theme.warning + "66" : theme.border}`, borderRadius: 6, padding: "3px 6px", fontSize: 11, fontFamily: "inherit", cursor: "pointer" }}>
                        <option value="">Myself ({actualRole})</option>
                        <option value="admin">Admin</option>
                        <option value="premium">Premium</option>
                        <option value="user">User</option>
                      </select>
                    </div>
                  )}
                  <div className="header-avatar" style={{ width: 32, height: 32, borderRadius: "50%", background: `linear-gradient(135deg, ${theme.accent}, #60a5fa)`, display: "flex", alignItems: "center", justifyContent: "center", fontSize: 14, fontWeight: 700, color: "#fff" }}>{(authUser.display_name || authUser.email)[0].toUpperCase()}</div>
                  <div style={{ fontSize: 13 }}>
                    <div className="header-username" style={{ fontWeight: 600, lineHeight: 1.2 }}>{authUser.display_name || authUser.email.split("@")[0]}</div>
                    <div className="header-role" style={{ fontSize: 10, color: theme.textDim }}>
                      {viewAsRole ? <span style={{ color: theme.warning }}>Viewing as {viewAsRole.toUpperCase()}</span> : authUser.role.replace("_", " ").toUpperCase()}
                    </div>
                  </div>
                  <button className="header-signout" onClick={handleLogout} style={{ background: "none", border: `1px solid ${theme.border}`, borderRadius: 6, padding: "4px 10px", color: theme.textDim, fontSize: 11, cursor: "pointer", fontFamily: "inherit" }}>Sign Out</button>
                </div>
              ) : (
                <button className="header-signin" onClick={() => setShowAuthModal(true)} style={{ background: theme.accent, color: "#fff", border: "none", borderRadius: 8, padding: "8px 16px", fontSize: 13, fontWeight: 600, cursor: "pointer", fontFamily: "inherit" }}>Sign In</button>
              )}
            </div>
          </div>
          {/* View-As banner */}
          {viewAsRole && (
            <div style={{ background: theme.warning + "15", border: `1px solid ${theme.warning}33`, borderRadius: 8, padding: "6px 16px", marginBottom: 8, display: "flex", justifyContent: "space-between", alignItems: "center" }}>
              <span style={{ fontSize: 12, color: theme.warning }}>Preview mode — viewing as <strong>{viewAsRole.toUpperCase()}</strong> user</span>
              <button onClick={() => setViewAsRole(null)} style={{ background: theme.warning, color: "#000", border: "none", borderRadius: 6, padding: "3px 10px", fontSize: 11, fontWeight: 600, cursor: "pointer", fontFamily: "inherit" }}>Exit Preview</button>
            </div>
          )}
          <nav aria-label="Main navigation"><div className="header-tabs" role="tablist" style={{ display: "flex", gap: 0 }}>
            {(() => {
              const allTabs = [
                { id: "search", label: "Search", icon: "🔍", show: true },
                { id: "programs", label: "Programs", icon: "🏛", show: true },
                { id: "dashboard", label: "Dashboard", icon: "📊", show: true },
                { id: "profile", label: "Profile", icon: "👤", show: !!authUser },
                { id: "admin", label: "Admin", icon: "⚙️", show: isEffectiveAdmin },
                { id: "email", label: "Alerts", icon: "📧", show: !!authUser },
                { id: "ingestion", label: "Ingestion", icon: "🚀", show: isEffectiveAdmin },
              ];
              return allTabs.filter(t => t.show).map(t => (
                <button key={t.id} role="tab" aria-selected={tab === t.id} aria-controls={`tabpanel-${t.id}`} onClick={() => { trackEvent('tab_switch', { from_tab: tab, to_tab: t.id }); setTab(t.id); }} style={{ background: "transparent", color: tab === t.id ? theme.accent : theme.textDim, border: "none", borderBottom: `2px solid ${tab === t.id ? theme.accent : "transparent"}`, padding: "10px 20px", fontSize: 14, fontWeight: 600, cursor: "pointer", fontFamily: "inherit", transition: "all 0.2s" }}>{t.icon} {t.label}</button>
              ));
            })()}
          </div></nav>
        </div>
      </header>

      {/* Content */}
      <main id="main-content" className="main-content" role="tabpanel" aria-labelledby={`tab-${tab}`} style={{ maxWidth: 1100, margin: "0 auto", padding: "24px 24px 60px" }}>

        {/* SEARCH TAB */}
        {tab === "search" && (
          <div>
            <div className="search-card" style={{ background: theme.surface, border: `1px solid ${theme.border}`, borderRadius: 16, padding: 24, marginBottom: 24 }}>
              <div className="search-route-grid" style={{ display: "grid", gridTemplateColumns: "minmax(0, 1fr) auto minmax(0, 1fr)", gap: 16, marginBottom: 16, position: "relative", zIndex: Z_SEARCH_FORM }}>
                <div><label style={{ fontSize: 12, color: theme.textDim, fontWeight: 600, textTransform: "uppercase", letterSpacing: 1, display: "block", marginBottom: 6 }}>From</label><AutocompleteInput value={searchParams.origin} onChange={(v) => updateParam("origin", v)} placeholder="City, country, region, or IATA code" theme={theme} /></div>
                <button type="button" aria-label="Swap origin and destination" onClick={() => { const f = searchParams.origin; const d = searchParams.destination; updateParam("origin", d); updateParam("destination", f); }} style={{ alignSelf: "flex-end", marginBottom: 8, background: "none", border: "1px solid " + theme.border, borderRadius: 8, padding: "6px 10px", color: theme.textDim, cursor: "pointer", fontSize: 18 }} title="Swap">⇄</button>
                <div><label style={{ fontSize: 12, color: theme.textDim, fontWeight: 600, textTransform: "uppercase", letterSpacing: 1, display: "block", marginBottom: 6 }}>To</label><AutocompleteInput value={searchParams.destination} onChange={(v) => updateParam("destination", v)} placeholder="City, country, region, or IATA code" theme={theme} /></div>
              </div>
              <div className="search-options-grid" style={{ display: "grid", gridTemplateColumns: "1fr 1fr 1fr 1fr", gap: 12, marginBottom: 16, position: "relative", zIndex: 1 }}>
                <div><label style={{ fontSize: 12, color: theme.textDim, fontWeight: 600, textTransform: "uppercase", letterSpacing: 1, display: "block", marginBottom: 6 }}>Trip Type</label>
                  <div style={{ display: "flex", gap: 4 }}>{["one_way", "round_trip", "multi_city"].map(t => <button key={t} onClick={() => { trackEvent('trip_type_change', { trip_type: t }); updateParam("tripType", t); }} style={{ flex: 1, padding: "8px 4px", borderRadius: 6, border: searchParams.tripType === t ? "1px solid " + theme.accent : "1px solid " + theme.border, background: searchParams.tripType === t ? theme.accent : "transparent", color: searchParams.tripType === t ? "#fff" : theme.textMuted, fontSize: 11, cursor: "pointer", fontWeight: searchParams.tripType === t ? 700 : 500 }}>{t === "one_way" ? "One Way" : t === "round_trip" ? "Round Trip" : "Multi City"}</button>)}</div>
                </div>
                <div><label style={{ fontSize: 12, color: theme.textDim, fontWeight: 600, textTransform: "uppercase", letterSpacing: 1, display: "block", marginBottom: 6 }}>Passengers</label>
                  <div style={{ display: "flex", alignItems: "center", gap: 8 }}>
                    <button onClick={() => updateParam("passengers", Math.max(1, (searchParams.passengers || 1) - 1))} style={{ width: 32, height: 32, borderRadius: 6, border: "1px solid " + theme.border, background: theme.card, color: theme.text, cursor: "pointer", fontSize: 16 }}>-</button>
                    <span style={{ fontSize: 16, fontWeight: 600, minWidth: 20, textAlign: "center" }}>{searchParams.passengers || 1}</span>
                    <button onClick={() => updateParam("passengers", Math.min(9, (searchParams.passengers || 1) + 1))} style={{ width: 32, height: 32, borderRadius: 6, border: "1px solid " + theme.border, background: theme.card, color: theme.text, cursor: "pointer", fontSize: 16 }}>+</button>
                  </div>
                </div>
                <div><label style={{ fontSize: 12, color: theme.textDim, fontWeight: 600, textTransform: "uppercase", letterSpacing: 1, display: "block", marginBottom: 6 }}>Bank Programs</label><MultiSelectDropdown label="All Banks" options={Object.entries(BANKS)} selected={searchParams.selectedBanks || []} onToggle={(id) => updateParam("selectedBanks", (searchParams.selectedBanks || []).includes(id) ? (searchParams.selectedBanks || []).filter(x => x !== id) : [...(searchParams.selectedBanks || []), id])} onClear={() => updateParam("selectedBanks", [])} onSelectAll={(ids) => updateParam("selectedBanks", ids)} theme={theme} /></div>
                <div><label style={{ fontSize: 12, color: theme.textDim, fontWeight: 600, textTransform: "uppercase", letterSpacing: 1, display: "block", marginBottom: 6 }}>Airline Programs</label><MultiSelectDropdown label="All Airlines" options={Object.entries(PROGRAMS).map(([k,v]) => [k, {name: v.name, color: null}])} selected={searchParams.selectedAirlines || []} onToggle={(id) => updateParam("selectedAirlines", (searchParams.selectedAirlines || []).includes(id) ? (searchParams.selectedAirlines || []).filter(x => x !== id) : [...(searchParams.selectedAirlines || []), id])} onClear={() => updateParam("selectedAirlines", [])} onSelectAll={(ids) => updateParam("selectedAirlines", ids)} theme={theme} /></div>
              </div>
              <div className="search-dates-grid" style={{ display: "grid", gridTemplateColumns: searchParams.tripType === "round_trip" ? "1fr 1fr 1fr 1fr 1fr" : "1fr 1fr 1fr", gap: 16, marginBottom: 16 }}>
                <div><label style={{ fontSize: 12, color: theme.textDim, fontWeight: 600, textTransform: "uppercase", letterSpacing: 1, display: "block", marginBottom: 6 }}>{searchParams.tripType === "round_trip" ? "Depart From" : "Date From"}</label><Input type="date" min={todayStr} value={searchParams.dateFrom} onChange={(v) => { updateParam("dateFrom", v); if (searchParams.dateTo && v && searchParams.dateTo < v) { updateParam("dateTo", ""); updateParam("returnDateFrom", ""); updateParam("returnDateTo", ""); } }} /></div>
                <div><label style={{ fontSize: 12, color: theme.textDim, fontWeight: 600, textTransform: "uppercase", letterSpacing: 1, display: "block", marginBottom: 6 }}>{searchParams.tripType === "round_trip" ? "Depart To" : "Date To"}</label><Input type="date" min={searchParams.dateFrom || todayStr} value={searchParams.dateTo} onChange={(v) => { updateParam("dateTo", v); if (searchParams.returnDateFrom && v && searchParams.returnDateFrom < v) { updateParam("returnDateFrom", ""); updateParam("returnDateTo", ""); } }} /></div>
                {searchParams.tripType === "round_trip" && <div><label style={{ fontSize: 12, color: theme.textDim, fontWeight: 600, textTransform: "uppercase", letterSpacing: 1, display: "block", marginBottom: 6 }}>Return From</label><Input type="date" min={searchParams.dateTo || searchParams.dateFrom || todayStr} value={searchParams.returnDateFrom} onChange={(v) => { updateParam("returnDateFrom", v); if (searchParams.returnDateTo && v && searchParams.returnDateTo < v) { updateParam("returnDateTo", ""); } }} /></div>}
                {searchParams.tripType === "round_trip" && <div><label style={{ fontSize: 12, color: theme.textDim, fontWeight: 600, textTransform: "uppercase", letterSpacing: 1, display: "block", marginBottom: 6 }}>Return To</label><Input type="date" min={searchParams.returnDateFrom || searchParams.dateTo || todayStr} value={searchParams.returnDateTo} onChange={(v) => updateParam("returnDateTo", v)} /></div>}
                <div><label style={{ fontSize: 12, color: theme.textDim, fontWeight: 600, textTransform: "uppercase", letterSpacing: 1, display: "block", marginBottom: 6 }}>Max Miles</label><Input value={searchParams.maxMiles} onChange={(v) => updateParam("maxMiles", v)} placeholder="120000" /></div>
              </div>
              <div className="search-cabin-row" style={{ display: "flex", justifyContent: "space-between", alignItems: "center" }}>
                <div style={{ display: "flex", gap: 6 }}>{["F", "J", "W", "Y"].map(c => (<GlowButton key={c} active={searchParams.cabin === c} onClick={() => { trackEvent('cabin_filter', { cabin: c }); updateParam("cabin", c); }} small>{cabinInfo[c].icon} {cabinInfo[c].label}</GlowButton>))}</div>
                <div style={{ display: "flex", gap: 12, alignItems: "center" }}>
                  <div style={{ display: "flex", alignItems: "center", gap: 4 }}>
                    <span style={{ fontSize: 12, color: theme.textDim, fontWeight: 600, textTransform: "uppercase", letterSpacing: 1, marginRight: 4 }}>Max Stops</span>
                    {[0, 1, 2].map(s => (
                      <button key={s} onClick={() => { trackEvent('stops_change', { max_stops: s }); updateParam("maxStops", s); }} style={{
                        padding: "4px 10px", borderRadius: 8, fontSize: 12, fontWeight: 600, cursor: "pointer",
                        border: `1px solid ${searchParams.maxStops === s ? theme.accent : theme.border}`,
                        background: searchParams.maxStops === s ? theme.accent + "20" : theme.surface,
                        color: searchParams.maxStops === s ? theme.accent : theme.textMuted,
                        transition: "all 0.2s"
                      }}>{s === 0 ? "Nonstop" : s === 1 ? "1 Stop" : "2 Stops"}</button>
                    ))}
                  </div>
                  <GlowButton onClick={doSearch} active disabled={loading}>{loading ? "Searching..." : "Search Flights"}</GlowButton>
                </div>
              </div>
            </div>
            {loading && <div style={{ textAlign: "center", padding: 60, color: theme.textDim }}><div style={{ fontSize: 32, marginBottom: 12, animation: "spin 1s linear infinite" }}>✈</div><style>{`@keyframes spin { from { transform: rotate(0deg); } to { transform: rotate(360deg); } }`}</style>Searching across 25 programs...</div>}
            {!loading && searched && results.length === 0 && connectionResults.length === 0 && returnResults.length === 0 && returnConnectionResults.length === 0 && <div style={{ textAlign: "center", padding: 60, color: theme.textDim }}><div style={{ fontSize: 40, marginBottom: 12 }}>🔍</div><div style={{ fontSize: 16 }}>No availability found</div><div style={{ fontSize: 13, marginTop: 8 }}>Try widening your date range or cabin class</div></div>}
            {!loading && (results.length > 0 || connectionResults.length > 0 || returnResults.length > 0 || returnConnectionResults.length > 0) && (()=>{
              const isRT = searchParams.tripType === "round_trip" && (returnResults.length > 0 || returnConnectionResults.length > 0);

              const fetchFlightOptions = (origin, destination, flightDate, cardKey) => {
                const cab = searchParams.cabin || "J";
                const tt = searchParams.tripType || "one_way";
                const rd = searchParams.returnDate || "";
                const cacheKey = `${origin}|${destination}|${flightDate}|${cab}|${tt}`;
                if (flightOptionsCache[cacheKey]) return; // already cached
                setLoadingOptions(cardKey);
                let url = `${API}/api/flight-options?origin=${origin}&destination=${destination}&date=${flightDate}&cabin=${cab}&trip_type=${tt}`;
                if (tt === "round_trip" && rd) url += `&return_date=${rd}`;
                fetch(url)
                  .then(r => r.json())
                  .then(data => { setFlightOptionsCache(prev => ({ ...prev, [cacheKey]: data })); setLoadingOptions(null); })
                  .catch(() => setLoadingOptions(null));
              };

              const handleDirectToggle = (flight, cardKey) => {
                if (expandedCard === cardKey) { setExpandedCard(null); return; }
                setExpandedCard(cardKey);
                trackEvent('card_expand', { route: flight.origin + '→' + flight.destination, date: flight.date });
                fetchFlightOptions(flight.origin, flight.destination, flight.date, cardKey);
              };

              const handleConnLegToggle = (leg, connKey, legIdx) => {
                const fullKey = `${connKey}-leg-${legIdx}`;
                if (expandedCard === fullKey) { setExpandedCard(null); return; }
                setExpandedCard(fullKey);
                trackEvent('card_expand', { route: leg.origin + '→' + leg.destination, date: leg.date, is_connection_leg: true });
                fetchFlightOptions(leg.origin, leg.destination, leg.date, fullKey);
              };

              const renderFlightBlock = (directs, conns, hubs, sectionLabel) => {
                const prefix = sectionLabel === "Return" ? "ret-" : "out-";
                // Deduplicate direct flights by route+date, keeping only the best (lowest mileage)
                const dedupedDirects = (() => {
                  const groups = {};
                  directs.forEach(f => {
                    const key = `${f.origin}|${f.destination}|${f.date}`;
                    if (!groups[key]) {
                      groups[key] = { best: f, count: 1 };
                    } else {
                      groups[key].count++;
                      if (f.mileage < groups[key].best.mileage) {
                        groups[key].best = f;
                      }
                    }
                  });
                  return Object.values(groups).map(g => ({ ...g.best, _programCount: g.count }));
                })();
                // Sort deduped directs by current sort preference
                const sortedDirects = [...dedupedDirects].sort((a, b) => {
                  if (resultSort === "date") return (a.date || "").localeCompare(b.date || "");
                  return (a.mileage || 0) - (b.mileage || 0);
                });
                // Sort connections too
                const sortedConns = [...conns].sort((a, b) => {
                  if (resultSort === "date") return (a.legs?.[0]?.date || "").localeCompare(b.legs?.[0]?.date || "");
                  return (a.total_mileage || 0) - (b.total_mileage || 0);
                });
                const SortBtn = ({ value, label }) => (
                  <button onClick={() => setResultSort(value)} style={{
                    background: resultSort === value ? `${theme.accent}22` : "transparent",
                    color: resultSort === value ? theme.accent : theme.textDim,
                    border: `1px solid ${resultSort === value ? theme.accent + "44" : "transparent"}`,
                    borderRadius: 8, padding: "4px 12px", fontSize: 12, fontWeight: 600,
                    cursor: "pointer", fontFamily: "inherit", transition: "all 0.2s"
                  }}>{label}</button>
                );
                return (
                <div style={{ marginBottom: isRT ? 32 : 0 }}>
                  {sectionLabel && <div style={{ fontSize: 16, fontWeight: 800, color: theme.accent, marginBottom: 12, paddingBottom: 8, borderBottom: `2px solid ${theme.accent}33`, display: "flex", alignItems: "center", gap: 8 }}><span style={{ fontSize: 20 }}>{sectionLabel === "Outbound" ? "🛫" : "🛬"}</span>{sectionLabel}</div>}
                  <div style={{ marginBottom: 16, display: "flex", justifyContent: "space-between", alignItems: "center" }}>
                    <span style={{ color: theme.textMuted, fontSize: 14 }}>{sortedDirects.length + sortedConns.length} flights found · {cabinInfo[searchParams.cabin]?.label} class</span>
                    <div style={{ display: "flex", alignItems: "center", gap: 4 }}>
                      <span style={{ color: theme.textDim, fontSize: 12, marginRight: 4 }}>Sort by:</span>
                      <SortBtn value="miles" label="Miles ↑" />
                      <SortBtn value="date" label="Date ↑" />
                    </div>
                  </div>
                  {sortedDirects.length > 0 && (
                    <div>
                      {sortedConns.length > 0 && <div style={{ fontSize: 12, fontWeight: 700, color: theme.textDim, letterSpacing: 1, textTransform: "uppercase", marginBottom: 8 }}>Direct Flights ({sortedDirects.length})</div>}
                      <div style={{ display: "flex", flexDirection: "column", gap: 8 }}>{sortedDirects.map((f, i) => {
                        const cardKey = `${prefix}direct-${f.id || i}`;
                        const cacheKey = `${f.origin}|${f.destination}|${f.date}|${searchParams.cabin || "J"}|${searchParams.tripType || "one_way"}`;
                        return <FlightCard key={cardKey} flight={f} cabin={searchParams.cabin} airportMap={airportMap}
                          expanded={expandedCard === cardKey}
                          onToggle={() => handleDirectToggle(f, cardKey)}
                          optionsData={flightOptionsCache[cacheKey]}
                          optionsLoading={loadingOptions === cardKey}
                          programCount={f._programCount}
                          tripExpanded={tripExpandedCard === f.id}
                          onTripToggle={() => setTripExpandedCard(tripExpandedCard === f.id ? null : f.id)}
                          authToken={authToken}
                        />;
                      })}</div>
                    </div>
                  )}
                  {sortedConns.length > 0 && (
                    <div style={{ marginTop: sortedDirects.length > 0 ? 24 : 0 }}>
                      <div style={{ fontSize: 12, fontWeight: 700, color: theme.textDim, letterSpacing: 1, textTransform: "uppercase", marginBottom: 8 }}>Connecting Flights ({sortedConns.length}){hubs.length > 0 && <span style={{ fontWeight: 400, textTransform: "none", marginLeft: 8 }}>via {hubs.slice(0, 5).join(", ")}{hubs.length > 5 ? ` +${hubs.length - 5} more` : ""}</span>}</div>
                      {sortedDirects.length === 0 && <div style={{ textAlign: "center", padding: "12px 16px", color: theme.textDim, fontSize: 13, background: `${theme.accent}08`, borderRadius: 8, marginBottom: 12 }}>No direct flights found — showing connections through hub cities</div>}
                      <div style={{ display: "flex", flexDirection: "column", gap: 8 }}>{sortedConns.map((c, i) => {
                        const connKey = `${prefix}conn-${i}`;
                        // Determine which leg is expanded (if any)
                        let expLeg = null;
                        if (expandedCard === `${connKey}-leg-0`) expLeg = 0;
                        else if (expandedCard === `${connKey}-leg-1`) expLeg = 1;
                        return <ConnectionFlightCard key={connKey} conn={c} cabin={searchParams.cabin} airportMap={airportMap}
                          expandedLeg={expLeg}
                          onToggleLeg={(li) => handleConnLegToggle(c.legs[li], connKey, li)}
                          getOptionsData={(key) => flightOptionsCache[key]}
                          getOptionsLoading={(key) => { const leg0Key = c.legs[0].origin + "|" + c.legs[0].destination + "|" + c.legs[0].date + "|" + (searchParams.cabin || "J") + "|one_way"; return loadingOptions === (connKey + "-leg-" + (key === leg0Key ? "0" : "1")); }}
                        />;
                      })}</div>
                    </div>
                  )}
                  {sortedDirects.length === 0 && sortedConns.length === 0 && <div style={{ textAlign: "center", padding: 32, color: theme.textDim }}>No results found for this direction.</div>}
                </div>
              )};
              return (
                <div>
                  {renderFlightBlock(results, connectionResults, connectionHubs, isRT ? "Outbound" : null)}
                  {isRT && renderFlightBlock(returnResults, returnConnectionResults, returnConnectionHubs, "Return")}
                </div>
              );
            })()}
            {!searched && !loading && <div style={{ textAlign: "center", padding: "60px 20px", color: theme.textDim }}><div style={{ fontSize: 48, marginBottom: 16 }}>✈️</div><div style={{ fontSize: 18, fontWeight: 600, color: theme.text, marginBottom: 8 }}>Search Award Flights</div><div style={{ fontSize: 14, maxWidth: 400, margin: "0 auto", lineHeight: 1.6 }}>Search across 25 loyalty programs for the best award flight deals. Enter your origin and destination to get started.</div></div>}
          </div>
        )}

        {/* PROGRAMS TAB */}
        {tab === "programs" && (
          <div>
            {/* Combined Totals Banner */}
            {(Object.values(programBalances).some(v => v > 0) || Object.values(bankBalances).some(v => v > 0)) && (
              <div style={{ background: `linear-gradient(135deg, ${theme.surface}, ${theme.card})`, border: `1px solid ${theme.border}`, borderRadius: 12, padding: "16px 24px", marginBottom: 24, display: "flex", justifyContent: "space-between", alignItems: "center", flexWrap: "wrap", gap: 16 }}>
                <div style={{ textAlign: "center", flex: 1, minWidth: 120 }}>
                  <div style={{ fontSize: 11, color: theme.textDim, textTransform: "uppercase", letterSpacing: 1, marginBottom: 4 }}>Airline Miles</div>
                  <div style={{ fontSize: 22, fontWeight: 700, color: theme.gold }}>{Object.values(programBalances).reduce((a, b) => a + (b || 0), 0).toLocaleString()}</div>
                </div>
                <div style={{ fontSize: 20, color: theme.textDim, fontWeight: 300 }}>+</div>
                <div style={{ textAlign: "center", flex: 1, minWidth: 120 }}>
                  <div style={{ fontSize: 11, color: theme.textDim, textTransform: "uppercase", letterSpacing: 1, marginBottom: 4 }}>Bank Points</div>
                  <div style={{ fontSize: 22, fontWeight: 700, color: theme.accent }}>{Object.values(bankBalances).reduce((a, b) => a + (b || 0), 0).toLocaleString()}</div>
                </div>
                <div style={{ fontSize: 20, color: theme.textDim, fontWeight: 300 }}>=</div>
                <div style={{ textAlign: "center", flex: 1, minWidth: 140 }}>
                  <div style={{ fontSize: 11, color: theme.textDim, textTransform: "uppercase", letterSpacing: 1, marginBottom: 4 }}>Total Points & Miles</div>
                  <div style={{ fontSize: 26, fontWeight: 800, color: theme.success }}>{(Object.values(programBalances).reduce((a, b) => a + (b || 0), 0) + Object.values(bankBalances).reduce((a, b) => a + (b || 0), 0)).toLocaleString()}</div>
                </div>
              </div>
            )}
            {/* Active Transfer Bonuses */}
            {(() => {
              const BONUS_BANK_NAMES = { chase_ur: 'Chase Ultimate Rewards', amex_mr: 'Amex Membership Rewards', capital_one: 'Capital One Miles', bilt: 'Bilt Rewards', citi_typ: 'Citi ThankYou Points', wells_fargo: 'Wells Fargo Rewards', rove: 'Rove' };
              const BONUS_PROGRAM_NAMES = { aeroplan: 'Air Canada Aeroplan', flyingblue: 'Air France/KLM Flying Blue', united: 'United MileagePlus', virginatlantic: 'Virgin Atlantic Flying Club', singapore: 'Singapore KrisFlyer', avios: 'British Airways Avios', marriott: 'Marriott Bonvoy', jal: 'JAL Mileage Bank', delta: 'Delta SkyMiles', emirates: 'Emirates Skywards', alaska: 'Alaska Mileage Plan', american: 'AAdvantage', lifemiles: 'Avianca LifeMiles', etihad: 'Etihad Guest', qantas: 'Qantas Frequent Flyer', cathay: 'Cathay Pacific Asia Miles', turkish: 'Turkish Miles&Smiles', aeromexico: 'Aeromexico Rewards', jetblue: 'JetBlue TrueBlue', korean: 'Korean Air SKYPASS', sas: 'SAS EuroBonus', tap: 'TAP Miles&Go', quantas: 'Qantas', southwest: 'Southwest Rapid Rewards', hawaiian: 'Hawaiian Airlines' };
              const activeBonuses = transferBonuses.filter(b => b.bonus_pct > 0);
              const grouped = {};
              activeBonuses.forEach(b => { if (!grouped[b.bank]) grouped[b.bank] = []; grouped[b.bank].push(b); });
              if (activeBonuses.length === 0) return null;
              return (
                <div style={{ background: theme.surface, border: `1px solid ${theme.border}`, borderRadius: 16, padding: 24, marginBottom: 28 }}>
                  <div style={{ display: "flex", justifyContent: "space-between", alignItems: "center", marginBottom: 16 }}>
                    <div style={{ display: "flex", alignItems: "center", gap: 10 }}>
                      <span style={{ fontSize: 20 }}>🔥</span>
                      <h2 style={{ fontSize: 18, fontWeight: 700, margin: 0 }}>Active Transfer Bonuses</h2>
                      <span style={{ fontSize: 12, color: theme.textDim, background: theme.card, padding: "2px 8px", borderRadius: 10, fontWeight: 600 }}>{activeBonuses.length} active</span>
                    </div>
                  </div>
                  <p style={{ color: theme.textMuted, fontSize: 13, margin: "0 0 16px 0" }}>Current point transfer bonuses from credit card programs. Transfer during a bonus to get extra miles.</p>
                  {Object.entries(grouped).map(([bank, items]) => (
                    <div key={bank} style={{ marginBottom: 16 }}>
                      <div style={{ fontSize: 13, fontWeight: 700, color: theme.textMuted, textTransform: "uppercase", letterSpacing: 1, marginBottom: 8, paddingBottom: 6, borderBottom: "1px solid " + theme.border }}>{BONUS_BANK_NAMES[bank] || bank}</div>
                      {items.map((b, i) => (
                        <div key={i} style={{ display: "flex", justifyContent: "space-between", alignItems: "center", padding: "10px 14px", marginBottom: 4, borderRadius: 8, background: theme.card, border: "1px solid " + theme.border }}>
                          <div>
                            <div style={{ fontSize: 14, fontWeight: 600, color: theme.text }}>{BONUS_PROGRAM_NAMES[b.program] || b.program}</div>
                            <div style={{ fontSize: 11, color: theme.textMuted, marginTop: 2 }}>{b.ratio} transfer ratio{b.speed ? " · " + b.speed : ""}{b.expires && b.expires !== "None" ? " · Expires " + b.expires : ""}</div>
                          </div>
                          <div style={{ fontSize: 20, fontWeight: 800, color: b.bonus_pct >= 40 ? "#f59e0b" : b.bonus_pct >= 25 ? "#10b981" : "#3b82f6", background: b.bonus_pct >= 40 ? "rgba(245,158,11,0.1)" : b.bonus_pct >= 25 ? "rgba(16,185,129,0.1)" : "rgba(59,130,246,0.1)", padding: "4px 10px", borderRadius: 8 }}>+{b.bonus_pct}%</div>
                        </div>
                      ))}
                    </div>
                  ))}
                </div>
              );
            })()}

            <div style={{ display: "flex", justifyContent: "space-between", alignItems: "flex-start", marginBottom: 24 }}>
              <div>
                <h2 style={{ fontSize: 20, fontWeight: 700, marginBottom: 8 }}>25 Loyalty Programs</h2>
                <p style={{ color: theme.textMuted, fontSize: 14, margin: 0 }}>Click any program name to visit their site. {authUser ? "Click the balance to edit." : <span>Sign in to track your balances. <button onClick={() => setShowAuthModal(true)} style={{ background: "none", border: "none", color: theme.accent, cursor: "pointer", fontWeight: 600, fontSize: 14, fontFamily: "inherit", padding: 0, textDecoration: "underline" }}>Sign In</button></span>}</p>
              </div>
            </div>
            {["Star Alliance", "Oneworld", "SkyTeam", "Independent"].map(alliance => {
              const progs = Object.entries(PROGRAMS).filter(([, p]) => p.alliance === alliance);
              const allianceData = ALLIANCE_LOGOS[alliance];
              return (<div key={alliance} style={{ marginBottom: 32 }}>
                <div style={{ display: "flex", alignItems: "center", gap: 8, marginBottom: 12, position: "relative" }}>
                  {allianceData ? <img src={allianceData.src} alt={alliance} style={{ width: 42, height: 42, objectFit: "contain", borderRadius: 6, background: allianceData.dark ? "rgba(255,255,255,0.12)" : "transparent", padding: 3 }} onError={e => { e.target.style.display = "none"; }} /> : <div style={{ width: 14, height: 14, borderRadius: "50%", background: allianceColors[alliance] }} />}
                  {allianceData && allianceData.teamImg ? (
                    <span style={{ position: "relative", display: "inline-flex", alignItems: "center" }}
                      onMouseEnter={e => { const tip = e.currentTarget.querySelector(".team-tip"); if (tip) tip.style.display = "block"; }}
                      onMouseLeave={e => { const tip = e.currentTarget.querySelector(".team-tip"); if (tip) tip.style.display = "none"; }}>
                      <h3 style={{ fontSize: 16, fontWeight: 600, margin: 0, cursor: "pointer", borderBottom: "1px dashed " + theme.textDim }}>{alliance}</h3>
                      <div className="team-tip" style={{ display: "none", position: "absolute", top: "100%", left: 0, zIndex: Z_MODAL, marginTop: 8, background: theme.card, border: `1px solid ${theme.border}`, borderRadius: 12, padding: 12, boxShadow: "0 12px 40px rgba(0,0,0,0.5)", maxWidth: ALLIANCE_INFO_MAX_WIDTH, width: "max-content" }}>
                        <p style={{ margin: "0 0 8px 0", fontSize: 13, fontWeight: 600, color: allianceColors[alliance] }}>{alliance} Member Airlines</p>
                        <img src={allianceData.teamImg} alt={`${alliance} members`} style={{ width: "100%", maxWidth: 500, borderRadius: 8, display: "block" }} />
                      </div>
                    </span>
                  ) : <h3 style={{ fontSize: 16, fontWeight: 600, margin: 0 }}>{alliance}</h3>}
                  <span style={{ color: theme.textDim, fontSize: 12 }}>{progs.length} programs</span>
                </div>
                <div className="profile-programs-grid" style={{ display: "grid", gridTemplateColumns: "repeat(auto-fill, minmax(340px, 1fr))", gap: 8 }}>{progs.map(([slug, prog]) => <ProgramCard key={slug} slug={slug} program={prog} balance={programBalances[slug]} onBalanceChange={saveProgramBalance} readOnly={!authUser} />)}</div>
              </div>);
            })}
            <div style={{ marginTop: 40 }}>
              <div style={{ display: "flex", justifyContent: "space-between", alignItems: "flex-start", marginBottom: 20 }}>
                <div>
                  <h2 style={{ fontSize: 20, fontWeight: 700, marginBottom: 8 }}>Bank Partners</h2>
                  <p style={{ color: theme.textMuted, fontSize: 14, margin: 0 }}>7 credit card programs that transfer points to airline partners.</p>
                </div>
              </div>
              <div className="profile-banks-grid" style={{ display: "grid", gridTemplateColumns: "repeat(auto-fill, minmax(260px, 1fr))", gap: 10 }}>{Object.entries(BANKS).map(([id, bank]) => (
                <div key={id} style={{ background: theme.card, border: `1px solid ${theme.border}`, borderRadius: 10, padding: "16px 14px", borderLeft: `3px solid ${bank.color}`, display: "flex", alignItems: "center", justifyContent: "space-between" }}>
                  <div style={{ display: "flex", alignItems: "center", gap: 10 }}>
                    {bank.logo && <img src={bank.logo} alt={`${bank.name || "Bank"} logo`} style={{ width: 47, height: 47, borderRadius: 8, objectFit: "contain", background: bank.darkLogo ? "rgba(255,255,255,0.12)" : "transparent", padding: 3, flexShrink: 0 }} onError={e => { e.target.style.display = "none"; }} />}
                    <div>
                      <a href={bank.url} target="_blank" rel="noopener noreferrer" style={{ fontWeight: 600, fontSize: 14, color: theme.text, textDecoration: "none" }}
                        onMouseEnter={e => { e.target.style.color = bank.color; e.target.style.textDecoration = "underline"; }}
                        onMouseLeave={e => { e.target.style.color = theme.text; e.target.style.textDecoration = "none"; }}>
                        {bank.name}
                      </a>
                      <span style={{ fontSize: 10, color: theme.textDim, marginLeft: 4 }}>↗</span>
                    </div>
                  </div>
                  <EditableBalance value={bankBalances[id]} onSave={val => saveBankBalance(id, val)} label={bank.currency || "points"} color={bank.color} readOnly={!authUser} />
                </div>
              ))}</div>
            </div>

            {/* Award Availability Release Schedule */}
            <ReleaseScheduleTable />
          </div>
        )}

        {/* DASHBOARD TAB */}
        {tab === "dashboard" && (
          <div>
            <h2 style={{ fontSize: 20, fontWeight: 700, marginBottom: 20 }}>Database Overview</h2>
            {stats ? (<>
              <div className="dash-stats-grid" style={{ display: "grid", gridTemplateColumns: "repeat(auto-fill, minmax(160px, 1fr))", gap: 12, marginBottom: 32 }}>
                <StatCard label="Total Flights" value={stats.total_availability?.toLocaleString() || "0"} icon="✈️" />
                <StatCard label="Programs" value={stats.total_programs || 25} icon="🏛" color={theme.accent} />
                <StatCard label="Routes" value={stats.total_routes?.toLocaleString() || "—"} icon="🗺️" />
                <StatCard label="Airports" value={stats.total_airports?.toLocaleString() || "—"} icon="🛬" />
              </div>
              <div style={{ background: theme.surface, border: `1px solid ${theme.border}`, borderRadius: 12, padding: 20 }}>
                <h3 style={{ fontSize: 15, fontWeight: 600, marginBottom: 12, color: theme.textMuted }}>System Status</h3>
                <div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: 16, fontSize: 14 }}>
                  <div><span style={{ color: theme.textDim }}>Database: </span><span style={{ color: theme.success }}>● Connected</span></div>
                  <div><span style={{ color: theme.textDim }}>Last ingestion: </span><span>{stats.last_ingestion || "—"}</span></div>
                  <div><span style={{ color: theme.textDim }}>Date range: </span><span>{stats.date_min || "—"} → {stats.date_max || "—"}</span></div>
                  <div><span style={{ color: theme.textDim }}>API version: </span><span>v2.0.0</span></div>
                </div>
              </div>
              <ErrorBoundary><DBProgressWidget /></ErrorBoundary>
              <ErrorBoundary><AirportMap /></ErrorBoundary>
              <ErrorBoundary><RouteDensityMap /></ErrorBoundary>
              <ErrorBoundary><SweetSpotFinder /></ErrorBoundary>
              <ErrorBoundary><CabinCalendar /></ErrorBoundary>
            </>) : (
              <div style={{ textAlign: "center", padding: 60, color: theme.textDim }}><div style={{ fontSize: 40, marginBottom: 12 }}>📊</div><div style={{ fontSize: 16 }}>Connect to API to view stats</div></div>
            )}
          </div>
        )}

        {/* ADMIN TAB */}
        {tab === "admin" && (
          <div>
            <div style={{ display: "flex", justifyContent: "space-between", alignItems: "center", marginBottom: 20 }}>
              <h2 style={{ fontSize: 20, fontWeight: 700, margin: 0 }}>Admin Panel</h2>
              <span style={{ fontSize: 12, color: theme.textDim }}><StatusDot status={stats ? "healthy" : "offline"} />{stats ? "System Online" : "API Offline"}</span>
            </div>
            {/* User Management */}
            <div style={{ background: theme.surface, border: `1px solid ${theme.border}`, borderRadius: 16, padding: 24, marginBottom: 20 }}>
              <AdminUsersSection token={authToken} currentUserRole={actualRole} />
            </div>
            {/* Role Permissions */}
            <div style={{ background: theme.surface, border: `1px solid ${theme.border}`, borderRadius: 16, padding: 24, marginBottom: 20 }}>
              <RolePermissionsPanel token={authToken} currentUserRole={actualRole} />
            </div>
            <ProgramRadar />
            <IngestionHealth stats={stats} history={adminHistory} />
            <AnalyticsDashboard token={authToken} />
            <HighScores highScores={adminHighScores} />
            <AlertManager alerts={adminAlerts} onSave={handleAlertSave} onDelete={handleAlertDelete} onToggle={handleAlertToggle} />
            <MyCards cards={adminCards} onToggle={handleCardToggle} />
            <TransferBonuses bonuses={adminBonuses} onRefresh={handleRefreshBonuses} />
            <RunHistory runs={adminRuns} />
            <SystemInfo stats={stats} />
          </div>
        )}
      </main>

      {tab === "email" && <EmailPreview />}
      {tab === "ingestion" && <IngestionControl />}
      {tab === "profile" && authUser && (
        <main id="main-content" className="main-content" style={{ maxWidth: 1100, margin: "0 auto", padding: "24px 24px 60px" }}>
          <ProfileTab user={authUser} token={authToken} onUserUpdate={(u) => setAuthUser(u)} themeMode={themeMode} onThemeChange={changeTheme} onHomeAirportsChange={(airports) => setSearchParams(p => ({ ...p, origin: (airports || []).join(",") }))} onSearchDefaultsChange={applySearchDefaults} />
        </main>
      )}

      {/* Auth Modal */}
      {showAuthModal && <AuthModal onClose={() => { setShowAuthModal(false); setResetToken(""); }} initialResetToken={resetToken} onAuth={handleAuth} />}
      {/* Help Guide Modal */}
      {showHelpGuide && <HelpGuide onClose={() => setShowHelpGuide(false)} />}
      <ToastContainer />
    </div>
  );
}
