"use client";
import { Draggable } from "@hello-pangea/dnd";
import type { MouseEvent } from "react";
import { useT } from "@/hooks/i18n/useT";
import { cn } from "@/lib/utils";
import { formatValorDoNegocio, MOEDA_PADRAO } from "@/lib/money";
import { rotuloDaRecorrencia } from "@/lib/leads/valor-recorrente";
import type { Lead } from "@/lib/types/leads";
import type { Stage } from "@/lib/kanban/types";
import { resolveCardState, stageAgeLabel, type CardInput } from "@/lib/kanban/card-state";
import { KanbanCardActions } from "./KanbanCardActions";
import { NextActionSlot } from "./NextActionSlot";
import { ReactivationSlot } from "./ReactivationSlot";
import { ConversaSlot } from "./ConversaSlot";
import { ScoreSlot } from "./ScoreSlot";
import { OwnerBadge } from "./OwnerBadge";
import { ContatoNoCard } from "./ContatoNoCard";
import { ArquivosNoCard } from "./ArquivosNoCard";
import { TarefasNoCard } from "./TarefasNoCard";

/** Os dois gestos de seleção que o card sabe relatar. */
export type GestoDeSelecao = "alterna" | "intervalo";

interface KanbanCardProps {
  /** O que o card mostra — explicitamente NÃO é a linha do banco. */
  card: CardInput;
  /** A linha do lead, só para o menu de ações (que muta o lead). */
  lead: Lead;
  index: number;
  pipelineId: string;
  isSelected?: boolean;
  /**
   * Há seleção viva no quadro. Só muda a VISIBILIDADE da caixa (que fora disso
   * aparece no hover/foco): quando o usuário já está selecionando, esconder as
   * caixas dos outros cards transforma "clicar em mais um" numa caça ao pixel.
   */
  isSelecting?: boolean;
  /**
   * Contador de pulsos deste card (evento REMOTO). Muda a cada evento novo — é
   * a MUDANÇA que remonta o overlay e reinicia a animação; um booleano deixaria
   * o segundo evento dentro da janela passar despercebido.
   */
  pulseCount?: number;
  /**
   * `alterna` = um card entra/sai da seleção. `intervalo` = daqui até a âncora
   * (shift). Quem resolve o intervalo é a COLUNA, que é a única que conhece a
   * ordem visível dos cards — o card só relata o gesto.
   */
  onSelect?: (leadId: string, gesto: GestoDeSelecao) => void;
  /** Abrir o dossiê. Separado de `onSelect`: são gestos e intenções diferentes. */
  onOpen?: (leadId: string) => void;
  /** Repassados ao menu de ações ("Mover para…"). */
  etapas?: Stage[];
  onMoverPara?: (lead: Lead, stageId: string) => void;
  /**
   * Densidade COMPACTA (issue #3461, item 2): cabe mais contato na mesma tela.
   *
   * O que muda é o ORÇAMENTO de altura, não o conteúdo: o título passa de duas
   * linhas para uma (com o texto inteiro no `title` e no dossiê), os respiros
   * encolhem e a última mensagem e o bloco de contato saem do card — os dois
   * continuam inteiros no dossiê (`ConversaNoDossie` e `ContatoDoNegocio`, que
   * abrem com o mesmo clique). Valor, dono, selo de tarefas, tempo na etapa e a
   * faixa do agente continuam aqui: são o que se lê de relance para decidir.
   */
  denso?: boolean;
  /**
   * Quadro de funil arquivado (#1298): o card é só leitura. Sem arraste, sem
   * menu de ações, sem caixa de seleção e sem abrir o dossiê (que tem ações
   * de escrita próprias).
   */
  somenteLeitura?: boolean;
}

function formatValor(cents: number | null, currency: string | null): string | null {
  // A régua do negócio (×100 em qualquer moeda) e o locale da moeda moram em
  // `formatValorDoNegocio` — a mesma função do total da coluna, para o card e o
  // topo da coluna nunca escreverem o mesmo dinheiro de dois jeitos.
  if (cents == null) return null;
  return formatValorDoNegocio(cents, currency ?? MOEDA_PADRAO, { semCentavos: true });
}

/**
 * O card do Kanban — orçamento FIXO: 5 elementos, 3 faixas, altura constante.
 *
 * As alturas são reservadas em vez de derivadas do conteúdo: título sempre
 * ocupa 2 linhas, valor sempre ocupa a sua linha (com "—" quando não há valor),
 * e a faixa do agente existe mesmo vazia. É isso que faz o board continuar
 * legível quando score, próxima ação e alerta chegarem — o card não cresce com
 * dados, ele TROCA de estado.
 *
 * Cor só aparece na borda esquerda, e só quando o estado pede (Lei C).
 */
export function KanbanCard({
  card,
  lead,
  index,
  pipelineId,
  isSelected,
  isSelecting = false,
  pulseCount = 0,
  onSelect,
  onOpen,
  etapas,
  onMoverPara,
  denso = false,
  somenteLeitura = false,
}: KanbanCardProps) {
  const t = useT();
  const value = formatValor(card.valueCents, card.currency);
  /**
   * A MENSALIDADE, ao lado do valor único (fatia 1 da #3602): o card escreve
   * "R$ 3.000 + R$ 500/mês". Os dois números aparecem — nunca a soma, que é
   * exatamente o que a issue pediu para não fazer — e o negócio de valor único
   * fica com a linha de sempre (o segundo trecho só existe quando há
   * recorrência). `semCentavos` acompanha o valor único: o card nunca mostrou
   * centavos, e a linha tem altura fixa.
   */
  const recorrente = rotuloDaRecorrencia(
    card.recurringValueCents,
    card.recurringPeriod,
    card.currency ?? MOEDA_PADRAO,
    { semCentavos: true, sufixo: t("/mês") },
  );
  const state = resolveCardState(card, t);
  const age = stageAgeLabel(card.hoursInStage, t);

  // Clique ABRE o dossiê; ctrl/cmd+clique SELECIONA; shift+clique estende até a
  // âncora. "Clicar abre" é a convenção mais forte, e seleção múltipla é recurso
  // de poder, que tolera modificador. O arrasto continua funcionando porque o
  // dnd distingue clique de arrasto por movimento, não por handler.
  //
  // A CAIXA abaixo existe porque modificador não se descobre: até ela, a única
  // porta para o lote era saber que ctrl+clique fazia algo — e um recurso que
  // só quem já sabe encontra não é recurso, é folclore.
  //
  // ⚠️ UMA função, dois pontos de entrada — e a duplicação que existia aqui
  // custou o recurso inteiro no alvo mais óbvio. O TÍTULO é um `<button>` com
  // `stopPropagation()` (ver abaixo), então o clique nele NUNCA chega a este
  // handler; e o `onClick` do título ignorava os modificadores e abria o dossiê
  // sempre. Medido pela tela em 2026-09-04, com 6 cards e a âncora no 2º:
  //
  //   shift+clique no TÍTULO do 5º  → 1 marcado, 1 diálogo aberto (o dossiê)
  //   shift+clique no CORPO  do 5º  → 4 marcados, 0 diálogos
  //
  // O título é o maior e mais natural alvo do card. Quem lê "Segurando Shift,
  // um clique seleciona tudo entre o card anterior e o que você clicou" e clica
  // no card clica no nome dele — e recebia o dossiê.
  const decidirClique = (e: {
    shiftKey: boolean;
    metaKey: boolean;
    ctrlKey: boolean;
  }): void => {
    if (e.shiftKey) {
      onSelect?.(card.id, "intervalo");
      return;
    }
    if (e.metaKey || e.ctrlKey) {
      onSelect?.(card.id, "alterna");
      return;
    }
    onOpen?.(card.id);
  };
  const handleClick = (e: MouseEvent<HTMLDivElement>) => decidirClique(e);

  return (
    <Draggable draggableId={card.id} index={index} isDragDisabled={somenteLeitura}>
      {(provided, snapshot) => (
        <div
          ref={provided.innerRef}
          {...provided.draggableProps}
          {...provided.dragHandleProps}
          // O dnd marca o handle como role="button"; com o menu de ações dentro,
          // isso vira nested-interactive no axe. "group" mantém o foco e o
          // teclado do dnd (tabIndex e handlers continuam vindo do spread) sem
          // aninhar dois controles — nada de aria-hidden nem de suprimir regra.
          role="group"
          aria-label={`${t("Negócio")}: ${card.title}`}
          onClick={handleClick}
          // Tags saem do card (Lei A): ficam a um hover, sem ocupar altura.
          title={card.tags.length > 0 ? `Tags: ${card.tags.join(", ")}` : undefined}
          // `data-densidade` é o observável da densidade: o teste de tela mede
          // o card, não o estado do React.
          data-densidade={denso ? "compacta" : "confortavel"}
          className={cn(
            "group relative overflow-hidden rounded-md border border-border bg-surface",
            denso ? "py-1.5 pl-2.5 pr-2" : "py-2.5 pl-3 pr-3",
            "shadow-xs transition-colors",
            "hover:border-border-strong",
            snapshot.isDragging && "rotate-1 shadow-md ring-1 ring-accent/40",
            isSelected && "ring-2 ring-accent",
          )}
        >
          {/* key = contador: cada evento remoto monta um overlay NOVO, e é isso
              que reinicia a animação. Fica no elemento interno — pôr no wrapper
              remontaria o draggable e quebraria o arrasto. */}
          {pulseCount > 0 && (
            <span
              key={pulseCount}
              aria-hidden
              // Observável de propósito: é assim que o teste prova que o
              // overlay REMONTOU (contador novo) em vez de ter sobrado do
              // evento anterior — e "sobrou" era exatamente o defeito.
              data-pulse={pulseCount}
              className="card-pulse pointer-events-none absolute inset-0"
            />
          )}
          {/* Borda de estado — 2px, a única cor do card. */}
          <span
            aria-hidden
            className={cn(
              "absolute inset-y-0 left-0 w-0.5",
              state.border === "accent" && "bg-accent",
              state.border === "warning" && "bg-warning",
              state.border === "neutral" && "bg-transparent",
            )}
          />

          {/* ① identidade — altura FIXA de 2 linhas, com ou sem texto longo. */}
          <div className="flex items-start justify-between gap-2">
            <div className="flex min-w-0 flex-1 items-start gap-1.5">
              {/* A largura é SEMPRE reservada (`h-4 w-4` num wrapper que não
                  some), só a tinta é condicional: o card tem orçamento fixo de
                  altura e largura, e uma caixa que aparece no hover EMPURRANDO
                  o título faria o quadro inteiro tremer com o mouse. Some por
                  opacidade, nunca por `hidden`. `focus:opacity-100` no próprio
                  input: uma caixa invisível e tabulável seria armadilha de
                  teclado. */}
              {/* No funil arquivado a caixa some de vez: seleção só existe
                  para a ação em lote, e escrever ali é o que o arquivo proíbe (#1298). */}
              {!somenteLeitura && (
                <input
                  type="checkbox"
                  checked={Boolean(isSelected)}
                  aria-label={`${t("Selecionar")}: ${card.title}`}
                  onClick={(e) => {
                    // O card inteiro tem onClick (abre o dossiê): sem parar a
                    // propagação, marcar a caixa abriria o dossiê por cima.
                    e.stopPropagation();
                    onSelect?.(card.id, e.shiftKey ? "intervalo" : "alterna");
                  }}
                  onChange={() => {
                    /* estado vem de `isSelected`; quem decide é o onClick acima */
                  }}
                  className={cn(
                    "mt-1 h-4 w-4 shrink-0 cursor-pointer accent-accent transition-opacity",
                    "focus:opacity-100 focus-visible:outline-2 focus-visible:outline-accent",
                    isSelected || isSelecting
                      ? "opacity-100"
                      : "opacity-0 group-hover:opacity-100",
                  )}
                />
              )}
              {card.canonicalTag && (
                <span
                  className="mt-1.5 h-1.5 w-1.5 shrink-0 rounded-full bg-accent"
                  title={card.canonicalTag}
                  // role="img": um span nu não aceita aria-label (aria-prohibited-attr).
                  role="img"
                  aria-label={`${t("Tag")}: ${card.canonicalTag}`}
                />
              )}
              {/* O TÍTULO é o elemento ativável, não o card inteiro.
                  `role="group"` no card foi decisão da wave 2 (o dnd marca o
                  handle como button, e com o menu de ações dentro isso vira
                  nested-interactive no axe). Voltar o card para `button`
                  reintroduziria aquele defeito com cara de melhoria de
                  acessibilidade; deixar só onKeyDown daria uma ação que existe
                  e NÃO É DESCOBERTA por leitor de tela. O título como button
                  atende mouse, teclado e leitor sem desfazer a decisão antiga. */}
              <h3
                // Compacto: UMA linha, com o texto inteiro no `title` (e no
                // dossiê). O dado não some — some a segunda linha.
                title={denso ? card.title : undefined}
                className={cn(
                  "text-sm font-medium leading-5 text-text",
                  denso ? "line-clamp-1 h-5" : "line-clamp-2 h-10",
                )}
              >
                <button
                  type="button"
                  onClick={(e) => {
                    // `stopPropagation` continua: sem ele o handler do card
                    // rodaria de novo e o gesto seria contado duas vezes (um
                    // ctrl+clique marcaria e desmarcaria no mesmo instante).
                    // Por isso a DECISÃO tem de ser tomada aqui também.
                    e.stopPropagation();
                    decidirClique(e);
                  }}
                  className="text-left hover:underline"
                >
                  {card.title}
                </button>
              </h3>
            </div>
            {!somenteLeitura && (
              <KanbanCardActions
                lead={lead}
                pipelineId={pipelineId}
                etapas={etapas}
                onMoverPara={onMoverPara}
              />
            )}
          </div>

          {/* ② valor — altura reservada mesmo sem valor, senão o card encolhe.
              `card-valor` é o observável do teste: o MESMO elemento carrega o
              valor único e a mensalidade, para nenhuma tela de teste achar que
              "os dois aparecem" olhando dois lugares diferentes. */}
          <p
            data-testid="card-valor"
            className={cn(
              "flex items-baseline gap-1 font-medium tabular-nums",
              denso ? "mt-0.5 h-4 text-[11px] leading-4" : "mt-1 h-5 text-xs leading-5",
              value || recorrente ? "text-text" : "text-text-muted",
            )}
            title={recorrente ? `${value ?? ""} + ${recorrente}`.trim() : undefined}
          >
            {(value || recorrente) && (
              <span className="truncate">{value ?? t("Sem valor único")}</span>
            )}
            {recorrente && (
              <span className="shrink-0 text-text-muted">
                {value ? "+ " : ""}
                {recorrente}
              </span>
            )}
            {!value && !recorrente && "—"}
          </p>

          {/* ③ a linha do agente — um slot, três estados, nunca três blocos. */}
          <div
            className={cn(
              "flex items-center gap-2 text-xs",
              denso ? "mt-1 h-5" : "mt-1.5 h-6",
            )}
          >
            {state.slot.type === "awaiting" && (
              // A proposta do agente é a ÚNICA linha do card com ação: é o
              // ponto onde a decisão do humano entra. Sem os botões aqui, o
              // texto seria só mais um aviso — e a wave existe porque avisar
              // sem poder decidir é o que já acontecia (o dado ficava no banco).
              <NextActionSlot
                label={state.slot.label}
                leadId={card.id}
                approvedSeq={lead.next_action?.seq ?? -1}
                pipelineId={pipelineId}
              />
            )}
            {state.slot.type === "reactivation" && (
              // O negócio parou E aqui está o que fazer. Mesma faixa, mesma
              // altura: o card não cresce quando o sistema tem algo a propor.
              <ReactivationSlot
                leadId={card.id}
                proposalId={state.slot.proposalId}
                expiresAt={state.slot.expiresAt}
                pipelineId={pipelineId}
              />
            )}
            {state.slot.type === "cooling" && (
              // -fg é a variante de TEXTO do token (o -warning puro dá 3.7:1 em
              // 12px); a cor cheia fica na borda de estado, que é gráfica.
              <span className="truncate text-warning-fg">{state.slot.label}</span>
            )}
            {state.slot.type === "meter" && (
              <ScoreSlot
                probability={state.slot.probability}
                band={state.slot.band}
                reason={state.slot.reason}
                factors={state.slot.factors}
              />
            )}
          </div>

          {/* A última mensagem, com atalho para o inbox. Fica ANTES do rodapé
              de dono/tempo porque é conteúdo do negócio, não metadado do card —
              e some por inteiro quando não há conversa.

              ⚠️ NO COMPACTO ela SAI DO CARD (issue #3461, item 2), e é decisão
              declarada, não perda silenciosa: as duas linhas variáveis do card
              (esta e o contato abaixo) são as que fazem a altura depender do
              dado, e densidade com altura variável não densidade — é o mesmo
              card de antes com outro nome. As duas continuam inteiras no
              DOSSIÊ (`ConversaNoDossie`, `ContatoDoNegocio`), que abre com o
              mesmo clique no card. O que fica no compacto é o que se lê de
              relance para decidir: título, valor, faixa do agente, dono,
              tarefas e tempo na etapa. */}
          {!denso && (
            <>
              <ConversaSlot
                conversa={lead.conversa}
                contactId={lead.contact_id}
                phone={lead.contact_phone}
              />

              {/* Telefone, e-mail e links do contato — some por inteiro quando o
                  negócio não tem nada disso, como a conversa acima. */}
              <ContatoNoCard lead={lead} />
            </>
          )}

          {/* Os campos `file` do funil (#2829): contagem + o documento clicável.
              Fica junto do contato, entre o conteúdo do negócio e o rodapé de
              dono/tempo. Some por inteiro quando o funil não declara campo de
              arquivo — quem não usa o recurso não vê card diferente. */}
          <ArquivosNoCard lead={lead} />

          {/* ④ dono (+ selo de tarefas) · ⑤ tempo no estágio */}
          <div
            className={cn(
              "flex items-center justify-between gap-2",
              denso ? "mt-1 h-5" : "mt-1 h-6",
            )}
          >
            <div className="flex min-w-0 items-center gap-2">
              <OwnerBadge
                ownerKind={card.owner.kind}
                ownerName={card.owner.name}
                agentVersion={card.owner.agentVersion}
              />
              <TarefasNoCard tarefas={lead.tarefas} />
            </div>
            <span className="shrink-0 whitespace-nowrap text-[11px] tabular-nums text-text-muted">
              {state.showStageAge && age
                ? `${age} ${t("em")} ${card.stageName}`
                : `${t("em")} ${card.stageName}`}
            </span>
          </div>
        </div>
      )}
    </Draggable>
  );
}
