"use client";

/**
 * One panel for every part-to-whole breakdown on the dashboard.
 *
 * Replaces five separate donut-plus-legend blocks that were stacked vertically,
 * each the same shape — repetitive, and it made the analytics card very tall.
 * A selector picks which breakdown to show, so there is one chart in one place.
 */

import { Group, ScrollArea, Skeleton, Stack, Text } from "@mantine/core";
import { useMemo, useState } from "react";
import { AnimatedDonut, type DonutDatum } from "./charts/AnimatedDonut";
import { DrillChevron, drillProps } from "./drillable";
import { motion, useReducedMotion } from "./motion";
import styles from "./promo.module.css";

export interface Breakdown {
  key: string;
  label: string;
  /** Noun for tooltips — "member", "point", "booking". */
  unit: string;
  centerLabel: string;
  slices: DonutDatum[];
  /**
   * Headline figure, when it isn't the sum of the slices.
   *
   * Most breakdowns split a whole, so the total is the sum and the chip is a
   * summary of it. A few instead chart "X against not-X" — their label names only
   * X, and summing the ring there reports the population, not the thing the label
   * promises. Percentages are unaffected: they stay measured against the real sum
   * so they still add to 100%.
   */
  total?: number;
  /** Optional note shown under the legend, e.g. how the figure was derived. */
  note?: string;
  /**
   * Makes each legend row a button that drills into that slice.
   *
   * Per-breakdown rather than a panel-level prop: only some breakdowns have
   * somewhere to drill to (bookings does; contact completeness does not), and a
   * row that looks clickable but isn't is worse than a plain one.
   */
  onSliceClick?: (slice: DonutDatum) => void;
}

export function BreakdownPanel({
  breakdowns,
  loading = false,
  height = 220,
}: {
  breakdowns: Breakdown[];
  loading?: boolean;
  height?: number;
}) {
  const reduce = useReducedMotion();
  const [selected, setSelected] = useState<string | null>(null);

  const available = useMemo(
    () => breakdowns.filter((b) => b.slices.some((s) => s.value > 0)),
    [breakdowns],
  );

  // Falls back to the first available breakdown when the selected one empties
  // out — otherwise changing a filter can leave the panel blank.
  const active =
    available.find((b) => b.key === selected) ?? available[0] ?? null;

  if (loading) return <Skeleton height={height + 60} radius="md" />;

  if (!active) {
    return (
      <Stack align="center" justify="center" mih={height} gap={6}>
        <Text c="dimmed" size="sm">
          Nothing to break down in this range.
        </Text>
      </Stack>
    );
  }

  /*
   * Two different numbers, deliberately.
   *
   * `total` is the real sum of the ring and is what the legend's percentages divide
   * by — override it and they stop adding to 100%. `headline` is what the chip and
   * the donut's centre display, which for an "X vs not-X" breakdown is X alone.
   */
  const total = active.slices.reduce((sum, s) => sum + s.value, 0);
  const headline = active.total ?? total;

  return (
    <Stack gap="sm">
      {/* Chips rather than a segmented control: each carries its own total, so
          the selector is also the summary and several long labels are not
          fighting for room in one strip.

          Hidden when there is only one breakdown: a lone chip is a control that
          cannot do anything, and its total is already the donut's centre figure.
          This is what lets the panel be reused for a single fixed view. */}
      {available.length > 1 && (
        <div className={styles.selectorRow}>
          {available.map((b) => {
            const isActive = b.key === active.key;
            const sum =
              b.total ?? b.slices.reduce((acc, x) => acc + x.value, 0);
            return (
              <motion.button
                key={b.key}
                type="button"
                aria-pressed={isActive}
                onClick={() => setSelected(b.key)}
                whileTap={reduce ? undefined : { scale: 0.97 }}
                className={`${styles.selectorChip} ${
                  isActive ? styles.selectorChipActive : ""
                }`}
              >
                <span className={styles.selectorChipLabel}>{b.label}</span>
                <span className={styles.selectorChipValue}>
                  {sum.toLocaleString()}
                </span>
              </motion.button>
            );
          })}
        </div>
      )}

      <Group gap="xl" align="center" wrap="wrap" grow>
        {/* Zero-value slices are dropped from the donut but kept in the legend
            below: a zero-width wedge is noise, a "0" in a list is information. */}
        <AnimatedDonut
          data={active.slices.filter((s) => s.value > 0)}
          height={height}
          centerLabel={active.centerLabel}
          centerValue={headline}
          unit={active.unit}
          /* Same destination as the legend row for that slice — a chart and its
             legend disagreeing about what a click does would be worse than
             neither being clickable. */
          onSliceClick={active.onSliceClick}
        />
        <Stack gap="xs" justify="center" miw={220} style={{ flex: 1 }}>
          {/*
           * The value list scrolls instead of stretching the panel.
           *
           * Some breakdowns have a handful of rows and others have dozens, so an
           * unbounded list made the card's height jump every time you switched
           * chip — and with many rows it grew far past the donut, leaving the
           * chart floating in whitespace. Capped at the donut's own height via
           * Autosize, so short lists still size to their content and only long
           * ones scroll.
           */}
          <ScrollArea.Autosize
            mah={height}
            type="auto"
            /* Reserves the scrollbar's width so it never sits on top of the
               right-aligned counts and percentages. */
            offsetScrollbars
          >
            <Stack gap="xs">
              {active.slices.map((slice) => (
                <Group
                  key={slice.name}
                  className={styles.legendRow}
                  justify="space-between"
                  gap="xs"
                  wrap="nowrap"
                  {...drillProps(
                    active.onSliceClick
                      ? () => active.onSliceClick!(slice)
                      : undefined,
                    slice.name,
                  )}
                >
                  <Group gap="xs" wrap="nowrap" style={{ minWidth: 0 }}>
                    <span
                      className={styles.legendSwatch}
                      style={{ background: slice.color }}
                    />
                    <Text
                      size="sm"
                      lineClamp={1}
                      c={slice.value === 0 ? "dimmed" : undefined}
                    >
                      {slice.name}
                    </Text>
                  </Group>
                  <Group gap={8} wrap="nowrap">
                    <Text size="sm" fw={600} className={styles.num}>
                      {slice.value.toLocaleString()}
                    </Text>
                    <Text
                      size="xs"
                      c="dimmed"
                      w={44}
                      ta="right"
                      className={styles.num}
                    >
                      {total > 0
                        ? ((slice.value / total) * 100).toFixed(1)
                        : "0.0"}
                      %
                    </Text>
                    {active.onSliceClick && <DrillChevron />}
                  </Group>
                </Group>
              ))}
            </Stack>
          </ScrollArea.Autosize>

          {/* Outside the scroll area on purpose: the total is the figure people
              reconcile against, so it must never be scrolled out of view. */}
          <Group
            justify="space-between"
            mt={4}
            pt={6}
            className={styles.legendTotal}
          >
            <Text size="sm" fw={700}>
              Total
            </Text>
            <Text size="sm" fw={700} className={styles.num}>
              {total.toLocaleString()}
            </Text>
          </Group>

          {active.note && (
            <Text size="xs" c="dimmed">
              {active.note}
            </Text>
          )}
        </Stack>
      </Group>
    </Stack>
  );
}
