import { useMemo, useState } from 'react';
import { ChevronLeft, ChevronRight, Eye, EyeOff, RotateCcw, Settings, SlidersHorizontal } from 'lucide-react';
import { CustomizeColumnsModal } from '@/Components/Proto/Modals/CustomizeColumnsModal';
import { useColumnPrefs } from '@/lib/useColumnPrefs';

/**
 * CustomBoard — the per-user customizable dashboard engine, extracted from Dashboard v3 so the
 * v2 boards can be made customizable the same way ("kalau yg v2 mo dijadiin gitu juga",
 * 2026-08-04) without a second implementation.
 *
 * What a page supplies: widget defs `{id, label, spans, defaultSpan, render(ctx)}` — the visuals
 * stay 100% the page's own. What the engine supplies:
 *
 *   ✎ "Atur Dashboard" — in-board edit mode: arrows = order, eye = hide, ⅓ ½ ⅔ 1/1 = width
 *     presets (the ONLY sizes allowed, so custom boards can't overlap or misalign), hidden tray.
 *   ⚙ — the same CustomizeColumnsModal as every list page, editing the same state.
 *
 * Both doors write ONE state: order+visibility via useColumnPrefs (`prefsKey`), widths in a
 * separate `{id: span}` payload (`spansKey`) clamped to each widget's allowed presets on load.
 *
 * Usage:
 *   const { controls, board } = useCustomBoard({ prefsKey, spansKey, widgets, ... });
 *   <DashHeader actions={controls} /> ... {board}
 *
 * Options for v3's extras — harmless to v2:
 *   tabDefs   second modal group ("Tab Transaksi"); visible ids reach render ctx as `tabIds`.
 *   buildCtx  (base) => ctx passed to every widget render; base = {tabIds, editing, onHideTab}.
 *   hideWhen  per-widget `{hideWhen: (base) => bool}` — e.g. v3's tx card with every tab hidden.
 *   spanOverride (id, visibleIds) => span|null — default-width adaptivity (e.g. a chart widens
 *     when its donut partner is hidden). An explicit user width always wins over it.
 */

const SPAN_CLS = { 4: 'lg:col-span-4', 5: 'lg:col-span-5', 6: 'lg:col-span-6', 7: 'lg:col-span-7', 8: 'lg:col-span-8', 12: 'lg:col-span-12' };
const SPAN_LABEL = { 4: '⅓', 5: '5/12', 6: '½', 7: '7/12', 8: '⅔', 12: '1/1' };

const BTN_GHOST = 'inline-flex h-9 items-center gap-1.5 rounded-lg border border-input bg-card px-4 text-xs font-bold text-muted-foreground transition-colors hover:border-primary hover:text-primary';
const BTN_PRIMARY = 'inline-flex h-9 items-center gap-1.5 rounded-lg bg-linear-to-br from-violet-500 to-primary px-4 text-xs font-bold text-white shadow-sm transition-[filter] hover:brightness-105';
const CTL = 'grid size-6 place-items-center rounded-md text-muted-foreground transition-colors hover:bg-accent hover:text-primary disabled:pointer-events-none disabled:opacity-30';

export function useCustomBoard({ prefsKey, spansKey, widgets, tabDefs = [], buildCtx = (base) => base, spanOverride = null, modalTitle = 'Atur Dashboard' }) {
    const [editing, setEditing] = useState(false);
    const [modalOpen, setModalOpen] = useState(false);

    const defs = useMemo(() => [
        ...widgets.map((w) => ({ id: w.id, label: w.label, groupId: 'widget' })),
        ...tabDefs.map((t) => ({ id: t.id, label: t.label, groupId: 'tab' })),
    ], [widgets, tabDefs]);
    const groups = useMemo(() => [
        { id: 'widget', label: 'Widget' },
        ...(tabDefs.length ? [{ id: 'tab', label: 'Tab Transaksi' }] : []),
    ], [tabDefs]);
    const prefs = useColumnPrefs(prefsKey, defs);

    // Widths, clamped to each widget's allowed presets so a stale payload can never render an
    // unknown span (mirrors useColumnPrefs' own load guards).
    const [spans, setSpans] = useState(() => {
        try {
            const raw = JSON.parse(localStorage.getItem(spansKey) ?? '{}');
            return Object.fromEntries(widgets
                .filter((w) => raw[w.id] && w.spans.includes(raw[w.id]))
                .map((w) => [w.id, raw[w.id]]));
        } catch {
            return {};
        }
    });
    const setSpan = (id, span) => {
        const next = { ...spans, [id]: span };
        setSpans(next);
        try { localStorage.setItem(spansKey, JSON.stringify(next)); } catch { /* private mode */ }
    };

    const setVisible = (id, visible) => prefs.applyColumns(prefs.columnState.map((c) => (c.id === id ? { ...c, visible } : c)));
    const isDefault = useMemo(
        () => JSON.stringify(prefs.columnState) === JSON.stringify(defs.map((d) => ({ id: d.id, visible: true })))
            && widgets.every((w) => (spans[w.id] ?? w.defaultSpan) === w.defaultSpan),
        [prefs.columnState, defs, spans, widgets],
    );
    const resetAll = () => {
        prefs.resetColumns();
        setSpans({});
        try { localStorage.removeItem(spansKey); } catch { /* private mode */ }
    };

    const byId = useMemo(() => new Map(widgets.map((w) => [w.id, w])), [widgets]);
    const defById = useMemo(() => new Map(defs.map((d) => [d.id, d])), [defs]);
    const tabIds = prefs.visibleCols.filter((d) => d.groupId === 'tab').map((d) => d.id);
    const base = { tabIds, editing, onHideTab: (id) => setVisible(id, false) };
    const boardWidgets = prefs.visibleCols
        .filter((d) => d.groupId === 'widget')
        .map((d) => byId.get(d.id))
        .filter((w) => w && !w.hideWhen?.(base));
    const visibleIds = new Set(boardWidgets.map((w) => w.id));
    const hiddenAll = prefs.columnState.filter((c) => !c.visible);
    const ctx = buildCtx(base);

    // Reorder among the widgets actually ON the board — swapping with something not rendered
    // would look like a dead button.
    const moveWidget = (id, dir) => {
        const vis = boardWidgets.map((w) => w.id);
        const vi = vis.indexOf(id);
        const ti = vi + dir;
        if (vi < 0 || ti < 0 || ti >= vis.length) return;
        const st = prefs.columnState.slice();
        const a = st.findIndex((c) => c.id === id);
        const b = st.findIndex((c) => c.id === vis[ti]);
        [st[a], st[b]] = [st[b], st[a]];
        prefs.applyColumns(st);
    };

    const spanClsOf = (w) => {
        let span = spans[w.id] ?? ((spanOverride?.(w.id, visibleIds)) ?? w.defaultSpan);
        return SPAN_CLS[span] ?? 'lg:col-span-12';
    };

    const controls = (
        <>
            {editing && !isDefault && (
                <button type="button" onClick={resetAll} className={BTN_GHOST}>
                    <RotateCcw className="size-3.5" /> Reset susunan
                </button>
            )}
            <button type="button" onClick={() => setEditing((e) => !e)} className={editing ? BTN_PRIMARY : BTN_GHOST}>
                <SlidersHorizontal className="size-3.5" /> {editing ? 'Selesai' : 'Atur Dashboard'}
            </button>
            <button
                type="button"
                onClick={() => setModalOpen(true)}
                title="Atur lewat daftar"
                aria-label="Atur lewat daftar"
                className="grid size-9 place-items-center rounded-lg border border-input bg-card text-muted-foreground transition-colors hover:border-primary hover:text-primary"
            >
                {/* The GEAR, not Settings2. design-system.md pins the CustomizeColumnsModal opener
                    to the `Settings` gear on every list page, and this button opens that same
                    modal — Settings2 is a sliders glyph, so the dashboard was the only screen in
                    the app where the familiar gear was missing. */}
                <Settings className="size-4" />
            </button>
        </>
    );

    const board = (
        <>
            {editing && (
                <p className="m-0 rounded-xl border border-primary/25 bg-accent/40 px-4 py-2 text-[12px] font-medium text-muted-foreground">
                    Panah = pindah urutan · mata = sembunyikan{tabDefs.length ? ' (widget maupun tab)' : ''} · ⅓ ½ ⅔ 1/1 = lebar widget · selesai, klik <strong className="font-bold text-foreground">Selesai</strong>.
                </p>
            )}

            <div className="grid grid-cols-1 gap-4 lg:grid-cols-12">
                {boardWidgets.map((w, vi) => (
                    <div key={w.id} className={`min-w-0 ${spanClsOf(w)}`}>
                        {/* Wrapper renders in BOTH modes (stable reconciliation root — a mode
                            toggle must not remount the widget subtree). */}
                        <div className={`relative h-full [&>article]:flex [&>article]:h-full [&>article]:flex-col [&>article>div:last-child]:my-auto ${editing ? 'rounded-2xl outline-2 outline-dashed outline-primary/40 outline-offset-4' : ''}`}>
                            {editing && (
                                <div className="absolute -top-3 right-4 z-20 flex items-center gap-0.5 rounded-full border border-border bg-card px-1.5 py-1 shadow-md">
                                    <span className="max-w-36 truncate px-1 text-[10px] font-bold uppercase tracking-wide text-muted-foreground">{w.label}</span>
                                    <button type="button" onClick={() => moveWidget(w.id, -1)} disabled={vi === 0} title="Pindah lebih awal" aria-label={`Pindah ${w.label} lebih awal`} className={CTL}><ChevronLeft className="size-3.5" /></button>
                                    <button type="button" onClick={() => moveWidget(w.id, 1)} disabled={vi === boardWidgets.length - 1} title="Pindah lebih akhir" aria-label={`Pindah ${w.label} lebih akhir`} className={CTL}><ChevronRight className="size-3.5" /></button>
                                    {w.spans.length > 1 && (
                                        <span className="ml-0.5 inline-flex items-center gap-px rounded-lg bg-secondary/60 p-0.5">
                                            {w.spans.map((sp) => {
                                                const cur = spans[w.id] ?? w.defaultSpan;
                                                return (
                                                    <button key={sp} type="button" onClick={() => setSpan(w.id, sp)} title={`Lebar ${SPAN_LABEL[sp]}`} aria-label={`Lebar ${w.label} ${SPAN_LABEL[sp]}`}
                                                        className={`h-5 w-7 rounded-md text-[10px] font-bold transition-colors ${sp === cur ? 'bg-accent text-primary' : 'text-muted-foreground hover:text-primary'}`}>
                                                        {SPAN_LABEL[sp]}
                                                    </button>
                                                );
                                            })}
                                        </span>
                                    )}
                                    <button type="button" onClick={() => setVisible(w.id, false)} title="Sembunyikan widget" aria-label={`Sembunyikan ${w.label}`} className={CTL}><EyeOff className="size-3.5" /></button>
                                </div>
                            )}
                            {w.render(ctx)}
                        </div>
                    </div>
                ))}
            </div>

            {editing && hiddenAll.length > 0 && (
                <div className="rounded-2xl border border-dashed border-border bg-card/60 px-4 py-3">
                    <p className="m-0 mb-2 text-[10px] font-extrabold uppercase tracking-[0.08em] text-muted-foreground">Disembunyikan — klik untuk menampilkan lagi</p>
                    <div className="flex flex-wrap gap-2">
                        {hiddenAll.map((c) => (
                            <button
                                key={c.id}
                                type="button"
                                onClick={() => setVisible(c.id, true)}
                                className="inline-flex h-8 items-center gap-1.5 rounded-lg border border-input bg-card px-3 text-xs font-semibold text-muted-foreground transition-colors hover:border-primary hover:text-primary"
                            >
                                <Eye className="size-3.5" /> {defById.get(c.id)?.label}
                            </button>
                        ))}
                    </div>
                </div>
            )}

            <CustomizeColumnsModal
                open={modalOpen}
                onClose={() => setModalOpen(false)}
                title={modalTitle}
                subtitle="Saklar untuk tampil/sembunyi · geser untuk mengatur urutan"
                searchPlaceholder="Cari widget..."
                groups={groups}
                definitions={defs}
                state={prefs.columnState}
                onApply={prefs.applyColumns}
                onReset={prefs.resetColumns}
            />
        </>
    );

    return { controls, board, editing };
}
