// src/components/keywords/planning/keyword-overview-modal.tsx
// Right-hand side panel (redesign "Panneau Keyword — Planning", ELEVA):
// header (breadcrumb + prev/next + close), source-readiness strip (ring +
// pills), attention cards for non-ready sources, then the scrollable body
// (semantic profile + prompt generator) and a sticky footer with keyboard
// hints and the big "Copier le prompt" action. Data comes from the prompt
// bundle (server action), the prompt itself from the pure builder — both
// written elsewhere.
"use client";

import {
  AlertTriangleIcon,
  CheckIcon,
  ChevronDownIcon,
  ChevronUpIcon,
  CopyIcon,
  XIcon,
} from "lucide-react";
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
import { toast } from "sonner";
import { INTENT_FULL_LABELS } from "@/components/keywords/intent-badge";
import { Button } from "@/components/ui/button";
import { Flag } from "@/components/ui/flag";
import {
  Sheet,
  SheetClose,
  SheetContent,
  SheetDescription,
  SheetTitle,
} from "@/components/ui/sheet";
import { Skeleton } from "@/components/ui/skeleton";
import { useSerpImport } from "@/hooks/use-serp-import";
import { getAnalyticsJob } from "@/lib/analytics/actions";
import { getKeywordPromptBundle } from "@/lib/keywords/prompt/actions";
import { computeReadiness } from "@/lib/keywords/prompt/compute-readiness";
import type {
  KeywordPromptBundle,
  ReadinessItem,
} from "@/lib/keywords/prompt/types";
import { crawlSerpForKeywordAction } from "@/lib/keywords/serp-actions";
import { cn } from "@/lib/utils";
import { KeywordPromptCard } from "./keyword-prompt-card";
import { SemanticAnalysisSection } from "./semantic-analysis-section";

const DOT_BG: Record<ReadinessItem["state"], string> = {
  ready: "bg-emerald-500",
  progress: "bg-amber-500 animate-pulse",
  missing: "bg-red-500",
};

// crawl-onpage job poll cadence + safety cap — mirrors ImportKeywordsDialog's
// startPolling (2 s) with a 6 min ceiling on top (the analytics service's own
// batch-scrape deadline is 5 min, cf. crawl-onpage.ts header note; this is
// just belt-and-braces against a wedged poll on THIS client).
const CRAWL_POLL_MS = 2000;
const CRAWL_POLL_MAX_ATTEMPTS = 180;

/** Progress/result shape written to `analyticsJobs.totals` for a
 *  `crawl-onpage` job (`completeJob(job.id, { crawled, blocked, failed })` in
 *  `services/analytics/src/crawl-onpage.ts`). The shared
 *  `AnalyticsJobStatus.totals` type only declares the csv-import shape, so
 *  this is a local, documented cast (same pattern as `SemanticJobTotals` in
 *  semantic-analysis-section.tsx). */
type CrawlOnpageJobTotals = {
  crawled?: number;
  blocked?: number;
  failed?: number;
};

const PILL_STYLE: Record<ReadinessItem["state"], string> = {
  ready:
    "border-border/70 bg-muted/30 text-muted-foreground hover:border-foreground/25 hover:text-foreground",
  progress:
    "border-amber-500/35 bg-amber-500/8 text-amber-700 dark:text-amber-300",
  missing: "border-red-500/35 bg-red-500/8 text-red-700 dark:text-red-300",
};

const CARD_BORDER: Record<ReadinessItem["state"], string> = {
  ready: "border-border",
  progress: "border-amber-500/30",
  missing: "border-red-500/30",
};

/** Short pill/card labels (design's compact strip) vs the full row labels
 *  used elsewhere in the app (`ReadinessItem.label`). */
const SHORT_LABEL: Record<ReadinessItem["key"], string> = {
  metrics: "Métriques",
  gsc: "GSC",
  serp: "SERP",
  onpage: "Crawl",
  semantic: "Profil",
  brief: "Brief YTG",
};

// Display order for the source strip / attention cards — the mockup groups
// them as Métriques, GSC, SERP, Crawl, Profil, Brief YTG, which differs from
// `computeReadiness`'s item order (…, brief, semantic) by swapping the last
// two. Kept as an explicit reorder rather than touching the shared builder.
const DISPLAY_ORDER: ReadinessItem["key"][] = [
  "metrics",
  "gsc",
  "serp",
  "onpage",
  "semantic",
  "brief",
];

function orderedItems(items: ReadinessItem[]): ReadinessItem[] {
  return DISPLAY_ORDER.map((key) => items.find((i) => i.key === key)).filter(
    (i): i is ReadinessItem => Boolean(i),
  );
}

/** Keyboard nav (↑/↓) and ⌘C/Ctrl+C must not fire while the user is typing in
 *  a field or interacting with an open select/menu/nested dialog (e.g. the
 *  semantic section's curation dialog). */
function isInteractiveTarget(target: EventTarget | null): boolean {
  if (!(target instanceof HTMLElement)) return false;
  const tag = target.tagName;
  if (tag === "INPUT" || tag === "TEXTAREA" || tag === "SELECT") return true;
  if (target.isContentEditable) return true;
  return Boolean(
    target.closest(
      '[data-slot="dialog-content"], [data-slot="select-content"], [data-slot="dropdown-menu-content"], [role="menu"], [role="listbox"]',
    ),
  );
}

function ProgressRing({ pct }: { pct: number }) {
  const r = 10.5;
  const c = 2 * Math.PI * r;
  const dash = (pct / 100) * c;
  return (
    <svg
      width="26"
      height="26"
      viewBox="0 0 26 26"
      className="-rotate-90 shrink-0"
      aria-hidden
    >
      <title>Progression des sources</title>
      <circle
        cx="13"
        cy="13"
        r={r}
        strokeWidth="3"
        className="fill-none stroke-muted"
      />
      <circle
        cx="13"
        cy="13"
        r={r}
        strokeWidth="3"
        strokeLinecap="round"
        strokeDasharray={`${dash} ${c}`}
        className="fill-none stroke-emerald-500 transition-[stroke-dasharray] duration-500"
      />
    </svg>
  );
}

function KbdHint({ children }: { children: React.ReactNode }) {
  return (
    <kbd className="rounded-md border bg-muted/40 px-1.5 py-0.5 font-semibold text-[10.5px] text-muted-foreground">
      {children}
    </kbd>
  );
}

export function KeywordOverviewModal({
  keywordId,
  open,
  onOpenChange,
  onImportBrief,
  onImport,
  onDataChanged,
  position,
  onNavigate,
}: {
  keywordId: string | null;
  open: boolean;
  onOpenChange: (o: boolean) => void;
  /** Opens the import-brief dialog for this keyword (brief verification action). */
  onImportBrief?: (keywordId: string) => void;
  /** Opens the generic keyword-import entry (metrics verification action). */
  onImport?: () => void;
  /** Called after each successful mutation inside the panel (SERP import,
   *  on-page crawl) — lets the caller re-fetch server-props-driven data
   *  (e.g. `router.refresh()`) since the underlying table doesn't re-fetch
   *  on its own. */
  onDataChanged?: () => void;
  /** Position of the open keyword among the currently displayed/filtered
   *  rows — drives the header's "n / total" and the prev/next buttons. */
  position?: { index: number; total: number };
  /** Move to the previous (-1) / next (1) keyword in that same order. */
  onNavigate?: (dir: -1 | 1) => void;
}) {
  const [bundle, setBundle] = useState<KeywordPromptBundle | null>(null);
  const [loading, setLoading] = useState(false);
  const [error, setError] = useState(false);
  // SERP import is keyword-scoped, so it uploads directly here (not via the
  // global SourcesModal, which has no keyword context). Uses the same
  // useSerpImport hook as SerpPanel.
  const serpInputRef = useRef<HTMLInputElement>(null);
  const [crawling, setCrawling] = useState(false);
  const crawlPollRef = useRef<ReturnType<typeof setInterval> | null>(null);

  function clearCrawlPoll() {
    if (crawlPollRef.current) {
      clearInterval(crawlPollRef.current);
      crawlPollRef.current = null;
    }
  }

  // Clear any pending crawl poll when the displayed keyword changes (↑/↓
  // navigation keeps this Sheet mounted, so without this the previous
  // keyword's interval would keep running: `crawling` stays stuck true and,
  // on completion, its closure calls the OLD keyword's `retry()`, clobbering
  // the bundle just loaded for the new one) and on unmount — the cleanup
  // below covers both. `setCrawling(false)` resets the button so the new
  // keyword doesn't inherit "Crawl…" from the one being left.
  // biome-ignore lint/correctness/useExhaustiveDependencies: clearCrawlPoll only touches a ref (stable identity) and setCrawling is a stable setState setter — both safe to omit from deps.
  useEffect(() => {
    return () => {
      clearCrawlPoll();
      setCrawling(false);
    };
  }, [keywordId]);
  // Set by `SemanticAnalysisSection` via `registerOpenCuration` — lets the
  // "semantic" readiness row open THAT section's own curation dialog (see the
  // bug note in docs/bugs/2026-07-21-planning-serp-import.md: each row must
  // trigger its own action, never another row's handler).
  const semanticOpenRef = useRef<(() => void) | null>(null);
  // Stable identity so the section's `registerOpenCuration` effect doesn't
  // re-run (and re-register) on every parent render.
  const registerSemanticOpenCuration = useCallback(
    (open: (() => void) | null) => {
      semanticOpenRef.current = open;
    },
    [],
  );

  // Set by `KeywordPromptCard` via `registerCopyPrompt` — lets the sticky
  // footer's big "Copier le prompt" button trigger THAT card's own copy
  // action (same pattern as `registerOpenCuration` above).
  const copyPromptRef = useRef<(() => void) | null>(null);
  const registerCopyPrompt = useCallback((fn: (() => void) | null) => {
    copyPromptRef.current = fn;
  }, []);
  const [copied, setCopied] = useState(false);

  const handleCopyPrompt = useCallback(() => {
    copyPromptRef.current?.();
    setCopied(true);
    window.setTimeout(() => setCopied(false), 1800);
  }, []);

  useEffect(() => {
    if (!open || !keywordId) return;
    let alive = true;
    setLoading(true);
    setError(false);
    setBundle(null);
    getKeywordPromptBundle(keywordId)
      .then((b) => {
        if (alive) setBundle(b);
      })
      .catch(() => {
        if (alive) setError(true);
      })
      .finally(() => {
        if (alive) setLoading(false);
      });
    return () => {
      alive = false;
    };
  }, [open, keywordId]);

  const readiness = useMemo(
    () => (bundle ? computeReadiness(bundle) : null),
    [bundle],
  );

  const complete = readiness ? readiness.pct >= 100 : false;
  const ordered = useMemo(
    () => (readiness ? orderedItems(readiness.items) : []),
    [readiness],
  );
  const readyCount = ordered.filter((i) => i.state === "ready").length;

  // ↑/↓ navigate, ⌘C/Ctrl+C copies the prompt (only without a text
  // selection — otherwise the user is just copying selected text), both
  // skipped while focus is in a field or an open select/menu/nested dialog.
  useEffect(() => {
    if (!open) return;
    function onKeyDown(e: KeyboardEvent) {
      if (isInteractiveTarget(e.target)) return;
      if (e.key === "ArrowUp") {
        if (position && position.index > 0) {
          e.preventDefault();
          onNavigate?.(-1);
        }
        return;
      }
      if (e.key === "ArrowDown") {
        if (position && position.index < position.total - 1) {
          e.preventDefault();
          onNavigate?.(1);
        }
        return;
      }
      if ((e.metaKey || e.ctrlKey) && e.key.toLowerCase() === "c") {
        const hasSelection =
          (window.getSelection()?.toString().length ?? 0) > 0;
        if (!hasSelection) {
          e.preventDefault();
          handleCopyPrompt();
        }
      }
    }
    document.addEventListener("keydown", onKeyDown);
    return () => document.removeEventListener("keydown", onKeyDown);
  }, [open, position, onNavigate, handleCopyPrompt]);

  function retry() {
    if (!keywordId) return;
    setLoading(true);
    setError(false);
    getKeywordPromptBundle(keywordId)
      .then(setBundle)
      .catch(() => setError(true))
      .finally(() => setLoading(false));
  }

  // Upload a SERP export for THIS keyword (bypasses the 1 MB Server Action cap
  // via the route), then reload the bundle so completeness refreshes.
  const { importFile: runSerpImport, busy: serpBusy } = useSerpImport(
    keywordId ?? "",
    {
      onSuccess: () => {
        toast.success("Export SERP importé.");
        retry();
        onDataChanged?.();
      },
      onError: (message) => toast.error(message),
      genericErrorMessage: "Import SERP échoué.",
      networkErrorMessage:
        "Import SERP impossible (fichier invalide ou trop lourd).",
    },
  );

  async function importSerpFile(file: File) {
    if (!keywordId) return;
    await runSerpImport(file);
  }

  // Enqueues a `crawl-onpage` analytics job (server no longer fetches
  // synchronously) and polls it every 2 s until it settles. Same semantics as
  // SerpPanel's crawl(): local `crawling` state kept for the whole poll,
  // success toast + retry() (reloads the bundle so completeness refreshes) +
  // onDataChanged() (server-props-driven table), error toast with the
  // server/job message when available.
  function pollCrawlJob(jobId: string) {
    clearCrawlPoll();
    let attempts = 0;
    crawlPollRef.current = setInterval(async () => {
      // Cap checked FIRST, before the fetch: a job stuck returning `null`
      // (see the `!job` guard below) or a fetch that keeps throwing must
      // still count towards the ceiling, otherwise the poll never stops.
      attempts++;
      if (attempts >= CRAWL_POLL_MAX_ATTEMPTS) {
        clearCrawlPoll();
        setCrawling(false);
        toast.error(
          "Crawl : toujours en cours après 6 min, réessayez plus tard.",
        );
        return;
      }
      try {
        const job = await getAnalyticsJob(jobId);
        if (!job) return;
        if (job.status === "done") {
          clearCrawlPoll();
          setCrawling(false);
          const totals = job.totals as CrawlOnpageJobTotals;
          toast.success(
            `Crawl : ${totals.crawled ?? 0} ok${totals.blocked ? `, ${totals.blocked} bloqué(s)` : ""}${totals.failed ? `, ${totals.failed} échec(s)` : ""}`,
          );
          retry();
          onDataChanged?.();
          return;
        }
        if (job.status === "failed") {
          clearCrawlPoll();
          setCrawling(false);
          toast.error(job.error ?? "Crawl échoué");
          return;
        }
        // still queued/running — keep the `crawling` pill up, the cap above
        // will eventually trigger if this never settles.
      } catch {
        // Network hiccup fetching job status — swallow and let the next tick
        // retry; the cap above bounds how long this can go on for.
      }
    }, CRAWL_POLL_MS);
  }

  async function crawlOnpage() {
    if (!keywordId) return;
    setCrawling(true);
    try {
      const res = await crawlSerpForKeywordAction(keywordId);
      if (!res.ok) {
        toast.error(res.error ?? "Crawl échoué");
        setCrawling(false);
        return;
      }
      if (!res.jobId) {
        // 409: an identical job is already queued/running for this keyword —
        // nothing new to poll, just say so.
        toast.info("Crawl déjà en cours.");
        setCrawling(false);
        return;
      }
      pollCrawlJob(res.jobId);
    } catch {
      toast.error("Crawl impossible.");
      setCrawling(false);
    }
  }

  function itemAction(item: ReadinessItem) {
    if (item.state === "ready") return null;
    // Pas d'action sur la ligne Search Console : la connexion est org-level et
    // se gérera depuis la future page GSC dédiée — un « Connecter » ici était
    // trompeur (l'org peut déjà être connectée sans positions pour ce mot-clé).
    if (item.key === "brief" && keywordId && onImportBrief) {
      return (
        <Button
          variant="outline"
          size="sm"
          onClick={() => onImportBrief(keywordId)}
        >
          Importer
        </Button>
      );
    }
    if (item.key === "serp" && keywordId) {
      return (
        <Button
          variant="outline"
          size="sm"
          disabled={serpBusy}
          onClick={() => serpInputRef.current?.click()}
        >
          {serpBusy ? "Import…" : "Importer"}
        </Button>
      );
    }
    if (item.key === "metrics" && onImport) {
      return (
        <Button variant="outline" size="sm" onClick={onImport}>
          Importer
        </Button>
      );
    }
    if (item.key === "semantic" && keywordId) {
      return (
        <Button
          variant="outline"
          size="sm"
          onClick={() => semanticOpenRef.current?.()}
        >
          Analyser
        </Button>
      );
    }
    if (item.key === "onpage" && keywordId) {
      // Crawl only makes sense once a SERP is available — disable until the
      // "serp" item of the same bundle is ready (readiness is already in
      // scope, so this stays a plain lookup rather than new plumbing).
      const serpReady =
        readiness?.items.find((i) => i.key === "serp")?.state === "ready";
      return (
        <Button
          variant="outline"
          size="sm"
          disabled={crawling || !serpReady}
          onClick={() => void crawlOnpage()}
        >
          {crawling ? "Crawl…" : "Crawler"}
        </Button>
      );
    }
    return null;
  }

  const attentionItems = ordered.filter((i) => i.state !== "ready");

  return (
    <Sheet open={open} onOpenChange={onOpenChange}>
      <SheetContent
        side="right"
        showCloseButton={false}
        className="flex w-full flex-col gap-0 p-0 data-[side=right]:sm:max-w-[680px]"
      >
        <input
          ref={serpInputRef}
          type="file"
          accept=".csv,text/csv"
          className="hidden"
          onChange={(e) => {
            const f = e.target.files?.[0];
            e.target.value = "";
            if (f) void importSerpFile(f);
          }}
        />

        {/* ---------- Header ---------- */}
        <div className="flex-none px-6 pt-5">
          <div className="flex items-center gap-2">
            <span className="font-medium text-muted-foreground text-xs">
              Keywords · Planning
            </span>
            <div className="ml-auto flex items-center gap-1.5">
              {position ? (
                <span className="pr-1 text-muted-foreground text-xs tabular-nums">
                  {position.index + 1} / {position.total}
                </span>
              ) : null}
              {position && onNavigate ? (
                <>
                  <Button
                    variant="outline"
                    size="icon-sm"
                    className="text-muted-foreground"
                    title="↑"
                    aria-label="Keyword précédent"
                    disabled={position.index <= 0}
                    onClick={() => onNavigate(-1)}
                  >
                    <ChevronUpIcon />
                  </Button>
                  <Button
                    variant="outline"
                    size="icon-sm"
                    className="text-muted-foreground"
                    title="↓"
                    aria-label="Keyword suivant"
                    disabled={position.index >= position.total - 1}
                    onClick={() => onNavigate(1)}
                  >
                    <ChevronDownIcon />
                  </Button>
                </>
              ) : null}
              <SheetClose asChild>
                <Button
                  variant="outline"
                  size="icon-sm"
                  className="text-muted-foreground"
                  title="Esc"
                  aria-label="Fermer"
                >
                  <XIcon />
                </Button>
              </SheetClose>
            </div>
          </div>

          <SheetTitle className="flex items-center gap-2.5 pt-2.5 pb-1 font-heading text-xl leading-tight tracking-tight">
            {bundle ? (
              <>
                <Flag code={bundle.country} />
                {bundle.term}
              </>
            ) : (
              "Overview"
            )}
          </SheetTitle>
          <SheetDescription className="sr-only">
            Complétude des données et générateur de prompt pour le mot-clé.
          </SheetDescription>

          {bundle ? (
            <p className="flex flex-wrap items-center gap-1.5 pb-3.5 text-muted-foreground text-xs">
              <span className="font-semibold text-foreground/85">
                Vol{" "}
                {bundle.vol != null ? bundle.vol.toLocaleString("fr-FR") : "—"}
              </span>
              <span aria-hidden className="text-muted-foreground/60">
                ·
              </span>
              <span className="font-semibold text-foreground/85">
                KD {bundle.kd ?? "—"}
              </span>
              <span aria-hidden className="text-muted-foreground/60">
                ·
              </span>
              <span className="font-semibold text-foreground/85">
                CPC {bundle.cpc != null ? `$${bundle.cpc.toFixed(2)}` : "—"}
              </span>
              <span aria-hidden className="text-muted-foreground/60">
                ·
              </span>
              <span>
                {bundle.intent[0] ? INTENT_FULL_LABELS[bundle.intent[0]] : "—"}
              </span>
              <span aria-hidden className="text-muted-foreground/60">
                ·
              </span>
              <span>{bundle.country}</span>
            </p>
          ) : null}
        </div>

        {/* ---------- Sources strip ---------- */}
        {bundle && readiness ? (
          <div className="flex flex-none flex-wrap items-center gap-2 px-6 pb-3.5">
            <span className="mr-0.5 inline-flex items-center gap-2 font-semibold text-muted-foreground text-xs">
              <ProgressRing pct={readiness.pct} />
              {readyCount}/{ordered.length} sources prêtes
            </span>
            {ordered.map((item) => (
              <span
                key={item.key}
                title={item.detail}
                className={cn(
                  "inline-flex h-7 items-center gap-1.5 rounded-full border px-2.5 font-semibold text-xs transition-colors",
                  PILL_STYLE[item.state],
                )}
              >
                <span
                  className={cn("size-1.5 rounded-full", DOT_BG[item.state])}
                />
                {SHORT_LABEL[item.key]}
              </span>
            ))}
          </div>
        ) : null}

        {/* ---------- Attention cards (non-ready sources only) ---------- */}
        {attentionItems.length > 0 ? (
          <div className="flex flex-none flex-col gap-2 px-6 pb-4">
            {attentionItems.map((item) => (
              <div
                key={item.key}
                className={cn(
                  "flex items-center gap-3 rounded-xl border bg-card px-3.5 py-2.5",
                  CARD_BORDER[item.state],
                )}
              >
                <span
                  className={cn(
                    "size-1.5 flex-none rounded-full",
                    DOT_BG[item.state],
                  )}
                />
                <div className="min-w-0 flex-1">
                  <p className="font-semibold text-sm">{item.label}</p>
                  <p className="truncate text-muted-foreground text-xs">
                    {item.detail}
                  </p>
                </div>
                {itemAction(item)}
              </div>
            ))}
          </div>
        ) : null}

        {/* ---------- Scrollable body ---------- */}
        <div className="min-h-0 flex-1 overflow-y-auto px-6 py-4">
          {loading ? (
            <div className="space-y-4">
              <Skeleton className="h-44 w-full" />
              <Skeleton className="h-64 w-full" />
            </div>
          ) : error ? (
            <div className="flex flex-col items-center gap-3 py-12 text-center">
              <p className="text-muted-foreground text-sm">
                Impossible de charger les données du mot-clé.
              </p>
              <Button variant="outline" size="sm" onClick={retry}>
                Réessayer
              </Button>
            </div>
          ) : bundle && readiness ? (
            <div className="flex flex-col gap-4">
              {!complete ? (
                <div className="flex items-start gap-2 rounded-lg border border-amber-500/30 bg-amber-500/6 p-3 text-xs">
                  <AlertTriangleIcon
                    className="mt-0.5 size-4 shrink-0 text-amber-500"
                    aria-hidden
                  />
                  <span className="text-muted-foreground">
                    Certaines sources sont incomplètes — le prompt utilisera les
                    données disponibles et signalera ce qui manque.
                  </span>
                </div>
              ) : null}

              {/* Profil sémantique SERP — montée telle quelle, un autre agent
                  la restyle en parallèle. */}
              <SemanticAnalysisSection
                keywordId={bundle.id}
                country={bundle.country}
                profile={bundle.semanticProfile}
                onAnalyzed={() => {
                  retry();
                  onDataChanged?.();
                }}
                registerOpenCuration={registerSemanticOpenCuration}
              />

              {/* Prompt à injecter — reuses the loaded bundle (no double
                  fetch). `registerCopyPrompt` is a new contract prop consumed
                  by the footer's big "Copier le prompt" button (see
                  `registerCopyPrompt` above) — another agent implements it
                  on the card side in parallel. */}
              <KeywordPromptCard
                keywordId={bundle.id}
                bundle={bundle}
                registerCopyPrompt={registerCopyPrompt}
                onRegenerate={retry}
              />
            </div>
          ) : null}
        </div>

        {/* ---------- Sticky footer ---------- */}
        <div className="flex flex-none items-center gap-3 border-t bg-background px-6 py-3.5">
          <span className="hidden items-center gap-1.5 text-[11.5px] text-muted-foreground sm:inline-flex">
            <KbdHint>↑</KbdHint> <KbdHint>↓</KbdHint> naviguer ·{" "}
            <KbdHint>⌘C</KbdHint> copier · <KbdHint>Esc</KbdHint> fermer
          </span>
          <div className="flex-1" />
          <Button
            size="lg"
            disabled={!bundle}
            onClick={handleCopyPrompt}
            className={cn(
              "gap-2 font-semibold shadow-lg transition-colors",
              copied &&
                "bg-emerald-500 text-white hover:bg-emerald-500 dark:text-white",
            )}
          >
            {copied ? <CheckIcon /> : <CopyIcon />}
            {copied ? "Copié !" : "Copier le prompt"}
          </Button>
        </div>
      </SheetContent>
    </Sheet>
  );
}
