"use client";

import { useEffect, useMemo, useRef, useState } from "react";
import Link from "next/link";
import { motion } from "motion/react";
import { ArrowUp, ArrowUpRight, Bookmark, Database, LoaderCircle, RotateCcw, Search, ShieldCheck, Sparkle, Square, User, X } from "lucide-react";
import { Button } from "@/components/ui/button";
import { Textarea } from "@/components/ui/textarea";
import { Input } from "@/components/ui/input";
import { Card } from "@/components/ui/card";
import { Badge } from "@/components/ui/badge";
import { Progress } from "@/components/ui/progress";
import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip";
import { ProductImage } from "@/components/dashboard/product-image";
import { useLanguage } from "@/lib/i18n/context";
import { cn } from "@/lib/utils";
import { PLANS } from "@/config/plans";
import { STAGGER_CONTAINER, STAGGER_ITEM } from "@/components/dashboard/motion-variants";
import type { PlanCode } from "@prisma/client";
import type { AssistantAnalysisMode } from "@/lib/ai/model-router";
import type { AssistantHistoryMessage, AssistantProductReference, AssistantSource, AssistantUsage } from "@/lib/ai/types";
import type { SavedProductView } from "@/lib/product-data/view-models";

const COPY = {
  en: {
    title: "AI Sales Assistant",
    prompt: "Ask about products, trends, categories, or scores…",
    ask: "Ask assistant",
    stop: "Stop",
    composerTitle: "Where would you like to start?",
    composerSubtitle: "Describe what you want to accomplish with your TokNext data.",
    disabled: "AI assistant is disabled in this environment.",
    disabledHint: "An administrator must enable the feature and configure the server-side OpenAI key before requests can run.",
    suggestionsLabel: "Try a suggested prompt",
    savedTitle: "Saved products",
    savedDescription: "Select one to generate a grounded TikTok Shop script.",
    savedEmpty: "Save a product to generate a script from its verified data.",
    generateScript: "Generate script",
    openSaved: "Open saved products",
    scriptPrompt: "Create a TikTok Shop sales script for this saved product.",
    analysisMode: "Analysis depth",
    auto: "Auto",
    fast: "Fast",
    deep: "Deep",
    quickAnalysis: "Quick analysis",
    deepAnalysis: "Deep analysis",
    lightFallback: "Using quick analysis",
    contextTitle: "Assistant guardrails",
    usage: "Monthly usage",
    plan: "Plan",
    creditsByPlan: "Credits by plan",
    yourPlan: "Your plan",
    perMonth: "mo",
    upgradeForMore: "Upgrade for more credits",
    noCreditsLeft: "No credits left this month. Upgrade your plan to keep generating.",
    searchSaved: "Search saved products",
    noSavedMatch: "No saved products match that search.",
    answerTitle: "Conversation",
    answerSubtitle: "Responses cite the TokNext records used to build them.",
    noAnswer: "No response yet. Ask a question to start.",
    clear: "Clear",
    retry: "Retry",
    dismiss: "Dismiss",
    cancelled: "Request stopped.",
    error: "The assistant could not complete that request.",
    sources: "Records used",
    open: "Open product",
    unavailable: "Unavailable",
    suggestions: ["Which products have the best opportunity?", "Compare my saved products", "Explain the strongest Opportunity Scores", "What categories are growing?"],
    greeting: "Hi! I can search TokNext data, compare products, explain Opportunity Scores, and help you choose what to investigate next.",
  },
  es: {
    title: "Asistente de ventas con IA",
    prompt: "Pregunta sobre productos, tendencias, categorías o puntajes…",
    ask: "Preguntar",
    stop: "Detener",
    composerTitle: "¿Por dónde quieres empezar?",
    composerSubtitle: "Describe qué quieres lograr con tus datos de TokNext.",
    disabled: "El asistente IA está desactivado en este entorno.",
    disabledHint: "Un administrador debe activar la función y configurar la clave de OpenAI del servidor antes de ejecutar solicitudes.",
    suggestionsLabel: "Prueba una pregunta sugerida",
    savedTitle: "Productos guardados",
    savedDescription: "Selecciona uno para generar un guion de TikTok Shop con datos reales.",
    savedEmpty: "Guarda un producto para crear un guion con sus datos verificados.",
    generateScript: "Generar guion",
    openSaved: "Abrir productos guardados",
    scriptPrompt: "Crea un guion de ventas para TikTok Shop sobre este producto guardado.",
    analysisMode: "Profundidad del análisis",
    auto: "Automático",
    fast: "Rápido",
    deep: "Profundo",
    quickAnalysis: "Análisis rápido",
    deepAnalysis: "Análisis profundo",
    lightFallback: "Usando análisis rápido",
    contextTitle: "Protecciones del asistente",
    usage: "Uso mensual",
    plan: "Plan",
    creditsByPlan: "Créditos por plan",
    yourPlan: "Tu plan",
    perMonth: "mes",
    upgradeForMore: "Mejora tu plan para más créditos",
    noCreditsLeft: "No te quedan créditos este mes. Mejora tu plan para seguir generando.",
    searchSaved: "Buscar productos guardados",
    noSavedMatch: "Ningún producto guardado coincide con esa búsqueda.",
    answerTitle: "Conversación",
    answerSubtitle: "Las respuestas muestran los registros TokNext utilizados.",
    noAnswer: "Aún no hay respuesta. Haz una pregunta para empezar.",
    clear: "Limpiar",
    retry: "Reintentar",
    dismiss: "Cerrar",
    cancelled: "Solicitud detenida.",
    error: "El asistente no pudo completar la solicitud.",
    sources: "Registros utilizados",
    open: "Abrir producto",
    unavailable: "No disponible",
    suggestions: ["¿Qué productos tienen mejor oportunidad?", "Compara mis productos guardados", "Explica los Puntajes de oportunidad más fuertes", "¿Qué categorías están creciendo?"],
    greeting: "¡Hola! Puedo buscar datos de TokNext, comparar productos, explicar Puntajes de oportunidad y ayudarte a decidir qué investigar después.",
  },
} as const;

type ChatMessage = AssistantHistoryMessage & { id: string; sources?: AssistantSource[]; depth?: "fast" | "deep"; fallback?: boolean };

function planLabel(plan: PlanCode, locale: "en" | "es") {
  if (locale === "es") return plan === "AGENCY" ? "Agencia" : plan === "PRO" ? "Pro" : "Gratis";
  return plan === "AGENCY" ? "Agency" : plan === "PRO" ? "Pro" : "Free";
}

function scoreBadgeClasses(score: number | null): string {
  if (score === null) return "bg-muted text-muted-foreground";
  return score >= 70 ? "bg-success/10 text-success" : score >= 40 ? "bg-muted text-foreground" : "bg-warning/10 text-warning";
}

function ProductSource({ product, copy }: { product: AssistantProductReference; copy: { unavailable: string } }) {
  return (
    <Link
      className="flex min-w-0 items-center gap-2.5 rounded-lg border bg-card px-2.5 py-2 transition-colors hover:border-primary/40 hover:bg-primary/5"
      href={product.detailUrl}
    >
      <span className="flex size-8 shrink-0 items-center justify-center overflow-hidden rounded-md bg-muted text-muted-foreground">
        {product.imageUrl ? (
          // eslint-disable-next-line @next/next/no-img-element
          <img src={product.imageUrl} alt="" className="size-full object-cover" />
        ) : (
          <Database className="size-3.5" aria-hidden="true" />
        )}
      </span>
      <span className="flex min-w-0 flex-1 flex-col">
        <strong className="truncate text-[11px] font-semibold text-foreground">{product.title}</strong>
        <small className="truncate text-[10px] text-muted-foreground">
          {product.category} · {product.sourceLabel}
        </small>
      </span>
      <span className={cn("flex shrink-0 items-center gap-1 rounded-md px-1.5 py-0.5 text-[11px] font-bold", scoreBadgeClasses(product.opportunityScore))}>
        {product.opportunityScore ?? copy.unavailable}
        <ArrowUpRight className="size-3" aria-hidden="true" />
      </span>
    </Link>
  );
}

export function AssistantPageContent({ enabled, plan, usage: initialUsage, savedProducts }: { enabled: boolean; plan: PlanCode; usage: AssistantUsage; savedProducts: SavedProductView[] }) {
  const { locale } = useLanguage();
  const copy = COPY[locale];
  const [question, setQuestion] = useState("");
  const [analysisMode, setAnalysisMode] = useState<AssistantAnalysisMode>("auto");
  const [messages, setMessages] = useState<ChatMessage[]>([{ id: "greeting", role: "assistant", content: copy.greeting }]);
  const [usage, setUsage] = useState(initialUsage);
  const [pending, setPending] = useState(false);
  const [progress, setProgress] = useState<string | null>(null);
  const [error, setError] = useState<string | null>(null);
  const controllerRef = useRef<AbortController | null>(null);
  const [lastQuestion, setLastQuestion] = useState("");
  const [savedFilter, setSavedFilter] = useState("");
  const threadEndRef = useRef<HTMLDivElement>(null);

  async function handleAsk(event?: React.FormEvent, retryMessage?: string, savedProductId?: string) {
    event?.preventDefault();
    const message = (retryMessage ?? question).trim();
    if (!message || pending || !enabled || usage.remaining <= 0) return;
    const priorMessages = messages.map(({ role, content }) => ({ role, content }));
    setLastQuestion(message);
    setQuestion("");
    setError(null);
    const assistantId = crypto.randomUUID();
    setMessages((current) => [...current, { id: crypto.randomUUID(), role: "user", content: message }, { id: assistantId, role: "assistant", content: "" }]);
    setPending(true);
    setProgress(locale === "es" ? "Entendiendo tu solicitud…" : "Understanding your request…");
    const controller = new AbortController();
    controllerRef.current = controller;
    try {
      const response = await fetch("/api/ai/chat", {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({ message, history: priorMessages.slice(-8), locale, analysisMode, ...(savedProductId ? { savedProductId } : {}) }),
        signal: controller.signal,
      });
      const isStream = response.headers.get("content-type")?.includes("application/x-ndjson");
      if (!response.ok || !isStream) {
        const payload = await response.json().catch(() => null) as { error?: { message?: string } } | null;
        throw new Error(payload?.error?.message || copy.error);
      }
      if (!response.body) throw new Error(copy.error);
      const reader = response.body.getReader();
      const decoder = new TextDecoder();
      let buffer = "";
      type StreamEvent = { type?: string; delta?: string; message?: string; sources?: AssistantSource[]; quota?: AssistantUsage; usage?: AssistantUsage | { inputTokens: number; outputTokens: number; totalTokens: number }; routing?: { depth?: "fast" | "deep"; fallback?: boolean }; code?: string };
      const applyEvent = (event: StreamEvent) => {
        if (event.type === "tool.started") setProgress(event.message || (locale === "es" ? "Revisando datos de TokNext…" : "Reviewing TokNext data…"));
        if (event.type === "routing.completed") setProgress(locale === "es" ? "Preparando la respuesta…" : "Preparing your answer…");
        if (event.type === "text.delta" && event.delta) setMessages((current) => current.map((item) => item.id === assistantId ? { ...item, content: item.content + event.delta } : item));
        if (event.type === "response.completed") {
          setMessages((current) => current.map((item) => item.id === assistantId ? { ...item, sources: event.sources ?? [], depth: event.routing?.depth, fallback: event.routing?.fallback } : item));
          if (event.quota) setUsage(event.quota);
        }
        if (event.type === "response.error") throw new Error(event.message || copy.error);
      };
      while (true) {
        const chunk = await reader.read();
        buffer += decoder.decode(chunk.value || new Uint8Array(), { stream: !chunk.done });
        const lines = buffer.split("\n");
        buffer = lines.pop() || "";
        for (const line of lines) {
          if (!line.trim()) continue;
          try { applyEvent(JSON.parse(line) as StreamEvent); } catch { throw new Error(copy.error); }
        }
        if (chunk.done) break;
      }
      if (buffer.trim()) applyEvent(JSON.parse(buffer) as StreamEvent);
    } catch (caught) {
      if (caught instanceof DOMException && caught.name === "AbortError") setError(copy.cancelled);
      else setError(caught instanceof Error ? caught.message : copy.error);
    } finally {
      setPending(false);
      setProgress(null);
      controllerRef.current = null;
    }
  }

  function stop() { controllerRef.current?.abort(); }

  function clearConversation() {
    if (pending) return;
    setMessages([{ id: "greeting", role: "assistant", content: copy.greeting }]);
    setError(null);
  }

  function generateScript(savedProduct: SavedProductView) {
    const prompt = `${copy.scriptPrompt}\n\nProducto seleccionado: ${savedProduct.product.canonicalName}.`;
    void handleAsk(undefined, prompt, savedProduct.product.id);
  }

  useEffect(() => {
    threadEndRef.current?.scrollIntoView({ behavior: "smooth", block: "end" });
  }, [messages, pending]);

  const filteredSavedProducts = useMemo(() => {
    const query = savedFilter.trim().toLowerCase();
    const filtered = query
      ? savedProducts.filter(
          (saved) =>
            saved.product.canonicalName.toLowerCase().includes(query) || saved.product.category.toLowerCase().includes(query),
        )
      : savedProducts;
    return [...filtered].sort((a, b) => (b.product.score?.opportunityScore ?? -1) - (a.product.score?.opportunityScore ?? -1));
  }, [savedProducts, savedFilter]);

  const percent = usage.limit > 0 ? Math.min(100, (usage.used / usage.limit) * 100) : 100;
  const askDisabledReason = !enabled ? copy.disabled : usage.remaining <= 0 ? copy.noCreditsLeft : null;
  const planOrder: PlanCode[] = ["FREE", "PRO", "AGENCY"];

  return (
    <div className="flex w-full max-w-[1500px] flex-col gap-4 pb-8">
      <header className="flex flex-wrap items-start justify-between gap-4 pt-0.5">
        <div>
          <h1 className="text-[26px] font-semibold tracking-tight text-foreground sm:text-[28px]">{copy.title}</h1>
        </div>
      </header>

      <div className="grid grid-cols-1 gap-4 lg:grid-cols-[minmax(0,1fr)_320px]">
        <div className="flex min-w-0 flex-col gap-4">
          {/* Chat card — conversation is always visible, including the greeting, so it reads as one continuous thread */}
          <Card className="gap-0 overflow-hidden rounded-2xl py-0">
            <div className="flex items-center justify-between gap-4 border-b px-5 py-4">
              <div className="flex items-center gap-3">
                <span
                  className="flex size-10 shrink-0 items-center justify-center rounded-xl text-white shadow-md shadow-[#7758ff]/30"
                  style={{ background: "linear-gradient(145deg,#c084ff,#7758ff)" }}
                >
                  <Sparkle className="size-4.5" fill="currentColor" strokeWidth={1} aria-hidden="true" />
                </span>
                <div>
                  <h2 className="text-[15px] font-bold text-foreground">{copy.composerTitle}</h2>
                  <p className="text-xs text-muted-foreground">{copy.composerSubtitle}</p>
                </div>
              </div>
              <div className="flex items-center gap-3">
                {messages.length > 1 && (
                  <Button type="button" variant="ghost" size="sm" disabled={pending} onClick={clearConversation}>
                    <RotateCcw className="size-3.5" aria-hidden="true" />
                    {copy.clear}
                  </Button>
                )}
              </div>
            </div>

            {!enabled && (
              <div className="mx-5 mt-4 flex items-start gap-3 rounded-xl border border-warning/30 bg-warning/10 p-3.5 text-warning">
                <ShieldCheck className="mt-0.5 size-4 shrink-0" aria-hidden="true" />
                <div>
                  <strong className="block text-xs font-bold">{copy.disabled}</strong>
                  <p className="mt-1 text-xs">{copy.disabledHint}</p>
                </div>
              </div>
            )}

            {/* Chat thread — fixed height so a growing conversation scrolls in place
                instead of pushing the composer and the rest of the page down. */}
            <div className="flex h-[420px] shrink-0 flex-col gap-4 overflow-y-auto px-5 py-5" aria-live="polite" aria-busy={pending}>
              {messages.map((item) => {
                const isUser = item.role === "user";
                const isStreamingPlaceholder = pending && item.id === messages.at(-1)?.id && !item.content;
                return (
                  <div key={item.id} className={cn("flex items-start gap-2.5", isUser && "flex-row-reverse")}>
                    <span
                      className={cn(
                        "flex size-8 shrink-0 items-center justify-center rounded-full",
                        isUser ? "bg-muted text-foreground" : "text-white shadow-sm shadow-[#7758ff]/30",
                      )}
                      style={isUser ? undefined : { background: "linear-gradient(145deg,#c084ff,#7758ff)" }}
                    >
                      {isUser ? (
                        <User className="size-4" aria-hidden="true" />
                      ) : (
                        <Sparkle className="size-4" fill="currentColor" strokeWidth={1} aria-hidden="true" />
                      )}
                    </span>
                    <div className={cn("flex max-w-[80%] min-w-0 flex-col gap-1.5", isUser && "items-end")}>
                      {!isUser && item.depth && (
                        <span className="text-[10px] font-semibold tracking-wide text-muted-foreground uppercase">
                          {item.fallback ? copy.lightFallback : item.depth === "deep" ? copy.deepAnalysis : copy.quickAnalysis}
                        </span>
                      )}
                      <div
                        className={cn(
                          "rounded-2xl px-4 py-2.5 text-sm leading-relaxed whitespace-pre-wrap",
                          isUser ? "rounded-tr-sm bg-primary text-primary-foreground" : "rounded-tl-sm bg-muted text-foreground",
                        )}
                      >
                        {isStreamingPlaceholder ? (
                          <span className="flex items-center gap-2 text-muted-foreground">
                            <LoaderCircle className="size-3.5 animate-spin" aria-hidden="true" />
                            {progress}
                          </span>
                        ) : item.id === "greeting" ? (
                          copy.greeting
                        ) : (
                          item.content
                        )}
                      </div>
                      {item.sources?.length ? (
                        <div className="mt-1 flex w-full flex-col gap-1.5 rounded-xl border bg-card p-2">
                          <span className="px-1 text-[10px] font-bold tracking-wide text-muted-foreground uppercase">{copy.sources}</span>
                          {item.sources.flatMap((source) => source.products).map((product) => (
                            <ProductSource key={`${item.id}-${product.id}`} product={product} copy={copy} />
                          ))}
                        </div>
                      ) : null}
                    </div>
                  </div>
                );
              })}
              <div ref={threadEndRef} />
            </div>

            {error && (
              <div
                className="animate-in fade-in slide-in-from-top-4 fixed top-20 right-4 z-50 flex max-w-sm items-start gap-3 rounded-xl border border-destructive/30 bg-card px-4 py-3 text-xs text-destructive shadow-lg shadow-destructive/10 duration-300"
                role="alert"
              >
                <span className="flex-1 pt-0.5">{error}</span>
                <div className="flex shrink-0 items-center gap-1">
                  <Button type="button" variant="ghost" size="sm" onClick={() => void handleAsk(undefined, lastQuestion)} disabled={pending || !lastQuestion}>
                    <RotateCcw className="size-3.5" aria-hidden="true" />
                    {copy.retry}
                  </Button>
                  <Button type="button" variant="ghost" size="icon" className="size-6 text-destructive" onClick={() => setError(null)} aria-label={copy.dismiss}>
                    <X className="size-3.5" />
                  </Button>
                </div>
              </div>
            )}

            {/* Composer */}
            <div className="border-t px-5 py-4">
              <form
                onSubmit={handleAsk}
                aria-busy={pending}
                className="relative flex flex-col gap-2.5 rounded-2xl border border-transparent bg-muted/30 p-3 pr-16 transition-all duration-300 focus-within:border-blue-500/50 focus-within:bg-card focus-within:shadow-[0_0_0_4px_rgba(59,130,246,0.16),0_8px_32px_-6px_rgba(59,130,246,0.45)]"
              >
                <Textarea
                  value={question}
                  onChange={(event) => setQuestion(event.target.value)}
                  placeholder={copy.prompt}
                  rows={3}
                  className="resize-none border-0 bg-transparent px-1 shadow-none focus-visible:ring-0 dark:bg-transparent"
                  disabled={!enabled || pending || usage.remaining <= 0}
                />

                {/* Floating send/stop button — rises into the composer body, vertically
                    centered on the trailing edge, matching premium chat UIs. */}
                <div className="absolute top-1/2 right-3 -translate-y-1/2">
                  {pending ? (
                    <Button
                      type="button"
                      size="icon"
                      variant="secondary"
                      className="size-10 rounded-full shadow-md"
                      onClick={stop}
                      aria-label={copy.stop}
                    >
                      <Square className="size-3.5" />
                    </Button>
                  ) : (
                    <Button
                      type="submit"
                      size="icon"
                      className={cn(
                        "size-10 rounded-full border border-white/20 bg-[linear-gradient(90deg,#11cbe9_0%,#6c5af4_48%,#ff3f78_100%)] text-white",
                        "shadow-[inset_0_1px_0_rgba(255,255,255,0.35),0_9px_24px_-6px_rgba(117,72,239,0.55)]",
                        "ring-4 ring-violet-500/15",
                        "transition-all duration-200 ease-out",
                        "hover:-translate-y-0.5 hover:shadow-[inset_0_1px_0_rgba(255,255,255,0.4),0_10px_28px_-4px_rgba(117,72,239,0.7)] hover:ring-violet-500/25 hover:saturate-125",
                        "active:translate-y-0 active:scale-95 active:shadow-[inset_0_2px_4px_rgba(0,0,0,0.25)]",
                        "disabled:translate-y-0 disabled:scale-100 disabled:opacity-100",
                      )}
                      disabled={!enabled || !question.trim() || usage.remaining <= 0}
                      aria-label={copy.ask}
                    >
                      <ArrowUp className="size-4.5" strokeWidth={2.75} />
                    </Button>
                  )}
                </div>
              </form>

              <div className="mt-2.5 flex items-center justify-between gap-3">
                {messages.length <= 1 ? (
                  <span className="text-[10px] font-bold tracking-wide text-muted-foreground uppercase">{copy.suggestionsLabel}</span>
                ) : (
                  <span />
                )}
                <label className="flex w-fit shrink-0 items-center gap-1.5 text-[11px] font-medium text-muted-foreground">
                  <span>{copy.analysisMode}</span>
                  <select
                    value={analysisMode}
                    onChange={(event) => setAnalysisMode(event.target.value as AssistantAnalysisMode)}
                    disabled={!enabled || pending || usage.remaining <= 0}
                    className="h-8 rounded-md border border-border bg-card px-2 text-xs text-foreground"
                  >
                    <option value="auto">{copy.auto}</option>
                    <option value="fast">{copy.fast}</option>
                    <option value="deep" disabled={plan === "FREE"}>{copy.deep}</option>
                  </select>
                </label>
              </div>

              {messages.length <= 1 && (
                <div className="mt-2">
                  <div className="flex flex-wrap gap-2">
                    {copy.suggestions.map((suggestion) => (
                      <Button
                        key={suggestion}
                        type="button"
                        variant="outline"
                        size="sm"
                        className="rounded-full border-blue-500/15 bg-card shadow-[0_1px_2px_rgba(15,23,42,0.04),0_4px_12px_-4px_rgba(59,130,246,0.25)] transition-all duration-200 hover:-translate-y-0.5 hover:border-blue-500/30 hover:bg-blue-500/5 hover:shadow-[0_2px_4px_rgba(15,23,42,0.06),0_10px_24px_-6px_rgba(59,130,246,0.4)]"
                        disabled={!enabled || pending}
                        onClick={() => setQuestion(suggestion)}
                      >
                        {suggestion}
                        <ArrowUpRight className="size-3.5 text-blue-500" aria-hidden="true" />
                      </Button>
                    ))}
                  </div>
                </div>
              )}
            </div>
          </Card>

          {/* Saved products → script generation */}
          <Card className="gap-0 overflow-hidden rounded-2xl py-0">
            <div className="flex flex-wrap items-center justify-between gap-3 border-b px-5 py-4">
              <div className="flex items-center gap-3">
                <span className="flex size-9 shrink-0 items-center justify-center rounded-full bg-primary/10 text-primary">
                  <Bookmark className="size-4" aria-hidden="true" />
                </span>
                <div>
                  <h3 className="text-[15px] font-bold text-foreground">{copy.savedTitle}</h3>
                  <p className="text-xs text-muted-foreground">{copy.savedDescription}</p>
                </div>
              </div>
              <Link href="/dashboard/saved" className="flex shrink-0 items-center gap-1 text-sm font-semibold text-primary hover:underline">
                {copy.openSaved}
                <ArrowUpRight className="size-3.5" aria-hidden="true" />
              </Link>
            </div>

            {savedProducts.length === 0 ? (
              <div className="flex flex-col items-center justify-center gap-1 px-6 py-10 text-center">
                <Bookmark className="size-6 text-muted-foreground/60" aria-hidden="true" />
                <p className="text-sm text-muted-foreground">{copy.savedEmpty}</p>
              </div>
            ) : (
              <>
                {savedProducts.length > 5 && (
                  <div className="border-b px-5 py-3">
                    <div className="relative">
                      <Search className="pointer-events-none absolute top-1/2 left-3 size-3.5 -translate-y-1/2 text-muted-foreground" aria-hidden="true" />
                      <Input
                        value={savedFilter}
                        onChange={(event) => setSavedFilter(event.target.value)}
                        placeholder={copy.searchSaved}
                        className="h-9 pl-8 text-xs"
                        aria-label={copy.searchSaved}
                      />
                    </div>
                  </div>
                )}
                {filteredSavedProducts.length === 0 ? (
                  <p className="px-5 py-8 text-center text-xs text-muted-foreground">{copy.noSavedMatch}</p>
                ) : (
                  <motion.div
                    className="flex max-h-[360px] flex-col overflow-y-auto"
                    initial="hidden"
                    whileInView="visible"
                    viewport={{ once: true, amount: 0.1 }}
                    variants={STAGGER_CONTAINER}
                  >
                    {filteredSavedProducts.map((saved) => (
                      <motion.div
                        key={saved.id}
                        variants={STAGGER_ITEM}
                        className="flex items-center gap-3 border-b px-5 py-3 last:border-b-0 hover:bg-muted/40"
                      >
                        <ProductImage src={saved.product.imageUrl} alt={saved.product.canonicalName} className="size-11 shrink-0 rounded-lg border" sizes="44px" fit="contain" />
                        <Link href={`/dashboard/products/${saved.product.id}`} className="min-w-0 flex-1">
                          <strong className="block truncate text-[12.5px] font-semibold text-foreground hover:text-primary">{saved.product.canonicalName}</strong>
                          <span className="block truncate text-[11px] text-muted-foreground">
                            {saved.product.category}
                            {saved.product.currentPrice !== null
                              ? ` · ${new Intl.NumberFormat(locale === "es" ? "es-US" : "en-US", { style: "currency", currency: saved.product.currency }).format(saved.product.currentPrice)}`
                              : ""}
                          </span>
                        </Link>
                        <Badge className={cn("shrink-0 font-bold tabular-nums", scoreBadgeClasses(saved.product.score?.opportunityScore ?? null))}>
                          {saved.product.score?.opportunityScore ?? copy.unavailable}
                        </Badge>
                        {askDisabledReason ? (
                          <Tooltip>
                            <TooltipTrigger asChild>
                              <span className="inline-block shrink-0">
                                <Button type="button" variant="outline" size="sm" className="gap-1" disabled>
                                  <Sparkle className="size-3.5" aria-hidden="true" />
                                  {copy.generateScript}
                                </Button>
                              </span>
                            </TooltipTrigger>
                            <TooltipContent>{askDisabledReason}</TooltipContent>
                          </Tooltip>
                        ) : (
                          <Button type="button" variant="outline" size="sm" className="shrink-0 gap-1" disabled={pending} onClick={() => generateScript(saved)}>
                            <Sparkle className="size-3.5" aria-hidden="true" />
                            {copy.generateScript}
                          </Button>
                        )}
                      </motion.div>
                    ))}
                  </motion.div>
                )}
              </>
            )}
          </Card>
        </div>

        {/* Context / credits sidebar */}
        <div className="flex flex-col gap-4">
          <Card className="flex flex-col gap-0 rounded-2xl p-5">
            <div className="flex items-center justify-between gap-3 border-b pb-4">
              <div>
                <span className="block text-[10px] font-bold tracking-wide text-muted-foreground uppercase">TokNext data</span>
                <h2 className="text-base font-bold text-foreground">{copy.contextTitle}</h2>
              </div>
              <ShieldCheck className="size-5 shrink-0 text-primary" aria-hidden="true" />
            </div>

            <div className="flex flex-col gap-4 py-4">
              <div>
                <div className="flex items-baseline justify-between text-xs text-muted-foreground">
                  <span>{copy.usage}</span>
                  <span className="font-bold text-foreground tabular-nums">
                    {usage.used}/{usage.limit}
                  </span>
                </div>
                <Progress value={percent} className="mt-2 h-1.5" />
              </div>

              <div className="flex flex-col gap-1.5 rounded-xl border bg-white p-3 shadow-sm dark:bg-card">
                <span className="mb-1 text-[10px] font-bold tracking-wide text-muted-foreground uppercase">{copy.creditsByPlan}</span>
                {planOrder.map((code) => {
                  const isCurrent = plan === code;
                  return (
                    <div
                      key={code}
                      className={cn("relative flex items-center justify-between gap-2 rounded-lg px-2.5 py-2 text-xs", !isCurrent && "text-muted-foreground")}
                      style={
                        isCurrent
                          ? {
                              background: "linear-gradient(90deg,rgba(5,187,220,.16),rgba(8,111,154,.28))",
                              boxShadow: "inset 0 0 0 1px rgba(8,111,154,.18)",
                            }
                          : undefined
                      }
                    >
                      {isCurrent && (
                        <span
                          className="absolute top-1 bottom-1 left-0 w-[3px] rounded-r"
                          style={{ background: "linear-gradient(180deg,#18e2f1,#238bf4)", boxShadow: "0 0 12px rgba(23,209,237,.55)" }}
                        />
                      )}
                      <span className={cn("flex items-center gap-1.5", isCurrent && "font-bold text-foreground")}>
                        {planLabel(code, locale)}
                        {isCurrent && (
                          <span
                            className="rounded-full px-1.5 py-0.5 text-[9px] font-bold text-white"
                            style={{ background: "linear-gradient(90deg,#11cbe9 0%,#6c5af4 48%,#ff3f78 100%)" }}
                          >
                            {copy.yourPlan}
                          </span>
                        )}
                      </span>
                      <span className={cn("font-semibold tabular-nums", isCurrent && "text-foreground")}>
                        {new Intl.NumberFormat(locale === "es" ? "es-US" : "en-US").format(PLANS[code].limits.aiGenerationsMonthly)}/{copy.perMonth}
                      </span>
                    </div>
                  );
                })}
                {plan !== "AGENCY" && (
                  <Link
                    href="/dashboard/billing"
                    className="mt-1.5 flex items-center justify-center gap-1.5 rounded-lg border border-white/20 px-3 py-2 text-xs font-bold text-white shadow-[inset_0_1px_0_rgba(255,255,255,0.25),0_6px_16px_-4px_rgba(117,72,239,0.5)] transition-transform hover:-translate-y-0.5"
                    style={{ background: "linear-gradient(90deg,#11cbe9 0%,#6c5af4 48%,#ff3f78 100%)" }}
                  >
                    {copy.upgradeForMore}
                    <ArrowUpRight className="size-3.5" aria-hidden="true" />
                  </Link>
                )}
              </div>
            </div>
          </Card>
        </div>
      </div>
    </div>
  );
}
