// Shared primitives, hooks, and util components for the CliBank Soft app.
const { useState, useEffect, useMemo, useRef, useCallback, createContext, useContext } = React;

// D is a lazy proxy: components access it during render (after data loads),
// not at module load time. Direct assignment was the original mock-data path.
const D = new Proxy({}, {
  get(_, prop) {
    if (!window.APP_DATA) return undefined;
    return window.APP_DATA[prop];
  },
  has(_, prop) {
    return window.APP_DATA != null && prop in window.APP_DATA;
  },
});
const fmtILS = (n, opts) => (window.APP_DATA?.fmtILS || ((x) => String(x ?? 0)))(n, opts);
const fmtN = (n) => (window.APP_DATA?.fmtN || ((x) => String(x ?? 0)))(n);

// ─── Theme + Tweaks context ───────────────────────────────────────
const AppCtx = createContext(null);
const useApp = () => useContext(AppCtx);

// Count-up hook
function useCountUp(target, duration = 700) {
  const [v, setV] = useState(target);
  const startRef = useRef(target);
  const tRef = useRef(target);
  useEffect(() => {
    if (target === tRef.current) return;
    startRef.current = v;
    tRef.current = target;
    const t0 = performance.now();
    let raf;
    const tick = (now) => {
      const p = Math.min(1, (now - t0) / duration);
      const eased = 1 - Math.pow(1 - p, 3);
      setV(Math.round(startRef.current + (target - startRef.current) * eased));
      if (p < 1) raf = requestAnimationFrame(tick);
    };
    raf = requestAnimationFrame(tick);
    return () => cancelAnimationFrame(raf);
  }, [target, duration]);
  return v;
}

// Currency display with count-up
function Money({ value, size = 16, weight = 600, color, sign = false, dec = 0 }) {
  const v = useCountUp(value);
  const isNeg = value < 0;
  return (
    <span className="tnum" style={{ fontSize: size, fontWeight: weight, letterSpacing: '-0.015em', color }}>
      {sign && value > 0 ? '+' : ''}
      {fmtILS(v, { dec })}
    </span>
  );
}

// Hairline surface card
function Surface({ children, style, padded = true, className = '', onClick }) {
  return (
    <div className={className} onClick={onClick} style={{
      background: 'var(--surface)',
      borderRadius: 'var(--radius-lg)',
      boxShadow: 'var(--shadow-md)',
      padding: padded ? `calc(22px * var(--density))` : 0,
      transition: 'background .25s, box-shadow .25s',
      ...style,
    }}>{children}</div>
  );
}

// Segmented iOS-style
function Segmented({ options, value, onChange }) {
  return (
    <div style={{
      display: 'inline-flex', background: 'var(--bg-deep)', borderRadius: 10, padding: 2,
      fontSize: 12, fontWeight: 500,
    }}>
      {options.map((o, i) => (
        <span key={o.value ?? o} onClick={() => onChange(o.value ?? o)} style={{
          padding: '5px 14px', borderRadius: 8,
          background: (o.value ?? o) === value ? 'var(--surface)' : 'transparent',
          color: (o.value ?? o) === value ? 'var(--ink)' : 'var(--ink-soft)',
          boxShadow: (o.value ?? o) === value ? 'var(--shadow-sm)' : 'none',
          cursor: 'pointer', transition: 'background .15s', userSelect: 'none',
        }}>{o.label ?? o}</span>
      ))}
    </div>
  );
}

// Sparkle / AI badge
function SparkBadge({ size = 30 }) {
  return (
    <div style={{
      width: size, height: size, borderRadius: size * 0.3,
      background: `radial-gradient(circle at 30% 30%, var(--accent), var(--accent-ink))`,
      display: 'flex', alignItems: 'center', justifyContent: 'center',
      color: 'white', fontSize: size * 0.45, fontWeight: 600,
      boxShadow: `0 4px 10px -2px var(--accent)55, 0 0 0 0.5px rgba(0,0,0,0.05)`,
      flexShrink: 0,
    }}>✦</div>
  );
}

// Pace chart — signature visual
function PaceChart({ daily, today, projected, height = 170 }) {
  const days = daily.length;
  const W = 540, H = height, padX = 4, padTop = 30, padBot = 22;
  const innerW = W - padX*2, innerH = H - padTop - padBot;
  const yMax = Math.max(daily[daily.length-1] || 0, projected) * 1.05 || 1;
  const xAt = (i) => padX + (i / (days-1)) * innerW;
  const yAt = (v) => padTop + innerH - (v / yMax) * innerH;
  const actualPts = daily.slice(0, today).map((v, i) => [xAt(i), yAt(v)]);
  const actualD = actualPts.map((p, i) => (i === 0 ? `M${p[0]},${p[1]}` : `L${p[0]},${p[1]}`)).join(' ');
  const fillD = actualPts.length > 0
    ? `${actualD} L${actualPts[actualPts.length-1][0]},${padTop+innerH} L${actualPts[0][0]},${padTop+innerH} Z`
    : '';
  const paceD = `M${xAt(0)},${yAt(0)} L${xAt(days-1)},${yAt(projected)}`;
  const cur = actualPts[actualPts.length - 1] || [padX, padTop+innerH];
  return (
    <svg width="100%" height={H} viewBox={`0 0 ${W} ${H}`} style={{ display: 'block', direction: 'ltr' }}>
      <defs>
        <linearGradient id="pace-fill" x1="0" y1="0" x2="0" y2="1">
          <stop offset="0%" stopColor="currentColor" stopOpacity="0.15" />
          <stop offset="100%" stopColor="currentColor" stopOpacity="0" />
        </linearGradient>
      </defs>
      <line x1={padX} x2={W-padX} y1={padTop+innerH} y2={padTop+innerH} stroke="var(--rule)" strokeWidth="0.5" />
      <path d={paceD} fill="none" stroke="var(--ink-faint)" strokeWidth="1.5" strokeDasharray="3 4" strokeLinecap="round" />
      <path d={fillD} fill="url(#pace-fill)" style={{ color: 'var(--ink)' }} />
      <path d={actualD} fill="none" stroke="var(--ink)" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" />
      <circle cx={cur[0]} cy={cur[1]} r="6" fill="var(--surface)" stroke="var(--ink)" strokeWidth="2" />
      <circle cx={cur[0]} cy={cur[1]} r="2.5" fill="var(--ink)" />
      <circle cx={xAt(days-1)} cy={yAt(projected)} r="4" fill="var(--surface)" stroke="var(--ink-faint)" strokeWidth="1.5" strokeDasharray="2 2" />
      {[1, 8, 15, 22, 31].filter(d => d <= days).map(d => (
        <text key={d} x={xAt(d-1)} y={H-6} textAnchor="middle"
              fontSize="10" fill="var(--ink-soft)" fontFamily="var(--font-mono)">{d}</text>
      ))}
      <line x1={cur[0]} x2={cur[0]} y1={padTop} y2={padTop+innerH}
            stroke="var(--ink)" strokeWidth="0.5" strokeDasharray="2 3" opacity="0.3" />
    </svg>
  );
}

// 12-month area chart
function MultiMonthChart({ months, height = 140, accent = 'var(--accent)' }) {
  const values = months.map(m => m.summary.expenses);
  const max = Math.max(...values) * 1.1;
  const min = Math.min(...values) * 0.85;
  const range = max - min || 1;
  const W = 600, H = height, padX = 8, padTop = 8, padBot = 22;
  const innerW = W - padX*2, innerH = H - padTop - padBot;
  const step = innerW / (values.length - 1 || 1);
  const pts = values.map((v, i) => [padX + i * step, padTop + innerH - ((v - min) / range) * innerH]);
  const d = pts.map((p, i) => (i === 0 ? `M${p[0]},${p[1]}` : `L${p[0]},${p[1]}`)).join(' ');
  const fillD = `${d} L${pts[pts.length-1][0]},${padTop+innerH} L${pts[0][0]},${padTop+innerH} Z`;
  return (
    <svg width="100%" height={H} viewBox={`0 0 ${W} ${H}`} style={{ display: 'block', direction: 'ltr' }}>
      <defs>
        <linearGradient id="mm-grad" x1="0" y1="0" x2="0" y2="1">
          <stop offset="0%" stopColor={accent} stopOpacity="0.22" />
          <stop offset="100%" stopColor={accent} stopOpacity="0" />
        </linearGradient>
      </defs>
      {[0, 0.5, 1].map((t, i) => (
        <line key={i} x1={padX} x2={W-padX} y1={padTop+innerH*t} y2={padTop+innerH*t}
              stroke="var(--rule)" strokeWidth="0.5" strokeDasharray="2 3" />
      ))}
      <path d={fillD} fill="url(#mm-grad)" />
      <path d={d} fill="none" stroke={accent} strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" />
      {pts.map((p, i) => (
        <circle key={i} cx={p[0]} cy={p[1]} r={i === pts.length-1 ? 4 : 2} fill={accent} />
      ))}
      {months.map((m, i) => i % 2 === 0 && (
        <text key={i} x={pts[i][0]} y={H-6} textAnchor="middle"
              fontSize="10" fill="var(--ink-soft)" fontFamily="var(--font-mono)">{m.shortLabel}</text>
      ))}
    </svg>
  );
}

// Daily-pace data helper
function useDailyPace(month, today) {
  return useMemo(() => {
    const days = 31;
    const arr = new Array(days).fill(0);
    month.txns.filter(t => t.amount < 0).forEach(t => {
      const d = parseInt(t.date.slice(8), 10) - 1;
      if (d >= 0 && d < days) arr[d] += -t.amount;
    });
    let acc = 0;
    return arr.map(v => acc += v);
  }, [month]);
}

// Skeleton helpers
function SkeletonRow({ height = 14, width = '100%', mb = 8 }) {
  return <div className="skeleton" style={{ height, width, marginBottom: mb }} />;
}

// Empty state
function EmptyState({ icon = '◌', title, sub, action }) {
  return (
    <div style={{
      padding: '40px 28px', textAlign: 'center',
      display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 8,
    }}>
      <div style={{
        width: 56, height: 56, borderRadius: 16, background: 'var(--bg-deep)',
        display: 'flex', alignItems: 'center', justifyContent: 'center',
        fontSize: 22, color: 'var(--ink-soft)', marginBottom: 4,
      }}>{icon}</div>
      <div style={{ fontSize: 15, fontWeight: 600 }}>{title}</div>
      {sub && <div style={{ fontSize: 12.5, color: 'var(--ink-soft)', maxWidth: 300, lineHeight: 1.5 }}>{sub}</div>}
      {action}
    </div>
  );
}

// Hebrew month formatter
const MONTH_NAMES = ['ינואר','פברואר','מרץ','אפריל','מאי','יוני','יולי','אוגוסט','ספטמבר','אוקטובר','נובמבר','דצמבר'];
const fmtDate = (s, opts = {}) => {
  const d = new Date(s);
  const day = d.getDate();
  const m = MONTH_NAMES[d.getMonth()];
  if (opts.short) return `${day}.${String(d.getMonth()+1).padStart(2,'0')}`;
  return `${day} ב${m}`;
};

Object.assign(window, {
  useApp, AppCtx, useCountUp, Money, Surface, Segmented, SparkBadge,
  PaceChart, MultiMonthChart, useDailyPace, SkeletonRow, EmptyState,
  fmtDate, MONTH_NAMES, D, fmtILS, fmtN,
});
