"use client";

import * as React from "react";
import {
  DndContext,
  KeyboardSensor,
  PointerSensor,
  closestCenter,
  type DragEndEvent,
  useSensor,
  useSensors,
} from "@dnd-kit/core";
import {
  SortableContext,
  rectSortingStrategy,
  sortableKeyboardCoordinates,
  useSortable,
} from "@dnd-kit/sortable";
import { CSS } from "@dnd-kit/utilities";
import { ChevronDown, ChevronRight, GripVertical, Ungroup } from "lucide-react";

import { cn } from "@/lib/utils";
import type { DashboardCardGroup } from "@/app/customer/_lib/dashboard-card-groups";
import { shareFromLabel, splitCurrencyDisplay } from "@/app/customer/_lib/dashboard-card-display";
import {
  METRIC_TILE_PX,
  TYPE_ROW_GAP_PX,
  TYPE_ROW_MAX_PX,
  TYPE_ROW_PX,
  TYPE_ROWS_MAX_VISIBLE,
  TYPE_ROWS_TOP_PX,
} from "@/app/customer/_lib/dashboard-layout";
import type { MacroDashboardAssetType } from "@/app/customer/_lib/dashboard-layout-card-groups";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import {
  FiguresMaskedContext,
  IconChip,
  assetCardVisual,
} from "@/app/customer/_components/dashboard-overview-layout";

/* The `@[360px]/section:` classes below are written out in full on purpose — Tailwind only
   emits classes it can read literally in the source. 360px is also where the nested tiles go
   two-wide (see sectionContentColumns()). */
const STACK_LIMIT = 3;

/** Overlapping icon chips of the cards inside a group, so a closed group still shows what it holds. */
function MemberIconStack({
  memberKeys,
  className,
}: {
  memberKeys: string[];
  className?: string;
}) {
  if (memberKeys.length === 0) return null;
  const shown = memberKeys.slice(0, STACK_LIMIT);
  const extra = memberKeys.length - shown.length;

  return (
    <div className={cn("flex shrink-0 items-center", className)} aria-hidden>
      {shown.map((key, index) => {
        const { Icon, tone } = assetCardVisual(key);
        return (
          <div key={key} className={cn("rounded-md ring-2 ring-card", index > 0 && "-ml-1.5")}>
            <IconChip icon={Icon} tone={tone} size="sm" />
          </div>
        );
      })}
      {extra > 0 ? (
        <div className="-ml-1.5 flex size-6 items-center justify-center rounded-md bg-muted text-[10px] font-semibold text-muted-foreground ring-2 ring-card">
          +{extra}
        </div>
      ) : null}
    </div>
  );
}

/** One asset type of a Macro group: its name, figure, and share of the group as a faint bar. */
function AssetTypeRow({
  row,
  groupTotal,
  accentBar,
}: {
  row: MacroDashboardAssetType;
  groupTotal: number;
  accentBar: string;
}) {
  const figuresMasked = React.useContext(FiguresMaskedContext);
  const { figure } = splitCurrencyDisplay(row.display);
  const share = groupTotal !== 0 && !row.plain ? (row.amount / groupTotal) * 100 : null;
  const barWidth = share != null && share > 0 ? Math.min(100, share) : 0;

  return (
    <div
      // Lines share the tile's spare height, so a short list fills it instead of leaving a gap.
      style={{ flex: `1 1 ${TYPE_ROW_PX}px`, minHeight: TYPE_ROW_PX, maxHeight: TYPE_ROW_MAX_PX }}
      className="relative flex items-center justify-between gap-2 overflow-hidden rounded-md bg-card px-2.5"
      data-dashboard-asset-type={row.label}
    >
      {barWidth > 0 && !figuresMasked ? (
        <div
          className="pointer-events-none absolute inset-x-2 bottom-0 h-[2px] rounded-full bg-muted/60"
          aria-hidden
        >
          <div
            className={cn("h-full rounded-full opacity-70", accentBar)}
            style={{ width: `${barWidth}%` }}
          />
        </div>
      ) : null}
      <span className="relative min-w-0 truncate text-xs text-muted-foreground" title={row.label}>
        {row.label}
      </span>
      <span className="relative flex shrink-0 items-baseline gap-1.5 tabular-nums">
        <span className="text-[13px] font-semibold text-foreground">
          {figuresMasked ? "*******" : figure}
        </span>
        <span className="w-10 text-right text-[10px] text-muted-foreground">
          {share == null ? "" : figuresMasked ? "**%" : `${share.toFixed(1)}%`}
        </span>
      </span>
    </div>
  );
}

type DashboardCardSectionProps = {
  group: DashboardCardGroup;
  editMode?: boolean;
  sumDisplay: string;
  percentLabel: string | null;
  onRename?: (title: string) => void;
  onToggleCollapsed?: () => void;
  onUngroup?: () => void;
  onReorderMembers?: (activeKey: string, overKey: string) => void;
  onPullOutMember?: (key: string) => void;
  children: (cardKey: string) => React.ReactNode;
  memberKeys: string[];
  /** Macro groups: open onto these asset types (compact rows) instead of the nested cards. */
  assetTypes?: MacroDashboardAssetType[];
};

export function DashboardCardSection({
  group,
  editMode = false,
  sumDisplay,
  percentLabel,
  onRename,
  onToggleCollapsed,
  onUngroup,
  onReorderMembers,
  onPullOutMember,
  children,
  memberKeys,
  assetTypes,
}: DashboardCardSectionProps) {
  const typeRows = assetTypes && assetTypes.length > 0 ? assetTypes : null;
  const lineCount = typeRows ? typeRows.length : memberKeys.length;
  // A one-line group has nothing to expand (its line would only repeat the header), so it is
  // always the closed summary tile and offers no chevron.
  const expandable = lineCount >= 2;
  const collapsed = !expandable || Boolean(group.collapsed);
  // The title being typed in edit mode. It is remembered together with the title it was typed
  // over, so a title that changes underneath (renamed elsewhere, or saved) shows the new one
  // without an effect having to copy it into state.
  const [draft, setDraft] = React.useState({ source: group.title, value: group.title });
  const draftTitle = draft.source === group.title ? draft.value : group.title;
  const setDraftTitle = (value: string) => setDraft({ source: group.title, value });

  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;
    onReorderMembers?.(String(active.id), String(over.id));
  };

  const lineNoun = typeRows ? "asset type" : "card";
  const memberCountLabel =
    lineCount > 0 ? `${lineCount} ${lineCount === 1 ? lineNoun : `${lineNoun}s`}` : null;
  const typeRowsTotal = typeRows ? typeRows.reduce((sum, row) => (row.plain ? sum : sum + row.amount), 0) : 0;
  const figuresMasked = React.useContext(FiguresMaskedContext);
  const visibleSum = figuresMasked ? "*******" : sumDisplay;
  const visiblePercent = percentLabel && figuresMasked ? "**%" : percentLabel;
  const accent = assetCardVisual(memberKeys[0] ?? "").tone;
  // Share-of-total bar along the header's bottom edge, like the metric tiles.
  const shareWidth = figuresMasked ? 0 : shareFromLabel(percentLabel);

  return (
    <div
      className={cn(
        "@container/section flex h-full min-h-0 flex-col overflow-hidden rounded-[inherit]",
        collapsed
          ? "bg-muted/40"
          : "bg-muted/30",
      )}
      data-dashboard-section={group.id}
    >
      {/* Header is exactly one tile row in both states (see METRIC_TILE_PX), so an expanded
          section is a whole number of tile rows and the grid beneath it stays aligned. */}
      <div
        style={collapsed ? undefined : { height: METRIC_TILE_PX }}
        className={cn(
          "relative grid shrink-0 content-center items-center gap-x-2.5 gap-y-0.5 px-2.5 py-1.5",
          // Narrow (a closed group is one tile wide): icons + actions, then name, then total.
          "grid-cols-[minmax(0,1fr)_auto]",
          // Wide: one row — icons, name, total, actions.
          "@[360px]/section:grid-cols-[auto_minmax(0,1fr)_auto_auto] @[360px]/section:py-0",
          editMode ? "pl-8" : "pl-2.5",
          collapsed && "h-full",
          collapsed && expandable && "cursor-pointer transition-colors hover:bg-muted/60",
        )}
        onClick={
          collapsed && expandable
            ? (event) => {
                event.stopPropagation();
                onToggleCollapsed?.();
              }
            : undefined
        }
        onKeyDown={
          collapsed && expandable
            ? (event) => {
                if (event.key === "Enter" || event.key === " ") {
                  event.preventDefault();
                  event.stopPropagation();
                  onToggleCollapsed?.();
                }
              }
            : undefined
        }
        role={collapsed && expandable ? "button" : undefined}
        tabIndex={collapsed && expandable ? 0 : undefined}
        aria-expanded={expandable ? !collapsed : undefined}
      >
        {editMode ? null : (
          <MemberIconStack
            memberKeys={memberKeys}
            className={cn("col-start-1 row-start-1", "@[360px]/section:col-start-1")}
          />
        )}

        <div
          className={cn(
            "col-span-2 row-start-2 min-w-0",
            "@[360px]/section:col-span-1 @[360px]/section:col-start-2 @[360px]/section:row-start-1",
          )}
        >
          {editMode ? (
            <Input
              value={draftTitle}
              onChange={(event) => setDraftTitle(event.target.value)}
              onBlur={() => onRename?.(draftTitle)}
              onClick={(event) => event.stopPropagation()}
              onKeyDown={(event) => {
                if (event.key === "Enter") {
                  event.currentTarget.blur();
                }
              }}
              className="h-6 min-w-0 max-w-[11rem] border-border/70 bg-background px-1.5 text-[10px] font-semibold text-primary"
              aria-label="Section title"
              title="Rename section"
            />
          ) : (
            <>
              <p className="truncate text-[13px] font-semibold leading-tight tracking-tight text-foreground sm:text-sm">
                {group.title}
                {memberCountLabel ? (
                  <span
                    className={cn(
                      "ml-1.5 text-[10px] font-normal text-muted-foreground",
                      "@[360px]/section:hidden",
                    )}
                  >
                    {memberCountLabel}
                  </span>
                ) : null}
              </p>
              {memberCountLabel ? (
                <p
                  className={cn(
                    "mt-0.5 hidden truncate text-[10px] leading-tight text-muted-foreground",
                    "@[360px]/section:block",
                  )}
                >
                  {memberCountLabel}
                </p>
              ) : null}
            </>
          )}
        </div>

        <div
          className={cn(
            "col-span-2 row-start-3 flex min-w-0 items-center gap-2 leading-tight",
            "@[360px]/section:col-span-1 @[360px]/section:col-start-3 @[360px]/section:row-start-1 @[360px]/section:flex-col @[360px]/section:items-end @[360px]/section:gap-0",
          )}
        >
          <p className="truncate text-[13px] font-semibold tabular-nums tracking-tight sm:text-sm">
            {visibleSum}
          </p>
          {visiblePercent ? (
            <span
              className={cn(
                "shrink-0 rounded-full bg-background/80 px-1.5 py-0.5 text-[10px] font-medium tabular-nums text-muted-foreground",
                "@[360px]/section:mt-0.5",
              )}
            >
              {visiblePercent}
            </span>
          ) : null}
        </div>

        <div
          className={cn(
            "col-start-2 row-start-1 flex items-center justify-self-end",
            "@[360px]/section:col-start-4",
          )}
        >
          {expandable ? (
            <Button
              type="button"
              size="icon"
              variant="ghost"
              className="size-7 shrink-0 rounded-full bg-background text-muted-foreground hover:text-foreground"
              onClick={(event) => {
                event.stopPropagation();
                onToggleCollapsed?.();
              }}
              title={collapsed ? "Expand section" : "Collapse section"}
              aria-label={collapsed ? "Expand section" : "Collapse section"}
            >
              {collapsed ? (
                <ChevronRight className="size-3.5" />
              ) : (
                <ChevronDown className="size-3.5" />
              )}
            </Button>
          ) : null}
          {editMode ? (
            <Button
              type="button"
              size="icon"
              variant="ghost"
              className="ml-1.5 size-7 shrink-0 rounded-full bg-background text-muted-foreground hover:text-foreground"
              onClick={(event) => {
                event.stopPropagation();
                onUngroup?.();
              }}
              title="Ungroup cards"
              aria-label="Ungroup cards"
            >
              <Ungroup className="size-3" />
            </Button>
          ) : null}
        </div>

        {shareWidth > 0 ? (
          <div
            className="pointer-events-none absolute inset-x-0 bottom-0 h-[3px] bg-muted/60"
            aria-hidden
          >
            <div
              className={cn("h-full rounded-r-full opacity-80", accent.bar)}
              style={{ width: `${shareWidth}%` }}
            />
          </div>
        ) : null}
      </div>

      {!collapsed && typeRows ? (
        <div
          style={{
            gap: TYPE_ROW_GAP_PX,
            paddingTop: TYPE_ROWS_TOP_PX,
            // The scrollbar is hidden, so a list that continues fades out at the bottom edge.
            ...(typeRows.length > TYPE_ROWS_MAX_VISIBLE
              ? {
                  maskImage: "linear-gradient(to bottom, #000 calc(100% - 28px), transparent)",
                  WebkitMaskImage: "linear-gradient(to bottom, #000 calc(100% - 28px), transparent)",
                }
              : null),
          }}
          className="flex min-h-0 flex-1 flex-col overflow-y-auto px-1.5 pb-2.5 [scrollbar-width:none] [&::-webkit-scrollbar]:hidden"
        >
          {typeRows.map((row) => (
            <AssetTypeRow
              key={row.label}
              row={row}
              groupTotal={typeRowsTotal}
              accentBar={accent.bar}
            />
          ))}
        </div>
      ) : null}

      {!collapsed && !typeRows ? (
        <DndContext
          sensors={sensors}
          collisionDetection={closestCenter}
          onDragEnd={editMode && onReorderMembers ? handleDragEnd : () => undefined}
        >
          <SortableContext items={memberKeys} strategy={rectSortingStrategy}>
            {/* Two columns from 360px — keep in step with sectionContentColumns() in
                dashboard-layout.ts, which sizes the section's grid height from the same rule. */}
            <div
              // Fixed tile-height rows: a section the row-equaliser made taller than its content
              // keeps normal tiles and shows spare space, instead of stretching them.
              style={{ gridAutoRows: METRIC_TILE_PX }}
              className={cn(
                "grid min-h-0 flex-1 content-start gap-2 overflow-y-auto px-1.5 pb-1.5",
                memberKeys.length < 2
                  ? "grid-cols-1"
                  : "grid-cols-1 @[360px]/section:grid-cols-2",
              )}
            >
              {memberKeys.map((key) => (
                <NestedMetricShell
                  key={key}
                  cardKey={key}
                  editMode={editMode}
                  sortable={Boolean(editMode && onReorderMembers)}
                  onPullOut={onPullOutMember}
                >
                  {children(key)}
                </NestedMetricShell>
              ))}
            </div>
          </SortableContext>
        </DndContext>
      ) : null}
    </div>
  );
}

type NestedMetricShellProps = {
  cardKey: string;
  editMode?: boolean;
  sortable?: boolean;
  onPullOut?: (key: string) => void;
  children: React.ReactNode;
};

/** Nested statistic card inside a clubbed section (not an RGL child). */
export function NestedMetricShell({
  cardKey,
  editMode = false,
  sortable = false,
  onPullOut,
  children,
}: NestedMetricShellProps) {
  const { attributes, listeners, setNodeRef, transform, transition, isDragging } = useSortable({
    id: cardKey,
    disabled: !sortable,
  });

  return (
    <div
      ref={sortable ? setNodeRef : undefined}
      style={
        sortable
          ? {
              transform: CSS.Transform.toString(transform),
              transition,
            }
          : undefined
      }
      className={cn(
        "relative min-h-0 overflow-hidden rounded-lg bg-card",
        isDragging && "z-10 opacity-90",
      )}
      data-dashboard-card={cardKey}
      data-nested-metric="true"
    >
      {editMode && sortable ? (
        <button
          type="button"
          className="absolute top-0.5 left-0.5 z-10 inline-flex size-5 cursor-grab touch-none items-center justify-center rounded border bg-background/95 text-muted-foreground active:cursor-grabbing"
          aria-label="Drag to reorder inside section"
          title="Drag to reorder"
          {...attributes}
          {...listeners}
        >
          <GripVertical className="size-3" />
        </button>
      ) : null}
      {editMode && onPullOut ? (
        <button
          type="button"
          className="absolute top-0.5 right-0.5 z-10 rounded border bg-background/95 px-1 py-0.5 text-[9px] font-medium text-muted-foreground hover:text-foreground"
          onClick={() => onPullOut(cardKey)}
          title="Remove from section"
        >
          Remove
        </button>
      ) : null}
      <div className="h-full [&>*]:h-full [&>*]:border-0 [&>*]:shadow-none">{children}</div>
    </div>
  );
}
