/* global React, ReactDOM */
const { useState, useMemo, useEffect, useRef } = React;

// ============================================================
// UTILS — parse messy spec strings into numbers for sparklines
// ============================================================
const NBSP = /\u00a0|\s/g;

// Returns [minKm, maxKm] or null
function parseRange(s) {
  if (!s || s === "—") return null;
  const m = s.replace(NBSP, "").match(/(\d+(?:\.\d+)?)(?:–(\d+(?:\.\d+)?))?/);
  if (!m) return null;
  const a = parseFloat(m[1]);
  const b = m[2] ? parseFloat(m[2]) : a;
  return [a, b];
}
// Endurance in minutes
function parseEndurance(s) {
  if (!s || s === "—") return null;
  const x = s.replace(NBSP, "");
  // hours pattern "3 год", "18–27 хв", "6–8 год", "3.75 год", "8–10 год"
  const m = x.match(/(\d+(?:\.\d+)?)(?:–(\d+(?:\.\d+)?))?\s*(год|хв)/);
  if (!m) return null;
  const a = parseFloat(m[1]);
  const b = m[2] ? parseFloat(m[2]) : a;
  const mul = m[3] === "год" ? 60 : 1;
  return [a * mul, b * mul];
}
// Price in UAH
function parsePrice(s) {
  if (!s || s === "—") return null;
  const x = s.replace(NBSP, "");
  const num = x.replace(/[₴$ ]/g, "");
  const m = num.match(/(\d+(?:\.\d+)?)(k|M)?(?:–(\d+(?:\.\d+)?)(k|M)?)?/);
  if (!m) return null;
  const scale = (suf) => suf === "M" ? 1_000_000 : suf === "k" ? 1_000 : 1;
  const a = parseFloat(m[1]) * scale(m[2] || "");
  const b = m[3] ? parseFloat(m[3]) * scale(m[4] || m[2] || "") : a;
  return [a, b];
}

function fmtUsd(v) {
  if (v == null) return "—";
  if (v >= 1_000_000) return `₴${(v/1_000_000).toFixed(1).replace(/\.0$/, "")}М`;
  if (v >= 1000) return `₴${Math.round(v/1000)}к`;
  return `₴${Math.round(v)}`;
}

// ============================================================
// DATA ENRICHMENT
// ============================================================
function buildNiches() {
  return window.UAV_NICHES.map(n => {
    const models = window.UAV_DATA.filter(d => window.TYPE_TO_NICHE[d.type] === n.id);
    const ours = models.filter(m => window.IS_OURS(m.maker));
    const competitors = models.filter(m => !window.IS_OURS(m.maker));
    // aggregated specs
    const prices = models.map(m => parsePrice(m.price)).filter(Boolean);
    const ranges = models.map(m => parseRange(m.range)).filter(Boolean);
    const priceMin = prices.length ? Math.min(...prices.map(p => p[0])) : null;
    const priceMax = prices.length ? Math.max(...prices.map(p => p[1])) : null;
    const rangeMin = ranges.length ? Math.min(...ranges.map(r => r[0])) : null;
    const rangeMax = ranges.length ? Math.max(...ranges.map(r => r[1])) : null;
    return {
      ...n, models, ours, competitors,
      hasUs: ours.length > 0,
      isAbsent: models.length === 0,
      priceMin, priceMax, rangeMin, rangeMax,
    };
  });
}

function buildStats(niches) {
  const totalModels = niches.reduce((a, n) => a + n.models.length, 0);
  const ourModels = niches.reduce((a, n) => a + n.ours.length, 0);
  const ourNiches = niches.filter(n => n.hasUs).length;
  const absentNiches = niches.filter(n => n.isAbsent).length;
  const competitorOnly = niches.filter(n => !n.hasUs && !n.isAbsent).length;
  const makers = new Set(window.UAV_DATA.map(m => m.maker).filter(m => m && m !== "—"));
  return { totalModels, ourModels, ourNiches, absentNiches, competitorOnly, makers: makers.size };
}

// ============================================================
// SMALL ATOMS
// ============================================================
const Dot = ({ color, size = 8 }) => (
  <span style={{ display: "inline-block", width: size, height: size, background: color, borderRadius: "50%", flex: "0 0 auto" }}/>
);

const RangeBar = ({ min, max, niceMin, niceMax, color = "#fff", height = 4, label }) => {
  if (min == null) return <div style={{ height, background: "rgba(255,255,255,0.05)" }}/>;
  const left = ((min - niceMin) / (niceMax - niceMin)) * 100;
  const width = Math.max(2, ((max - min) / (niceMax - niceMin)) * 100);
  return (
    <div style={{ position: "relative", height, background: "rgba(255,255,255,0.06)", overflow: "visible" }}>
      <div style={{ position: "absolute", left: `${left}%`, width: `${width}%`, top: 0, bottom: 0, background: color }}/>
      {label && <div style={{ position: "absolute", right: 0, top: -14, fontSize: 9, color: "rgba(255,255,255,0.5)", fontFamily: "var(--mono-num)" }}>{label}</div>}
    </div>
  );
};

// presence/status pill
const StatusPill = ({ kind, accent }) => {
  const map = {
    ours: { bg: `color-mix(in srgb, ${accent} 18%, transparent)`, fg: accent, border: accent, label: "🍒 присутні" },
    competitor: { bg: "rgba(255,255,255,0.04)", fg: "rgba(255,255,255,0.7)", border: "rgba(255,255,255,0.25)", label: "конкуренти" },
    absent: { bg: "rgba(217,170,80,0.08)", fg: "#d9aa50", border: "rgba(217,170,80,0.4)", label: "⌀ вільна" },
  };
  const s = map[kind];
  return (
    <span style={{
      fontSize: 10, fontWeight: 600, color: s.fg, background: s.bg,
      border: `1px solid ${s.border}`, padding: "2px 7px", letterSpacing: 0.3,
      textTransform: "uppercase", borderRadius: 999, fontFamily: "var(--sketch)",
      whiteSpace: "nowrap",
    }}>{s.label}</span>
  );
};

// ============================================================
// KPI STRIP
// ============================================================
function KpiStrip({ stats, accent }) {
  const tiles = [
    { kpi: stats.ourNiches, total: stats.ourNiches + stats.competitorOnly + stats.absentNiches, label: "Ніш з нашим продуктом", tone: "ours" },
    { kpi: stats.competitorOnly, label: "Тільки конкуренти", sub: "потенційне витіснення", tone: "neutral" },
    { kpi: stats.absentNiches, label: "Білих плям", sub: "ніша вільна", tone: "warn" },
    { kpi: stats.ourModels, total: stats.totalModels, label: "Моделей у каталозі", sub: `${stats.makers} виробників`, tone: "ours" },
  ];
  return (
    <div style={{
      display: "grid", gridTemplateColumns: "repeat(4, 1fr)", gap: 1,
      background: "rgba(255,255,255,0.08)", border: "1px solid rgba(255,255,255,0.08)",
      marginBottom: 16,
    }}>
      {tiles.map((t, i) => {
        const color = t.tone === "ours" ? accent : t.tone === "warn" ? "#d9aa50" : "#fff";
        return (
          <div key={i} style={{
            background: "#080808", padding: "16px 18px", display: "flex", flexDirection: "column", gap: 4,
            position: "relative", overflow: "hidden",
          }}>
            <div style={{ fontSize: 10, opacity: 0.5, textTransform: "uppercase", letterSpacing: 1.5, fontWeight: 600 }}>
              {t.label}
            </div>
            <div style={{ display: "flex", alignItems: "baseline", gap: 8 }}>
              <div style={{ fontFamily: "var(--mono-num)", fontSize: 36, fontWeight: 700, color, lineHeight: 1, letterSpacing: -1 }}>
                {t.kpi}
              </div>
              {t.total != null && (
                <div style={{ fontFamily: "var(--mono-num)", fontSize: 14, opacity: 0.4 }}>
                  / {t.total}
                </div>
              )}
            </div>
            <div style={{ fontSize: 11, opacity: 0.55, marginTop: 2 }}>
              {t.sub || (t.total ? `${Math.round(100 * t.kpi / t.total)}% покриття` : "\u00a0")}
            </div>
            {/* progress sliver */}
            {t.total != null && (
              <div style={{ height: 2, background: "rgba(255,255,255,0.06)", marginTop: 6 }}>
                <div style={{ height: "100%", width: `${100 * t.kpi / t.total}%`, background: color }}/>
              </div>
            )}
          </div>
        );
      })}
    </div>
  );
}

// ============================================================
// COMMAND BAR (top) — title strip with live indicator
// ============================================================
function CommandBar({ accent }) {
  const [now, setNow] = useState(() => new Date());
  const [exporting, setExporting] = useState(false);
  useEffect(() => { const t = setInterval(() => setNow(new Date()), 1000); return () => clearInterval(t); }, []);
  const ts = now.toLocaleTimeString("uk-UA", { hour: "2-digit", minute: "2-digit", second: "2-digit" });

  async function handleExport() {
    if (exporting) return;
    setExporting(true);
    try {
      const d = new Date();
      const dd   = String(d.getDate()).padStart(2, "0");
      const mm   = String(d.getMonth() + 1).padStart(2, "0");
      const yyyy = d.getFullYear();
      const filename = `UAV Landscape Dashboard ${dd}.${mm}.${yyyy}.pdf`;

      const el = document.getElementById("root");
      const W  = el.scrollWidth;
      const H  = el.scrollHeight;

      // dom-to-image-more renders via SVG foreignObject so the browser's own
      // CSS engine handles color-mix(), gradients, etc. — no manual patching needed.
      const dataUrl = await window.domtoimage.toJpeg(el, {
        bgcolor: "#000000",
        quality: 0.92,
        width: W,
        height: H,
      });

      const { jsPDF } = window.jspdf;
      const pdf = new jsPDF({ orientation: "landscape", unit: "px", format: [W, H], compress: true });
      pdf.addImage(dataUrl, "JPEG", 0, 0, W, H);
      pdf.save(filename);
    } catch (e) {
      console.error("Export failed:", e);
      alert("Помилка експорту: " + (e.message || e));
    } finally {
      setExporting(false);
    }
  }

  return (
    <div style={{
      display: "flex", alignItems: "center", gap: 14, padding: "12px 20px",
      borderBottom: "1px solid rgba(255,255,255,0.08)", background: "#050505",
    }}>
      <div style={{ display: "flex", alignItems: "center", gap: 10 }}>
        <div style={{
          width: 26, height: 26, borderRadius: 6, background: accent, color: "#000",
          display: "grid", placeItems: "center", fontWeight: 800, fontSize: 14, fontFamily: "var(--sketch)",
        }}>🍒</div>
        <div style={{ display: "flex", flexDirection: "column", lineHeight: 1.1 }}>
          <div style={{ fontSize: 13, fontWeight: 700, letterSpacing: 0.3 }}>Gen Cherry · Інтелідженс</div>
          <div style={{ fontSize: 10, opacity: 0.45, letterSpacing: 1.5, textTransform: "uppercase" }}>Ландшафт БПЛА · v2.0</div>
        </div>
      </div>
      <div style={{ flex: 1 }}/>
      <div style={{ display: "flex", alignItems: "center", gap: 6, fontSize: 11, opacity: 0.6 }}>
        <Dot color={accent} size={6}/>
        <span style={{ fontFamily: "var(--mono-num)" }}>LIVE</span>
        <span style={{ opacity: 0.5 }}>·</span>
        <span style={{ fontFamily: "var(--mono-num)" }}>{ts}</span>
      </div>
      <div style={{ width: 1, height: 16, background: "rgba(255,255,255,0.12)" }}/>
      <div style={{ fontSize: 11, opacity: 0.5 }}>оновлено сьогодні · {window.UAV_DATA.length} моделей</div>
      <button onClick={handleExport} disabled={exporting} style={{
        background: exporting ? "rgba(255,255,255,0.06)" : "transparent",
        border: "1px solid rgba(255,255,255,0.2)", color: "#fff",
        padding: "5px 10px", fontSize: 11,
        cursor: exporting ? "default" : "pointer",
        fontFamily: "var(--sketch)", borderRadius: 4,
        opacity: exporting ? 0.6 : 1,
        transition: "opacity 150ms",
        minWidth: 90,
      }}>{exporting ? "⏳ Генерація…" : "↓ Експорт PDF"}</button>
    </div>
  );
}

// ============================================================
// FILTERS + SORT
// ============================================================
function ControlBar({ niche, maker, onNiche, onMaker, search, onSearch, presence, onPresence, sort, onSort, accent, counts }) {
  const niches = ["Усі", ...new Set(window.UAV_NICHES.map(n => n.title))];
  const makers = ["Усі", ...new Set(window.UAV_DATA.map(d => d.maker).filter(m => m && m !== "—"))];
  return (
    <div style={{
      display: "flex", gap: 10, alignItems: "center", flexWrap: "wrap",
      padding: "12px 20px", borderBottom: "1px solid rgba(255,255,255,0.08)",
      background: "#080808", position: "sticky", top: 0, zIndex: 50, backdropFilter: "blur(8px)",
    }}>
      <div style={{ display: "flex", gap: 6 }}>
        <Pill active={presence === "all"} onClick={() => onPresence("all")} count={counts.all}>Усі</Pill>
        <Pill active={presence === "ours"} onClick={() => onPresence("ours")} variant="ours" accent={accent} count={counts.ours}>🍒 Наші</Pill>
        <Pill active={presence === "competitor"} onClick={() => onPresence("competitor")} variant="neutral" count={counts.competitor}>Конкуренти</Pill>
        <Pill active={presence === "absent"} onClick={() => onPresence("absent")} variant="warn" count={counts.absent}>⌀ Білі плями</Pill>
      </div>
      <div style={{ width: 1, height: 22, background: "rgba(255,255,255,0.1)" }}/>
      <Sel value={niche} onChange={onNiche} options={niches} label="Ніша"/>
      <Sel value={maker} onChange={onMaker} options={makers} label="Виробник"/>
      <Sel value={sort} onChange={onSort} options={[
        { v: "default", l: "За замовч." },
        { v: "count-desc", l: "За к-стю ↓" },
        { v: "count-asc", l: "За к-стю ↑" },
        { v: "alpha", l: "Абетка" },
        { v: "ours-first", l: "Спочатку наші" },
      ]} label="Сорт."/>
      <div style={{ flex: 1, minWidth: 180, position: "relative" }}>
        <span style={{ position: "absolute", left: 10, top: "50%", transform: "translateY(-50%)", opacity: 0.4, fontSize: 12 }}>⌕</span>
        <input value={search} onChange={e => onSearch(e.target.value)} placeholder="Пошук моделі або виробника…"
          style={{
            width: "100%", background: "#0a0a0a", border: "1px solid rgba(255,255,255,0.12)", color: "#fff",
            padding: "7px 10px 7px 28px", fontFamily: "var(--sketch)", fontSize: 12, outline: "none", borderRadius: 4,
          }}/>
      </div>
    </div>
  );
}
const Pill = ({ active, onClick, children, variant, accent, count }) => {
  const colors = variant === "ours" ? { bg: `color-mix(in srgb, ${accent} 18%, transparent)`, bd: accent, fg: accent }
                 : variant === "warn" ? { bg: "rgba(217,170,80,0.12)", bd: "#d9aa50", fg: "#d9aa50" }
                 : variant === "neutral" ? { bg: "rgba(255,255,255,0.06)", bd: "rgba(255,255,255,0.4)", fg: "#fff" }
                 : { bg: "rgba(255,255,255,0.08)", bd: "#fff", fg: "#fff" };
  return (
    <button onClick={onClick} style={{
      display: "inline-flex", alignItems: "center", gap: 6,
      background: active ? colors.bg : "transparent", color: active ? colors.fg : "rgba(255,255,255,0.65)",
      border: `1px solid ${active ? colors.bd : "rgba(255,255,255,0.15)"}`,
      padding: "5px 11px", fontFamily: "var(--sketch)", fontSize: 12, cursor: "pointer",
      borderRadius: 4, fontWeight: 500, transition: "all 120ms",
    }}>
      {children}
      {count != null && (
        <span style={{
          fontFamily: "var(--mono-num)", fontSize: 10, opacity: active ? 0.85 : 0.45,
          padding: "0 4px", background: active ? "rgba(0,0,0,0.25)" : "rgba(255,255,255,0.06)",
          borderRadius: 3,
        }}>{count}</span>
      )}
    </button>
  );
};
const Sel = ({ value, onChange, options, label }) => {
  const opts = options.map(o => typeof o === "string" ? { v: o, l: o } : o);
  return (
    <label style={{ display: "flex", alignItems: "center", gap: 6, fontFamily: "var(--sketch)", fontSize: 12, color: "rgba(255,255,255,0.55)" }}>
      <span style={{ opacity: 0.7, textTransform: "uppercase", letterSpacing: 1, fontSize: 10, fontWeight: 600 }}>{label}</span>
      <select value={value} onChange={e => onChange(e.target.value)} style={{
        background: "#0a0a0a", color: "#fff", border: "1px solid rgba(255,255,255,0.12)",
        padding: "5px 8px", fontFamily: "var(--sketch)", fontSize: 12, outline: "none", borderRadius: 4,
      }}>{opts.map(o => <option key={o.v} value={o.v}>{o.l}</option>)}</select>
    </label>
  );
};

window.__dashUtils = { parseRange, parseEndurance, parsePrice, fmtUsd, buildNiches, buildStats };
window.__dashAtoms = { Dot, RangeBar, StatusPill, KpiStrip, CommandBar, ControlBar };
