// src/components/account-settings.tsx
"use client";

import {
  ClockIcon,
  GlobeIcon,
  LockIcon,
  MonitorIcon,
  SmartphoneIcon,
  TabletIcon,
  TriangleAlertIcon,
  UploadIcon,
} from "lucide-react";
import { useCallback, useEffect, useState } from "react";
import { toast } from "sonner";
import { LanguageSelector } from "@/components/i18n/language-selector";
import { ActivationRestore } from "@/components/settings/activation-restore";
import {
  AlertDialog,
  AlertDialogAction,
  AlertDialogCancel,
  AlertDialogContent,
  AlertDialogDescription,
  AlertDialogFooter,
  AlertDialogHeader,
  AlertDialogTitle,
  AlertDialogTrigger,
} from "@/components/ui/alert-dialog";
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
import {
  Card,
  CardContent,
  CardDescription,
  CardFooter,
  CardHeader,
  CardTitle,
} from "@/components/ui/card";
import { Field, FieldGroup, FieldLabel } from "@/components/ui/field";
import { Input } from "@/components/ui/input";
import { Skeleton } from "@/components/ui/skeleton";
import { setInitialPasswordAction } from "@/lib/account/password-actions";
import {
  MIN_PASSWORD_LENGTH,
  validatePassword,
} from "@/lib/auth/password-policy";
import {
  isValidUsername,
  MAX_USERNAME_LENGTH,
  MIN_USERNAME_LENGTH,
} from "@/lib/auth/username-policy";
import { authClient } from "@/lib/auth-client";
import { cn } from "@/lib/utils";
import { TwoFactorSettings } from "./two-factor-settings";

type AccountUser = {
  id: string;
  name: string;
  email: string;
  image?: string | null;
  twoFactorEnabled: boolean;
  // Username plugin: `username` is the normalized handle, `displayUsername`
  // keeps the original casing. Both nullable (the account may not have one).
  username?: string | null;
  displayUsername?: string | null;
};

type SessionRow = {
  id: string;
  token: string;
  createdAt: string | Date;
  updatedAt: string | Date;
  expiresAt: string | Date;
  ipAddress?: string | null;
  userAgent?: string | null;
};

function initials(name: string, email: string) {
  const source = name.trim() || email;
  const parts = source.split(/\s+/).filter(Boolean);
  if (parts.length >= 2) {
    return (parts[0][0] + parts[1][0]).toUpperCase();
  }
  return source.slice(0, 2).toUpperCase();
}

function formatDate(value: string | Date) {
  const date = typeof value === "string" ? new Date(value) : value;
  return new Intl.DateTimeFormat("fr-FR", {
    dateStyle: "medium",
    timeStyle: "short",
  }).format(date);
}

// Turn a raw User-Agent string into a human label like "Chrome sur macOS".
// Order matters: Edge/Opera advertise "Chrome" too, and iOS browsers all
// advertise "Safari", so the more specific tokens are tested first. Returns
// "Appareil inconnu" when nothing matches (or the UA is empty).
function formatUserAgent(ua?: string | null): {
  label: string;
  kind: "desktop" | "mobile" | "tablet";
} {
  if (!ua) return { label: "Appareil inconnu", kind: "desktop" };

  // OS detection.
  let os = "un appareil inconnu";
  if (/iPhone/.test(ua)) os = "iOS";
  else if (/iPad/.test(ua)) os = "iPadOS";
  else if (/Android/.test(ua)) os = "Android";
  else if (/Windows/.test(ua)) os = "Windows";
  else if (/Mac OS X|Macintosh/.test(ua)) os = "macOS";
  else if (/CrOS/.test(ua)) os = "ChromeOS";
  else if (/Linux/.test(ua)) os = "Linux";

  // Browser detection (specific → generic).
  let browser = "Navigateur";
  if (/Edg[A-Z]?\//.test(ua)) browser = "Edge";
  else if (/OPR\/|Opera/.test(ua)) browser = "Opera";
  else if (/SamsungBrowser/.test(ua)) browser = "Samsung Internet";
  else if (/Firefox\/|FxiOS/.test(ua)) browser = "Firefox";
  else if (/Chrome\/|CriOS/.test(ua)) browser = "Chrome";
  else if (/Safari\//.test(ua)) browser = "Safari";

  const kind: "desktop" | "mobile" | "tablet" = /iPad|Tablet/.test(ua)
    ? "tablet"
    : /Mobi|iPhone|Android.*Mobile/.test(ua)
      ? "mobile"
      : "desktop";

  return { label: `${browser} sur ${os}`, kind };
}

export function AccountSettings({
  user,
  hasPassword,
  activationDismissed,
}: {
  user: AccountUser;
  hasPassword: boolean;
  /** La checklist de démarrage est-elle masquée ? Lu côté serveur par la page. */
  activationDismissed: boolean;
}) {
  return (
    <>
      <div className="mb-1 flex flex-col gap-1.5">
        <h1 className="font-heading font-semibold text-2xl tracking-tight">
          Compte
        </h1>
        <p className="text-muted-foreground text-sm">
          Gère ton profil, ta connexion et la sécurité de ton compte.
        </p>
      </div>
      <ProfileCard user={user} />
      <UsernameCard user={user} />
      <LanguageCard />
      <ActivationRestore initialDismissed={activationDismissed} />
      <ChangeEmailCard user={user} />
      <PasswordCard hasPassword={hasPassword} />
      <TwoFactorSettings enabled={user.twoFactorEnabled} />
      <SessionsCard />
      <DangerZoneCard />
    </>
  );
}

function LanguageCard() {
  return (
    <Card>
      <CardHeader>
        <CardTitle>Langue / Language</CardTitle>
        <CardDescription>La langue d'affichage de l'interface.</CardDescription>
      </CardHeader>
      <CardContent>
        <Field>
          <FieldLabel htmlFor="account-language">Langue / Language</FieldLabel>
          <LanguageSelector className="w-full sm:w-[240px]" />
        </Field>
      </CardContent>
    </Card>
  );
}

// The design mock shows the email field "locked" with an amber warning, but in
// THIS app `user.changeEmail.enabled` is true (auth.ts) and `authClient.changeEmail`
// genuinely sends a confirmation link to the new address. So we keep the working
// functional form rather than downgrading it to a static locked field.
function ChangeEmailCard({ user }: { user: AccountUser }) {
  const [newEmail, setNewEmail] = useState("");
  const [pending, setPending] = useState(false);
  const [sent, setSent] = useState(false);

  const dirty =
    newEmail.trim().length > 0 &&
    newEmail.trim().toLowerCase() !== user.email.trim().toLowerCase();

  async function submit(e: React.FormEvent) {
    e.preventDefault();
    setPending(true);
    setSent(false);
    const res = await authClient.changeEmail({
      newEmail: newEmail.trim(),
      callbackURL: `${window.location.origin}/settings/account`,
    });
    setPending(false);
    if (res.error) {
      toast.error(res.error.message ?? "Impossible de changer ton email.");
      return;
    }
    setSent(true);
    toast.success("Lien de confirmation envoyé à la nouvelle adresse.");
  }

  return (
    <Card>
      <CardHeader>
        <CardTitle>Adresse email</CardTitle>
        <CardDescription>
          L'email associé à ton compte, utilisé pour la connexion.
        </CardDescription>
      </CardHeader>
      <form onSubmit={submit}>
        <CardContent>
          <FieldGroup>
            <Field>
              <FieldLabel htmlFor="current-email">Email actuel</FieldLabel>
              <Input
                id="current-email"
                type="email"
                value={user.email}
                readOnly
                disabled
                autoComplete="email"
              />
            </Field>
            <Field>
              <FieldLabel htmlFor="new-email">
                Nouvelle adresse email
              </FieldLabel>
              <Input
                id="new-email"
                type="email"
                value={newEmail}
                onChange={(e) => {
                  setNewEmail(e.target.value);
                  setSent(false);
                }}
                autoComplete="email"
                placeholder="nouvelle@adresse.com"
                required
              />
              <p className="text-muted-foreground text-xs">
                L'email actuel reste actif tant que la nouvelle adresse n'est
                pas confirmée.
              </p>
            </Field>
          </FieldGroup>

          {sent ? (
            <p className="mt-4 text-emerald-600 text-sm dark:text-emerald-400">
              Un lien de confirmation a été envoyé à la nouvelle adresse. Clique
              dessus pour l'activer.
            </p>
          ) : null}
        </CardContent>
        <CardFooter className="justify-end">
          <Button type="submit" disabled={pending || !dirty}>
            {pending ? "Envoi…" : "Enregistrer"}
          </Button>
        </CardFooter>
      </form>
    </Card>
  );
}

function DangerZoneCard() {
  const [pending, setPending] = useState(false);
  const [sent, setSent] = useState(false);

  async function confirmDelete() {
    setPending(true);
    setSent(false);
    const res = await authClient.deleteUser({
      callbackURL: `${window.location.origin}/login`,
    });
    setPending(false);
    if (res.error) {
      toast.error(res.error.message ?? "Impossible de supprimer ton compte.");
      return;
    }
    setSent(true);
    toast.success(
      "Email de confirmation envoyé pour finaliser la suppression.",
    );
  }

  return (
    <Card className="border-destructive/40 bg-destructive/[0.03]">
      <CardHeader>
        <CardTitle className="flex items-center gap-2 text-destructive">
          <TriangleAlertIcon className="size-[17px]" />
          Zone de danger
        </CardTitle>
      </CardHeader>
      <CardFooter className="justify-between gap-4 border-destructive/30 border-t">
        <div className="flex flex-col gap-0.5">
          <span className="font-medium text-foreground text-sm">
            Supprimer le compte
          </span>
          <span className="text-muted-foreground text-sm">
            La suppression de ton compte est définitive et irréversible.
          </span>
        </div>
        <AlertDialog>
          <AlertDialogTrigger asChild>
            <Button
              variant="destructive"
              className="shrink-0"
              disabled={pending || sent}
            >
              {pending ? "Suppression…" : "Supprimer mon compte"}
            </Button>
          </AlertDialogTrigger>
          <AlertDialogContent>
            <AlertDialogHeader>
              <AlertDialogTitle>Supprimer ton compte ?</AlertDialogTitle>
              <AlertDialogDescription>
                Cette action est irréversible. Toutes tes données seront
                définitivement supprimées. Un email de confirmation te sera
                envoyé pour finaliser la suppression.
              </AlertDialogDescription>
            </AlertDialogHeader>
            <AlertDialogFooter>
              <AlertDialogCancel>Annuler</AlertDialogCancel>
              <AlertDialogAction variant="destructive" onClick={confirmDelete}>
                Supprimer définitivement
              </AlertDialogAction>
            </AlertDialogFooter>
          </AlertDialogContent>
        </AlertDialog>
      </CardFooter>
    </Card>
  );
}

function ProfileCard({ user }: { user: AccountUser }) {
  const [name, setName] = useState(user.name);
  const [pending, setPending] = useState(false);

  const dirty = name.trim() !== user.name.trim();

  async function submit(e: React.FormEvent) {
    e.preventDefault();
    setPending(true);
    const res = await authClient.updateUser({ name: name.trim() });
    setPending(false);
    if (res.error) {
      toast.error(
        res.error.message ?? "Impossible de mettre à jour ton profil.",
      );
      return;
    }
    toast.success("Profil enregistré.");
  }

  return (
    <Card>
      <CardHeader>
        <CardTitle>Profil</CardTitle>
        <CardDescription>
          Ton nom affiché et ton avatar dans l'application.
        </CardDescription>
      </CardHeader>
      <form onSubmit={submit}>
        <CardContent className="flex flex-col gap-6">
          <div className="flex items-center gap-4">
            <Avatar className="size-16">
              {user.image ? (
                <AvatarImage src={user.image} alt={user.name} />
              ) : null}
              <AvatarFallback className="text-lg">
                {initials(user.name, user.email)}
              </AvatarFallback>
            </Avatar>
            <div className="flex flex-col gap-2">
              {/* Avatar upload requires file storage, which isn't wired yet. */}
              <Button
                type="button"
                variant="outline"
                size="sm"
                className="w-fit"
                disabled
              >
                <UploadIcon className="size-4" />
                Changer l'avatar
              </Button>
              <p className="text-muted-foreground text-xs">
                Le stockage de fichiers n'est pas encore disponible.
              </p>
            </div>
          </div>

          <FieldGroup>
            <Field>
              <FieldLabel htmlFor="account-name">Nom affiché</FieldLabel>
              <Input
                id="account-name"
                value={name}
                onChange={(e) => setName(e.target.value)}
                autoComplete="name"
                required
              />
            </Field>
          </FieldGroup>
        </CardContent>
        <CardFooter className="justify-end gap-3">
          {dirty ? (
            <Button
              type="button"
              variant="outline"
              onClick={() => setName(user.name)}
              disabled={pending}
            >
              Annuler
            </Button>
          ) : null}
          <Button type="submit" disabled={pending || !dirty || !name.trim()}>
            {pending ? "Enregistrement…" : "Enregistrer"}
          </Button>
        </CardFooter>
      </form>
    </Card>
  );
}

// Lets a signed-in user set or change their public username. The display value
// keeps its original casing (displayUsername); availability + format are checked
// inline before updateUser({ username }) is allowed.
function UsernameCard({ user }: { user: AccountUser }) {
  const current = user.displayUsername ?? user.username ?? "";
  const [username, setUsername] = useState(current);
  const [status, setStatus] = useState<
    "idle" | "invalid" | "checking" | "available" | "taken"
  >("idle");
  const [pending, setPending] = useState(false);

  const handle = username.trim();
  const dirty = handle.toLowerCase() !== current.toLowerCase();

  // Same debounced availability check as signup — but skip it while the value
  // still matches the user's own current handle (that's not "taken").
  useEffect(() => {
    if (!dirty || !handle) {
      setStatus("idle");
      return;
    }
    if (
      handle.length < MIN_USERNAME_LENGTH ||
      handle.length > MAX_USERNAME_LENGTH ||
      !isValidUsername(handle)
    ) {
      setStatus("invalid");
      return;
    }
    setStatus("checking");
    const id = setTimeout(async () => {
      const res = await authClient.isUsernameAvailable({ username: handle });
      setStatus(res.data?.available ? "available" : "taken");
    }, 400);
    return () => clearTimeout(id);
  }, [handle, dirty]);

  async function submit(e: React.FormEvent) {
    e.preventDefault();
    if (status !== "available") return;
    setPending(true);
    const res = await authClient.updateUser({ username: handle });
    setPending(false);
    if (res.error) {
      toast.error(
        res.error.message ??
          "Impossible de mettre à jour ton nom d'utilisateur.",
      );
      return;
    }
    setStatus("idle");
    toast.success("Nom d'utilisateur enregistré.");
  }

  return (
    <Card>
      <CardHeader>
        <CardTitle>Nom d'utilisateur</CardTitle>
        <CardDescription>
          Un identifiant public, utilisable pour te connecter à la place de ton
          email.
        </CardDescription>
      </CardHeader>
      <form onSubmit={submit}>
        <CardContent>
          <FieldGroup>
            <Field>
              <FieldLabel htmlFor="account-username">
                Nom d'utilisateur
              </FieldLabel>
              <Input
                id="account-username"
                value={username}
                onChange={(e) => setUsername(e.target.value)}
                autoComplete="username"
                placeholder="ton_handle"
                aria-invalid={status === "invalid" || undefined}
              />
              {status === "invalid" ? (
                <p className="text-destructive text-xs">
                  3 à 30 caractères : lettres, chiffres, _ et . uniquement.
                </p>
              ) : status === "taken" ? (
                <p className="text-destructive text-xs">
                  Ce nom d'utilisateur est déjà pris.
                </p>
              ) : status === "checking" ? (
                <p className="text-muted-foreground text-xs">Vérification…</p>
              ) : status === "available" ? (
                <p className="text-xs text-emerald-600 dark:text-emerald-400">
                  Nom d'utilisateur disponible.
                </p>
              ) : (
                <p className="text-muted-foreground text-xs">
                  Lettres, chiffres, _ et . — 3 à 30 caractères.
                </p>
              )}
            </Field>
          </FieldGroup>
        </CardContent>
        <CardFooter className="justify-end gap-3">
          {dirty ? (
            <Button
              type="button"
              variant="outline"
              onClick={() => setUsername(current)}
              disabled={pending}
            >
              Annuler
            </Button>
          ) : null}
          <Button type="submit" disabled={pending || status !== "available"}>
            {pending ? "Enregistrement…" : "Enregistrer"}
          </Button>
        </CardFooter>
      </form>
    </Card>
  );
}

function PasswordCard({
  hasPassword: initialHasPassword,
}: {
  hasPassword: boolean;
}) {
  // Bascule locale : après un « définir » réussi, la carte passe en mode
  // « changer » sans re-render serveur.
  const [hasPassword, setHasPassword] = useState(initialHasPassword);
  if (!hasPassword) {
    return <SetPasswordCard onSet={() => setHasPassword(true)} />;
  }
  return <ChangePasswordCard />;
}

/** Compte né sans credential (magic link / social) : définir un PREMIER mot de
 *  passe — pas de « mot de passe actuel » à fournir, il n'en existe pas. */
function SetPasswordCard({ onSet }: { onSet: () => void }) {
  const [newPassword, setNewPassword] = useState("");
  const [confirm, setConfirm] = useState("");
  const [pending, setPending] = useState(false);
  const [error, setError] = useState<string | null>(null);

  async function submit(e: React.FormEvent) {
    e.preventDefault();
    setError(null);

    const policy = validatePassword(newPassword);
    if (!policy.ok) {
      setError(
        `Mot de passe trop faible : ${MIN_PASSWORD_LENGTH} caractères minimum, ` +
          "avec minuscule, majuscule, chiffre et symbole.",
      );
      return;
    }
    if (newPassword !== confirm) {
      setError("Les mots de passe ne correspondent pas.");
      return;
    }

    setPending(true);
    const res = await setInitialPasswordAction(newPassword);
    setPending(false);

    if (!res.ok) {
      setError(
        res.error === "already-set"
          ? "Un mot de passe existe déjà sur ce compte — recharge la page."
          : res.error === "impersonation-forbidden"
            ? "Impossible pendant une impersonation : ce mot de passe serait posé sur le compte de l'utilisateur inspecté."
            : "Impossible de définir le mot de passe. Réessaie.",
      );
      return;
    }

    toast.success("Mot de passe défini. Tu peux maintenant te connecter avec.");
    onSet();
  }

  return (
    <Card>
      <CardHeader>
        <CardTitle>Mot de passe</CardTitle>
        <CardDescription>
          Ton compte a été créé via un lien magique ou un compte social : aucun
          mot de passe n'est défini. Choisis-en un pour pouvoir te connecter
          aussi par mot de passe.
        </CardDescription>
      </CardHeader>
      <form onSubmit={submit}>
        <CardContent>
          <FieldGroup>
            <Field>
              <FieldLabel htmlFor="set-new-password">Mot de passe</FieldLabel>
              <Input
                id="set-new-password"
                type="password"
                value={newPassword}
                onChange={(e) => setNewPassword(e.target.value)}
                autoComplete="new-password"
                minLength={MIN_PASSWORD_LENGTH}
                required
              />
              <p className="text-muted-foreground text-xs">
                {MIN_PASSWORD_LENGTH} caractères minimum, avec minuscule,
                majuscule, chiffre et symbole.
              </p>
            </Field>
            <Field>
              <FieldLabel htmlFor="set-confirm-password">
                Confirme le mot de passe
              </FieldLabel>
              <Input
                id="set-confirm-password"
                type="password"
                value={confirm}
                onChange={(e) => setConfirm(e.target.value)}
                autoComplete="new-password"
                minLength={MIN_PASSWORD_LENGTH}
                required
              />
            </Field>
          </FieldGroup>

          {error ? (
            <p className="mt-4 text-destructive text-sm" role="alert">
              {error}
            </p>
          ) : null}
        </CardContent>
        <CardFooter className="justify-end">
          <Button type="submit" disabled={pending || !newPassword || !confirm}>
            <LockIcon className="size-4" />
            {pending ? "Enregistrement…" : "Définir le mot de passe"}
          </Button>
        </CardFooter>
      </form>
    </Card>
  );
}

function ChangePasswordCard() {
  const [currentPassword, setCurrentPassword] = useState("");
  const [newPassword, setNewPassword] = useState("");
  const [confirm, setConfirm] = useState("");
  const [pending, setPending] = useState(false);
  const [error, setError] = useState<string | null>(null);

  function reset() {
    setCurrentPassword("");
    setNewPassword("");
    setConfirm("");
  }

  async function submit(e: React.FormEvent) {
    e.preventDefault();
    setError(null);

    if (newPassword.length < 8) {
      setError("Le nouveau mot de passe doit faire au moins 8 caractères.");
      return;
    }
    if (newPassword !== confirm) {
      setError("Les mots de passe ne correspondent pas.");
      return;
    }

    setPending(true);
    const res = await authClient.changePassword({
      currentPassword,
      newPassword,
      revokeOtherSessions: true,
    });
    setPending(false);

    if (res.error) {
      // Cette carte ne s'affiche que pour les comptes AVEC credential (les
      // comptes social/magic-link voient SetPasswordCard) : un 400/401 ici
      // veut simplement dire que le mot de passe actuel est faux.
      const status = res.error.status;
      if (status === 400 || status === 401) {
        setError("Mot de passe actuel incorrect.");
      } else {
        setError(res.error.message ?? "Impossible de changer le mot de passe.");
      }
      return;
    }

    reset();
    toast.success("Mot de passe mis à jour.");
  }

  return (
    <Card>
      <CardHeader>
        <CardTitle>Mot de passe</CardTitle>
        <CardDescription>
          Modifie ton mot de passe. Tes autres sessions seront déconnectées.
        </CardDescription>
      </CardHeader>
      <form onSubmit={submit}>
        <CardContent>
          <FieldGroup>
            <Field>
              <FieldLabel htmlFor="current-password">
                Mot de passe actuel
              </FieldLabel>
              <Input
                id="current-password"
                type="password"
                value={currentPassword}
                onChange={(e) => setCurrentPassword(e.target.value)}
                autoComplete="current-password"
                required
              />
            </Field>
            <Field>
              <FieldLabel htmlFor="new-password">
                Nouveau mot de passe
              </FieldLabel>
              <Input
                id="new-password"
                type="password"
                value={newPassword}
                onChange={(e) => setNewPassword(e.target.value)}
                autoComplete="new-password"
                minLength={8}
                required
              />
              <p className="text-muted-foreground text-xs">
                8 caractères minimum, mélange de lettres et chiffres recommandé.
              </p>
            </Field>
            <Field>
              <FieldLabel htmlFor="confirm-password">
                Confirme le nouveau mot de passe
              </FieldLabel>
              <Input
                id="confirm-password"
                type="password"
                value={confirm}
                onChange={(e) => setConfirm(e.target.value)}
                autoComplete="new-password"
                minLength={8}
                required
              />
            </Field>
          </FieldGroup>

          {error ? (
            <p className="mt-4 text-destructive text-sm" role="alert">
              {error}
            </p>
          ) : null}
        </CardContent>
        <CardFooter className="justify-end">
          <Button
            type="submit"
            disabled={pending || !currentPassword || !newPassword || !confirm}
          >
            <LockIcon className="size-4" />
            {pending ? "Mise à jour…" : "Changer le mot de passe"}
          </Button>
        </CardFooter>
      </form>
    </Card>
  );
}

function SessionsCard() {
  const [sessions, setSessions] = useState<SessionRow[] | null>(null);
  const [currentToken, setCurrentToken] = useState<string | null>(null);
  const [loadError, setLoadError] = useState<string | null>(null);
  const [revokingToken, setRevokingToken] = useState<string | null>(null);
  const [revokingOthers, setRevokingOthers] = useState(false);

  const load = useCallback(async () => {
    setLoadError(null);
    const [listRes, currentRes] = await Promise.all([
      authClient.listSessions(),
      authClient.getSession(),
    ]);

    if (listRes.error) {
      setLoadError(
        listRes.error.message ?? "Impossible de charger tes sessions.",
      );
      return;
    }

    // The current session is identified by matching the active session token
    // (from getSession) against each row's token in listSessions.
    setCurrentToken(currentRes.data?.session.token ?? null);
    const rows = (listRes.data ?? []) as SessionRow[];
    // Most recently active first.
    rows.sort(
      (a, b) =>
        new Date(b.updatedAt).getTime() - new Date(a.updatedAt).getTime(),
    );
    setSessions(rows);
  }, []);

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

  async function revoke(token: string) {
    setRevokingToken(token);
    const res = await authClient.revokeSession({ token });
    setRevokingToken(null);
    if (res.error) {
      toast.error(res.error.message ?? "Impossible de révoquer la session.");
      return;
    }
    toast.success("Session révoquée.");
    await load();
  }

  async function revokeOthers() {
    setRevokingOthers(true);
    const res = await authClient.revokeOtherSessions();
    setRevokingOthers(false);
    if (res.error) {
      toast.error(
        res.error.message ?? "Impossible de déconnecter les autres sessions.",
      );
      return;
    }
    toast.success("Autres sessions déconnectées.");
    await load();
  }

  const hasOthers = sessions?.some((s) => s.token !== currentToken) ?? false;

  return (
    <Card>
      <CardHeader>
        <div className="flex items-start justify-between gap-4">
          <div className="flex flex-col gap-1.5">
            <CardTitle>Sessions actives</CardTitle>
            <CardDescription>
              Les appareils actuellement connectés à ton compte.
            </CardDescription>
          </div>
          {hasOthers ? (
            <Button
              variant="outline"
              size="sm"
              className="shrink-0"
              onClick={revokeOthers}
              disabled={revokingOthers}
            >
              {revokingOthers ? "…" : "Déconnecter les autres"}
            </Button>
          ) : null}
        </div>
      </CardHeader>
      <CardContent>
        {loadError ? (
          <p className="text-destructive text-sm" role="alert">
            {loadError}
          </p>
        ) : null}

        {sessions === null && !loadError ? (
          <div className="flex flex-col gap-3">
            <Skeleton className="h-16 w-full" />
            <Skeleton className="h-16 w-full" />
          </div>
        ) : sessions !== null && sessions.length === 0 ? (
          <p className="text-muted-foreground text-sm">
            Aucune session active.
          </p>
        ) : sessions !== null ? (
          <ul className="flex flex-col gap-2.5">
            {sessions.map((session) => (
              <SessionItem
                key={session.id}
                session={session}
                isCurrent={session.token === currentToken}
                revoking={revokingToken === session.token}
                onRevoke={() => revoke(session.token)}
              />
            ))}
          </ul>
        ) : null}
      </CardContent>
    </Card>
  );
}

function SessionItem({
  session,
  isCurrent,
  revoking,
  onRevoke,
}: {
  session: SessionRow;
  isCurrent: boolean;
  revoking: boolean;
  onRevoke: () => void;
}) {
  const { label, kind } = formatUserAgent(session.userAgent);
  const DeviceIcon =
    kind === "mobile"
      ? SmartphoneIcon
      : kind === "tablet"
        ? TabletIcon
        : MonitorIcon;

  return (
    <li
      className={cn(
        "flex items-start gap-3.5 rounded-xl border bg-muted/30 p-4",
        isCurrent && "border-primary/40 bg-primary/[0.04]",
      )}
    >
      <div className="grid size-9 shrink-0 place-items-center rounded-lg border bg-card text-muted-foreground">
        <DeviceIcon className="size-[19px]" />
      </div>
      <div className="min-w-0 flex-1">
        <div className="flex flex-wrap items-center gap-2">
          <span className="font-medium text-sm">{label}</span>
          {isCurrent ? (
            <Badge
              variant="secondary"
              className="border-primary/30 bg-primary/15 text-primary"
            >
              Session actuelle
            </Badge>
          ) : null}
        </div>
        <div className="mt-1.5 flex flex-wrap gap-x-4 gap-y-1 text-muted-foreground text-xs">
          {isCurrent ? (
            <span className="flex items-center gap-1.5">
              <span className="size-1.5 rounded-full bg-emerald-500 shadow-[0_0_0_3px] shadow-emerald-500/20" />
              Actif maintenant
            </span>
          ) : (
            <span className="flex items-center gap-1.5">
              <ClockIcon className="size-3.5" />
              Dernière activité le {formatDate(session.updatedAt)}
            </span>
          )}
          {isCurrent ? (
            <span className="flex items-center gap-1.5">
              <ClockIcon className="size-3.5" />
              Créée le {formatDate(session.createdAt)}
            </span>
          ) : null}
          {session.ipAddress ? (
            <span className="flex items-center gap-1.5">
              <GlobeIcon className="size-3.5" />
              {session.ipAddress}
            </span>
          ) : null}
        </div>
      </div>
      {isCurrent ? null : (
        <Button
          variant="ghost"
          size="sm"
          className="shrink-0 text-muted-foreground hover:text-destructive"
          onClick={onRevoke}
          disabled={revoking}
        >
          {revoking ? "…" : "Révoquer"}
        </Button>
      )}
    </li>
  );
}
