import { useMemo, useRef, useState, useEffect } from 'react';
import type { PriceMetric, PricePoint } from '../../types';
import { formatValue } from '../../lib/format';

interface PriceHistoryChartProps {
  points: PricePoint[];
  /** Which value to plot. Defaults to the absolute sale price. */
  metric?: PriceMetric;
}

const PADDING = { top: 8, right: 24, bottom: 20, left: 48 };
const HEIGHT = 120;
const PRICE_SCALE_TOP_PERCENTILE = 95;
const priceFmt = { prefix: '£' };

/** Ticks are nice round values; "£800.0k" both clips and reads worse than "£800k". */
function formatTick(tick: number): string {
  return formatValue(tick, priceFmt).replace(/\.0(?=[kM])/, '');
}

interface PriceScale {
  min: number;
  max: number;
  ticks: number[];
}

export default function PriceHistoryChart({ points, metric = 'price' }: PriceHistoryChartProps) {
  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();
  }, []);

  // Project each point onto the chosen metric as a plain {year, price} so the
  // rest of the chart is metric-agnostic. In per-m² mode, points without a
  // recorded floor area drop out.
  const plotPoints = useMemo<PricePoint[]>(() => {
    if (metric === 'price') return points;
    return points
      .filter((p) => Number.isFinite(p.price_per_sqm))
      .map((p) => ({ year: p.year, price: p.price_per_sqm as number }));
  }, [points, metric]);

  const { yearMin, yearMax, priceScale, medians } = useMemo(() => {
    const points = plotPoints;
    let yMin = Infinity,
      yMax = -Infinity;
    for (const p of points) {
      if (p.year < yMin) yMin = p.year;
      if (p.year > yMax) yMax = p.year;
    }

    // Yearly medians (robust to outliers)
    const byYear = new Map<number, number[]>();
    for (const p of points) {
      const yr = Math.floor(p.year);
      const arr = byYear.get(yr);
      if (arr) arr.push(p.price);
      else byYear.set(yr, [p.price]);
    }
    const yearlyMedians = Array.from(byYear.entries())
      .map(([yr, prices]) => {
        prices.sort((a, b) => a - b);
        const mid = Math.floor(prices.length / 2);
        const median = prices.length % 2 ? prices[mid] : (prices[mid - 1] + prices[mid]) / 2;
        return { year: yr, price: median };
      })
      .sort((a, b) => a.year - b.year);

    // 3-year rolling average
    const meds = yearlyMedians.map((pt, i) => {
      let sum = pt.price;
      let count = 1;
      for (let j = i - 1; j >= 0 && pt.year - yearlyMedians[j].year <= 1; j--) {
        sum += yearlyMedians[j].price;
        count++;
      }
      for (let j = i + 1; j < yearlyMedians.length && yearlyMedians[j].year - pt.year <= 1; j++) {
        sum += yearlyMedians[j].price;
        count++;
      }
      return { year: pt.year + 0.5, price: sum / count };
    });

    return {
      yearMin: yMin,
      yearMax: yMax,
      priceScale: getPriceScale(points),
      medians: meds,
    };
  }, [plotPoints]);

  const plotW = width - PADDING.left - PADDING.right;
  const plotH = HEIGHT - PADDING.top - PADDING.bottom;
  const yearRange = yearMax - yearMin || 1;

  const scaleX = (year: number) => PADDING.left + ((year - yearMin) / yearRange) * plotW;
  const scaleY = (price: number) => {
    const t = (price - priceScale.min) / (priceScale.max - priceScale.min || 1);
    return PADDING.top + (1 - t) * plotH;
  };

  // Year labels: every 5 years
  const yearStart = Math.ceil(yearMin / 5) * 5;
  const yearLabels: number[] = [];
  for (let y = yearStart; y <= yearMax; y += 5) yearLabels.push(y);

  const medianPolyline = medians.map((a) => `${scaleX(a.year)},${scaleY(a.price)}`).join(' ');

  return (
    <div ref={containerRef} style={{ height: HEIGHT }}>
      {width > 0 && plotPoints.length > 0 && (
        <svg width={width} height={HEIGHT}>
          {/* Grid lines */}
          {priceScale.ticks.map((tick) => (
            <line
              key={tick}
              x1={PADDING.left}
              y1={scaleY(tick)}
              x2={width - PADDING.right}
              y2={scaleY(tick)}
              className="stroke-warm-200 dark:stroke-warm-700"
              strokeWidth={1}
            />
          ))}

          {/* Dots */}
          {plotPoints.map((p, i) => (
            <circle
              key={i}
              cx={scaleX(p.year)}
              cy={scaleY(p.price)}
              r={3}
              className="fill-teal-500 dark:fill-teal-400"
              opacity={0.35}
            />
          ))}

          {/* Median line */}
          {medians.length > 1 && (
            <polyline
              points={medianPolyline}
              fill="none"
              className="stroke-teal-600 dark:stroke-teal-400"
              strokeWidth={2}
              strokeLinejoin="round"
            />
          )}

          {/* Y-axis labels */}
          {priceScale.ticks.map((tick) => (
            <text
              key={`label-${tick}`}
              x={PADDING.left - 4}
              y={scaleY(tick)}
              textAnchor="end"
              dominantBaseline="middle"
              className="fill-warm-500 dark:fill-warm-400"
              fontSize={10}
            >
              {formatTick(tick)}
            </text>
          ))}

          {/* X-axis year labels */}
          {yearLabels.map((yr) => (
            <text
              key={yr}
              x={scaleX(yr)}
              y={HEIGHT - 2}
              textAnchor="middle"
              className="fill-warm-500 dark:fill-warm-400"
              fontSize={10}
            >
              {yr}
            </text>
          ))}
        </svg>
      )}
    </div>
  );
}

export function getPriceScale(points: PricePoint[]): PriceScale {
  const prices = points
    .map((p) => p.price)
    .filter(Number.isFinite)
    .sort((a, b) => a - b);
  if (prices.length === 0) {
    return { min: 0, max: 1, ticks: [0, 1] };
  }

  const min = prices[0];
  const scaleTop = percentile(prices, PRICE_SCALE_TOP_PERCENTILE);
  const range = scaleTop - min;
  const padding = range > 0 ? range * 0.1 : Math.max(Math.abs(scaleTop) * 0.1, 1);
  const paddedMin = Math.max(0, min - padding);
  const paddedMax = scaleTop + padding;
  const ticks = niceTicksForRange(paddedMin, paddedMax, 4);

  return {
    min: ticks[0] ?? paddedMin,
    max: ticks[ticks.length - 1] ?? paddedMax,
    ticks,
  };
}

function percentile(sorted: number[], p: number): number {
  const clamped = Math.max(0, Math.min(100, p));
  const rank = ((sorted.length - 1) * clamped) / 100;
  const lower = Math.floor(rank);
  const upper = Math.ceil(rank);
  if (lower === upper) return sorted[lower];
  const weight = rank - lower;
  return sorted[lower] * (1 - weight) + sorted[upper] * weight;
}

/** Generate ~count nice round tick values spanning [min, max]. */
function niceTicksForRange(min: number, max: number, count: number): number[] {
  const range = max - min;
  if (range <= 0) return [min];
  const rough = range / count;
  const magnitude = Math.pow(10, Math.floor(Math.log10(rough)));
  let step: number;
  const normalized = rough / magnitude;
  if (normalized <= 1.5) step = magnitude;
  else if (normalized <= 3.5) step = 2 * magnitude;
  else if (normalized <= 7.5) step = 5 * magnitude;
  else step = 10 * magnitude;

  const start =
    min >= 0 ? Math.max(0, Math.floor(min / step) * step) : Math.floor(min / step) * step;
  const end = Math.ceil(max / step) * step;

  const ticks: number[] = [];
  for (let t = start; t <= end + step / 2; t += step) {
    ticks.push(cleanTick(t));
  }
  return ticks;
}

function cleanTick(value: number): number {
  return Number(value.toPrecision(12));
}
