/* global React */
// ME-30 v3 — Components matching user's actual Looker dashboard style

const { useState: useStateC, useEffect: useEffectC, useRef: useRefC } = React;

// ===== Icons =====
const Icon = ({ name, size = 14 }) => {
  const props = { width: size, height: size, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: 1.8, strokeLinecap: "round", strokeLinejoin: "round" };
  switch (name) {
    case "menu":return <svg {...props}><line x1="3" y1="6" x2="21" y2="6" /><line x1="3" y1="12" x2="21" y2="12" /><line x1="3" y1="18" x2="21" y2="18" /></svg>;
    case "search":return <svg {...props}><circle cx="11" cy="11" r="7" /><line x1="21" y1="21" x2="16.5" y2="16.5" /></svg>;
    case "bell":return <svg {...props}><path d="M18 8a6 6 0 1 0-12 0c0 7-3 9-3 9h18s-3-2-3-9" /><path d="M13.7 21a2 2 0 0 1-3.4 0" /></svg>;
    case "help":return <svg {...props}><circle cx="12" cy="12" r="9" /><path d="M9.5 9.5a2.5 2.5 0 1 1 4.5 1.5c-.5.5-2 1-2 2v.5" /><line x1="12" y1="17" x2="12" y2="17.5" /></svg>;
    case "heart":return <svg {...props}><path d="M20.84 4.61a5.5 5.5 0 0 0-7.78 0L12 5.67l-1.06-1.06a5.5 5.5 0 0 0-7.78 7.78l1.06 1.06L12 21.23l7.78-7.78 1.06-1.06a5.5 5.5 0 0 0 0-7.78z" /></svg>;
    case "duplicate":return <svg {...props}><rect x="9" y="9" width="13" height="13" rx="2" /><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1" /></svg>;
    case "refresh":return <svg {...props}><polyline points="23 4 23 10 17 10" /><polyline points="1 20 1 14 7 14" /><path d="M3.51 9a9 9 0 0 1 14.85-3.36L23 10" /><path d="M20.49 15a9 9 0 0 1-14.85 3.36L1 14" /></svg>;
    case "more":return <svg {...props}><circle cx="12" cy="5" r="1" /><circle cx="12" cy="12" r="1" /><circle cx="12" cy="19" r="1" /></svg>;
    case "more-h":return <svg {...props}><circle cx="5" cy="12" r="1" /><circle cx="12" cy="12" r="1" /><circle cx="19" cy="12" r="1" /></svg>;
    case "chevron":return <svg {...props}><polyline points="6 9 12 15 18 9" /></svg>;
    case "chevron-r":return <svg {...props}><polyline points="9 6 15 12 9 18" /></svg>;
    case "download":return <svg {...props}><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" /><polyline points="7 10 12 15 17 10" /><line x1="12" y1="15" x2="12" y2="3" /></svg>;
    case "external":return <svg {...props}><path d="M14 3h7v7" /><path d="M21 3l-9 9" /><path d="M19 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V7a2 2 0 0 1 2-2h6" /></svg>;
    case "info":return <svg {...props}><circle cx="12" cy="12" r="9" /><line x1="12" y1="10" x2="12" y2="16" /><line x1="12" y1="7" x2="12" y2="7.5" /></svg>;
    case "filter":return <svg {...props}><polygon points="3 4 21 4 14 12 14 20 10 20 10 12 3 4" /></svg>;
    case "trending":return <svg {...props}><polyline points="23 6 13.5 15.5 8.5 10.5 1 18" /><polyline points="17 6 23 6 23 12" /></svg>;
    case "calendar":return <svg {...props}><rect x="3" y="4" width="18" height="17" rx="2" /><line x1="3" y1="9" x2="21" y2="9" /><line x1="8" y1="3" x2="8" y2="5" /><line x1="16" y1="3" x2="16" y2="5" /></svg>;
    case "x":return <svg {...props}><line x1="18" y1="6" x2="6" y2="18" /><line x1="6" y1="6" x2="18" y2="18" /></svg>;
    case "settings":return <svg {...props}><circle cx="12" cy="12" r="3" /><path d="M19.4 15a1.7 1.7 0 0 0 .3 1.8l.1.1a2 2 0 1 1-2.8 2.8l-.1-.1a1.7 1.7 0 0 0-1.8-.3 1.7 1.7 0 0 0-1 1.5V21a2 2 0 1 1-4 0v-.1a1.7 1.7 0 0 0-1.1-1.5 1.7 1.7 0 0 0-1.8.3l-.1.1a2 2 0 1 1-2.8-2.8l.1-.1a1.7 1.7 0 0 0 .3-1.8 1.7 1.7 0 0 0-1.5-1H3a2 2 0 1 1 0-4h.1A1.7 1.7 0 0 0 4.6 9a1.7 1.7 0 0 0-.3-1.8l-.1-.1a2 2 0 1 1 2.8-2.8l.1.1a1.7 1.7 0 0 0 1.8.3H9a1.7 1.7 0 0 0 1-1.5V3a2 2 0 1 1 4 0v.1a1.7 1.7 0 0 0 1 1.5 1.7 1.7 0 0 0 1.8-.3l.1-.1a2 2 0 1 1 2.8 2.8l-.1.1a1.7 1.7 0 0 0-.3 1.8V9a1.7 1.7 0 0 0 1.5 1H21a2 2 0 1 1 0 4h-.1a1.7 1.7 0 0 0-1.5 1z" /></svg>;
    case "schedule":return <svg {...props}><circle cx="12" cy="12" r="9" /><polyline points="12 7 12 12 15 14" /></svg>;
    default:return <svg {...props}><circle cx="12" cy="12" r="9" /></svg>;
  }
};

// ===== Delta cell (for tables) =====
function DeltaCell({ value, inverted = false, suffix = "%", showZero = true }) {
  let sign = value > 0 ? "pos" : value < 0 ? "neg" : "flat";
  if (inverted) sign = sign === "pos" ? "neg" : sign === "neg" ? "pos" : "flat";
  const arrow = value > 0 ? "▲" : value < 0 ? "▼" : "▬";
  if (!showZero && Math.abs(value) < 0.005) return <span className="muted">—</span>;
  return (
    <span className={`delta-cell ${sign}`}>
      <span className="arrow">{arrow}</span>
      {value > 0 ? "+" : ""}{value.toFixed(2)}{suffix}
    </span>);

}

// ===== Top Bar (BI app chrome — generic, not Looker branded) =====
function TopBar() {
  return (
    <div className="topbar">
      <button className="icon-btn" title="Menu"><Icon name="menu" size={16} /></button>
      <div className="topbar-logo">
        <span className="badge">M3</span>
        <span>ME-30 Insights</span>
      </div>
      <div className="topbar-search">
        <Icon name="search" size={13} />
        <span>Start typing to search…</span>
      </div>
      <div className="topbar-right">
        <button className="icon-btn" title="Schedule"><Icon name="schedule" /></button>
        <button className="icon-btn" title="Help"><Icon name="help" /></button>
        <div className="avatar">RA</div>
      </div>
    </div>);

}

// ===== Title Bar =====
function TitleBar({ title, meta, onEdit, onTour }) {
  return (
    <div className="titlebar">
      <span className="titlebar-title">
        {title}
        <span className="heart" title="Favorite"><Icon name="heart" size={15} /></span>
        <span className="duplicate" title="Duplicate"><Icon name="duplicate" size={14} /></span>
      </span>
      <span className="titlebar-actions">
        {meta && <span className="titlebar-meta">{meta}</span>}
        <button className="btn ghost"><Icon name="refresh" size={13} /></button>
        <button className="btn ghost"><Icon name="filter" size={13} /></button>
        {onTour && (
          <button className="btn ghost" onClick={onTour} title="Panduan interaktif tile-by-tile"
            style={{ color: "#2eb5e6", fontWeight: 600, fontSize: 11, display: "inline-flex", alignItems: "center", gap: 4 }}>
            <Icon name="help" size={13} /> Tour
          </button>
        )}
        <button className="btn ghost"><Icon name="more" size={14} /></button>
      </span>
    </div>);

}

// ===== Filter Row (compact, label above) =====
function FilterRow({ filters, onFilterChange }) {
  return (
    <div className="filterrow" data-tour="filter-row">
      <FilterField
        label="Year"
        value={filters.year}
        highlight="#dae8fb"
        options={["Latest Year", "2024", "2025", "2026", "2027"]}
        onChange={(v) => onFilterChange({ year: v })} />

      <FilterField
        label="Month"
        value={filters.month}
        highlight="#dae8fb"
        options={["Latest Month", "Januari", "Februari", "Maret", "April", "Mei", "Juni", "Juli", "Agustus", "September", "Oktober", "November", "Desember"]}
        onChange={(v) => onFilterChange({ month: v })} />

      <FilterField
        label="Region"
        value={filters.region}
        options={["Any Value", "1", "2", "3", "4", "5", "6", "7", "West", "East", "Jawa", "Sumatra"]}
        onChange={(v) => onFilterChange({ region: v })} />

      <FilterField
        label="Province"
        value={filters.province}
        options={["Any Value", "Aceh", "Sumatera Utara", "Sumatera Barat", "Riau", "Kepulauan Riau", "Jambi", "Bengkulu", "Sumatera Selatan", "Kepulauan Bangka Belitung", "Lampung", "Banten", "DKI Jakarta", "Jawa Barat", "Jawa Tengah", "DI Yogyakarta", "Jawa Timur", "Bali", "Nusa Tenggara Barat", "Nusa Tenggara Timur", "Kalimantan Barat", "Kalimantan Tengah", "Kalimantan Selatan", "Kalimantan Timur", "Kalimantan Utara", "Sulawesi Utara", "Gorontalo", "Sulawesi Tengah", "Sulawesi Barat", "Sulawesi Selatan", "Sulawesi Tenggara", "Maluku", "Maluku Utara", "Papua", "Papua Barat", "Papua Barat Daya", "Papua Tengah", "Papua Pegunungan", "Papua Selatan"]}
        onChange={(v) => onFilterChange({ province: v })} />

    </div>);

}

function FilterField({ label, prefix, value, options, onChange, sm, highlight }) {
  const [open, setOpen] = useStateC(false);
  const ref = useRefC();
  useEffectC(() => {
    const h = (e) => {if (ref.current && !ref.current.contains(e.target)) setOpen(false);};
    document.addEventListener("click", h);
    return () => document.removeEventListener("click", h);
  }, []);
  return (
    <div className="fr-field" ref={ref} style={{ padding: "0px", margin: "0px" }}>
      <div className="fr-label" style={{ lineHeight: "1.4", fontWeight: "400" }}>{label}</div>
      <button className={`fr-select ${prefix ? "has-prefix" : ""} ${sm ? "sm" : ""}`} onClick={(e) => {e.stopPropagation();setOpen((o) => !o);}} style={{ position: "relative", color: "black", backgroundColor: highlight || "white" }}>
        {prefix && <span className="prefix">{prefix}</span>}
        <span>{value}</span>
        <span className="caret"><Icon name="chevron" size={13} /></span>
        {open &&
        <div className="dropdown" onClick={(e) => e.stopPropagation()}>
            {options.map((o, i) =>
          o.startsWith("—") ?
          <div key={i} className="group-label">{o.replaceAll("—", "").trim()}</div> :
          <div key={i} className={`item ${o === value ? "active" : ""}`} onClick={() => {onChange(o);setOpen(false);}}>
                  {o}
                </div>
          )}
          </div>
        }
      </button>
    </div>);

}

// ===== Pill Nav (sub-navigation) =====
function PillNav({ tabs, value, onChange }) {
  return (
    <div className="pillnav" data-tour="pill-nav">
      {tabs.map((t) =>
      <button key={t.id} className={value === t.id ? "active" : ""} onClick={() => onChange(t.id)}>
          {t.label}
        </button>
      )}
    </div>);

}

// ===== Section Band (the defining visual element) =====
function SectionBand({ children, color = "primary", right, level = 2, tourId }) {
  return (
    <div className={`section-band ${color} ${level === 3 ? "sub" : ""}`} data-tour={tourId || undefined}>
      {children}
      {right && <span className="right">{right}</span>}
    </div>);

}

// ===== KPI Tile (compact — matches references) =====
function KpiTile({ kpi, color = "blue", onOpen, showPill = true, compact = false, filters }) {
  const dB = kpi.delta_b;
  let sign = dB > 0 ? "pos" : dB < 0 ? "neg" : "flat";
  if (kpi.inverted) sign = sign === "pos" ? "neg" : sign === "neg" ? "pos" : "flat";
  const arrow = dB > 0 ? "▲" : dB < 0 ? "▼" : "▬";
  const suffix = kpi.id === "birate" || kpi.id === "inflation" ? " pp" : "%";
  const semanticClass = sign === "pos" ? "pos-semantic" : sign === "neg" ? "neg-semantic" : "flat";

  return (
    <div className={`kpi color-${color}`} onClick={() => onOpen && onOpen(kpi.id)} style={compact ? { minHeight: 78, padding: "10px 10px 8px" } : undefined}>
      <div className="kpi-label">
        {kpi.title}
      </div>
      <div className="kpi-value" style={compact ? { fontSize: 21 } : undefined}>
        {kpi.valueFmt}{kpi.valueSuffix && <span className="unit" style={compact ? { fontSize: 11 } : undefined}>{kpi.valueSuffix}</span>}
      </div>
      {showPill &&
      <div className={`kpi-pill ${semanticClass}`}>
          <span className="arrow">{arrow}</span>
          {dB > 0 ? "+" : ""}{dB.toFixed(2)}{suffix} YoY
        </div>
      }
      {!compact && <div className="kpi-meta">{(typeof asOfPeriod === "function" && filters) ? asOfPeriod(filters, kpi.cadence) : kpi.period}</div>}
    </div>);

}

// ===== Generic Tile (for charts/tables) =====
function Tile({ title, subtitle, tools, children, flush, className = "", span = 4, onClick, style, tourId }) {
  const [menu, setMenu] = useStateC(null);
  const hasInfo = !!tourId;
  const openMenu = (e) => {
    if (!hasInfo) return;
    e.preventDefault();
    e.stopPropagation();
    setMenu({ x: e.clientX, y: e.clientY });
  };
  const fire = (sid) => { setMenu(null); window.dispatchEvent(new CustomEvent("me30-open-tour", { detail: { startId: sid } })); };
  useEffectC(() => {
    if (!menu) return;
    const close = () => setMenu(null);
    window.addEventListener("click", close);
    window.addEventListener("scroll", close, true);
    return () => { window.removeEventListener("click", close); window.removeEventListener("scroll", close, true); };
  }, [menu]);
  const ctxStyle = { display: "block", width: "100%", textAlign: "left", background: "none", border: 0, padding: "7px 10px", fontSize: 12, color: "var(--text-1)", borderRadius: 4, cursor: "pointer", whiteSpace: "nowrap" };
  return (
    <section className={`tile span-${span} ${className}`} style={{ cursor: onClick ? "pointer" : "default", ...style }} onClick={onClick} onContextMenu={openMenu} data-tour={tourId || undefined}>
      {title &&
      <header className="tile-head">
          <span className="tile-title">{title}</span>
          <div className="tile-tools" onClick={(e) => e.stopPropagation()}>
            {tools}
            <button className="tool-btn" onClick={openMenu} title="Opsi tile"><Icon name="more-h" size={14} /></button>
          </div>
        </header>
      }
      <div className={`tile-body ${flush ? "flush" : ""}`}>{children}</div>
      {menu &&
      <div style={{ position: "fixed", top: menu.y, left: Math.min(menu.x, (typeof window !== "undefined" ? window.innerWidth : 1200) - 188), zIndex: 9995,
        background: "#fff", border: "1px solid var(--border)", borderRadius: 6,
        boxShadow: "0 6px 24px rgba(0,0,0,0.18)", padding: 4, minWidth: 176,
        fontFamily: "var(--font-sans, Inter, sans-serif)" }}
        onClick={(e) => e.stopPropagation()}>
          <button className="ctx-item" style={ctxStyle} onClick={() => fire(tourId)}>Ringkasan tile</button>
          <button className="ctx-item" style={ctxStyle} onClick={() => fire(null)}>Tour halaman ini</button>
        </div>
      }
    </section>);

}

// ===== Segmented control =====
function Segmented({ items, value, onChange }) {
  return (
    <div style={{ display: "inline-flex", border: "1px solid var(--border-strong)", borderRadius: 3, overflow: "hidden", height: 24 }}>
      {items.map((i) =>
      <button key={i}
      style={{
        background: value === i ? "var(--bg-tag)" : "var(--bg-panel)",
        color: value === i ? "var(--text-1)" : "var(--text-2)",
        fontWeight: value === i ? 600 : 500,
        border: 0,
        padding: "0 10px",
        fontSize: 11,
        borderRight: "1px solid var(--border-strong)"
      }}
      onClick={() => onChange(i)}>{i}</button>
      )}
    </div>);

}

// ===== Looker mockup-note =====
function LookerNote({ children }) {
  return <div className="lk-note"><strong>Looker:</strong> {children}</div>;
}

// ===== Guided Tour =====
const TOUR_STEPS_OV = [
  {
    id: "filter-row",
    title: "① Filter Global — Year · Month · Region · Province",
    desc: "Empat filter ini mengontrol seluruh dashboard. Di tahap development, judul tiap tile akan ikut DINAMIS mengikuti Year & Month yang dipilih (mis. \"… Mei 2026\"). Region & Province mempersempit data ke wilayah tertentu.",
    looker: "Dashboard filters native Looker. Year/Month → tanggal; Region/Province → dimensi wilayah. Filter di-pass ke semua tile via filter-binding."
  },
  {
    id: "pill-nav",
    title: "② Navigasi Halaman",
    desc: "Dashboard terdiri dari 4 halaman: Overview (ringkasan lintas-domain) + 3 halaman per domain — Moneter & Harga, Aktivitas Ekonomi, dan Otomotif & Mobilitas. Klik pill untuk berpindah halaman.",
    looker: "Beberapa dashboard ter-link, atau satu dashboard dengan navigasi tab. Looker mendukung cross-dashboard linking lewat tombol/teks."
  },
  {
    id: "kpi-strip",
    title: "③ Scorecard KPI — dikelompokkan per Domain",
    desc: "9 angka utama dikelompokkan ke 3 domain. Warna biru = Moneter & Aktivitas, oranye = Otomotif & Mobilitas (color-coding per domain). Klik salah satu scorecard untuk membuka halaman detail indikatornya; klik judul domain untuk membuka halaman domain.",
    looker: "Single Value viz × 9. Delta YoY dengan conditional formatting (hijau positif, merah negatif). Tile dikelompokkan dalam grid berlabel domain."
  },
  {
    id: "hero-moneter",
    title: "④ Sorotan Moneter — BI Rate vs Inflasi YoY",
    desc: "Dua garis bulanan dari Bank Indonesia: BI Rate dan Laju Inflasi YoY. Jarak antar-garis = gambaran real rate. Ini chart \"sorotan\" yang mewakili domain Moneter & Harga.",
    looker: "Line Chart dual-series, satu sumbu Y (%). Sumber: bi.go.id (bi-rate & data-inflasi), bulanan."
  },
  {
    id: "hero-aktivitas",
    title: "⑤ Sorotan Aktivitas — PDB Triwulan + Growth YoY",
    desc: "Bar = nilai PDB ADHK per triwulan; tinggi bar menunjukkan skala ekonomi. Chart sorotan untuk domain Aktivitas Ekonomi (lengkapnya: PDB, CCI, PMI di halaman domain).",
    looker: "Column + Line overlay (dual Y-axis). Sumber: BPS, triwulanan."
  },
  {
    id: "hero-otomotif",
    title: "⑥ Sorotan Otomotif — Tren Penjualan",
    desc: "Garis biru = mobil R4+ (K unit), garis oranye = motor R2 (÷10 agar proporsional). Chart sorotan domain Otomotif & Mobilitas. Catatan: sebagian seri bulanan masih ilustrasi untuk mockup.",
    looker: "Line Chart dual axis. Sumber: Gaikindo (mobil) & AISI (motor), bulanan."
  },
  {
    id: "peta-kendaraan",
    title: "⑦ Peta Sebaran Populasi Kendaraan",
    desc: "Choropleth dengan batas provinsi asli (GADM 4.1). Makin gelap = populasi kendaraan makin tinggi; Jawa mendominasi. Arahkan kursor ke wilayah untuk rincian per jenis kendaraan.",
    looker: "Map (Filled Map) native Looker. Dimension = provinsi/wilayah. Sumber: Korlantas ERI."
  },
  {
    id: "tabel-populasi-nasional",
    title: "⑧ Tabel Populasi Kendaraan Nasional",
    desc: "Rincian populasi per jenis kendaraan (Apr 2025 vs Apr 2026) dengan CAGR 2 tahun. Gaya tabel mengikuti Looker: header abu-abu, garis kolom, dan baris zebra.",
    looker: "Table viz dengan conditional formatting di kolom CAGR. Sumber: Korlantas ERI."
  }
];

// ===== Per-page tour steps (random-access via right-click "Ringkasan tile") =====
const TOUR_STEPS_AUTO = [
  { id: "pop-section", title: "Populasi Kendaraan Terdaftar", desc: "Bagian pertama: populasi kendaraan terdaftar nasional (data REAL Korlantas ERI). Berisi komposisi per jenis, peta sebaran per wilayah, pertumbuhan, dan tabel rinci. Indikator ini memakai aksen oranye (domain Otomotif & Mobilitas).", looker: "Halaman/section indikator. Filter Year/Month di atas akan menyetel periode secara dinamis." },
  { id: "pop-komposisi", title: "Komposisi per Jenis Kendaraan", desc: "Donut komposisi populasi kendaraan terdaftar per jenis. Sepeda motor mendominasi (±84%), disusul mobil penumpang. Total nasional ada di scorecard atas.", looker: "Pie/Donut viz. Dimension = jenis kendaraan; measure = unit terdaftar. Sumber: Korlantas ERI." },
  { id: "pop-peta", title: "Peta Sebaran Populasi per Wilayah", desc: "Choropleth dengan batas provinsi asli (GADM 4.1). Makin gelap = populasi makin tinggi; Jawa mendominasi. Arahkan kursor ke wilayah untuk rincian per jenis.", looker: "Filled Map (Google Geo chart · admin1/provinsi). Measure = total populasi." },
  { id: "pop-cagr", title: "Pertumbuhan Populasi per Wilayah", desc: "CAGR - Compound Annual Growth Rate (Laju Pertumbuhan Tahunan Majemuk)\nBar horizontal laju pertumbuhan populasi tiap wilayah. 2Y CAGR = rata-rata pertumbuhan PER TAHUN (majemuk) selama 2 tahun terakhir\nrumus: (Nilai Akhir ÷ Nilai Awal)^(1/2) − 1. Dipakai agar antar-wilayah adil dibandingkan (per tahun), \nmis. Kalimantan 7,0%/th vs Jawa 4,0%/th. CATATAN: angka CAGR ini turunan/ilustratif untuk mockup — saat development dihitung dari ≥2 periode data Korlantas.", looker: "Bar Chart horizontal. Sort desc; measure = CAGR 2 tahun (table calculation: pow(akhir/awal, 1/2) − 1)." },
  { id: "pop-top10", title: "Top 10 Provinsi", desc: "Ranking 10 provinsi dengan populasi kendaraan terbesar. Sel kolom share diwarnai (conditional formatting) — makin besar share, makin gelap.", looker: "Table viz + conditional formatting. Sort desc, limit 10." },
  { id: "pop-tabel", title: "Populasi per Jenis (Tahunan)", desc: "Tabel populasi per jenis kendaraan 2025 vs 2026 berikut CAGR. Baris Total disorot. Tombol CSV untuk unduh.", looker: "Table viz. Kolom CAGR conditional formatting; CSV download native." },
  { id: "motor-section", title: "Penjualan Motor R2 (AISI)", desc: "Bagian kedua: penjualan sepeda motor (sumber AISI). Berisi tren bulanan, komposisi segmen, domestik vs ekspor, dan data mentah. CATATAN: AISI tidak merilis penjualan per-MEREK ke publik, jadi tile Top Merek ditiadakan. Pecahan segmen/channel masih angka ilustrasi untuk mockup.", looker: "Section indikator. Sumber: AISI (aisi.or.id/statistic)." },
  { id: "motor-tren", title: "Tren Penjualan Motor R2", desc: "Garis penjualan motor bulanan (ribu unit). CATATAN: angka ilustrasi — menanti feed bulanan AISI.", looker: "Line Chart. Dimension = bulan; measure = unit domestik (wholesales). Sumber: AISI." },
  { id: "motor-segmen", title: "Komposisi Segmen Motor", desc: "Donut pangsa per segmen AISI: Skutik (AT) dominan, lalu Bebek, Sport, dan Listrik yang tumbuh. Angka ilustrasi.", looker: "Pie/Donut. Dimension = segmen (AT/Cub/Sport/EV)." },
  { id: "motor-channel", title: "Domestik vs Ekspor (Motor)", desc: "Donut perbandingan penjualan domestik (wholesales) vs ekspor (CBU/CKD). Porsi tiap channel terbaca dari legend. Angka ilustrasi.", looker: "Pie/Donut. Dimension = channel (domestik/ekspor)." },
  { id: "motor-segtabel", title: "Penjualan per Segmen (Motor)", desc: "Tabel unit & share tiap segmen motor. Angka ilustrasi.", looker: "Table viz + conditional formatting di kolom share." },
  { id: "motor-raw", title: "Data Mentah Motor (Bulanan)", desc: "Tabel nilai bulanan + perubahan MoM. Tombol CSV untuk unduh.", looker: "Table viz. Kolom MoM = table calculation; CSV download native." },
  { id: "mobil-section", title: "Penjualan Mobil R4+ (Gaikindo)", desc: "Bagian ketiga: penjualan mobil R4+ (sumber Gaikindo). Berisi tren bulanan, komposisi segmen, top merek, domestik vs ekspor, dan data mentah. Catatan: pecahan segmen/merek/channel masih angka ilustrasi untuk mockup.", looker: "Section indikator. Sumber: Gaikindo (gaikindo.or.id)." },
  { id: "mobil-tren", title: "Tren Penjualan Mobil R4+", desc: "Garis penjualan mobil bulanan (ribu unit). CATATAN: angka ilustrasi — menanti feed bulanan Gaikindo.", looker: "Line Chart. Dimension = bulan; measure = unit wholesales. Sumber: Gaikindo." },
  { id: "mobil-segmen", title: "Komposisi Segmen Mobil", desc: "Donut pangsa per segmen Gaikindo: MPV/SUV dominan, lalu komersial, xEV (hybrid/BEV), sedan, bus. Ilustrasi.", looker: "Pie/Donut. Dimension = segmen kendaraan." },
  { id: "mobil-merek", title: "Top Merek Mobil", desc: "Bar horizontal pangsa penjualan per merek. Toyota & Daihatsu memimpin. Angka ilustrasi.", looker: "Bar Chart horizontal. Dimension = merek; sort desc by unit." },
  { id: "mobil-channel", title: "Domestik vs Ekspor (Mobil)", desc: "Donut domestik vs ekspor CBU — Indonesia hub ekspor CBU. Porsi tiap channel terbaca dari legend. Angka ilustrasi.", looker: "Pie/Donut. Dimension = channel (domestik/ekspor)." },
  { id: "mobil-segtabel", title: "Penjualan per Segmen (Mobil)", desc: "Tabel unit & share tiap segmen mobil. Angka ilustrasi.", looker: "Table viz + conditional formatting di kolom share." },
  { id: "mobil-raw", title: "Data Mentah Mobil (Bulanan)", desc: "Tabel nilai bulanan + perubahan MoM. Tombol CSV untuk unduh.", looker: "Table viz. Kolom MoM = table calculation; CSV download native." },
];
const TOUR_STEPS_MONETER = [
  { title: "Tour — Moneter & Harga", desc: "Halaman ini memuat detail CPI, Laju Inflasi YoY, dan BI Rate (tren historis, kontributor, dan data mentah). Klik-kanan sebuah tile untuk membuka ringkasannya.", looker: "Panduan per-tile untuk halaman ini akan dilengkapi saat halaman dipoles." },
];
const TOUR_STEPS_AKTIVITAS = [
  // ===== PDB / PDRB =====
  { id: "akt-pdb-section", title: "PDB / PDRB — Produk Domestik Bruto", desc: "Bagian pertama: ukuran skala ekonomi (BPS, triwulanan). ADHK = Atas Dasar Harga Konstan (riil, base 2010) untuk melihat pertumbuhan; ADHB = harga berlaku (nominal). Indikator domain Aktivitas Ekonomi memakai aksen biru.", looker: "Section indikator. Filter Year/Month di atas menyetel periode. Sumber: BPS (bps.go.id)." },
  { id: "akt-pdb-tren", title: "Tren PDB ADHK (Riil) + Growth YoY", desc: "Bar = nilai PDB Atas Dasar Harga Konstan (riil, base 2010), Rp ribuan triliun (sumbu kiri); garis merah = pertumbuhan YoY riil (%, sumbu kanan). Inilah angka ~5% yang dikutip sebagai \"pertumbuhan ekonomi\". Dipisah dari ADHB karena Looker native tak punya toggle ganti field di satu tile.", looker: "Column + Line combo, dual Y-axis (sumbu kanan diaktifkan di Y-axis settings). Satu tile = satu field — tanpa toggle." },
  { id: "akt-pdb-tren-adhb", title: "Tren PDB ADHB (Nominal) + Growth YoY", desc: "Bar ungu = nilai PDB Atas Dasar Harga Berlaku (nominal, harga periode berjalan); garis oranye = growth YoY nominal (%, sumbu kanan). Selalu lebih tinggi dari riil karena memuat inflasi/deflator. 4 triwulan awal belum bergaris karena butuh basis tahun sebelumnya.", looker: "Tile terpisah untuk field ADHB. Growth nominal = table calculation YoY; titik tanpa basis (i<4) otomatis kosong." },
  { id: "akt-pdb-pulau", title: "Distribusi PDRB per Pulau", desc: "Donut pangsa PDRB tiap pulau. Jawa mendominasi (~57%) — ekonomi masih Jawa-sentris. Total tak ditulis di tengah donut (mengikuti perilaku donut native Looker).", looker: "Pie/Donut viz. Dimension = pulau; measure = share PDRB. Tanpa label tengah (native)." },
  { id: "akt-pdb-struktur", title: "Struktur PDB per Lapangan Usaha", desc: "Tabel kontribusi tiap sektor (nilai, share, growth YoY). Klik header kolom untuk mengurutkan. Sel Share diwarnai (conditional formatting) — makin besar, makin pekat. Industri Pengolahan & Perdagangan penyumbang terbesar.", looker: "Table viz. Sort interaktif via header kolom; conditional formatting pada kolom Share." },
  { id: "akt-pdb-topsektor", title: "Top Sektor Pertumbuhan", desc: "Bar horizontal sektor dengan growth YoY tertinggi. Info-Komunikasi & Transportasi tumbuh paling cepat. Diurut menurun.", looker: "Bar Chart horizontal. Sort desc by growth; limit 6." },
  { id: "akt-pdb-raw", title: "Data Mentah PDB Triwulanan", desc: "Tabel ADHK, ADHB, Growth YoY, dan QoQ per triwulan. PENTING: kolom Growth YoY & QoQ keduanya dihitung dari ADHK (riil), bukan ADHB — sesuai konvensi BPS agar bebas efek inflasi. YoY = vs triwulan sama tahun lalu (mundur 4 kuartal); QoQ = vs triwulan sebelumnya (mundur 1 kuartal). Di dashboard live, baris teratas pun terisi karena Looker memakai riwayat penuh (jendela 12 triwulan hanya filter tampilan). Tombol CSV untuk unduh.", looker: "Table viz. QoQ = table calculation offset(1); YoY = offset(4) dari measure ADHK. Filter 12-triwulan = filter tampilan, tak memotong basis perhitungan. CSV native." },
  // ===== CCI =====
  { id: "akt-cci-section", title: "CCI — Indeks Keyakinan Konsumen", desc: "Bagian kedua: optimisme konsumen. > 100 = optimistis, < 100 = pesimistis; mengukur persepsi rumah tangga atas kondisi & ekspektasi ekonomi. CATATAN: sumber (TradingEconomics) hanya menyediakan angka HEADLINE — sub-indeks & distribusi sentimen tidak tersedia, jadi halaman ini fokus ke tren + data mentah.", looker: "Section indikator. Sumber: TradingEconomics (indonesia/consumer-confidence)." },
  { id: "akt-cci-tren", title: "Tren CCI Bulanan + Garis Netral", desc: "Garis indeks bulanan dengan garis acuan di 100 (reference line). Di atas 100 = zona optimistis; di bawah = pesimistis. Pantau arahnya sebagai sinyal daya beli konsumen. Diambil langsung dari nilai headline CCI bulanan.", looker: "Line Chart + Reference Line (value = 100). Dimension = bulan; measure = CCI. Sumber: TradingEconomics." },
  { id: "akt-cci-raw", title: "Data Mentah CCI", desc: "Tabel CCI bulanan + perubahan MoM + label status (Optimis bila ≥ 100, Pesimis bila < 100). MoM diturunkan dari selisih antar-bulan. Tombol CSV untuk unduh.", looker: "Table viz; kolom MoM = table calculation; Status via threshold 100. CSV native. Sumber: TradingEconomics." },
  // ===== PMI =====
  { id: "akt-pmi-section", title: "PMI Manufaktur", desc: "Bagian ketiga: kesehatan sektor manufaktur (TradingEconomics, bulanan). Ambang 50 adalah kunci — ≥ 50 = ekspansi, < 50 = kontraksi. Indikator paling dini untuk arah industri. CATATAN: seri bulanan ilustrasi; titik akhir disetel = headline 50,0.", looker: "Section indikator. Sumber: TradingEconomics (indonesia/manufacturing-pmi)." },
  { id: "akt-pmi-tren", title: "Tren PMI + Ambang Ekspansi", desc: "Garis PMI bulanan dengan garis acuan di 50 (reference line). Saat garis menembus ke bawah 50, manufaktur masuk fase kontraksi; Mei 2026 kembali tepat di ambang 50,0.", looker: "Line Chart + Reference Line (value = 50), di-set pada Y-axis settings." },
  { id: "akt-pmi-ambang", title: "PMI vs Ambang 50", desc: "Bar dengan baseline di 50: batang HIJAU naik = ekspansi, batang MERAH turun = kontraksi. Cara cepat melihat bulan mana yang di atas/bawah ambang.", looker: "Bar Chart dengan baseline = 50 + conditional color (hijau/merah berdasar nilai vs 50)." },
  { id: "akt-pmi-status", title: "Status Bulanan Manufaktur", desc: "Tabel PMI bulanan + MoM + YoY + label Ekspansi/Kontraksi. MoM membandingkan ke bulan sebelumnya (baris pertama kosong karena tak ada bulan sebelum di jendela); YoY membandingkan ke bulan yang sama tahun lalu — di mockup pakai riwayat tersembunyi (Opsi A) karena jendela tampilan hanya 12 bulan. Tombol CSV untuk unduh.", looker: "Table viz + conditional formatting (threshold 50). MoM/YoY = table calc offset(1)/offset(12) dari riwayat penuh; jendela 12 bulan = filter tampilan saja." },
];
const TOUR_STEPS = {
  overview: TOUR_STEPS_OV,
  otomotif: TOUR_STEPS_AUTO,
  moneter: TOUR_STEPS_MONETER,
  aktivitas: TOUR_STEPS_AKTIVITAS,
};
const TOUR_LABEL = {
  overview: "Panduan Overview",
  otomotif: "Panduan Otomotif & Mobilitas",
  moneter: "Panduan Moneter & Harga",
  aktivitas: "Panduan Aktivitas Ekonomi",
};

function GuidedTour({ active, page = "overview", startId, onClose }) {
  const steps = TOUR_STEPS[page] || [];
  const [stepIdx, setStepIdx] = useStateC(0);
  const [rect, setRect] = useStateC(null);
  const current = steps[stepIdx];
  const anchorless = current && !current.id;

  useEffectC(() => {
    if (!active) { setRect(null); return; }
    let idx = 0;
    if (startId) { const i = steps.findIndex((s) => s.id === startId); if (i >= 0) idx = i; }
    setStepIdx(idx);
    setRect(null);
  }, [active, page, startId]);

  useEffectC(() => {
    if (!active || !current) return;
    if (anchorless) { setRect(null); return; }
    setRect(null);

    let tries = 0;
    let cleanupResize = null;
    let pollTimer = null;
    let rectTimer = null;

    const locate = () => {
      const el = document.querySelector(`[data-tour="${current.id}"]`);
      if (!el) {
        // Anchor not (yet) in DOM. Retry a few frames, then skip to next step
        // so the tour can never get stuck on a missing/renamed anchor.
        if (tries++ < 12) { pollTimer = setTimeout(locate, 120); return; }
        if (stepIdx < steps.length - 1) setStepIdx(stepIdx + 1); else onClose();
        return;
      }
      const rawTop = el.getBoundingClientRect().top + window.pageYOffset;
      window.scrollTo({ top: Math.max(0, rawTop - window.innerHeight * 0.28), behavior: "auto" });
      const updateRect = () => {
        const r = el.getBoundingClientRect();
        setRect({ top: r.top, left: r.left, width: r.width, height: r.height });
      };
      rectTimer = setTimeout(updateRect, 120);
      window.addEventListener("resize", updateRect);
      window.addEventListener("scroll", updateRect, { passive: true });
      cleanupResize = () => {
        window.removeEventListener("resize", updateRect);
        window.removeEventListener("scroll", updateRect);
      };
    };

    locate();
    return () => {
      if (pollTimer) clearTimeout(pollTimer);
      if (rectTimer) clearTimeout(rectTimer);
      if (cleanupResize) cleanupResize();
    };
  }, [active, stepIdx, page]);

  if (!active || !current) return null;

  const TW = 346;
  if (!anchorless && !rect) return (
    <div style={{ position: "fixed", inset: 0, background: "rgba(8,16,36,0.72)", zIndex: 9990,
      display: "flex", alignItems: "center", justifyContent: "center" }}>
      <div style={{ color: "#5dbbeb", fontSize: 13, fontFamily: "var(--font-sans, Inter, sans-serif)" }}>
        Memuat panduan…
      </div>
    </div>
  );

  const PAD = 8;
  const hl = anchorless ? null : { top: rect.top - PAD, left: rect.left - PAD, width: rect.width + PAD * 2, height: rect.height + PAD * 2 };
  let cardPos;
  if (anchorless) {
    cardPos = { top: "50%", left: "50%", transform: "translate(-50%,-50%)" };
  } else {
    const tLeft = Math.max(8, Math.min(hl.left, window.innerWidth - TW - 8));
    const spaceBelow = window.innerHeight - (hl.top + hl.height);
    const spaceAbove = hl.top;
    const tTop = (spaceBelow >= 220 || spaceBelow >= spaceAbove) ?
    hl.top + hl.height + 12 :
    Math.max(8, hl.top - 224);
    cardPos = { top: tTop, left: tLeft, transform: "none" };
  }

  const goTo = (idx) => { setStepIdx(idx); setRect(null); };

  return (
    <>
      {/* Click-to-close backdrop layer */}
      <div onClick={onClose} style={{ position: "fixed", inset: 0, zIndex: 9989, cursor: "default" }} />

      {/* Spotlight highlight with box-shadow dimming */}
      {hl ?
      <div style={{
        position: "fixed", top: hl.top, left: hl.left, width: hl.width, height: hl.height,
        border: "2px solid #2eb5e6", borderRadius: 5,
        boxShadow: "0 0 0 9999px rgba(8,16,36,0.70)",
        zIndex: 9990, pointerEvents: "none",
        transition: "top 0.32s ease, left 0.32s ease, width 0.32s ease, height 0.32s ease"
      }} /> :
      <div style={{ position: "fixed", inset: 0, background: "rgba(8,16,36,0.70)", zIndex: 9990, pointerEvents: "none" }} />
      }

      {/* Tooltip card */}
      <div style={{
        position: "fixed", top: cardPos.top, left: cardPos.left, transform: cardPos.transform, width: TW,
        background: "#0c1a36", color: "white", borderRadius: 8,
        padding: "14px 16px 12px", zIndex: 9991,
        boxShadow: "0 8px 36px rgba(0,0,0,0.6), 0 0 0 1px #1e3566",
        fontFamily: "var(--font-sans, Inter, sans-serif)",
        transition: "top 0.32s ease, left 0.32s ease"
      }}>
        {/* Header */}
        <div style={{ display: "flex", justifyContent: "space-between", alignItems: "center", marginBottom: 9 }}>
          <span style={{ fontSize: 10.5, fontWeight: 700, letterSpacing: 0.9, color: "#5dbbeb", textTransform: "uppercase" }}>
            {TOUR_LABEL[page] || "Panduan"} &nbsp;·&nbsp; {stepIdx + 1} / {steps.length}
          </span>
          <button onClick={onClose} style={{ background: "none", border: "none", color: "#5a7090", cursor: "pointer", fontSize: 15, lineHeight: 1, padding: "0 2px" }}>✕</button>
        </div>

        {/* Title */}
        <div style={{ fontSize: 13.5, fontWeight: 700, lineHeight: 1.35, color: "white", marginBottom: 7 }}>
          {current.title}
        </div>

        {/* Description */}
        <div style={{ fontSize: 12, lineHeight: 1.62, color: "#b0c4de", marginBottom: 9, whiteSpace: "pre-line" }}>
          {current.desc}
        </div>

        {/* Looker note */}
        {current.looker &&
        <div style={{ fontSize: 11, lineHeight: 1.5, color: "#6a9fbf",
          borderTop: "1px solid #162850", paddingTop: 8, marginBottom: 11 }}>
          <strong style={{ color: "#2eb5e6" }}>Looker viz:</strong> {current.looker}
        </div>
        }

        {/* Navigation buttons */}
        <div style={{ display: "flex", gap: 6, alignItems: "center" }}>
          <button onClick={() => goTo(stepIdx - 1)} disabled={stepIdx === 0}
            style={{ background: "transparent", border: "1px solid #1b2f55",
              color: stepIdx === 0 ? "#2d4060" : "#8aaac8",
              borderRadius: 4, padding: "5px 11px", fontSize: 11.5,
              cursor: stepIdx === 0 ? "default" : "pointer" }}>
            ← Sebelumnya
          </button>
          <div style={{ flex: 1 }} />
          {stepIdx < steps.length - 1 ? (
            <button onClick={() => goTo(stepIdx + 1)}
              style={{ background: "#1565b8", border: "none", color: "white",
                borderRadius: 4, padding: "5px 15px", fontSize: 11.5,
                cursor: "pointer", fontWeight: 600 }}>
              Selanjutnya →
            </button>
          ) : (
            <button onClick={onClose}
              style={{ background: "#1b7a45", border: "none", color: "white",
                borderRadius: 4, padding: "5px 15px", fontSize: 11.5,
                cursor: "pointer", fontWeight: 600 }}>
              Selesai ✓
            </button>
          )}
        </div>
      </div>

      {/* Progress dots */}
      <div style={{ position: "fixed", bottom: 18, left: "50%", transform: "translateX(-50%)",
        display: "flex", gap: 5, zIndex: 9992, pointerEvents: "none" }}>
        {steps.map((_, i) => (
          <div key={i} style={{
            width: i === stepIdx ? 20 : 6, height: 6, borderRadius: 3,
            background: i === stepIdx ? "#2eb5e6" : i < stepIdx ? "#1f4a80" : "#141e34",
            transition: "all 0.3s"
          }} />
        ))}
      </div>
    </>
  );
}

Object.assign(window, {
  Icon, DeltaCell,
  TopBar, TitleBar, FilterRow, FilterField, PillNav, SectionBand,
  KpiTile, Tile, Segmented, LookerNote, GuidedTour
});