/**
 * Pure series-building for the comparison trend chart, extracted from
 * ComparisonChart so it is unit-testable without a canvas.
 *
 * Chart truthfulness rules (spec §8.1): every academic year between the
 * first and last data point appears on the axis — cancelled test years
 * (2019/20, 2020/21) and the unpublished 2021/22 school-level year render
 * as real gaps, never as compressed time; school lines never bridge gaps.
 */

import type { ComparisonData } from './types';

/**
 * A yearly row only counts once it carries a real academic year. A school with
 * no performance data still comes back from /api/compare with a single phantom
 * row (the dim_school LEFT JOIN) where `year` is null — and Math.trunc(null) is
 * 0, which would seed the axis at year 0 and, via fillAcademicYears, blow past
 * every real year and blank all schools' lines. Drop those rows up front.
 */
function hasYear(row: { year: number }): boolean {
  return typeof row.year === 'number' && Number.isFinite(row.year);
}

/** 201819 → 201920 (academic-year arithmetic on YYYYYY codes). */
function nextAcademicYear(year: number): number {
  const start = Math.floor(year / 100);
  const end = year % 100;
  return (start + 1) * 100 + (end + 1);
}

/** Every academic year from min(years) to max(years), inclusive. */
export function fillAcademicYears(years: number[]): number[] {
  if (years.length === 0) return [];
  const ints = [...new Set(years.map((y) => Math.trunc(y)))].sort((a, b) => a - b);
  const out: number[] = [];
  let y = ints[0];
  const last = ints[ints.length - 1];
  while (y <= last && out.length < 50) {
    out.push(y);
    y = nextAcademicYear(y);
  }
  return out;
}

export interface CompareChartSeries {
  label: string;
  data: Array<number | null>;
  /** Index into CHART_COLORS / point styles. */
  schoolIndex: number;
  spanGaps: false;
}

export interface EnglandSeries {
  label: 'England average';
  data: Array<number | null>;
  borderDash: [number, number];
  spanGaps: false;
}

export interface CompareChart {
  years: number[];
  schoolDatasets: CompareChartSeries[];
  englandDataset: EnglandSeries | null;
  /** True when England published a 2021/22 figure but no school has one —
   * the UI shows: "DfE didn't publish school-level figures for 2021/22". */
  showUnpublished202122Note: boolean;
  /** Years where the England overlay has a value but no school does — the
   * chart shows a dashed-line-only stretch that needs explaining (KS2 and
   * KS4 have different honest explanations, so the component owns the copy). */
  englandOnlyYears: number[];
}

export function buildCompareChart(
  comparisonData: Record<string, ComparisonData>,
  schools: Array<{ urn: number; school_name: string }>,
  metric: string,
  nationalByYear?: Record<number, number | null | undefined>,
): CompareChart {
  const rawYears = schools.flatMap(
    (s) =>
      comparisonData[String(s.urn)]?.yearly_data.filter(hasYear).map((d) => Math.trunc(d.year)) ??
      [],
  );
  const years = fillAcademicYears(rawYears);

  const schoolDatasets: CompareChartSeries[] = schools.map((school, schoolIndex) => {
    const rows = comparisonData[String(school.urn)]?.yearly_data ?? [];
    const byYear = new Map<number, Record<string, unknown>>();
    for (const row of rows) {
      if (!hasYear(row)) continue;
      byYear.set(Math.trunc(row.year), row as unknown as Record<string, unknown>);
    }
    return {
      label: school.school_name,
      data: years.map((year) => {
        const v = byYear.get(year)?.[metric];
        return typeof v === 'number' && !Number.isNaN(v) ? v : null;
      }),
      schoolIndex,
      spanGaps: false,
    };
  });

  let englandDataset: EnglandSeries | null = null;
  if (nationalByYear) {
    const data = years.map((year) => {
      const v = nationalByYear[year];
      return typeof v === 'number' && !Number.isNaN(v) ? v : null;
    });
    if (data.some((v) => v != null)) {
      englandDataset = { label: 'England average', data, borderDash: [5, 4], spanGaps: false };
    }
  }

  const englandOnlyYears = years.filter(
    (year, i) =>
      englandDataset?.data[i] != null && schoolDatasets.every((ds) => ds.data[i] == null),
  );

  const showUnpublished202122Note = englandOnlyYears.includes(202122);

  return { years, schoolDatasets, englandDataset, showUnpublished202122Note, englandOnlyYears };
}
