// Shared utilities, icons, primitives

// Returns "YYYY-MM" for the current month — used by sumExpensesThisMonth etc.
function thisMonthPrefix() {
  const d = new Date();
  return d.getFullYear() + "-" + String(d.getMonth() + 1).padStart(2, "0");
}

// Returns "YYYY-MM" for last month — used in MoM comparisons
function lastMonthPrefix() {
  const d = new Date();
  d.setDate(1);
  d.setMonth(d.getMonth() - 1);
  return d.getFullYear() + "-" + String(d.getMonth() + 1).padStart(2, "0");
}

const fmt = {
  sgd: (n, opts = {}) => {
    const { showSign = false, decimals = 2 } = opts;
    const abs = Math.abs(n);
    const str = abs.toLocaleString("en-SG", {
      minimumFractionDigits: decimals,
      maximumFractionDigits: decimals,
    });
    if (showSign) return (n >= 0 ? "+" : "−") + "S$" + str;
    return (n < 0 ? "−" : "") + "S$" + str;
  },
  sgdShort: (n) => {
    const abs = Math.abs(n);
    if (abs >= 1000) return "S$" + (abs / 1000).toFixed(1) + "k";
    return "S$" + abs.toFixed(0);
  },
  date: (iso) => {
    const d = new Date(iso + "T00:00:00");
    const today = new Date();
    today.setHours(0, 0, 0, 0);
    const yest = new Date(today);
    yest.setDate(yest.getDate() - 1);
    if (d.getTime() === today.getTime()) return "Today";
    if (d.getTime() === yest.getTime()) return "Yesterday";
    const sameYear = d.getFullYear() === today.getFullYear();
    return d.toLocaleDateString("en-SG", sameYear
      ? { weekday: "short", day: "numeric", month: "short" }
      : { weekday: "short", day: "numeric", month: "short", year: "numeric" });
  },
  dateShort: (iso) => {
    const d = new Date(iso + "T00:00:00");
    const today = new Date();
    const sameYear = d.getFullYear() === today.getFullYear();
    return d.toLocaleDateString("en-SG", sameYear
      ? { day: "numeric", month: "short" }
      : { day: "numeric", month: "short", year: "numeric" });
  },
  monthLabel: (iso) => {
    const d = new Date(iso + "T00:00:00");
    return d.toLocaleDateString("en-SG", { month: "long", year: "numeric" });
  },
  miles: (n) => Math.round(n).toLocaleString("en-SG"),
};

// Icon component — using inline SVGs (lucide-style)
function Icon({ name, size = 16, stroke = 1.75, className = "", style = {} }) {
  const props = {
    width: size,
    height: size,
    viewBox: "0 0 24 24",
    fill: "none",
    stroke: "currentColor",
    strokeWidth: stroke,
    strokeLinecap: "round",
    strokeLinejoin: "round",
    className,
    style,
  };
  const paths = {
    "home": <><path d="m3 9 9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"/><polyline points="9 22 9 12 15 12 15 22"/></>,
    "list": <><line x1="8" y1="6" x2="21" y2="6"/><line x1="8" y1="12" x2="21" y2="12"/><line x1="8" y1="18" x2="21" y2="18"/><line x1="3" y1="6" x2="3.01" y2="6"/><line x1="3" y1="12" x2="3.01" y2="12"/><line x1="3" y1="18" x2="3.01" y2="18"/></>,
    "plane": <><path d="M17.8 19.2 16 11l3.5-3.5C21 6 21.5 4 21 3c-1-.5-3 0-4.5 1.5L13 8 4.8 6.2c-.5-.1-.9.1-1.1.5l-.3.5c-.2.5-.1 1 .3 1.3L9 12l-2 3H4l-1 1 3 2 2 3 1-1v-3l3-2 3.5 5.3c.3.4.8.5 1.3.3l.5-.2c.4-.3.6-.7.5-1.2z"/></>,
    "card": <><rect width="20" height="14" x="2" y="5" rx="2"/><line x1="2" x2="22" y1="10" y2="10"/></>,
    "wallet": <><path d="M19 7V4a1 1 0 0 0-1-1H5a2 2 0 0 0 0 4h15a1 1 0 0 1 1 1v4h-3a2 2 0 0 0 0 4h3a1 1 0 0 0 1-1v-2a1 1 0 0 0-1-1"/><path d="M3 5v14a2 2 0 0 0 2 2h15a1 1 0 0 0 1-1v-4"/></>,
    "swap": <><path d="m17 3 4 4-4 4"/><path d="M21 7H9"/><path d="m7 21-4-4 4-4"/><path d="M15 17H3"/></>,
    "chart": <><path d="M3 3v18h18"/><path d="m19 9-5 5-4-4-3 3"/></>,
    "settings": <><path d="M12.22 2h-.44a2 2 0 0 0-2 2v.18a2 2 0 0 1-1 1.73l-.43.25a2 2 0 0 1-2 0l-.15-.08a2 2 0 0 0-2.73.73l-.22.38a2 2 0 0 0 .73 2.73l.15.1a2 2 0 0 1 1 1.72v.51a2 2 0 0 1-1 1.74l-.15.09a2 2 0 0 0-.73 2.73l.22.38a2 2 0 0 0 2.73.73l.15-.08a2 2 0 0 1 2 0l.43.25a2 2 0 0 1 1 1.73V20a2 2 0 0 0 2 2h.44a2 2 0 0 0 2-2v-.18a2 2 0 0 1 1-1.73l.43-.25a2 2 0 0 1 2 0l.15.08a2 2 0 0 0 2.73-.73l.22-.39a2 2 0 0 0-.73-2.73l-.15-.08a2 2 0 0 1-1-1.74v-.5a2 2 0 0 1 1-1.74l.15-.09a2 2 0 0 0 .73-2.73l-.22-.38a2 2 0 0 0-2.73-.73l-.15.08a2 2 0 0 1-2 0l-.43-.25a2 2 0 0 1-1-1.73V4a2 2 0 0 0-2-2"/><circle cx="12" cy="12" r="3"/></>,
    "plus": <><path d="M5 12h14"/><path d="M12 5v14"/></>,
    "search": <><circle cx="11" cy="11" r="8"/><path d="m21 21-4.35-4.35"/></>,
    "arrow-right": <><path d="M5 12h14"/><path d="m12 5 7 7-7 7"/></>,
    "arrow-up": <><path d="m5 12 7-7 7 7"/><path d="M12 19V5"/></>,
    "arrow-down": <><path d="M12 5v14"/><path d="m19 12-7 7-7-7"/></>,
    "arrow-up-right": <><path d="M7 7h10v10"/><path d="M7 17 17 7"/></>,
    "arrow-down-left": <><path d="M17 7 7 17"/><path d="M17 17H7V7"/></>,
    "trending-up": <><polyline points="22 7 13.5 15.5 8.5 10.5 2 17"/><polyline points="16 7 22 7 22 13"/></>,
    "trending-down": <><polyline points="22 17 13.5 8.5 8.5 13.5 2 7"/><polyline points="16 17 22 17 22 11"/></>,
    "check": <><polyline points="20 6 9 17 4 12"/></>,
    "x": <><path d="M18 6 6 18"/><path d="m6 6 12 12"/></>,
    "info": <><circle cx="12" cy="12" r="10"/><line x1="12" y1="16" x2="12" y2="12"/><line x1="12" y1="8" x2="12.01" y2="8"/></>,
    "alert": <><path d="m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3"/><line x1="12" y1="9" x2="12" y2="13"/><line x1="12" y1="17" x2="12.01" y2="17"/></>,
    "sparkle": <><path d="M9.937 15.5A2 2 0 0 0 8.5 14.063l-6.135-1.582a.5.5 0 0 1 0-.962L8.5 9.936A2 2 0 0 0 9.937 8.5l1.582-6.135a.5.5 0 0 1 .963 0L14.063 8.5A2 2 0 0 0 15.5 9.937l6.135 1.581a.5.5 0 0 1 0 .964L15.5 14.063a2 2 0 0 0-1.437 1.437l-1.582 6.135a.5.5 0 0 1-.963 0z"/></>,
    "filter": <><polygon points="22 3 2 3 10 12.46 10 19 14 21 14 12.46 22 3"/></>,
    "calendar": <><rect width="18" height="18" x="3" y="4" rx="2"/><path d="M16 2v4"/><path d="M8 2v4"/><path d="M3 10h18"/></>,
    "utensils": <><path d="M3 2v7c0 1.1.9 2 2 2h4a2 2 0 0 0 2-2V2"/><path d="M7 2v20"/><path d="M21 15V2v0a5 5 0 0 0-5 5v6c0 1.1.9 2 2 2h3Zm0 0v7"/></>,
    "home-2": <><path d="m3 9 9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"/></>,
    "dumbbell": <><path d="M14.4 14.4 9.6 9.6"/><path d="M18.657 21.485a2 2 0 1 1-2.829-2.828l-1.767 1.768a2 2 0 1 1-2.829-2.829l6.364-6.364a2 2 0 1 1 2.829 2.829l-1.768 1.767a2 2 0 1 1 2.828 2.829z"/><path d="m21.5 21.5-1.4-1.4"/><path d="M3.9 3.9 2.5 2.5"/><path d="M6.404 12.768a2 2 0 1 1-2.829-2.829l1.768-1.767a2 2 0 1 1-2.828-2.829l2.828-2.828a2 2 0 1 1 2.829 2.828l1.767-1.768a2 2 0 1 1 2.829 2.829z"/></>,
    "cart": <><circle cx="8" cy="21" r="1"/><circle cx="19" cy="21" r="1"/><path d="M2.05 2.05h2l2.66 12.42a2 2 0 0 0 2 1.58h9.78a2 2 0 0 0 1.95-1.57l1.65-7.43H5.12"/></>,
    "shopping-bag": <><path d="M6 2 3 6v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2V6l-3-4Z"/><path d="M3 6h18"/><path d="M16 10a4 4 0 0 1-8 0"/></>,
    "car": <><path d="M19 17h2c.6 0 1-.4 1-1v-3c0-.9-.7-1.7-1.5-1.9C18.7 10.6 16 10 16 10s-1.3-1.4-2.2-2.3c-.5-.4-1.1-.7-1.8-.7H5c-.6 0-1.1.4-1.4.9l-1.4 2.9A3.7 3.7 0 0 0 2 12v4c0 .6.4 1 1 1h2"/><circle cx="7" cy="17" r="2"/><path d="M9 17h6"/><circle cx="17" cy="17" r="2"/></>,
    "film": <><rect width="18" height="18" x="3" y="3" rx="2"/><path d="M7 3v18"/><path d="M3 7.5h4"/><path d="M3 12h18"/><path d="M3 16.5h4"/><path d="M17 3v18"/><path d="M17 7.5h4"/><path d="M17 16.5h4"/></>,
    "circle": <><circle cx="12" cy="12" r="10"/></>,
    "more": <><circle cx="12" cy="12" r="1"/><circle cx="19" cy="12" r="1"/><circle cx="5" cy="12" r="1"/></>,
    "external": <><path d="M15 3h6v6"/><path d="M10 14 21 3"/><path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6"/></>,
    "moon": <><path d="M12 3a6 6 0 0 0 9 9 9 9 0 1 1-9-9Z"/></>,
    "sun": <><circle cx="12" cy="12" r="4"/><path d="M12 2v2"/><path d="M12 20v2"/><path d="m4.93 4.93 1.41 1.41"/><path d="m17.66 17.66 1.41 1.41"/><path d="M2 12h2"/><path d="M20 12h2"/><path d="m6.34 17.66-1.41 1.41"/><path d="m19.07 4.93-1.41 1.41"/></>,
    "bell": <><path d="M6 8a6 6 0 0 1 12 0c0 7 3 9 3 9H3s3-2 3-9"/><path d="M10.3 21a1.94 1.94 0 0 0 3.4 0"/></>,
    "menu": <><line x1="4" x2="20" y1="12" y2="12"/><line x1="4" x2="20" y1="6" y2="6"/><line x1="4" x2="20" y1="18" y2="18"/></>,
    "chevron-right": <><path d="m9 18 6-6-6-6"/></>,
    "chevron-down": <><path d="m6 9 6 6 6-6"/></>,
    "edit": <><path d="M12 3H5a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7"/><path d="M18.375 2.625a1 1 0 0 1 3 3l-9.013 9.014a2 2 0 0 1-.853.505l-2.873.84a.5.5 0 0 1-.62-.62l.84-2.873a2 2 0 0 1 .506-.852z"/></>,
    "tag": <><path d="M12.586 2.586A2 2 0 0 0 11.172 2H4a2 2 0 0 0-2 2v7.172a2 2 0 0 0 .586 1.414l8.704 8.704a2.426 2.426 0 0 0 3.42 0l6.58-6.58a2.426 2.426 0 0 0 0-3.42z"/><circle cx="7.5" cy="7.5" r=".5" fill="currentColor"/></>,
    "repeat": <><path d="m17 2 4 4-4 4"/><path d="M3 11v-1a4 4 0 0 1 4-4h14"/><path d="m7 22-4-4 4-4"/><path d="M21 13v1a4 4 0 0 1-4 4H3"/></>,
    "users": <><path d="M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2"/><circle cx="9" cy="7" r="4"/><path d="M22 21v-2a4 4 0 0 0-3-3.87"/><path d="M16 3.13a4 4 0 0 1 0 7.75"/></>,
    "target": <><circle cx="12" cy="12" r="10"/><circle cx="12" cy="12" r="6"/><circle cx="12" cy="12" r="2"/></>,
    "zap": <><path d="M4 14a1 1 0 0 1-.78-1.63l9.9-10.2a.5.5 0 0 1 .86.46l-1.92 6.02A1 1 0 0 0 13 10h7a1 1 0 0 1 .78 1.63l-9.9 10.2a.5.5 0 0 1-.86-.46l1.92-6.02A1 1 0 0 0 11 14z"/></>,
    "log-out": <><path d="M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4"/><polyline points="16 17 21 12 16 7"/><line x1="21" y1="12" x2="9" y2="12"/></>,
    "lock": <><rect width="18" height="11" x="3" y="11" rx="2" ry="2"/><path d="M7 11V7a5 5 0 0 1 10 0v4"/></>,
    "mail": <><rect width="20" height="16" x="2" y="4" rx="2"/><path d="m22 7-8.97 5.7a1.94 1.94 0 0 1-2.06 0L2 7"/></>,
  };
  return <svg {...props}>{paths[name] || paths["circle"]}</svg>;
}

// Card brand logo placeholders
function CardBrand({ network, size = 24 }) {
  const styles = {
    Visa: { bg: "transparent", text: "#fff", font: "italic 700 0.75em Georgia, serif", label: "VISA" },
    Mastercard: { bg: "transparent", custom: "mastercard" },
    Amex: { bg: "transparent", text: "#fff", font: "700 0.55em system-ui", label: "AMERICAN\nEXPRESS" },
  };
  if (network === "Mastercard") {
    return (
      <svg width={size * 1.6} height={size} viewBox="0 0 40 24">
        <circle cx="15" cy="12" r="9" fill="#eb001b" opacity="0.95"/>
        <circle cx="25" cy="12" r="9" fill="#f79e1b" opacity="0.95"/>
        <path d="M20 5.5a9 9 0 0 1 0 13 9 9 0 0 1 0-13z" fill="#ff5f00"/>
      </svg>
    );
  }
  const s = styles[network] || styles.Visa;
  return (
    <div style={{ font: s.font, color: s.text, letterSpacing: "0.02em", lineHeight: 0.9, textAlign: "right", whiteSpace: "pre" }}>
      {s.label}
    </div>
  );
}

// Helper: get card by id
function getCard(id) { return window.FIN_DATA.CARDS.find(c => c.id === id); }
function getCategory(id) { return window.FIN_DATA.CATEGORIES.find(c => c.id === id); }
function getAccount(id) { return window.FIN_DATA.ACCOUNTS.find(a => a.id === id); }

// Compute miles earned on a card given current spend.
// Handles each card's real reward structure.
function computeMiles(card) {
  if (!card || !card.bonus) return { miles: 0, bonusMiles: 0, baseMiles: 0, segments: [] };
  const b = card.bonus;
  const spent = card.spent || 0;

  // KrisFlyer: SQ-related = 3mpd flat; accelerated 2.4mpd unlocks after $1k SQ spend; else base 1.2
  if (b.kind === "krisflyer") {
    const sqSpent = card.sqSpent || 0;
    const otherSpent = Math.max(0, spent - sqSpent);
    const sqUnlocked = sqSpent >= (b.sqGateSgd || 1000);
    const sqMiles = sqSpent * b.rate;
    const acceleratedRate = sqUnlocked ? b.acceleratedRate : b.baseRate;
    const otherMiles = otherSpent * acceleratedRate;
    return {
      miles: sqMiles + otherMiles,
      bonusMiles: sqMiles + (sqUnlocked ? otherSpent * (b.acceleratedRate - b.baseRate) : 0),
      baseMiles: otherSpent * b.baseRate,
      segments: [
        { label: "SQ-related @ 3mpd", spent: sqSpent, miles: sqMiles },
        { label: sqUnlocked ? "Other @ 2.4mpd" : `Other @ 1.2mpd (need S$${(b.sqGateSgd - sqSpent).toFixed(0)} more SQ to unlock 2.4)`, spent: otherSpent, miles: otherMiles },
      ],
    };
  }

  // PRVI: tiered by region; no cap
  if (b.kind === "prvi") {
    const local = card.localSpent || 0;
    const sea = card.seaSpent || 0;
    const other = card.otherOverseasSpent || 0;
    const hotel = card.hotelSpent || 0;
    const localM = local * b.baseRate;
    const seaM = sea * b.seaRate;
    const otherM = other * b.otherOverseasRate;
    const hotelM = hotel * b.hotelBookingRate;
    return {
      miles: localM + seaM + otherM + hotelM,
      bonusMiles: seaM + otherM + hotelM,
      baseMiles: localM,
      segments: [
        { label: "Local @ 1.4mpd", spent: local, miles: localM },
        { label: "SEA (MY/ID/TH/VN) @ 3mpd", spent: sea, miles: seaM },
        { label: "Other overseas @ 2.4mpd", spent: other, miles: otherM },
        { label: "Agoda/Expedia up to 8mpd", spent: hotel, miles: hotelM },
      ],
    };
  }

  // PPV: split caps — $600 contactless + $600 online @ 4mpd, rest @ base
  if (b.kind === "ppv") {
    const cl = card.contactlessSpent || 0;
    const on = card.onlineSpent || 0;
    const clBonus = Math.min(cl, b.contactlessCapSgd);
    const clOver = Math.max(0, cl - b.contactlessCapSgd);
    const onBonus = Math.min(on, b.onlineCapSgd);
    const onOver = Math.max(0, on - b.onlineCapSgd);
    const bonusMiles = (clBonus + onBonus) * b.rate;
    const baseMiles = (clOver + onOver) * b.baseRate;
    return {
      miles: bonusMiles + baseMiles,
      bonusMiles,
      baseMiles,
      segments: [
        { label: `Contactless @ 4mpd (cap S$${b.contactlessCapSgd})`, spent: cl, cap: b.contactlessCapSgd, miles: clBonus * b.rate + clOver * b.baseRate },
        { label: `Online @ 4mpd (cap S$${b.onlineCapSgd})`, spent: on, cap: b.onlineCapSgd, miles: onBonus * b.rate + onOver * b.baseRate },
      ],
    };
  }

  // Maribank: 1.5% cashback on overseas only, S$1500 cap
  if (b.kind === "cashback-overseas") {
    const overseas = card.overseasSpent || 0;
    const inBonus = Math.min(overseas, b.capSgd);
    return {
      miles: 0, bonusMiles: 0, baseMiles: 0,
      cashback: inBonus * (b.rate / 100),
      segments: [{ label: `Overseas @ ${b.rate}%`, spent: overseas, cap: b.capSgd }],
    };
  }

  // Default: simple capped bonus + base on overflow (Lady's, Citi Rewards, Trust)
  if (!b.capSgd) {
    return { miles: spent * b.rate, bonusMiles: 0, baseMiles: spent * b.rate, segments: [] };
  }
  const inBonus = Math.min(spent, b.capSgd);
  const overflow = Math.max(0, spent - b.capSgd);
  const bonusMiles = inBonus * b.rate;
  const baseMiles = overflow * b.baseRate;
  return { miles: bonusMiles + baseMiles, bonusMiles, baseMiles, segments: [] };
}

// Sum helpers — now use dynamic current month (was hardcoded "2026-04" before)
function sumExpensesThisMonth() {
  const prefix = thisMonthPrefix();
  return (window.FIN_DATA.TRANSACTIONS || [])
    .filter(t => t.type === "expense" && t.date.startsWith(prefix))
    .reduce((s, t) => s + Number(t.amount), 0);
}

function sumIncomeThisMonth() {
  const prefix = thisMonthPrefix();
  return (window.FIN_DATA.TRANSACTIONS || [])
    .filter(t => t.type === "income" && t.date.startsWith(prefix))
    .reduce((s, t) => s + Number(t.amount), 0);
}

function sumByCategory() {
  const prefix = thisMonthPrefix();
  const out = {};
  (window.FIN_DATA.TRANSACTIONS || [])
    .filter(t => t.type === "expense" && t.date.startsWith(prefix))
    .forEach(t => { out[t.category] = (out[t.category] || 0) + Number(t.amount); });
  return out;
}

function sumByCard() {
  const prefix = thisMonthPrefix();
  const out = {};
  (window.FIN_DATA.TRANSACTIONS || [])
    .filter(t => t.type === "expense" && t.date.startsWith(prefix) && t.cardId)
    .forEach(t => { out[t.cardId] = (out[t.cardId] || 0) + Number(t.amount); });
  return out;
}

Object.assign(window, {
  fmt, Icon, CardBrand, getCard, getCategory, getAccount, computeMiles,
  sumExpensesThisMonth, sumIncomeThisMonth, sumByCategory, sumByCard,
  thisMonthPrefix, lastMonthPrefix,
});
