import { useId, type CSSProperties, type ReactNode } from 'react';

export interface DonutSlice {
  /** Stable id — React key + accessible label. */
  id: string;
  /** Slice value — must be ≥ 0. */
  value: number;
  /** Optional override colour (defaults walk the token palette). */
  color?: string;
  /** Optional display label (falls back to `id`). */
  label?: string;
}

export interface DonutProps {
  slices: DonutSlice[];
  /** Total diameter in px. Default 200. */
  size?: number;
  /** Donut hole radius as a fraction of the outer radius. Default 0.6. */
  innerRatio?: number;
  /** Slot rendered in the centre — typically a total / percent. */
  centerContent?: ReactNode;
  /** Accessible label. */
  ariaLabel?: string;
  className?: string;
  style?: CSSProperties;
}

const TOKEN_PALETTE = [
  'var(--bl-accent, #6366f1)',
  'var(--bl-success, #10b981)',
  'var(--bl-warning, #f59e0b)',
  'var(--bl-info, #0ea5e9)',
  'var(--bl-danger, #ef4444)',
  'color-mix(in srgb, var(--bl-accent, #6366f1) 50%, transparent)',
];

/**
 * `<Donut>` — categorical share-of-total chart. Pure SVG.
 *
 * Wave 9.A.4. Empty / all-zero data renders an unsegmented muted ring
 * (no NaN slices, no console warnings).
 */
export function Donut({
  slices,
  size = 200,
  innerRatio = 0.6,
  centerContent,
  ariaLabel,
  className,
  style,
}: DonutProps) {
  const titleId = useId();
  const cx = size / 2;
  const cy = size / 2;
  const rOuter = (size / 2) * 0.96;
  const rInner = rOuter * Math.max(0, Math.min(0.95, innerRatio));

  const total = slices.reduce((s, x) => s + Math.max(0, x.value), 0);

  return (
    <svg
      role="img"
      aria-labelledby={titleId}
      viewBox={`0 0 ${size} ${size}`}
      width={size}
      height={size}
      data-testid="bl-donut"
      className={className}
      style={style}
    >
      <title id={titleId}>{ariaLabel ?? 'Donut chart'}</title>
      {total === 0 ? (
        <RingShape
          cx={cx}
          cy={cy}
          rOuter={rOuter}
          rInner={rInner}
          fill="var(--bl-surface-muted, rgba(0,0,0,0.06))"
          dataKind="empty"
        />
      ) : (
        slices.map((slice, i) => {
          const colour =
            slice.color ?? TOKEN_PALETTE[i % TOKEN_PALETTE.length]!;
          const start = priorSum(slices, i) / total;
          const end = priorSum(slices, i + 1) / total;
          return (
            <ArcSlice
              key={slice.id}
              cx={cx}
              cy={cy}
              rOuter={rOuter}
              rInner={rInner}
              startFraction={start}
              endFraction={end}
              fill={colour}
              testId="bl-donut-slice"
              data-slice-id={slice.id}
            />
          );
        })
      )}
      {centerContent !== undefined && (
        <foreignObject
          x={cx - rInner}
          y={cy - rInner}
          width={rInner * 2}
          height={rInner * 2}
        >
          <div
            style={{
              width: '100%',
              height: '100%',
              display: 'grid',
              placeItems: 'center',
              textAlign: 'center',
              fontFamily: 'ui-sans-serif, system-ui, sans-serif',
            }}
          >
            {centerContent}
          </div>
        </foreignObject>
      )}
    </svg>
  );
}

function priorSum(slices: DonutSlice[], n: number): number {
  let s = 0;
  for (let i = 0; i < n && i < slices.length; i++) {
    s += Math.max(0, slices[i]!.value);
  }
  return s;
}

function RingShape({
  cx,
  cy,
  rOuter,
  rInner,
  fill,
  dataKind,
}: {
  cx: number;
  cy: number;
  rOuter: number;
  rInner: number;
  fill: string;
  dataKind: string;
}) {
  // Outer circle minus inner circle via even-odd path.
  const d = [
    `M ${cx - rOuter} ${cy}`,
    `a ${rOuter} ${rOuter} 0 1 0 ${rOuter * 2} 0`,
    `a ${rOuter} ${rOuter} 0 1 0 ${-rOuter * 2} 0`,
    `Z`,
    `M ${cx - rInner} ${cy}`,
    `a ${rInner} ${rInner} 0 1 0 ${rInner * 2} 0`,
    `a ${rInner} ${rInner} 0 1 0 ${-rInner * 2} 0`,
    `Z`,
  ].join(' ');
  return <path d={d} fill={fill} fillRule="evenodd" data-kind={dataKind} />;
}

function ArcSlice({
  cx,
  cy,
  rOuter,
  rInner,
  startFraction,
  endFraction,
  fill,
  testId,
  ...rest
}: {
  cx: number;
  cy: number;
  rOuter: number;
  rInner: number;
  startFraction: number;
  endFraction: number;
  fill: string;
  testId?: string;
  [k: string]: unknown;
}) {
  // Treat near-full slice as a closed ring (single-slice donut).
  if (endFraction - startFraction >= 0.999) {
    return <RingShape cx={cx} cy={cy} rOuter={rOuter} rInner={rInner} fill={fill} dataKind="full" />;
  }
  const a0 = startFraction * 2 * Math.PI - Math.PI / 2;
  const a1 = endFraction * 2 * Math.PI - Math.PI / 2;
  const x0o = cx + rOuter * Math.cos(a0);
  const y0o = cy + rOuter * Math.sin(a0);
  const x1o = cx + rOuter * Math.cos(a1);
  const y1o = cy + rOuter * Math.sin(a1);
  const x0i = cx + rInner * Math.cos(a0);
  const y0i = cy + rInner * Math.sin(a0);
  const x1i = cx + rInner * Math.cos(a1);
  const y1i = cy + rInner * Math.sin(a1);
  const largeArc = endFraction - startFraction > 0.5 ? 1 : 0;
  const d = [
    `M ${x0o} ${y0o}`,
    `A ${rOuter} ${rOuter} 0 ${largeArc} 1 ${x1o} ${y1o}`,
    `L ${x1i} ${y1i}`,
    `A ${rInner} ${rInner} 0 ${largeArc} 0 ${x0i} ${y0i}`,
    'Z',
  ].join(' ');
  return <path d={d} fill={fill} data-testid={testId} {...(rest as object)} />;
}
