/* marketeer-seo.jsx — MktSeo: SEO & keyword-tracking dashboard for the marketeer.
   - Tracks keyword performance (volume, difficulty, intent, rank position, GSC).
   - Surfaces which keywords are already used/ranking and which are follow-up
     potentials (quick wins, untapped high-volume, content ideas, opportunities).
   - Seeded with the two real Van Haren Semrush exports (window.SEO_SEED).
   - Import more keywords from a CSV, or connect Semrush for live data (window.VHSeo).
   Registered before marketeer-app.jsx; exposed as window.MktSeo. */

/* ── helpers ── */
function seoNum(v) {
  if (v === null || v === undefined || v === "") return null;
  var n = parseFloat(String(v).replace(",", "."));
  return isFinite(n) ? n : null;
}
function seoPosColor(p) {
  if (p == null) return { bg: "var(--vh-blue-50,#eef2f7)", fg: "var(--ink-3,#7b899f)" };
  if (p <= 3) return { bg: "rgba(34,197,94,.15)", fg: "#15803d" };
  if (p <= 10) return { bg: "rgba(18,129,196,.14)", fg: "#0e6ba8" };
  if (p <= 30) return { bg: "rgba(232,114,12,.15)", fg: "#b45309" };
  return { bg: "rgba(148,163,184,.18)", fg: "#64748b" };
}
function seoQuestion(kw) {
  return /^(wat|hoe|waarom|welke|welk|wanneer|wie|what|how|why|which|when|who|is |are |can )/i.test(String(kw || "").trim());
}
function seoUniq(arr) { var s = {}; var o = []; arr.forEach(function (x) { if (x && !s[x]) { s[x] = 1; o.push(x); } }); return o; }

function seoKpis(kws) {
  var vol = 0, rank = 0, t10 = 0, t3 = 0, posSum = 0, posN = 0, kdSum = 0, kdN = 0, used = 0;
  kws.forEach(function (k) {
    vol += k.volume || 0;
    var p = k.position != null ? k.position : (k.gscPos != null ? k.gscPos : null);
    if (p != null) { rank++; posSum += p; posN++; if (p <= 10) t10++; if (p <= 3) t3++; }
    if (p != null || (k.impressions || 0) > 0 || (k.status && /rank|updaten|live/i.test(k.status))) used++;
    if (k.kd != null) { kdSum += k.kd; kdN++; }
  });
  return {
    tracked: kws.length, volume: vol, ranking: rank, top10: t10, top3: t3, used: used,
    avgPos: posN ? Math.round((posSum / posN) * 10) / 10 : null,
    avgKd: kdN ? Math.round(kdSum / kdN) : null,
  };
}

/* follow-up potentials, computed generically so it also works for imported/live data */
function seoFollowups(set) {
  var kws = (set && set.keywords) || [];
  var quickWins = kws.filter(function (k) {
    var p = k.position != null ? k.position : k.gscPos;
    return p != null && p >= 11 && p <= 30;
  }).sort(function (a, b) { return (b.volume || 0) - (a.volume || 0); });

  var untapped = kws.filter(function (k) {
    var p = k.position != null ? k.position : k.gscPos;
    return (k.volume || 0) >= 50 && (p == null || p > 30);
  }).sort(function (a, b) { return (b.volume || 0) - (a.volume || 0); });

  var ideas = kws.filter(function (k) {
    var p = k.position != null ? k.position : k.gscPos;
    return (k.type === "longtail" || k.type === "reference" || k.type === "related" || seoQuestion(k.kw)) && (p == null || p > 10);
  }).sort(function (a, b) { return (b.volume || 0) - (a.volume || 0); });

  return {
    quickWins: quickWins,
    untapped: untapped,
    ideas: ideas,
    opportunities: (set && set.opportunities) || [],
    dataQuickwins: (set && set.quickwins) || [],
    gaps: (set && set.gaps) || [],
  };
}

/* tiny CSV parser (handles quotes; auto-detects ; , or tab) → array of row-arrays */
function seoParseCsv(text) {
  text = String(text || "").replace(/^﻿/, "");
  var firstNl = text.indexOf("\n");
  var head = firstNl >= 0 ? text.slice(0, firstNl) : text;
  var counts = { ";": (head.split(";").length), ",": (head.split(",").length), "\t": (head.split("\t").length) };
  var delim = ";";
  if (counts[","] > counts[delim]) delim = ",";
  if (counts["\t"] > counts[delim]) delim = "\t";
  var rows = [], row = [], field = "", inQ = false;
  for (var i = 0; i < text.length; i++) {
    var c = text[i];
    if (inQ) {
      if (c === '"') { if (text[i + 1] === '"') { field += '"'; i++; } else inQ = false; }
      else field += c;
    } else if (c === '"') inQ = true;
    else if (c === delim) { row.push(field); field = ""; }
    else if (c === "\n") { row.push(field); rows.push(row); row = []; field = ""; }
    else if (c === "\r") { /* skip */ }
    else field += c;
  }
  if (field.length || row.length) { row.push(field); rows.push(row); }
  return rows.filter(function (r) { return r.some(function (c) { return String(c).trim() !== ""; }); });
}

function seoKeywordsFromCsv(rows) {
  if (!rows.length) return [];
  var head = rows[0].map(function (h) { return String(h || "").trim().toLowerCase(); });
  function col(re) { for (var i = 0; i < head.length; i++) if (re.test(head[i])) return i; return -1; }
  var ci = {
    kw: col(/keyword|phrase|zoekwoord|term/), vol: col(/volume|nq|zoekvolume/),
    kd: col(/difficulty|kd|moeilijk/), intent: col(/intent|intentie/),
    pos: col(/position|positie|^pos|rank/), url: col(/url|pagina|page/),
    cluster: col(/cluster|standaard|group|thema/), lang: col(/taal|lang|language/),
  };
  if (ci.kw < 0) ci.kw = 0;
  var out = [];
  for (var r = 1; r < rows.length; r++) {
    var row = rows[r];
    var kw = String(row[ci.kw] != null ? row[ci.kw] : "").trim();
    if (!kw) continue;
    out.push({
      kw: kw,
      lang: ci.lang >= 0 ? String(row[ci.lang] || "").trim() : "",
      volume: Math.round(seoNum(ci.vol >= 0 ? row[ci.vol] : 0) || 0),
      kd: ci.kd >= 0 ? (function () { var n = seoNum(row[ci.kd]); return n == null ? null : Math.round(n); })() : null,
      intent: ci.intent >= 0 ? String(row[ci.intent] || "").trim() : "",
      cluster: ci.cluster >= 0 ? String(row[ci.cluster] || "").trim() : "Imported",
      target: "", url: ci.url >= 0 ? String(row[ci.url] || "").trim() : "",
      position: ci.pos >= 0 ? seoNum(row[ci.pos]) : null,
      status: "", priority: "", type: "core",
    });
  }
  return out;
}

function seoLoadSets() {
  var seed = (window.SEO_SEED && window.SEO_SEED.sets) || [];
  return seed.map(function (s) { return Object.assign({}, s, { origin: "seed" }); });
}

/* ── UI atoms ── */
function SeoKpi(props) {
  return (
    <div className="card" style={{ padding: "15px 17px", display: "flex", alignItems: "center", gap: 13 }}>
      <div style={{ width: 42, height: 42, flex: "none", borderRadius: 11, display: "grid", placeItems: "center", background: props.accent ? "var(--vh-orange-50,#fff1e6)" : "var(--vh-blue-50,#eaf3fb)" }}>
        <Icon name={props.icon} size={21} style={{ color: props.accent ? "var(--vh-orange,#e8720c)" : "var(--vh-blue-500,#1281c4)" }} />
      </div>
      <div style={{ minWidth: 0 }}>
        <div style={{ fontFamily: "var(--display)", fontWeight: 800, fontSize: 22, lineHeight: 1.1 }}>{props.value}</div>
        <div className="dim" style={{ fontSize: 12, fontWeight: 600 }}>{props.label}</div>
      </div>
    </div>
  );
}

function SeoPill(props) {
  var c = seoPosColor(props.p);
  return <span style={{ display: "inline-block", minWidth: 30, textAlign: "center", padding: "3px 8px", borderRadius: 8, fontSize: 12.5, fontWeight: 700, background: c.bg, color: c.fg }}>{props.p == null ? "—" : (Math.round(props.p * 10) / 10)}</span>;
}

/* ── main screen ── */
function MktSeo() {
  var CARD = { background: "var(--surface,#fff)", border: "1px solid var(--line,#e5e9f0)", borderRadius: 14, padding: 20, marginBottom: 18 };
  var PRI = { display: "inline-flex", alignItems: "center", gap: 7, background: "var(--vh-blue-500,#1281c4)", color: "#fff", border: "none", borderRadius: 10, padding: "9px 15px", fontWeight: 700, fontSize: 13.5, cursor: "pointer" };
  var OUT = { display: "inline-flex", alignItems: "center", gap: 6, background: "transparent", color: "var(--ink,#1c2740)", border: "1px solid var(--line,#e5e9f0)", borderRadius: 9, padding: "8px 13px", fontWeight: 600, fontSize: 13, cursor: "pointer" };
  var FIELD = { padding: "9px 12px", border: "1.5px solid var(--line,#e5e9f0)", borderRadius: 9, fontSize: 13.5, fontFamily: "inherit", outline: "none", background: "var(--surface,#fff)", color: "var(--ink,#1c2740)" };
  var TH = { textAlign: "left", padding: "9px 10px", fontSize: 11, fontWeight: 800, letterSpacing: ".05em", textTransform: "uppercase", color: "var(--ink-3,#7b899f)", borderBottom: "1px solid var(--line,#e5e9f0)", cursor: "pointer", whiteSpace: "nowrap" };
  var TD = { padding: "9px 10px", fontSize: 13, borderBottom: "1px solid var(--line-2,#eef1f6)", verticalAlign: "middle" };

  function toast(m, i) { if (window.eduToast) window.eduToast(m, i); }

  var _s = React.useState(seoLoadSets()); var sets = _s[0], setSets = _s[1];
  var _a = React.useState((seoLoadSets()[0] || {}).id || ""); var activeId = _a[0], setActiveId = _a[1];
  var _q = React.useState(""); var query = _q[0], setQuery = _q[1];
  var _fi = React.useState("all"); var fIntent = _fi[0], setFIntent = _fi[1];
  var _fl = React.useState("all"); var fLang = _fl[0], setFLang = _fl[1];
  var _fu = React.useState("all"); var fUse = _fu[0], setFUse = _fu[1]; // all | ranking | potential
  var _so = React.useState("volume"); var sortBy = _so[0], setSortBy = _so[1];
  var _tab = React.useState("performance"); var tab = _tab[0], setTab = _tab[1];
  var _sr = React.useState({ connected: false, domain: "", database: "nl" }); var semrush = _sr[0], setSemrush = _sr[1];
  var _cm = React.useState(false); var showConnect = _cm[0], setShowConnect = _cm[1];
  var _busy = React.useState(false); var busy = _busy[0], setBusy = _busy[1];
  var fileRef = React.useRef(null);

  // load persisted workspace + semrush status
  React.useEffect(function () {
    if (!window.VHSeo) return;
    window.VHSeo.load().then(function (d) {
      if (d && d.semrush) setSemrush(d.semrush);
      var saved = d && d.workspace && Array.isArray(d.workspace.sets) ? d.workspace.sets : [];
      if (saved.length) {
        var seed = seoLoadSets();
        var byId = {}; seed.forEach(function (s) { byId[s.id] = s; });
        saved.forEach(function (s) { byId[s.id] = Object.assign({ origin: "import" }, s); });
        var merged = []; seed.forEach(function (s) { merged.push(byId[s.id]); delete byId[s.id]; });
        Object.keys(byId).forEach(function (k) { merged.push(byId[k]); });
        setSets(merged);
      }
    }).catch(function () {});
  }, []);

  function persist(nextSets) {
    setSets(nextSets);
    if (window.VHSeo) {
      var userSets = nextSets.filter(function (s) { return s.origin !== "seed"; });
      window.VHSeo.save({ sets: userSets }).catch(function () {});
    }
  }

  var activeSet = sets.filter(function (s) { return s.id === activeId; })[0] || sets[0] || { keywords: [] };
  var allKws = activeSet.keywords || [];
  var intents = seoUniq(allKws.map(function (k) { return k.intent; }).filter(Boolean));
  var langs = seoUniq(allKws.map(function (k) { return k.lang; }).filter(Boolean));

  var filtered = allKws.filter(function (k) {
    if (query && String(k.kw).toLowerCase().indexOf(query.toLowerCase()) < 0) return false;
    if (fIntent !== "all" && k.intent !== fIntent) return false;
    if (fLang !== "all" && k.lang !== fLang) return false;
    var p = k.position != null ? k.position : k.gscPos;
    if (fUse === "ranking" && p == null) return false;
    if (fUse === "potential" && !(p == null || p > 10)) return false;
    return true;
  });
  filtered = filtered.slice().sort(function (a, b) {
    if (sortBy === "position") {
      var pa = a.position != null ? a.position : (a.gscPos != null ? a.gscPos : 9999);
      var pb = b.position != null ? b.position : (b.gscPos != null ? b.gscPos : 9999);
      return pa - pb;
    }
    if (sortBy === "kd") return (a.kd == null ? 999 : a.kd) - (b.kd == null ? 999 : b.kd);
    if (sortBy === "alpha") return String(a.kw).localeCompare(String(b.kw));
    return (b.volume || 0) - (a.volume || 0);
  });

  var kpi = seoKpis(allKws);
  var fu = seoFollowups(activeSet);
  var followCount = fu.quickWins.length + fu.untapped.length + fu.opportunities.length + fu.dataQuickwins.length;

  /* import CSV */
  function onImport(e) {
    var f = e.target.files && e.target.files[0]; e.target.value = ""; if (!f) return;
    var reader = new FileReader();
    reader.onload = function () {
      try {
        var rows = seoParseCsv(reader.result);
        var kws = seoKeywordsFromCsv(rows);
        if (!kws.length) { toast("No keywords found in that file", "x"); return; }
        var id = "import-" + Math.abs(hashStr(f.name + kws.length)).toString(36);
        var name = f.name.replace(/\.(csv|tsv|txt)$/i, "");
        var next = sets.filter(function (s) { return s.id !== id; }).concat([{ id: id, name: name, domain: semrush.domain || "", updated: "", source: "Import", origin: "import", keywords: kws, opportunities: [], quickwins: [], gaps: [] }]);
        persist(next); setActiveId(id); setTab("performance");
        toast("Imported " + kws.length + " keywords · " + name, "check");
      } catch (err) { toast("Could not read that file", "x"); }
    };
    reader.readAsText(f);
  }
  function hashStr(s) { var h = 0; s = String(s); for (var i = 0; i < s.length; i++) h = (h * 31 + s.charCodeAt(i)) | 0; return h; }

  function exportCsv() {
    var cols = ["keyword", "lang", "volume", "kd", "intent", "position", "gsc_impressions", "gsc_clicks", "cluster", "url"];
    var lines = [cols.join(",")];
    allKws.forEach(function (k) {
      var row = [k.kw, k.lang, k.volume, k.kd == null ? "" : k.kd, k.intent, k.position == null ? "" : k.position, k.impressions == null ? "" : k.impressions, k.clicks == null ? "" : k.clicks, k.cluster, k.url];
      lines.push(row.map(function (c) { return '"' + String(c == null ? "" : c).replace(/"/g, '""') + '"'; }).join(","));
    });
    var blob = new Blob([lines.join("\n")], { type: "text/csv" });
    var a = document.createElement("a"); a.href = URL.createObjectURL(blob);
    a.download = (activeSet.name || "keywords").replace(/[^a-z0-9]+/gi, "-").toLowerCase() + ".csv"; a.click();
    setTimeout(function () { URL.revokeObjectURL(a.href); }, 1500);
  }

  /* Semrush */
  function fetchSemrush(kind) {
    if (!semrush.connected) { setShowConnect(true); return; }
    var phrase = "";
    if (kind === "related") { phrase = window.prompt("Seed keyword to find follow-up / related keywords:", allKws[0] ? allKws[0].kw : ""); if (!phrase) return; }
    setBusy(true);
    window.VHSeo.semrushFetch({ kind: kind, phrase: phrase, limit: 150 }).then(function (d) {
      var kws = (d && d.keywords) || [];
      if (!kws.length) { toast("Semrush returned no rows", "x"); setBusy(false); return; }
      var id = kind === "related" ? ("semrush-ideas-" + Math.abs(hashStr(phrase)).toString(36)) : "semrush-live";
      var name = kind === "related" ? ("Semrush ideas · " + phrase) : ("Semrush · " + (semrush.domain || "live"));
      var next = sets.filter(function (s) { return s.id !== id; }).concat([{ id: id, name: name, domain: semrush.domain, updated: new Date().toISOString().slice(0, 10), source: "Semrush", origin: "semrush", keywords: kws, opportunities: [], quickwins: [], gaps: [] }]);
      persist(next); setActiveId(id); setTab(kind === "related" ? "followups" : "performance");
      toast("Fetched " + kws.length + " keywords from Semrush", "check");
      setBusy(false);
    }).catch(function (e) { toast(e.message || "Semrush fetch failed", "x"); setBusy(false); });
  }

  return (
    <div className="vh-in" style={{ padding: "26px 30px", maxWidth: 1180, margin: "0 auto" }}>
      {/* header */}
      <div style={{ display: "flex", justifyContent: "space-between", alignItems: "flex-end", gap: 16, flexWrap: "wrap", marginBottom: 18 }}>
        <div>
          <h1 style={{ fontSize: 25, fontWeight: 800, margin: 0 }}>SEO &amp; Keywords</h1>
          <p className="dim" style={{ fontSize: 13.5, marginTop: 5, maxWidth: 620 }}>Track keyword performance, see what you already rank for, and spot follow-up potentials. Import a keyword list or connect Semrush for live data.</p>
        </div>
        <div style={{ display: "flex", gap: 8, flexWrap: "wrap" }}>
          <input ref={fileRef} type="file" accept=".csv,.tsv,.txt" style={{ display: "none" }} onChange={onImport} />
          <button style={OUT} onClick={function () { fileRef.current && fileRef.current.click(); }}><Icon name="download" size={15} style={{ transform: "rotate(180deg)" }} />Import keywords</button>
          <button style={OUT} onClick={exportCsv}><Icon name="download" size={15} />Export</button>
          <button style={semrush.connected ? OUT : PRI} onClick={function () { setShowConnect(true); }}>
            <Icon name={semrush.connected ? "check" : "globe"} size={15} />{semrush.connected ? "Semrush connected" : "Connect Semrush"}
          </button>
        </div>
      </div>

      {/* set selector */}
      <div style={{ display: "flex", gap: 8, flexWrap: "wrap", marginBottom: 16, alignItems: "center" }}>
        {sets.map(function (s) {
          var on = s.id === activeId;
          return (
            <button key={s.id} onClick={function () { setActiveId(s.id); }} style={{
              display: "inline-flex", alignItems: "center", gap: 7, padding: "7px 13px", borderRadius: 999, cursor: "pointer",
              border: on ? "1.5px solid var(--vh-blue-500,#1281c4)" : "1px solid var(--line,#e5e9f0)",
              background: on ? "var(--vh-blue-50,#eaf3fb)" : "transparent", color: on ? "var(--vh-blue-600,#0e6ba8)" : "var(--ink-2,#3a4761)", fontWeight: 700, fontSize: 13 }}>
              <Icon name={s.source === "Semrush" ? "globe" : s.source === "Import" ? "layers" : "book"} size={14} />
              {s.name}<span className="dim" style={{ fontWeight: 600 }}>· {(s.keywords || []).length}</span>
            </button>
          );
        })}
        {activeSet.source && <span className="dim" style={{ fontSize: 12, marginLeft: 4 }}>{activeSet.source}{activeSet.updated ? " · " + activeSet.updated : ""}{activeSet.domain ? " · " + activeSet.domain : ""}</span>}
      </div>

      {/* KPIs */}
      <div style={{ display: "grid", gridTemplateColumns: "repeat(auto-fit,minmax(165px,1fr))", gap: 12, marginBottom: 20 }}>
        <SeoKpi icon="layers" label="Tracked keywords" value={kpi.tracked} />
        <SeoKpi icon="bars" label="Total search volume" value={kpi.volume.toLocaleString()} />
        <SeoKpi icon="trophy" label="Ranking keywords" value={kpi.ranking} />
        <SeoKpi icon="check" label="In top 10" value={kpi.top10} />
        <SeoKpi icon="flame" label="Avg. position" value={kpi.avgPos == null ? "—" : kpi.avgPos} />
        <SeoKpi icon="wand" label="Follow-up ideas" value={followCount} accent />
      </div>

      {/* tabs */}
      <div style={{ display: "flex", gap: 4, borderBottom: "1px solid var(--line,#e5e9f0)", marginBottom: 16 }}>
        {[["performance", "Keyword performance", "bars"], ["followups", "Follow-up potentials", "wand"]].map(function (t) {
          var on = tab === t[0];
          return <button key={t[0]} onClick={function () { setTab(t[0]); }} style={{ display: "inline-flex", alignItems: "center", gap: 7, padding: "10px 15px", background: "none", border: "none", borderBottom: on ? "2.5px solid var(--vh-blue-500,#1281c4)" : "2.5px solid transparent", color: on ? "var(--ink,#1c2740)" : "var(--ink-3,#7b899f)", fontWeight: 700, fontSize: 14, cursor: "pointer", marginBottom: -1 }}><Icon name={t[2]} size={15} />{t[1]}</button>;
        })}
      </div>

      {tab === "performance" && (
        <div style={CARD}>
          {/* filters */}
          <div style={{ display: "flex", gap: 8, flexWrap: "wrap", marginBottom: 14, alignItems: "center" }}>
            <div style={{ position: "relative", flex: 1, minWidth: 200 }}>
              <Icon name="search" size={15} style={{ position: "absolute", left: 11, top: "50%", transform: "translateY(-50%)", color: "var(--ink-3,#7b899f)" }} />
              <input value={query} onChange={function (e) { setQuery(e.target.value); }} placeholder="Search keywords…" style={Object.assign({}, FIELD, { width: "100%", paddingLeft: 32 })} />
            </div>
            <select value={fUse} onChange={function (e) { setFUse(e.target.value); }} style={FIELD}>
              <option value="all">All keywords</option>
              <option value="ranking">Ranking (used)</option>
              <option value="potential">Not in top 10 (potential)</option>
            </select>
            {intents.length > 0 && <select value={fIntent} onChange={function (e) { setFIntent(e.target.value); }} style={FIELD}><option value="all">All intents</option>{intents.map(function (x) { return <option key={x} value={x}>{x}</option>; })}</select>}
            {langs.length > 1 && <select value={fLang} onChange={function (e) { setFLang(e.target.value); }} style={FIELD}><option value="all">All languages</option>{langs.map(function (x) { return <option key={x} value={x}>{x}</option>; })}</select>}
            <select value={sortBy} onChange={function (e) { setSortBy(e.target.value); }} style={FIELD}>
              <option value="volume">Sort: Volume</option>
              <option value="position">Sort: Position</option>
              <option value="kd">Sort: Difficulty</option>
              <option value="alpha">Sort: A–Z</option>
            </select>
          </div>

          <div style={{ overflowX: "auto" }}>
            <table style={{ width: "100%", borderCollapse: "collapse", minWidth: 720 }}>
              <thead><tr>
                <th style={TH} onClick={function () { setSortBy("alpha"); }}>Keyword</th>
                <th style={TH} onClick={function () { setSortBy("volume"); }}>Volume</th>
                <th style={TH} onClick={function () { setSortBy("kd"); }}>KD %</th>
                <th style={TH}>Intent</th>
                <th style={TH} onClick={function () { setSortBy("position"); }}>Position</th>
                <th style={TH}>GSC impr.</th>
                <th style={TH}>Cluster / target</th>
              </tr></thead>
              <tbody>
                {filtered.map(function (k, i) {
                  var p = k.position != null ? k.position : k.gscPos;
                  return (
                    <tr key={i}>
                      <td style={TD}>
                        <div style={{ fontWeight: 600 }}>{k.kw}{k.lang ? <span className="dim" style={{ fontWeight: 600, fontSize: 11, marginLeft: 6 }}>{k.lang}</span> : null}</div>
                        {k.url ? <a href={(/^https?:/.test(k.url) ? k.url : "https://www." + (activeSet.domain || "vanharen.net") + k.url)} target="_blank" rel="noopener" className="dim" style={{ fontSize: 11.5, textDecoration: "none" }}>{k.url}</a> : null}
                      </td>
                      <td style={TD}>{(k.volume || 0).toLocaleString()}</td>
                      <td style={TD}>{k.kd == null ? "—" : k.kd}</td>
                      <td style={TD}><span className="dim">{k.intent || "—"}</span></td>
                      <td style={TD}><SeoPill p={p} /></td>
                      <td style={TD}>{k.impressions == null ? "—" : k.impressions.toLocaleString()}</td>
                      <td style={TD}><span className="dim" style={{ fontSize: 12 }}>{k.cluster || k.target || "—"}</span></td>
                    </tr>
                  );
                })}
                {filtered.length === 0 && <tr><td style={Object.assign({}, TD, { textAlign: "center", padding: "26px 0", color: "var(--ink-3,#7b899f)" })} colSpan={7}>No keywords match these filters.</td></tr>}
              </tbody>
            </table>
          </div>
          <div className="dim" style={{ fontSize: 12, marginTop: 10 }}>Showing {filtered.length} of {allKws.length} keywords · position from Semrush, impressions from Google Search Console where available.</div>
        </div>
      )}

      {tab === "followups" && (
        <div>
          <FollowupBlock title="Quick wins — a small push to page 1" sub="Ranking 11–30. Optimize the existing page and you likely reach the top 10." icon="trending" color="#e8720c"
            rows={fu.quickWins.slice(0, 12).map(function (k) { return { kw: k.kw, vol: k.volume, meta: "Position " + (Math.round((k.position != null ? k.position : k.gscPos) * 10) / 10) + (k.url ? " · " + k.url : ""), tag: k.intent }; })} />
          <FollowupBlock title="Untapped high-volume keywords" sub="Real search demand but you don't rank in the top 30 yet — create or expand a page." icon="bolt" color="#1281c4"
            rows={fu.untapped.slice(0, 12).map(function (k) { return { kw: k.kw, vol: k.volume, meta: (k.position == null ? "Not ranking" : "Position " + Math.round(k.position)) + (k.cluster ? " · " + k.cluster : ""), tag: k.intent }; })} />
          <FollowupBlock title="Content ideas — questions &amp; long-tail" sub="Informational and question-form queries to answer in articles or FAQs." icon="wand" color="#7c58d3"
            rows={fu.ideas.slice(0, 12).map(function (k) { return { kw: k.kw, vol: k.volume, meta: (k.target || k.cluster || "Long-tail"), tag: k.intent || k.type }; })} />
          {fu.opportunities.length > 0 &&
            <FollowupBlock title="Search Console opportunities" sub="Queries you already get impressions for but few clicks — near-miss rankings worth targeting." icon="search" color="#0e9f9f"
              rows={fu.opportunities.slice(0, 14).map(function (o) { return { kw: o.query, vol: o.impressions, volLabel: "impr.", meta: o.market + " · " + o.form + (o.position != null ? " · pos " + o.position : ""), tag: o.signal }; })} />}
          {fu.dataQuickwins.length > 0 &&
            <FollowupBlock title="Recommended actions" sub="Prioritized quick wins from the keyword research." icon="check" color="#15803d"
              rows={fu.dataQuickwins.map(function (q) { return { kw: q.action, vol: q.volume, meta: q.page + " — " + q.why, tag: q.impact }; })} />}
          {fu.gaps.length > 0 &&
            <FollowupBlock title="Competitor gaps" sub="Keywords competitors cover that you're missing." icon="users" color="#c02b63"
              rows={fu.gaps.map(function (g) { return { kw: g.gapKeywords, vol: null, meta: g.competitor + " · " + g.cluster + " — " + g.action, tag: "Gap" }; })} />}
          {followCount === 0 && fu.ideas.length === 0 && <div style={CARD}><div className="dim" style={{ textAlign: "center", padding: "20px 0" }}>No follow-up potentials detected for this set yet.</div></div>}
        </div>
      )}

      {/* Semrush connect modal */}
      {showConnect && <SemrushModal semrush={semrush} onClose={function () { setShowConnect(false); }} onSaved={function (s) { setSemrush(s); }} onFetch={fetchSemrush} busy={busy} toast={toast} />}
    </div>
  );
}

function FollowupBlock(props) {
  var rows = props.rows || [];
  if (!rows.length) return null;
  return (
    <div className="card" style={{ padding: 20, marginBottom: 16, borderLeft: "4px solid " + props.color }}>
      <div style={{ display: "flex", alignItems: "center", gap: 10, marginBottom: 4 }}>
        <Icon name={props.icon === "trending" ? "bars" : props.icon} size={17} style={{ color: props.color }} />
        <h3 style={{ fontSize: 16, fontWeight: 700, margin: 0 }}>{props.title}</h3>
        <span className="dim" style={{ fontSize: 12, fontWeight: 700, marginLeft: "auto" }}>{rows.length}</span>
      </div>
      <p className="dim" style={{ fontSize: 12.5, margin: "0 0 12px" }}>{props.sub}</p>
      <div style={{ display: "flex", flexDirection: "column", gap: 7 }}>
        {rows.map(function (r, i) {
          return (
            <div key={i} style={{ display: "flex", alignItems: "center", gap: 12, padding: "9px 12px", borderRadius: 10, background: "var(--vh-blue-50,#f6f9fc)" }}>
              <div style={{ flex: 1, minWidth: 0 }}>
                <div style={{ fontWeight: 600, fontSize: 13.5 }}>{r.kw}</div>
                {r.meta ? <div className="dim" style={{ fontSize: 11.5, whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis" }}>{r.meta}</div> : null}
              </div>
              {r.tag ? <span style={{ flex: "none", fontSize: 11, fontWeight: 700, padding: "3px 9px", borderRadius: 999, background: "rgba(18,129,196,.1)", color: "#0e6ba8" }}>{r.tag}</span> : null}
              {r.vol != null ? <div style={{ flex: "none", textAlign: "right", minWidth: 66 }}><div style={{ fontFamily: "var(--display)", fontWeight: 800, fontSize: 15 }}>{Number(r.vol).toLocaleString()}</div><div className="dim" style={{ fontSize: 10, fontWeight: 700, textTransform: "uppercase", letterSpacing: ".05em" }}>{r.volLabel || "vol"}</div></div> : null}
            </div>
          );
        })}
      </div>
    </div>
  );
}

function SemrushModal(props) {
  var s = props.semrush || {};
  var _k = React.useState(""); var key = _k[0], setKey = _k[1];
  var _d = React.useState(s.domain || "vanharen.net"); var domain = _d[0], setDomain = _d[1];
  var _db = React.useState(s.database || "nl"); var db = _db[0], setDb = _db[1];
  var _b = React.useState(false); var saving = _b[0], setSaving = _b[1];
  var FIELD = { padding: "10px 12px", border: "1.5px solid var(--line,#e5e9f0)", borderRadius: 9, fontSize: 14, fontFamily: "inherit", outline: "none", background: "var(--surface,#fff)", color: "var(--ink,#1c2740)", width: "100%" };
  var PRI = { display: "inline-flex", alignItems: "center", gap: 7, background: "var(--vh-blue-500,#1281c4)", color: "#fff", border: "none", borderRadius: 10, padding: "10px 16px", fontWeight: 700, fontSize: 14, cursor: "pointer" };
  var OUT = { display: "inline-flex", alignItems: "center", gap: 6, background: "transparent", color: "var(--ink,#1c2740)", border: "1px solid var(--line,#e5e9f0)", borderRadius: 9, padding: "9px 14px", fontWeight: 600, fontSize: 13.5, cursor: "pointer" };

  function connect() {
    if (!key.trim()) { props.toast("Enter your Semrush API key", "x"); return; }
    setSaving(true);
    window.VHSeo.semrushConnect({ apiKey: key.trim(), domain: domain.trim(), database: db }).then(function (d) {
      props.onSaved(d.semrush || { connected: true, domain: domain.trim(), database: db });
      props.toast("Semrush connected", "check"); setSaving(false); setKey("");
    }).catch(function (e) { props.toast(e.message || "Could not connect", "x"); setSaving(false); });
  }
  function disconnect() {
    window.VHSeo.semrushDisconnect().then(function () { props.onSaved({ connected: false, domain: domain, database: db }); props.toast("Semrush disconnected", "check"); }).catch(function () {});
  }

  return (
    <div onClick={props.onClose} style={{ position: "fixed", inset: 0, background: "rgba(13,26,52,.45)", zIndex: 500, display: "grid", placeItems: "center", padding: 20 }}>
      <div onClick={function (e) { e.stopPropagation(); }} style={{ background: "var(--surface,#fff)", borderRadius: 16, padding: 26, width: "100%", maxWidth: 460, boxShadow: "0 20px 60px rgba(13,26,52,.35)" }}>
        <div style={{ display: "flex", alignItems: "center", gap: 10, marginBottom: 6 }}>
          <Icon name="globe" size={20} style={{ color: "var(--vh-blue-500,#1281c4)" }} />
          <h2 style={{ fontSize: 19, fontWeight: 800, margin: 0 }}>Connect Semrush</h2>
          <button onClick={props.onClose} style={{ marginLeft: "auto", background: "none", border: "none", cursor: "pointer", color: "var(--ink-3,#7b899f)" }}><Icon name="x" size={18} /></button>
        </div>
        <p className="dim" style={{ fontSize: 13, marginTop: 0, marginBottom: 18 }}>Paste a Semrush API key to pull live rankings and keyword ideas. The key is stored securely on the server and never shown again.</p>

        {props.semrush.connected ? (
          <div style={{ padding: "12px 14px", borderRadius: 10, background: "rgba(34,197,94,.1)", border: "1px solid rgba(34,197,94,.3)", marginBottom: 16, fontSize: 13.5, fontWeight: 600, color: "#15803d", display: "flex", alignItems: "center", gap: 8 }}>
            <Icon name="check" size={16} />Connected · {props.semrush.domain || "no domain"} · db {props.semrush.database || "nl"}
          </div>
        ) : null}

        <label style={{ fontSize: 12.5, fontWeight: 700, display: "block", marginBottom: 5 }}>API key</label>
        <input type="password" value={key} onChange={function (e) { setKey(e.target.value); }} placeholder={props.semrush.connected ? "•••••••• (enter a new key to replace)" : "Semrush API key"} style={Object.assign({}, FIELD, { marginBottom: 12, fontFamily: "ui-monospace,monospace" })} />
        <div style={{ display: "flex", gap: 10, marginBottom: 18 }}>
          <div style={{ flex: 2 }}>
            <label style={{ fontSize: 12.5, fontWeight: 700, display: "block", marginBottom: 5 }}>Domain</label>
            <input value={domain} onChange={function (e) { setDomain(e.target.value); }} placeholder="vanharen.net" style={FIELD} />
          </div>
          <div style={{ flex: 1 }}>
            <label style={{ fontSize: 12.5, fontWeight: 700, display: "block", marginBottom: 5 }}>Database</label>
            <select value={db} onChange={function (e) { setDb(e.target.value); }} style={FIELD}>
              {["nl", "us", "uk", "de", "be", "fr", "es"].map(function (x) { return <option key={x} value={x}>{x.toUpperCase()}</option>; })}
            </select>
          </div>
        </div>

        <div style={{ display: "flex", gap: 9, alignItems: "center", flexWrap: "wrap" }}>
          <button style={Object.assign({}, PRI, { opacity: saving ? .6 : 1 })} disabled={saving} onClick={connect}><Icon name="check" size={15} />{props.semrush.connected ? "Update key" : "Connect"}</button>
          {props.semrush.connected && <>
            <button style={Object.assign({}, OUT, { opacity: props.busy ? .6 : 1 })} disabled={props.busy} onClick={function () { props.onFetch("domain"); }}><Icon name="refresh" size={14} />Fetch domain rankings</button>
            <button style={Object.assign({}, OUT, { opacity: props.busy ? .6 : 1 })} disabled={props.busy} onClick={function () { props.onFetch("related"); }}><Icon name="wand" size={14} />Find keyword ideas</button>
            <button style={Object.assign({}, OUT, { marginLeft: "auto", color: "#c62828" })} onClick={disconnect}>Disconnect</button>
          </>}
        </div>
        <div className="dim" style={{ fontSize: 11.5, marginTop: 14 }}>No key yet? You can still import a CSV from the Semrush UI export. Get an API key at semrush.com → Subscription → API units.</div>
      </div>
    </div>
  );
}

window.MktSeo = MktSeo;
