"use client";

import * as React from "react";
import {
  DndContext,
  KeyboardSensor,
  PointerSensor,
  closestCenter,
  type DragEndEvent,
  useSensor,
  useSensors,
} from "@dnd-kit/core";
import {
  SortableContext,
  sortableKeyboardCoordinates,
  useSortable,
  verticalListSortingStrategy,
} from "@dnd-kit/sortable";
import { CSS } from "@dnd-kit/utilities";
import { Columns3, GripVertical, Info, Search, X } from "lucide-react";

import type { CustomerGridColumn } from "@/app/customer/_lib/customer-grid-columns";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import {
  Sheet,
  SheetContent,
  SheetDescription,
  SheetTitle,
} from "@/components/ui/sheet";
import { Switch } from "@/components/ui/switch";
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip";
import { cn } from "@/lib/utils";

export type CustomerFieldsPanelGroup = {
  id: string;
  label: string;
};

type CustomerFieldsPanelProps = {
  open: boolean;
  onOpenChange: (open: boolean) => void;
  catalog: CustomerGridColumn[];
  visibility: Record<string, boolean>;
  order: string[];
  lockedKeys?: string[];
  /** When set, fields are split into labeled sections by catalog `group`. */
  groups?: CustomerFieldsPanelGroup[];
  title?: string;
  description?: string;
  /** Optional controls under the search box (e.g. tab switchers). */
  toolbar?: React.ReactNode;
  footer?: React.ReactNode;
  onToggle: (columnId: string, visible: boolean) => void;
  onReorder: (activeId: string, overId: string) => void;
  onHideAll: () => void;
  /** Hide all unlocked fields in one group (used when `groups` is set). */
  onHideGroup?: (groupId: string) => void;
  /** Keys of columns that support a "Total" toggle in addition to the Column toggle. */
  totalKeys?: Set<string>;
  /** Visibility state for the Total toggles, keyed by column key. */
  totalVisibility?: Record<string, boolean>;
  /** Called when a Total toggle is changed. */
  onTotalToggle?: (columnId: string, visible: boolean) => void;
};

function SortableFieldRow({
  id,
  label,
  checked,
  locked,
  onToggle,
  companions,
}: {
  id: string;
  label: string;
  checked: boolean;
  locked?: boolean;
  onToggle: (visible: boolean) => void;
  companions?: Array<{
    label: string;
    checked: boolean;
    onToggle: (visible: boolean) => void;
  }>;
}) {
  const { attributes, listeners, setNodeRef, transform, transition, isDragging } = useSortable({
    id,
  });

  return (
    <div
      ref={setNodeRef}
      style={{
        transform: CSS.Transform.toString(transform),
        transition,
      }}
      className={cn(
        "flex items-center gap-2 rounded-lg border border-border/20 bg-white px-2 py-1.5 transition-colors dark:bg-background",
        "hover:border-border/40 hover:bg-white dark:hover:bg-background",
        isDragging && "z-10 border-border/50 bg-white shadow-sm dark:bg-background",
      )}
    >
      <button
        type="button"
        className={cn(
          "inline-flex size-7 shrink-0 items-center justify-center rounded-md text-muted-foreground touch-none",
          "cursor-grab active:cursor-grabbing",
        )}
        aria-label={`Reorder ${label}`}
        {...attributes}
        {...listeners}
      >
        <GripVertical className="size-3.5" />
      </button>
      <span className="min-w-0 flex-1 truncate text-sm font-medium leading-snug">{label}</span>
      {companions && companions.length > 0 ? (
        <div className="flex shrink-0 items-center gap-3">
          <div className="flex flex-col items-end gap-0.5">
            <span className="text-[10px] font-medium leading-none text-muted-foreground">Column</span>
            <Switch
              checked={checked}
              disabled={locked}
              onCheckedChange={onToggle}
              aria-label={`Toggle column ${label}`}
              size="sm"
            />
          </div>
          {companions.map((comp) => (
            <div key={comp.label} className="flex flex-col items-end gap-0.5">
              <span className="text-[10px] font-medium leading-none text-muted-foreground">
                {comp.label}
              </span>
              <Switch
                checked={comp.checked}
                onCheckedChange={comp.onToggle}
                aria-label={`Toggle ${comp.label} for ${label}`}
                size="sm"
              />
            </div>
          ))}
        </div>
      ) : (
        <Switch
          checked={checked}
          disabled={locked}
          onCheckedChange={onToggle}
          aria-label={`Toggle ${label}`}
          size="sm"
        />
      )}
    </div>
  );
}

function FieldRow({
  label,
  checked,
  locked,
  onToggle,
  companions,
}: {
  label: string;
  checked: boolean;
  locked?: boolean;
  onToggle: (visible: boolean) => void;
  companions?: Array<{
    label: string;
    checked: boolean;
    onToggle: (visible: boolean) => void;
  }>;
}) {
  return (
    <div
      className={cn(
        "flex items-center gap-2 rounded-lg border border-border/20 bg-white px-2 py-1.5 transition-colors dark:bg-background",
        "hover:border-border/40",
      )}
    >
      <span className="size-7 shrink-0" />
      <span className="min-w-0 flex-1 truncate text-sm font-medium leading-snug">{label}</span>
      {companions && companions.length > 0 ? (
        <div className="flex shrink-0 items-center gap-3">
          <div className="flex flex-col items-end gap-0.5">
            <span className="text-[10px] font-medium leading-none text-muted-foreground">Column</span>
            <Switch
              checked={checked}
              disabled={locked}
              onCheckedChange={onToggle}
              aria-label={`Toggle column ${label}`}
              size="sm"
            />
          </div>
          {companions.map((comp) => (
            <div key={comp.label} className="flex flex-col items-end gap-0.5">
              <span className="text-[10px] font-medium leading-none text-muted-foreground">
                {comp.label}
              </span>
              <Switch
                checked={comp.checked}
                onCheckedChange={comp.onToggle}
                aria-label={`Toggle ${comp.label} for ${label}`}
                size="sm"
              />
            </div>
          ))}
        </div>
      ) : (
        <Switch
          checked={checked}
          disabled={locked}
          onCheckedChange={onToggle}
          aria-label={`Toggle ${label}`}
          size="sm"
        />
      )}
    </div>
  );
}

function FieldSectionList({
  shownKeys,
  hiddenKeys,
  labelByKey,
  companionByKey,
  visibility,
  lockedKeys = [],
  onToggle,
  onReorder,
  onHideShown,
  hideLabel = "Hide all",
  emptyShown = "No shown fields.",
  emptyHidden = "No hidden fields.",
  totalKeys,
  totalVisibility,
  onTotalToggle,
}: {
  shownKeys: string[];
  hiddenKeys: string[];
  labelByKey: Map<string, string>;
  companionByKey: Map<string, Array<{ key: string; label: string }>>;
  visibility: Record<string, boolean>;
  lockedKeys?: string[];
  onToggle: (columnId: string, visible: boolean) => void;
  onReorder: (activeId: string, overId: string) => void;
  onHideShown: () => void;
  hideLabel?: string;
  emptyShown?: string;
  emptyHidden?: string;
  totalKeys?: Set<string>;
  totalVisibility?: Record<string, boolean>;
  onTotalToggle?: (columnId: string, visible: boolean) => void;
}) {
  const sensors = useSensors(
    useSensor(PointerSensor, { activationConstraint: { distance: 6 } }),
    useSensor(KeyboardSensor, { coordinateGetter: sortableKeyboardCoordinates }),
  );

  const handleDragEnd = (event: DragEndEvent) => {
    const { active, over } = event;
    if (!over || active.id === over.id) return;
    onReorder(String(active.id), String(over.id));
  };

  const companionProps = (key: string) => {
    const companions = companionByKey.get(key);
    if (companions && companions.length > 0) {
      return companions.map((comp) => ({
        label: comp.label,
        checked: visibility[comp.key] !== false,
        onToggle: (visible: boolean) => onToggle(comp.key, visible),
      }));
    }
    // Separate totals prefs (accumulator / overall-summary) map into the same dual-toggle UI.
    if (totalKeys?.has(key) && onTotalToggle) {
      return [
        {
          label: "Total",
          checked: totalVisibility?.[key] !== false,
          onToggle: (visible: boolean) => onTotalToggle(key, visible),
        },
      ];
    }
    return undefined;
  };

  return (
    <div className="overflow-hidden rounded-2xl border border-border/40 bg-white shadow-sm dark:bg-card">
      <div className="flex items-center justify-between gap-2 bg-gradient-to-r from-primary/10 via-transparent to-transparent px-3.5 py-2">
        <p className="font-semibold text-sm tracking-tight">Shown</p>
        <button
          type="button"
          className="text-xs font-medium text-muted-foreground transition-colors hover:text-foreground"
          onClick={onHideShown}
        >
          {hideLabel}
        </button>
      </div>

      <div className="space-y-3 border-t border-border/40 bg-white p-2.5 dark:bg-card">
        <div className="space-y-0.5 overflow-hidden rounded-xl border border-border/25 bg-white p-1 dark:bg-background">
          <DndContext sensors={sensors} collisionDetection={closestCenter} onDragEnd={handleDragEnd}>
            <SortableContext items={shownKeys} strategy={verticalListSortingStrategy}>
              <div className="space-y-0.5">
                {shownKeys.length === 0 ? (
                  <p className="px-2 py-5 text-center text-muted-foreground text-xs">{emptyShown}</p>
                ) : (
                  shownKeys.map((key) => (
                    <SortableFieldRow
                      key={key}
                      id={key}
                      label={labelByKey.get(key) ?? key}
                      checked={visibility[key] !== false}
                      locked={lockedKeys.includes(key)}
                      onToggle={(visible) => onToggle(key, visible)}
                      companions={companionProps(key)}
                    />
                  ))
                )}
              </div>
            </SortableContext>
          </DndContext>
        </div>

        <div>
          <p className="mb-1.5 px-1 font-semibold text-muted-foreground text-[11px] uppercase tracking-wider">
            Hidden
          </p>
          <div className="space-y-0.5 overflow-hidden rounded-xl border border-border/25 bg-white p-1 dark:bg-background">
            <div className="space-y-0.5">
              {hiddenKeys.length === 0 ? (
                <p className="px-2 py-5 text-center text-muted-foreground text-xs">{emptyHidden}</p>
              ) : (
                hiddenKeys.map((key) => (
                  <FieldRow
                    key={key}
                    label={labelByKey.get(key) ?? key}
                    checked={false}
                    onToggle={(visible) => onToggle(key, visible)}
                    companions={companionProps(key)}
                  />
                ))
              )}
            </div>
          </div>
        </div>
      </div>
    </div>
  );
}

export function CustomerFieldsPanel({
  open,
  onOpenChange,
  catalog,
  visibility,
  order,
  lockedKeys = [],
  groups,
  title = "Fields",
  description,
  toolbar,
  footer,
  onToggle,
  onReorder,
  onHideAll,
  onHideGroup,
  totalKeys,
  totalVisibility,
  onTotalToggle,
}: CustomerFieldsPanelProps) {
  const [query, setQuery] = React.useState("");

  const helpText =
    description ?? "Show, hide, and reorder table columns. Preferences are saved for your user.";

  const labelByKey = React.useMemo(() => {
    const map = new Map<string, string>();
    for (const column of catalog) map.set(column.key, column.label);
    return map;
  }, [catalog]);

  const companionByKey = React.useMemo(() => {
    const map = new Map<string, Array<{ key: string; label: string }>>();
    for (const column of catalog) {
      if (!column.companion) continue;
      const comps = Array.isArray(column.companion) ? column.companion : [column.companion];
      const normalized = comps
        .filter((c) => typeof c.key === "string" && c.key.trim() !== "")
        .map((c) => ({ key: c.key, label: c.label }));
      if (normalized.length > 0) {
        map.set(column.key, normalized);
      }
    }
    return map;
  }, [catalog]);

  const companionOnlyKeys = React.useMemo(() => {
    const keys = new Set<string>();
    for (const companions of companionByKey.values()) {
      for (const c of companions) keys.add(c.key);
    }
    return keys;
  }, [companionByKey]);

  const groupByKey = React.useMemo(() => {
    const map = new Map<string, string | undefined>();
    for (const column of catalog) map.set(column.key, column.group);
    return map;
  }, [catalog]);

  const orderedKeys = React.useMemo(() => {
    const keys = order.filter(
      (key) => labelByKey.has(key) && !companionOnlyKeys.has(key),
    );
    for (const column of catalog) {
      if (companionOnlyKeys.has(column.key)) continue;
      if (!keys.includes(column.key)) keys.push(column.key);
    }
    return keys;
  }, [catalog, companionOnlyKeys, labelByKey, order]);

  const normalizedQuery = query.trim().toLowerCase();
  const matchesQuery = React.useCallback(
    (key: string) => {
      if (!normalizedQuery) return true;
      const label = labelByKey.get(key) ?? key;
      if (label.toLowerCase().includes(normalizedQuery) || key.toLowerCase().includes(normalizedQuery)) {
        return true;
      }
      const companions = companionByKey.get(key);
      if (!companions?.length) return false;
      return companions.some(
        (comp) =>
          comp.label.toLowerCase().includes(normalizedQuery) ||
          comp.key.toLowerCase().includes(normalizedQuery),
      );
    },
    [companionByKey, labelByKey, normalizedQuery],
  );

  const filterKeys = React.useCallback(
    (keys: string[], groupId?: string, includeLocked = false) =>
      keys.filter((key) => {
        if (!matchesQuery(key)) return false;
        if (!includeLocked && lockedKeys.includes(key)) return false;
        if (groupId == null) return true;
        return groupByKey.get(key) === groupId;
      }),
    [groupByKey, lockedKeys, matchesQuery],
  );

  const shownKeys = filterKeys(
    orderedKeys.filter((key) => visibility[key] !== false),
    undefined,
    true,
  );
  const hiddenKeys = filterKeys(orderedKeys.filter((key) => visibility[key] === false));

  const useGroups = Boolean(groups && groups.length > 0);
  const shownCount = orderedKeys.filter((key) => visibility[key] !== false).length;

  return (
    <Sheet open={open} onOpenChange={onOpenChange}>
      <SheetContent
        side="right"
        className="flex h-dvh w-full flex-col gap-0 overflow-hidden bg-muted/30 p-0 data-[side=right]:sm:max-w-md"
        showCloseButton={false}
      >
        <div className="shrink-0 space-y-2 px-4 pt-4 pr-12">
          <p className="text-muted-foreground text-[10px] font-semibold tracking-widest uppercase">
            Display
          </p>
          <div className="flex items-center justify-between gap-2">
            <SheetTitle className="flex min-w-0 items-center gap-2 text-left text-base font-semibold tracking-tight">
              <span className="flex size-8 shrink-0 items-center justify-center rounded-xl bg-primary text-primary-foreground shadow-md shadow-primary/25">
                <Columns3 className="size-3.5" />
              </span>
              <span className="truncate">{title}</span>
              <TooltipProvider delayDuration={200}>
                <Tooltip>
                  <TooltipTrigger asChild>
                    <button
                      type="button"
                      className="inline-flex size-6 shrink-0 items-center justify-center rounded-full text-muted-foreground transition-colors hover:bg-muted hover:text-foreground"
                      aria-label="About this panel"
                    >
                      <Info className="size-3.5" />
                    </button>
                  </TooltipTrigger>
                  <TooltipContent side="bottom" className="max-w-[240px] text-xs leading-relaxed">
                    {helpText}
                  </TooltipContent>
                </Tooltip>
              </TooltipProvider>
              {shownCount > 0 ? (
                <span className="inline-flex shrink-0 items-center rounded-full bg-muted px-2 py-0.5 text-[10px] font-medium tabular-nums text-muted-foreground">
                  {shownCount} visible
                </span>
              ) : null}
            </SheetTitle>
            <Button
              type="button"
              variant="ghost"
              size="icon-sm"
              className="rounded-lg"
              onClick={() => onOpenChange(false)}
              aria-label="Close fields panel"
            >
              <X className="size-4" />
            </Button>
          </div>
          <SheetDescription className="sr-only">{helpText}</SheetDescription>
        </div>

        <div className="shrink-0 space-y-2 px-4 py-2">
          <div className="relative">
            <Search className="pointer-events-none absolute top-1/2 left-2.5 size-3.5 -translate-y-1/2 text-muted-foreground" />
            <Input
              value={query}
              onChange={(event) => setQuery(event.target.value)}
              placeholder="Search…"
              className="h-8 border-border/60 bg-card/80 pl-8 text-sm backdrop-blur-sm"
            />
          </div>
          {toolbar ? <div className="pt-0.5">{toolbar}</div> : null}
        </div>

        <div className="min-h-0 flex-1 overflow-y-auto px-4 py-1">
          {useGroups ? (
            <div className="space-y-3 pb-3">
              {groups!.map((group) => {
                const groupShown = filterKeys(
                  orderedKeys.filter((key) => visibility[key] !== false),
                  group.id,
                  true,
                );
                const groupHidden = filterKeys(
                  orderedKeys.filter((key) => visibility[key] === false),
                  group.id,
                );
                return (
                  <section key={group.id} aria-label={group.label} className="space-y-1.5">
                    <p className="px-0.5 font-semibold text-xs tracking-tight text-muted-foreground">
                      {group.label}
                    </p>
                    <FieldSectionList
                      shownKeys={groupShown}
                      hiddenKeys={groupHidden}
                      labelByKey={labelByKey}
                      companionByKey={companionByKey}
                      visibility={visibility}
                      lockedKeys={lockedKeys}
                      onToggle={onToggle}
                      onReorder={onReorder}
                      onHideShown={() => {
                        if (onHideGroup) onHideGroup(group.id);
                        else onHideAll();
                      }}
                      hideLabel="Hide all"
                      emptyShown={`No shown ${group.label.toLowerCase()}.`}
                      emptyHidden={`No hidden ${group.label.toLowerCase()}.`}
                      totalKeys={totalKeys}
                      totalVisibility={totalVisibility}
                      onTotalToggle={onTotalToggle}
                    />
                  </section>
                );
              })}
            </div>
          ) : (
            <div className="pb-3">
              <FieldSectionList
                shownKeys={shownKeys}
                hiddenKeys={hiddenKeys}
                labelByKey={labelByKey}
                companionByKey={companionByKey}
                visibility={visibility}
                lockedKeys={lockedKeys}
                onToggle={onToggle}
                onReorder={onReorder}
                onHideShown={onHideAll}
                totalKeys={totalKeys}
                totalVisibility={totalVisibility}
                onTotalToggle={onTotalToggle}
              />
            </div>
          )}
        </div>

        {footer ? (
          <div className="shrink-0 border-t border-border/60 bg-card/80 px-4 py-3 backdrop-blur-md">
            {footer}
          </div>
        ) : null}
      </SheetContent>
    </Sheet>
  );
}

/** Convenience trigger button for toolbars — matches report `size-9` icon actions. */
export function CustomerFieldsButton({
  onClick,
  disabled,
  label = "Fields",
  showLabel = false,
  className,
}: {
  onClick: () => void;
  disabled?: boolean;
  label?: string;
  /** When true, render label text (e.g. dashboard “Cards”). Reports keep icon-only. */
  showLabel?: boolean;
  className?: string;
}) {
  const button = (
    <Button
      type="button"
      variant="outline"
      size={showLabel ? "sm" : "icon-lg"}
      className={cn(showLabel ? "h-9 gap-1.5 px-3" : "shrink-0", className)}
      onClick={onClick}
      disabled={disabled}
      aria-label={label}
    >
      <Columns3 className="size-4" />
      {showLabel ? label : <span className="sr-only">{label}</span>}
    </Button>
  );

  if (showLabel) return button;

  return (
    <TooltipProvider delayDuration={200}>
      <Tooltip>
        <TooltipTrigger asChild>{button}</TooltipTrigger>
        <TooltipContent side="bottom">{label}</TooltipContent>
      </Tooltip>
    </TooltipProvider>
  );
}
