/* Shared UI primitives — icons, Modal, sparkline */

const Icon = ({ name, size = 18, ...rest }) => {
  const icons = {
    dashboard: <><rect x="3" y="3" width="7" height="9" rx="1.5"/><rect x="14" y="3" width="7" height="5" rx="1.5"/><rect x="14" y="12" width="7" height="9" rx="1.5"/><rect x="3" y="16" width="7" height="5" rx="1.5"/></>,
    ledger: <><rect x="4" y="3" width="16" height="18" rx="2"/><path d="M8 8h8M8 12h8M8 16h5"/></>,
    reports: <><path d="M4 19V5M20 19H4"/><path d="M7 15l3-3 3 2 5-6"/></>,
    settings: <><circle cx="12" cy="12" r="3"/><path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 1 1-2.83 2.83l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 1 1-4 0v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 1 1-2.83-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 1 1 0-4h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 1 1 2.83-2.83l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 1 1 4 0v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 1 1 2.83 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 1 1 0 4h-.09a1.65 1.65 0 0 0-1.51 1z"/></>,
    search: <><circle cx="11" cy="11" r="7"/><path d="M21 21l-4.3-4.3"/></>,
    plus: <><path d="M12 5v14M5 12h14"/></>,
    download: <><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4M7 10l5 5 5-5M12 15V3"/></>,
    upload: <><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4M17 8l-5-5-5 5M12 3v12"/></>,
    arrowUp: <><path d="M7 17 17 7M7 7h10v10"/></>,
    arrowDown: <><path d="M17 7 7 17M17 17H7V7"/></>,
    chevronDown: <><path d="m6 9 6 6 6-6"/></>,
    chevronRight: <><path d="m9 6 6 6-6 6"/></>,
    chevronLeft: <><path d="m15 6-6 6 6 6"/></>,
    close: <><path d="M18 6 6 18M6 6l12 12"/></>,
    check: <><path d="M20 6 9 17l-5-5"/></>,
    link: <><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"/><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"/></>,
    sparkles: <><path d="M12 3v4M12 17v4M3 12h4M17 12h4M5.6 5.6l2.8 2.8M15.6 15.6l2.8 2.8M5.6 18.4l2.8-2.8M15.6 8.4l2.8-2.8"/></>,
    filter: <><path d="M3 6h18M6 12h12M10 18h4"/></>,
    receipt: <><path d="M4 4h16v18l-3-2-3 2-3-2-3 2-4-2V4z"/><path d="M8 9h8M8 13h8M8 17h5"/></>,
    bank: <><path d="M3 21h18M5 21V10M19 21V10M3 10l9-6 9 6M9 21v-7M15 21v-7"/></>,
    youtube: <><rect x="2" y="5" width="20" height="14" rx="3"/><path d="m10 9 5 3-5 3z" fill="currentColor"/></>,
    card: <><rect x="2" y="5" width="20" height="14" rx="2"/><path d="M2 10h20"/></>,
    cal: <><rect x="3" y="4" width="18" height="17" rx="2"/><path d="M3 9h18M8 2v4M16 2v4"/></>,
    user: <><circle cx="12" cy="8" r="4"/><path d="M4 20c0-4 4-6 8-6s8 2 8 6"/></>,
    bell: <><path d="M6 8a6 6 0 1 1 12 0c0 7 3 9 3 9H3s3-2 3-9z"/><path d="M10 21a2 2 0 0 0 4 0"/></>,
    sort: <><path d="M7 4v16M7 20l-3-3M7 20l3-3M17 20V4M17 4l-3 3M17 4l3 3"/></>,
    dots: <><circle cx="12" cy="12" r="1"/><circle cx="12" cy="5" r="1"/><circle cx="12" cy="19" r="1"/></>,
    tag: <><path d="M20 12 12 20l-9-9V4h7z"/><circle cx="7" cy="8" r="1.5" fill="currentColor"/></>,
    sliders: <><path d="M4 6h16M4 12h16M4 18h16"/><circle cx="9" cy="6" r="2" fill="var(--bg)"/><circle cx="15" cy="12" r="2" fill="var(--bg)"/><circle cx="7" cy="18" r="2" fill="var(--bg)"/></>,
    sun: <><circle cx="12" cy="12" r="4"/><path d="M12 2v2M12 20v2M4.93 4.93l1.41 1.41M17.66 17.66l1.41 1.41M2 12h2M20 12h2M4.93 19.07l1.41-1.41M17.66 6.34l1.41-1.41"/></>,
    moon: <><path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"/></>,
    bell2: <><path d="M6 8a6 6 0 0 1 12 0c0 7 3 9 3 9H3s3-2 3-9z"/></>,
    refresh: <><path d="M21 12a9 9 0 1 1-3.5-7.1"/><path d="M21 3v6h-6"/></>,
    file: <><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><path d="M14 2v6h6"/></>,
    logout: <><path d="M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4M16 17l5-5-5-5M21 12H9"/></>,
    trash: <><path d="M3 6h18M8 6V4h8v2M19 6l-1 14a2 2 0 0 1-2 2H8a2 2 0 0 1-2-2L5 6M10 11v6M14 11v6"/></>,
  };
  return (
    <svg width={size} height={size} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.7" strokeLinecap="round" strokeLinejoin="round" {...rest}>
      {icons[name]}
    </svg>
  );
};

const Modal = ({ open, onClose, title, subtitle, children, footer, maxWidth = 540 }) => {
  React.useEffect(() => {
    if (!open) return;
    const handler = (e) => { if (e.key === "Escape") onClose(); };
    window.addEventListener("keydown", handler);
    return () => window.removeEventListener("keydown", handler);
  }, [open, onClose]);
  if (!open) return null;
  return (
    <div className="modal-veil" onMouseDown={(e) => { if (e.target === e.currentTarget) onClose(); }}>
      <div className="modal" style={{ maxWidth }}>
        <div className="modal-head">
          <div style={{ display: "flex", alignItems: "start", justifyContent: "space-between", gap: 12 }}>
            <div>
              <h2 className="modal-title">{title}</h2>
              {subtitle && <div className="modal-sub">{subtitle}</div>}
            </div>
            <button className="btn btn-ghost btn-sm" onClick={onClose} aria-label="Close"><Icon name="close" size={16} /></button>
          </div>
        </div>
        <div className="modal-body">{children}</div>
        {footer && <div className="modal-foot">{footer}</div>}
      </div>
    </div>
  );
};

// Pill / badge
const Pill = ({ kind = "", children, dot }) => (
  <span className={"pill " + kind}>
    {dot && <span className="dot" />}
    {children}
  </span>
);

// Sparkline SVG (values 0..1 normalized)
const Sparkline = ({ values, color = "var(--primary)", fill = true, height = 40 }) => {
  if (!values || values.length === 0) return null;
  const max = Math.max(...values);
  const min = Math.min(...values);
  const w = 200, h = height;
  const pad = 2;
  const pts = values.map((v, i) => {
    const x = (i / (values.length - 1)) * (w - pad * 2) + pad;
    const y = h - pad - ((v - min) / Math.max(0.0001, (max - min))) * (h - pad * 2);
    return [x, y];
  });
  const d = pts.map((p, i) => (i === 0 ? "M" : "L") + p[0].toFixed(1) + "," + p[1].toFixed(1)).join(" ");
  const dFill = d + ` L ${w - pad},${h - pad} L ${pad},${h - pad} Z`;
  return (
    <svg className="spark" viewBox={`0 0 ${w} ${h}`} preserveAspectRatio="none">
      {fill && <path d={dFill} fill={color} fillOpacity="0.12" />}
      <path d={d} stroke={color} strokeWidth="1.6" fill="none" strokeLinecap="round" strokeLinejoin="round" />
    </svg>
  );
};

// Big animated bar/line chart for dashboard
// Supports either { months, incomeData, expenseData } (back-compat) or
// { buckets: [{ label, income, expense }, ...] } (new, supports any granularity)
const IncomeExpenseChart = ({ months, incomeData, expenseData, buckets }) => {
  const [hovered, setHovered] = React.useState(null); // index of hovered bucket

  const data = buckets || (months || []).map((m, i) => ({
    label: CB.monthLabel(m, true).split(" ")[0],
    income: incomeData?.[i] || 0,
    expense: expenseData?.[i] || 0,
  }));
  // Hide label every Nth bucket when there are many (e.g. 30 days of daily data)
  const labelEvery = data.length > 14 ? Math.ceil(data.length / 8) : 1;

  const w = 720, h = 240, padL = 44, padR = 12, padT = 12, padB = 28;
  const innerW = w - padL - padR;
  const innerH = h - padT - padB;
  const all = data.flatMap(b => [b.income, b.expense]);
  const max = Math.max(...all, 1) * 1.15;
  const barW = innerW / data.length;
  // Bars get thinner as buckets multiply, but keep a hairline gap.
  const groupW = Math.max(barW * 0.5, 4);
  const halfW = groupW / 2;

  // y-grid ticks
  const ticks = 4;
  const tickVals = [];
  for (let i = 0; i <= ticks; i++) tickVals.push((max / ticks) * i);

  // Tooltip helpers
  const tipW = 158, tipH = 90;
  const fmtTip = (v) => {
    const abs = Math.abs(v);
    const s = abs >= 10000 ? "$" + (abs / 1000).toFixed(1) + "k"
            : abs >= 1000  ? "$" + (abs / 1000).toFixed(2) + "k"
            : "$" + abs.toFixed(2);
    return v < 0 ? "−" + s : s;
  };

  return (
    <svg viewBox={`0 0 ${w} ${h}`} style={{ width: "100%", height: "100%" }}
      onMouseLeave={() => setHovered(null)}>
      <defs>
        <filter id="tip-shadow" x="-20%" y="-20%" width="140%" height="140%">
          <feDropShadow dx="0" dy="2" stdDeviation="3" floodOpacity="0.12" />
        </filter>
      </defs>

      {/* gridlines */}
      {tickVals.map((v, i) => {
        const y = padT + innerH - (v / max) * innerH;
        return (
          <g key={i}>
            <line x1={padL} y1={y} x2={w - padR} y2={y} stroke="var(--hairline)" strokeDasharray={i === 0 ? "" : "2 4"} />
            <text x={padL - 8} y={y + 4} textAnchor="end" fontSize="10" fill="var(--muted)" fontFamily="var(--font-mono)">
              {v >= 1000 ? "$" + (v / 1000).toFixed(0) + "k" : "$" + v.toFixed(0)}
            </text>
          </g>
        );
      })}

      {data.map((b, i) => {
        const x = padL + i * barW + barW / 2;
        const inc = b.income;
        const exp = b.expense;
        const incH = (inc / max) * innerH;
        const expH = (exp / max) * innerH;
        const showLabel = i % labelEvery === 0 || i === data.length - 1;
        const isHov = hovered === i;
        return (
          <g key={i} onMouseEnter={() => setHovered(i)} style={{ cursor: "default" }}>
            {/* invisible wider hit area so hover is easy to trigger */}
            <rect x={x - barW / 2} y={padT} width={barW} height={innerH} fill="transparent" />
            <rect
              x={x - halfW}
              y={padT + innerH - incH}
              width={Math.max(halfW - 1, 1)}
              height={incH}
              fill="var(--income)"
              opacity={isHov ? 1 : 0.85}
              rx={halfW > 4 ? 3 : 1}
            />
            <rect
              x={x + 1}
              y={padT + innerH - expH}
              width={Math.max(halfW - 1, 1)}
              height={expH}
              fill="var(--expense)"
              opacity={isHov ? 0.9 : 0.75}
              rx={halfW > 4 ? 3 : 1}
            />
            {showLabel && (
              <text x={x} y={h - 8} textAnchor="middle" fontSize="10" fill={isHov ? "var(--ink)" : "var(--muted)"} fontWeight={isHov ? "600" : "400"}>
                {b.label}
              </text>
            )}
          </g>
        );
      })}

      {/* Tooltip — rendered last so it sits on top */}
      {hovered !== null && (() => {
        const b = data[hovered];
        const bx = padL + hovered * barW + barW / 2;
        const net = b.income - b.expense;
        // Clamp so tooltip never bleeds past left/right edge
        const tx = Math.max(padL, Math.min(bx - tipW / 2, w - padR - tipW));
        const ty = padT + 4;
        const row = (label, value, color, yo) => (
          <g>
            <text x={tx + 12} y={ty + yo} fontSize="10" fill="var(--muted)">{label}</text>
            <text x={tx + tipW - 12} y={ty + yo} textAnchor="end" fontSize="10.5"
              fontWeight="600" fill={color} fontFamily="var(--font-mono)">
              {fmtTip(value)}
            </text>
          </g>
        );
        return (
          <g style={{ pointerEvents: "none" }} filter="url(#tip-shadow)">
            <rect x={tx} y={ty} width={tipW} height={tipH} rx="8"
              fill="var(--surface)" stroke="var(--hairline)" strokeWidth="1" />
            {/* label */}
            <text x={tx + tipW / 2} y={ty + 18} textAnchor="middle"
              fontSize="11" fontWeight="700" fill="var(--ink)">{b.label}</text>
            <line x1={tx + 10} y1={ty + 24} x2={tx + tipW - 10} y2={ty + 24}
              stroke="var(--hairline)" />
            {row("Income",   b.income,  "var(--income)",  40)}
            {row("Expenses", b.expense, "var(--expense)", 57)}
            <line x1={tx + 10} y1={ty + 63} x2={tx + tipW - 10} y2={ty + 63}
              stroke="var(--hairline)" />
            {row("Net", net, net >= 0 ? "var(--income)" : "var(--expense)", 78)}
          </g>
        );
      })()}
    </svg>
  );
};

// Donut for category breakdown
const Donut = ({ slices, size = 180, thickness = 28 }) => {
  const total = slices.reduce((s, x) => s + x.value, 0) || 1;
  const r = size / 2 - thickness / 2;
  const C = 2 * Math.PI * r;
  let offset = 0;
  return (
    <svg width={size} height={size} viewBox={`0 0 ${size} ${size}`}>
      <circle cx={size/2} cy={size/2} r={r} fill="none" stroke="var(--surface-2)" strokeWidth={thickness} />
      {slices.map((s, i) => {
        const len = (s.value / total) * C;
        const el = (
          <circle key={i}
            cx={size/2} cy={size/2} r={r}
            fill="none"
            stroke={s.color}
            strokeWidth={thickness}
            strokeDasharray={`${len} ${C - len}`}
            strokeDashoffset={-offset}
            transform={`rotate(-90 ${size/2} ${size/2})`}
          />
        );
        offset += len;
        return el;
      })}
    </svg>
  );
};

Object.assign(window, { Icon, Modal, Pill, Sparkline, IncomeExpenseChart, Donut });
