import { Popover } from '@/Components/ui';
import { cn } from '@/lib/cn';
import type { SharedProps } from '@/types/inertia';
import { router, usePage } from '@inertiajs/react';
import {
    Bell,
    Check,
    FilePlus2,
    FolderMinus,
    FolderPlus,
    Mail,
    ShieldCheck,
    Trash2,
    type LucideIcon,
} from 'lucide-react';
import type { JSX } from 'react';

// Category → icon + tone (matches /notifications + the activity log).
function iconFor(category: string | null): { Icon: LucideIcon; tone: string } {
    switch (category) {
        case 'project.created':
            return { Icon: FilePlus2, tone: 'text-success' };
        case 'project.deleted':
            return { Icon: Trash2, tone: 'text-destructive' };
        case 'project.shared':
            return { Icon: FolderPlus, tone: 'text-success' };
        case 'project.revoked':
            return { Icon: FolderMinus, tone: 'text-destructive' };
        case 'role':
            return { Icon: ShieldCheck, tone: 'text-accent' };
        case 'invite':
            return { Icon: Mail, tone: 'text-accent' };
        default:
            return { Icon: Bell, tone: 'text-muted-foreground' };
    }
}

/**
 * In-app notification item (Δ25). Backed by Laravel's built-in
 * database-notifications channel — `data` is flattened to title/body/url
 * server-side, so the panel doesn't have to know per-notification-type
 * payload shapes. Project-share is the first (and currently only) event;
 * design stays minimal so it doesn't collide with `announcements-plan.md`
 * (which owns the richer feed later).
 */
type NotificationItem = {
    id: string;
    title: string;
    body: string;
    url: string | null;
    /** Workspace the notification belongs to — rendered as a badge so a user
     *  in multiple workspaces can tell them apart. Null for legacy rows. */
    workspace: string | null;
    /** Notification category — drives the row icon. Null for legacy rows. */
    category: string | null;
    read_at: string | null;
    created_at: string;
};

/**
 * Shared `notifications` prop. SaaS app-surfaces only; `null` in self-hosted
 * (the bell renders nothing there). Shipped by `HandleInertiaRequests`.
 * Read directly off the page props each render (Δ10) — never seeded into
 * local `useState` (the always-mounted-chrome staleness trap).
 */
export type NotificationsProp = {
    unread_count: number;
    items: NotificationItem[];
} | null;

const RELATIVE_FMT = new Intl.RelativeTimeFormat('en', { numeric: 'auto' });

function relativeTime(iso: string): string {
    const ms = new Date(iso).getTime();
    if (Number.isNaN(ms)) return '';
    const diffMs = ms - Date.now();
    const absMin = Math.abs(diffMs) / 60_000;
    if (absMin < 1) return 'just now';
    if (absMin < 60) return RELATIVE_FMT.format(Math.round(diffMs / 60_000), 'minute');
    if (absMin < 60 * 24) return RELATIVE_FMT.format(Math.round(diffMs / 3_600_000), 'hour');
    if (absMin < 60 * 24 * 30) return RELATIVE_FMT.format(Math.round(diffMs / 86_400_000), 'day');
    if (absMin < 60 * 24 * 365)
        return RELATIVE_FMT.format(Math.round(diffMs / (86_400_000 * 30)), 'month');
    return RELATIVE_FMT.format(Math.round(diffMs / (86_400_000 * 365)), 'year');
}

export default function NotificationBell(): JSX.Element | null {
    // Δ10 — read the prop directly each render. AppNav is always mounted, so
    // partial reloads refresh this prop without a remount; seeding local
    // state from it once would go stale (the toolbar sync-toggle bug).
    const { notifications } = usePage<
        SharedProps & { notifications: NotificationsProp }
    >().props;

    // Self-hosted (prop null) or a not-yet-shipped prop → render nothing.
    if (!notifications) return null;

    const { unread_count, items } = notifications;
    const hasUnread = unread_count > 0;

    // Mark-read via plain fetch (NOT Inertia router) — a `router.post` here
    // races/cancels the navigating `router.visit` below, which left the page
    // with partial props (auth undefined → crash). fetch is fire-and-forget
    // and never touches Inertia's single-visit queue; `keepalive` lets it
    // finish even when we navigate away immediately after.
    const postRead = (url: string): Promise<unknown> => {
        const csrf =
            document
                .querySelector('meta[name="csrf-token"]')
                ?.getAttribute('content') ?? '';
        return fetch(url, {
            method: 'POST',
            credentials: 'same-origin',
            keepalive: true,
            headers: { 'X-CSRF-TOKEN': csrf, Accept: 'application/json' },
        }).catch(() => undefined);
    };

    const markRead = async (id: string) => {
        await postRead(`/notifications/${id}/read`);
        // Refresh just the shared prop; the bell re-reads it (Δ10).
        router.reload({ only: ['notifications'] });
    };

    const openItem = (item: NotificationItem) => {
        // Fire-and-forget mark-read, then navigate. The destination render
        // ships a fresh `notifications` prop, so we don't gate the visit on it.
        if (!item.read_at) void postRead(`/notifications/${item.id}/read`);
        if (item.url) router.visit(item.url);
    };

    const markAllRead = async () => {
        await postRead('/notifications/read-all');
        router.reload({ only: ['notifications'] });
    };

    return (
        <Popover
            align="end"
            trigger={({ open, toggle }) => (
                <button
                    type="button"
                    onClick={toggle}
                    data-testid="notification-bell"
                    aria-expanded={open}
                    aria-label={
                        hasUnread
                            ? `Notifications (${unread_count} unread)`
                            : 'Notifications'
                    }
                    className={cn(
                        'relative inline-flex h-8 w-8 items-center justify-center rounded-md transition-colors',
                        open
                            ? 'bg-muted text-foreground'
                            : 'text-muted-foreground hover:bg-muted/60 hover:text-foreground',
                    )}
                >
                    <Bell className="h-4 w-4" strokeWidth={2} />
                    {hasUnread && (
                        <span
                            className="absolute -right-0.5 -top-0.5 flex h-4 min-w-4 items-center justify-center rounded-full bg-accent px-1 text-[9px] font-semibold text-accent-foreground"
                            data-testid="notification-bell-badge"
                            aria-hidden
                        >
                            {unread_count > 9 ? '9+' : unread_count}
                        </span>
                    )}
                </button>
            )}
        >
            <div className="flex w-[320px] max-w-[calc(100vw-1rem)] flex-col">
                <div className="flex items-center justify-between gap-2 px-2 pb-1 pt-1">
                    <span className="text-[11px] font-medium text-foreground">
                        Notifications
                    </span>
                    {hasUnread && (
                        <button
                            type="button"
                            onClick={(e) => {
                                e.stopPropagation();
                                markAllRead();
                            }}
                            data-testid="notification-mark-all-read"
                            className="inline-flex items-center gap-1 rounded-sm px-1 text-[10px] font-medium text-muted-foreground transition-colors hover:text-foreground"
                        >
                            <Check className="h-3 w-3" strokeWidth={2.5} />
                            Mark all read
                        </button>
                    )}
                </div>

                {items.length === 0 ? (
                    <p
                        className="px-2 py-6 text-center text-[11px] text-muted-foreground"
                        data-testid="notification-empty"
                    >
                        You're all caught up.
                    </p>
                ) : (
                    <ul className="max-h-[360px] space-y-1 overflow-y-auto py-1 sleek-scroll">
                        {items.map((item) => {
                            const unread = !item.read_at;
                            const clickable = Boolean(item.url);
                            const { Icon, tone } = iconFor(item.category);
                            return (
                                <li key={item.id}>
                                    <button
                                        type="button"
                                        onClick={(e) => {
                                            // Stateful actions live inside the
                                            // panel; Popover auto-closes on any
                                            // panel click, which is fine for a
                                            // navigating item but we stop the
                                            // bubble so mark-read (no nav) can
                                            // run without an immediate dismiss
                                            // race when there's no url.
                                            if (!clickable) e.stopPropagation();
                                            if (clickable) openItem(item);
                                            else if (unread) markRead(item.id);
                                        }}
                                        data-testid={`notification-item-${item.id}`}
                                        data-unread={unread ? 'true' : undefined}
                                        className={cn(
                                            'flex w-full items-start gap-2 rounded-md px-2 py-2 text-left transition-colors hover:bg-muted',
                                            unread ? 'bg-accent/[0.06]' : '',
                                        )}
                                    >
                                        <span className="relative mt-0.5 shrink-0">
                                            <Icon
                                                className={cn('h-4 w-4', tone)}
                                                strokeWidth={2}
                                            />
                                            {unread && (
                                                <span
                                                    className="absolute -right-1 -top-1 h-1.5 w-1.5 rounded-full bg-accent"
                                                    aria-hidden
                                                />
                                            )}
                                        </span>
                                        <span className="flex min-w-0 flex-col gap-0.5">
                                            <span
                                                className={cn(
                                                    'truncate text-[12px]',
                                                    unread
                                                        ? 'font-medium text-foreground'
                                                        : 'text-foreground/90',
                                                )}
                                            >
                                                {item.title}
                                            </span>
                                            {item.body && (
                                                <span className="line-clamp-2 text-[11px] leading-snug text-muted-foreground">
                                                    {item.body}
                                                </span>
                                            )}
                                            <span className="flex flex-wrap items-center gap-1.5 text-[10px] text-muted-foreground/70">
                                                {item.workspace && (
                                                    <span className="rounded-sm border border-border bg-muted px-1 text-[9px] font-medium text-muted-foreground">
                                                        {item.workspace}
                                                    </span>
                                                )}
                                                <span>
                                                    {relativeTime(item.created_at)}
                                                </span>
                                            </span>
                                        </span>
                                    </button>
                                </li>
                            );
                        })}
                    </ul>
                )}
                <div className="mt-0.5 px-2 pt-1">
                    <button
                        type="button"
                        onClick={() => router.visit('/notifications')}
                        data-testid="notification-view-all"
                        className="block w-full rounded-sm px-1 py-1.5 text-center text-[11px] font-medium text-accent transition-colors hover:bg-muted"
                    >
                        View all
                    </button>
                </div>
            </div>
        </Popover>
    );
}
