const { useState, useEffect, useMemo, useCallback } = React;

// ---------- design tokens ----------
const COLORS = {
  paper: "#EEF1EE",
  card: "#F8FAF7",
  ink: "#1E2A22",
  inkMuted: "#5B665D",
  forest: "#2F6F4E",
  forestSoft: "#E4EEE7",
  rust: "#B8492E",
  rustSoft: "#F5E5E0",
  brass: "#A9812E",
  brassSoft: "#F1E9D4",
  line: "#CDD5CA",
  lineSoft: "#E1E6DE",
};

const FONT = {
  display: "'Space Grotesk', ui-sans-serif, sans-serif",
  mono: "'IBM Plex Mono', ui-monospace, monospace",
  body: "'IBM Plex Sans', ui-sans-serif, sans-serif",
};

const QUOTES = [
  "Plans survive contact with the market only if you follow them.",
  "The trade you skip is still a decision.",
  "Small losses are tuition. Big losses are lessons you didn't need.",
  "Boredom is not a signal to enter a trade.",
  "Your edge is a process, not a prediction.",
  "Cut the loss before it becomes a story you have to tell.",
  "Consistency beats intensity over a full month.",
  "The market doesn't know you're on a losing streak.",
  "Size the position for the plan, not for the feeling.",
  "A good exit is worth more than a good entry.",
  "Revenge trades are paid for twice.",
  "Write it down before you forget why it felt obvious.",
  "The best traders are boring on purpose.",
  "One bad day doesn't erase a good process.",
  "If it wasn't in the plan, it wasn't a setup.",
  "Discipline is remembering what you decided when you were calm.",
];

const MOODS = ["Confident", "Calm", "Anxious", "Frustrated", "Excited"];
const MOOD_COLORS = {
  Confident: { fg: COLORS.forest, bg: COLORS.forestSoft },
  Calm: { fg: "#4C6B8A", bg: "#E4EAF0" },
  Anxious: { fg: "#C97A2B", bg: "#F5E8D8" },
  Frustrated: { fg: COLORS.rust, bg: COLORS.rustSoft },
  Excited: { fg: "#8A4C6B", bg: "#F0E2EA" },
};

const WEEK_LABELS = ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"];
const API_BASE = "/api";

// ---------- helpers ----------
function formatKey(d) {
  const y = d.getFullYear();
  const m = String(d.getMonth() + 1).padStart(2, "0");
  const day = String(d.getDate()).padStart(2, "0");
  return `${y}-${m}-${day}`;
}
function todayKey() {
  return formatKey(new Date());
}
function monthKey(d) {
  return formatKey(d).slice(0, 7);
}
function parseKey(key) {
  const [y, m, d] = key.split("-").map(Number);
  return new Date(y, m - 1, d);
}
function fmtMoney(n, opts = {}) {
  if (n === null || n === undefined || Number.isNaN(n)) return "—";
  const sign = n < 0 ? "-" : opts.showPlus && n > 0 ? "+" : "";
  return `${sign}$${Math.abs(n).toLocaleString(undefined, {
    minimumFractionDigits: 2,
    maximumFractionDigits: 2,
  })}`;
}
function fmtDateHuman(key) {
  const d = parseKey(key);
  return d.toLocaleDateString(undefined, {
    weekday: "short",
    month: "short",
    day: "numeric",
    year: "numeric",
  });
}
function uid() {
  return (typeof crypto !== "undefined" && crypto.randomUUID)
    ? crypto.randomUUID()
    : Math.random().toString(36).slice(2) + Date.now().toString(36);
}
function cloneData(d) {
  return JSON.parse(JSON.stringify(d));
}
function addMonths(date, n) {
  const d = new Date(date);
  d.setMonth(d.getMonth() + n);
  d.setDate(1);
  return d;
}
function buildMonthGrid(monthDate) {
  const y = monthDate.getFullYear();
  const m = monthDate.getMonth();
  const firstDay = new Date(y, m, 1);
  const startOffset = (firstDay.getDay() + 6) % 7;
  const daysInMon = new Date(y, m + 1, 0).getDate();
  const cells = [];
  for (let i = 0; i < startOffset; i++) cells.push(null);
  for (let d = 1; d <= daysInMon; d++) cells.push(new Date(y, m, d));
  while (cells.length % 7 !== 0) cells.push(null);
  const weeks = [];
  for (let i = 0; i < cells.length; i += 7) weeks.push(cells.slice(i, i + 7));
  return weeks;
}

// ---------- API layer ----------
async function apiLogin(password) {
  const res = await fetch(`${API_BASE}/login.php`, {
    method: "POST",
    credentials: "include",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({ password }),
  });
  return res.ok;
}
async function apiLogout() {
  try {
    await fetch(`${API_BASE}/logout.php`, { method: "POST", credentials: "include" });
  } catch (e) {
    // ignore
  }
}
async function apiCheckSession() {
  try {
    const res = await fetch(`${API_BASE}/me.php`, { credentials: "include" });
    if (!res.ok) return false;
    const data = await res.json();
    return !!data.authed;
  } catch (e) {
    return false;
  }
}
async function apiGetState() {
  const res = await fetch(`${API_BASE}/state.php`, { credentials: "include" });
  if (!res.ok) throw new Error("Failed to load");
  return res.json();
}
async function apiSaveState(state) {
  const res = await fetch(`${API_BASE}/state.php`, {
    method: "POST",
    credentials: "include",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify(state),
  });
  if (!res.ok) throw new Error("Failed to save");
  return res.json();
}

// ---------- icons (hand-drawn, no icon library needed) ----------
function Icon({ children, size = 18, color = "currentColor", strokeWidth = 1.8 }) {
  return (
    <svg width={size} height={size} viewBox="0 0 24 24" fill="none" stroke={color} strokeWidth={strokeWidth} strokeLinecap="round" strokeLinejoin="round">
      {children}
    </svg>
  );
}
const HomeIcon = (props) => (
  <Icon {...props}><path d="M3 11l9-8 9 8" /><path d="M5 10v10h14V10" /></Icon>
);
const CalendarIcon = (props) => (
  <Icon {...props}><rect x="3" y="5" width="18" height="16" rx="2" /><path d="M3 10h18" /><path d="M8 3v4" /><path d="M16 3v4" /></Icon>
);
const JournalIcon = (props) => (
  <Icon {...props}><path d="M5 4h11a2 2 0 0 1 2 2v14l-3-2-3 2-3-2-3 2V6a2 2 0 0 1 2-2z" /><path d="M9 9h6" /><path d="M9 13h6" /></Icon>
);
const PlusIcon = (props) => (
  <Icon {...props}><path d="M12 5v14" /><path d="M5 12h14" /></Icon>
);
const XIcon = (props) => (
  <Icon {...props}><path d="M18 6 6 18" /><path d="M6 6l12 12" /></Icon>
);
const ChevronLeftIcon = (props) => (
  <Icon {...props}><path d="M15 18l-6-6 6-6" /></Icon>
);
const ChevronRightIcon = (props) => (
  <Icon {...props}><path d="M9 18l6-6-6-6" /></Icon>
);
const TrashIcon = (props) => (
  <Icon {...props}><path d="M4 7h16" /><path d="M9 7V4h6v3" /><path d="M6 7l1 13h10l1-13" /></Icon>
);
const PencilIcon = (props) => (
  <Icon {...props}><path d="M12 20h9" /><path d="M16.5 3.5a2.1 2.1 0 0 1 3 3L7 19l-4 1 1-4Z" /></Icon>
);
const LoaderIcon = ({ size = 22, color = COLORS.ink }) => (
  <svg width={size} height={size} viewBox="0 0 24 24" fill="none" className="tj-spin">
    <circle cx="12" cy="12" r="9" stroke={color} strokeWidth="2.5" strokeLinecap="round" strokeDasharray="40 100" opacity="0.9" />
  </svg>
);

// ---------- hand-rolled equity chart (no chart library needed) ----------
function EquityChart({ data }) {
  const width = 320;
  const height = 200;
  const padding = { top: 10, right: 10, bottom: 20, left: 46 };
  const innerW = width - padding.left - padding.right;
  const innerH = height - padding.top - padding.bottom;

  const allVals = data.reduce((acc, d) => acc.concat([d.equity, d.adjusted]), []);
  const min = Math.min.apply(null, allVals);
  const max = Math.max.apply(null, allVals);
  const range = max - min || 1;
  const pad = range * 0.15;
  const yMin = min - pad;
  const yMax = max + pad;

  const xFor = (i) => padding.left + (data.length <= 1 ? 0 : (i / (data.length - 1)) * innerW);
  const yFor = (v) => padding.top + innerH - ((v - yMin) / (yMax - yMin)) * innerH;

  function pathFor(key) {
    return data.map((d, i) => `${i === 0 ? "M" : "L"} ${xFor(i).toFixed(1)} ${yFor(d[key]).toFixed(1)}`).join(" ");
  }

  const tickCount = 4;
  const ticks = Array.from({ length: tickCount + 1 }, (_, i) => yMin + ((yMax - yMin) * i) / tickCount);

  return (
    <div style={{ width: "100%", overflowX: "auto" }}>
      <svg viewBox={`0 0 ${width} ${height}`} style={{ width: "100%", height: 200 }}>
        {ticks.map((t, i) => (
          <g key={i}>
            <line x1={padding.left} x2={width - padding.right} y1={yFor(t)} y2={yFor(t)} stroke={COLORS.line} strokeDasharray="2 4" />
            <text x={padding.left - 6} y={yFor(t) + 3} fontSize="9" fill={COLORS.inkMuted} textAnchor="end" fontFamily={FONT.mono}>
              {(t / 1000).toFixed(1)}k
            </text>
          </g>
        ))}
        <path d={pathFor("equity")} fill="none" stroke={COLORS.ink} strokeWidth="2" />
        <path d={pathFor("adjusted")} fill="none" stroke={COLORS.brass} strokeWidth="2" strokeDasharray="4 3" />
      </svg>
    </div>
  );
}

// ---------- auth screens ----------
function CenteredLoader() {
  return (
    <div style={{ minHeight: "100vh", display: "flex", alignItems: "center", justifyContent: "center", background: COLORS.paper }}>
      <LoaderIcon size={24} />
    </div>
  );
}

function LoginScreen({ onSuccess }) {
  const [password, setPassword] = useState("");
  const [error, setError] = useState("");
  const [busy, setBusy] = useState(false);

  async function submit(e) {
    e.preventDefault();
    setBusy(true);
    setError("");
    const ok = await apiLogin(password);
    setBusy(false);
    if (ok) {
      onSuccess();
    } else {
      setError("Wrong password.");
    }
  }

  return (
    <div style={{ minHeight: "100vh", display: "flex", alignItems: "center", justifyContent: "center", background: COLORS.paper, fontFamily: FONT.body }}>
      <form onSubmit={submit} style={{ width: "100%", maxWidth: 300, padding: 24 }}>
        <div style={{ fontFamily: FONT.display, fontSize: 24, fontWeight: 700, color: COLORS.ink, marginBottom: 4 }}>Tally</div>
        <div style={{ fontFamily: FONT.mono, fontSize: 11, color: COLORS.inkMuted, marginBottom: 16 }}>Enter your password</div>
        <input
          type="password"
          value={password}
          onChange={(e) => setPassword(e.target.value)}
          autoFocus
          style={{ width: "100%", padding: "10px 12px", borderRadius: 8, border: `1px solid ${COLORS.line}`, fontFamily: FONT.mono, background: COLORS.card, color: COLORS.ink, marginBottom: 10, boxSizing: "border-box" }}
        />
        {error && <div style={{ color: COLORS.rust, fontSize: 12, marginBottom: 10, fontFamily: FONT.body }}>{error}</div>}
        <button type="submit" disabled={busy} style={{ width: "100%", padding: "10px 12px", borderRadius: 8, background: COLORS.ink, color: COLORS.paper, fontFamily: FONT.mono, border: "none" }}>
          {busy ? "Checking..." : "Log in"}
        </button>
      </form>
    </div>
  );
}

function Root() {
  const [authed, setAuthed] = useState(null);

  useEffect(() => {
    apiCheckSession().then(setAuthed);
  }, []);

  if (authed === null) return <CenteredLoader />;
  if (!authed) return <LoginScreen onSuccess={() => setAuthed(true)} />;
  return <TradingJournalApp onLogout={() => { apiLogout(); setAuthed(false); }} />;
}

// ---------- main app ----------
function TradingJournalApp({ onLogout }) {
  const [data, setData] = useState({ trades: {}, days: {}, entries: {} });
  const [loading, setLoading] = useState(true);
  const [saving, setSaving] = useState(false);
  const [tab, setTab] = useState("home");
  const [viewMonth, setViewMonth] = useState(() => {
    const d = new Date();
    d.setDate(1);
    return d;
  });
  const [openDate, setOpenDate] = useState(null);
  const [toast, setToast] = useState(null);
  const [confirmReset, setConfirmReset] = useState(false);
  const [journalFilter, setJournalFilter] = useState("");

  useEffect(() => {
    (async () => {
      try {
        const remote = await apiGetState();
        setData({
          trades: remote.trades || {},
          days: remote.days || {},
          entries: remote.entries || {},
        });
      } catch (e) {
        // start empty if the very first load fails; it'll be created on first save
      } finally {
        setLoading(false);
      }
    })();
  }, []);

  const persist = useCallback(async (next) => {
    setData(next);
    setSaving(true);
    try {
      await apiSaveState(next);
    } catch (e) {
      setToast({ type: "error", text: "Couldn't save — check your connection and try again." });
    } finally {
      setSaving(false);
    }
  }, []);

  useEffect(() => {
    if (!toast) return;
    const t = setTimeout(() => setToast(null), 3200);
    return () => clearTimeout(t);
  }, [toast]);

  const upsertTrade = useCallback(
    (dateKey, trade) => {
      const next = cloneData(data);
      next.trades[trade.id] = { ...trade, date: dateKey };
      persist(next);
    },
    [data, persist]
  );

  const deleteTrade = useCallback(
    (id) => {
      const next = cloneData(data);
      delete next.trades[id];
      persist(next);
    },
    [data, persist]
  );

  const setDayMeta = useCallback(
    (dateKey, patch) => {
      const next = cloneData(data);
      next.days[dateKey] = { ...(next.days[dateKey] || {}), ...patch };
      persist(next);
    },
    [data, persist]
  );

  const upsertEntry = useCallback(
    (entry) => {
      const next = cloneData(data);
      next.entries[entry.id] = entry;
      persist(next);
    },
    [data, persist]
  );

  const deleteEntry = useCallback(
    (id) => {
      const next = cloneData(data);
      delete next.entries[id];
      persist(next);
    },
    [data, persist]
  );

  const resetAll = useCallback(() => {
    persist({ trades: {}, days: {}, entries: {} });
    setConfirmReset(false);
    setToast({ type: "ok", text: "All data cleared." });
  }, [persist]);

  const currentMonthStr = monthKey(new Date());

  const dashboardStats = useMemo(() => {
    let totalTrades = 0;
    let totalWins = 0;
    let activeDays = 0;
    let totalWithdrawal = 0;
    let totalDeposit = 0;
    Object.entries(data.days).forEach(([k, v]) => {
      if (!k.startsWith(currentMonthStr) || !v) return;
      const tc = Number(v.tradesCount || 0);
      if (tc > 0) activeDays += 1;
      totalTrades += tc;
      totalWins += Number(v.winsCount || 0);
      totalWithdrawal += Number(v.withdrawal || 0);
      totalDeposit += Number(v.deposit || 0);
    });
    return {
      pl: totalWithdrawal - totalDeposit,
      winRate: totalTrades ? (totalWins / totalTrades) * 100 : null,
      avgPerDay: activeDays ? totalTrades / activeDays : null,
      total: totalTrades,
    };
  }, [data.days, currentMonthStr]);

  const equitySeries = useMemo(() => {
    const now = new Date();
    const y = now.getFullYear();
    const m = now.getMonth();
    const lastDay = new Date(y, m + 1, 0).getDate();
    const endDay = Math.min(now.getDate(), lastDay);
    let lastEquity = null;
    let cumWithdrawal = 0;
    const series = [];
    for (let day = 1; day <= endDay; day++) {
      const key = formatKey(new Date(y, m, day));
      const meta = data.days[key];
      if (meta && meta.equity !== undefined && meta.equity !== null && meta.equity !== "") {
        lastEquity = Number(meta.equity);
      }
      cumWithdrawal += Number((meta && meta.withdrawal) || 0);
      if (lastEquity !== null) {
        series.push({
          day,
          label: String(day),
          equity: lastEquity,
          adjusted: lastEquity + cumWithdrawal,
        });
      }
    }
    return series;
  }, [data.days]);

  const equityDiffByDate = useMemo(() => {
    const entries = Object.entries(data.days)
      .filter(([, v]) => v && v.equity !== undefined && v.equity !== null && v.equity !== "")
      .map(([k, v]) => [k, Number(v.equity)])
      .sort((a, b) => (a[0] < b[0] ? -1 : 1));
    const map = {};
    for (let i = 1; i < entries.length; i++) {
      const [key, eq] = entries[i];
      const [, prevEq] = entries[i - 1];
      map[key] = eq - prevEq;
    }
    return map;
  }, [data.days]);

  const weeks = useMemo(() => buildMonthGrid(viewMonth), [viewMonth]);

  const tradesByDate = useMemo(() => {
    const map = {};
    Object.values(data.trades).forEach((t) => {
      map[t.date] = map[t.date] || [];
      map[t.date].push(t);
    });
    return map;
  }, [data.trades]);

  const entriesByDate = useMemo(() => {
    const map = {};
    Object.values(data.entries).forEach((e) => {
      map[e.date] = e;
    });
    return map;
  }, [data.entries]);

  const monthRollup = useMemo(() => {
    const prefix = monthKey(viewMonth);
    let deposit = 0;
    let withdrawal = 0;
    Object.entries(data.days).forEach(([k, v]) => {
      if (k.startsWith(prefix)) {
        deposit += Number(v.deposit || 0);
        withdrawal += Number(v.withdrawal || 0);
      }
    });
    return { deposit, withdrawal };
  }, [data.days, viewMonth]);

  const quote = useMemo(() => QUOTES[Math.floor(Math.random() * QUOTES.length)], []);

  const openDayTrades = openDate ? tradesByDate[openDate] || [] : [];
  const openDayMeta = openDate ? data.days[openDate] || {} : {};
  const openDayEntry = openDate ? entriesByDate[openDate] : null;

  if (loading) return <CenteredLoader />;

  return (
    <div className="min-h-screen flex flex-col" style={{ background: COLORS.paper, fontFamily: FONT.body, minHeight: "100vh", display: "flex", flexDirection: "column" }}>
      <TopBar saving={saving} />

      <main style={{ flex: 1, padding: "0 16px 24px", maxWidth: 640, margin: "0 auto", width: "100%", boxSizing: "border-box" }}>
        {tab === "home" && (
          <Dashboard
            stats={dashboardStats}
            equitySeries={equitySeries}
            quote={quote}
            confirmReset={confirmReset}
            setConfirmReset={setConfirmReset}
            resetAll={resetAll}
            onLogout={onLogout}
          />
        )}
        {tab === "calendar" && (
          <CalendarView
            viewMonth={viewMonth}
            onPrev={() => setViewMonth((d) => addMonths(d, -1))}
            onNext={() => setViewMonth((d) => addMonths(d, 1))}
            weeks={weeks}
            entriesByDate={entriesByDate}
            daysMeta={data.days}
            equityDiffByDate={equityDiffByDate}
            rollup={monthRollup}
            onSelectDate={setOpenDate}
          />
        )}
        {tab === "journal" && (
          <JournalView
            entries={Object.values(data.entries)}
            onOpenDate={setOpenDate}
            onNewEntry={() => setOpenDate(todayKey())}
            filter={journalFilter}
            setFilter={setJournalFilter}
          />
        )}
      </main>

      <BottomNav tab={tab} setTab={setTab} />

      {openDate && (
        <DayDetailModal
          key={openDate}
          dateKey={openDate}
          onClose={() => setOpenDate(null)}
          dayMeta={openDayMeta}
          trades={openDayTrades}
          entry={openDayEntry}
          onSaveDayMeta={setDayMeta}
          onUpsertTrade={upsertTrade}
          onDeleteTrade={deleteTrade}
          onUpsertEntry={upsertEntry}
          onDeleteEntry={deleteEntry}
        />
      )}

      <ToastBanner toast={toast} />
    </div>
  );
}

// ---------- top-level chrome ----------
function TopBar({ saving }) {
  return (
    <div style={{ position: "sticky", top: 0, zIndex: 40, padding: "16px 16px 8px", background: COLORS.paper }}>
      <div style={{ display: "flex", alignItems: "baseline", justifyContent: "space-between", maxWidth: 640, margin: "0 auto" }}>
        <div style={{ fontFamily: FONT.display, color: COLORS.ink, fontSize: 20, fontWeight: 700, letterSpacing: "-0.01em" }}>Tally</div>
        <div style={{ fontFamily: FONT.mono, fontSize: 10, color: COLORS.inkMuted }}>
          {saving ? "Saving…" : new Date().toLocaleDateString(undefined, { weekday: "long", month: "short", day: "numeric" })}
        </div>
      </div>
      <div style={{ height: 2, background: `linear-gradient(90deg, ${COLORS.brass}, transparent)`, marginTop: 8, maxWidth: 640, marginLeft: "auto", marginRight: "auto" }} />
    </div>
  );
}

function BottomNav({ tab, setTab }) {
  const items = [
    { id: "home", label: "Home", Icon: HomeIcon },
    { id: "calendar", label: "Calendar", Icon: CalendarIcon },
    { id: "journal", label: "Journal", Icon: JournalIcon },
  ];
  return (
    <div style={{ position: "sticky", bottom: 0, zIndex: 40, background: COLORS.paper, borderTop: `1px solid ${COLORS.line}` }}>
      <div style={{ maxWidth: 640, margin: "0 auto", display: "grid", gridTemplateColumns: "repeat(3, 1fr)" }}>
        {items.map(({ id, label, Icon }) => (
          <button
            key={id}
            onClick={() => setTab(id)}
            style={{ display: "flex", flexDirection: "column", alignItems: "center", gap: 4, padding: "10px 0", color: tab === id ? COLORS.ink : COLORS.inkMuted, background: "none", border: "none" }}
          >
            <Icon size={20} strokeWidth={tab === id ? 2.4 : 1.8} />
            <span style={{ fontFamily: FONT.mono, fontSize: 10 }}>{label}</span>
          </button>
        ))}
      </div>
    </div>
  );
}

function ToastBanner({ toast }) {
  if (!toast) return null;
  return (
    <div style={{ position: "fixed", bottom: 80, left: 0, right: 0, display: "flex", justifyContent: "center", zIndex: 50, padding: "0 16px" }}>
      <div style={{ padding: "8px 16px", borderRadius: 8, fontSize: 13, maxWidth: 380, background: toast.type === "error" ? COLORS.rust : COLORS.ink, color: COLORS.paper, fontFamily: FONT.body }}>
        {toast.text}
      </div>
    </div>
  );
}

// ---------- dashboard ----------
function Dashboard({ stats, equitySeries, quote, confirmReset, setConfirmReset, resetAll, onLogout }) {
  return (
    <div style={{ display: "flex", flexDirection: "column", gap: 20, paddingTop: 8 }}>
      <div style={{ borderRadius: 16, padding: 20, background: COLORS.card, border: `1px solid ${COLORS.line}` }}>
        <div style={{ fontFamily: FONT.mono, fontSize: 11, letterSpacing: "0.08em", color: COLORS.brass, textTransform: "uppercase", marginBottom: 8 }}>
          Today's reminder
        </div>
        <div style={{ fontFamily: FONT.display, color: COLORS.ink, fontSize: 18, lineHeight: 1.4 }}>"{quote}"</div>
      </div>

      <div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: 12 }}>
        <StatCard label="Month P/L" value={fmtMoney(stats.pl, { showPlus: true })} tone={stats.pl > 0 ? "pos" : stats.pl < 0 ? "neg" : "flat"} />
        <StatCard label="Win rate" value={stats.winRate === null ? "—" : `${stats.winRate.toFixed(0)}%`} tone="flat" />
        <StatCard label="Avg trades/day" value={stats.avgPerDay === null ? "—" : stats.avgPerDay.toFixed(1)} tone="flat" />
        <StatCard label="Trades this month" value={String(stats.total)} tone="flat" />
      </div>

      <div style={{ borderRadius: 16, padding: 20, background: COLORS.card, border: `1px solid ${COLORS.line}` }}>
        <div style={{ fontFamily: FONT.display, color: COLORS.ink, fontWeight: 600, marginBottom: 8 }}>Equity — this month</div>
        <div style={{ display: "flex", alignItems: "center", gap: 16, marginBottom: 8 }}>
          <LegendDot color={COLORS.ink} label="Equity" />
          <LegendDot color={COLORS.brass} label="Equity + withdrawals" dashed />
        </div>
        {equitySeries.length === 0 ? (
          <div style={{ color: COLORS.inkMuted, fontFamily: FONT.body, fontSize: 14, padding: "40px 0", textAlign: "center" }}>
            No equity logged yet this month. Add today's equity from the Calendar to start the line.
          </div>
        ) : (
          <EquityChart data={equitySeries} />
        )}
      </div>

      <div style={{ textAlign: "center", paddingTop: 8, paddingBottom: 4, display: "flex", flexDirection: "column", gap: 10 }}>
        {!confirmReset ? (
          <button onClick={() => setConfirmReset(true)} style={{ fontFamily: FONT.mono, fontSize: 11, color: COLORS.inkMuted, background: "none", border: "none", textDecoration: "underline" }}>
            Reset all data
          </button>
        ) : (
          <div style={{ display: "flex", alignItems: "center", justifyContent: "center", gap: 12 }}>
            <span style={{ fontFamily: FONT.body, fontSize: 12, color: COLORS.rust }}>Delete everything?</span>
            <button onClick={resetAll} style={{ fontFamily: FONT.mono, fontSize: 11, color: COLORS.rust, background: "none", border: "none", textDecoration: "underline" }}>
              Yes, reset
            </button>
            <button onClick={() => setConfirmReset(false)} style={{ fontFamily: FONT.mono, fontSize: 11, color: COLORS.inkMuted, background: "none", border: "none", textDecoration: "underline" }}>
              Cancel
            </button>
          </div>
        )}
        <button onClick={onLogout} style={{ fontFamily: FONT.mono, fontSize: 11, color: COLORS.inkMuted, background: "none", border: "none", textDecoration: "underline" }}>
          Log out
        </button>
      </div>
    </div>
  );
}

function StatCard({ label, value, tone }) {
  const color = tone === "pos" ? COLORS.forest : tone === "neg" ? COLORS.rust : COLORS.ink;
  return (
    <div style={{ borderRadius: 12, padding: 16, background: COLORS.card, border: `1px solid ${COLORS.line}` }}>
      <div style={{ fontFamily: FONT.mono, fontSize: 10, letterSpacing: "0.08em", color: COLORS.inkMuted, textTransform: "uppercase", marginBottom: 4 }}>
        {label}
      </div>
      <div style={{ fontFamily: FONT.mono, color, fontSize: 22, fontWeight: 600 }}>{value}</div>
    </div>
  );
}

function LegendDot({ color, label, dashed }) {
  return (
    <div style={{ display: "flex", alignItems: "center", gap: 6 }}>
      <span
        style={{
          width: 14,
          height: dashed ? 2 : 3,
          background: dashed ? "transparent" : color,
          backgroundImage: dashed ? `repeating-linear-gradient(90deg, ${color} 0 4px, transparent 4px 7px)` : undefined,
          display: "inline-block",
          borderRadius: 2,
        }}
      />
      <span style={{ fontFamily: FONT.body, fontSize: 12, color: COLORS.inkMuted }}>{label}</span>
    </div>
  );
}

// ---------- calendar ----------
function CalendarView({ viewMonth, onPrev, onNext, weeks, entriesByDate, daysMeta, equityDiffByDate, rollup, onSelectDate }) {
  const monthLabel = viewMonth.toLocaleDateString(undefined, { month: "long", year: "numeric" });
  const today = todayKey();
  return (
    <div style={{ display: "flex", flexDirection: "column", gap: 16, paddingTop: 8 }}>
      <div style={{ display: "flex", alignItems: "center", justifyContent: "space-between" }}>
        <button onClick={onPrev} style={{ padding: 8, borderRadius: 8, border: `1px solid ${COLORS.line}`, background: "none" }} aria-label="Previous month">
          <ChevronLeftIcon size={18} color={COLORS.ink} />
        </button>
        <div style={{ fontFamily: FONT.display, color: COLORS.ink, fontSize: 18, fontWeight: 600 }}>{monthLabel}</div>
        <button onClick={onNext} style={{ padding: 8, borderRadius: 8, border: `1px solid ${COLORS.line}`, background: "none" }} aria-label="Next month">
          <ChevronRightIcon size={18} color={COLORS.ink} />
        </button>
      </div>

      <div style={{ display: "flex", justifyContent: "space-between", flexWrap: "wrap", columnGap: 12, rowGap: 4, fontFamily: FONT.mono, fontSize: 11, color: COLORS.inkMuted }}>
        <span>Deposits: <b style={{ color: COLORS.rust }}>{rollup.deposit ? "-" : ""}{fmtMoney(rollup.deposit)}</b></span>
        <span>P/L: <b style={{ color: rollup.withdrawal - rollup.deposit >= 0 ? COLORS.forest : COLORS.rust }}>{fmtMoney(rollup.withdrawal - rollup.deposit, { showPlus: true })}</b></span>
        <span>Withdrawals: <b style={{ color: COLORS.forest }}>{rollup.withdrawal ? "+" : ""}{fmtMoney(rollup.withdrawal)}</b></span>
      </div>

      <div>
        <div style={{ display: "grid", gridTemplateColumns: "repeat(7, 1fr)", marginBottom: 4 }}>
          {WEEK_LABELS.map((w) => (
            <div key={w} style={{ fontFamily: FONT.mono, fontSize: 10, color: COLORS.inkMuted, textAlign: "center", textTransform: "uppercase" }}>
              {w}
            </div>
          ))}
        </div>
        <div style={{ display: "flex", flexDirection: "column", gap: 4 }}>
          {weeks.map((week, wi) => (
            <div key={wi} style={{ display: "grid", gridTemplateColumns: "repeat(7, 1fr)", gap: 4 }}>
              {week.map((date, di) => {
                if (!date) return <div key={di} />;
                const key = formatKey(date);
                const meta = daysMeta[key] || {};
                const hasEntry = !!entriesByDate[key];
                const isToday = key === today;
                const diff = equityDiffByDate[key];
                const hasEquity = meta.equity !== undefined && meta.equity !== "";
                return (
                  <button
                    key={di}
                    onClick={() => onSelectDate(key)}
                    style={{
                      borderRadius: 8,
                      padding: 4,
                      display: "flex",
                      flexDirection: "column",
                      alignItems: "flex-start",
                      textAlign: "left",
                      background: COLORS.card,
                      border: `${isToday ? 2 : 1}px solid ${isToday ? COLORS.brass : COLORS.line}`,
                      minHeight: 62,
                    }}
                  >
                    <div style={{ display: "flex", alignItems: "center", justifyContent: "space-between", width: "100%" }}>
                      <span style={{ fontFamily: FONT.mono, fontSize: 10, color: COLORS.inkMuted }}>{date.getDate()}</span>
                      {hasEntry && <span style={{ width: 5, height: 5, borderRadius: 999, background: COLORS.brass, display: "inline-block" }} />}
                    </div>
                    {hasEquity && (
                      <span style={{ display: "flex", alignItems: "baseline", gap: 4, flexWrap: "wrap" }}>
                        <span style={{ fontFamily: FONT.mono, fontSize: 10, color: COLORS.ink }}>
                          {Number(meta.equity).toLocaleString(undefined, { maximumFractionDigits: 0 })}
                        </span>
                        {diff !== undefined && (
                          <span style={{ fontFamily: FONT.mono, fontSize: 8, color: diff >= 0 ? COLORS.forest : COLORS.rust }}>
                            {diff >= 0 ? "+" : "-"}{Math.abs(diff).toLocaleString(undefined, { maximumFractionDigits: 0 })}
                          </span>
                        )}
                      </span>
                    )}
                    <div style={{ display: "flex", alignItems: "center", gap: 4, flexWrap: "wrap" }}>
                      {meta.deposit ? <span style={{ fontFamily: FONT.mono, fontSize: 8, color: COLORS.rust }}>-{Number(meta.deposit).toLocaleString()}</span> : null}
                      {meta.withdrawal ? <span style={{ fontFamily: FONT.mono, fontSize: 8, color: COLORS.forest }}>+{Number(meta.withdrawal).toLocaleString()}</span> : null}
                    </div>
                    {meta.tradesCount ? (
                      <span style={{ fontFamily: FONT.mono, fontSize: 8, color: COLORS.inkMuted, marginTop: "auto" }}>
                        {meta.winsCount || 0}/{meta.tradesCount}
                      </span>
                    ) : null}
                  </button>
                );
              })}
            </div>
          ))}
        </div>
      </div>
    </div>
  );
}

// ---------- day detail modal ----------
function DayDetailModal({ dateKey, onClose, dayMeta, trades, entry, onSaveDayMeta, onUpsertTrade, onDeleteTrade, onUpsertEntry, onDeleteEntry }) {
  const [equity, setEquity] = useState(dayMeta.equity ?? "");
  const [deposit, setDeposit] = useState(dayMeta.deposit ?? "");
  const [withdrawal, setWithdrawal] = useState(dayMeta.withdrawal ?? "");
  const [tradesCount, setTradesCount] = useState(dayMeta.tradesCount ?? "");
  const [winsCount, setWinsCount] = useState(dayMeta.winsCount ?? "");
  const [addingTrade, setAddingTrade] = useState(false);
  const [editingTrade, setEditingTrade] = useState(null);
  const [editingEntry, setEditingEntry] = useState(false);

  function commitMeta(patch) {
    onSaveDayMeta(dateKey, patch);
  }

  return (
    <div
      style={{ position: "fixed", inset: 0, zIndex: 50, display: "flex", alignItems: "flex-end", justifyContent: "center", background: "rgba(30,42,34,0.45)" }}
      onClick={onClose}
    >
      <div
        onClick={(e) => e.stopPropagation()}
        style={{ width: "100%", maxWidth: 480, borderTopLeftRadius: 20, borderTopRightRadius: 20, overflowY: "auto", background: COLORS.paper, border: `1px solid ${COLORS.line}`, maxHeight: "88vh" }}
      >
        <div style={{ display: "flex", alignItems: "center", justifyContent: "space-between", padding: 16, position: "sticky", top: 0, background: COLORS.paper, borderBottom: `1px solid ${COLORS.line}` }}>
          <div style={{ fontFamily: FONT.display, color: COLORS.ink, fontWeight: 600 }}>{fmtDateHuman(dateKey)}</div>
          <button onClick={onClose} style={{ padding: 6, borderRadius: 8, border: `1px solid ${COLORS.line}`, background: "none" }}>
            <XIcon size={16} color={COLORS.ink} />
          </button>
        </div>

        <div style={{ padding: 16, display: "flex", flexDirection: "column", gap: 20 }}>
          <section>
            <SectionLabel>Equity & cash</SectionLabel>
            <div style={{ display: "grid", gridTemplateColumns: "repeat(3, 1fr)", gap: 8, marginTop: 8 }}>
              <FieldNumber label="Equity" value={equity} onChange={setEquity} onBlur={() => commitMeta({ equity: equity === "" ? undefined : Number(equity) })} />
              <FieldNumber label="Deposit" value={deposit} onChange={setDeposit} onBlur={() => commitMeta({ deposit: deposit === "" ? undefined : Number(deposit) })} tone="neg" />
              <FieldNumber label="Withdrawal" value={withdrawal} onChange={setWithdrawal} onBlur={() => commitMeta({ withdrawal: withdrawal === "" ? undefined : Number(withdrawal) })} tone="pos" />
            </div>
            <div style={{ fontFamily: FONT.mono, fontSize: 10, color: COLORS.inkMuted, marginTop: 4 }}>
              Saved automatically. Withdrawals count as your real gain here — deposits don't.
            </div>
          </section>

          <section>
            <SectionLabel>Trade count</SectionLabel>
            <div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: 8, marginTop: 8 }}>
              <FieldNumber label="Number of trades" value={tradesCount} onChange={setTradesCount} onBlur={() => commitMeta({ tradesCount: tradesCount === "" ? undefined : Number(tradesCount) })} />
              <FieldNumber label="Number of wins" value={winsCount} onChange={setWinsCount} onBlur={() => commitMeta({ winsCount: winsCount === "" ? undefined : Number(winsCount) })} tone="pos" />
            </div>
            <div style={{ fontFamily: FONT.mono, fontSize: 10, color: COLORS.inkMuted, marginTop: 4 }}>
              This is what Home's win rate and trade averages are based on.
            </div>
          </section>

          <section>
            <div style={{ display: "flex", alignItems: "center", justifyContent: "space-between" }}>
              <SectionLabel>Trade details (optional)</SectionLabel>
              <button
                onClick={() => { setEditingTrade(null); setAddingTrade(true); }}
                style={{ display: "flex", alignItems: "center", gap: 4, fontSize: 11, padding: "4px 8px", borderRadius: 8, border: `1px solid ${COLORS.line}`, fontFamily: FONT.mono, color: COLORS.ink, background: "none" }}
              >
                <PlusIcon size={12} /> Add
              </button>
            </div>
            <div style={{ display: "flex", flexDirection: "column", gap: 8, marginTop: 8 }}>
              {trades.length === 0 && !addingTrade && (
                <div style={{ fontFamily: FONT.body, color: COLORS.inkMuted, fontSize: 14 }}>
                  No detailed trades for this day — optional, for the ones worth writing up.
                </div>
              )}
              {trades.map((t) => (
                <TradeRow
                  key={t.id}
                  trade={t}
                  onEdit={() => { setEditingTrade(t); setAddingTrade(true); }}
                  onDelete={() => onDeleteTrade(t.id)}
                />
              ))}
              {addingTrade && (
                <TradeForm
                  key={editingTrade ? editingTrade.id : "new"}
                  initial={editingTrade}
                  onCancel={() => { setAddingTrade(false); setEditingTrade(null); }}
                  onSave={(trade) => {
                    onUpsertTrade(dateKey, { ...trade, id: trade.id || uid() });
                    setAddingTrade(false);
                    setEditingTrade(null);
                  }}
                />
              )}
            </div>
          </section>

          <section>
            <SectionLabel>Journal</SectionLabel>
            <div style={{ marginTop: 8 }}>
              {!entry && !editingEntry && (
                <button
                  onClick={() => setEditingEntry(true)}
                  style={{ width: "100%", fontSize: 14, borderRadius: 8, padding: 12, textAlign: "left", border: `1px dashed ${COLORS.line}`, fontFamily: FONT.body, color: COLORS.inkMuted, background: "none" }}
                >
                  No entry for this day — add one
                </button>
              )}
              {entry && !editingEntry && (
                <EntryDisplay entry={entry} onEdit={() => setEditingEntry(true)} onDelete={() => onDeleteEntry(entry.id)} />
              )}
              {editingEntry && (
                <EntryForm
                  key={entry ? entry.id : "new"}
                  initial={entry}
                  dateKey={dateKey}
                  onCancel={() => setEditingEntry(false)}
                  onSave={(e) => { onUpsertEntry(e); setEditingEntry(false); }}
                />
              )}
            </div>
          </section>
        </div>
      </div>
    </div>
  );
}

function SectionLabel({ children }) {
  return (
    <div style={{ fontFamily: FONT.mono, fontSize: 10, letterSpacing: "0.08em", color: COLORS.brass, textTransform: "uppercase" }}>
      {children}
    </div>
  );
}

function FieldNumber({ label, value, onChange, onBlur, tone }) {
  const color = tone === "pos" ? COLORS.forest : tone === "neg" ? COLORS.rust : COLORS.ink;
  return (
    <label style={{ display: "flex", flexDirection: "column", gap: 4 }}>
      <span style={{ fontFamily: FONT.body, fontSize: 11, color: COLORS.inkMuted }}>{label}</span>
      <input
        type="number"
        inputMode="decimal"
        value={value}
        onChange={(e) => onChange(e.target.value)}
        onBlur={onBlur}
        placeholder="0"
        style={{ borderRadius: 8, padding: "6px 8px", width: "100%", border: `1px solid ${COLORS.line}`, fontFamily: FONT.mono, color, background: COLORS.card, boxSizing: "border-box" }}
      />
    </label>
  );
}

function TextField({ label, value, onChange, placeholder, textarea }) {
  const Comp = textarea ? "textarea" : "input";
  return (
    <label style={{ display: "flex", flexDirection: "column", gap: 4 }}>
      <span style={{ fontFamily: FONT.body, fontSize: 11, color: COLORS.inkMuted }}>{label}</span>
      <Comp
        value={value}
        onChange={(e) => onChange(e.target.value)}
        placeholder={placeholder}
        rows={textarea ? 3 : undefined}
        style={{ borderRadius: 8, padding: "6px 8px", width: "100%", border: `1px solid ${COLORS.line}`, fontFamily: FONT.body, color: COLORS.ink, background: COLORS.card, resize: "none", boxSizing: "border-box" }}
      />
    </label>
  );
}

function TradeRow({ trade, onEdit, onDelete }) {
  const pl = Number(trade.pl) || 0;
  const color = trade.result === "win" ? COLORS.forest : trade.result === "loss" ? COLORS.rust : COLORS.inkMuted;
  return (
    <div style={{ display: "flex", alignItems: "center", justifyContent: "space-between", borderRadius: 8, padding: 8, border: `1px solid ${COLORS.line}`, background: COLORS.card }}>
      <button onClick={onEdit} style={{ display: "flex", alignItems: "center", gap: 8, textAlign: "left", flex: 1, minWidth: 0, background: "none", border: "none" }}>
        <span style={{ width: 8, height: 8, borderRadius: 999, background: color, flexShrink: 0 }} />
        <span style={{ fontFamily: FONT.body, color: COLORS.ink, fontSize: 13, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}>
          {trade.symbol || (trade.result === "win" ? "Win" : trade.result === "loss" ? "Loss" : "Breakeven")}
        </span>
      </button>
      <div style={{ display: "flex", alignItems: "center", gap: 8 }}>
        <span style={{ fontFamily: FONT.mono, fontSize: 13, color }}>{fmtMoney(pl, { showPlus: true })}</span>
        <button onClick={onDelete} aria-label="Delete trade" style={{ background: "none", border: "none" }}>
          <TrashIcon size={14} color={COLORS.inkMuted} />
        </button>
      </div>
    </div>
  );
}

function TradeForm({ initial, onSave, onCancel }) {
  const [result, setResult] = useState((initial && initial.result) || "win");
  const [pl, setPl] = useState(initial && initial.pl !== undefined ? initial.pl : "");
  const [showMore, setShowMore] = useState(!!(initial && (initial.symbol || initial.notes)));
  const [symbol, setSymbol] = useState((initial && initial.symbol) || "");
  const [direction, setDirection] = useState((initial && initial.direction) || "buy");
  const [lotSize, setLotSize] = useState(initial && initial.lotSize !== undefined ? initial.lotSize : "");
  const [entryPrice, setEntryPrice] = useState(initial && initial.entryPrice !== undefined ? initial.entryPrice : "");
  const [exitPrice, setExitPrice] = useState(initial && initial.exitPrice !== undefined ? initial.exitPrice : "");
  const [notes, setNotes] = useState((initial && initial.notes) || "");

  function submit() {
    if (pl === "") return;
    onSave({
      id: initial ? initial.id : undefined,
      result,
      pl: Number(pl),
      symbol: symbol || undefined,
      direction: showMore ? direction : undefined,
      lotSize: lotSize === "" ? undefined : Number(lotSize),
      entryPrice: entryPrice === "" ? undefined : Number(entryPrice),
      exitPrice: exitPrice === "" ? undefined : Number(exitPrice),
      notes: notes || undefined,
    });
  }

  return (
    <div style={{ borderRadius: 8, padding: 12, display: "flex", flexDirection: "column", gap: 12, border: `1px solid ${COLORS.brass}`, background: COLORS.card }}>
      <div style={{ display: "flex", gap: 8 }}>
        {["win", "loss", "breakeven"].map((r) => (
          <button
            key={r}
            onClick={() => setResult(r)}
            style={{
              flex: 1, borderRadius: 8, padding: "6px 0", fontSize: 12, textTransform: "capitalize",
              border: `1px solid ${result === r ? COLORS.ink : COLORS.line}`,
              background: result === r ? COLORS.ink : "transparent",
              color: result === r ? COLORS.paper : COLORS.inkMuted,
              fontFamily: FONT.mono,
            }}
          >
            {r}
          </button>
        ))}
      </div>
      <FieldNumber label="P/L" value={pl} onChange={setPl} onBlur={() => {}} />
      <button onClick={() => setShowMore((s) => !s)} style={{ fontFamily: FONT.mono, fontSize: 11, color: COLORS.brass, alignSelf: "flex-start", background: "none", border: "none" }}>
        {showMore ? "− Less detail" : "+ More detail"}
      </button>
      {showMore && (
        <div style={{ display: "flex", flexDirection: "column", gap: 8 }}>
          <TextField label="Symbol" value={symbol} onChange={setSymbol} placeholder="EURUSD" />
          <div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: 8 }}>
            <FieldNumber label="Lot size" value={lotSize} onChange={setLotSize} onBlur={() => {}} />
            <label style={{ display: "flex", flexDirection: "column", gap: 4 }}>
              <span style={{ fontFamily: FONT.body, fontSize: 11, color: COLORS.inkMuted }}>Direction</span>
              <select
                value={direction}
                onChange={(e) => setDirection(e.target.value)}
                style={{ borderRadius: 8, padding: "6px 8px", border: `1px solid ${COLORS.line}`, fontFamily: FONT.mono, background: COLORS.card, color: COLORS.ink }}
              >
                <option value="buy">Buy</option>
                <option value="sell">Sell</option>
              </select>
            </label>
          </div>
          <div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: 8 }}>
            <FieldNumber label="Entry price" value={entryPrice} onChange={setEntryPrice} onBlur={() => {}} />
            <FieldNumber label="Exit price" value={exitPrice} onChange={setExitPrice} onBlur={() => {}} />
          </div>
          <TextField label="Notes" value={notes} onChange={setNotes} placeholder="Why this trade..." textarea />
        </div>
      )}
      <div style={{ display: "flex", gap: 8, justifyContent: "flex-end" }}>
        <button onClick={onCancel} style={{ padding: "6px 12px", borderRadius: 8, fontSize: 12, border: `1px solid ${COLORS.line}`, fontFamily: FONT.mono, color: COLORS.inkMuted, background: "none" }}>
          Cancel
        </button>
        <button onClick={submit} style={{ padding: "6px 12px", borderRadius: 8, fontSize: 12, background: COLORS.ink, color: COLORS.paper, fontFamily: FONT.mono, border: "none" }}>
          Save trade
        </button>
      </div>
    </div>
  );
}

function EntryForm({ initial, dateKey, onCancel, onSave }) {
  const [title, setTitle] = useState((initial && initial.title) || "");
  const [body, setBody] = useState((initial && initial.body) || "");
  const [mood, setMood] = useState((initial && initial.mood) || "");
  const [tags, setTags] = useState(((initial && initial.tags) || []).join(", "));
  const [screenshot, setScreenshot] = useState((initial && initial.screenshot) || "");
  const [imgError, setImgError] = useState(false);

  function submit() {
    if (!title && !body) return;
    onSave({
      id: initial ? initial.id : uid(),
      date: dateKey,
      title,
      body,
      mood: mood || undefined,
      tags: tags.split(",").map((t) => t.trim()).filter(Boolean),
      screenshot: screenshot.trim() || undefined,
    });
  }

  return (
    <div style={{ borderRadius: 8, padding: 12, display: "flex", flexDirection: "column", gap: 12, border: `1px solid ${COLORS.brass}`, background: COLORS.card }}>
      <TextField label="Title" value={title} onChange={setTitle} placeholder="What happened today" />
      <TextField label="Notes" value={body} onChange={setBody} placeholder="Thoughts, reasoning, what to remember" textarea />
      <div style={{ display: "flex", flexWrap: "wrap", gap: 6 }}>
        {MOODS.map((m) => {
          const c = MOOD_COLORS[m];
          return (
            <button
              key={m}
              onClick={() => setMood(mood === m ? "" : m)}
              style={{
                padding: "4px 10px", borderRadius: 999, fontSize: 12,
                border: `1px solid ${mood === m ? c.fg : COLORS.line}`,
                background: mood === m ? c.bg : "transparent",
                color: mood === m ? c.fg : COLORS.inkMuted,
                fontFamily: FONT.body,
              }}
            >
              {m}
            </button>
          );
        })}
      </div>
      <TextField label="Tags (comma separated)" value={tags} onChange={setTags} placeholder="breakout, discipline" />
      <div>
        <TextField
          label="Screenshot URL"
          value={screenshot}
          onChange={(v) => { setScreenshot(v); setImgError(false); }}
          placeholder="https://..."
        />
        {screenshot && !imgError && (
          <img src={screenshot} alt="Screenshot preview" style={{ borderRadius: 8, marginTop: 8, maxHeight: 120, border: `1px solid ${COLORS.line}` }} onError={() => setImgError(true)} />
        )}
        {screenshot && imgError && (
          <div style={{ fontFamily: FONT.mono, fontSize: 11, color: COLORS.rust, marginTop: 4 }}>Couldn't load an image from that link.</div>
        )}
      </div>
      <div style={{ display: "flex", gap: 8, justifyContent: "flex-end" }}>
        <button onClick={onCancel} style={{ padding: "6px 12px", borderRadius: 8, fontSize: 12, border: `1px solid ${COLORS.line}`, fontFamily: FONT.mono, color: COLORS.inkMuted, background: "none" }}>
          Cancel
        </button>
        <button onClick={submit} style={{ padding: "6px 12px", borderRadius: 8, fontSize: 12, background: COLORS.ink, color: COLORS.paper, fontFamily: FONT.mono, border: "none" }}>
          Save entry
        </button>
      </div>
    </div>
  );
}

function EntryDisplay({ entry, onEdit, onDelete }) {
  return (
    <div style={{ borderRadius: 8, padding: 12, display: "flex", flexDirection: "column", gap: 8, border: `1px solid ${COLORS.line}`, background: COLORS.card }}>
      <div style={{ display: "flex", alignItems: "flex-start", justifyContent: "space-between" }}>
        <div style={{ fontFamily: FONT.display, color: COLORS.ink, fontWeight: 600, fontSize: 14 }}>{entry.title || "Untitled entry"}</div>
        <div style={{ display: "flex", gap: 8 }}>
          <button onClick={onEdit} style={{ background: "none", border: "none" }}><PencilIcon size={13} color={COLORS.inkMuted} /></button>
          <button onClick={onDelete} style={{ background: "none", border: "none" }}><TrashIcon size={13} color={COLORS.inkMuted} /></button>
        </div>
      </div>
      {entry.body && <div style={{ fontFamily: FONT.body, color: COLORS.ink, fontSize: 13, whiteSpace: "pre-wrap" }}>{entry.body}</div>}
      {entry.screenshot && <img src={entry.screenshot} alt="Journal screenshot" style={{ borderRadius: 8, maxHeight: 160, border: `1px solid ${COLORS.line}` }} />}
      <div style={{ display: "flex", alignItems: "center", gap: 6, flexWrap: "wrap" }}>
        {entry.mood && (
          <span style={{ padding: "2px 8px", borderRadius: 999, fontSize: 12, background: (MOOD_COLORS[entry.mood] || { bg: COLORS.brassSoft }).bg, color: (MOOD_COLORS[entry.mood] || { fg: COLORS.brass }).fg, fontFamily: FONT.body }}>
            {entry.mood}
          </span>
        )}
        {(entry.tags || []).map((t) => (
          <span key={t} style={{ padding: "2px 8px", borderRadius: 999, fontSize: 12, background: COLORS.lineSoft, color: COLORS.inkMuted, fontFamily: FONT.body }}>#{t}</span>
        ))}
      </div>
    </div>
  );
}

// ---------- journal list ----------
function JournalView({ entries, onOpenDate, onNewEntry, filter, setFilter }) {
  const sorted = [...entries].sort((a, b) => (a.date < b.date ? 1 : -1));
  const filtered = filter
    ? sorted.filter((e) => (e.title + " " + e.body + " " + (e.tags || []).join(" ")).toLowerCase().includes(filter.toLowerCase()))
    : sorted;
  return (
    <div style={{ display: "flex", flexDirection: "column", gap: 16, paddingTop: 8 }}>
      <div style={{ display: "flex", gap: 8 }}>
        <input
          value={filter}
          onChange={(e) => setFilter(e.target.value)}
          placeholder="Search entries..."
          style={{ flex: 1, borderRadius: 8, padding: "8px 12px", fontSize: 14, border: `1px solid ${COLORS.line}`, fontFamily: FONT.body, background: COLORS.card, color: COLORS.ink }}
        />
        <button onClick={onNewEntry} style={{ display: "flex", alignItems: "center", gap: 4, padding: "0 12px", borderRadius: 8, fontSize: 12, background: COLORS.ink, color: COLORS.paper, fontFamily: FONT.mono, border: "none" }}>
          <PlusIcon size={14} /> New
        </button>
      </div>
      {filtered.length === 0 ? (
        <div style={{ textAlign: "center", padding: "64px 0", fontFamily: FONT.body, color: COLORS.inkMuted }}>
          {entries.length === 0 ? "No journal entries yet. Write about a trade worth remembering." : "No entries match that search."}
        </div>
      ) : (
        <div style={{ display: "flex", flexDirection: "column", gap: 8 }}>
          {filtered.map((e) => (
            <button
              key={e.id}
              onClick={() => onOpenDate(e.date)}
              style={{ textAlign: "left", borderRadius: 12, padding: 12, background: COLORS.card, border: `1px solid ${COLORS.line}` }}
            >
              <div style={{ display: "flex", alignItems: "center", justifyContent: "space-between" }}>
                <span style={{ fontFamily: FONT.mono, fontSize: 11, color: COLORS.brass }}>{fmtDateHuman(e.date)}</span>
                {e.mood && (
                  <span style={{ fontFamily: FONT.body, fontSize: 11, color: (MOOD_COLORS[e.mood] || { fg: COLORS.inkMuted }).fg }}>{e.mood}</span>
                )}
              </div>
              <div style={{ fontFamily: FONT.display, color: COLORS.ink, fontWeight: 600, fontSize: 14, marginTop: 4 }}>{e.title || "Untitled entry"}</div>
              {e.body && (
                <div style={{ fontFamily: FONT.body, color: COLORS.inkMuted, fontSize: 12, marginTop: 2, display: "-webkit-box", WebkitLineClamp: 2, WebkitBoxOrient: "vertical", overflow: "hidden" }}>
                  {e.body}
                </div>
              )}
            </button>
          ))}
        </div>
      )}
    </div>
  );
}

// ---------- mount ----------
const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(<Root />);
