import * as React from 'react';
import { clamp, linearTaper } from '../core/math';
import type { Taper } from '../core/math';
import { useKnobTheme } from '../core/theme';
import type { LEDFaderZone } from './LEDFader';
import { SegmentDisplay } from '../digital/SegmentDisplay';

export interface MeterProps {
  /** Level to display. The meter is read-only — always controlled. */
  value: number;
  min?: number;
  max?: number;
  taper?: Taper;
  orientation?: 'vertical' | 'horizontal';
  /** Travel length in px. Default: 160. */
  length?: number;
  /** Cross-axis size in px. Default: 16. */
  breadth?: number;
  /** Number of LED segments. Default: 28. */
  segments?: number;
  /** LED color when no zones are given. */
  color?: string;
  /** Meter zones, e.g. green / yellow / red by normalized position. */
  zones?: readonly LEDFaderZone[];
  /** Opacity of unlit segments. Default: 0.1. */
  offOpacity?: number;
  /**
   * Peak hold time in ms — the highest recent segment stays lit and falls
   * back after this long. `false` disables. Default: 1200.
   */
  peakHold?: number | false;
  /** Peak indicator color. Defaults to the peak's zone color. */
  peakColor?: string;
  /** Dedicated clip LED at the hot end of the meter. Default: true. */
  showClip?: boolean;
  /** Value at/above which the clip LED lights. Default: `max`. */
  clipThreshold?: number;
  /**
   * Signal used for clip detection, when it differs from the displayed
   * level — e.g. show RMS on the bar but clip on sample peaks. Defaults
   * to `value`.
   */
  clipValue?: number;
  /**
   * How long the clip LED stays lit in ms, or 'latch' to stay lit until
   * clicked. Default: 1500.
   */
  clipHold?: number | 'latch';
  clipColor?: string;
  /** Fired when the signal first crosses the clip threshold. */
  onClip?: (value: number) => void;
  /** Panel color behind the LEDs. */
  faceColor?: string;
  label?: string;
  /** Show a seven-segment readout. Default: false. */
  showValue?: boolean;
  digits?: number;
  displayDecimals?: number;
  labelColor?: string;
  className?: string;
  style?: React.CSSProperties;
  'aria-label'?: string;
}

/**
 * Read-only LED level meter with peak hold — the display-side sibling of
 * LEDFader for VU/output metering.
 */
export const Meter: React.FC<MeterProps> = ({
  value,
  min = 0,
  max = 100,
  taper = linearTaper,
  orientation = 'vertical',
  length = 160,
  breadth = 16,
  segments = 28,
  color = '#3df2ad',
  zones,
  offOpacity = 0.1,
  peakHold = 1200,
  peakColor,
  showClip = true,
  clipThreshold,
  clipValue,
  clipHold = 1500,
  clipColor = '#ff2b39',
  onClip,
  faceColor = '#0b0d0e',
  label,
  showValue = false,
  digits = 4,
  displayDecimals = 1,
  labelColor,
  className,
  style,
  ...aria
}) => {
  const theme = useKnobTheme();
  const vertical = orientation === 'vertical';
  const n = clamp(taper.toNormalized(clamp(value, min, max), min, max), 0, 1);

  // Peak: track the highest recent level, fall back after `peakHold` ms.
  const [peak, setPeak] = React.useState(n);
  React.useEffect(() => {
    if (peakHold === false) return;
    if (n >= peak) {
      setPeak(n);
      return;
    }
    const t = setTimeout(() => setPeak(n), peakHold);
    return () => clearTimeout(t);
  }, [n, peak, peakHold]);

  // Clip: latch a dedicated LED when the raw (unclamped) signal reaches the
  // threshold. Hold for `clipHold` ms, or until clicked in 'latch' mode.
  const [clipped, setClipped] = React.useState(false);
  const clipTimer = React.useRef<ReturnType<typeof setTimeout>>();
  const wasOver = React.useRef(false);
  const onClipRef = React.useRef(onClip);
  onClipRef.current = onClip;
  React.useEffect(() => {
    if (!showClip) return;
    const signal = clipValue ?? value;
    const over = signal >= (clipThreshold ?? max) - 1e-9;
    if (over) {
      if (!wasOver.current) onClipRef.current?.(signal);
      setClipped(true);
      if (clipHold !== 'latch') {
        clearTimeout(clipTimer.current);
        clipTimer.current = setTimeout(() => setClipped(false), clipHold);
      }
    }
    wasOver.current = over;
  }, [value, clipValue, showClip, clipThreshold, max, clipHold]);
  React.useEffect(() => () => clearTimeout(clipTimer.current), []);

  const pad = 4;
  const w = vertical ? breadth : length;
  const h = vertical ? length : breadth;
  const zoneList: readonly LEDFaderZone[] = React.useMemo(
    () => (zones ? [...zones].sort((a, b) => a.upTo - b.upTo) : [{ upTo: 1, color }]),
    [zones, color],
  );
  const zoneFor = (t: number): string =>
    (zoneList.find(z => t <= z.upTo + 1e-9) ?? zoneList[zoneList.length - 1]).color;

  const lit = Math.round(n * segments);
  const peakIdx =
    peakHold !== false && peak > 0 ? Math.min(segments - 1, Math.ceil(peak * segments) - 1) : -1;
  // Reserve room at the hot end for the clip LED (7px LED + 3px gap).
  const clipLed = 7;
  const clipSpan = showClip ? clipLed + 3 : 0;
  const slot = (length - pad * 2 - clipSpan) / segments;
  const gap = Math.min(2.5, slot * 0.35);
  const cross = breadth - pad * 2;

  const leds: React.ReactNode[] = [];
  for (let i = 0; i < segments; i++) {
    const zone = zoneFor((i + 1) / segments);
    const isPeak = i === peakIdx && i >= lit;
    const on = i < lit || isPeak;
    const fill = isPeak ? (peakColor ?? zone) : zone;
    const common = { rx: 1.2, fill, opacity: on ? 1 : offOpacity };
    leds.push(
      vertical ? (
        <rect
          key={i}
          x={pad}
          y={length - pad - (i + 1) * slot + gap / 2}
          width={cross}
          height={slot - gap}
          {...common}
        />
      ) : (
        <rect key={i} x={pad + i * slot + gap / 2} y={pad} width={slot - gap} height={cross} {...common} />
      ),
    );
  }

  return (
    <div
      className={className}
      role="meter"
      aria-valuemin={min}
      aria-valuemax={max}
      aria-valuenow={clamp(value, min, max)}
      aria-label={aria['aria-label']}
      data-clipped={clipped ? '' : undefined}
      style={{
        display: 'inline-flex',
        flexDirection: 'column',
        alignItems: 'center',
        gap: 5,
        width: w,
        ...style,
      }}
    >
      {showValue && (
        <div style={{ width: '100%', display: 'flex', justifyContent: 'center' }}>
          <SegmentDisplay
            value={clamp(value, min, max)}
            digits={digits}
            decimals={displayDecimals}
            height={11}
            color={zoneFor(n)}
            background="none"
            ghostOpacity={0.06}
          />
        </div>
      )}
      <svg width={w} height={h} viewBox={`0 0 ${w} ${h}`} style={{ display: 'block' }} aria-hidden="true">
        <rect x={0.5} y={0.5} width={w - 1} height={h - 1} rx={4} fill={faceColor} stroke="rgba(255,255,255,0.09)" />
        {leds}
        {showClip && (
          <rect
            data-part="clip"
            x={vertical ? pad : length - pad - clipLed}
            y={vertical ? pad : pad}
            width={vertical ? cross : clipLed}
            height={vertical ? clipLed : cross}
            rx={1.5}
            fill={clipColor}
            opacity={clipped ? 1 : 0.14}
            onClick={() => setClipped(false)}
            style={{ pointerEvents: 'auto', cursor: 'pointer' }}
          >
            <title>{clipped ? 'Clip! Click to clear' : 'Clip indicator'}</title>
          </rect>
        )}
      </svg>
      {label && (
        <span
          style={{
            fontFamily: theme.fontUI,
            fontSize: 11,
            letterSpacing: '0.08em',
            textTransform: 'uppercase',
            color: labelColor ?? theme.label,
            whiteSpace: 'nowrap',
          }}
        >
          {label}
        </span>
      )}
    </div>
  );
};
