import type { ReactNode } from "react"
import { ArrowRight, BookmarkCheck, CircleCheck, Lock, Pencil, Trash2, Undo2 } from "lucide-react"
import { Badge } from "@/components/ui/badge"
import { Button } from "@/components/ui/button"
import { Checkbox } from "@/components/ui/checkbox"
import { cn } from "@/lib/utils"
import type { ChangesetResponse, RecordView } from "@/api/types"

type Tone = "update" | "delete" | "custom" | "readonly"

const TONE_META: Record<
  Tone,
  { label: string; empty: string; icon: typeof Pencil; dot: string; ring: string }
> = {
  update: {
    label: "Updates",
    empty: "Нет изменений — все значения совпадают.",
    icon: Pencil,
    dot: "var(--diff-update)",
    ring: "ring-[color-mix(in_oklch,var(--diff-update),transparent_78%)]",
  },
  delete: {
    label: "Prunes",
    empty: "Нечего удалять.",
    icon: Trash2,
    dot: "var(--diff-delete)",
    ring: "ring-[color-mix(in_oklch,var(--diff-delete),transparent_78%)]",
  },
  custom: {
    label: "Customs",
    empty: "Нет записей, помеченных как осознанные.",
    icon: BookmarkCheck,
    dot: "var(--diff-custom)",
    ring: "ring-[color-mix(in_oklch,var(--diff-custom),transparent_80%)]",
  },
  readonly: {
    label: "Read-only",
    empty: "Нет read-only записей.",
    icon: Lock,
    dot: "var(--diff-readonly)",
    ring: "ring-[color-mix(in_oklch,var(--diff-readonly),transparent_82%)]",
  },
}

function Values({ values }: { values?: string[] }) {
  if (!values || values.length === 0) {
    return <span className="text-muted-foreground/50">—</span>
  }
  return <>{values.join(", ")}</>
}

function RecordRow({
  record,
  tone,
  checked,
  onToggle,
  action,
}: {
  record: RecordView
  tone: Tone
  checked?: boolean
  onToggle?: (key: string) => void
  action?: ReactNode
}) {
  const meta = TONE_META[tone]
  const showArrow = tone !== "delete"
  // Read-only records aren't selectable — onToggle is only passed for
  // update/delete sections. Presence of onToggle is the selectability flag,
  // not the tone, so this stays correct if a tone's selectability ever changes.
  const selectable = onToggle !== undefined

  return (
    <div
      className={cn(
        "group/row flex flex-col gap-1.5 border-l-2 px-3 py-2.5 transition-colors",
        "hover:bg-foreground/[0.025]",
      )}
      style={{ borderLeftColor: meta.dot }}
    >
      {/* Top line: (optional) checkbox, type badge, name, read-only flag —
          always single-line, never affected by how long the record values are. */}
      <div className="flex items-center gap-3">
        {selectable && (
          <Checkbox
            checked={checked ?? false}
            onCheckedChange={() => onToggle!(record.key)}
            aria-label={`${tone === "delete" ? "Удалить" : "Применить"} ${record.type} ${record.name}`}
            className="shrink-0"
          />
        )}

        <Badge
          variant="outline"
          className="font-dns w-11 shrink-0 justify-center border-border text-[10px] tracking-wide text-muted-foreground"
        >
          {record.type}
        </Badge>

        <span className="font-dns min-w-0 flex-1 truncate text-sm text-foreground">
          {record.name}
        </span>

        {record.readOnly && (
          <Badge
            variant="secondary"
            className="shrink-0 gap-1 bg-muted text-[10px] text-muted-foreground"
          >
            <Lock className="size-2.5" strokeWidth={2} />
            read-only
          </Badge>
        )}

        {action}
      </div>

      {/* Values line: plain block-level text (not flex) so a long
          unbreakable value like a DKIM key wraps within the row's own
          width instead of stretching it — a flex item's content can
          otherwise refuse to shrink below its intrinsic width. Indented
          to align under the name (badge width + gap, plus checkbox width +
          gap when this row is selectable). */}
      <div
        className={cn(
          "font-dns hidden text-xs leading-relaxed break-all text-muted-foreground sm:block",
          selectable ? "pl-[5.25rem]" : "pl-14",
        )}
      >
        <Values values={record.actual} />
        {showArrow && (
          <>
            {" "}
            <ArrowRight
              className="mx-1 inline-block size-3 align-[-2px] text-muted-foreground/50"
              strokeWidth={1.75}
            />{" "}
            <span style={{ color: meta.dot }}>
              <Values values={record.desired} />
            </span>
          </>
        )}
      </div>
    </div>
  )
}

function Section({
  tone,
  records,
  selected,
  onToggle,
  onToggleAll,
  renderAction,
}: {
  tone: Tone
  records: RecordView[]
  selected?: Set<string>
  onToggle?: (key: string) => void
  onToggleAll?: (checked: boolean) => void
  renderAction?: (record: RecordView) => ReactNode
}) {
  const meta = TONE_META[tone]
  const Icon = meta.icon
  // Read-only (NS/SOA) and custom records are never selectable — only
  // update/delete sections receive selection props from DiffView.
  const selectable =
    tone !== "readonly" && tone !== "custom" && !!selected && !!onToggle && !!onToggleAll
  const allSelected = selectable && records.length > 0 && records.every((r) => selected!.has(r.key))
  const someSelected = selectable && records.some((r) => selected!.has(r.key))
  const indeterminate = someSelected && !allSelected

  return (
    <section aria-label={meta.label} className="flex flex-col gap-2">
      <header className="flex items-center gap-2 px-0.5">
        {selectable && records.length > 0 && (
          <Checkbox
            checked={allSelected}
            indeterminate={indeterminate}
            onCheckedChange={(v) => onToggleAll!(v === true)}
            aria-label={`Выбрать все — ${meta.label}`}
          />
        )}
        <Icon className="size-3.5" strokeWidth={1.75} style={{ color: meta.dot }} />
        <h2 className="text-xs font-semibold tracking-wide text-foreground uppercase">
          {meta.label}
        </h2>
        <Badge variant="outline" className="font-dns h-4.5 px-1.5 text-[10px] text-muted-foreground">
          {records.length}
        </Badge>
      </header>

      {records.length === 0 ? (
        <p className="rounded-lg border border-dashed border-border px-3 py-2.5 text-sm text-muted-foreground/70">
          {meta.empty}
        </p>
      ) : (
        <div
          className={cn(
            "flex flex-col divide-y divide-border rounded-lg bg-card ring-1",
            meta.ring,
          )}
        >
          {records.map((record) => (
            <RecordRow
              key={record.key}
              record={record}
              tone={tone}
              checked={selectable ? selected!.has(record.key) : undefined}
              onToggle={selectable ? onToggle : undefined}
              action={renderAction?.(record)}
            />
          ))}
        </div>
      )}
    </section>
  )
}

export function DiffView({
  changeset,
  selectedUpdates,
  selectedPrunes,
  onToggleUpdate,
  onTogglePrune,
  onToggleAllUpdates,
  onToggleAllPrunes,
  onMarkCustom,
  onUnmarkCustom,
  footerExtra,
}: {
  changeset: ChangesetResponse
  selectedUpdates: Set<string>
  selectedPrunes: Set<string>
  onToggleUpdate: (key: string) => void
  onTogglePrune: (key: string) => void
  onToggleAllUpdates: (checked: boolean) => void
  onToggleAllPrunes: (checked: boolean) => void
  onMarkCustom?: (key: string) => void
  onUnmarkCustom?: (key: string) => void
  footerExtra?: ReactNode
}) {
  // Defensive: a field may arrive as null (e.g. a nil slice from an older
  // backend) — normalise to [] so Section never calls .length/.map on null.
  return (
    <div className="flex flex-col gap-6">
      <Section
        tone="update"
        records={changeset.updates ?? []}
        selected={selectedUpdates}
        onToggle={onToggleUpdate}
        onToggleAll={onToggleAllUpdates}
      />
      <Section
        tone="delete"
        records={changeset.prunes ?? []}
        selected={selectedPrunes}
        onToggle={onTogglePrune}
        onToggleAll={onToggleAllPrunes}
        renderAction={
          onMarkCustom &&
          ((record) => (
            <Button
              variant="ghost"
              size="icon-sm"
              aria-label={`В customs ${record.type} ${record.name}`}
              onClick={() => onMarkCustom(record.key)}
            >
              <BookmarkCheck className="size-3.5" strokeWidth={1.75} />
            </Button>
          ))
        }
      />
      <Section
        tone="custom"
        records={changeset.customs ?? []}
        renderAction={
          onUnmarkCustom &&
          ((record) => (
            <Button
              variant="ghost"
              size="icon-sm"
              aria-label={`Вернуть в дифф ${record.type} ${record.name}`}
              onClick={() => onUnmarkCustom(record.key)}
            >
              <Undo2 className="size-3.5" strokeWidth={1.75} />
            </Button>
          ))
        }
      />
      <Section tone="readonly" records={changeset.readOnly ?? []} />

      <div className="flex items-center justify-between gap-3 border-t border-border pt-4">
        <div className="flex items-center gap-1.5 text-sm text-muted-foreground">
          <CircleCheck
            className="size-3.5"
            strokeWidth={1.75}
            style={{ color: "var(--diff-insync)" }}
          />
          <span className="font-dns">{changeset.inSyncCount}</span>
          <span>record{changeset.inSyncCount === 1 ? "" : "s"} in sync</span>
        </div>
        {footerExtra}
      </div>
    </div>
  )
}
