"use client";

import { useEffect, useState, useTransition } from "react";
import { LinkIcon, ListPlusIcon, TagIcon, Unlink2Icon } from "lucide-react";
import {
  detachKeywordAction,
  metricHistoryAction,
} from "@/app/(app)/sites/[siteId]/keywords/actions";
import { AttachBadge } from "@/components/keywords/attach-badge";
import { IntentBadge } from "@/components/keywords/intent-badge";
import type { KeywordTableRow } from "@/components/keywords/keyword-columns";
import type { OpenDialog } from "@/components/keywords/keyword-row-actions";
import { LabeledFigure } from "@/components/keywords/metric-cell";
import { SourcePill } from "@/components/keywords/source-pill";
import { Sparkline } from "@/components/keywords/sparkline";
import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
import { Separator } from "@/components/ui/separator";
import {
  Sheet,
  SheetContent,
  SheetDescription,
  SheetHeader,
  SheetTitle,
} from "@/components/ui/sheet";
import { cpcFormat, sourceLabel, type MetricPoint } from "@/lib/keywords";

// Panneau de détail d'un mot-clé — ce que la cellule ne peut pas montrer sans devenir large :
// **une section par source**, chacune avec ses chiffres, sa fraîcheur et sa tendance.
//
// C'est aussi le chemin **clavier** vers les autres sources : l'infobulle des cellules est un
// confort de souris qui n'est pas focusable, ce panneau est ce qui rend l'information
// atteignable au clavier comme au lecteur d'écran.
//
// Ni SERP ni backlinks : nous n'avons pas ces données, et une section vide en promettrait.
//
// L'historique est chargé **à l'ouverture**, jamais avec la page — cinquante lignes
// déclencheraient cinquante requêtes pour un panneau qu'on ouvre une fois. Il est mémorisé
// par mot-clé, avec la fraîcheur de la ligne dans la clé : un import qui apporte de nouvelles
// observations change cette date, donc la clé, donc l'entrée — le cache ne peut pas devenir
// périmé.

const CACHE_LIMIT = 200;
const historyCache = new Map<string, MetricPoint[]>();

function cacheKey(targetId: string, keywordId: string, freshness: string | null): string {
  return `${targetId} ${keywordId} ${freshness ?? ""}`;
}

const numberFormat = new Intl.NumberFormat("fr-FR");
//  Le CPC se formate par `cpcFormat` (`lib/keywords.ts`), partagé avec le réservoir.
const dateFormat = new Intl.DateTimeFormat("fr-FR", {
  day: "2-digit",
  month: "2-digit",
  year: "numeric",
});

function formatNumber(value: number | null): string {
  return value === null ? "—" : numberFormat.format(value);
}

/**
 * Les sources de cette ligne, dans l'ordre rendu par l'api (préférée en tête). Une source
 * apparaît dès qu'elle a observé **une** des quatre métriques ; l'écran ne retrie jamais.
 */
function sourcesOf(row: KeywordTableRow): string[] {
  const seen: string[] = [];
  for (const cell of [row.volume, row.kd, row.cpc, row.intent]) {
    for (const value of cell.values) {
      if (!seen.includes(value.source)) seen.push(value.source);
    }
  }
  return seen;
}

/** Un chiffre du panneau : la valeur d'**une** source, en mono tabulaire. */
function Figure({ label, value }: { label: string; value: string }) {
  return (
    <LabeledFigure label={label}>
      <span className="num text-sm">{value}</span>
    </LabeledFigure>
  );
}

export function KeywordSheet({
  row,
  open,
  onOpenChange,
  siteId,
  targetId,
  targetLabel,
  openDialog,
}: {
  row: KeywordTableRow | null;
  open: boolean;
  onOpenChange: (open: boolean) => void;
  siteId: string;
  targetId: string;
  /** Cible de la page, « France · français » — le cloisonnement pays de la décision A6. */
  targetLabel: string;
  openDialog: OpenDialog;
}) {
  const [pending, startTransition] = useTransition();

  const keywordId = row?.id ?? null;
  const freshness = row?.lastUpdated ?? null;
  const key = keywordId === null ? null : cacheKey(targetId, keywordId, freshness);

  // L'état retenu porte **sa** clé. C'est ce qui permet de lire le cache pendant le rendu et
  // de n'écrire dans l'état que depuis la réponse asynchrone : passer d'un mot-clé à l'autre
  // ne montre jamais l'historique du précédent, et aucun `setState` n'est appelé
  // synchronement dans un effet.
  const [fetched, setFetched] = useState<{ key: string; points: MetricPoint[] } | null>(null);
  const [failed, setFailed] = useState<{ key: string; message: string } | null>(null);
  const [detached, setDetached] = useState<{ id: string; message: string } | null>(null);

  const cached = key === null ? undefined : historyCache.get(key);
  const points = cached ?? (fetched !== null && fetched.key === key ? fetched.points : null);
  const historyError = failed !== null && failed.key === key ? failed.message : null;
  const detachError = detached !== null && detached.id === keywordId ? detached.message : null;

  useEffect(() => {
    if (!open || key === null || keywordId === null || historyCache.has(key)) return;
    // Le panneau peut être refermé, ou changer de mot-clé, avant la réponse : le drapeau
    // évite d'écrire l'historique d'une ligne dans l'état d'une autre.
    let current = true;
    void metricHistoryAction({ keywordId, targetId }).then((result) => {
      if (!current) return;
      if ("error" in result) {
        setFailed({ key, message: result.error });
        return;
      }
      if (historyCache.size >= CACHE_LIMIT) historyCache.clear();
      historyCache.set(key, result.history.items);
      setFetched({ key, points: result.history.items });
    });
    return () => {
      current = false;
    };
  }, [open, key, keywordId, targetId]);

  if (row === null) return null;

  const sources = sourcesOf(row);
  const intent = row.intent.values[0]?.text ?? null;
  const ids = [row.id];

  /** La tendance sur douze mois de cette source, telle qu'elle a été importée. */
  const trendOf = (source: string): number[] =>
    (points ?? []).find((point) => point.source === source && (point.trend?.length ?? 0) > 1)
      ?.trend ?? [];

  return (
    <Sheet open={open} onOpenChange={onOpenChange}>
      <SheetContent className="gap-0 overflow-y-auto data-[side=right]:w-full data-[side=right]:sm:max-w-md">
        <SheetHeader className="gap-2">
          <SheetTitle className="pr-8 break-words">{row.keyword}</SheetTitle>
          <SheetDescription>
            {targetLabel} · {sources.length} source{sources.length > 1 ? "s" : ""}
          </SheetDescription>
          {intent === null ? null : (
            <div className="flex flex-wrap gap-2">
              <IntentBadge value={intent} />
            </div>
          )}
        </SheetHeader>

        <div className="flex min-w-0 flex-col gap-4 px-4 pb-6">
          {sources.length === 0 ? (
            <p className="text-muted-foreground text-sm">Aucune observation.</p>
          ) : (
            sources.map((source) => {
              const volume = row.volume.values.find((value) => value.source === source);
              const kd = row.kd.values.find((value) => value.source === source);
              const cpc = row.cpc.values.find((value) => value.source === source);
              const observed =
                volume?.fetchedAtLabel ?? kd?.fetchedAtLabel ?? cpc?.fetchedAtLabel ?? "—";
              const trend = trendOf(source);
              return (
                <Card key={source} size="sm">
                  <CardHeader className="flex-row items-center gap-2">
                    <SourcePill source={source} />
                    <div className="min-w-0">
                      <CardTitle className="truncate">{sourceLabel(source)}</CardTitle>
                      <CardDescription>{observed}</CardDescription>
                    </div>
                  </CardHeader>
                  <CardContent className="flex flex-col gap-3">
                    <div className="grid grid-cols-3 gap-2">
                      <Figure label="Volume" value={volume?.text ?? "—"} />
                      <Figure label="KD" value={kd?.text ?? "—"} />
                      <Figure label="CPC" value={cpc?.text ?? "—"} />
                    </div>
                    {trend.length > 1 ? (
                      <div className="flex flex-col gap-1">
                        <span className="text-muted-foreground text-xs">Tendance 12 mois</span>
                        <Sparkline
                          values={trend}
                          label={`Tendance sur 12 mois — ${sourceLabel(source)}`}
                        />
                      </div>
                    ) : null}
                  </CardContent>
                </Card>
              );
            })
          )}

          <Separator />

          <div className="flex min-w-0 flex-col gap-2">
            <h3 className="font-heading text-sm font-medium">Attache</h3>
            <div className="flex flex-wrap items-center gap-2">
              <AttachBadge status={row.attachStatus} />
              <Button
                variant="outline"
                size="sm"
                onClick={() =>
                  openDialog({ kind: "attach", keywordIds: ids, keyword: row.keyword })
                }
              >
                <LinkIcon data-icon="inline-start" />
                Attacher
              </Button>
              <Button
                variant="ghost"
                size="sm"
                disabled={pending}
                onClick={() =>
                  startTransition(async () => {
                    setDetached(null);
                    const result = await detachKeywordAction({
                      siteId,
                      targetId,
                      keywordId: row.id,
                    });
                    if ("error" in result) setDetached({ id: row.id, message: result.error });
                  })
                }
              >
                <Unlink2Icon data-icon="inline-start" />
                Détacher
              </Button>
            </div>
            {detachError === null ? null : (
              <p role="alert" className="text-destructive text-xs">
                {detachError}
              </p>
            )}
          </div>

          <Separator />

          <div className="flex min-w-0 flex-col gap-2">
            <h3 className="font-heading text-sm font-medium">Listes et tags</h3>
            <div className="flex flex-wrap gap-1">
              {row.lists.length === 0 && row.tags.length === 0 ? (
                <span className="text-muted-foreground text-sm">—</span>
              ) : (
                <>
                  {row.lists.map((list) => (
                    <Badge key={list.id} variant="outline" className="font-normal">
                      {list.name}
                    </Badge>
                  ))}
                  {row.tags.map((tag) => (
                    <Badge key={tag.id} variant="secondary" className="font-normal">
                      {tag.name}
                    </Badge>
                  ))}
                </>
              )}
            </div>
            <div className="flex flex-wrap gap-2">
              <Button
                variant="outline"
                size="sm"
                onClick={() =>
                  openDialog({ kind: "list-add", keywordIds: ids, keyword: row.keyword })
                }
              >
                <ListPlusIcon data-icon="inline-start" />
                Ajouter à une liste
              </Button>
              <Button
                variant="outline"
                size="sm"
                onClick={() =>
                  openDialog({ kind: "tag-add", keywordIds: ids, keyword: row.keyword })
                }
              >
                <TagIcon data-icon="inline-start" />
                Tagger
              </Button>
            </div>
          </div>

          <Separator />

          <div className="flex min-w-0 flex-col gap-2">
            <h3 className="font-heading text-sm font-medium">Historique</h3>
            {historyError !== null ? (
              <p role="alert" className="text-destructive text-xs">
                {historyError}
              </p>
            ) : points === null ? (
              <p className="text-muted-foreground text-xs">Chargement…</p>
            ) : points.length === 0 ? (
              <p className="text-muted-foreground text-xs">Aucune observation enregistrée.</p>
            ) : (
              <div className="max-h-64 min-w-0 overflow-auto">
                <table className="w-full text-xs">
                  <thead className="text-muted-foreground">
                    <tr className="text-left">
                      <th className="py-1 font-medium">Source</th>
                      <th className="py-1 font-medium">Date</th>
                      <th className="py-1 text-right font-medium">Volume</th>
                      <th className="py-1 text-right font-medium">KD</th>
                      <th className="py-1 text-right font-medium">CPC</th>
                    </tr>
                  </thead>
                  <tbody>
                    {points.map((point) => (
                      <tr
                        key={`${point.source}-${point.fetched_at}`}
                        className="border-border-subtle border-t"
                      >
                        <td className="py-1 pr-2">{sourceLabel(point.source)}</td>
                        <td className="py-1 pr-2 num">
                          {dateFormat.format(new Date(point.fetched_at))}
                        </td>
                        <td className="py-1 text-right num">{formatNumber(point.volume)}</td>
                        <td className="py-1 text-right num">{formatNumber(point.kd)}</td>
                        <td className="py-1 text-right num">
                          {point.cpc === null ? "—" : cpcFormat.format(point.cpc)}
                        </td>
                      </tr>
                    ))}
                  </tbody>
                </table>
              </div>
            )}
          </div>
        </div>
      </SheetContent>
    </Sheet>
  );
}
