import { useMemo, useRef, useState } from 'react';
import { Link, router } from '@inertiajs/react';
import {
    ArrowUpRight, Building2, Cake, CalendarCheck, ClipboardCheck, ClipboardList, Clock, FileText, FlaskConical, Info,
    LineChart, ListOrdered, Package, Target, TrendingDown, TrendingUp, Trophy, User, Wallet,
} from 'lucide-react';
import { Card } from '@/Components/ui/card';

/**
 * Real dashboard widgets — every figure arrives via props from DashboardWidgetService (the
 * viewer's OWN data). Pure presentation: no fetching, no dummy fallbacks — an empty list
 * renders an honest "Belum ada data".
 *
 * Colors are design tokens only (`bg-card`, `text-primary`, `var(--color-*)` in SVG) so
 * dark mode flips everything from one switch — no proto PALETTE hex here. The donut palette
 * is the shadcn `--color-chart-*` set, which app.css already maps to brand tokens.
 */

/**
 * The card-header icon tile. ONE definition, and every colour in it is a token.
 *
 * The hardcoded form it replaces — `border-violet-100 bg-white text-violet-600` — broke the design
 * system twice over: `bg-white` is a literal, so the tile stayed white on a dark card instead of
 * flipping with the theme, and `violet-600` is not this app's brand violet (#57008b / #c89cff in
 * dark), so the icons drifted a shade off every other primary mark on the page.
 */
/** Approval queues shown before the "Lihat semua" toggle. */
const VISIBLE_APPROVALS = 6;

const TILE_SKIN = 'border border-primary/20 bg-card text-primary shadow-xs';
const HEAD_TILE = `grid size-8 shrink-0 place-items-center rounded-lg ${TILE_SKIN}`;

const MONTHS = ['Jan', 'Feb', 'Mar', 'Apr', 'Mei', 'Jun', 'Jul', 'Agu', 'Sep', 'Okt', 'Nov', 'Des'];
// Full month names + weekday heads for BirthdaysCard's calendar grid. Separate from MONTHS above,
// which is the abbreviated set the 12-month revenue trend axis uses.
const MONTH_FULL_ID = ['Januari', 'Februari', 'Maret', 'April', 'Mei', 'Juni', 'Juli', 'Agustus', 'September', 'Oktober', 'November', 'Desember'];
const WEEKDAY_ID = ['Sen', 'Sel', 'Rab', 'Kam', 'Jum', 'Sab', 'Min'];

/**
 * Every money figure on this board is IDR, exactly as stored, with nothing converted anywhere
 * (user decision 2026-08-24). `netsuiteinvoicedetail.TotalRevenue` is rupiah and has no currency
 * column; the recent-quotation feed carries the document's own `TotalIDR`.
 *
 * ⛔ Do NOT reintroduce a rate, in either direction. Until 2026-08-22 the board divided EVERY
 * figure by one flat `USD_IDR_RATE` (config/currency.php, default 16,000) carried down as a
 * `MoneyProvider` context — one made-up number applied to six years of history, so the "USD" it
 * printed was not a conversion, it was a wrong figure. The config file, the `currency` prop and
 * the context are DELETED, not set to zero. If USD is ever wanted it has to come from the rate on
 * each individual row, never from one number for the whole board.
 *
 * The context went with the rate: with a single fixed currency there is no board-wide value left
 * to share, so this is a plain module constant. Nothing to provide, nothing to forget to wrap.
 */
const compact = (n) => {
    if (Math.abs(n) >= 1e9) return `IDR ${(n / 1e9).toFixed(1)}B`;
    if (Math.abs(n) >= 1e6) return `IDR ${(n / 1e6).toFixed(1)}M`;
    if (Math.abs(n) >= 1e3) return `IDR ${(n / 1e3).toFixed(0)}K`;
    return `IDR ${n.toLocaleString('en-US', { maximumFractionDigits: 0 })}`;
};

export const money = {
    /** Card-sized: "IDR 12.3B". Magnitude only — `full` is what the tooltips carry. */
    short: (v) => compact(Number(v) || 0),
    /**
     * Tooltip-sized: every rupiah, grouped, and NO decimals. `TotalRevenue` is decimal(20,5) and
     * `TotalIDR` decimal(20,2), but nothing here is priced in sen — a real figure is IDR
     * 3,481,905,772, and printing ".00" after ten digits only makes it harder to read.
     */
    full: (v) => `IDR ${(Number(v) || 0).toLocaleString('en-US', { maximumFractionDigits: 0 })}`,
};

const fmtDate = (s) => {
    if (!s) return '—';
    const [y, m, d] = String(s).slice(0, 10).split('-').map(Number);
    return m >= 1 && m <= 12 ? `${d} ${MONTHS[m - 1]} ${y}` : String(s).slice(0, 10);
};

// ── Scope switcher — only rendered for users who actually hold more than one scope ──────────

const SCOPE_ICON = { sales: User, sm: Building2, pm: Package };

/**
 * Sales / Divisi Saya / Principal Saya. The server decides which options exist (a scope the user
 * does not head is never offered, and a hand-typed one degrades back to `sales`), so this is a
 * plain partial reload — no client-side authority over what the viewer may see.
 */
export function ScopeSwitcher({ scope }) {
    if (!scope || scope.options.length < 2) return null;

    const go = (id) => {
        if (id === scope.mode) return;
        router.get(route('dashboard'), { scope: id }, {
            only: ['scope', 'kpis', 'revenueTrend', 'recent', 'podium'],
            preserveState: true,
            preserveScroll: true,
            replace: true,
        });
    };

    return (
        <div className="flex rounded-lg border border-border p-0.5">
            {scope.options.map((o) => {
                const Icon = SCOPE_ICON[o.id] ?? User;
                const active = o.id === scope.mode;

                return (
                    <button
                        key={o.id}
                        type="button"
                        title={o.caption}
                        onClick={() => go(o.id)}
                        className={`inline-flex items-center gap-1.5 rounded-md px-2.5 py-1 text-[11px] font-bold transition-colors ${active ? 'bg-accent text-primary' : 'text-muted-foreground hover:text-foreground'}`}
                    >
                        <Icon className="size-3.5" aria-hidden="true" />
                        {o.label}
                    </button>
                );
            })}
        </div>
    );
}

/**
 * One line saying whose figures these are, plus the honest note when the money source is partial.
 *
 * ⛔ The rate pill that used to sit here ("Kurs: USD 1 = IDR 16,000") was REMOVED by user decision
 * on 2026-08-11, and the rate itself was deleted on 2026-08-22. Do not restore either: the board
 * is IDR end to end now (user decision 2026-08-24), so there is nothing to state a rate for.
 *
 * `scope.nsNote` is BACK, and it has to be. The money reads `netsuiteinvoicedetail`, whose keys
 * reach 8 of 104 users and 12 of 126 principals — so most viewers are shown a structural zero, and
 * an unexplained Rp 0 is indistinguishable from "you sold nothing this month". The banner is the
 * difference between a partial figure and a lie. It renders only when the server sends a note.
 */
export function ScopeBar({ scope }) {
    if (!scope) return null;
    const Icon = SCOPE_ICON[scope.mode] ?? User;

    return (
        <div className="flex flex-col gap-1.5">
            <p className="m-0 flex flex-wrap items-center gap-1.5 text-[11.5px] text-muted-foreground">
                <Icon className="size-3.5 shrink-0" aria-hidden="true" />
                <span className="truncate" title={scope.label}>{scope.label}</span>
            </p>
            {scope.nsNote && (
                <p className="m-0 flex items-start gap-1.5 rounded-lg bg-warning-bg px-3 py-1.5 text-[11.5px] font-medium text-warning-text">
                    <Info className="mt-px size-3.5 shrink-0" aria-hidden="true" />
                    {scope.nsNote}
                </p>
            )}
        </div>
    );
}

// ── KPI strip — one slim card, four stats with per-stat icon tint ───────────────────────────

// All four KPI tiles wear the SAME skin as the card-header tiles — outlined brand violet on the
// card colour. They used to be violet / green / amber / blue, which read as four STATUS colours:
// green on "Win Rate" and amber on "Pending Approval" implied a verdict the number never gave. The
// tile says what the figure is ABOUT; the delta chip beside the value is what carries good-or-bad.
const KPI_TINTS = { omset: TILE_SKIN, win: TILE_SKIN, pending: TILE_SKIN, visits: TILE_SKIN };

export function KpiStrip({ kpis }) {
    const delta = kpis.omzetMtd.momPct;
    const up = (delta ?? 0) >= 0;
    const stats = [
        {
            label: 'Omset (MTD)',
            value: money.short(kpis.omzetMtd.value),
            icon: Wallet,
            tint: KPI_TINTS.omset,
            chip: delta != null && (
                <span className={`inline-flex items-center gap-0.5 rounded-full px-1.5 py-0.5 text-[10px] font-bold ${up ? 'bg-success-bg text-success-text' : 'bg-danger-bg text-danger-text'}`}>
                    {up ? <TrendingUp className="size-2.5" /> : <TrendingDown className="size-2.5" />}
                    {up ? '+' : ''}{delta}%
                </span>
            ),
            caption: 'vs bulan lalu',
        },
        {
            label: 'Win Rate',
            value: kpis.winRate.pct != null ? `${kpis.winRate.pct}%` : '—',
            icon: Target,
            tint: KPI_TINTS.win,
            caption: `${kpis.winRate.won}/${kpis.winRate.decided} quotation diputuskan`,
        },
        { label: 'Pending Approval', value: kpis.pendingApproval, icon: Clock, tint: KPI_TINTS.pending, caption: 'quotation menunggu SM/PM' },
        { label: 'Visit Minggu Ini', value: kpis.visitsThisWeek, icon: CalendarCheck, tint: KPI_TINTS.visits, caption: 'jadwal kunjungan' },
    ];

    return (
        <Card className="grid grid-cols-2 gap-0 p-0 transition-shadow duration-200 hover:shadow-md lg:grid-cols-4 lg:divide-x lg:divide-border/50">
            {stats.map((s) => (
                <div key={s.label} className="flex items-center gap-3 rounded-xl px-5 py-4 transition-colors hover:bg-secondary/30">
                    <span className={`grid size-9 shrink-0 place-items-center rounded-lg ${s.tint}`}>
                        <s.icon className="size-4" aria-hidden="true" />
                    </span>
                    <div className="min-w-0">
                        <p className="m-0 truncate text-[10px] font-extrabold uppercase tracking-[0.06em] text-muted-foreground">{s.label}</p>
                        <p className="m-0 flex items-center gap-1.5 text-[18px] font-extrabold leading-tight tracking-tight text-foreground">
                            <span className="tabular-nums">{s.value}</span>
                            {s.chip}
                        </p>
                        <p className="m-0 truncate text-[10.5px] text-muted-foreground/80">{s.caption}</p>
                    </div>
                </div>
            ))}
        </Card>
    );
}

// ── Omset trend — 12 bulan, SVG token-colored, hover tooltip + window toggle ───────

export function RevenueTrendCard({ trend }) {
    const H = 180;
    const [months, setMonths] = useState(12);
    const [hover, setHover] = useState(null);
    const areaRef = useRef(null);

    const data = months === 6 ? trend.slice(-6) : trend;
    const n = data.length;
    const max = Math.max(...data.map((t) => t.total), 1);
    const nice = Math.ceil(max / Math.pow(10, Math.floor(Math.log10(max)))) * Math.pow(10, Math.floor(Math.log10(max)));
    const py = (v) => H - (v / nice) * H;
    const px = (i) => (n > 1 ? (i / (n - 1)) * 100 : 50);
    const pts = data.map((t, i) => `${px(i) * 10},${py(t.total)}`).join(' ');
    const area = `M0,${H} ${data.map((t, i) => `L${px(i) * 10},${py(t.total)}`).join(' ')} L1000,${H} Z`;
    const ticks = [1, 0.5, 0];
    const windowTotal = data.reduce((a, t) => a + t.total, 0);
    // Every bucket at zero is a REAL state, not a loading one — and on this source it is the
    // COMMON one: the money reads `netsuiteinvoicedetail`, whose keys reach 8 of 104 users, so
    // most viewers have a genuinely flat 12 months. Drawing the chart anyway is worse than saying
    // so — `max` floors at 1 to avoid dividing by zero, which makes the axis print
    // 'IDR 1, IDR 1, IDR 0' and reads as near-zero sales rather than no sales at all.
    const isEmpty = windowTotal === 0;

    const onMove = (e) => {
        const rect = areaRef.current?.getBoundingClientRect();
        if (!rect || rect.width === 0) return;
        const frac = (e.clientX - rect.left) / rect.width;
        setHover(Math.min(n - 1, Math.max(0, Math.round(frac * (n - 1)))));
    };

    const hovered = hover != null ? data[hover] : null;
    const prev = hover != null && hover > 0 ? data[hover - 1] : null;
    const momPct = hovered && prev && prev.total > 0 ? Math.round(((hovered.total - prev.total) / prev.total) * 100) : null;
    // Keep the tooltip inside the card at both ends of the x-axis.
    const tipShift = hover == null ? '-50%' : hover === 0 ? '-8%' : hover === n - 1 ? '-92%' : '-50%';

    return (
        <Card className="flex h-full flex-col gap-0 p-0 transition-shadow duration-200 hover:shadow-md">
            <header className="flex flex-wrap items-center gap-3 border-b border-border/40 px-5 py-3">
                <span className={HEAD_TILE}><LineChart className="size-4" aria-hidden="true" /></span>
                <h2 className="m-0 text-sm font-bold uppercase tracking-wide text-foreground">Omset {months} Bulan</h2>
                <div className="ml-auto flex items-center gap-3">
                    {!isEmpty && (
                        <span className="text-[11px] text-muted-foreground mr-1">
                            Total <span className="font-bold tabular-nums text-foreground">{money.short(windowTotal)}</span>
                        </span>
                    )}
                    <div className="flex rounded-lg border border-border bg-card p-0.5 text-[11px] font-bold shadow-xs">
                        {[6, 12].map((m) => (
                            <button
                                key={m}
                                type="button"
                                onClick={() => { setMonths(m); setHover(null); }}
                                className={`rounded-md px-3.5 py-1 transition-all duration-150 cursor-pointer ${
                                    months === m
                                        ? 'bg-primary text-primary-foreground shadow-xs'
                                        : 'text-muted-foreground hover:text-foreground'
                                }`}
                            >
                                {m}B
                            </button>
                        ))}
                    </div>
                </div>
            </header>
            {isEmpty ? (
                /* Same flex-1 as the chart below, so an empty card still fills the row height the
                   board hands this widget and the board keeps its grid. */
                <div className="flex flex-1 flex-col items-center justify-center gap-1 px-5 py-10 text-center">
                    <span className={HEAD_TILE}><LineChart className="size-4" aria-hidden="true" /></span>
                    <p className="m-0 mt-2 text-sm font-bold text-foreground">Belum ada omset</p>
                    <p className="m-0 max-w-[34ch] text-[11px] leading-relaxed text-muted-foreground">
                        Tidak ada invoice tercatat dalam {months} bulan terakhir.
                    </p>
                </div>
            ) : (
            <>
            {/* flex-1 + absolute SVG: the chart absorbs whatever row height the board gives this
                widget (e.g. matching the taller donut card beside it) instead of leaving a gap. */}
            <div className="flex flex-1 gap-2 px-5 pt-4">
                <div className="flex shrink-0 flex-col justify-between text-right text-[10px] tabular-nums text-muted-foreground">
                    {ticks.map((t) => <span key={t}>{money.short(nice * t)}</span>)}
                </div>
                <div ref={areaRef} className="relative min-h-[180px] min-w-0 flex-1" onMouseMove={onMove} onMouseLeave={() => setHover(null)}>
                    <svg viewBox={`0 0 1000 ${H}`} preserveAspectRatio="none" className="absolute inset-0 size-full">
                        <defs>
                            <linearGradient id="dashTrendFill" x1="0" y1="0" x2="0" y2="1">
                                <stop offset="0%" stopColor="var(--color-primary)" stopOpacity="0.18" />
                                <stop offset="100%" stopColor="var(--color-primary)" stopOpacity="0" />
                            </linearGradient>
                        </defs>
                        {ticks.map((t) => (
                            <line key={t} x1="0" x2="1000" y1={H - t * H} y2={H - t * H} stroke="var(--color-border)" strokeOpacity="0.5" strokeWidth="1" vectorEffect="non-scaling-stroke" />
                        ))}
                        <path d={area} fill="url(#dashTrendFill)" />
                        <polyline points={pts} fill="none" stroke="var(--color-primary)" strokeWidth="2.5" strokeLinejoin="round" strokeLinecap="round" vectorEffect="non-scaling-stroke" />
                    </svg>
                    {hover != null && (
                        <div className="pointer-events-none absolute inset-y-0 border-l border-dashed border-muted-foreground/40" style={{ left: `${px(hover)}%` }} />
                    )}
                    {data.map((t, i) => (
                        <span
                            key={`${t.y}-${t.m}`}
                            className={`pointer-events-none absolute -translate-x-1/2 -translate-y-1/2 rounded-full border-2 border-card bg-primary transition-all duration-100 ${hover === i ? 'size-3.5 shadow-md' : 'size-2'}`}
                            style={{ left: `${px(i)}%`, top: `${(py(t.total) / H) * 100}%` }}
                        />
                    ))}
                    {hovered && (
                        <div
                            className="pointer-events-none absolute z-10 rounded-lg bg-foreground px-3 py-1.5 text-background shadow-lg"
                            style={{ left: `${px(hover)}%`, top: `${(py(hovered.total) / H) * 100}%`, transform: `translate(${tipShift}, calc(-100% - 10px))` }}
                        >
                            <p className="m-0 whitespace-nowrap text-[10px] font-bold uppercase tracking-wide opacity-70">{MONTHS[hovered.m - 1]} {hovered.y}</p>
                            <p className="m-0 flex items-center gap-1.5 whitespace-nowrap text-[12.5px] font-extrabold tabular-nums">
                                {money.full(hovered.total)}
                                {momPct != null && (
                                    <span className={`text-[10px] font-bold ${momPct >= 0 ? 'text-success' : 'text-danger'}`}>
                                        {momPct >= 0 ? '+' : ''}{momPct}%
                                    </span>
                                )}
                            </p>
                        </div>
                    )}
                </div>
            </div>
            <div className="flex justify-between px-5 pb-4 pl-14 pt-2 text-[10px] font-medium text-muted-foreground/70">
                {data.map((t, i) => (
                    <span key={`${t.y}-${t.m}`} className={hover === i ? 'font-extrabold text-primary' : ''}>{MONTHS[t.m - 1]}</span>
                ))}
            </div>
            </>
            )}
        </Card>
    );
}

// ── Top principal — the transactional face of "Principal": own YTD revenue per brand ────────

const DONUT_COLORS = [
    'var(--color-chart-1)', 'var(--color-chart-2)', 'var(--color-chart-3)',
    'var(--color-chart-4)', 'var(--color-chart-5)',
];

export function TopPrincipalsCard({ rows }) {
    const [active, setActive] = useState(null);
    const sum = rows.reduce((a, r) => a + r.total, 0);

    // Donut geometry: r=62 in a 160-box, segments as stroke dashes with a 2-unit gap.
    const R = 62;
    const C = 2 * Math.PI * R;
    const segments = useMemo(() => {
        let acc = 0;
        return rows.map((r) => {
            const len = sum > 0 ? (r.total / sum) * C : 0;
            const seg = { start: acc, len: Math.max(len - 2, 0.5) };
            acc += len;
            return seg;
        });
    }, [rows, sum, C]);

    const focus = active != null ? rows[active] : null;

    return (
        <Card className="flex h-full flex-col gap-0 p-0 transition-shadow duration-200 hover:shadow-md">
            <header className="flex items-center gap-2.5 border-b border-border/40 px-5 py-3.5">
                <span className={HEAD_TILE}><Package className="size-4" aria-hidden="true" /></span>
                <h2 className="m-0 text-sm font-bold uppercase tracking-wide text-foreground">Top Principal</h2>
                {/* Static for now: the figures behind this card are YTD only, so a period picker
                    that could not actually change the period would be a lie with a chevron on it. */}
                <span className="ml-auto inline-flex items-center rounded-lg border border-border px-2.5 py-1 text-[11px] font-bold text-muted-foreground">
                    YTD
                </span>
            </header>
            {rows.length === 0 ? (
                <div className="flex min-h-[290px] flex-1 flex-col items-center justify-center gap-2 px-5 py-8 text-center">
                    <Package className="size-8 text-muted-foreground/30" aria-hidden="true" />
                    <p className="m-0 text-xs text-muted-foreground">Belum ada data penjualan tahun ini.</p>
                </div>
            ) : (
                <div className="flex flex-1 items-stretch gap-4 px-5 pb-4 pt-3 animate-fade-in" onMouseLeave={() => setActive(null)}>
                    <div className="relative size-32 shrink-0 self-center">
                        <svg viewBox="0 0 160 160" className="size-full -rotate-90">
                            {segments.map((s, i) => (
                                <circle
                                    key={rows[i].principal}
                                    cx="80" cy="80" r={R} fill="none"
                                    stroke={DONUT_COLORS[i % DONUT_COLORS.length]}
                                    strokeWidth={active === i ? 30 : 24}
                                    strokeDasharray={`${s.len} ${C - s.len}`}
                                    strokeDashoffset={-s.start}
                                    className="cursor-pointer transition-all duration-150"
                                    opacity={active == null || active === i ? 1 : 0.3}
                                    onMouseEnter={() => setActive(i)}
                                />
                            ))}
                        </svg>
                        <div className="pointer-events-none absolute inset-0 grid place-items-center">
                            <div className="max-w-[76px] text-center">
                                <p className="m-0 truncate text-[10px] font-bold uppercase tracking-wide text-muted-foreground" title={focus ? focus.principal : undefined}>
                                    {focus ? focus.principal : 'Total YTD'}
                                </p>
                                <p className="m-0 text-[12.5px] font-extrabold tabular-nums tracking-tight text-foreground">
                                    {money.short(focus ? focus.total : sum)}
                                </p>
                                {focus && sum > 0 && (
                                    <p className="m-0 text-[10px] font-bold text-muted-foreground">{Math.round((focus.total / sum) * 100)}%</p>
                                )}
                            </div>
                        </div>
                    </div>
                    <div className="flex min-w-0 flex-1 flex-col justify-between gap-0.5">
                        {rows.map((r, i) => (
                            <div
                                key={r.principal}
                                onMouseEnter={() => setActive(i)}
                                className={`flex items-center gap-1.5 rounded-lg px-1.5 py-1 transition-colors ${active === i ? 'bg-secondary/50' : ''}`}
                            >
                                <span className="size-2 shrink-0 rounded-full" style={{ background: DONUT_COLORS[i % DONUT_COLORS.length] }} />
                                <span className="min-w-0 flex-1 truncate text-[11px] font-medium text-foreground" title={r.principal}>{r.principal}</span>
                                <span className="shrink-0 text-[10px] tabular-nums text-muted-foreground">{sum > 0 ? Math.round((r.total / sum) * 100) : 0}%</span>
                                <span className="w-16 shrink-0 text-right text-[11px] font-bold tabular-nums text-foreground">{money.short(r.total)}</span>
                            </div>
                        ))}
                    </div>
                </div>
            )}
        </Card>
    );
}
// ── Sales podium — hero (top 3) + ranked list ───────────────────────────────────────────────

/**
 * Two widgets over ONE payload: the hero shows the three that made the podium, the list shows
 * everyone below it. They are separate board widgets so a user who only wants the leaderboard, or
 * only the standings, can hide the other half.
 *
 * Both dimensions (per sales rep / per division) ship together, so each card toggles instantly and
 * they cannot end up reading from different data.
 */

/**
 * Trophy artwork, drawn as inline SVG rather than shipped as images.
 *
 * Three reasons it is vector: the board is theme-aware (a PNG cup would keep its baked-in white
 * halo on a dark card), the metals must stay in step with the tier tokens used elsewhere in the
 * widget, and three raster cups at retina size would outweigh the entire page payload.
 *
 * The gradients here are ILLUSTRATION shading, not decoration — the ban in ui-conventions.md is on
 * large decorative background/section/hero gradients, and the card behind these stays a flat tint.
 */
const TROPHY_METAL = {
    1: { spec: '#FFFDF0', hi: '#FFE9A0', mid: '#F0BE3E', lo: '#C08A10', deep: '#8A5D04', num: '#7A5100', leaf: '#E4AE2A' },
    2: { spec: '#FFFFFF', hi: '#F1F4F8', mid: '#CBD3DD', lo: '#9BA5B2', deep: '#6B7482', num: '#5A626D', leaf: '#BCC5D0' },
    3: { spec: '#FFF4E8', hi: '#F7CDA4', mid: '#DA9257', lo: '#A85F2C', deep: '#7A431E', num: '#6B3A18', leaf: '#C67F45' },
};

/**
 * Laurel wreath drawn IN FRONT of the bowl's base, opening upward like a pair of cupped branches —
 * that framing is what makes a cup read as a trophy rather than a goblet.
 */
function Laurel({ dir, color, shade, spec }) {
    // The branch stays INSIDE the bowl's width and stops around mid-bowl. The previous arc swept
    // up past the rim and crossed the handles, which read as a chain draped over the cup rather
    // than a wreath framing it.
    const p0 = [120 - dir * 8, 204];
    const c = [120 - dir * 76, 198];
    const p1 = [120 - dir * 72, 106];
    const at = (t) => [
        (1 - t) ** 2 * p0[0] + 2 * (1 - t) * t * c[0] + t ** 2 * p1[0],
        (1 - t) ** 2 * p0[1] + 2 * (1 - t) * t * c[1] + t ** 2 * p1[1],
    ];

    return (
        <g>
            {/* Branch in the leaf colour, not a dark stroke — a dark line reads as wire. */}
            <path
                d={`M${p0[0]} ${p0[1]} Q${c[0]} ${c[1]} ${p1[0]} ${p1[1]}`}
                fill="none" stroke={shade} strokeWidth="3.2" strokeLinecap="round" opacity="0.75"
            />
            {Array.from({ length: 5 }, (_, i) => {
                const t = 0.16 + (i / 4) * 0.84;
                const [x, y] = at(t);
                // Leaves point up and outward along the branch, fanning as they climb — at the old
                // size and angle they overlapped into a continuous strip and read as a chain.
                const rot = -dir * (80 - t * 34);

                return (
                    <g key={i} transform={`translate(${x} ${y}) rotate(${rot}) scale(${dir * 1.35} 1.35)`}>
                        {/* Almond leaf: pointed at both ends, unlike the flat ellipse it replaces. */}
                        <path d="M0 0 C5 -8 15 -9 21 0 C15 9 5 8 0 0 Z" fill={color} />
                        <path d="M3 0 C7 -5 14 -5.5 18 0 C14 2 7 2.5 3 0 Z" fill={spec} opacity="0.45" />
                        <path d="M1 0 L20 0" stroke={shade} strokeWidth="0.9" opacity="0.5" />
                    </g>
                );
            })}
        </g>
    );
}

/**
 * The cup. Shape follows the reference: a wide flared chalice, big loop handles springing from the
 * rim, a slender stem into a flared foot, all standing on a black plinth.
 *
 * The metal is a horizontal multi-stop gradient — dark edge, body, a narrow specular band, body,
 * dark edge — which is what gives a flat vector shape its roundness.
 */
function TrophyArt({ rank, className }) {
    const m = TROPHY_METAL[rank];
    const id = `podTrophy${rank}`;

    return (
        <svg viewBox="0 40 240 260" className={className} role="img" aria-label={`Piala peringkat ${rank}`}>
            <defs>
                <linearGradient id={`${id}body`} x1="0" y1="0" x2="1" y2="0">
                    <stop offset="0%" stopColor={m.deep} />
                    <stop offset="8%" stopColor={m.lo} />
                    <stop offset="22%" stopColor={m.mid} />
                    <stop offset="33%" stopColor={m.hi} />
                    <stop offset="39%" stopColor={m.spec} />
                    <stop offset="46%" stopColor={m.hi} />
                    <stop offset="60%" stopColor={m.mid} />
                    <stop offset="82%" stopColor={m.lo} />
                    <stop offset="100%" stopColor={m.deep} />
                </linearGradient>
                <linearGradient id={`${id}rim`} x1="0" y1="0" x2="1" y2="0">
                    <stop offset="0%" stopColor={m.deep} />
                    <stop offset="18%" stopColor={m.hi} />
                    <stop offset="34%" stopColor={m.spec} />
                    <stop offset="60%" stopColor={m.mid} />
                    <stop offset="100%" stopColor={m.deep} />
                </linearGradient>
                <linearGradient id={`${id}plinth`} x1="0" y1="0" x2="1" y2="0">
                    <stop offset="0%" stopColor="#0d0e11" />
                    <stop offset="26%" stopColor="#41454e" />
                    <stop offset="42%" stopColor="#5a5f6a" />
                    <stop offset="70%" stopColor="#23262c" />
                    <stop offset="100%" stopColor="#0b0c0f" />
                </linearGradient>
            </defs>

            {/* Contact shadow so the cup sits on the step instead of floating. */}
            <ellipse cx="120" cy="286" rx="62" ry="7" fill="#000" opacity="0.16" />

            {/* Handles: closed crescents, drawn behind the bowl so only the outer loop shows. */}
            {[-1, 1].map((d) => (
                <path
                    key={d}
                    d={`M${120 + d * 52} 86
                        C${120 + d * 104} 78 ${120 + d * 122} 128 ${120 + d * 100} 166
                        C${120 + d * 88} 188 ${120 + d * 60} 194 ${120 + d * 40} 184
                        L${120 + d * 46} 170
                        C${120 + d * 62} 176 ${120 + d * 78} 170 ${120 + d * 84} 152
                        C${120 + d * 92} 126 ${120 + d * 80} 100 ${120 + d * 50} 104 Z`}
                    fill={`url(#${id}body)`}
                />
            ))}

            {/* Bowl: wide at the rim, tucking sharply into the stem. */}
            <path d="M70 78 C70 132 82 176 108 190 L132 190 C158 176 170 132 170 78 Z" fill={`url(#${id}body)`} />
            {/* Broad gloss down the left wall + a tight specular streak. */}
            <path d="M88 88 C88 134 96 168 112 184 C98 176 80 138 80 90 Z" fill={m.spec} opacity="0.34" />
            <path d="M101 92 C100 130 104 158 111 176 C107 156 106 124 108 92 Z" fill={m.spec} opacity="0.6" />

            {/* Rim: a real elliptical lip (outer ellipse) over the dark mouth, so the top of the
                cup reads as an opening rather than a flat bar laid across it. */}
            <path d="M62 78 a58 15 0 0 0 116 0 v-6 a58 15 0 0 0 -116 0 z" fill={`url(#${id}rim)`} />
            <ellipse cx="120" cy="72" rx="58" ry="15" fill={`url(#${id}rim)`} />
            <ellipse cx="120" cy="73" rx="47" ry="11" fill={m.deep} opacity="0.75" />
            <ellipse cx="120" cy="72" rx="47" ry="11" fill={m.lo} opacity="0.55" />

            <text x="120" y="152" textAnchor="middle" fontSize="54" fontWeight="900" fill={m.num} opacity="0.92">
                {rank}
            </text>

            {/* Wreath sits IN FRONT of the bowl's lower half, framing it. */}
            <Laurel dir={1} color={m.leaf} shade={m.deep} spec={m.spec} />
            <Laurel dir={-1} color={m.leaf} shade={m.deep} spec={m.spec} />

            {/* Stem → flared foot. */}
            <path d="M108 190 h24 l5 30 h-34 z" fill={`url(#${id}body)`} />
            <ellipse cx="120" cy="222" rx="20" ry="5" fill={m.mid} />
            <path d="M92 224 h56 l12 18 h-80 z" fill={`url(#${id}body)`} />

            {/* Black plinth. */}
            <rect x="76" y="242" width="88" height="40" rx="5" fill={`url(#${id}plinth)`} />
            <ellipse cx="120" cy="243" rx="44" ry="6" fill="#4a4f59" />
            <rect x="76" y="256" width="88" height="3" rx="1.5" fill={m.mid} opacity="0.35" />
        </svg>
    );
}

/**
 * The cylindrical step each trophy stands on: an elliptical top face over a straight body, which
 * is what reads as a cylinder without needing a second SVG.
 */
/**
 * The step AND the name plate. A podium block is a name plate, so the name belongs on it — but it
 * only reads as a name plate if the surface is clean: card-coloured with a vivid tier edge, not a
 * mid-tone wash. The rank chip straddles the top edge so the plate has an anchor and the step
 * still announces its position without repeating the number twice at full size.
 */
function Pedestal({ rank, className, name, value, valueTitle, isMe }) {
    const tier = PODIUM_TIERS[rank - 1];

    return (
        // min-height, NOT height. A two-line name inside a FIXED-height box with overflow-hidden is
        // sliced through the middle of the letters — which is what cut "Hendra Ariansyah" in half.
        // The plate now grows for the rare long name; line-clamp-2 still caps the truly absurd ones,
        // and then it ellipsises instead of cutting.
        <div className={`relative flex w-full flex-col items-center justify-center gap-1 rounded-xl border border-border bg-card px-2.5 py-2 text-center shadow-sm ${className}`}>
            <>
                <span className={`inline-flex items-center gap-1 rounded-full px-2 py-0.5 text-[9.5px] font-extrabold uppercase tracking-wide ${tier.chip}`}>
                    <Trophy className="size-2.5" aria-hidden="true" />#{rank}
                </span>
                <p className={`m-0 line-clamp-2 font-extrabold leading-tight text-foreground ${tier.nameSize}`} title={name}>
                    {name}
                </p>
                <p className={`m-0 text-[12px] font-bold tabular-nums ${rank === 1 ? 'text-primary' : 'text-muted-foreground'}`} title={valueTitle}>
                    {value}
                    {isMe && <span className="ml-1.5 rounded-full bg-primary px-1.5 py-px text-[8.5px] font-bold text-primary-foreground">ANDA</span>}
                </p>
            </>
        </div>
    );
}

const PODIUM_TIERS = [
    // `edge` is a VIVID solid bar; `plate` stays card-coloured. The step used to be a mid-tone
    // wash (warning at 28% over card) with the name on it, and dark text on a muddy tint is
    // exactly what made the name look lifeless — no contrast to carry it. Colour now lives in a
    // thin edge and the value, so the name gets a clean surface to sit on.
    { edge: 'bg-warning', chip: 'bg-warning-bg text-warning-text', accent: 'text-warning-text', plateH: 'min-h-[76px]', nameSize: 'text-[13.5px]', t: 'w-32' },
    { edge: 'bg-muted-foreground/70', chip: 'bg-secondary text-muted-foreground', accent: 'text-muted-foreground', plateH: 'min-h-[64px]', nameSize: 'text-[12.5px]', t: 'w-24' },
    { edge: 'bg-stat-orange-text/80', chip: 'bg-stat-orange-bg text-stat-orange-text', accent: 'text-stat-orange-text', plateH: 'min-h-[56px]', nameSize: 'text-[12.5px]', t: 'w-20' },
];

const initialsOf = (s) => String(s || '?').trim().split(/\s+/).slice(0, 2).map((w) => w[0]).join('').toUpperCase() || '?';

/** Sales ↔ Divisi switch, shared by both cards (each keeps its own choice). */
function DimToggle({ dim, onChange }) {
    return (
        <div className="ml-auto flex rounded-lg border border-border bg-card p-0.5 text-[11px] font-bold shadow-xs">
            {[['sales', 'Sales'], ['division', 'Divisi']].map(([id, label]) => (
                <button
                    key={id}
                    type="button"
                    onClick={() => onChange(id)}
                    className={`rounded-md px-3.5 py-1 transition-all duration-150 cursor-pointer ${
                        dim === id
                            ? 'bg-primary text-primary-foreground shadow-xs'
                            : 'text-muted-foreground hover:text-foreground'
                    }`}
                >
                    {label}
                </button>
            ))}
        </div>
    );
}

function PodiumEmpty({ children }) {
    return (
        <div className="flex flex-1 flex-col items-center justify-center gap-2 px-5 py-10 text-center">
            <Trophy className="size-8 text-muted-foreground/30" aria-hidden="true" />
            <p className="m-0 text-xs text-muted-foreground">{children}</p>
        </div>
    );
}

const TIER_INFO_STYLES = {
    1: {
        badgeBg: 'bg-amber-50 border border-amber-100/50',
        badgeText: 'text-amber-600',
        rankIcon: 'text-amber-500',
    },
    2: {
        badgeBg: 'bg-secondary border border-border',
        badgeText: 'text-muted-foreground',
        rankIcon: 'text-muted-foreground',
    },
    3: {
        badgeBg: 'bg-orange-50 border border-orange-100/50',
        badgeText: 'text-orange-600',
        rankIcon: 'text-orange-500',
    },
};

/** The hero: three trophies on three pedestals, the winner centre and tallest. */
export function SalesPodiumHero({ podium }) {
    const [dim, setDim] = useState('sales');
    const [hover, setHover] = useState(null);
    const [showRest, setShowRest] = useState(false);

    const rows = podium?.[dim] ?? [];
    const top = rows.slice(0, 3);
    const rest = rows.slice(3);
    const leader = rows[0]?.total ?? 0;
    // Steps read 2nd · 1st · 3rd, the way a real podium stands.
    const steps = [top[1], top[0], top[2]].map((row, i) => ({ row, rank: [2, 1, 3][i] })).filter((s) => s.row);

    return (
        <Card className="relative flex h-full flex-col gap-0 overflow-hidden p-0 border border-border bg-[color-mix(in_srgb,var(--color-primary)_4%,var(--color-card))] transition-shadow duration-200 hover:shadow-md">
            {/* Decorative floating diamonds */}
            <div className="absolute left-[3%] top-[80%] w-2 h-2 bg-violet-400/20 rotate-45 pointer-events-none z-0" />
            <div className="absolute left-[10%] top-[48%] w-2.5 h-2.5 bg-violet-400/20 rotate-45 pointer-events-none z-0" />
            <div className="absolute left-[28%] top-[25%] w-2 h-2 bg-violet-400/15 rotate-45 pointer-events-none z-0" />
            <div className="absolute left-[37%] top-[75%] w-3 h-3 bg-violet-400/15 rotate-45 pointer-events-none z-0" />
            <div className="absolute right-[32%] top-[30%] w-2 h-2 bg-violet-400/15 rotate-45 pointer-events-none z-0" />
            <div className="absolute right-[27%] top-[72%] w-2.5 h-2.5 bg-violet-400/20 rotate-45 pointer-events-none z-0" />
            <div className="absolute right-[9%] top-[30%] w-2 h-2 bg-violet-400/15 rotate-45 pointer-events-none z-0" />
            <div className="absolute right-[6%] top-[75%] w-3 h-3 bg-violet-400/20 rotate-45 pointer-events-none z-0" />

            {/* z-20, ABOVE the podium body's z-10. The header is positioned WITH a z-index, so it is a
                stacking context: the "Peringkat 4–10" panel's own z-30 only ranks it inside the
                header, and the header as a whole was tying with the body at z-10 — so the body,
                being later in DOM order, painted its trophies straight over the open panel. */}
            <header className="relative z-20 flex flex-wrap items-center gap-2.5 px-6 pt-5 pb-2">
                <span className={HEAD_TILE}><Trophy className="size-4" aria-hidden="true" /></span>
                <div className="min-w-0">
                    <h2 className="m-0 text-sm font-extrabold uppercase tracking-wide text-foreground">
                        Podium Penjualan
                    </h2>
                    <p className="m-0 text-[11px] font-medium text-muted-foreground">
                        Top Performer {dim === 'sales' ? 'Sales' : 'Divisi'} (YTD)
                    </p>
                </div>

                <div className="ml-auto flex items-center gap-3">
                    {rest.length > 0 && (
                        // Ranks 4+ live behind this button rather than in a card of their own: the
                        // podium is the headline, the standings are the follow-up question.
                        // Hover opens it, click pins it — a hover-only panel is unreachable by
                        // keyboard and unusable on touch.
                        <div
                            className="relative"
                            onMouseEnter={() => setShowRest(true)}
                            onMouseLeave={() => setShowRest(false)}
                        >
                            <button
                                type="button"
                                onClick={() => setShowRest((v) => !v)}
                                aria-expanded={showRest}
                                className={`inline-flex items-center gap-1.5 rounded-lg border px-2.5 py-1 text-[11px] font-bold transition-colors ${showRest ? 'border-primary bg-accent text-primary' : 'border-border text-muted-foreground hover:text-foreground'}`}
                            >
                                <ListOrdered className="size-3.5" aria-hidden="true" />
                                Peringkat 4–{rest.length + 3}
                            </button>

                            {showRest && (
                                <div className="absolute right-0 top-full z-30 mt-2 w-[400px] overflow-hidden rounded-xl border border-border bg-card shadow-lg">
                                    <p className="m-0 border-b border-border/50 px-3.5 py-2 text-[10.5px] font-bold uppercase tracking-wide text-muted-foreground">
                                        Peringkat berikutnya · % dari #1
                                    </p>
                                    <div className="max-h-[190px] overflow-y-auto py-1">
                                        {rest.map((r, i) => {
                                            const pct = leader > 0 ? Math.round((r.total / leader) * 100) : 0;

                                            return (
                                                <div
                                                    key={r.key}
                                                    className={`flex items-center gap-2.5 px-3.5 py-1.5 ${r.isMe ? 'bg-accent/60' : ''}`}
                                                >
                                                    <span className="w-4 shrink-0 text-right text-[11px] font-bold tabular-nums text-muted-foreground">{i + 4}</span>
                                                    <span className="grid size-6 shrink-0 place-items-center rounded-full bg-secondary text-[9.5px] font-bold text-muted-foreground">
                                                        {initialsOf(r.label)}
                                                    </span>
                                                    <span className="min-w-0 flex-1 truncate text-[12px] font-medium text-foreground" title={r.label}>
                                                        {r.label}
                                                        {r.isMe && <span className="ml-1.5 rounded-full bg-primary px-1.5 py-px text-[9px] font-bold text-primary-foreground">ANDA</span>}
                                                    </span>
                                                    <span className="h-1.5 w-10 shrink-0 overflow-hidden rounded-full bg-secondary/70">
                                                        <span className="block h-full rounded-full bg-primary" style={{ width: `${pct}%` }} />
                                                    </span>
                                                    <span className="w-8 shrink-0 text-right text-[10.5px] font-semibold tabular-nums text-muted-foreground">{pct}%</span>
                                                    <span className="w-20 shrink-0 text-right text-[11.5px] font-bold tabular-nums text-foreground" title={money.full(r.total)}>
                                                        {money.short(r.total)}
                                                    </span>
                                                </div>
                                            );
                                        })}
                                    </div>
                                </div>
                            )}
                        </div>
                    )}
                    <DimToggle dim={dim} onChange={(d) => { setDim(d); setHover(null); setShowRest(false); }} />
                </div>
            </header>

            {steps.length === 0 ? (
                <PodiumEmpty>Belum ada penjualan tahun ini.</PodiumEmpty>
            ) : (
                // ⚠️ Two nested boxes on purpose. This card shares its grid row with Top Principal
                // and the row stretches both to the taller one (437px measured), so the podium gets
                // ~90px more height than its content needs. With `items-end` on the FILLING box the
                // whole podium was shoved to the floor of that extra height and the trophies sat far
                // below the header. The outer box now centres the group in whatever height the row
                // hands over; the inner one keeps the three columns bottom-aligned to each other so
                // the steps still line up.
                <div
                    className="relative z-10 flex flex-1 items-center px-6 pb-4 pt-2"
                    onMouseLeave={() => setHover(null)}
                >
                    <div className="flex w-full items-end justify-center gap-3 sm:gap-4 md:gap-6 lg:gap-8">
                    {steps.map(({ row, rank }) => {
                        const tier = PODIUM_TIERS[rank - 1];
                        const on = hover === row.key;

                        return (
                            <div
                                key={row.key}
                                onMouseEnter={() => setHover(row.key)}
                                className={`flex w-full max-w-[210px] cursor-default flex-col items-center transition-all duration-200 ${hover && !on ? 'opacity-60' : ''} ${on ? '-translate-y-1' : ''}`}
                            >
                                <TrophyArt rank={rank} className={tier.t} />
                                <Pedestal
                                    rank={rank}
                                    className={`mt-1.5 ${tier.plateH}`}
                                    name={row.label}
                                    value={money.short(row.total)}
                                    valueTitle={money.full(row.total)}
                                    isMe={row.isMe}
                                />
                            </div>
                        );
                    })}
                    </div>
                </div>
            )}
        </Card>
    );
}


// ── Approval shortcuts ──────────────────────────────────────────────────────────────────────

/**
 * Doors into the approval queues, and ONLY the ones this viewer holds a grant for — the server
 * builds the list from `allowedMenuLinks()`, so a shortcut here can never answer 403.
 *
 * The number is the same one the sidebar badge shows (NavBadges): work the viewer can ACT ON.
 * When there is no number the chip is simply absent — a queue nobody counted is not the same
 * claim as a queue with nothing in it.
 */
export function ApprovalShortcutsCard({ approvals = [] }) {
    const [showAll, setShowAll] = useState(false);

    // An admin holds nearly every grant — 27 queues — and an un-capped list turned this card into a
    // wall that also stretched the row beside it. Queues WITH work come first (the service already
    // sorted them that way), the rest are one click away, and the list scrolls instead of growing.
    const withWork = approvals.filter((a) => (a.count ?? 0) > 0);
    const primary = withWork.length > 0 ? withWork : approvals.slice(0, VISIBLE_APPROVALS);
    const visible = showAll ? approvals : primary.slice(0, VISIBLE_APPROVALS);
    const hidden = approvals.length - visible.length;

    return (
        <Card className="flex h-full flex-col gap-0 p-0 transition-shadow duration-200 hover:shadow-md">
            <header className="flex items-center gap-2.5 border-b border-border/40 px-5 py-3.5">
                <span className={HEAD_TILE}><ClipboardCheck className="size-4" aria-hidden="true" /></span>
                <h2 className="m-0 text-sm font-bold uppercase tracking-wide text-foreground">Approval</h2>
                {withWork.length > 0 && (
                    <span className="ml-auto rounded-full bg-warning-bg px-2 py-0.5 text-[10.5px] font-extrabold tabular-nums text-warning-text">
                        {withWork.length} perlu aksi
                    </span>
                )}
            </header>

            {approvals.length === 0 ? (
                <div className="flex flex-1 flex-col items-center justify-center gap-2 px-5 py-8 text-center">
                    <ClipboardCheck className="size-8 text-muted-foreground/30" aria-hidden="true" />
                    <p className="m-0 text-xs text-muted-foreground">Anda tidak memegang antrean approval.</p>
                </div>
            ) : (
                <>
                    {/* @container, not a viewport breakpoint: this card can be set to ⅓, ½ or full
                        width from Atur Dashboard, so the column count has to follow the CARD's own
                        width. At ⅓ it stays a single list; at full width it fills the row instead of
                        leaving two thirds of it empty. */}
                    <div className="@container max-h-[300px] flex-1 overflow-y-auto">
                        <div className="grid grid-cols-1 @2xl:grid-cols-2 @5xl:grid-cols-3">
                        {visible.map((a) => (
                            <Link
                                key={a.link}
                                href={a.link}
                                className="group flex items-center gap-3 border-b border-border/40 px-5 py-2.5 transition-colors hover:bg-secondary/40"
                            >
                                <span className="min-w-0 flex-1 truncate text-[12.5px] font-semibold text-foreground" title={a.label}>
                                    {a.label}
                                </span>
                                {a.count !== null && a.count !== undefined && (
                                    <span className="shrink-0 rounded-full bg-warning-bg px-2 py-0.5 text-[10.5px] font-extrabold tabular-nums text-warning-text">
                                        {a.count}
                                    </span>
                                )}
                                <ArrowUpRight className="size-3.5 shrink-0 text-muted-foreground transition-colors group-hover:text-primary" aria-hidden="true" />
                            </Link>
                        ))}
                        </div>
                    </div>

                    {(hidden > 0 || showAll) && (
                        <button
                            type="button"
                            onClick={() => setShowAll((v) => !v)}
                            className="border-t border-border/40 px-5 py-2 text-[11.5px] font-bold text-primary transition-colors hover:bg-secondary/40"
                        >
                            {showAll ? 'Tampilkan lebih sedikit' : `Lihat semua (${approvals.length})`}
                        </button>
                    )}
                </>
            )}
        </Card>
    );
}

// ── Recent transactional lists ──────────────────────────────────────────────────────────────

// Per-module icon tint so the four cards read as four modules, not four copies of one table.
//
// `href` is each module's VIEW REQUEST list, not its View All. For quotation/visit/lwr the bare
// link already is that page (menu 63 'View Request - Quotation', 83 'View Visit Report', 110
// 'View Request'); Sample Order is the odd one out, where '/sample-orders' is menu 33 'View All'
// and View Request lives at '/sample-orders/view-request' (menu 32). Pointing the Sample tab at
// the bare link sent everyone to the wrong list, and to a 403 for most: View All is granted to 6
// roles, View Request to 13.
export const RECENT_META = {
    quotation: { title: 'Quotation Terbaru', icon: FileText, href: '/quotations', idLabel: 'Qt', money: true, tint: 'bg-accent text-primary' },
    sample: { title: 'Sample Order Terbaru', icon: Package, href: '/sample-orders/view-request', idLabel: 'SO', tint: 'bg-info-bg text-info-text' },
    visit: { title: 'Visit Plan Terbaru', icon: ClipboardList, href: '/visit-plans', idLabel: 'No', tint: 'bg-success-bg text-success-text' },
    lwr: { title: 'Lab Work Request Terbaru', icon: FlaskConical, href: '/lwrs', idLabel: 'LWR', tint: 'bg-warning-bg text-warning-text' },
};

// Status names differ per module; classify by keyword and fall back to a neutral pill.
const statusTone = (s) => {
    const t = String(s || '').toLowerCase();
    if (!t) return null;
    if (/(approve[^a]|approved|won|good|receive|order|complete|close|done|active|finish|deliver)/.test(t + ' ')) return 'bg-success-bg text-success-text';
    if (/(reject|lost|cancel|expired|fail|overdue)/.test(t)) return 'bg-danger-bg text-danger-text';
    if (/(approval|pending|wait|process|review|progress|open|new|submit|request|draft|feedback|revis)/.test(t)) return 'bg-warning-bg text-warning-text';
    return 'bg-secondary text-foreground';
};

/**
 * ONE recent-transactions card for all four modules, with a module toggle in the header
 * (user request 2026-08-05 — replaces the four separate "… Terbaru" cards). All four lists
 * ship in the `recent` payload already, so switching is instant and free.
 */
export function RecentUnifiedCard({ recent }) {
    const [kind, setKind] = useState('quotation');
    const meta = RECENT_META[kind];
    const rows = recent?.[kind] ?? [];

    return (
        <Card className="flex h-full flex-col gap-0 p-0 transition-shadow duration-200 hover:shadow-md">
            <header className="flex flex-wrap items-center gap-2.5 border-b border-border/40 px-5 py-3">
                <span className={HEAD_TILE}><meta.icon className="size-4" aria-hidden="true" /></span>
                <h2 className="m-0 text-sm font-bold uppercase tracking-wide text-foreground">Transaksi Terbaru</h2>
                <div className="ml-auto flex items-center gap-2">
                    <div className="flex rounded-lg border border-border bg-card p-0.5 text-[11px] font-bold shadow-xs">
                        {[['quotation', 'Quotation'], ['sample', 'Sample'], ['visit', 'Visit'], ['lwr', 'LWR']].map(([id, label]) => (
                            <button
                                key={id}
                                type="button"
                                onClick={() => setKind(id)}
                                className={`rounded-md px-3 py-0.5 transition-all duration-150 cursor-pointer ${
                                    kind === id
                                        ? 'bg-primary text-primary-foreground shadow-xs'
                                        : 'text-muted-foreground hover:text-foreground'
                                }`}
                            >
                                {label}
                            </button>
                        ))}
                    </div>
                    <Link href={meta.href} className="inline-flex items-center gap-1 text-[11px] font-bold text-primary hover:underline">
                        View all <ArrowUpRight className="size-3" />
                    </Link>
                </div>
            </header>
            {rows.length === 0 ? (
                <div className="flex flex-1 flex-col items-center justify-center gap-2 px-5 py-8 text-center">
                    <meta.icon className="size-8 text-muted-foreground/30" aria-hidden="true" />
                    <p className="m-0 text-xs text-muted-foreground">Belum ada transaksi.</p>
                </div>
            ) : (
                /* Activity-list rows (avatar + two lines + status on the right) — deliberately not a
                   <table>: five rows of the viewer's own latest work read better as a feed. */
                /* min-h = five rows. The tabs return different row counts, so without a floor the
                   whole card (and the widget beside it) jumped every time you switched module. */
                /* @container: two columns once the CARD (not the window) is wide enough. The
                   server sends TEN rows; in one column that would run past the card, so rows
                   6-10 are hidden until the second column exists. Two columns × five lines is
                   exactly the height the Ulang Tahun calendar beside it sets, which is what
                   used to be a ~130px empty band under a five-row list. */
                <div className="@container flex min-h-[180px] flex-1 flex-col justify-start py-1">
                <div className="grid grid-cols-1 [&>*:nth-child(n+6)]:hidden @2xl:grid-cols-2 @2xl:[&>*:nth-child(n+6)]:flex">
                    {rows.map((r) => (
                        <div key={r.id} className="flex min-h-[60px] items-center gap-3 border-b border-border/40 px-5 py-2.5 transition-colors last:border-b-0 hover:bg-secondary/40">
                            <span className={`grid size-8 shrink-0 place-items-center rounded-full text-[11px] font-bold ${meta.tint}`}>
                                {String(r.company || '?').trim().charAt(0).toUpperCase() || '?'}
                            </span>
                            <div className="min-w-0 flex-1">
                                <p className="m-0 truncate text-[12.5px] font-semibold leading-snug text-foreground" title={r.company}>{r.company}</p>
                                <p className="m-0 truncate text-[11px] leading-snug text-muted-foreground">
                                    <span className="font-bold tabular-nums text-primary">{meta.idLabel} {r.id}</span>
                                    <span className="mx-1.5 opacity-60">·</span>
                                    <span className="tabular-nums">{fmtDate(r.tanggal)}</span>
                                </p>
                            </div>
                            <div className="flex shrink-0 flex-col items-end gap-0.5">
                                {/* `r.total != null` is the redaction guard, not a defensive habit.
                                    DashboardWidgetService::recent() OMITS the `total` key for a
                                    role that holds `recent` without `money` — the key is absent
                                    precisely so no figure is shown. But `money.short` is
                                    `Number(v) || 0`, so rendering it anyway prints "IDR 0", which
                                    reads as a real rupiah amount sitting next to real ones. */}
                                {meta.money && r.total != null && <span className="text-[12px] font-bold tabular-nums text-foreground" title={money.full(r.total)}>{money.short(r.total)}</span>}
                                {statusTone(r.status) ? (
                                    <span className={`inline-flex whitespace-nowrap rounded-full px-2 py-0.5 text-[10px] font-bold ${statusTone(r.status)}`}>{r.status}</span>
                                ) : (
                                    <span className="text-[11px] text-muted-foreground">—</span>
                                )}
                            </div>
                        </div>
                    ))}
                </div>
                </div>
            )}
        </Card>
    );
}

/**
 * Ulang Tahun — a month calendar with birthdays marked, fed by DashboardWidgetService's
 * `calendar` payload. Grafted back from the pre-merge WIP stash on 2026-08-10: Pages/Dashboard/
 * Index.jsx imports it, so the bundle could not build without it (MISSING_EXPORT at chunk
 * render). Only this component came across — the podium/Pedestal iteration in this file is a
 * separate open question and was deliberately left untouched.
 */
export function BirthdaysCard({ calendar = null }) {
    const [open, setOpen] = useState(null);

    if (!calendar) return null;

    const marked = new Map((calendar.days ?? []).map((d) => [d.day, d.names]));
    // Leading blanks so the 1st lands under its weekday.
    const cells = [
        ...Array.from({ length: calendar.startWeekday }, () => null),
        ...Array.from({ length: calendar.daysInMonth }, (_, i) => i + 1),
    ];

    return (
        <Card className="flex h-full flex-col gap-0 overflow-visible p-0 transition-shadow duration-200 hover:shadow-md">
            <header className="flex items-center gap-2.5 border-b border-border/40 px-5 py-3.5">
                <span className={HEAD_TILE}>
                    <Cake className="size-4" aria-hidden="true" />
                </span>
                <h2 className="m-0 text-sm font-bold uppercase tracking-wide text-foreground">Ulang Tahun</h2>
                <span className="ml-auto text-[11px] font-semibold text-muted-foreground">
                    {MONTH_FULL_ID[calendar.month - 1]} {calendar.year}
                </span>
            </header>

            <div className="relative flex-1 p-4">
                <div className="grid grid-cols-7 gap-1.5 text-center">
                    {WEEKDAY_ID.map((w, i) => (
                        <span key={i} className="pb-1 text-[10px] font-bold uppercase tracking-wide text-muted-foreground/70">{w}</span>
                    ))}

                    {cells.map((day, i) => {
                        if (day === null) return <span key={`b${i}`} />;
                        const names = marked.get(day);
                        const isToday = day === calendar.today;
                        const isOpen = open === day;

                        return (
                            <span key={day} className="relative">
                                <span
                                    tabIndex={names ? 0 : -1}
                                    onMouseEnter={() => names && setOpen(day)}
                                    onMouseLeave={() => setOpen(null)}
                                    onFocus={() => names && setOpen(day)}
                                    onBlur={() => setOpen(null)}
                                    aria-label={names ? `${day}: ${names.join(', ')}` : undefined}
                                    className={`grid h-9 w-full place-items-center rounded-lg text-[12px] tabular-nums outline-none transition-colors ${
                                        names
                                            ? 'cursor-default bg-primary font-bold text-primary-foreground hover:brightness-110 focus-visible:ring-2 focus-visible:ring-primary'
                                            : 'font-medium text-muted-foreground'
                                    } ${isToday ? 'ring-2 ring-primary ring-offset-2 ring-offset-card' : ''}`}
                                >
                                    {day}
                                </span>

                                {/* z-40 and a `relative` cell: the grid rows below would otherwise
                                    paint over this, the same stacking trap the podium hit. */}
                                {isOpen && names && (
                                    <span className="absolute left-1/2 top-full z-40 mt-1.5 w-max max-w-[220px] -translate-x-1/2 rounded-lg border border-border bg-card px-3 py-2 text-left shadow-lg">
                                        <span className="block text-[10px] font-bold uppercase tracking-wide text-muted-foreground">
                                            {day} {MONTH_FULL_ID[calendar.month - 1]}
                                        </span>
                                        {names.map((n) => (
                                            <span key={n} className="mt-0.5 block text-[12px] font-semibold text-foreground">{n}</span>
                                        ))}
                                    </span>
                                )}
                            </span>
                        );
                    })}
                </div>
            </div>
        </Card>
    );
}
