import { Suspense, lazy, useMemo } from 'react';
import { cn } from '@/lib/utils';
import { EditedChip, Label } from './chrome';
import { formatNumber, formatSigned, useMediaQuery, usePrefersReducedMotion } from './format';
import type { MetricPoint } from './MetricChart';

export type { MetricPoint } from './MetricChart';

// Lazy, and deliberately not a static import: recharts is ~100 kB gzipped and
// exactly one surface on the site uses it. `vite.config.ts` also names it as
// its own manual chunk, so this stays out of the entry bundle in both dev and
// production builds.
const MetricChart = lazy(() => import('./MetricChart'));

const CHART_HEIGHT = 168;

export interface MetricMoverProps {
  /** The one number a reader would repeat in a meeting. */
  label: string;
  value: number;
  digits?: number;
  unit?: string;
  /** The dashed rule: what the number was before, or what counts as par. */
  baseline?: { value: number; label: string };
  series?: MetricPoint[];
  /** One sentence on what moved it. Not a caveat — the mechanism. */
  caption?: string;
  edited?: boolean;
  className?: string;
}

/**
 * The headline number, with the line that shows it moving.
 *
 * The reference rule is not decoration: a number on its own is a claim, and a
 * number against the line it used to sit on is evidence. If a demo has no
 * baseline to draw, it should not be using this component.
 *
 * Under `sm` the table IS the view and the chart is not fetched: a 168px line
 * with four categorical ticks in a 326px column is a picture of a table that
 * would fit better than the picture.
 */
export function MetricMover({
  label,
  value,
  digits = 3,
  unit,
  baseline,
  series,
  caption,
  edited = false,
  className,
}: MetricMoverProps) {
  const reducedMotion = usePrefersReducedMotion();
  const wide = useMediaQuery('(min-width: 640px)', true);
  // The rule is still drawn when the headline IS the baseline — it is the line
  // the other arms are read against. The delta text is not: "+0.000 vs itself"
  // is noise dressed up as a measurement.
  const rawDelta = baseline ? value - baseline.value : null;
  const delta = rawDelta !== null && Math.abs(rawDelta) > 1e-9 ? rawDelta : null;
  const points = useMemo(() => series ?? [], [series]);

  const table = (
    <table className="w-full border-collapse font-mono text-caption">
      <caption className="sr-only">{label}, one row per recorded arm</caption>
      <thead>
        <tr className="bg-surface-3 text-left text-label uppercase tracking-label text-muted">
          <th scope="col" className="px-2 py-1.5 font-medium">
            Arm
          </th>
          <th scope="col" className="px-2 py-1.5 text-right font-medium">
            Total
          </th>
        </tr>
      </thead>
      <tbody className="divide-y divide-border">
        {points.map((point) => (
          <tr key={String(point.x)}>
            <th scope="row" className="px-2 py-1.5 text-left font-normal text-fg-2">
              {String(point.x)}
            </th>
            <td className="px-2 py-1.5 text-right text-fg">{formatNumber(point.y, digits)}</td>
          </tr>
        ))}
      </tbody>
    </table>
  );

  return (
    <section
      aria-label={label}
      className={cn('rounded-xl border border-border bg-surface p-4 sm:p-5', className)}
    >
      <div className="flex flex-wrap items-baseline gap-x-3 gap-y-1">
        <Label as="h3">{label}</Label>
        {edited ? <EditedChip /> : null}
      </div>
      <p className="mt-1.5 flex flex-wrap items-baseline gap-x-2 gap-y-1">
        <span className="font-mono text-metric text-fg">{formatNumber(value, digits)}</span>
        {unit ? <span className="text-ui text-muted">{unit}</span> : null}
        {delta !== null ? (
          <span
            className={cn(
              'font-mono text-mono-data font-medium',
              delta > 0 ? 'text-positive' : delta < 0 ? 'text-danger' : 'text-muted',
            )}
          >
            {formatSigned(delta, digits)} <span className="text-muted">vs {baseline?.label}</span>
          </span>
        ) : null}
      </p>

      {points.length > 1 ? (
        <div className="mt-3">
          {wide ? (
            <>
              <Suspense
                fallback={
                  // Reserve the exact chart height. A chart that pops in and
                  // pushes the caption down is the layout shift this whole
                  // page is trying not to have.
                  <div
                    style={{ height: CHART_HEIGHT }}
                    className="w-full rounded-md bg-surface-2"
                    aria-hidden="true"
                  />
                }
              >
                <MetricChart
                  data={points}
                  {...(baseline ? { baseline } : {})}
                  height={CHART_HEIGHT}
                  animate={!reducedMotion}
                  digits={digits}
                />
              </Suspense>
              {/* The chart is a picture of the table; the table is the
                  accessible version of the picture. Both are the same numbers. */}
              <details className="mt-1">
                <summary className="tap inline-flex cursor-pointer items-center text-small text-muted hover:text-fg">
                  Show these points as a table
                </summary>
                <div className="mt-1.5 overflow-hidden rounded-md border border-border">{table}</div>
              </details>
            </>
          ) : (
            <div className="overflow-hidden rounded-md border border-border">{table}</div>
          )}
        </div>
      ) : null}

      {caption ? <p className="mt-3 max-w-measure text-small text-muted">{caption}</p> : null}
    </section>
  );
}
