/* The shared building blocks every page uses: buttons, cards, form fields, badges and so on. */
import { Link } from '@inertiajs/react';
import { Children, cloneElement, isValidElement, useEffect, useRef, useState, type ButtonHTMLAttributes, type ComponentType, type InputHTMLAttributes, type ReactNode, type Ref, type SelectHTMLAttributes, type TextareaHTMLAttributes } from 'react';
import { AlertCircle, ChevronLeft, ChevronRight, type LucideProps } from 'lucide-react';
import type { Paginated } from '../types';

export const cx = (...c: (string | false | null | undefined)[]) => c.filter(Boolean).join(' ');

/* ---------- buttons ---------- */
type Variant = 'primary' | 'dark' | 'secondary' | 'danger' | 'ghost';
const VARIANTS: Record<Variant, string> = {
    primary: 'bg-brand text-white shadow-xs hover:brightness-110',
    dark: 'bg-ink text-white hover:bg-ink-2',
    secondary: 'border border-line bg-white text-ink shadow-xs hover:bg-line-2',
    danger: 'border border-rose-200 bg-rose-50 text-rose-700 hover:bg-rose-100',
    ghost: 'text-ink-2 hover:bg-line-2',
};
const btn = (variant: Variant, size: 'sm' | 'md' | 'lg', extra?: string) =>
    cx(
        'inline-flex items-center justify-center gap-2 rounded-lg font-semibold whitespace-nowrap transition active:scale-[0.98] disabled:cursor-not-allowed disabled:opacity-50 disabled:active:scale-100',
        size === 'sm' ? 'h-8 px-3 text-xs' : size === 'lg' ? 'h-12 px-5 text-base' : 'h-10 px-4 text-sm',
        VARIANTS[variant],
        extra,
    );

type BtnProps = { variant?: Variant; size?: 'sm' | 'md' | 'lg'; icon?: ComponentType<LucideProps>; children?: ReactNode; className?: string };

export function Button({ variant = 'primary', size = 'md', icon: Icon, children, className, ...rest }: BtnProps & ButtonHTMLAttributes<HTMLButtonElement> & { ref?: Ref<HTMLButtonElement> }) {
    return (
        <button className={btn(variant, size, className)} {...rest}>
            {Icon && <Icon className={size === 'sm' ? 'size-3.5' : 'size-4'} aria-hidden />}
            {children}
        </button>
    );
}

export function ButtonLink({ href, variant = 'primary', size = 'md', icon: Icon, children, className, external }: BtnProps & { href: string; external?: boolean }) {
    const inner = (
        <>
            {Icon && <Icon className={size === 'sm' ? 'size-3.5' : 'size-4'} aria-hidden />}
            {children}
        </>
    );
    // downloads and files are plain links; pages go through Inertia
    return external ? (
        <a href={href} className={btn(variant, size, className)} target="_blank" rel="noopener">{inner}</a>
    ) : (
        <Link href={href} className={btn(variant, size, className)}>{inner}</Link>
    );
}

/* ---------- surfaces ---------- */
export function Card({ children, className, pad = true }: { children: ReactNode; className?: string; pad?: boolean }) {
    return <section className={cx('rounded-card border border-line bg-panel shadow-card', pad && 'p-5 sm:p-6', className)}>{children}</section>;
}

export function CardHeader({ title, subtitle, action, icon: Icon }: { title: ReactNode; subtitle?: ReactNode; action?: ReactNode; icon?: ComponentType<LucideProps> }) {
    return (
        <div className="flex flex-wrap items-start justify-between gap-3">
            <div className="flex min-w-0 items-start gap-3">
                {Icon && (
                    <span className="grid size-9 shrink-0 place-items-center rounded-lg border border-line bg-white text-brand shadow-xs">
                        <Icon className="size-[18px]" aria-hidden />
                    </span>
                )}
                <div className="min-w-0">
                    <h2 className="text-[15px] font-semibold tracking-tight text-ink">{title}</h2>
                    {subtitle && <p className="mt-0.5 text-sm text-muted">{subtitle}</p>}
                </div>
            </div>
            {action}
        </div>
    );
}

export function PageHeader({ title, subtitle, actions, back }: { title: ReactNode; subtitle?: ReactNode; actions?: ReactNode; back?: { href: string; label: string } }) {
    return (
        <div className="mb-6">
            {back && (
                <Link href={back.href} className="mb-2 inline-flex items-center gap-1 text-sm font-medium text-muted hover:text-ink">
                    <ChevronLeft className="size-4" aria-hidden /> {back.label}
                </Link>
            )}
            <div className="flex flex-wrap items-end justify-between gap-4">
                <div className="min-w-0">
                    <h1 className="text-2xl font-bold tracking-tight text-ink">{title}</h1>
                    {subtitle && <div className="mt-1 text-sm text-muted">{subtitle}</div>}
                </div>
                {actions && <div className="flex flex-wrap items-center gap-2">{actions}</div>}
            </div>
        </div>
    );
}

export function EmptyState({ icon: Icon, children }: { icon?: ComponentType<LucideProps>; children: ReactNode }) {
    return (
        <div className="flex flex-col items-center gap-2 px-6 py-10 text-center text-sm text-muted">
            {Icon && <Icon className="size-8 text-line" aria-hidden />}
            <div>{children}</div>
        </div>
    );
}

/* ---------- badges ---------- */
export type Tone = 'green' | 'amber' | 'rose' | 'sky' | 'violet' | 'gray' | 'brand';
const TONES: Record<Tone, string> = {
    green: 'bg-emerald-50 text-emerald-700 ring-emerald-600/15',
    amber: 'bg-amber-50 text-amber-800 ring-amber-600/20',
    rose: 'bg-rose-50 text-rose-700 ring-rose-600/15',
    sky: 'bg-sky-50 text-sky-700 ring-sky-600/15',
    violet: 'bg-violet-50 text-violet-700 ring-violet-600/15',
    gray: 'bg-slate-100 text-slate-600 ring-slate-500/15',
    brand: 'bg-brand/10 text-brand ring-brand/20',
};
export function Badge({ tone = 'gray', children, dot, className }: { tone?: Tone; children: ReactNode; dot?: boolean; className?: string }) {
    return (
        <span className={cx('inline-flex items-center gap-1.5 rounded-md px-2 py-0.5 text-xs font-medium whitespace-nowrap ring-1 ring-inset', TONES[tone], className)}>
            {dot && <span className="size-1.5 rounded-full bg-current" aria-hidden />}
            {children}
        </span>
    );
}

/* ---------- people ---------- */
/** A round profile photo, or the person's initials when they have none (or it fails to load). */
export function Avatar({ name, src, size = 'md', className }: { name: string; src?: string | null; size?: 'sm' | 'md' | 'lg' | 'xl'; className?: string }) {
    const [broken, setBroken] = useState(false);
    const initials = name.split(/\s+/).filter(Boolean).slice(0, 2).map((w) => w[0]).join('').toUpperCase();
    const dims = size === 'sm' ? 'size-8 text-xs' : size === 'lg' ? 'size-14 text-lg' : size === 'xl' ? 'size-24 text-3xl' : 'size-10 text-sm';
    if (src && !broken) {
        return <img src={src} alt="" onError={() => setBroken(true)} className={cx('shrink-0 rounded-full object-cover ring-1 ring-black/5', dims, className)} />;
    }
    return (
        <span
            className={cx(
                'inline-grid shrink-0 place-items-center rounded-full bg-brand/12 font-bold text-brand',
                dims,
                className,
            )}
            aria-hidden
        >
            {initials || '?'}
        </span>
    );
}

/* ---------- form fields ---------- */
export function Field({ label, htmlFor, hint, error, required, children, className }: { label?: ReactNode; htmlFor?: string; hint?: ReactNode; error?: string; required?: boolean; children: ReactNode; className?: string }) {
    return (
        <div className={className}>
            {label && (
                <label htmlFor={htmlFor} className="mb-1.5 block text-sm font-semibold text-ink">
                    {label} {required && <span className="text-rose-600">*</span>}
                </label>
            )}
            {children}
            {hint && !error && <p className="mt-1.5 text-xs text-muted">{hint}</p>}
            {error && <p className="mt-1.5 text-xs font-semibold text-rose-600" role="alert">{error}</p>}
        </div>
    );
}

/** A box listing what needs fixing, shown above a form after a failed save; it scrolls itself into view. */
export function ErrorSummary({ errors, className }: { errors: Record<string, string | undefined>; className?: string }) {
    const ref = useRef<HTMLDivElement>(null);
    const list = [...new Set(Object.values(errors).filter(Boolean))] as string[];
    const key = list.join('|');
    useEffect(() => { if (key) ref.current?.scrollIntoView({ behavior: 'smooth', block: 'center' }); }, [key]);
    if (!list.length) return null;
    return (
        <div ref={ref} role="alert" className={cx('flex gap-3 rounded-2xl border border-rose-200 bg-rose-50 px-4 py-3.5 text-sm text-rose-900', className)}>
            <AlertCircle className="mt-0.5 size-5 shrink-0 text-rose-600" aria-hidden />
            <div className="min-w-0">
                <p className="font-bold">{list.length === 1 ? 'One thing needs fixing before saving:' : `${list.length} things need fixing before saving:`}</p>
                <ul className="mt-1 list-disc space-y-0.5 pl-4">{list.map((e) => <li key={e}>{e}</li>)}</ul>
            </div>
        </div>
    );
}

export function Input({ invalid, className, ...rest }: InputHTMLAttributes<HTMLInputElement> & { invalid?: boolean; ref?: Ref<HTMLInputElement> }) {
    return <input className={cx('field-input', className)} aria-invalid={invalid || undefined} {...rest} />;
}
export function Select({ invalid, className, children, ...rest }: SelectHTMLAttributes<HTMLSelectElement> & { invalid?: boolean }) {
    return <select className={cx('field-input pr-9', className)} aria-invalid={invalid || undefined} {...rest}>{children}</select>;
}
export function Textarea({ invalid, className, ...rest }: TextareaHTMLAttributes<HTMLTextAreaElement> & { invalid?: boolean }) {
    return <textarea className={cx('field-input', className)} aria-invalid={invalid || undefined} {...rest} />;
}

/** A radio or checkbox drawn as a selectable card. */
export function Choice({ type = 'radio', checked, onChange, name, value, children, className, required }: { type?: 'radio' | 'checkbox'; checked: boolean; onChange: (checked: boolean) => void; name: string; value: string; children: ReactNode; className?: string; required?: boolean }) {
    return (
        <label
            className={cx(
                'flex cursor-pointer items-start gap-3 rounded-lg border bg-white p-3.5 text-sm transition',
                checked ? 'border-brand bg-brand/[0.04] ring-1 ring-brand' : 'border-line hover:border-ink/25',
                className,
            )}
        >
            <input
                type={type}
                name={name}
                value={value}
                checked={checked}
                required={required}
                onChange={(e) => onChange(e.target.checked)}
                className="mt-0.5 size-4 shrink-0 accent-[var(--brand)]"
            />
            <span className="min-w-0 flex-1">{children}</span>
        </label>
    );
}

export function Toggle({ checked, onChange, label }: { checked: boolean; onChange: (v: boolean) => void; label: string }) {
    return (
        <button
            type="button"
            role="switch"
            aria-checked={checked}
            aria-label={label}
            onClick={() => onChange(!checked)}
            className={cx('relative inline-flex h-6 w-11 shrink-0 items-center rounded-full transition', checked ? 'bg-brand' : 'bg-slate-300')}
        >
            <span className={cx('inline-block size-5 rounded-full bg-white shadow transition', checked ? 'translate-x-5.5' : 'translate-x-0.5')} />
        </button>
    );
}

/* ---------- numbers ---------- */
export function Stat({ label, value, href, icon: Icon, tone = 'gray', highlight }: { label: string; value: number | string; href?: string; icon: ComponentType<LucideProps>; tone?: Tone; highlight?: boolean }) {
    const body = (
        <>
            <div className="flex items-center justify-between gap-2">
                <p className="text-sm font-medium text-muted">{label}</p>
                <span className={cx('grid size-8 place-items-center rounded-lg ring-1 ring-inset', TONES[tone])}>
                    <Icon className="size-4" aria-hidden />
                </span>
            </div>
            <p className="mt-3 flex items-end justify-between gap-2 text-3xl font-bold tracking-tight text-ink">
                {value}
                {href && <ChevronRight className="mb-1 size-4 text-muted opacity-0 transition group-hover:translate-x-0.5 group-hover:opacity-100" aria-hidden />}
            </p>
        </>
    );
    const cls = cx('group block rounded-card border bg-panel p-5 shadow-card transition', highlight ? 'border-brand/50 ring-2 ring-brand/15' : 'border-line hover:border-ink/15 hover:shadow-md hover:shadow-ink/[0.04]');
    return href ? <Link href={href} className={cls}>{body}</Link> : <div className={cls}>{body}</div>;
}

export function DescList({ items, className }: { items: [ReactNode, ReactNode][]; className?: string }) {
    return (
        <dl className={cx('grid gap-4', className)}>
            {items.map(([k, v], i) => (
                <div key={i}>
                    <dt className="text-xs font-medium text-muted">{k}</dt>
                    <dd className="mt-1 text-sm font-semibold text-ink">{v ?? '–'}</dd>
                </div>
            ))}
        </dl>
    );
}

/* ---------- lists ---------- */
export function Pagination<T>({ page }: { page: Paginated<T> }) {
    if (page.lastPage <= 1) return null;
    return (
        <nav className="mt-4 flex items-center justify-between gap-3 text-sm" aria-label="Pages">
            <p className="text-muted">
                {page.from}–{page.to} of {page.total}
            </p>
            <div className="flex gap-2">
                {page.prev ? <ButtonLink href={page.prev} variant="secondary" size="sm" icon={ChevronLeft}>Previous</ButtonLink> : <Button variant="secondary" size="sm" disabled icon={ChevronLeft}>Previous</Button>}
                {page.next ? <ButtonLink href={page.next} variant="secondary" size="sm">Next <ChevronRight className="size-3.5" aria-hidden /></ButtonLink> : <Button variant="secondary" size="sm" disabled>Next</Button>}
            </div>
        </nav>
    );
}

/**
 * A table on wider screens; on phones every row becomes a stacked card, each value labelled with its column
 * heading (the first column, usually the name, leads the card without a label). Pages pass rows of <td> as usual.
 */
export function Table({ head, children, empty }: { head: ReactNode[]; children: ReactNode; empty?: ReactNode }) {
    const labels = head.map((h) => (typeof h === 'string' ? h : ''));
    const rows = Children.map(children, (row) => {
        if (!isValidElement<{ children?: ReactNode }>(row)) return row;
        const cells = Children.toArray(row.props.children).map((cell, i) =>
            isValidElement<Record<string, unknown>>(cell) ? cloneElement(cell, { 'data-label': labels[i] ?? '' }) : cell);
        return cloneElement(row, {}, ...cells);
    });
    return (
        <div className="overflow-x-auto max-sm:overflow-visible">
            <table className="w-full text-left text-sm max-sm:block">
                <thead className="max-sm:hidden">
                    <tr>
                        {head.map((h, i) => (
                            <th key={i} className="h-11 border-b border-line bg-line-2/50 px-4 text-xs font-medium whitespace-nowrap text-muted first:pl-5 last:pr-5">{h}</th>
                        ))}
                    </tr>
                </thead>
                <tbody className="stack-table [&>tr]:transition-colors sm:[&>tr:hover]:bg-line-2/40 [&>tr>td]:border-b [&>tr>td]:border-line-2 [&>tr>td]:px-4 [&>tr>td]:py-3 [&>tr>td:first-child]:pl-5 [&>tr>td:last-child]:pr-5 [&>tr:last-child>td]:border-b-0">
                    {rows}
                </tbody>
            </table>
            {empty}
        </div>
    );
}

/* ---------- tabs ---------- */
export type TabDef = { key: string; label: string; count?: number | null; icon?: ComponentType<LucideProps>; alert?: boolean };

/**
 * Which tab is open, kept in the address (?tab=payments) so a refresh or a shared link opens the same tab.
 * Changing tab doesn't reload the page.
 */
export function useTab(keys: string[], fallback = keys[0]): [string, (key: string) => void] {
    const read = () => {
        if (typeof window === 'undefined') return fallback;
        const t = new URLSearchParams(window.location.search).get('tab');
        return t && keys.includes(t) ? t : fallback;
    };
    const [tab, setTab] = useState(read);
    const change = (key: string) => {
        setTab(key);
        try {
            const url = new URL(window.location.href);
            if (key === fallback) url.searchParams.delete('tab'); else url.searchParams.set('tab', key);
            window.history.replaceState(window.history.state, '', url);
        } catch { /* address bar not writable: the tab still changes */ }
    };
    return [tab, change];
}

/** A row of tabs; scrolls sideways on phones instead of wrapping. */
export function Tabs({ tabs, value, onChange, className }: { tabs: TabDef[]; value: string; onChange: (key: string) => void; className?: string }) {
    return (
        <div className={cx('-mx-4 overflow-x-auto px-4 [mask-image:linear-gradient(to_right,black_85%,transparent)] sm:mx-0 sm:px-0 sm:[mask-image:none]', className)}>
            <div role="tablist" className="flex w-max min-w-full gap-5 border-b border-line">
                {tabs.map((t) => {
                    const on = t.key === value;
                    return (
                        <button key={t.key} role="tab" type="button" aria-selected={on} onClick={() => onChange(t.key)}
                            className={cx('relative -mb-px flex items-center gap-2 border-b-2 px-0.5 py-3 text-sm font-medium whitespace-nowrap transition sm:py-2.5',
                                on ? 'border-ink text-ink' : 'border-transparent text-muted hover:text-ink')}>
                            {t.icon && <t.icon className={cx('hidden size-4 sm:block', on ? 'text-brand' : 'text-muted')} aria-hidden />}
                            {t.label}
                            {t.count != null && t.count > 0 && (
                                <span className={cx('rounded-md px-1.5 py-0.5 text-[11px] leading-none font-semibold', t.alert ? 'bg-brand text-white' : 'bg-line-2 text-ink-2 ring-1 ring-line ring-inset')}>{t.count}</span>
                            )}
                        </button>
                    );
                })}
            </div>
        </div>
    );
}
