const { useState, useEffect, useMemo, useRef } = React;
const {
  PieChart, Pie, Cell, ResponsiveContainer, Tooltip, Legend,
  ComposedChart, Bar, Line, XAxis, YAxis, CartesianGrid,
} = Recharts;

const THEME = {
  bg: "#F0F4F2", card: "#FFFFFF", border: "#D2DCD7", ink: "#1B2A2D", inkSoft: "#5E6D6A",
  teal: "#0F7A76", tealDark: "#0A5B5A", tealSoft: "#E1F3F1",
  gold: "#C9952F", goldSoft: "#F8F0D9",
  green: "#3A7D63", greenSoft: "#E6F3EB",
  brick: "#C65B4D", brickSoft: "#F9E6E2",
};

// پالت‌های هماهنگ با تم برنامه — تمایز خوب و رنگ‌های ملایم
const PIE_COLORS_EXPENSES = ["#C65B4D","#E8A87C","#D4A574","#85C1A3","#5B8FA8","#8B7EC8","#E07A5F","#81B29A","#F2CC8F","#3D405B"];
const PIE_COLORS_INVESTMENTS = ["#0F7A76","#C9952F","#3A7D63","#5D8AA8","#8A5A9E","#D7A040","#2E86AB","#6B9B7A","#B98A3E","#4C7A88"];
const MONTHS = ["فروردین", "اردیبهشت", "خرداد", "تیر", "مرداد", "شهریور", "مهر", "آبان", "آذر", "دی", "بهمن", "اسفند"];
const YEARS = (() => {
  let currentY = 1404;
  try {
    const parts = new Intl.DateTimeFormat("en-US-u-ca-persian", { year: "numeric" }).formatToParts(new Date());
    const y = parseInt(parts.find((p) => p.type === "year").value, 10);
    if (Number.isFinite(y)) currentY = y;
  } catch (e) {}
  const arr = [];
  for (let y = currentY - 2; y <= currentY + 6; y++) arr.push(y);
  return arr;
})();
const STORAGE_KEY = "finance-manager-data-v1";
const LOANS_KEY = STORAGE_KEY + "-loans";
const DATA_FILE_NAME = "finance-data.json";
const DEFAULT_TOMAN_RATE = 189000; // fallback تقریبی بازار آزاد؛ با بروزرسانی قیمت جایگزین می‌شود

const fmt = (n) => {
  const safe = Number.isFinite(Number(n)) ? Number(n) : 0;
  return new Intl.NumberFormat("fa-IR").format(Math.round(safe));
};
const fmtNumber = (n, maximumFractionDigits = 0) => {
  const safe = Number.isFinite(Number(n)) ? Number(n) : 0;
  return new Intl.NumberFormat("fa-IR", { maximumFractionDigits }).format(safe);
};
const fmtPercent = (p) => {
  const safe = Number.isFinite(Number(p)) ? Number(p) : 0;
  return new Intl.NumberFormat("fa-IR", { maximumFractionDigits: 1 }).format(safe);
};
const fmtMillion = (n) => {
  const value = Number(n || 0) / 1_000_000;
  if (value >= 1) return `${Math.round(value)} میلیون\u00A0تومان`;
  return `${value.toFixed(1).replace(/\.0$/, "")} میلیون\u00A0تومان`;
};
const fmtUsdt = (n, digits = 2) => {
  const safe = Number.isFinite(Number(n)) ? Number(n) : 0;
  return new Intl.NumberFormat("fa-IR", { maximumFractionDigits: digits, minimumFractionDigits: 0 }).format(safe);
};
const monthKey = (y, m) => `${y}-${m}`;
const uid = () => Date.now().toString(36) + Math.random().toString(36).slice(2, 7);
const sanitizeNumber = (value, fallback = 0, max = 10_000_000_000) => {
  const num = Number(value);
  if (!Number.isFinite(num) || num < 0 || num > max) return fallback;
  return num;
};
const normalizeAssetLabel = (label) => String(label ?? "").trim().replace(/\s+/g, " ");
const assetKey = (label) => normalizeAssetLabel(label).toLowerCase().replace(/\u200c/g, "");

function getCurrentPersianYM() {
  try {
    const parts = new Intl.DateTimeFormat("en-US-u-ca-persian", { year: "numeric", month: "numeric" }).formatToParts(new Date());
    const y = parseInt(parts.find((p) => p.type === "year").value, 10);
    const m = parseInt(parts.find((p) => p.type === "month").value, 10) - 1;
    if (Number.isFinite(y) && Number.isFinite(m)) return { y, m };
  } catch (e) {}
  return { y: 1404, m: 0 };
}

function getCurrentPersianDay() {
  try {
    const parts = new Intl.DateTimeFormat("en-US-u-ca-persian", { day: "numeric" }).formatToParts(new Date());
    const d = parseInt(parts.find((p) => p.type === "day").value, 10);
    if (Number.isFinite(d) && d >= 1) return d;
  } catch (e) {}
  return 1;
}

function persianMonthIndex(y, m) {
  return y * 12 + m;
}

/** آیا این ماه در بازهٔ اقساط وام است؟ */
function loanCoversMonth(loan, y, m) {
  const startY = Number(loan.startYear);
  const startM = Number(loan.startMonth);
  const months = Math.max(1, Number(loan.totalMonths) || 1);
  if (!Number.isFinite(startY) || !Number.isFinite(startM)) return false;
  const startIdx = persianMonthIndex(startY, startM);
  const curIdx = persianMonthIndex(y, m);
  return curIdx >= startIdx && curIdx < startIdx + months;
}

function loanPaidForMonth(loan, mk) {
  const payments = Array.isArray(loan.payments) ? loan.payments : [];
  return payments.some((p) => p && p.monthKey === mk);
}

/** روزهای مانده تا سررسید در ماه جاری (منفی = عقب‌افتاده) */
function daysUntilDueInMonth(dueDay, currentDay) {
  const due = Math.min(31, Math.max(1, Number(dueDay) || 1));
  const today = Math.max(1, Number(currentDay) || 1);
  return due - today;
}

function normalizeLoan(l) {
  if (!l || typeof l !== "object") return null;
  const cur = getCurrentPersianYM();
  const monthly = Number(l.monthlyPayment) || 0;
  const remaining = Number(l.remainingAmount);
  const totalMonths = Number(l.totalMonths) > 0
    ? Number(l.totalMonths)
    : (monthly > 0 && Number.isFinite(remaining) ? Math.max(1, Math.ceil(remaining / monthly)) : 0);
  return {
    ...l,
    title: String(l.title || "وام"),
    monthlyPayment: monthly,
    totalMonths,
    dueDay: Math.min(31, Math.max(1, Number(l.dueDay) || 0)),
    startYear: Number.isFinite(Number(l.startYear)) ? Number(l.startYear) : cur.y,
    startMonth: Number.isFinite(Number(l.startMonth)) ? Number(l.startMonth) : cur.m,
    totalAmount: Number(l.totalAmount) || monthly * totalMonths,
    remainingAmount: Number.isFinite(remaining) ? remaining : monthly * totalMonths,
    paidCount: Number(l.paidCount) || 0,
    payments: Array.isArray(l.payments) ? l.payments : [],
  };
}

function loadLoansFromStorage() {
  try {
    const raw = localStorage.getItem(LOANS_KEY);
    if (raw) {
      const parsed = JSON.parse(raw);
      if (Array.isArray(parsed)) return parsed.map(normalizeLoan).filter(Boolean);
    }
  } catch (e) {}
  return [];
}

function saveLoansToStorage(loans) {
  try {
    localStorage.setItem(LOANS_KEY, JSON.stringify(loans));
    return true;
  } catch (e) {
    return false;
  }
}

function loadDataFromStorage() {
  try {
    const raw = localStorage.getItem(STORAGE_KEY);
    if (raw) {
      const parsed = JSON.parse(raw);
      if (parsed && typeof parsed === "object") return parsed;
    }
  } catch (e) {}
  return {};
}

async function loadDataFromSidecar() {
  // روی file:// مرورگر اجازهٔ fetch فایل محلی نمی‌دهد — بی‌صدا رد شو
  if (typeof location !== "undefined" && location.protocol === "file:") return {};
  try {
    const response = await fetch(`./${DATA_FILE_NAME}`, { cache: "no-store" });
    if (!response.ok) return {};
    const payload = await response.json();
    if (payload && typeof payload === "object") {
      if (payload.budgetData && typeof payload.budgetData === "object") return payload.budgetData;
      if (payload.data && typeof payload.data === "object") return payload.data;
      return payload;
    }
  } catch (e) {}
  return {};
}

async function saveData(data, livePrices = {}, writeSidecar = false, tomanRate = DEFAULT_TOMAN_RATE) {
  const safeRate = sanitizeNumber(tomanRate, DEFAULT_TOMAN_RATE, 1_000_000);
  try {
    localStorage.setItem(STORAGE_KEY, JSON.stringify(data));
    localStorage.setItem(STORAGE_KEY + "-live-prices", JSON.stringify(livePrices));
    localStorage.setItem(STORAGE_KEY + "-toman-rate", String(safeRate));
  } catch (e) { return false; }

  if (!writeSidecar) return true;

  try {
    const payload = JSON.stringify({ budgetData: data, livePrices, tomanRate: safeRate }, null, 2);
    if (window.showSaveFilePicker) {
      const handle = await window.showSaveFilePicker({
        suggestedName: DATA_FILE_NAME,
        startIn: "downloads",
        types: [{ description: "JSON data file", accept: { "application/json": [".json"] } }],
      });
      const writable = await handle.createWritable();
      await writable.write(payload);
      await writable.close();
      return true;
    }
    const blob = new Blob([payload], { type: "application/json" });
    const url = URL.createObjectURL(blob);
    const a = document.createElement("a");
    a.href = url;
    a.download = DATA_FILE_NAME;
    document.body.appendChild(a);
    a.click();
    a.remove();
    setTimeout(() => URL.revokeObjectURL(url), 1000);
    return true;
  } catch (e) {
    return true;
  }
}

function Icon({ name, size = 18, color = "currentColor" }) {
  const common = { width: size, height: size, viewBox: "0 0 24 24", fill: "none", stroke: color, strokeWidth: 2, strokeLinecap: "round", strokeLinejoin: "round" };
  const paths = {
    wallet: <path d="M20 7H4a2 2 0 0 0-2 2v9a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2V9a2 2 0 0 0-2-2Z M2 9V5a2 2 0 0 1 2-2h13 M16 14h.01" />,
    plus: <path d="M12 5v14M5 12h14" />,
    bell: <path d="M18 8A6 6 0 0 0 6 8c0 7-3 9-3 9h18s-3-2-3-9M13.73 21a2 2 0 0 1-3.46 0" />,
    trash: <path d="M3 6h18M8 6V4a1 1 0 0 1 1-1h6a1 1 0 0 1 1 1v2m2 0-1 14a1 1 0 0 1-1 1H7a1 1 0 0 1-1-1L5 6" />,
    download: <path d="M12 3v12m0 0-4-4m4 4 4-4M4 19h16" />,
    up: <path d="M23 6 13.5 15.5 8.5 10.5 1 18M17 6h6v6" />,
    down: <path d="M23 18 13.5 8.5 8.5 13.5 1 6M17 18h6v-6" />,
    piggy: <path d="M11 5c-4 0-7 3-7 6 0 1.5.6 2.7 1.5 3.6L5 18h3l.5-1.5c.8.2 1.6.3 2.5.3s1.7-.1 2.5-.3L14 18h3l-.5-3.4c.9-.9 1.5-2.1 1.5-3.6 0-.6-.1-1.1-.3-1.6L20 8l-2.5.3C16.3 6.5 13.8 5 11 5Zm-2.5 4h.01" />,
    reset: <path d="M3 12a9 9 0 1 0 3-6.7L3 8M3 3v5h5" />,
    calendar: <><rect x="3" y="4" width="18" height="18" rx="2" /><path d="M16 2v4M8 2v4M3 10h18" /></>,
    debt: <><path d="M12 2v4M12 18v4M4.93 4.93l2.83 2.83M16.24 16.24l2.83 2.83M2 12h4M18 12h4M4.93 19.07l2.83-2.83M16.24 7.76l2.83-2.83" /><circle cx="12" cy="12" r="4" /></>,
    chart: <path d="M3 3v18h18M7 14l4-4 3 3 5-6" />,
  };
  return <svg {...common} aria-hidden="true" focusable="false">{paths[name]}</svg>;
}

function SummaryCard({ icon, label, value, color, bg }) {
  return (
    <div style={{
      background: THEME.card,
      border: `1px solid ${THEME.border}`,
      borderRadius: 14,
      padding: "14px 16px",
      display: "flex",
      alignItems: "center",
      gap: 12,
      minWidth: 0,
      boxShadow: "0 1px 2px rgba(27,42,45,0.04)",
    }}>
      <div style={{ width: 42, height: 42, borderRadius: 12, background: bg, display: "flex", alignItems: "center", justifyContent: "center", flexShrink: 0 }}>
        <Icon name={icon} size={20} color={color} />
      </div>
      <div style={{ minWidth: 0, flex: 1 }}>
        <div style={{ fontSize: 12.5, color: THEME.inkSoft, marginBottom: 3, letterSpacing: "0.01em" }}>{label}</div>
        <div style={{ fontSize: 18, fontWeight: 700, color: THEME.ink, whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis", fontVariantNumeric: "tabular-nums" }}>
          {fmt(value)} <span style={{ fontSize: 11.5, fontWeight: 500, color: THEME.inkSoft }}>تومان</span>
        </div>
      </div>
    </div>
  );
}


function LoanForm({ onAdd, defaultYear, defaultMonth }) {
  const [title, setTitle] = useState("");
  const [monthlyPayment, setMonthlyPayment] = useState("");
  const [totalMonths, setTotalMonths] = useState("");
  const [dueDay, setDueDay] = useState("");
  const [startMonth, setStartMonth] = useState(String(defaultMonth ?? 0));
  const [startYear, setStartYear] = useState(String(defaultYear ?? 1404));

  const submit = () => {
    const monthly = parseFloat(monthlyPayment);
    const months = parseInt(totalMonths, 10);
    const day = parseInt(dueDay, 10);
    const sy = parseInt(startYear, 10);
    const sm = parseInt(startMonth, 10);
    if (!title.trim()) return;
    if (!Number.isFinite(monthly) || monthly <= 0) return;
    if (!Number.isFinite(months) || months < 1 || months > 360) return;
    if (!Number.isFinite(day) || day < 1 || day > 31) return;
    if (!Number.isFinite(sy) || !Number.isFinite(sm) || sm < 0 || sm > 11) return;
    onAdd({
      id: uid(),
      title: title.trim(),
      monthlyPayment: monthly,
      totalMonths: months,
      dueDay: day,
      startYear: sy,
      startMonth: sm,
      totalAmount: monthly * months,
      remainingAmount: monthly * months,
      paidCount: 0,
      payments: [],
      createdAt: Date.now(),
    });
    setTitle("");
    setMonthlyPayment("");
    setTotalMonths("");
    setDueDay("");
  };

  const fieldStyle = { flex: "1 1 100px", padding: "10px 12px", borderRadius: 8, border: `1px solid ${THEME.border}`, fontSize: 14, background: "#FBFBFA", color: THEME.ink, textAlign: "center" };

  return (
    <div style={{ display: "flex", flexDirection: "column", gap: 8, marginBottom: 12 }}>
      <div style={{ display: "flex", flexWrap: "wrap", gap: 8, alignItems: "stretch" }}>
        <button type="button" onClick={submit} aria-label="افزودن وام"
          style={{ width: 44, height: 44, flexShrink: 0, borderRadius: 10, border: "none", background: THEME.brick, color: "#fff", display: "flex", alignItems: "center", justifyContent: "center", cursor: "pointer" }}>
          <Icon name="plus" size={18} />
        </button>
        <input value={title} onChange={(e) => setTitle(e.target.value)} onKeyDown={(e) => e.key === "Enter" && submit()}
          placeholder="نام وام (مثلاً بلو بانک)" aria-label="عنوان وام"
          style={{ flex: "2 1 160px", padding: "10px 12px", borderRadius: 8, border: `1px solid ${THEME.border}`, fontSize: 14, background: "#FBFBFA", color: THEME.ink }} />
        <input inputMode="numeric" type="number" min="1" value={monthlyPayment} onChange={(e) => setMonthlyPayment(e.target.value)}
          onKeyDown={(e) => e.key === "Enter" && submit()} placeholder="مبلغ هر قسط" aria-label="قسط ماهانه" style={fieldStyle} />
        <input inputMode="numeric" type="number" min="1" max="360" value={totalMonths} onChange={(e) => setTotalMonths(e.target.value)}
          onKeyDown={(e) => e.key === "Enter" && submit()} placeholder="تعداد ماه" aria-label="تعداد ماه" style={fieldStyle} />
        <input inputMode="numeric" type="number" min="1" max="31" value={dueDay} onChange={(e) => setDueDay(e.target.value)}
          onKeyDown={(e) => e.key === "Enter" && submit()} placeholder="روز سررسید" aria-label="روز سررسید در ماه" style={fieldStyle} />
      </div>
      <div style={{ display: "flex", flexWrap: "wrap", gap: 8, alignItems: "center", fontSize: 12.5, color: THEME.inkSoft }}>
        <span>شروع اقساط از:</span>
        <select value={startMonth} onChange={(e) => setStartMonth(e.target.value)} aria-label="ماه شروع"
          style={{ padding: "8px 10px", borderRadius: 8, border: `1px solid ${THEME.border}`, background: "#fff", color: THEME.ink }}>
          {MONTHS.map((m, i) => <option key={m} value={i}>{m}</option>)}
        </select>
        <select value={startYear} onChange={(e) => setStartYear(e.target.value)} aria-label="سال شروع"
          style={{ padding: "8px 10px", borderRadius: 8, border: `1px solid ${THEME.border}`, background: "#fff", color: THEME.ink }}>
          {YEARS.map((y) => <option key={y} value={y}>{y}</option>)}
        </select>
        <span style={{ marginRight: 4 }}>· روز سررسید هر ماه (۱ تا ۳۱)</span>
      </div>
    </div>
  );
}

function EntryForm({ kind, suggestions, onAdd }) {
  const [label, setLabel] = useState("");
  const [amount, setAmount] = useState("");
  const [purchasePrice, setPurchasePrice] = useState("");
  const listId = kind === "income" ? "income-suggest" : (kind === "expense" ? "expense-suggest" : "investment-suggest");

  const submit = () => {
    const amt = parseFloat(amount);
    const pp = parseFloat(purchasePrice);
    if (!label.trim() || !Number.isFinite(amt) || amt <= 0) return;
    if (kind === "investment") {
      // قیمت خرید الزامی و غیرمنفی
      if (!Number.isFinite(pp) || pp < 0) return;
      onAdd({
        id: uid(),
        label: label.trim(),
        amount: amt,
        units: amt,
        purchasePriceUsdt: pp,
      });
    } else {
      onAdd({ id: uid(), label: label.trim(), amount: amt });
    }
    setLabel(""); setAmount(""); setPurchasePrice("");
  };

  return (
    <div style={{ display: "flex", gap: 8, marginBottom: 12, alignItems: "stretch", flexWrap: "wrap", width: "100%" }}>
      <button type="button" onClick={submit} aria-label="افزودن"
        style={{ width: 44, height: 44, flexShrink: 0, borderRadius: 10, border: "none", background: kind === "income" ? THEME.teal : kind === "expense" ? THEME.brick : THEME.gold, color: "#fff", display: "flex", alignItems: "center", justifyContent: "center", cursor: "pointer", order: -1 }}>
        <Icon name="plus" size={18} color="#fff" />
      </button>

      <input list={listId} value={label} onChange={(e) => setLabel(e.target.value)}
        aria-label={kind === "income" ? "منبع درآمد" : kind === "expense" ? "دسته هزینه" : "نوع سرمایه"}
        placeholder={kind === "income" ? "منبع درآمد" : kind === "expense" ? "دسته هزینه" : "نوع سرمایه"}
        style={{ flex: "2 1 160px", padding: "10px 12px", borderRadius: 8, border: `1px solid ${THEME.border}`, fontSize: 14, background: "#FBFBFA", color: THEME.ink }} />
      <datalist id={listId}>{suggestions.map((s) => <option key={s} value={s} />)}</datalist>

      {kind === "investment" ? (
        <>
          <input inputMode="decimal" type="number" min="0" step="any" value={amount} onChange={(e) => setAmount(e.target.value)}
            onKeyDown={(e) => e.key === "Enter" && submit()}
            aria-label="تعداد واحد"
            placeholder="تعداد واحد"
            style={{ flex: "1 1 110px", padding: "10px 12px", borderRadius: 8, border: `1px solid ${THEME.border}`, fontSize: 14, background: "#FBFBFA", color: THEME.ink, textAlign: "center" }} />
          <input inputMode="decimal" type="number" min="0" step="any" value={purchasePrice} onChange={(e) => setPurchasePrice(e.target.value)}
            aria-label="قیمت خرید هر واحد (USDT)" placeholder={"قیمت خرید \u200E(USDT)"}
            style={{ flex: "1 1 140px", padding: "10px 12px", borderRadius: 8, border: `1px solid ${THEME.border}`, fontSize: 14, background: "#FBFBFA", color: THEME.ink, textAlign: "center" }} />
        </>
      ) : (
        <input inputMode="numeric" type="number" min="0" value={amount} onChange={(e) => setAmount(e.target.value)}
          onKeyDown={(e) => e.key === "Enter" && submit()}
          aria-label="مبلغ"
          placeholder="مبلغ"
          style={{ flex: "1 1 140px", padding: "10px 12px", borderRadius: 8, border: `1px solid ${THEME.border}`, fontSize: 14, background: "#FBFBFA", color: THEME.ink }} />
      )}
    </div>
  );
}

function EntryList({ items, onDelete, accent, extra, renderAmount }) {
  if (!items.length) return <div style={{ fontSize: 13, color: THEME.inkSoft, padding: "10px 2px" }}>هنوز چیزی ثبت نشده.</div>;
  return (
    <div style={{ display: "flex", flexDirection: "column", gap: 6 }}>
      {items.map((it) => (
        <div key={it.id} style={{ display: "flex", alignItems: "center", justifyContent: "space-between", padding: "8px 10px", borderRadius: 8, background: "#FBFBFA", border: `0.5px solid ${THEME.border}` }}>
          <div style={{ minWidth: 0 }}>
            <div style={{ fontSize: 13.5, color: THEME.ink }}>{it.label}</div>
            {extra ? <div style={{ fontSize: 12, color: THEME.inkSoft, marginTop: 4 }}>{extra(it)}</div> : null}
          </div>
          <div style={{ display: "flex", alignItems: "center", gap: 10 }}>
            <span style={{ fontSize: 13.5, fontWeight: 600, color: accent }}>{renderAmount ? renderAmount(it) : `${fmt(it.amount)} تومان`}</span>
            <button type="button" onClick={() => onDelete(it.id)} aria-label="حذف" style={{ border: "none", background: "transparent", cursor: "pointer", color: THEME.inkSoft, display: "flex" }}>
              <Icon name="trash" size={15} />
            </button>
          </div>
        </div>
      ))}
    </div>
  );
}

function sortedMonthKeys(data) {
  return Object.keys(data)
    .filter((k) => {
      const entry = data[k] || {};
      return (entry.income || []).length || (entry.expenses || []).length || (entry.investments || []).length;
    })
    .sort((a, b) => {
      const [ay, am] = a.split("-").map(Number);
      const [by, bm] = b.split("-").map(Number);
      return ay * 12 + am - (by * 12 + bm);
    });
}

/** رنگ‌های تم برای اکسل */
const XL = {
  teal: "FF0F7A76",
  tealDark: "FF0A5B5A",
  tealSoft: "FFE1F3F1",
  gold: "FFC9952F",
  goldSoft: "FFF8F0D9",
  green: "FF3A7D63",
  greenSoft: "FFE6F3EB",
  brick: "FFC65B4D",
  brickSoft: "FFF9E6E2",
  ink: "FF1B2A2D",
  inkSoft: "FF5E6D6A",
  border: "FFD2DCD7",
  white: "FFFFFFFF",
  bg: "FFF0F4F2",
  card: "FFFFFFFF",
};

function xlFill(argb) {
  return { type: "pattern", pattern: "solid", fgColor: { argb } };
}
function xlFont(opts) {
  return { name: "Tahoma", size: 11, color: { argb: XL.ink }, ...opts };
}
function xlBorder() {
  const s = { style: "thin", color: { argb: XL.border } };
  return { top: s, bottom: s, left: s, right: s };
}
function xlNumFmt() {
  return "#,##0";
}

function styleHeaderRow(row, bgArgb) {
  row.eachCell((cell) => {
    cell.fill = xlFill(bgArgb);
    cell.font = xlFont({ bold: true, color: { argb: XL.white }, size: 12 });
    cell.alignment = { vertical: "middle", horizontal: "center", wrapText: true };
    cell.border = xlBorder();
  });
  row.height = 28;
}

function styleTitleRow(row) {
  row.eachCell((cell) => {
    cell.font = xlFont({ bold: true, size: 16, color: { argb: XL.tealDark } });
    cell.alignment = { vertical: "middle", horizontal: "right" };
  });
  row.height = 32;
}

function styleDataRow(row, alt) {
  row.eachCell((cell) => {
    cell.font = xlFont({ size: 11 });
    cell.border = xlBorder();
    cell.alignment = { vertical: "middle", horizontal: "right" };
    if (alt) cell.fill = xlFill("FFF7FAF8");
  });
  row.height = 22;
}

function styleTotalRow(row, bgArgb, fontArgb) {
  row.eachCell((cell) => {
    cell.fill = xlFill(bgArgb);
    cell.font = xlFont({ bold: true, size: 11, color: { argb: fontArgb || XL.ink } });
    cell.border = xlBorder();
    cell.alignment = { vertical: "middle", horizontal: "right" };
  });
  row.height = 24;
}

function applyColWidths(ws, widths) {
  widths.forEach((w, i) => {
    ws.getColumn(i + 1).width = w;
  });
}

/**
 * خروجی اکسل حرفه‌ای فقط برای ماه انتخاب‌شده — چند شیت مرتب با ظاهر نزدیک به UI
 * ctx: { year, monthIdx, monthData, loans, loansSummary, portfolioAssets, portfolioSummary,
 *        totalIncome, totalExpense, balance, percentSpent, tomanRate, categoryBreakdown }
 */
async function buildMonthlyWorkbook(ctx) {
  const {
    year, monthIdx, monthData, loans, loansSummary,
    portfolioAssets, portfolioSummary,
    totalIncome, totalExpense, balance, percentSpent, tomanRate, categoryBreakdown,
  } = ctx;
  const monthName = MONTHS[monthIdx] || "";
  const title = `دخل و خرج — ${monthName} ${year}`;
  const income = monthData.income || [];
  const expenses = monthData.expenses || [];
  const investments = monthData.investments || [];
  const wb = new ExcelJS.Workbook();
  wb.creator = "دخل و خرج";
  wb.created = new Date();
  wb.modified = new Date();

  // ─── شیت ۱: خلاصه ماه ───
  const wsSum = wb.addWorksheet("خلاصه ماه", {
    views: [{ rightToLeft: true }],
    properties: { defaultRowHeight: 20 },
  });
  applyColWidths(wsSum, [28, 22, 18, 18, 18, 18]);

  wsSum.mergeCells("A1:F1");
  wsSum.getCell("A1").value = title;
  styleTitleRow(wsSum.getRow(1));

  wsSum.mergeCells("A2:F2");
  wsSum.getCell("A2").value = `گزارش ماهانه · تاریخ صدور: ${new Date().toLocaleDateString("fa-IR")}`;
  wsSum.getCell("A2").font = xlFont({ size: 10, color: { argb: XL.inkSoft } });
  wsSum.getRow(2).height = 18;

  // کارت‌های خلاصه
  wsSum.getRow(4).values = ["شاخص", "مقدار (تومان)", "توضیح", "", "", ""];
  styleHeaderRow(wsSum.getRow(4), XL.teal);

  const summaryRows = [
    ["جمع درآمد", totalIncome, "مجموع منابع درآمد این ماه"],
    ["جمع هزینه", totalExpense, "مجموع هزینه‌های این ماه"],
    ["مانده", balance, balance >= 0 ? "مثبت — پس‌انداز" : "منفی — کسری"],
    ["اقساط این ماه", loansSummary?.dueThisMonthTotal || 0, `${(loansSummary?.dueThisMonth || []).length} قسط باز`],
    ["مانده کل وام‌ها", loansSummary?.totalRemaining || 0, `${loansSummary?.activeCount || 0} وام فعال`],
    ["ارزش پرتفوی", Math.round(portfolioSummary?.totalCurrentToman || 0), `≈ ${fmtUsdt(portfolioSummary?.totalCurrentUsdt || 0)} USDT`],
    ["سود/زیان پرتفوی", Math.round(portfolioSummary?.diffToman || 0), `${(portfolioSummary?.pct || 0) >= 0 ? "+" : ""}${fmtPercent(portfolioSummary?.pct || 0)}٪`],
    ["درصد درآمد خرج‌شده", null, `${fmtPercent(percentSpent)}٪ از درآمد`],
  ];
  summaryRows.forEach((r, i) => {
    const row = wsSum.getRow(5 + i);
    row.values = [r[0], r[1], r[2]];
    styleDataRow(row, i % 2 === 1);
    if (r[1] != null) {
      row.getCell(2).numFmt = xlNumFmt();
      row.getCell(2).font = xlFont({ bold: true, size: 12 });
    }
    // رنگ‌بندی شاخص‌ها
    if (i === 0) row.getCell(1).font = xlFont({ bold: true, color: { argb: XL.green } });
    if (i === 1) row.getCell(1).font = xlFont({ bold: true, color: { argb: XL.brick } });
    if (i === 2) {
      row.getCell(1).font = xlFont({ bold: true, color: { argb: XL.teal } });
      row.getCell(2).font = xlFont({ bold: true, color: { argb: balance >= 0 ? XL.green : XL.brick } });
    }
    if (i === 5) row.getCell(1).font = xlFont({ bold: true, color: { argb: XL.gold } });
  });
  // درصد خرج‌شده بدون عدد خام در ستون ۲
  wsSum.getCell("B12").value = percentSpent / 100;
  wsSum.getCell("B12").numFmt = "0.0%";

  // تفکیک هزینه
  let expStart = 14;
  wsSum.mergeCells(`A${expStart}:C${expStart}`);
  wsSum.getCell(`A${expStart}`).value = "تفکیک هزینه‌ها به تفکیک دسته";
  wsSum.getCell(`A${expStart}`).font = xlFont({ bold: true, size: 13, color: { argb: XL.brick } });
  expStart++;
  wsSum.getRow(expStart).values = ["دسته", "مبلغ (تومان)", "سهم از کل"];
  styleHeaderRow(wsSum.getRow(expStart), XL.brick);
  const cats = categoryBreakdown || [];
  cats.forEach((c, i) => {
    const row = wsSum.getRow(expStart + 1 + i);
    const pct = totalExpense > 0 ? c.value / totalExpense : 0;
    row.values = [c.name, c.value, pct];
    styleDataRow(row, i % 2 === 1);
    row.getCell(2).numFmt = xlNumFmt();
    row.getCell(3).numFmt = "0.0%";
  });
  if (cats.length) {
    const tRow = wsSum.getRow(expStart + 1 + cats.length);
    tRow.values = ["جمع", totalExpense, totalExpense > 0 ? 1 : 0];
    styleTotalRow(tRow, XL.brickSoft, XL.brick);
    tRow.getCell(2).numFmt = xlNumFmt();
    tRow.getCell(3).numFmt = "0.0%";
  } else {
    wsSum.getCell(`A${expStart + 1}`).value = "هزینه‌ای ثبت نشده.";
    wsSum.getCell(`A${expStart + 1}`).font = xlFont({ size: 10, color: { argb: XL.inkSoft } });
  }

  // نرخ تتر
  const rateRow = expStart + Math.max(cats.length, 1) + 3;
  wsSum.getCell(`A${rateRow}`).value = "نرخ تبدیل";
  wsSum.getCell(`B${rateRow}`).value = `۱ USDT = ${fmt(tomanRate)} تومان`;
  wsSum.getCell(`A${rateRow}`).font = xlFont({ bold: true, size: 10 });
  wsSum.getCell(`B${rateRow}`).font = xlFont({ size: 10, color: { argb: XL.gold } });

  // ─── شیت ۲: درآمد ───
  const wsInc = wb.addWorksheet("درآمد", { views: [{ rightToLeft: true }] });
  applyColWidths(wsInc, [8, 36, 20, 24]);
  wsInc.mergeCells("A1:D1");
  wsInc.getCell("A1").value = `درآمد — ${monthName} ${year}`;
  styleTitleRow(wsInc.getRow(1));
  wsInc.getRow(3).values = ["#", "منبع درآمد", "مبلغ (تومان)", "یادداشت"];
  styleHeaderRow(wsInc.getRow(3), XL.green);
  income.forEach((it, i) => {
    const row = wsInc.getRow(4 + i);
    row.values = [i + 1, it.label, Number(it.amount) || 0, ""];
    styleDataRow(row, i % 2 === 1);
    row.getCell(3).numFmt = xlNumFmt();
    row.getCell(1).alignment = { horizontal: "center", vertical: "middle" };
  });
  const incTotalRow = wsInc.getRow(4 + income.length);
  incTotalRow.values = ["", "جمع درآمد", totalIncome, ""];
  styleTotalRow(incTotalRow, XL.greenSoft, XL.green);
  incTotalRow.getCell(3).numFmt = xlNumFmt();
  if (!income.length) {
    wsInc.getCell("A4").value = "هنوز درآمدی ثبت نشده.";
    wsInc.getCell("A4").font = xlFont({ size: 10, color: { argb: XL.inkSoft } });
  }

  // ─── شیت ۳: هزینه ───
  const wsExp = wb.addWorksheet("هزینه", { views: [{ rightToLeft: true }] });
  applyColWidths(wsExp, [8, 36, 20, 14, 24]);
  wsExp.mergeCells("A1:E1");
  wsExp.getCell("A1").value = `هزینه‌ها — ${monthName} ${year}`;
  styleTitleRow(wsExp.getRow(1));
  wsExp.getRow(3).values = ["#", "دسته هزینه", "مبلغ (تومان)", "سهم", "یادداشت"];
  styleHeaderRow(wsExp.getRow(3), XL.brick);
  expenses.forEach((it, i) => {
    const row = wsExp.getRow(4 + i);
    const pct = totalExpense > 0 ? (Number(it.amount) || 0) / totalExpense : 0;
    row.values = [i + 1, it.label, Number(it.amount) || 0, pct, ""];
    styleDataRow(row, i % 2 === 1);
    row.getCell(3).numFmt = xlNumFmt();
    row.getCell(4).numFmt = "0.0%";
    row.getCell(1).alignment = { horizontal: "center", vertical: "middle" };
  });
  const expTotalRow = wsExp.getRow(4 + expenses.length);
  expTotalRow.values = ["", "جمع هزینه", totalExpense, totalExpense > 0 ? 1 : 0, ""];
  styleTotalRow(expTotalRow, XL.brickSoft, XL.brick);
  expTotalRow.getCell(3).numFmt = xlNumFmt();
  expTotalRow.getCell(4).numFmt = "0.0%";
  if (!expenses.length) {
    wsExp.getCell("A4").value = "هنوز هزینه‌ای ثبت نشده.";
    wsExp.getCell("A4").font = xlFont({ size: 10, color: { argb: XL.inkSoft } });
  }

  // ─── شیت ۴: سرمایه‌گذاری ───
  const wsInv = wb.addWorksheet("سرمایه‌گذاری", { views: [{ rightToLeft: true }] });
  applyColWidths(wsInv, [22, 14, 16, 16, 16, 16, 14]);
  wsInv.mergeCells("A1:G1");
  wsInv.getCell("A1").value = `سرمایه‌گذاری — ${monthName} ${year}`;
  styleTitleRow(wsInv.getRow(1));
  wsInv.mergeCells("A2:G2");
  wsInv.getCell("A2").value = `نرخ: ۱ USDT = ${fmt(tomanRate)} تومان · سود/زیان نسبت به قیمت خرید`;
  wsInv.getCell("A2").font = xlFont({ size: 10, color: { argb: XL.inkSoft } });

  wsInv.getCell("A4").value = "ثبت‌های این ماه";
  wsInv.getCell("A4").font = xlFont({ bold: true, size: 12, color: { argb: XL.gold } });
  wsInv.getRow(5).values = ["دارایی", "واحد", "قیمت خرید (USDT)", "ارزش خرید (USDT)", "ارزش فعلی (USDT)", "سود/زیان", "٪"];
  styleHeaderRow(wsInv.getRow(5), XL.gold);

  investments.forEach((it, i) => {
    const lots = Array.isArray(it.lots) && it.lots.length
      ? it.lots
      : [{ units: Number(it.units ?? it.amount ?? 0), purchasePriceUsdt: Number(it.purchasePriceUsdt ?? it.purchasePrice ?? 0) }];
    const units = lots.reduce((s, l) => s + (Number(l.units) || 0), 0);
    const invested = lots.reduce((s, l) => s + (Number(l.units) || 0) * Math.max(0, Number(l.purchasePriceUsdt) || 0), 0);
    const avgBuy = units > 0 ? invested / units : 0;
    // قیمت فعلی از ctx.portfolioAssets اگر موجود باشد
    const pa = (portfolioAssets || []).find((a) => assetKey(a.label) === assetKey(it.label));
    const curPrice = pa && !pa.estimated ? pa.currentPriceUsdt : avgBuy;
    const curVal = units * curPrice;
    const diff = curVal - invested;
    const pct = invested > 0 ? diff / invested : 0;
    const row = wsInv.getRow(6 + i);
    row.values = [it.label, units, avgBuy, invested, curVal, diff, pct];
    styleDataRow(row, i % 2 === 1);
    [2, 3, 4, 5, 6].forEach((c) => { row.getCell(c).numFmt = "#,##0.00"; });
    row.getCell(7).numFmt = "0.0%";
    row.getCell(6).font = xlFont({ bold: true, color: { argb: diff >= 0 ? XL.green : XL.brick } });
  });
  if (!investments.length) {
    wsInv.getCell("A6").value = "سرمایه‌گذاری جدیدی در این ماه ثبت نشده.";
    wsInv.getCell("A6").font = xlFont({ size: 10, color: { argb: XL.inkSoft } });
  }

  const portStart = 6 + Math.max(investments.length, 1) + 2;
  wsInv.getCell(`A${portStart}`).value = "پرتفوی کل (همه ماه‌ها)";
  wsInv.getCell(`A${portStart}`).font = xlFont({ bold: true, size: 12, color: { argb: XL.gold } });
  wsInv.getRow(portStart + 1).values = ["دارایی", "واحد", "میانگین خرید", "قیمت فعلی", "ارزش فعلی", "سرمایه‌گذاری", "سود/زیان ٪"];
  styleHeaderRow(wsInv.getRow(portStart + 1), XL.teal);

  const assets = portfolioAssets || [];
  assets.forEach((a, i) => {
    const row = wsInv.getRow(portStart + 2 + i);
    row.values = [
      a.label + (a.estimated ? " (تخمینی)" : ""),
      a.totalUnits,
      a.totalUnits ? a.totalInvestedUsdt / a.totalUnits : 0,
      a.currentPriceUsdt,
      a.currentTotalUsdt,
      a.totalInvestedUsdt,
      a.pct / 100,
    ];
    styleDataRow(row, i % 2 === 1);
    [2, 3, 4, 5, 6].forEach((c) => { row.getCell(c).numFmt = "#,##0.00"; });
    row.getCell(7).numFmt = "0.0%";
    row.getCell(7).font = xlFont({ bold: true, color: { argb: a.diffUsdt >= 0 ? XL.green : XL.brick } });
  });
  if (assets.length) {
    const tRow = wsInv.getRow(portStart + 2 + assets.length);
    tRow.values = [
      "جمع",
      "",
      "",
      "",
      portfolioSummary?.totalCurrentUsdt || 0,
      portfolioSummary?.totalInvestedUsdt || 0,
      (portfolioSummary?.pct || 0) / 100,
    ];
    styleTotalRow(tRow, XL.goldSoft, XL.ink);
    tRow.getCell(5).numFmt = "#,##0.00";
    tRow.getCell(6).numFmt = "#,##0.00";
    tRow.getCell(7).numFmt = "0.0%";
  }

  // ─── شیت ۵: وام و اقساط ───
  const wsLoan = wb.addWorksheet("وام و اقساط", { views: [{ rightToLeft: true }] });
  applyColWidths(wsLoan, [24, 16, 12, 12, 14, 16, 18, 16]);
  wsLoan.mergeCells("A1:H1");
  wsLoan.getCell("A1").value = `وام و اقساط — ${monthName} ${year}`;
  styleTitleRow(wsLoan.getRow(1));

  wsLoan.getRow(3).values = [
    "عنوان وام", "قسط ماهانه", "تعداد ماه", "روز سررسید", "شروع از",
    "مانده", "وضعیت این ماه", "پرداخت‌شده",
  ];
  styleHeaderRow(wsLoan.getRow(3), XL.brick);

  const loanList = loans || [];
  const mk = monthKey(year, monthIdx);
  loanList.forEach((loan, i) => {
    const covers = loanCoversMonth(loan, year, monthIdx);
    const paid = loanPaidForMonth(loan, mk);
    const remaining = Number(loan.remainingAmount) || 0;
    const done = remaining <= 0;
    let status = "—";
    if (done) status = "تسویه شد";
    else if (!covers) status = "خارج از بازه";
    else if (paid) status = "پرداخت شد ✓";
    else status = "در انتظار پرداخت";
    const startLabel = `${MONTHS[Number(loan.startMonth)] || "—"} ${loan.startYear || ""}`;
    const row = wsLoan.getRow(4 + i);
    row.values = [
      loan.title,
      Number(loan.monthlyPayment) || 0,
      Number(loan.totalMonths) || 0,
      Number(loan.dueDay) || 0,
      startLabel,
      remaining,
      status,
      Number(loan.paidCount) || 0,
    ];
    styleDataRow(row, i % 2 === 1);
    row.getCell(2).numFmt = xlNumFmt();
    row.getCell(6).numFmt = xlNumFmt();
    if (status.includes("پرداخت شد")) row.getCell(7).font = xlFont({ bold: true, color: { argb: XL.green } });
    else if (status.includes("در انتظار")) row.getCell(7).font = xlFont({ bold: true, color: { argb: XL.brick } });
  });
  if (!loanList.length) {
    wsLoan.getCell("A4").value = "وامی ثبت نشده.";
    wsLoan.getCell("A4").font = xlFont({ size: 10, color: { argb: XL.inkSoft } });
  } else {
    const tRow = wsLoan.getRow(4 + loanList.length);
    tRow.values = [
      "جمع مانده وام‌ها",
      "",
      "",
      "",
      "",
      loansSummary?.totalRemaining || 0,
      `${loansSummary?.activeCount || 0} فعال`,
      "",
    ];
    styleTotalRow(tRow, XL.brickSoft, XL.brick);
    tRow.getCell(6).numFmt = xlNumFmt();
  }

  // اقساط این ماه
  const dueStart = 4 + Math.max(loanList.length, 1) + 2;
  wsLoan.getCell(`A${dueStart}`).value = "اقساط سررسید این ماه";
  wsLoan.getCell(`A${dueStart}`).font = xlFont({ bold: true, size: 12, color: { argb: XL.brick } });
  wsLoan.getRow(dueStart + 1).values = ["عنوان", "مبلغ قسط (تومان)"];
  styleHeaderRow(wsLoan.getRow(dueStart + 1), XL.gold);
  const dueList = loansSummary?.dueThisMonth || [];
  dueList.forEach((l, i) => {
    const row = wsLoan.getRow(dueStart + 2 + i);
    row.values = [l.title, Number(l.monthlyPayment) || 0];
    styleDataRow(row, i % 2 === 1);
    row.getCell(2).numFmt = xlNumFmt();
  });
  if (dueList.length) {
    const tRow = wsLoan.getRow(dueStart + 2 + dueList.length);
    tRow.values = ["جمع اقساط این ماه", loansSummary?.dueThisMonthTotal || 0];
    styleTotalRow(tRow, XL.goldSoft, XL.ink);
    tRow.getCell(2).numFmt = xlNumFmt();
  } else {
    wsLoan.getCell(`A${dueStart + 2}`).value = "قسط بازی برای این ماه نمانده.";
    wsLoan.getCell(`A${dueStart + 2}`).font = xlFont({ size: 10, color: { argb: XL.inkSoft } });
  }

  return wb;
}

async function downloadWorkbook(wb, filename) {
  const buffer = await wb.xlsx.writeBuffer();
  const blob = new Blob([buffer], {
    type: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
  });
  const url = URL.createObjectURL(blob);
  const a = document.createElement("a");
  a.href = url;
  a.download = filename;
  document.body.appendChild(a);
  a.click();
  a.remove();
  setTimeout(() => URL.revokeObjectURL(url), 1500);
}

// نگاشت برچسب دارایی → نماد پایه والکس (BTC, ETH, ...)
function mapLabelToWallexBase(label) {
  if (!label) return null;
  const l = assetKey(label);
  if (/(بیت|btc|bitcoin)/.test(l)) return "BTC";
  if (/(اتریوم|eth|ethereum)/.test(l)) return "ETH";
  if (/(تتر|usdt|tether)/.test(l) && !/(طلا|gold|xaut)/.test(l)) return "USDT";
  if (/(طلا|سکه|طلای|gold|xaut)/.test(l)) return "XAUT";
  if (/(ریپل|xrp)/.test(l)) return "XRP";
  if (/(دای|dai)/.test(l)) return "DAI";
  if (/(سولانا|sol|solana)/.test(l)) return "SOL";
  if (/(کاردانو|ada|cardano)/.test(l)) return "ADA";
  if (/(بایننس|bnb)/.test(l)) return "BNB";
  if (/(دوج|doge|dogecoin)/.test(l)) return "DOGE";
  if (/(ترون|trx|tron)/.test(l)) return "TRX";
  if (/(لینک|link|chainlink)/.test(l)) return "LINK";
  if (/(متیک|matic|polygon)/.test(l)) return "MATIC";
  if (/(شیبا|shib)/.test(l)) return "SHIB";
  if (/(یونی|uni|uniswap)/.test(l)) return "UNI";
  if (/(ایوالانچ|avax|avalanche)/.test(l)) return "AVAX";
  if (/(تون|ton|toncoin)/.test(l)) return "TON";
  return null;
}

function isLocalServer() {
  try {
    return typeof location !== "undefined"
      && (location.protocol === "http:" || location.protocol === "https:")
      && (location.hostname === "127.0.0.1" || location.hostname === "localhost");
  } catch (e) {
    return false;
  }
}

/**
 * آدرس بک‌اند قیمت (والکس).
 * - روی localhost → همان origin (server.js محلی)
 * - روی Cloudflare Pages → آدرس Cloudflare Worker (رایگان)
 *
 * بعد از Deploy کردن Worker، این مقدار را پر کن و Push کن.
 * مثال: "https://dakhl-markets.USERNAME.workers.dev"
 */
const PRODUCTION_API_BASE = "https://dakhl-markets.shamshamikeivan.workers.dev";

function getApiBase() {
  try {
    if (isLocalServer()) return "";
    return String(PRODUCTION_API_BASE || "").replace(/\/$/, "");
  } catch (e) {
    return "";
  }
}

function hasPriceBackend() {
  if (isLocalServer()) return true;
  return Boolean(getApiBase());
}

function apiUrl(path) {
  const base = getApiBase();
  return base + path;
}

// قیمت‌ها از بک‌اند → والکس (لوکال یا Render)
async function fetchWallexFromBackend() {
  if (!hasPriceBackend()) return null;
  const res = await fetch(apiUrl("/api/markets"), { cache: "no-store" });
  if (!res.ok) throw new Error("backend markets HTTP " + res.status);
  const data = await res.json();
  if (!data || !data.ok) throw new Error((data && data.error) || "markets failed");
  return data;
}

function FinanceApp() {
  const [data, setData] = useState(() => loadDataFromStorage());
  const [saveError, setSaveError] = useState(false);
  const currentYM = useMemo(() => getCurrentPersianYM(), []);
  const [year, setYear] = useState(currentYM.y);
  const [monthIdx, setMonthIdx] = useState(currentYM.m);
  const [confirmReset, setConfirmReset] = useState(false);
  const [loans, setLoans] = useState(() => loadLoansFromStorage());
  const loansRef = useRef(loans);
  useEffect(() => { loansRef.current = loans; }, [loans]);
  const [showNotifs, setShowNotifs] = useState(false);

  // livePrices: نماد والکس (BTC, ETH, ...) -> قیمت به USDT
  const [livePrices, setLivePrices] = useState(() => {
    try {
      const raw = localStorage.getItem(STORAGE_KEY + "-live-prices");
      if (raw) {
        const parsed = JSON.parse(raw);
        if (parsed && typeof parsed === "object") return parsed;
      }
    } catch (e) {}
    return {};
  });
  const [priceLoading, setPriceLoading] = useState(false);
  const [priceLastUpdated, setPriceLastUpdated] = useState(null);
  const [priceStatus, setPriceStatus] = useState(null);

  // نرخ USDT→تومان از والکس (فقط خواندنی)
  const [tomanRateStr, setTomanRateStr] = useState(() => {
    try {
      const rateRaw = localStorage.getItem(STORAGE_KEY + "-toman-rate");
      if (rateRaw) {
        const n = sanitizeNumber(rateRaw, 0, 10_000_000);
        if (n > 0) return String(n);
      }
    } catch (e) {}
    return String(DEFAULT_TOMAN_RATE);
  });
  const tomanRate = (() => {
    const n = Number(tomanRateStr);
    return Number.isFinite(n) && n > 0 ? n : DEFAULT_TOMAN_RATE;
  })();

  const formKey = `${year}-${monthIdx}`; // remount forms on month/year change


  const loadFromLocalFile = async () => {
    const hasData = Object.keys(data).some((k) => {
      const md = data[k] || {};
      return (md.income || []).length || (md.expenses || []).length || (md.investments || []).length;
    });
    if (hasData) {
      const ok = window.confirm("با بارگذاری فایل، تمام داده‌های فعلی (همه ماه‌ها) جایگزین می‌شوند. ادامه می‌دهید؟");
      if (!ok) return false;
    }
    try {
      return await new Promise((resolve) => {
        const input = document.createElement("input");
        input.type = "file";
        input.accept = ".json,application/json";
        input.onchange = async (e) => {
          const f = e.target.files && e.target.files[0];
          if (!f) { resolve(false); return; }
          try {
            const text = await f.text();
            const payload = JSON.parse(text);
            const s = payload && typeof payload === "object" ? (payload.budgetData || payload.data || payload) : null;
            if (s && typeof s === "object") {
              setData(s);
              try { localStorage.setItem(STORAGE_KEY, JSON.stringify(s)); } catch (err) {}
              if (payload.livePrices && typeof payload.livePrices === "object") {
                setLivePrices(payload.livePrices);
                try { localStorage.setItem(STORAGE_KEY + "-live-prices", JSON.stringify(payload.livePrices)); } catch (err) {}
              }
              if (payload.tomanRate) {
                const n = sanitizeNumber(payload.tomanRate, DEFAULT_TOMAN_RATE, 10_000_000);
                if (n > 0) {
                  setTomanRateStr(String(n));
                  try { localStorage.setItem(STORAGE_KEY + "-toman-rate", String(n)); } catch (err) {}
                }
              }
              if (Array.isArray(payload.loans)) {
                const normalized = payload.loans.map(normalizeLoan).filter(Boolean);
                setLoans(normalized);
                saveLoansToStorage(normalized);
              }
              resolve(true);
            } else {
              alert("فرمت فایل معتبر نیست.");
              resolve(false);
            }
          } catch (err) {
            alert("خواندن فایل با خطا مواجه شد.");
            resolve(false);
          }
        };
        input.click();
      });
    } catch (e) { alert("بارگذاری فایل با خطا"); return false; }
  };

  useEffect(() => {
    let active = true;
    const hasLocalData = Object.keys(loadDataFromStorage()).length > 0;
    if (!hasLocalData) {
      (async () => {
        // اول از سرور محلی (finance-data.json)، بعد sidecar کنار HTML
        if (isLocalServer()) {
          try {
            const res = await fetch("/api/data", { cache: "no-store" });
            if (res.ok) {
              const payload = await res.json();
              const s = payload.budgetData || payload.data || null;
              if (active && s && typeof s === "object" && Object.keys(s).length) {
                setData(s);
                try { localStorage.setItem(STORAGE_KEY, JSON.stringify(s)); } catch (e) {}
                if (payload.tomanRate) {
                  setTomanRateStr(String(payload.tomanRate));
                }
                if (payload.livePrices) setLivePrices(payload.livePrices);
                if (Array.isArray(payload.loans)) {
                  const normalized = payload.loans.map(normalizeLoan).filter(Boolean);
                  setLoans(normalized);
                  saveLoansToStorage(normalized);
                }
                return;
              }
            }
          } catch (e) {}
        }
        const sidecarData = await loadDataFromSidecar();
        if (!active) return;
        if (sidecarData && Object.keys(sidecarData).length) {
          setData(sidecarData);
          try { localStorage.setItem(STORAGE_KEY, JSON.stringify(sidecarData)); } catch (e) {}
        }
      })();
    }
    return () => { active = false; };
  }, []);

  useEffect(() => {
    try {
      const raw = localStorage.getItem(STORAGE_KEY + "-live-prices");
      if (raw) {
        const parsed = JSON.parse(raw);
        if (parsed && typeof parsed === "object") setLivePrices(parsed);
      }
    } catch (e) {}
  }, []);

  const key = monthKey(year, monthIdx);
  const monthData = data[key] || { income: [], expenses: [], investments: [] };

  const saveToBackend = async (budgetData, prices, rate, loansData) => {
    if (!isLocalServer()) return;
    try {
      await fetch("/api/data", {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({
          budgetData,
          livePrices: prices,
          tomanRate: rate,
          loans: loansData != null ? loansData : loans,
        }),
      });
    } catch (e) {
      console.warn("backend save failed", e);
    }
  };

  const persist = (next) => {
    setData(next);
    const ok = saveData(next, livePrices, false, tomanRate);
    setSaveError(!ok);
    // پشتیبان فایل کنار برنامه (فقط وقتی سرور محلی بالاست)
    saveToBackend(next, livePrices, tomanRate, loansRef.current);
  };

  const persistLoans = (nextLoans) => {
    loansRef.current = nextLoans;
    setLoans(nextLoans);
    saveLoansToStorage(nextLoans);
    saveToBackend(data, livePrices, tomanRate, nextLoans);
  };

  const addLoan = (loan) => {
    persistLoans([...(loans || []), loan]);
  };

  const deleteLoan = (id) => {
    persistLoans((loans || []).filter((l) => l.id !== id));
  };

  const payLoanInstallment = (id) => {
    const loan = (loans || []).find((l) => l.id === id);
    if (!loan) return;
    if (loanPaidForMonth(loan, key)) return;
    const pay = Math.min(Number(loan.monthlyPayment) || 0, Number(loan.remainingAmount) || Number(loan.monthlyPayment) || 0);
    if (pay <= 0) return;
    const next = (loans || []).map((l) => {
      if (l.id !== id) return l;
      const remainingAmount = Math.max(0, (Number(l.remainingAmount) || 0) - pay);
      const paidCount = (Number(l.paidCount) || 0) + 1;
      const payments = Array.isArray(l.payments) ? l.payments.slice() : [];
      payments.push({ id: uid(), amount: pay, at: Date.now(), monthKey: key });
      return { ...l, remainingAmount, paidCount, payments };
    });
    persistLoans(next);
    addExpense({ id: uid(), label: "قسط: " + loan.title, amount: pay });
  };

  const updateMonth = (updater) => {
    const current = data[key] || { income: [], expenses: [], investments: [] };
    persist({ ...data, [key]: updater(current) });
  };

  const addIncome = (item) => updateMonth((cur) => ({ ...cur, income: [...(cur.income || []), item] }));
  const delIncome = (id) => updateMonth((cur) => ({ ...cur, income: (cur.income || []).filter((i) => i.id !== id) }));
  const addExpense = (item) => updateMonth((cur) => ({ ...cur, expenses: [...(cur.expenses || []), item] }));
  const delExpense = (id) => updateMonth((cur) => ({ ...cur, expenses: (cur.expenses || []).filter((i) => i.id !== id) }));
  const addInvestment = (item) => updateMonth((cur) => {
    const existing = (cur.investments || []).map((it) => ({ ...it, lots: Array.isArray(it.lots) ? it.lots.slice() : [] }));
    const label = normalizeAssetLabel(item.label);
    const units = Number(item.units ?? item.amount ?? 0);
    if (!label || !Number.isFinite(units) || units <= 0) return cur;

    const pp = item.purchasePriceUsdt ?? item.purchasePrice;
    if (!Number.isFinite(Number(pp)) || Number(pp) < 0) return cur;
    const purchasePriceUsdt = Number(pp);

    const lot = {
      id: uid(),
      units,
      purchasePriceUsdt,
      date: Date.now(),
    };
    const foundIdx = existing.findIndex((it) => assetKey(it.label) === assetKey(label));
    if (foundIdx >= 0) {
      const existingItem = { ...existing[foundIdx] };
      const prevLots = Array.isArray(existingItem.lots) ? existingItem.lots.slice() : [];
      prevLots.push(lot);
      const totalUnits = prevLots.reduce((sum, l) => sum + (Number(l.units) || 0), 0);
      existingItem.units = totalUnits;
      existingItem.amount = totalUnits;
      existingItem.lots = prevLots;
      existingItem.label = label; // normalize casing
      existing[foundIdx] = existingItem;
    } else {
      existing.push({ id: item.id || uid(), label, units, amount: units, lots: [lot] });
    }
    return { ...cur, investments: existing };
  });
  const delInvestment = (id) => updateMonth((cur) => ({ ...cur, investments: (cur.investments || []).filter((i) => i.id !== id) }));

  // Current price in USDT (Wallex via backend); fallback to avg purchase
  const getCurrentPriceUsdt = (label) => {
    const base = mapLabelToWallexBase(label);
    if (base && livePrices[base] != null && Number.isFinite(Number(livePrices[base]))) {
      return Number(livePrices[base]);
    }
    return null;
  };

  // Aggregate all lots for an asset across ALL months
  const getGlobalAssetSummary = (label) => {
    const k = assetKey(label);
    let totalUnits = 0;
    let totalInvestedUsdt = 0;
    Object.values(data || {}).forEach((md) => {
      (md.investments || []).forEach((it) => {
        if (assetKey(it.label) !== k) return;
        const lots = Array.isArray(it.lots) && it.lots.length
          ? it.lots
          : [{ units: Number(it.units ?? it.amount ?? 0), purchasePriceUsdt: Number(it.purchasePriceUsdt ?? it.purchasePrice ?? 0) }];
        lots.forEach((lot) => {
          const u = Number(lot.units || 0) || 0;
          const pp = Number(lot.purchasePriceUsdt || 0) || 0;
          if (u > 0) {
            totalUnits += u;
            totalInvestedUsdt += u * Math.max(0, pp);
          }
        });
      });
    });
    const livePrice = getCurrentPriceUsdt(label);
    const estimated = livePrice === null;
    const avgPurchaseUsdt = totalUnits ? totalInvestedUsdt / totalUnits : 0;
    const currentPriceUsdt = estimated ? avgPurchaseUsdt : livePrice;
    const currentTotalUsdt = totalUnits * currentPriceUsdt;
    const diffUsdt = currentTotalUsdt - totalInvestedUsdt;
    const pct = totalInvestedUsdt > 0 ? (diffUsdt / totalInvestedUsdt) * 100 : 0;
    return {
      totalUnits,
      totalInvestedUsdt,
      currentPriceUsdt,
      currentTotalUsdt,
      diffUsdt,
      pct,
      estimated,
      hasLivePrice: !estimated,
    };
  };

  // For a single month-entry item: show this month's lots but P/L uses global avg? 
  // Better: show this month's contribution in USDT, and global % from full portfolio for that asset.
  const getMonthItemSummary = (item) => {
    const lots = Array.isArray(item.lots) && item.lots.length
      ? item.lots
      : [{ units: Number(item.units ?? item.amount ?? 0), purchasePriceUsdt: Number(item.purchasePriceUsdt ?? item.purchasePrice ?? 0) }];
    const monthUnits = lots.reduce((s, l) => s + (Number(l.units || 0) || 0), 0);
    const monthInvestedUsdt = lots.reduce((s, l) => s + ((Number(l.units || 0) || 0) * Math.max(0, Number(l.purchasePriceUsdt || 0) || 0)), 0);
    const global = getGlobalAssetSummary(item.label);
    const livePrice = getCurrentPriceUsdt(item.label);
    const estimated = livePrice === null;
    const price = estimated
      ? (monthUnits ? monthInvestedUsdt / monthUnits : 0)
      : livePrice;
    const currentTotalUsdt = monthUnits * price;
    const diffUsdt = currentTotalUsdt - monthInvestedUsdt;
    const pct = monthInvestedUsdt > 0 ? (diffUsdt / monthInvestedUsdt) * 100 : 0;
    return {
      monthUnits,
      monthInvestedUsdt,
      currentTotalUsdt,
      diffUsdt,
      pct,
      estimated,
      globalPct: global.pct,
      globalUnits: global.totalUnits,
    };
  };

  const totalIncome = useMemo(() => (monthData.income || []).reduce((a, b) => a + b.amount, 0), [monthData]);
  const totalExpense = useMemo(() => (monthData.expenses || []).reduce((a, b) => a + b.amount, 0), [monthData]);
  const balance = totalIncome - totalExpense;
  const percentSpent = totalIncome ? (totalExpense / totalIncome) * 100 : 0;

  const incomeSuggestions = useMemo(() => {
    const set = new Set();
    Object.values(data).forEach((md) => (md.income || []).forEach((it) => set.add(it.label)));
    return [...set];
  }, [data]);
  const expenseSuggestions = useMemo(() => {
    const set = new Set();
    Object.values(data).forEach((md) => (md.expenses || []).forEach((it) => set.add(it.label)));
    return [...set];
  }, [data]);
  const investmentSuggestions = useMemo(() => {
    const set = new Set();
    Object.values(data).forEach((md) => (md.investments || []).forEach((it) => set.add(normalizeAssetLabel(it.label))));
    return [...set];
  }, [data]);

  const categoryBreakdown = useMemo(() => {
    const map = {};
    (monthData.expenses || []).forEach((it) => { map[it.label] = (map[it.label] || 0) + it.amount; });
    return Object.entries(map).map(([name, value]) => ({ name, value })).sort((a, b) => b.value - a.value);
  }, [monthData]);

  // Global portfolio by asset (case-insensitive merge)
  const persianDay = useMemo(() => getCurrentPersianDay(), []);
  const loansSummary = useMemo(() => {
    const list = loans || [];
    const y = year;
    const m = monthIdx;
    const mk = monthKey(y, m);
    const active = list.filter((l) => {
      if ((Number(l.remainingAmount) || 0) <= 0 && (Number(l.paidCount) || 0) >= (Number(l.totalMonths) || 0)) return false;
      // فعال اگر هنوز ماه‌هایی برای پرداخت مانده
      const startIdx = persianMonthIndex(Number(l.startYear), Number(l.startMonth));
      const endIdx = startIdx + Math.max(1, Number(l.totalMonths) || 1) - 1;
      const curIdx = persianMonthIndex(y, m);
      return curIdx <= endIdx && (Number(l.remainingAmount) || 0) > 0;
    });
    const totalRemaining = list.reduce((s, l) => s + Math.max(0, Number(l.remainingAmount) || 0), 0);
    const dueThisMonth = list.filter((l) => loanCoversMonth(l, y, m) && !loanPaidForMonth(l, mk) && (Number(l.remainingAmount) || 0) > 0);
    const dueThisMonthTotal = dueThisMonth.reduce((s, l) => s + (Number(l.monthlyPayment) || 0), 0);
    const upcoming = list
      .filter((l) => loanCoversMonth(l, y, m) && !loanPaidForMonth(l, mk) && (Number(l.remainingAmount) || 0) > 0)
      .map((l) => {
        const daysLeft = daysUntilDueInMonth(l.dueDay, persianDay);
        return {
          id: l.id,
          title: l.title,
          amount: Number(l.monthlyPayment) || 0,
          dueDay: Number(l.dueDay) || 1,
          daysLeft,
          overdue: daysLeft < 0,
        };
      })
      .sort((a, b) => a.daysLeft - b.daysLeft);
    const alerts = upcoming.filter((u) => u.daysLeft <= 5);
    return {
      activeCount: active.length,
      totalRemaining,
      dueThisMonth,
      dueThisMonthTotal,
      upcoming,
      alerts,
      count: list.length,
    };
  }, [loans, year, monthIdx, persianDay]);

  const portfolioAssets = useMemo(() => {
    const map = {}; // assetKey -> { label, summary }
    Object.values(data || {}).forEach((md) => {
      (md.investments || []).forEach((it) => {
        const k = assetKey(it.label);
        if (!map[k]) {
          map[k] = { label: normalizeAssetLabel(it.label), key: k };
        }
      });
    });
    return Object.values(map)
      .map((a) => ({ ...a, ...getGlobalAssetSummary(a.label) }))
      .filter((a) => a.totalUnits > 0)
      .sort((a, b) => b.currentTotalUsdt - a.currentTotalUsdt);
  }, [data, livePrices]);

  const portfolioSummary = useMemo(() => {
    let totalInvestedUsdt = 0;
    let totalCurrentUsdt = 0;
    portfolioAssets.forEach((a) => {
      totalInvestedUsdt += a.totalInvestedUsdt;
      totalCurrentUsdt += a.currentTotalUsdt;
    });
    const diffUsdt = totalCurrentUsdt - totalInvestedUsdt;
    const pct = totalInvestedUsdt > 0 ? (diffUsdt / totalInvestedUsdt) * 100 : 0;
    const totalCurrentToman = totalCurrentUsdt * tomanRate;
    const totalInvestedToman = totalInvestedUsdt * tomanRate;
    const diffToman = totalCurrentToman - totalInvestedToman;
    return {
      totalInvestedUsdt,
      totalCurrentUsdt,
      diffUsdt,
      pct,
      totalCurrentToman,
      totalInvestedToman,
      diffToman,
    };
  }, [portfolioAssets, tomanRate]);

  const investmentsBreakdown = useMemo(() => {
    return portfolioAssets.map((a) => ({
      name: a.label,
      value: a.currentTotalUsdt * tomanRate,
    })).filter((x) => x.value > 0);
  }, [portfolioAssets, tomanRate]);

  const trend = useMemo(() => {
    const timeline = sortedMonthKeys(data);
    const cumulativeMap = {};
    let running = 0;
    timeline.forEach((k) => {
      const md = data[k];
      const inc = (md.income || []).reduce((a, b) => a + b.amount, 0);
      const exp = (md.expenses || []).reduce((a, b) => a + b.amount, 0);
      running += inc - exp;
      cumulativeMap[k] = running;
    });
    const seq = [];
    let lastKnownCumulative = 0;
    const { y: endY, m: endM } = currentYM;
    for (let i = 5; i >= 0; i--) {
      let y = endY, m = endM - i;
      while (m < 0) { m += 12; y -= 1; }
      const k = monthKey(y, m);
      const md = data[k] || { income: [], expenses: [] };
      const inc = (md.income || []).reduce((a, b) => a + b.amount, 0);
      const exp = (md.expenses || []).reduce((a, b) => a + b.amount, 0);
      const cum = k in cumulativeMap ? cumulativeMap[k] : lastKnownCumulative;
      lastKnownCumulative = cum;
      seq.push({ name: MONTHS[m], "درآمد": inc, "هزینه": exp, "مانده کل": cum });
    }
    return seq;
  }, [data, currentYM]);

  // Portfolio value trend: cumulative units * current prices (snapshot of today's valuation of holdings bought up to each month)
  const portfolioTrend = useMemo(() => {
    const { y: endY, m: endM } = currentYM;
    const seq = [];
    // Build cumulative holdings month by month
    const holdings = {}; // assetKey -> { units, investedUsdt, label }
    const allKeys = [];
    for (let y = endY - 2; y <= endY; y++) {
      for (let m = 0; m < 12; m++) {
        if (y === endY && m > endM) break;
        allKeys.push(monthKey(y, m));
      }
    }
    // Start from 6 months ago
    const windowKeys = [];
    for (let i = 5; i >= 0; i--) {
      let y = endY, m = endM - i;
      while (m < 0) { m += 12; y -= 1; }
      windowKeys.push({ y, m, k: monthKey(y, m) });
    }
    // Accumulate from earliest relevant
    const startIdx = Math.max(0, allKeys.indexOf(windowKeys[0].k));
    let wi = 0;
    for (let i = 0; i < allKeys.length && wi < windowKeys.length; i++) {
      const k = allKeys[i];
      const md = data[k] || {};
      (md.investments || []).forEach((it) => {
        const ak = assetKey(it.label);
        if (!holdings[ak]) holdings[ak] = { units: 0, investedUsdt: 0, label: normalizeAssetLabel(it.label) };
        const lots = Array.isArray(it.lots) && it.lots.length
          ? it.lots
          : [{ units: Number(it.units ?? it.amount ?? 0), purchasePriceUsdt: Number(it.purchasePriceUsdt ?? 0) }];
        lots.forEach((lot) => {
          const u = Number(lot.units || 0) || 0;
          const pp = Math.max(0, Number(lot.purchasePriceUsdt || 0) || 0);
          holdings[ak].units += u;
          holdings[ak].investedUsdt += u * pp;
        });
      });
      if (k === windowKeys[wi].k) {
        let valueUsdt = 0;
        Object.values(holdings).forEach((h) => {
          const live = getCurrentPriceUsdt(h.label);
          const price = live != null ? live : (h.units ? h.investedUsdt / h.units : 0);
          valueUsdt += h.units * price;
        });
        seq.push({ name: MONTHS[windowKeys[wi].m], "ارزش سرمایه": valueUsdt * tomanRate });
        wi++;
      }
    }
    while (wi < windowKeys.length) {
      seq.push({ name: MONTHS[windowKeys[wi].m], "ارزش سرمایه": seq.length ? seq[seq.length - 1]["ارزش سرمایه"] : 0 });
      wi++;
    }
    return seq;
  }, [data, currentYM, livePrices, tomanRate]);

  const handleExport = async () => {
    try {
      const wb = await buildMonthlyWorkbook({
        year,
        monthIdx,
        monthData,
        loans,
        loansSummary,
        portfolioAssets,
        portfolioSummary,
        totalIncome,
        totalExpense,
        balance,
        percentSpent,
        tomanRate,
        categoryBreakdown,
      });
      const monthName = MONTHS[monthIdx] || "";
      await downloadWorkbook(wb, `دخل-و-خرج-${monthName}-${year}.xlsx`);
    } catch (e) {
      console.error("export failed", e);
      alert("خروجی اکسل با خطا مواجه شد. لطفاً دوباره تلاش کنید.");
    }
  };

  // نرخ تتر + قیمت دارایی‌ها از والکس (از طریق بک‌اند محلی)
  async function fetchPrices() {
    setPriceLoading(true);
    setPriceStatus(null);
    try {
      if (!hasPriceBackend()) {
        setPriceStatus("بک‌اند قیمت در دسترس نیست. اتصال اینترنت یا Worker را بررسی کنید.");
        setPriceLastUpdated(Date.now());
        setTimeout(() => setPriceStatus(null), 5000);
        setPriceLoading(false);
        return;
      }
      const payload = await fetchWallexFromBackend();
      if (!payload) {
        setPriceStatus("پاسخی از سرور قیمت نیامد.");
        setPriceLoading(false);
        setTimeout(() => setPriceStatus(null), 4500);
        return;
      }
      let rateOk = false;
      let assetCount = 0;
      if (payload.usdtVariants) {
        const chosen =
          (payload.usdtVariants.mid && payload.usdtVariants.mid > 0
            ? payload.usdtVariants.mid
            : null) ||
          payload.usdtToman ||
          payload.usdtVariants.ask ||
          payload.usdtVariants.bid ||
          payload.usdtVariants.last;
        if (chosen && chosen > 0) {
          setTomanRateStr(String(chosen));
          try { localStorage.setItem(STORAGE_KEY + "-toman-rate", String(chosen)); } catch (e) {}
          rateOk = true;
        }
      } else if (payload.usdtToman && payload.usdtToman > 0) {
        setTomanRateStr(String(payload.usdtToman));
        try { localStorage.setItem(STORAGE_KEY + "-toman-rate", String(payload.usdtToman)); } catch (e) {}
        rateOk = true;
      }
      if (payload.pricesUsdt && typeof payload.pricesUsdt === "object") {
        const next = { ...livePrices, ...payload.pricesUsdt };
        setLivePrices(next);
        try { localStorage.setItem(STORAGE_KEY + "-live-prices", JSON.stringify(next)); } catch (e) {}
        assetCount = Object.keys(payload.pricesUsdt).filter((k) => k !== "USDT").length;
        saveToBackend(data, next, payload.usdtToman || tomanRate);
      }
      setPriceLastUpdated(Date.now());
      const parts = [];
      if (rateOk) parts.push("نرخ تتر");
      if (assetCount > 0) parts.push(assetCount + " قیمت دارایی");
      setPriceStatus(parts.length ? (parts.join(" و ") + " از والکس بروزرسانی شد.") : "داده‌ای از والکس نیامد.");
    } catch (e) {
      console.error("wallex backend failed", e);
      setPriceStatus("بروزرسانی از والکس ناموفق بود. سرور محلی را چک کنید.");
    }
    setTimeout(() => setPriceStatus(null), 4500);
    setPriceLoading(false);
  }

  // Auto-refresh asset prices on first load
  const didAutoFetch = useRef(false);
  useEffect(() => {
    if (didAutoFetch.current) return;
    didAutoFetch.current = true;
    fetchPrices();
  }, []);

  // ذخیره خودکار در localStorage هنگام بستن صفحه
  useEffect(() => {
    const flush = () => {
      try {
        localStorage.setItem(STORAGE_KEY, JSON.stringify(data));
        localStorage.setItem(STORAGE_KEY + "-live-prices", JSON.stringify(livePrices));
        localStorage.setItem(STORAGE_KEY + "-toman-rate", String(tomanRate));
        try { localStorage.setItem(LOANS_KEY, JSON.stringify(loans)); } catch (e2) {}
      } catch (e) {}
      // best-effort؛ ممکن است در beforeunload کامل نشود
      if (isLocalServer()) {
        try {
          navigator.sendBeacon(
            "/api/data",
            new Blob(
              [JSON.stringify({ budgetData: data, livePrices, tomanRate, loans })],
              { type: "application/json" }
            )
          );
        } catch (e) {}
      }
    };
    window.addEventListener("beforeunload", flush);
    window.addEventListener("pagehide", flush);
    return () => {
      window.removeEventListener("beforeunload", flush);
      window.removeEventListener("pagehide", flush);
    };
  }, [data, livePrices, tomanRate, loans]);

  const handleReset = () => {
    if (!confirmReset) { setConfirmReset(true); setTimeout(() => setConfirmReset(false), 3000); return; }
    const nextData = { ...data };
    delete nextData[key];
    setData(nextData);
    const ok = saveData(nextData, livePrices, false, tomanRate);
    setSaveError(!ok);
    setConfirmReset(false);
  };

  return (
    <main dir="rtl" role="main" style={{ fontFamily: "Vazirmatn, Tahoma, Arial, sans-serif", background: THEME.bg, minHeight: "100vh", padding: "0 0 32px" }}>
      <div style={{ height: 6, backgroundImage: `repeating-linear-gradient(-45deg, ${THEME.teal} 0px, ${THEME.teal} 6px, ${THEME.gold} 6px, ${THEME.gold} 12px, ${THEME.brick} 12px, ${THEME.brick} 18px)` }} />
      <div style={{ maxWidth: 1080, margin: "0 auto", padding: "24px 20px 0" }}>
        <div style={{ display: "flex", alignItems: "center", justifyContent: "space-between", flexWrap: "wrap", gap: 12, marginBottom: 22 }}>
          <header style={{ display: "flex", alignItems: "center", gap: 10 }}>
            <div style={{ width: 44, height: 44, borderRadius: 12, background: THEME.tealDark, display: "flex", alignItems: "center", justifyContent: "center", boxShadow: "0 2px 8px rgba(15,122,118,0.25)" }} aria-hidden="true">
              <Icon name="wallet" size={22} color="#fff" />
            </div>
            <div>
              <h1 style={{ fontSize: 20, fontWeight: 800, color: THEME.ink, margin: 0 }}>دخل و خرج</h1>
              <p style={{ fontSize: 12.5, color: THEME.inkSoft, margin: 0 }}>مدیریت بودجه ماهانه</p>
            </div>
            <div style={{ position: "relative", marginRight: 4 }}>
              <button type="button" onClick={() => setShowNotifs((v) => !v)} aria-label="اعلان‌ها"
                style={{ width: 40, height: 40, borderRadius: 10, border: `1px solid ${THEME.border}`, background: showNotifs ? THEME.brickSoft : "#fff", cursor: "pointer", display: "flex", alignItems: "center", justifyContent: "center", position: "relative", color: loansSummary.alerts.length ? THEME.brick : THEME.inkSoft }}>
                <Icon name="bell" size={18} />
                {loansSummary.alerts.length > 0 && (
                  <span style={{ position: "absolute", top: -4, left: -4, minWidth: 18, height: 18, borderRadius: 9, background: THEME.brick, color: "#fff", fontSize: 11, fontWeight: 700, display: "flex", alignItems: "center", justifyContent: "center", padding: "0 4px" }}>
                    {loansSummary.alerts.length}
                  </span>
                )}
              </button>
              {showNotifs && (
                <div role="dialog" aria-label="اعلان سررسید اقساط" style={{ position: "absolute", top: 48, right: 0, width: 300, maxWidth: "85vw", background: "#fff", border: `1px solid ${THEME.border}`, borderRadius: 12, boxShadow: "0 8px 28px rgba(0,0,0,0.12)", zIndex: 50, padding: 12 }}>
                  <div style={{ fontSize: 13, fontWeight: 700, color: THEME.ink, marginBottom: 8 }}>یادآور اقساط</div>
                  {loansSummary.alerts.length === 0 ? (
                    <div style={{ fontSize: 12.5, color: THEME.inkSoft, padding: "8px 0" }}>اعلان فوری‌ای نیست. اقساط با ۵ روز یا کمتر مانده اینجا می‌آیند.</div>
                  ) : (
                    <div style={{ display: "flex", flexDirection: "column", gap: 8 }}>
                      {loansSummary.alerts.map((a) => (
                        <div key={a.id} style={{ padding: "8px 10px", borderRadius: 8, background: a.overdue ? THEME.brickSoft : THEME.goldSoft, fontSize: 12.5, color: THEME.ink }}>
                          <div style={{ fontWeight: 700 }}>{a.title}</div>
                          <div style={{ marginTop: 2, color: THEME.inkSoft }}>
                            {a.overdue
                              ? `سررسید گذشته · ${fmt(a.amount)} تومان`
                              : a.daysLeft === 0
                                ? `امروز سررسید · ${fmt(a.amount)} تومان`
                                : `${fmtNumber(a.daysLeft)} روز تا سررسید · ${fmt(a.amount)} تومان`}
                          </div>
                        </div>
                      ))}
                    </div>
                  )}
                </div>
              )}
            </div>
          </header>
          <div style={{ display: "flex", alignItems: "center", gap: 8, flexWrap: "wrap" }}>
            <select aria-label="انتخاب سال" value={year} onChange={(e) => setYear(Number(e.target.value))}
              style={{ padding: "8px 10px", borderRadius: 8, border: `1px solid ${THEME.border}`, fontSize: 13.5, background: "#fff", color: THEME.ink }}>
              {YEARS.map((y) => <option key={y} value={y}>{y}</option>)}
            </select>
            <select aria-label="انتخاب ماه" value={monthIdx} onChange={(e) => setMonthIdx(Number(e.target.value))}
              style={{ padding: "8px 10px", borderRadius: 8, border: `1px solid ${THEME.border}`, fontSize: 13.5, background: "#fff", color: THEME.ink }}>
              {MONTHS.map((m, i) => <option key={m} value={i}>{m}</option>)}
            </select>
            <button type="button" onClick={handleExport}
              style={{ display: "flex", alignItems: "center", gap: 6, padding: "8px 14px", borderRadius: 10, border: "none", background: THEME.gold, color: "#3A2A05", fontWeight: 700, fontSize: 13.5, cursor: "pointer", boxShadow: "0 1px 3px rgba(201,149,47,0.35)" }}>
              <Icon name="download" size={15} color="#3A2A05" /> خروجی اکسل
            </button>
            <button type="button" onClick={() => loadFromLocalFile()}
              style={{ display: "flex", alignItems: "center", gap: 6, padding: "8px 14px", borderRadius: 8, border: `1px solid ${THEME.border}`, background: "#fff", color: THEME.ink, fontSize: 13.5, cursor: "pointer" }}>
              بارگذاری از فایل
            </button>
            <button type="button" onClick={fetchPrices} disabled={priceLoading}
              style={{ display: "flex", alignItems: "center", gap: 6, padding: "8px 14px", borderRadius: 8, border: `1px solid ${THEME.border}`, background: priceLoading ? THEME.tealSoft : "#fff", color: THEME.ink, fontSize: 13.5, cursor: priceLoading ? "wait" : "pointer" }}>
              {priceLoading ? "در حال بروزرسانی..." : "بروزرسانی قیمت‌ها"}
            </button>
          </div>
        </div>

        {(priceLastUpdated || priceStatus) && (
          <div style={{ fontSize: 12, color: THEME.inkSoft, marginBottom: 12, display: "flex", gap: 12, flexWrap: "wrap", alignItems: "center" }}>
            {priceLastUpdated && <span>آخرین بروزرسانی قیمت: {new Date(priceLastUpdated).toLocaleString("fa-IR")}</span>}
            {priceStatus && <span style={{ color: priceStatus.includes("ناموفق") || priceStatus.includes("یافت نشد") || priceStatus.includes("ثبت نشده") ? THEME.brick : THEME.green, fontWeight: 600 }}>{priceStatus}</span>}
          </div>
        )}

        {saveError && (
          <div role="alert" style={{ background: THEME.brickSoft, color: THEME.brick, padding: "8px 12px", borderRadius: 8, fontSize: 13, marginBottom: 16 }}>
            ذخیره تغییرات با خطا مواجه شد (شاید حافظه مرورگر پر یا محدود شده). لطفاً دوباره تلاش کنید.
          </div>
        )}

        <div style={{ display: "grid", gridTemplateColumns: "repeat(auto-fit, minmax(200px, 1fr))", gap: 12, marginBottom: 22 }}>
          <SummaryCard icon="up" label="جمع درآمد" value={totalIncome} color={THEME.green} bg={THEME.greenSoft} />
          <SummaryCard icon="down" label="جمع هزینه" value={totalExpense} color={THEME.brick} bg={THEME.brickSoft} />
          <SummaryCard icon="piggy" label="مانده" value={balance} color={THEME.teal} bg={THEME.tealSoft} />
          <SummaryCard icon="calendar" label="اقساط این ماه" value={loansSummary.dueThisMonthTotal} color={THEME.brick} bg={THEME.brickSoft} />
          <SummaryCard icon="debt" label="مانده کل وام‌ها" value={loansSummary.totalRemaining} color={THEME.gold} bg={THEME.goldSoft} />
          <div style={{ background: THEME.card, border: `0.5px solid ${THEME.border}`, borderRadius: 12, padding: "16px 18px" }}>
            <div style={{ fontSize: 13, color: THEME.inkSoft, marginBottom: 6 }}>سرمایه (کل پرتفوی)</div>
            <div style={{ fontSize: 19, fontWeight: 700, color: THEME.ink, whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis" }}>
              {fmt(portfolioSummary.totalCurrentToman)} <span style={{ fontSize: 12, fontWeight: 400, color: THEME.inkSoft }}>تومان</span>
            </div>
            <div style={{ marginTop: 4, fontSize: 12, color: THEME.inkSoft }}>
              ≈ {fmtUsdt(portfolioSummary.totalCurrentUsdt)} USDT
            </div>
            <div style={{ marginTop: 8, fontSize: 12, color: portfolioSummary.diffToman >= 0 ? THEME.green : THEME.brick, fontWeight: 700 }}>
              {portfolioSummary.diffToman >= 0 ? "سود" : "زیان"}: {fmt(Math.abs(portfolioSummary.diffToman))} تومان ({portfolioSummary.diffUsdt >= 0 ? "+" : ""}{fmtPercent(portfolioSummary.pct)}٪)
            </div>
          </div>
          <div style={{ background: THEME.card, border: `0.5px solid ${THEME.border}`, borderRadius: 12, padding: "16px 18px" }}>
            <div style={{ fontSize: 13, color: THEME.inkSoft, marginBottom: 6 }}>درصد درآمد خرج‌شده</div>
            <div style={{ display: "flex", alignItems: "center", gap: 10 }}>
              <div role="progressbar" aria-label="درصد درآمد خرج‌شده" aria-valuemin={0} aria-valuemax={100} aria-valuenow={Math.round(percentSpent)} style={{ flex: 1, height: 8, borderRadius: 4, background: "#EDEBE4", overflow: "hidden" }}>
                <div style={{ width: `${Math.min(percentSpent, 100)}%`, height: "100%", background: percentSpent > 90 ? THEME.brick : THEME.gold, borderRadius: 4 }} />
              </div>
              <span style={{ fontSize: 15, fontWeight: 700, color: THEME.ink }}>{fmtPercent(percentSpent)}٪</span>
            </div>
          </div>
        </div>

        <div style={{ display: "grid", gridTemplateColumns: "repeat(auto-fit, minmax(300px, 1fr))", gap: 16, marginBottom: 22 }}>
          <div style={{ background: THEME.card, border: `1px solid ${THEME.border}`, borderRadius: 14, padding: 18, boxShadow: "0 1px 2px rgba(27,42,45,0.04)" }}>
            <div style={{ fontSize: 15, fontWeight: 700, color: THEME.teal, marginBottom: 12 }}>ثبت درآمد</div>
            <EntryForm key={`inc-${formKey}`} kind="income" suggestions={incomeSuggestions} onAdd={addIncome} />
            <EntryList items={monthData.income || []} onDelete={delIncome} accent={THEME.green} />
          </div>
          <div style={{ background: THEME.card, border: `1px solid ${THEME.border}`, borderRadius: 14, padding: 18, boxShadow: "0 1px 2px rgba(27,42,45,0.04)" }}>
            <div style={{ fontSize: 15, fontWeight: 700, color: THEME.brick, marginBottom: 12 }}>ثبت هزینه</div>
            <EntryForm key={`exp-${formKey}`} kind="expense" suggestions={expenseSuggestions} onAdd={addExpense} />
            <EntryList items={monthData.expenses || []} onDelete={delExpense} accent={THEME.brick} />
          </div>
          <div style={{ background: THEME.card, border: `1px solid ${THEME.border}`, borderRadius: 14, padding: 18, boxShadow: "0 1px 2px rgba(27,42,45,0.04)" }}>
            <div style={{ fontSize: 15, fontWeight: 700, color: THEME.gold, marginBottom: 12 }}>سرمایه‌گذاری (USDT)</div>
            <div style={{ display: "flex", alignItems: "center", gap: 8, marginBottom: 12, padding: "8px 10px", background: THEME.goldSoft, borderRadius: 8, flexWrap: "wrap" }}>
              <span style={{ fontSize: 12.5, color: THEME.inkSoft }}>نرخ تبدیل ۱ USDT =</span>
              <span aria-label="نرخ تومان برای هر USDT" style={{ fontSize: 14, fontWeight: 700, color: THEME.ink, fontVariantNumeric: "tabular-nums" }}>
                {fmt(tomanRate)}
              </span>
              <span style={{ fontSize: 12.5, color: THEME.inkSoft }}>تومان</span>
            </div>
            <EntryForm key={`inv-${formKey}`} kind="investment" suggestions={investmentSuggestions} onAdd={addInvestment} />
            <EntryList
              items={monthData.investments || []}
              onDelete={delInvestment}
              accent={THEME.gold}
              renderAmount={(it) => {
                const s = getMonthItemSummary(it);
                return `${fmtUsdt(s.currentTotalUsdt)} USDT`;
              }}
              extra={(it) => {
                const s = getMonthItemSummary(it);
                const sign = s.pct > 0 ? "+" : "";
                const color = s.pct > 0 ? THEME.green : (s.pct < 0 ? THEME.brick : THEME.inkSoft);
                return (
                  <span style={{ display: "inline-flex", alignItems: "center", gap: 6, flexWrap: "wrap" }}>
                    <span>{fmtUsdt(s.monthUnits, 4)} واحد · خرید: {fmtUsdt(s.monthInvestedUsdt)} USDT</span>
                    <span style={{ color, fontWeight: 700 }}>{sign}{s.pct.toFixed(1)}%</span>
                    {s.estimated && (
                      <span style={{ color: THEME.inkSoft, fontWeight: 400, fontSize: 11 }} title="قیمت لحظه‌ای در دسترس نیست؛ بر اساس قیمت خرید">
                        (بدون قیمت لحظه‌ای)
                      </span>
                    )}
                  </span>
                );
              }}
            />
          </div>
        </div>

        {/* Dynamic global portfolio table */}
        {portfolioAssets.length > 0 && (
          <div style={{ background: THEME.card, border: `0.5px solid ${THEME.border}`, borderRadius: 12, padding: 18, marginBottom: 22 }}>
            <div style={{ display: "flex", alignItems: "center", justifyContent: "space-between", flexWrap: "wrap", gap: 8, marginBottom: 14 }}>
              <div style={{ fontSize: 15, fontWeight: 700, color: THEME.gold }}>پرتفوی کل (همه ماه‌ها)</div>
              <div style={{ fontSize: 12.5, color: THEME.inkSoft }}>
                سود/زیان نسبت به مبلغ خرید اولیه · قیمت از والکس
              </div>
            </div>
            <div style={{ overflowX: "auto" }}>
              <table style={{ width: "100%", borderCollapse: "collapse", fontSize: 13 }}>
                <thead>
                  <tr style={{ borderBottom: `1px solid ${THEME.border}`, color: THEME.inkSoft, textAlign: "right" }}>
                    <th style={{ padding: "8px 10px", fontWeight: 600 }}>دارایی</th>
                    <th style={{ padding: "8px 10px", fontWeight: 600 }}>واحد</th>
                    <th style={{ padding: "8px 10px", fontWeight: 600 }}>میانگین خرید</th>
                    <th style={{ padding: "8px 10px", fontWeight: 600 }}>قیمت فعلی</th>
                    <th style={{ padding: "8px 10px", fontWeight: 600 }}>ارزش فعلی</th>
                    <th style={{ padding: "8px 10px", fontWeight: 600 }}>سرمایه‌گذاری</th>
                    <th style={{ padding: "8px 10px", fontWeight: 600 }}>سود/زیان</th>
                  </tr>
                </thead>
                <tbody>
                  {portfolioAssets.map((a) => {
                    const sign = a.diffUsdt >= 0 ? "+" : "";
                    const color = a.diffUsdt > 0 ? THEME.green : (a.diffUsdt < 0 ? THEME.brick : THEME.inkSoft);
                    return (
                      <tr key={a.key} style={{ borderBottom: `1px dashed ${THEME.border}` }}>
                        <td style={{ padding: "10px", fontWeight: 600, color: THEME.ink }}>
                          {a.label}
                          {a.estimated && <span style={{ marginRight: 6, fontSize: 11, color: THEME.inkSoft, fontWeight: 400 }}>(تخمینی)</span>}
                        </td>
                        <td style={{ padding: "10px" }}>{fmtUsdt(a.totalUnits, 4)}</td>
                        <td style={{ padding: "10px" }}>{fmtUsdt(a.totalUnits ? a.totalInvestedUsdt / a.totalUnits : 0)} USDT</td>
                        <td style={{ padding: "10px" }}>{fmtUsdt(a.currentPriceUsdt)} USDT</td>
                        <td style={{ padding: "10px", fontWeight: 600 }}>{fmtUsdt(a.currentTotalUsdt)} USDT</td>
                        <td style={{ padding: "10px" }}>{fmtUsdt(a.totalInvestedUsdt)} USDT</td>
                        <td style={{ padding: "10px", color, fontWeight: 700 }}>
                          {sign}{fmtUsdt(a.diffUsdt)} ({sign}{a.pct.toFixed(1)}%)
                        </td>
                      </tr>
                    );
                  })}
                </tbody>
                <tfoot>
                  <tr style={{ borderTop: `2px solid ${THEME.border}`, fontWeight: 700 }}>
                    <td style={{ padding: "10px" }} colSpan={4}>جمع</td>
                    <td style={{ padding: "10px" }}>{fmtUsdt(portfolioSummary.totalCurrentUsdt)} USDT</td>
                    <td style={{ padding: "10px" }}>{fmtUsdt(portfolioSummary.totalInvestedUsdt)} USDT</td>
                    <td style={{ padding: "10px", color: portfolioSummary.diffUsdt >= 0 ? THEME.green : THEME.brick }}>
                      {portfolioSummary.diffUsdt >= 0 ? "+" : ""}{fmtUsdt(portfolioSummary.diffUsdt)} ({portfolioSummary.diffUsdt >= 0 ? "+" : ""}{fmtPercent(portfolioSummary.pct)}%)
                    </td>
                  </tr>
                </tfoot>
              </table>
            </div>
          </div>
        )}

        {/* وام و اقساط */}
        <div style={{ display: "grid", gridTemplateColumns: "repeat(auto-fit, minmax(280px, 1fr))", gap: 12, marginBottom: 16 }}>
          <div style={{ background: THEME.card, border: `1px solid ${THEME.border}`, borderRadius: 14, padding: 16, boxShadow: "0 1px 2px rgba(27,42,45,0.04)" }}>
            <div style={{ fontSize: 13, fontWeight: 700, color: THEME.brick, marginBottom: 10, display: "flex", alignItems: "center", gap: 6 }}>
              <Icon name="calendar" size={15} color={THEME.brick} /> اقساط این ماه
            </div>
            {loansSummary.dueThisMonth.length === 0 ? (
              <div style={{ fontSize: 12.5, color: THEME.inkSoft }}>برای این ماه قسط بازی نمانده.</div>
            ) : (
              <div style={{ display: "flex", flexDirection: "column", gap: 6 }}>
                {loansSummary.dueThisMonth.map((l) => (
                  <div key={l.id} style={{ display: "flex", justifyContent: "space-between", gap: 8, fontSize: 13, color: THEME.ink }}>
                    <span>{l.title}</span>
                    <span style={{ fontWeight: 700 }}>{fmt(l.monthlyPayment)} تومان</span>
                  </div>
                ))}
                <div style={{ borderTop: `1px dashed ${THEME.border}`, marginTop: 4, paddingTop: 6, display: "flex", justifyContent: "space-between", fontWeight: 700, fontSize: 13.5 }}>
                  <span>جمع</span>
                  <span style={{ color: THEME.brick }}>{fmt(loansSummary.dueThisMonthTotal)} تومان</span>
                </div>
              </div>
            )}
          </div>
          <div style={{ background: THEME.card, border: `1px solid ${THEME.border}`, borderRadius: 14, padding: 16, boxShadow: "0 1px 2px rgba(27,42,45,0.04)" }}>
            <div style={{ fontSize: 13, fontWeight: 700, color: THEME.ink, marginBottom: 10, display: "flex", alignItems: "center", gap: 6 }}>
              <Icon name="bell" size={15} color={THEME.gold} /> شمارش معکوس تا سررسید
            </div>
            {loansSummary.upcoming.length === 0 ? (
              <div style={{ fontSize: 12.5, color: THEME.inkSoft }}>سررسید نزدیکی نیست.</div>
            ) : (
              <div style={{ display: "flex", flexDirection: "column", gap: 6 }}>
                {loansSummary.upcoming.map((u) => (
                  <div key={u.id} style={{ fontSize: 12.5, color: THEME.ink, display: "flex", justifyContent: "space-between", gap: 8, flexWrap: "wrap" }}>
                    <span>
                      {u.overdue
                        ? `سررسید «${u.title}» گذشته`
                        : u.daysLeft === 0
                          ? `امروز قسط «${u.title}»`
                          : `${fmtNumber(u.daysLeft)} روز تا قسط «${u.title}»`}
                    </span>
                    <span style={{ fontWeight: 600, color: u.daysLeft <= 5 ? THEME.brick : THEME.inkSoft }}>{fmt(u.amount)} تومان</span>
                  </div>
                ))}
              </div>
            )}
          </div>
        </div>

        <div style={{ background: THEME.card, border: `1px solid ${THEME.border}`, borderRadius: 16, padding: 20, marginBottom: 22, boxShadow: "0 1px 3px rgba(27,42,45,0.05)" }}>
          <div style={{ display: "flex", flexWrap: "wrap", alignItems: "baseline", justifyContent: "space-between", gap: 10, marginBottom: 16 }}>
            <div style={{ fontSize: 16, fontWeight: 800, color: THEME.brick, display: "flex", alignItems: "center", gap: 8 }}>
              <span style={{ width: 28, height: 28, borderRadius: 8, background: THEME.brickSoft, display: "inline-flex", alignItems: "center", justifyContent: "center" }}>
                <Icon name="debt" size={15} color={THEME.brick} />
              </span>
              وام و اقساط
            </div>
            <div style={{ fontSize: 12.5, color: THEME.inkSoft }}>
              {loansSummary.count > 0
                ? `${loansSummary.activeCount} فعال · مانده ${fmt(loansSummary.totalRemaining)} تومان`
                : "وام چندماهه با روز سررسید ثابت ثبت کنید"}
            </div>
          </div>
          <LoanForm onAdd={addLoan} defaultYear={year} defaultMonth={monthIdx} />
          {(loans || []).length === 0 ? null : (
            <div style={{ display: "flex", flexDirection: "column", gap: 10 }}>
              {(loans || []).map((loan) => {
                const total = Number(loan.totalAmount) || ((Number(loan.monthlyPayment) || 0) * (Number(loan.totalMonths) || 0));
                const remaining = Number(loan.remainingAmount) || 0;
                const monthly = Number(loan.monthlyPayment) || 0;
                const months = Number(loan.totalMonths) || 0;
                const paidRatio = total > 0 ? Math.min(100, Math.max(0, ((total - remaining) / total) * 100)) : 0;
                const done = remaining <= 0;
                const covers = loanCoversMonth(loan, year, monthIdx);
                const paidHere = loanPaidForMonth(loan, key);
                const daysLeft = daysUntilDueInMonth(loan.dueDay, persianDay);
                return (
                  <div key={loan.id} style={{ padding: "14px 16px", borderRadius: 12, background: "#F7FAF8", border: `1px solid ${THEME.border}` }}>
                    <div style={{ display: "flex", justifyContent: "space-between", gap: 10, flexWrap: "wrap", alignItems: "flex-start" }}>
                      <div style={{ minWidth: 0, flex: "1 1 180px" }}>
                        <div style={{ fontSize: 14, fontWeight: 700, color: THEME.ink }}>{loan.title}</div>
                        <div style={{ fontSize: 12.5, color: THEME.inkSoft, marginTop: 4 }}>
                          هر قسط {fmt(monthly)} تومان · {fmtNumber(months)} ماهه · سررسید روز {fmtNumber(loan.dueDay)} هر ماه
                        </div>
                        <div style={{ fontSize: 12, color: THEME.inkSoft, marginTop: 2 }}>
                          از {MONTHS[Number(loan.startMonth)] || "—"} {fmtNumber(loan.startYear)}
                          {loan.paidCount ? ` · ${fmtNumber(loan.paidCount)} قسط پرداخت‌شده` : ""}
                        </div>
                      </div>
                      <div style={{ textAlign: "left", flex: "0 0 auto" }}>
                        <div style={{ fontSize: 13.5, fontWeight: 700, color: done ? THEME.green : THEME.brick }}>
                          {done ? "تسویه شد" : `${fmt(remaining)} تومان مانده`}
                        </div>
                        {!done && covers && (
                          <div style={{ fontSize: 12, color: paidHere ? THEME.green : (daysLeft <= 5 ? THEME.brick : THEME.inkSoft), marginTop: 2 }}>
                            {paidHere ? "قسط این ماه پرداخت شد" : daysLeft < 0 ? "سررسید گذشته" : daysLeft === 0 ? "سررسید امروز" : `${fmtNumber(daysLeft)} روز تا سررسید`}
                          </div>
                        )}
                      </div>
                    </div>
                    <div style={{ marginTop: 10, height: 8, borderRadius: 999, background: THEME.border, overflow: "hidden" }}>
                      <div style={{ width: `${paidRatio}%`, height: "100%", background: done ? THEME.green : THEME.brick, borderRadius: 999 }} />
                    </div>
                    <div style={{ marginTop: 10, display: "flex", flexWrap: "wrap", gap: 8, alignItems: "center" }}>
                      {!done && covers && !paidHere && (
                        <button type="button" onClick={() => payLoanInstallment(loan.id)}
                          style={{ padding: "6px 12px", borderRadius: 8, border: "none", background: THEME.teal, color: "#fff", fontSize: 12.5, fontWeight: 600, cursor: "pointer" }}>
                          پرداخت قسط این ماه
                        </button>
                      )}
                      <button type="button" onClick={() => deleteLoan(loan.id)} aria-label="حذف وام"
                        style={{ marginRight: "auto", border: "none", background: "transparent", cursor: "pointer", color: THEME.inkSoft, display: "flex", alignItems: "center", gap: 4, fontSize: 12.5 }}>
                        <Icon name="trash" size={14} /> حذف
                      </button>
                    </div>
                  </div>
                );
              })}
            </div>
          )}
        </div>

        <div style={{ display: "grid", gridTemplateColumns: "repeat(auto-fit, minmax(340px, 1fr))", gap: 16, marginBottom: 22 }}>
          <div style={{ background: THEME.card, border: `0.5px solid ${THEME.border}`, borderRadius: 12, padding: 18 }}>
            <div style={{ fontSize: 15, fontWeight: 700, color: THEME.ink, marginBottom: 10 }}>سهم هزینه‌ها به تفکیک دسته</div>
            {categoryBreakdown.length === 0 ? (
              <div style={{ fontSize: 13, color: THEME.inkSoft, padding: "30px 0", textAlign: "center" }}>هزینه‌ای برای این ماه ثبت نشده.</div>
            ) : (
              <div style={{ width: "100%", height: 260 }}>
                <ResponsiveContainer width="100%" height={260}>
                  <PieChart>
                    <Pie data={categoryBreakdown} dataKey="value" nameKey="name" innerRadius={55} outerRadius={90} paddingAngle={2}>
                      {categoryBreakdown.map((_, i) => <Cell key={i} fill={PIE_COLORS_EXPENSES[i % PIE_COLORS_EXPENSES.length]} />)}
                    </Pie>
                    <Tooltip formatter={(v) => fmt(v) + " تومان"} contentStyle={{ fontFamily: "Vazirmatn, sans-serif", fontSize: 12, direction: "rtl", borderRadius: 8 }} />
                    <Legend wrapperStyle={{ fontFamily: "Vazirmatn, sans-serif", fontSize: 11 }} formatter={(value) => <span style={{ display: "inline-block", padding: "0 10px" }}>{value}</span>} />
                  </PieChart>
                </ResponsiveContainer>
              </div>
            )}
          </div>

          <div style={{ background: THEME.card, border: `0.5px solid ${THEME.border}`, borderRadius: 12, padding: 18 }}>
            <div style={{ fontSize: 15, fontWeight: 700, color: THEME.gold, marginBottom: 10 }}>ترکیب سرمایه‌گذاری‌ها (کل)</div>
            {investmentsBreakdown.length === 0 ? (
              <div style={{ fontSize: 13, color: THEME.inkSoft, padding: "30px 0", textAlign: "center" }}>هنوز سرمایه‌ای ثبت نشده.</div>
            ) : (
              <div style={{ width: "100%", height: 260 }}>
                <ResponsiveContainer width="100%" height="100%">
                  <PieChart>
                    <Pie data={investmentsBreakdown} dataKey="value" nameKey="name" innerRadius={55} outerRadius={90} paddingAngle={2}>
                      {investmentsBreakdown.map((_, i) => <Cell key={i} fill={PIE_COLORS_INVESTMENTS[i % PIE_COLORS_INVESTMENTS.length]} />)}
                    </Pie>
                    <Tooltip formatter={(v) => fmt(v) + " تومان"} contentStyle={{ fontFamily: "Vazirmatn, sans-serif", fontSize: 12, direction: "rtl", borderRadius: 8 }} />
                    <Legend wrapperStyle={{ fontFamily: "Vazirmatn, sans-serif", fontSize: 11 }} formatter={(value) => <span style={{ display: "inline-block", padding: "0 10px" }}>{value}</span>} />
                  </PieChart>
                </ResponsiveContainer>
              </div>
            )}
          </div>

          <div style={{ background: THEME.card, border: `0.5px solid ${THEME.border}`, borderRadius: 12, padding: 18, display: "flex", flexDirection: "column", justifyContent: "center" }}>
            <div style={{ fontSize: 15, fontWeight: 700, color: THEME.ink, marginBottom: 12 }}>روند ۶ ماه اخیر</div>
            <div style={{ width: "100%", maxWidth: 760, margin: "0 auto", height: 260 }}>
              <ResponsiveContainer width="100%" height="100%">
                <ComposedChart data={trend} margin={{ top: 12, right: 12, left: 18, bottom: 18 }}>
                  <XAxis dataKey="name" interval={0} minTickGap={10} tickMargin={8} tick={{ fontFamily: "Vazirmatn, sans-serif", fontSize: 10, fill: THEME.inkSoft }} axisLine={{ stroke: THEME.border }} tickLine={false} />
                  <YAxis hide={true} />
                  <Tooltip formatter={(v) => fmtMillion(v)} contentStyle={{ fontFamily: "Vazirmatn, sans-serif", fontSize: 12, direction: "rtl", borderRadius: 8 }} />
                  <Legend wrapperStyle={{ fontFamily: "Vazirmatn, sans-serif", fontSize: 11, paddingTop: 8 }} formatter={(value) => <span style={{ display: "inline-block", padding: "0 10px" }}>{value}</span>} />
                  <Bar dataKey="درآمد" fill="#2EA39B" radius={[4, 4, 0, 0]} barSize={18} />
                  <Bar dataKey="هزینه" fill="#D06A5E" radius={[4, 4, 0, 0]} barSize={18} />
                  <Line type="monotone" dataKey="مانده کل" stroke="#0B6A6D" strokeWidth={2.8} dot={false} activeDot={false} />
                  <CartesianGrid strokeDasharray="0" stroke="transparent" strokeWidth={0} vertical={false} />
                </ComposedChart>
              </ResponsiveContainer>
            </div>
          </div>

          <div style={{ background: THEME.card, border: `0.5px solid ${THEME.border}`, borderRadius: 12, padding: 18, display: "flex", flexDirection: "column", justifyContent: "center" }}>
            <div style={{ fontSize: 15, fontWeight: 700, color: THEME.gold, marginBottom: 12 }}>ارزش سرمایه در ۶ ماه اخیر</div>
            <div style={{ width: "100%", maxWidth: 760, margin: "0 auto", height: 260 }}>
              <ResponsiveContainer width="100%" height="100%">
                <ComposedChart data={portfolioTrend} margin={{ top: 12, right: 12, left: 18, bottom: 18 }}>
                  <XAxis dataKey="name" interval={0} minTickGap={10} tickMargin={8} tick={{ fontFamily: "Vazirmatn, sans-serif", fontSize: 10, fill: THEME.inkSoft }} axisLine={{ stroke: THEME.border }} tickLine={false} />
                  <YAxis hide={true} />
                  <Tooltip formatter={(v) => fmtMillion(v)} contentStyle={{ fontFamily: "Vazirmatn, sans-serif", fontSize: 12, direction: "rtl", borderRadius: 8 }} />
                  <Legend wrapperStyle={{ fontFamily: "Vazirmatn, sans-serif", fontSize: 11, paddingTop: 8 }} formatter={(value) => <span style={{ display: "inline-block", padding: "0 10px" }}>{value}</span>} />
                  <Line type="monotone" dataKey="ارزش سرمایه" stroke="#D7A040" strokeWidth={2.8} dot={false} activeDot={false} />
                  <CartesianGrid strokeDasharray="0" stroke="transparent" strokeWidth={0} vertical={false} />
                </ComposedChart>
              </ResponsiveContainer>
            </div>
          </div>
        </div>

        <div style={{ textAlign: "left" }}>
          <button type="button" onClick={handleReset}
            style={{ display: "inline-flex", alignItems: "center", gap: 6, padding: "7px 12px", borderRadius: 8, border: `1px solid ${THEME.border}`, background: confirmReset ? THEME.brickSoft : "#fff", color: confirmReset ? THEME.brick : THEME.inkSoft, fontSize: 12.5, cursor: "pointer" }}>
            <Icon name="reset" size={13} /> {confirmReset ? "برای تأیید دوباره بزنید" : "پاک کردن داده‌های این ماه"}
          </button>
        </div>
      </div>
    </main>
  );
}

ReactDOM.createRoot(document.getElementById("root")).render(<FinanceApp />);
