import { useEffect, useMemo, useRef, useState } from 'react';

export type NumberLineKind = 'area' | 'national' | 'outcode' | 'sector';

export interface NumberLinePoint {
  kind: NumberLineKind;
  /** Short label shown above the tick (e.g. "This area", "National", "SE3", "SE3 9"). */
  label: string;
  value: number;
}

interface NumberLineProps {
  points: NumberLinePoint[];
  format: (value: number) => string;
}

const HEIGHT = 54;
const PAD_X = 4;
const NAME_Y = 9;
const VALUE_Y = 19;
const LEADER_TOP = 23;
const BASE_Y = 40;
const GAP = 4;

// Per-kind tick (stroke) and label (fill) colours. `area` is the subject and
// is emphasised; the rest are reference points.
const KIND_STYLE: Record<NumberLineKind, { tick: string; text: string }> = {
  area: { tick: 'stroke-teal-600 dark:stroke-teal-400', text: 'fill-teal-700 dark:fill-teal-300' },
  national: {
    tick: 'stroke-warm-400 dark:stroke-warm-500',
    text: 'fill-warm-500 dark:fill-warm-300',
  },
  outcode: {
    tick: 'stroke-amber-500 dark:stroke-amber-400',
    text: 'fill-amber-700 dark:fill-amber-400',
  },
  sector: {
    tick: 'stroke-indigo-500 dark:stroke-indigo-400',
    text: 'fill-indigo-600 dark:fill-indigo-300',
  },
};

export interface NumberLineLayoutItem extends NumberLinePoint {
  valueText: string;
  /** x of the tick (the true value position on the scale). */
  tickX: number;
  /** x of the (de-collided) label centre. */
  labelX: number;
  /** half the estimated label-box width, used for collision spacing. */
  halfWidth: number;
}

export interface NumberLineLayout {
  items: NumberLineLayoutItem[];
  plotLeft: number;
  plotRight: number;
}

/**
 * Pure layout: place each value's tick on a scale spanning the lowest→highest
 * value (not anchored at 0, to maximise separation) and spread the labels so
 * their boxes never overlap (nested area/sector/outcode values tend to cluster),
 * clamped within the plot bounds while preserving order. Returns null when there
 * is nothing to draw. Exported for testing.
 */
export function computeNumberLineLayout(
  points: NumberLinePoint[],
  width: number,
  format: (value: number) => string
): NumberLineLayout | null {
  if (width <= 0 || points.length === 0) return null;

  const values = points.map((p) => p.value);
  const lo = Math.min(...values);
  const hi = Math.max(...values);
  const span = hi - lo;
  const plotLeft = PAD_X;
  const plotRight = width - PAD_X;
  const plotW = Math.max(1, plotRight - plotLeft);
  // Scale spans the lowest→highest value (not anchored at 0) to maximise the
  // separation between the clustered ticks. When all values are equal, centre them.
  const scaleX = (value: number) =>
    span > 0 ? plotLeft + ((value - lo) / span) * plotW : plotLeft + plotW / 2;

  const items: NumberLineLayoutItem[] = points.map((point) => {
    const valueText = format(point.value);
    // Rough text-width estimate at the 9px font (≈5px/char), padded a little.
    const chars = Math.max(point.label.length, valueText.length);
    const halfWidth = Math.max(9, (chars * 5) / 2 + 2);
    const tickX = scaleX(point.value);
    return { ...point, valueText, tickX, halfWidth, labelX: tickX };
  });

  // Spread labels left-to-right so adjacent boxes never overlap, then clamp to
  // the plot bounds (right pass, then left pass) preserving order.
  items.sort((a, b) => a.tickX - b.tickX);
  for (let i = 1; i < items.length; i++) {
    const minX = items[i - 1].labelX + items[i - 1].halfWidth + GAP + items[i].halfWidth;
    if (items[i].labelX < minX) items[i].labelX = minX;
  }
  const last = items.length - 1;
  if (last >= 0) {
    items[last].labelX = Math.min(items[last].labelX, plotRight - items[last].halfWidth);
    for (let i = last - 1; i >= 0; i--) {
      const maxX = items[i + 1].labelX - items[i + 1].halfWidth - GAP - items[i].halfWidth;
      if (items[i].labelX > maxX) items[i].labelX = maxX;
    }
    items[0].labelX = Math.max(items[0].labelX, plotLeft + items[0].halfWidth);
    for (let i = 1; i < items.length; i++) {
      const minX = items[i - 1].labelX + items[i - 1].halfWidth + GAP + items[i].halfWidth;
      if (items[i].labelX < minX) items[i].labelX = minX;
    }
  }

  return { items, plotLeft, plotRight };
}

/**
 * A compact horizontal number line that places one tick per value on a scale
 * spanning the lowest→highest value, so the selection can be read against its
 * reference points (national / outcode / sector) at a glance. Labels are spread
 * to avoid overlap
 * (common since the nested area/sector/outcode values cluster) and connected
 * back to their tick with a thin leader line.
 */
export default function NumberLine({ points, format }: NumberLineProps) {
  const containerRef = useRef<HTMLDivElement>(null);
  const [width, setWidth] = useState(0);

  useEffect(() => {
    const el = containerRef.current;
    if (!el) return;
    const observer = new ResizeObserver((entries) => {
      const w = entries[0].contentRect.width;
      if (w > 0) setWidth(w);
    });
    observer.observe(el);
    return () => observer.disconnect();
  }, []);

  const laidOut = useMemo(
    () => computeNumberLineLayout(points, width, format),
    [points, width, format]
  );

  return (
    <div ref={containerRef} className="w-full">
      {laidOut && (
        <svg width={width} height={HEIGHT} className="block">
          <line
            x1={laidOut.plotLeft}
            y1={BASE_Y}
            x2={laidOut.plotRight}
            y2={BASE_Y}
            strokeWidth={1}
            className="stroke-warm-300 dark:stroke-warm-600"
          />
          {laidOut.items.map((item) => {
            const style = KIND_STYLE[item.kind];
            const emphasized = item.kind === 'area';
            const halfTick = emphasized ? 7 : 5;
            return (
              <g key={item.kind}>
                <line
                  x1={item.labelX}
                  y1={LEADER_TOP}
                  x2={item.tickX}
                  y2={BASE_Y - halfTick}
                  strokeWidth={0.75}
                  className="stroke-warm-300 dark:stroke-warm-600"
                />
                <line
                  x1={item.tickX}
                  y1={BASE_Y - halfTick}
                  x2={item.tickX}
                  y2={BASE_Y + halfTick}
                  strokeWidth={emphasized ? 2.5 : 1.5}
                  strokeLinecap="round"
                  className={style.tick}
                />
                <text
                  x={item.labelX}
                  y={NAME_Y}
                  textAnchor="middle"
                  fontSize={9}
                  fontWeight={emphasized ? 700 : 500}
                  className={style.text}
                >
                  {item.label}
                </text>
                <text
                  x={item.labelX}
                  y={VALUE_Y}
                  textAnchor="middle"
                  fontSize={9}
                  className={style.text}
                >
                  {item.valueText}
                </text>
                <title>{`${item.label}: ${item.valueText}`}</title>
              </g>
            );
          })}
        </svg>
      )}
    </div>
  );
}
