import { Button, Modal, Select, ToastProvider, useToast } from '@/Components/ui';
import { cn } from '@/lib/cn';
import { Link } from '@inertiajs/react';
import { Loader2, UserMinus, UserPlus } from 'lucide-react';
import { useCallback, useEffect, useState } from 'react';

/**
 * Project share modal (Δ23/Δ25). Owner-or-admin only — the affordance that
 * opens it is already gated on the calling surface (Projects/Index), but the
 * server re-checks via the WorkspacePolicy so a hand-crafted request still
 * 403s.
 *
 * Visibility is per-project (Δ23): "members" = workspace members currently
 * granted access to THIS project; "candidates" = workspace members you can
 * still add. Sharing raises an in-app notification to the member (Δ25,
 * backend-owned). The modal does its own JSON fetches (not Inertia page
 * actions) so it can stay open across grant/revoke without a navigation —
 * toasts surface success/failure inline. Projects/Index doesn't mount a
 * ToastProvider, so the modal brings its own.
 */

type MemberRole = 'owner' | 'admin' | 'editor' | 'viewer';

type ProjectMember = {
    id: number;
    name: string;
    email: string;
    role: string;
};

type MembersResponse = {
    project: { id: number; name: string };
    members: ProjectMember[];
    candidates: ProjectMember[];
};

function roleLabel(role: string): string {
    switch (role) {
        case 'owner':
            return 'Owner';
        case 'admin':
            return 'Admin';
        case 'editor':
            return 'Editor';
        case 'viewer':
            return 'Viewer';
        default:
            return role.charAt(0).toUpperCase() + role.slice(1);
    }
}

/** Owner/Admin → privileged accent tone (mirrors WorkspaceSwitcher's
 *  roleBadgeClasses). Editor/Viewer stay neutral so the common case doesn't
 *  shout. */
function roleBadgeClasses(role: string): string {
    return role === 'owner' || role === 'admin'
        ? 'border-accent/30 bg-accent/10 text-accent'
        : 'border-border bg-muted text-muted-foreground';
}

function csrfToken(): string {
    if (typeof document === 'undefined') return '';
    return (
        document
            .querySelector('meta[name="csrf-token"]')
            ?.getAttribute('content') ?? ''
    );
}

async function jsonFetch(url: string, init?: RequestInit): Promise<Response> {
    return fetch(url, {
        ...init,
        headers: {
            Accept: 'application/json',
            'Content-Type': 'application/json',
            'X-Requested-With': 'XMLHttpRequest',
            'X-CSRF-TOKEN': csrfToken(),
            ...(init?.headers ?? {}),
        },
        credentials: 'same-origin',
    });
}

type ShareProjectModalProps = {
    project: { id: number; name: string };
    onClose: () => void;
};

/**
 * Public entry — wraps the inner body in a local ToastProvider so success /
 * error toasts work even on surfaces (Projects/Index) that don't mount one
 * globally.
 */
export default function ShareProjectModal(props: ShareProjectModalProps) {
    return (
        <ToastProvider>
            <ShareProjectModalBody {...props} />
        </ToastProvider>
    );
}

function ShareProjectModalBody({ project, onClose }: ShareProjectModalProps) {
    const toast = useToast();
    const [loading, setLoading] = useState(true);
    const [members, setMembers] = useState<ProjectMember[]>([]);
    const [candidates, setCandidates] = useState<ProjectMember[]>([]);
    const [pickedUser, setPickedUser] = useState<string>('');
    const [adding, setAdding] = useState(false);
    // Per-user busy flag while a revoke is in flight (keeps the rest of the
    // list interactive).
    const [removingId, setRemovingId] = useState<number | null>(null);
    const [loadError, setLoadError] = useState(false);
    // Share/remove both notify the member by email, so they confirm first —
    // the button stages the action; the confirm modal commits it.
    const [pending, setPending] = useState<
        { type: 'add' | 'remove'; member: ProjectMember } | null
    >(null);

    const load = useCallback(async () => {
        setLoading(true);
        setLoadError(false);
        try {
            const res = await jsonFetch(`/projects/${project.id}/members`);
            if (!res.ok) throw new Error(`HTTP ${res.status}`);
            const data: MembersResponse = await res.json();
            setMembers(data.members ?? []);
            setCandidates(data.candidates ?? []);
        } catch {
            setLoadError(true);
        } finally {
            setLoading(false);
        }
    }, [project.id]);

    useEffect(() => {
        void load();
    }, [load]);

    const addMember = async () => {
        if (!pickedUser || adding) return;
        const userId = Number(pickedUser);
        const candidate = candidates.find((c) => c.id === userId);
        setAdding(true);
        try {
            const res = await jsonFetch(`/projects/${project.id}/members`, {
                method: 'POST',
                body: JSON.stringify({ user: userId }),
            });
            if (!res.ok) throw new Error(`HTTP ${res.status}`);
            // Optimistic local move; reconcile from the server response if it
            // ships the refreshed lists, else fall back to local state.
            const data = (await res
                .json()
                .catch(() => null)) as Partial<MembersResponse> | null;
            if (data?.members && data?.candidates) {
                setMembers(data.members);
                setCandidates(data.candidates);
            } else if (candidate) {
                setMembers((prev) => [...prev, candidate]);
                setCandidates((prev) => prev.filter((c) => c.id !== userId));
            }
            setPickedUser('');
            toast.success(
                candidate
                    ? `Shared with ${candidate.name}.`
                    : 'Project shared.',
            );
        } catch {
            toast.error('Could not share the project. Try again.');
        } finally {
            setAdding(false);
        }
    };

    const removeMember = async (member: ProjectMember) => {
        if (removingId !== null) return;
        setRemovingId(member.id);
        try {
            const res = await jsonFetch(
                `/projects/${project.id}/members/${member.id}`,
                { method: 'DELETE' },
            );
            if (!res.ok) throw new Error(`HTTP ${res.status}`);
            const data = (await res
                .json()
                .catch(() => null)) as Partial<MembersResponse> | null;
            if (data?.members && data?.candidates) {
                setMembers(data.members);
                setCandidates(data.candidates);
            } else {
                setMembers((prev) => prev.filter((m) => m.id !== member.id));
                setCandidates((prev) => [...prev, member]);
            }
            toast.success(`Removed ${member.name}.`);
        } catch {
            toast.error('Could not remove access. Try again.');
        } finally {
            setRemovingId(null);
        }
    };

    const confirmPending = async () => {
        if (!pending) return;
        if (pending.type === 'add') await addMember();
        else await removeMember(pending.member);
        setPending(null);
    };

    const actionBusy = adding || removingId !== null;

    return (
        <>
        <Modal open onClose={onClose} size="md" data-testid="share-project-modal">
            <Modal.Header
                eyebrow="Share"
                title={project.name}
                subtitle="Choose which workspace members can see this project. Editors can edit; viewers are read-only."
            />
            <Modal.Body>
                {loading ? (
                    <div
                        className="flex items-center justify-center gap-2 py-10 text-sm text-muted-foreground"
                        data-testid="share-project-loading"
                    >
                        <Loader2 className="h-4 w-4 animate-spin" strokeWidth={2} />
                        Loading members…
                    </div>
                ) : loadError ? (
                    <div className="flex flex-col items-center gap-3 py-8 text-center">
                        <p className="text-sm text-muted-foreground">
                            Couldn't load project members.
                        </p>
                        <Button
                            type="button"
                            variant="ghost"
                            onClick={() => void load()}
                            data-testid="share-project-retry"
                        >
                            Retry
                        </Button>
                    </div>
                ) : (
                    <>
                        {/* Add-member row */}
                        <div className="space-y-2">
                            <span className="text-[11px] font-medium text-muted-foreground">
                                Add a member
                            </span>
                            {candidates.length === 0 ? (
                                <p
                                    className="rounded-md border border-dashed border-border bg-muted/20 px-3 py-2.5 text-[11px] text-muted-foreground"
                                    data-testid="share-project-no-candidates"
                                >
                                    Every workspace member already has access.
                                    Invite more people from{' '}
                                    <Link
                                        href="/workspace/members"
                                        className="font-medium text-accent hover:underline"
                                        data-testid="share-project-manage-members-link"
                                    >
                                        Manage members
                                    </Link>
                                    .
                                </p>
                            ) : (
                                <div className="flex items-center gap-2">
                                    <Select
                                        value={pickedUser}
                                        onChange={(e) =>
                                            setPickedUser(e.target.value)
                                        }
                                        wrapperClassName="flex-1"
                                        data-testid="share-project-candidate-select"
                                        aria-label="Select a member to share with"
                                    >
                                        <option value="">
                                            Select a member…
                                        </option>
                                        {candidates.map((c) => (
                                            <option key={c.id} value={c.id}>
                                                {c.name} ({roleLabel(c.role)}) ·{' '}
                                                {c.email}
                                            </option>
                                        ))}
                                    </Select>
                                    <Button
                                        type="button"
                                        onClick={() => {
                                            const c = candidates.find(
                                                (x) => x.id === Number(pickedUser),
                                            );
                                            if (c)
                                                setPending({
                                                    type: 'add',
                                                    member: c,
                                                });
                                        }}
                                        disabled={!pickedUser || adding}
                                        data-testid="share-project-add"
                                    >
                                        {adding ? (
                                            <Loader2
                                                className="h-4 w-4 animate-spin"
                                                strokeWidth={2}
                                            />
                                        ) : (
                                            <>
                                                <UserPlus
                                                    className="h-4 w-4"
                                                    strokeWidth={2}
                                                />
                                                Share
                                            </>
                                        )}
                                    </Button>
                                </div>
                            )}
                        </div>

                        {/* Current members */}
                        <div className="space-y-2">
                            <span className="text-[11px] font-medium text-muted-foreground">
                                {members.length === 0
                                    ? 'No one has access yet'
                                    : `${members.length} ${members.length === 1 ? 'member has' : 'members have'} access`}
                            </span>
                            {members.length === 0 ? (
                                <p
                                    className="rounded-md border border-dashed border-border bg-muted/20 px-3 py-4 text-center text-[11px] text-muted-foreground"
                                    data-testid="share-project-members-empty"
                                >
                                    Share this project to give workspace members
                                    access.
                                </p>
                            ) : (
                                <ul className="flex flex-col divide-y divide-border/60 overflow-hidden rounded-md border border-border">
                                    {members.map((m) => {
                                        const isOwnerOrAdmin =
                                            m.role === 'owner' ||
                                            m.role === 'admin';
                                        const busy = removingId === m.id;
                                        return (
                                            <li
                                                key={m.id}
                                                data-testid={`share-project-member-${m.id}`}
                                                className="flex items-center justify-between gap-3 px-3 py-2.5"
                                            >
                                                <div className="flex min-w-0 flex-col gap-0.5">
                                                    <span className="flex items-center gap-2">
                                                        <span className="truncate text-[12px] font-medium text-foreground">
                                                            {m.name}
                                                        </span>
                                                        <span
                                                            className={cn(
                                                                'shrink-0 rounded-sm border px-1 text-[9px] font-medium uppercase tracking-wider',
                                                                roleBadgeClasses(
                                                                    m.role,
                                                                ),
                                                            )}
                                                        >
                                                            {roleLabel(m.role)}
                                                        </span>
                                                    </span>
                                                    <span className="truncate text-[11px] text-muted-foreground">
                                                        {m.email}
                                                    </span>
                                                </div>
                                                {isOwnerOrAdmin ? (
                                                    // Owner/admin see all
                                                    // projects via short-circuit
                                                    // (Δ24) — there's no grant
                                                    // row to revoke, so the
                                                    // remove affordance is
                                                    // suppressed.
                                                    <span
                                                        className="shrink-0 text-[10px] text-muted-foreground/70"
                                                        data-testid={`share-project-member-locked-${m.id}`}
                                                    >
                                                        Full access
                                                    </span>
                                                ) : (
                                                    <button
                                                        type="button"
                                                        onClick={() =>
                                                            setPending({
                                                                type: 'remove',
                                                                member: m,
                                                            })
                                                        }
                                                        disabled={
                                                            removingId !== null
                                                        }
                                                        data-testid={`share-project-remove-${m.id}`}
                                                        aria-label={`Remove ${m.name}`}
                                                        className="inline-flex h-7 shrink-0 items-center gap-1 rounded-md border border-border px-2 text-[11px] font-medium text-muted-foreground transition-colors hover:border-destructive/40 hover:bg-destructive/10 hover:text-destructive disabled:opacity-50"
                                                    >
                                                        {busy ? (
                                                            <Loader2
                                                                className="h-3 w-3 animate-spin"
                                                                strokeWidth={2}
                                                            />
                                                        ) : (
                                                            <UserMinus
                                                                className="h-3 w-3"
                                                                strokeWidth={2}
                                                            />
                                                        )}
                                                        Remove
                                                    </button>
                                                )}
                                            </li>
                                        );
                                    })}
                                </ul>
                            )}
                        </div>
                    </>
                )}
            </Modal.Body>
            <Modal.Footer>
                <Button
                    type="button"
                    variant="ghost"
                    onClick={onClose}
                    data-testid="share-project-done"
                >
                    Done
                </Button>
            </Modal.Footer>
        </Modal>

        {/* Confirm — both share + remove notify the member by email. */}
        <Modal
            open={pending !== null}
            onClose={() => setPending(null)}
            size="sm"
            closeOnBackdrop={false}
        >
            <Modal.Header
                eyebrow={pending?.type === 'remove' ? 'Remove access' : 'Share project'}
                eyebrowClassName={
                    pending?.type === 'remove' ? 'text-destructive' : undefined
                }
                title={pending?.member.name ?? ''}
                subtitle={
                    pending?.type === 'remove'
                        ? `Remove ${pending.member.name}'s access to "${project.name}"? They'll be notified by email.`
                        : pending
                          ? `Give ${pending.member.name} access to "${project.name}"? They'll be notified by email.`
                          : ''
                }
            />
            <Modal.Footer>
                <Button
                    type="button"
                    variant="ghost"
                    onClick={() => setPending(null)}
                >
                    Cancel
                </Button>
                <Button
                    type="button"
                    variant={pending?.type === 'remove' ? 'danger' : 'primary'}
                    disabled={actionBusy}
                    onClick={() => void confirmPending()}
                    data-testid="share-project-confirm"
                >
                    {pending?.type === 'remove' ? 'Remove' : 'Share'}
                </Button>
            </Modal.Footer>
        </Modal>
        </>
    );
}

// Re-export the role type so callers (Projects/Index gate) can reuse it.
export type { MemberRole };
