/* =====================================================
   Betting Tracker — extras
   CSV import · saved filter views · bet templates ·
   daily P&L calendar heatmap · validation helpers.
   Loaded after charts.jsx so Dashboard and Bets can use it.
   ===================================================== */

/* ---------- tiny localStorage stores (device-local, not written to the .xlsx) ---------- */
const LS_PRESETS = "doinp.tracker.presets";
const LS_TEMPLATES = "doinp.tracker.templates";
function lsGet(k) { try { const s = localStorage.getItem(k); return s ? JSON.parse(s) : []; } catch (_) { return []; } }
function lsSet(k, v) { try { localStorage.setItem(k, JSON.stringify(v)); } catch (_) {} }

/* =====================================================
   CSV parsing helpers
   ===================================================== */
function parseCSV(text, delim) {
  const rows = []; let row = [], cell = "", i = 0, q = false;
  const pushCell = () => { row.push(cell); cell = ""; };
  const pushRow = () => { rows.push(row); row = []; };
  while (i < text.length) {
    const c = text[i];
    if (q) {
      if (c === '"') { if (text[i + 1] === '"') { cell += '"'; i++; } else q = false; }
      else cell += c;
    } else {
      if (c === '"') q = true;
      else if (c === delim) pushCell();
      else if (c === "\n") { pushCell(); pushRow(); }
      else if (c === "\r") { /* skip */ }
      else cell += c;
    }
    i++;
  }
  if (cell.length || row.length) { pushCell(); pushRow(); }
  return rows.filter((r) => r.length && !(r.length === 1 && r[0].trim() === ""));
}
function guessDelim(text) {
  const first = (text.split(/\r?\n/)[0] || "");
  const counts = { ",": first.split(",").length, ";": first.split(";").length, "\t": first.split("\t").length };
  return Object.keys(counts).sort((a, b) => counts[b] - counts[a])[0] || ",";
}
function numOf(s) {
  if (s == null || s === "") return null;
  let x = String(s).replace(/[^\d.,\-]/g, "");
  if (x.indexOf(",") >= 0 && x.indexOf(".") < 0) x = x.replace(",", ".");
  else x = x.replace(/,/g, "");
  const v = parseFloat(x);
  return isFinite(v) ? v : null;
}
function normDate(s) {
  if (!s) return TRK.today();
  s = String(s).trim();
  if (/^\d{4}-\d{2}-\d{2}/.test(s)) return s.slice(0, 10);
  const m = s.match(/^(\d{1,2})[\/.\-](\d{1,2})[\/.\-](\d{2,4})/);
  if (m) {
    let d = +m[1], mo = +m[2], y = +m[3];
    if (y < 100) y += 2000;
    if (mo > 12 && d <= 12) { const tmp = d; d = mo; mo = tmp; } // fix if clearly month-first
    return y + "-" + String(mo).padStart(2, "0") + "-" + String(d).padStart(2, "0");
  }
  const dt = new Date(s);
  return isNaN(dt) ? TRK.today() : dt.toISOString().slice(0, 10);
}
function normResult(s) {
  const x = String(s == null ? "" : s).trim().toLowerCase();
  if (!x) return "pending";
  if (/half.?w/.test(x)) return "half-won";
  if (/half.?l/.test(x)) return "half-lost";
  if (/cash/.test(x)) return "cashout";
  if (/void|cancel/.test(x)) return "void";
  if (/push|refun/.test(x)) return "push";
  if (/pend|open/.test(x)) return "pending";
  if (/^w|won|win|green|ganh|verde/.test(x)) return "won";
  if (/^l|los|lose|red|perd|verm/.test(x)) return "lost";
  return "pending";
}

const IMPORT_FIELDS = [
  { k: "date", label: "form.date", group: "g.match" }, { k: "time", label: "form.time", group: "g.match" },
  { k: "sport", label: "form.sport", group: "g.match" }, { k: "league", label: "form.league", group: "g.match" },
  { k: "teamA", label: "form.teamA", group: "g.match" }, { k: "teamB", label: "form.teamB", group: "g.match" },
  { k: "map", label: "form.map", group: "g.match" }, { k: "book", label: "form.book", group: "g.match" },
  { k: "tipster", label: "col.tipster", group: "g.match" },
  { k: "market", label: "form.market", group: "g.sel" }, { k: "line", label: "form.line", group: "g.sel" },
  { k: "side", label: "form.side", group: "g.sel" }, { k: "live", label: "form.live", group: "g.sel" },
  { k: "stake", label: "form.stake", group: "g.stake" }, { k: "odds", label: "form.odds", group: "g.stake" },
  { k: "result", label: "form.result", group: "g.stake" },
  { k: "closingOdds", label: "form.closing", group: "g.model" }, { k: "modelProb", label: "form.modelprob", group: "g.model" },
  { k: "player", label: "form.player", group: "g.props" }, { k: "position", label: "form.position", group: "g.props" },
  { k: "minutes", label: "form.minutes", group: "g.props" }, { k: "projection", label: "form.projection", group: "g.props" },
  { k: "actual", label: "form.actual", group: "g.props" },
  { k: "tags", label: "form.tags", group: "g.notes" }, { k: "notes", label: "form.notes", group: "g.notes" }
];
const IMPORT_GROUPS = [
  { key: "g.match", label: "form.sec.match" }, { key: "g.sel", label: "form.sec.market" },
  { key: "g.stake", label: "form.sec.stake" }, { key: "g.model", label: "form.sec.model" },
  { key: "g.props", label: "an.props.title" }, { key: "g.notes", label: "form.sec.notes" }
];
const GUESS = {
  date: /date|data|dia/, time: /time|hora|hour/, teamA: /home|team.?a|event|match|jogo|mandante/, teamB: /away|team.?b|visit/,
  map: /map|period|half|set|quarter|mapa|per\b/, sport: /sport|esporte/, league: /league|liga|comp/, book: /book|casa|sportsbook/,
  market: /market|mercado|bet.?type|type|aposta tipo/, side: /side|selection|sele|pick|palpite/,
  line: /line|linha|handic|spread|total/, live: /live|ao.?vivo|in.?play/, player: /player|jogador|atleta/,
  position: /position|\bpos\b|posi/, minutes: /minut|\bmin\b/, projection: /proj|estimat|expected|previs/, actual: /actual|real|score|final/,
  odds: /odd|price|quota|cota/, stake: /stake|risk|valor|amount|montante/, result: /result|status|outcome|resultado/,
  closingOdds: /clos|fech/, modelProb: /model.?prob|\bprob|probabil/, tipster: /tipster|model|modelo|source|fonte/,
  tags: /tag|label|categoria/, notes: /note|obs|coment/
};
function truthy(s) { return /^(y|yes|true|1|sim|live|v)/i.test(String(s == null ? "" : s).trim()); }
function tagsOf(s) { if (!s) return null; const a = String(s).split(/[|,;]/).map((x) => x.trim()).filter(Boolean); return a.length ? a : null; }
function buildImportedBets(dataRows, mapping, state) {
  return dataRows.map((r) => {
    const g = (k) => { const c = mapping[k]; return c == null || c === "" ? "" : (r[+c] != null ? String(r[+c]).trim() : ""); };
    const base = blankBet(state);
    const odds = numOf(g("odds")), stake = numOf(g("stake")), close = numOf(g("closingOdds"));
    let mp = numOf(g("modelProb")); if (mp != null) mp = mp > 1 ? mp / 100 : mp;
    const tags = tagsOf(g("tags")); const liveRaw = g("live");
    return {
      ...base, date: normDate(g("date")), time: g("time") || "",
      teamA: g("teamA") || "\u2014", teamB: g("teamB"), map: g("map") || "",
      sport: g("sport") || base.sport, league: g("league"), book: g("book") || base.book,
      tipster: g("tipster") || base.tipster, market: g("market") || base.market, line: g("line"), side: g("side"),
      live: liveRaw ? truthy(liveRaw) : false, player: g("player"), position: g("position") || "",
      minutes: numOf(g("minutes")), projection: numOf(g("projection")), actual: numOf(g("actual")),
      odds: odds != null ? odds : base.odds, stake: stake != null ? stake : base.stake,
      result: normResult(g("result")), closingOdds: close != null ? close : "", modelProb: mp,
      tags: tags || base.tags, notes: g("notes") || ""
    };
  }).filter((b) => b.odds > 1);
}
const EXAMPLE_CSV = "date,time,sport,league,home,away,map,book,tipster,market,line,side,live,stake,odds,result,closing,model_prob,player,notes\n2025-05-19,16:30,Soccer,Premier League,Arsenal,Chelsea,,Pinnacle,PROPHET,Moneyline,,Arsenal,no,100,2.10,won,1.98,52,,Value spot\n2025-05-21,21:05,Basketball,NBA,Nikola Jokic,,,DraftKings,Own model,Player Points,27.5,Over 27.5,no,80,1.87,won,1.83,58,Nikola Jokic,Rebound edge";

/* =====================================================
   CSV import modal
   ===================================================== */
function CSVImport({ open, onClose, state, onImport, t }) {
  const [raw, setRaw] = useState("");
  const [delim, setDelim] = useState("auto");
  const [hasHeader, setHasHeader] = useState(true);
  const [mapping, setMapping] = useState({});
  const fileRef = useRef(null);

  const d = delim === "auto" ? guessDelim(raw) : (delim === "tab" ? "\t" : delim);
  const rows = React.useMemo(() => (raw.trim() ? parseCSV(raw, d) : []), [raw, d]);
  const header = hasHeader && rows.length ? rows[0] : [];
  const dataRows = hasHeader ? rows.slice(1) : rows;
  const colCount = rows.reduce((m, r) => Math.max(m, r.length), 0);

  useEffect(() => {
    if (!rows.length) { setMapping({}); return; }
    const m = {};
    IMPORT_FIELDS.forEach((f) => {
      let found = "";
      if (hasHeader) header.forEach((h, i) => { if (found === "" && GUESS[f.k] && GUESS[f.k].test(String(h).toLowerCase())) found = String(i); });
      m[f.k] = found;
    });
    setMapping(m);
  }, [raw, d, hasHeader]);

  const colOptions = [{ value: "", label: t("imp.ignore") }].concat(
    Array.from({ length: colCount }, (_, i) => ({
      value: String(i),
      label: hasHeader && header[i] != null && header[i] !== "" ? String(header[i]) : (t("imp.col") + " " + (i + 1))
    })));

  const bets = React.useMemo(() => (dataRows.length && Object.keys(mapping).length ? buildImportedBets(dataRows, mapping, state) : []), [dataRows, mapping]);
  const onFile = (e) => { const f = e.target.files && e.target.files[0]; e.target.value = ""; if (!f) return; const rd = new FileReader(); rd.onload = () => setRaw(String(rd.result || "")); rd.readAsText(f); };
  const reset = () => { setRaw(""); setMapping({}); };
  const doImport = () => { if (!bets.length) return; onImport(bets); reset(); onClose(); };

  return (
    <Modal open={open} onClose={onClose} title={t("imp.title")} width={660}
      footer={<>
        <Btn variant="ghost" onClick={onClose}>{t("form.cancel")}</Btn>
        <Btn variant="primary" icon="check" onClick={doImport} disabled={!bets.length}>{t("imp.import") + (bets.length ? " (" + bets.length + ")" : "")}</Btn>
      </>}>
      <p className="trk-savenote"><Icon name="lock" size={14} /> {t("imp.sub")}</p>
      <div className="trk-imp">
        <div className="trk-imp__srcrow">
          <button type="button" className="trk-textlink trk-textlink--sm" onClick={() => fileRef.current && fileRef.current.click()}><Icon name="upload" size={14} /> {t("imp.file")}</button>
          <input ref={fileRef} type="file" accept=".csv,.tsv,.txt" hidden onChange={onFile} />
          <span className="trk-imp__srcor">{t("entry.or")}</span>
          <span className="trk-imp__srclbl">{t("imp.paste")}</span>
          <button type="button" className="trk-textlink trk-textlink--sm trk-imp__ex" onClick={() => setRaw(EXAMPLE_CSV)}><Icon name="file" size={13} /> {t("imp.example")}</button>
        </div>
        <textarea className="trk-input trk-textarea trk-imp__ta" rows="5" value={raw}
          placeholder={"date,time,sport,league,home,away,book,market,line,side,odds,stake,result,closing,model_prob,player,notes\n2025-05-19,16:30,Soccer,Premier League,Arsenal,Chelsea,Pinnacle,Moneyline,,Arsenal,2.10,100,won,1.98,52,,Value spot"}
          onChange={(e) => setRaw(e.target.value)}></textarea>
        <div className="trk-imp__opts">
          <label className="trk-imp__opt"><span>{t("imp.delimiter")}</span>
            <Select value={delim} onChange={setDelim} options={[{ value: "auto", label: t("imp.auto") }, { value: ",", label: "," }, { value: ";", label: ";" }, { value: "tab", label: "Tab" }]} />
          </label>
          <label className="trk-imp__opt trk-imp__opt--check">
            <input type="checkbox" checked={hasHeader} onChange={(e) => setHasHeader(e.target.checked)} /> {t("imp.header")}
          </label>
          {rows.length ? <span className="trk-imp__count">{dataRows.length} {t("imp.rows")}</span> : null}
        </div>

        {colCount > 0 && (
          <div className="trk-imp__map">
            <div className="trk-imp__maplbl">{t("imp.map")}<span className="trk-imp__recog">{Object.values(mapping).filter(Boolean).length}/{IMPORT_FIELDS.length} {t("imp.recognized")}</span></div>
            {IMPORT_GROUPS.map((grp) => (
              <div className="trk-imp__grp" key={grp.key}>
                <div className="trk-imp__grptitle">{t(grp.label)}</div>
                <div className="trk-imp__mapgrid">
                  {IMPORT_FIELDS.filter((f) => f.group === grp.key).map((f) => (
                    <label className={"trk-imp__mapfield" + (mapping[f.k] ? " is-mapped" : "")} key={f.k}>
                      <span>{mapping[f.k] ? <Icon name="check" size={11} /> : null}{t(f.label)}</span>
                      <Select value={mapping[f.k] || ""} onChange={(v) => setMapping((p) => ({ ...p, [f.k]: v }))} options={colOptions} />
                    </label>
                  ))}
                </div>
              </div>
            ))}
          </div>
        )}
        {raw.trim() && !bets.length ? <div className="trk-entry__err"><Icon name="close" size={14} /> {t("imp.none")}</div> : null}
      </div>
    </Modal>
  );
}

/* =====================================================
   Saved filter views (per scope: "bets")
   value = { q, f }
   ===================================================== */
function FilterPresets({ scope, value, active, onApply, t }) {
  const [items, setItems] = useState(() => lsGet(LS_PRESETS).filter((p) => p.scope === scope));
  const [naming, setNaming] = useState(false);
  const [name, setName] = useState("");
  const refresh = (all) => { lsSet(LS_PRESETS, all); setItems(all.filter((p) => p.scope === scope)); };
  const save = () => {
    const nm = name.trim(); if (!nm) return;
    refresh([...lsGet(LS_PRESETS), { id: "p" + Date.now(), scope, name: nm, value: JSON.parse(JSON.stringify(value)) }]);
    setNaming(false); setName("");
  };
  const remove = (id) => refresh(lsGet(LS_PRESETS).filter((p) => p.id !== id));
  const eq = (a) => JSON.stringify(a) === JSON.stringify(value);

  return (
    <div className="trk-presets">
      <span className="trk-presets__lbl"><Icon name="filter" size={13} /> {t("pre.views")}</span>
      {items.length === 0 && !naming ? <span className="trk-presets__none">{t("pre.none")}</span> : null}
      <div className="trk-presets__chips">
        {items.map((p) => (
          <span key={p.id} className={"trk-preset" + (eq(p.value) ? " is-on" : "")}>
            <button type="button" className="trk-preset__apply" onClick={() => onApply(p.value)}>{p.name}</button>
            <button type="button" className="trk-preset__x" onClick={() => remove(p.id)} aria-label="delete view"><Icon name="close" size={11} /></button>
          </span>
        ))}
      </div>
      {naming ? (
        <span className="trk-presets__namer">
          <input className="trk-input" autoFocus value={name} placeholder={t("pre.name")}
            onChange={(e) => setName(e.target.value)} onKeyDown={(e) => { if (e.key === "Enter") save(); if (e.key === "Escape") setNaming(false); }} />
          <button type="button" className="trk-iconbtn trk-iconbtn--sm" onClick={save} aria-label="save"><Icon name="check" size={14} /></button>
        </span>
      ) : (
        active ? <button type="button" className="trk-textlink trk-textlink--sm" onClick={() => setNaming(true)}><Icon name="plus" size={13} /> {t("pre.save")}</button> : null
      )}
    </div>
  );
}

/* =====================================================
   Bet templates — quick-add dropdown + save-as-template
   ===================================================== */
const TEMPLATE_KEYS = ["sport", "league", "book", "source", "tipster", "market", "line", "side", "player", "position", "live", "odds", "stake", "tags", "bankrollId"];
function betToTemplate(b) { const o = {}; TEMPLATE_KEYS.forEach((k) => { if (b[k] !== undefined) o[k] = b[k]; }); return o; }
function saveTemplate(name, bet) {
  const all = lsGet(LS_TEMPLATES);
  all.push({ id: "tpl" + Date.now(), name: name, fields: betToTemplate(bet) });
  lsSet(LS_TEMPLATES, all);
}
function getTemplates() { return lsGet(LS_TEMPLATES); }
function deleteTemplate(id) { lsSet(LS_TEMPLATES, lsGet(LS_TEMPLATES).filter((x) => x.id !== id)); }

function TemplateMenu({ onPick, onNew, t, refreshKey }) {
  const [open, setOpen] = useState(false);
  const [items, setItems] = useState(getTemplates());
  const ref = useRef(null);
  useEffect(() => { setItems(getTemplates()); }, [refreshKey, open]);
  useEffect(() => {
    if (!open) return;
    const h = (e) => { if (ref.current && !ref.current.contains(e.target)) setOpen(false); };
    window.addEventListener("mousedown", h);
    return () => window.removeEventListener("mousedown", h);
  }, [open]);
  const del = (id, e) => { e.stopPropagation(); deleteTemplate(id); setItems(getTemplates()); };
  return (
    <div className="trk-tplmenu" ref={ref}>
      <Btn variant="ghost" icon="layers" size="sm" onClick={() => setOpen((v) => !v)}>{t("tpl.quick")}</Btn>
      {open && (
        <div className="trk-tplmenu__pop glass">
          {items.length === 0 ? <div className="trk-tplmenu__none">{t("tpl.none")}</div> : items.map((tp) => (
            <button key={tp.id} className="trk-tplmenu__item" onClick={() => { setOpen(false); onPick(tp); }}>
              <span className="trk-tplmenu__nm">{tp.name}</span>
              <span className="trk-tplmenu__meta">{[tp.fields.sport, tp.fields.market].filter(Boolean).join(" · ")}</span>
              <span className="trk-tplmenu__x" onClick={(e) => del(tp.id, e)} role="button" aria-label="delete"><Icon name="trash" size={13} /></span>
            </button>
          ))}
          {onNew && <button className="trk-tplmenu__new" onClick={() => { setOpen(false); onNew(); }}><Icon name="plus" size={14} /> {t("tpl.new")}</button>}
        </div>
      )}
    </div>
  );
}

/* =====================================================
   Daily P&L calendar — month view (units per day)
   ===================================================== */
function fmtUnits1(u) {
  const a = Math.abs(u);
  const s = a >= 100 ? String(Math.round(a)) : String(Math.round(a * 100) / 100);
  return s + "u";
}
function PnLCalendar({ bets, currency }) {
  const { state, t, lang } = useTracker();
  const unit = (state.settings.unitSize) || 100;
  const locale = lang === "pt" ? "pt-BR" : "en-US";
  const map = {};
  bets.filter(TRK.isSettled).forEach((b) => { if (!map[b.date]) map[b.date] = { profit: 0, n: 0 }; map[b.date].profit += TRK.betProfit(b); map[b.date].n++; });
  const dates = Object.keys(map).sort();

  const [cur, setCur] = useState(() => {
    const base = dates.length ? new Date(dates[dates.length - 1] + "T00:00:00") : new Date();
    return { y: base.getFullYear(), m: base.getMonth() };
  });
  const step = (n) => setCur((c) => { const d = new Date(c.y, c.m + n, 1); return { y: d.getFullYear(), m: d.getMonth() }; });
  const [mode, setMode] = useState("units");
  const cellVal = (p) => mode === "units" ? fmtUnits1(p / unit) : TRK.fmtMoney(TRK.round(p), currency);

  const first = new Date(cur.y, cur.m, 1);
  const lead = first.getDay(); // Sunday-first
  const days = new Date(cur.y, cur.m + 1, 0).getDate();
  const dowLabels = lang === "pt" ? ["D", "S", "T", "Q", "Q", "S", "S"] : ["S", "M", "T", "W", "T", "F", "S"];
  const cells = [];
  for (let i = 0; i < lead; i++) cells.push(null);
  for (let d = 1; d <= days; d++) {
    const iso = cur.y + "-" + String(cur.m + 1).padStart(2, "0") + "-" + String(d).padStart(2, "0");
    cells.push({ d, iso, rec: map[iso] });
  }
  const hasData = dates.length > 0;

  return (
    <div className="trk-cal2">
      <div className="trk-cal2__head">
        <div className="trk-cal2__title">{first.toLocaleDateString(locale, { month: "long" })}</div>
        <div className="trk-cal2__right">
          <Segmented size="sm" value={mode} onChange={setMode}
            options={[{ value: "units", label: t("dash.units") }, { value: "money", label: t("dash.money") }]} />
          <span className="trk-cal2__year">{cur.y}</span>
          <div className="trk-cal2__nav">
            <button type="button" className="trk-iconbtn trk-iconbtn--sm" onClick={() => step(-1)} aria-label="previous month"><Icon name="chevron" size={15} style={{ transform: "rotate(180deg)" }} /></button>
            <button type="button" className="trk-iconbtn trk-iconbtn--sm" onClick={() => step(1)} aria-label="next month"><Icon name="chevron" size={15} /></button>
          </div>
        </div>
      </div>
      {!hasData ? <Placeholder label={t("cal.empty")} height={200} /> : (
        <React.Fragment>
          <div className="trk-cal2__dow">{dowLabels.map((l, i) => <span key={i}>{l}</span>)}</div>
          <div className="trk-cal2__grid">
            {cells.map((c, i) => {
              if (!c) return <span key={"b" + i} className="trk-cal2__blank"></span>;
              const rec = c.rec;
              const pos = rec && rec.profit >= 0;
              const cls = rec && rec.n ? (pos ? "is-pos" : "is-neg") : "";
              return (
                <div key={c.iso} className={"trk-cal2__cell " + cls}
                  title={rec ? TRK.fmtMoney(TRK.round(rec.profit), currency) + " \u00b7 " + rec.n + " " + t("cal.bets") : ""}>
                  <span className="trk-cal2__d">{c.d}</span>
                  {rec && rec.n ? <span className="trk-cal2__u">{cellVal(rec.profit)}</span> : null}
                </div>
              );
            })}
          </div>
        </React.Fragment>
      )}
    </div>
  );
}

/* ---------- inline name prompt (sandbox blocks window.prompt) ---------- */
function NameModal({ open, title, placeholder, onConfirm, onClose, t }) {
  const [v, setV] = useState("");
  useEffect(() => { if (open) setV(""); }, [open]);
  const go = () => { if (v.trim()) { onConfirm(v.trim()); onClose(); } };
  return (
    <Modal open={open} onClose={onClose} title={title} width={420}
      footer={<>
        <Btn variant="ghost" onClick={onClose}>{t("form.cancel")}</Btn>
        <Btn variant="primary" icon="check" disabled={!v.trim()} onClick={go}>{t("form.save")}</Btn>
      </>}>
      <input className="trk-input" autoFocus value={v} placeholder={placeholder}
        onChange={(e) => setV(e.target.value)} onKeyDown={(e) => { if (e.key === "Enter") go(); }} />
    </Modal>
  );
}

Object.assign(window, { CSVImport, FilterPresets, TemplateMenu, PnLCalendar, NameModal, betToTemplate, saveTemplate, getTemplates });
