/**
 * Utility functions for SchoolCompare
 */

import type { School, MetricDefinition, OfstedInspection, SchoolAdmissions, SchoolResult } from './types';

// ============================================================================
// String Utilities
// ============================================================================

/**
 * Create a URL-friendly slug from a string
 */
export function slugify(text: string): string {
  return text
    .toLowerCase()
    .replace(/[^\w\s-]/g, '')
    .replace(/\s+/g, '-')
    .replace(/-+/g, '-')
    .trim();
}

/**
 * Build a school URL path: /school/123456-school-name (capped at ~80 chars total)
 */
const MAX_SLUG_LENGTH = 60;

export function schoolUrl(urn: number, schoolName?: string): string {
  if (!schoolName) return `/school/${urn}`;
  let slug = slugify(schoolName);
  if (slug.length > MAX_SLUG_LENGTH) {
    slug = slug.slice(0, MAX_SLUG_LENGTH).replace(/-+$/, '');
  }
  return `/school/${urn}-${slug}`;
}

/**
 * Extract the URN from a school slug (e.g. "138267-some-school-name" → 138267)
 */
export function parseSchoolSlug(slug: string): number | null {
  const match = slug.match(/^(\d{6})/);
  return match ? parseInt(match[1], 10) : null;
}

/**
 * Escape HTML to prevent XSS
 */
export function escapeHtml(text: string): string {
  const div = document.createElement('div');
  div.textContent = text;
  return div.innerHTML;
}

/**
 * Truncate text to a maximum length
 */
export function truncate(text: string, maxLength: number): string {
  if (text.length <= maxLength) return text;
  return text.slice(0, maxLength).trim() + '...';
}

/**
 * A compact school label for tight spaces (mobile compare rows, chip bars):
 * drop the trailing establishment-type words so "Barclay Primary School" →
 * "Barclay", "St Mary's Catholic Primary School" → "St Mary's". Falls back to
 * a length-capped truncation for names that don't carry a type suffix.
 */
export function shortName(name: string, maxLength = 32): string {
  let s = name
    .replace(
      /\s+(primary|junior|infant|nursery|community|foundation|catholic|academy|school|college)\b.*$/i,
      '',
    )
    .trim();
  if (!s) s = name;
  if (s.length > maxLength) s = s.slice(0, maxLength - 1).trim() + '…';
  return s;
}

/**
 * Format a school's age range for display, e.g. "3-11" → "Ages 3–11".
 * Display-only — leaves the raw `age_range` field (used for sixth-form
 * detection) untouched. Falls back to the raw value if it's not a plain range.
 */
export function formatAgeRange(ageRange: string | null | undefined): string {
  if (!ageRange) return '';
  const match = ageRange.match(/^\s*(\d+)\s*[-–]\s*(\d+)\s*$/);
  if (!match) return ageRange;
  return `Ages ${match[1]}–${match[2]}`;
}

// ============================================================================
// Number Formatting
// ============================================================================

/**
 * Format a number as a percentage
 */
export function formatPercentage(value: number | null | undefined, decimals: number = 1): string {
  if (value === null || value === undefined) return 'N/A';
  return `${value.toFixed(decimals)}%`;
}

export function formatWithSuppression(value: number | null | undefined): { display: string; suppressed: boolean } {
  if (value == null) return { display: '—', suppressed: true };
  return { display: formatPercentage(value), suppressed: false };
}

export const SUPPRESSED_TOOLTIP = 'Data not available — may be suppressed to protect small cohorts.';

/**
 * Format a progress score (can be negative)
 */
export function formatProgress(value: number | null | undefined, decimals: number = 1): string {
  if (value === null || value === undefined) return 'N/A';
  const formatted = value.toFixed(decimals);
  return value > 0 ? `+${formatted}` : formatted;
}

/**
 * Format a score (e.g., test scores)
 */
export function formatScore(value: number | null | undefined, decimals: number = 1): string {
  if (value === null || value === undefined) return 'N/A';
  return value.toFixed(decimals);
}

/**
 * Format a metric value based on its type
 */
export function formatMetricValue(
  value: number | null | undefined,
  format: MetricDefinition['format']
): string {
  switch (format) {
    case 'percentage':
      return formatPercentage(value);
    case 'progress':
      return formatProgress(value);
    case 'score':
      return formatScore(value);
    default:
      return value?.toString() || 'N/A';
  }
}

// ============================================================================
// Trend Analysis
// ============================================================================

/**
 * Calculate the trend between two values
 */
export function calculateTrend(
  current: number | null | undefined,
  previous: number | null | undefined
): 'up' | 'down' | 'stable' {
  if (current === null || current === undefined || previous === null || previous === undefined) {
    return 'stable';
  }

  const diff = current - previous;

  if (Math.abs(diff) < 0.5) return 'stable'; // Within 0.5% is considered stable
  return diff > 0 ? 'up' : 'down';
}

/**
 * Calculate percentage change
 */
export function calculateChange(
  current: number | null | undefined,
  previous: number | null | undefined
): number | null {
  if (current === null || current === undefined || previous === null || previous === undefined) {
    return null;
  }

  return current - previous;
}

// ============================================================================
// Statistical Functions
// ============================================================================

/**
 * Calculate the average of an array of numbers
 */
export function average(values: (number | null)[]): number | null {
  const validValues = values.filter((v): v is number => v !== null && !isNaN(v));
  if (validValues.length === 0) return null;

  const sum = validValues.reduce((acc, val) => acc + val, 0);
  return sum / validValues.length;
}

/**
 * Calculate the standard deviation
 */
export function standardDeviation(values: (number | null)[]): number | null {
  const validValues = values.filter((v): v is number => v !== null && !isNaN(v));
  if (validValues.length === 0) return null;

  const avg = average(validValues);
  if (avg === null) return null;

  const squareDiffs = validValues.map((value) => Math.pow(value - avg, 2));
  const avgSquareDiff = average(squareDiffs);

  return avgSquareDiff !== null ? Math.sqrt(avgSquareDiff) : null;
}

/**
 * Calculate variability label based on standard deviation
 */
export function getVariabilityLabel(stdDev: number | null): string {
  if (stdDev === null) return 'Unknown';
  if (stdDev < 2) return 'Very Stable';
  if (stdDev < 5) return 'Stable';
  if (stdDev < 10) return 'Moderate';
  return 'Variable';
}

// ============================================================================
// Validation
// ============================================================================

/**
 * Validate UK postcode format
 */
export function isValidPostcode(postcode: string): boolean {
  const postcodeRegex = /^[A-Z]{1,2}[0-9][A-Z0-9]?\s*[0-9][A-Z]{2}$/i;
  return postcodeRegex.test(postcode.trim());
}

/**
 * Validate URN (Unique Reference Number)
 */
export function isValidUrn(urn: number | string): boolean {
  const urnNumber = typeof urn === 'string' ? parseInt(urn, 10) : urn;
  return !isNaN(urnNumber) && urnNumber >= 100000 && urnNumber <= 999999;
}

// ============================================================================
// Debounce
// ============================================================================

/**
 * Debounce a function call
 */
export function debounce<T extends (...args: any[]) => any>(
  func: T,
  wait: number
): (...args: Parameters<T>) => void {
  let timeout: NodeJS.Timeout | null = null;

  return function executedFunction(...args: Parameters<T>) {
    const later = () => {
      timeout = null;
      func(...args);
    };

    if (timeout) {
      clearTimeout(timeout);
    }

    timeout = setTimeout(later, wait);
  };
}

// ============================================================================
// Color Utilities
// ============================================================================

/**
 * Series palette for comparing up to eight schools at once.
 *
 * These were Chart.js's stock demo colours, which is why the comparison view
 * never looked like the rest of the site. They now point at the --series-*
 * tokens, so the palette lives in one place and follows the theme.
 *
 * Every step clears WCAG AA on both grounds, so the same value works as a
 * chart line and as the legend text keyed to it — there is no longer a
 * separate text ramp to keep in sync.
 *
 * These are var() strings: correct anywhere the value lands in the DOM
 * (inline styles, CSS custom properties). Canvas can't resolve var(), so
 * Chart.js datasets must use `useSeriesColors()` from lib/theme instead.
 */
export const CHART_COLORS = [
  'var(--series-1)',
  'var(--series-2)',
  'var(--series-3)',
  'var(--series-4)',
  'var(--series-5)',
  'var(--series-6)',
  'var(--series-7)',
  'var(--series-8)',
];

/**
 * Get a color from the palette by index
 */
export function getChartColor(index: number): string {
  return CHART_COLORS[index % CHART_COLORS.length];
}

/**
 * @deprecated The series palette is now AA on both grounds, so line and text
 * share one value. Kept as an alias so call sites can migrate incrementally.
 */
export const CHART_TEXT_COLORS = CHART_COLORS;

export function getChartTextColor(index: number): string {
  return CHART_TEXT_COLORS[index % CHART_TEXT_COLORS.length];
}

/**
 * Add opacity to a colour. Accepts `rgb(...)` or `#rrggbb` — the series
 * palette resolves to hex now that it comes from the token layer, but the
 * rgb() form is still used by callers passing Chart.js literals.
 */
export function rgbToRgba(color: string, alpha: number): string {
  const hex = /^#([0-9a-f]{6})$/i.exec(color.trim());
  if (hex) {
    const n = parseInt(hex[1], 16);
    return `rgba(${(n >> 16) & 255}, ${(n >> 8) & 255}, ${n & 255}, ${alpha})`;
  }
  return color.replace('rgb', 'rgba').replace(')', `, ${alpha})`);
}

/**
 * Trend direction colour. Teal/amber rather than green/red so the signal
 * survives red–green colour blindness; `at` is deliberately neutral so a
 * school sitting on the average doesn't read as a verdict.
 */
export function getTrendColor(trend: 'up' | 'down' | 'stable'): string {
  switch (trend) {
    case 'up':
      return 'var(--status-above)';
    case 'down':
      return 'var(--status-below)';
    case 'stable':
      return 'var(--status-at)';
  }
}

/**
 * Broad shape of a KS2/KS4 metric, used to scale chart axes and format values.
 */
export type MetricKind = 'percentage' | 'progress' | 'score';

export function metricKind(metric: string): MetricKind {
  if (metric.includes('progress')) return 'progress';
  if (metric.includes('pct') || metric.includes('rate')) return 'percentage';
  return 'score';
}

/**
 * Fit a chart y-axis to the data instead of a fixed frame, so clustered
 * series remain distinguishable. Padding keeps a minimum span so noise is
 * not magnified into drama.
 *
 * - percentage: pad and snap to 5s; cap at 100; floor at 0 only when the
 *   data is non-negative (some trend metrics have `pct` in the key but hold
 *   negative year-over-year deltas).
 * - progress: symmetric around 0 so the zero line always shows.
 * - score (Attainment 8, scaled scores): pad and snap to integers; floor at
 *   0 only when the data is non-negative.
 */
export function computeYBounds(
  values: Array<number | null | undefined>,
  kind: MetricKind,
): { min?: number; max?: number } {
  const nums = values.filter((v): v is number => typeof v === 'number' && Number.isFinite(v));
  if (nums.length === 0) return {};

  const lo = Math.min(...nums);
  const hi = Math.max(...nums);

  if (kind === 'progress') {
    const reach = Math.max(2, Math.ceil(Math.max(Math.abs(lo), Math.abs(hi)) + 0.5));
    return { min: -reach, max: reach };
  }

  if (kind === 'percentage') {
    const pad = Math.max(5, Math.round((hi - lo) * 0.2));
    const min = Math.floor((lo - pad) / 5) * 5;
    const max = Math.min(100, Math.ceil((hi + pad) / 5) * 5);
    return { min: lo >= 0 ? Math.max(0, min) : min, max };
  }

  // score
  const pad = Math.max(2, (hi - lo) * 0.2);
  const min = Math.floor(lo - pad);
  return { min: lo >= 0 ? Math.max(0, min) : min, max: Math.ceil(hi + pad) };
}

// ============================================================================
// Local Storage Utilities
// ============================================================================

/**
 * Safely get item from localStorage
 */
export function getFromLocalStorage<T>(key: string, defaultValue: T): T {
  if (typeof window === 'undefined') return defaultValue;

  try {
    const item = window.localStorage.getItem(key);
    return item ? JSON.parse(item) : defaultValue;
  } catch (error) {
    console.error(`Error reading from localStorage key "${key}":`, error);
    return defaultValue;
  }
}

/**
 * Safely set item in localStorage
 */
export function setToLocalStorage<T>(key: string, value: T): void {
  if (typeof window === 'undefined') return;

  try {
    window.localStorage.setItem(key, JSON.stringify(value));
  } catch (error) {
    console.error(`Error writing to localStorage key "${key}":`, error);
  }
}

/**
 * Remove item from localStorage
 */
export function removeFromLocalStorage(key: string): void {
  if (typeof window === 'undefined') return;

  try {
    window.localStorage.removeItem(key);
  } catch (error) {
    console.error(`Error removing from localStorage key "${key}":`, error);
  }
}

// ============================================================================
// URL Utilities
// ============================================================================

/**
 * Build URL with query parameters
 */
export function buildUrl(base: string, params: Record<string, any>): string {
  const url = new URL(base, window.location.origin);

  Object.entries(params).forEach(([key, value]) => {
    if (value !== undefined && value !== null && value !== '') {
      url.searchParams.set(key, String(value));
    }
  });

  return url.pathname + url.search;
}

/**
 * Parse query string into object
 */
export function parseQueryString(search: string): Record<string, string> {
  const params = new URLSearchParams(search);
  const result: Record<string, string> = {};

  params.forEach((value, key) => {
    result[key] = value;
  });

  return result;
}

// ============================================================================
// Date Utilities
// ============================================================================

/**
 * Format academic year.
 * Handles both 4-digit start years (2023 → "2023/24") and
 * 6-digit EES codes (202526 → "2025/26").
 */
export function getPhaseStyle(phase?: string | null): { key: string; label: string } {
  switch (phase?.toLowerCase()) {
    case 'primary':
    case 'middle deemed primary':
      return { key: 'Primary', label: 'Primary' };
    case 'secondary':
    case 'middle deemed secondary':
      return { key: 'Secondary', label: 'Secondary' };
    case 'all-through':
      return { key: 'AllThrough', label: 'All-through' };
    case '16 plus':
      return { key: 'Post16', label: 'Post-16' };
    case 'nursery':
      return { key: 'Nursery', label: 'Nursery' };
    default:
      return { key: '', label: '' };
  }
}

export function formatAcademicYear(year: number | null | undefined): string {
  if (year == null) return '';
  const s = year.toString();
  if (s.length === 6) {
    return `${s.slice(0, 4)}/${s.slice(4)}`;
  }
  const nextYear = (year + 1).toString().slice(-2);
  return `${year}/${nextYear}`;
}

/**
 * Get current academic year
 */
export function getCurrentAcademicYear(): number {
  const now = new Date();
  const year = now.getFullYear();
  const month = now.getMonth();

  // Academic year starts in September (month 8)
  return month >= 8 ? year : year - 1;
}

// ============================================================================
// School Detail Hero Helpers
// ============================================================================

const OFSTED_OEIF_WORDS: Record<number, string> = {
  1: 'Outstanding', 2: 'Good', 3: 'Requires Improvement', 4: 'Inadequate',
};

/**
 * Format an Ofsted inspection date as "Month YYYY" (e.g. "November 2023").
 */
function formatOfstedMonth(date: string | null | undefined): string {
  if (!date) return '';
  const d = new Date(date);
  if (Number.isNaN(d.getTime())) return '';
  return d.toLocaleDateString('en-GB', { month: 'long', year: 'numeric' });
}

export type HeroTone = 'teal' | 'green' | 'gold' | 'coral' | 'neutral';

export interface OfstedHeroChip {
  state: 'oeif' | 'reportCard' | 'none';
  title: string;           // Main label (e.g. "Ofsted Outstanding", "Ofsted Report Card")
  subtitle: string;        // Context line (e.g. "Inspected November 2023")
  detail?: string;         // Optional extra line (e.g. "Safeguarding: Met")
  tone: HeroTone;          // Maps to dedicated hero tone classes (not badge classes)
}

/**
 * Build the hero-strip Ofsted chip, branching on the inspection framework.
 * Never synthesises a single overall grade for ReportCard schools.
 *
 * Note: the API may return ``framework`` as a literal string ``"NULL"`` for
 * older inspections, so we explicitly only branch into the ReportCard layout
 * when the value is exactly ``"ReportCard"``. Anything else with an
 * ``overall_effectiveness`` score is treated as OEIF.
 */
export function buildOfstedHeroChip(ofsted: OfstedInspection | null | undefined): OfstedHeroChip {
  if (!ofsted) {
    return {
      state: 'none',
      title: 'Ofsted pending',
      subtitle: 'No inspection on record',
      tone: 'neutral',
    };
  }

  const when = formatOfstedMonth(ofsted.inspection_date);

  // ReportCard branch — only if the API explicitly says so
  if (ofsted.framework === 'ReportCard') {
    const safeguarding = ofsted.rc_safeguarding_met;
    return {
      state: 'reportCard',
      title: 'Ofsted Report Card',
      subtitle: when ? `Inspected ${when}` : 'New framework inspection',
      detail:
        safeguarding == null
          ? undefined
          : safeguarding ? 'Safeguarding: Met' : 'Safeguarding: Not met',
      tone: safeguarding === false ? 'coral' : 'green',
    };
  }

  // Otherwise treat as OEIF (covers framework === 'OEIF', null, "NULL", etc.)
  const grade = ofsted.overall_effectiveness;
  if (grade && OFSTED_OEIF_WORDS[grade]) {
    const oeifTone: HeroTone =
      grade === 1 ? 'teal' :
      grade === 2 ? 'green' :
      grade === 3 ? 'gold' :
      'coral';
    return {
      state: 'oeif',
      title: `Ofsted ${OFSTED_OEIF_WORDS[grade]}`,
      subtitle: when ? `Inspected ${when}` : 'Inspected',
      tone: oeifTone,
    };
  }

  return {
    state: 'oeif',
    title: 'Ofsted inspected',
    subtitle: when ? `Inspected ${when}` : 'Inspection on record',
    tone: 'neutral',
  };
}

/**
 * Build a one-sentence editorial summary for the school detail hero.
 * Branches on Ofsted framework so Report Card schools are never described
 * with an overall grade they do not have.
 */
export function buildSchoolSummary(
  schoolInfo: School,
  ofsted: OfstedInspection | null | undefined,
  admissions: SchoolAdmissions | null | undefined,
  latestResults: SchoolResult | null | undefined,
): string {
  const parts: string[] = [];

  // Size descriptor
  const pupils = latestResults?.total_pupils ?? schoolInfo.total_pupils ?? null;
  const sizeWord =
    pupils == null ? '' :
    pupils < 200 ? 'Small' :
    pupils < 500 ? 'Mid-sized' :
    'Large';

  // Phase descriptor — avoid the raw code
  const phase = (schoolInfo.phase ?? '').toLowerCase();
  const phaseWord =
    phase.includes('secondary') ? 'secondary' :
    phase === 'all-through' ? 'all-through' :
    phase.includes('primary') ? 'primary' :
    'school';

  // Religious character
  const religion = schoolInfo.religious_denomination;
  const religionWord =
    !religion || /none|does not apply/i.test(religion) ? '' :
    /roman catholic|catholic/i.test(religion) ? 'Catholic ' :
    /church of england|ce|anglican/i.test(religion) ? 'Church of England ' :
    /jewish/i.test(religion) ? 'Jewish ' :
    /muslim|islam/i.test(religion) ? 'Muslim ' :
    /hindu/i.test(religion) ? 'Hindu ' :
    /sikh/i.test(religion) ? 'Sikh ' :
    '';

  // Locality — prefer town from address parsing (fallback to LA)
  const locality = schoolInfo.town || schoolInfo.local_authority || '';

  const lead = [sizeWord, religionWord + phaseWord].filter(Boolean).join(' ');
  let opening = lead || 'School';
  if (locality) opening += ` in ${locality}`;
  parts.push(opening);

  // Ofsted clause (framework-aware)
  if (ofsted?.framework === 'OEIF' && ofsted.overall_effectiveness) {
    parts.push(`rated ${OFSTED_OEIF_WORDS[ofsted.overall_effectiveness]} by Ofsted`);
  } else if (ofsted?.framework === 'ReportCard') {
    const when = formatOfstedMonth(ofsted.inspection_date);
    parts.push(
      when
        ? `most recently inspected under Ofsted's Report Card framework in ${when}`
        : "recently inspected under Ofsted's new Report Card framework",
    );
  }

  // Admissions clause
  if (admissions?.oversubscribed) {
    if (admissions.first_preference_offer_pct != null) {
      const pct = Math.round(admissions.first_preference_offer_pct);
      parts.push(
        `oversubscribed — ${pct}% of first-choice applicants are offered a place`,
      );
    } else {
      parts.push('oversubscribed');
    }
  } else if (admissions?.first_preference_offer_pct != null && admissions.first_preference_offer_pct >= 90) {
    parts.push('most families get their first-choice offer');
  }

  return parts.join(', ') + '.';
}

// ─── Legacy (OEIF) sub-judgement areas ────────────────────────────────────────

export interface OfstedLegacyArea {
  label: string;
  value: number;
}

/**
 * The published OEIF sub-judgement areas for the legacy Ofsted layout, in
 * display order. Only real grades (1–4) are returned: Ofsted's sentinel
 * codes for "not applicable / no judgement" (9, and any 0/8 variants) and
 * nulls are filtered out, so a cryptic "9" never renders as a rating.
 * Sixth Form provision is included where a school has one — it was
 * previously dropped from the detail grid entirely.
 */
export function ofstedLegacyAreas(ofsted: {
  quality_of_education?: number | null;
  behaviour_attitudes?: number | null;
  personal_development?: number | null;
  leadership_management?: number | null;
  early_years_provision?: number | null;
  sixth_form_provision?: number | null;
}): OfstedLegacyArea[] {
  const candidates: Array<[string, number | null | undefined]> = [
    ['Quality of Teaching', ofsted.quality_of_education],
    ['Behaviour in School', ofsted.behaviour_attitudes],
    ["Pupils' Wider Development", ofsted.personal_development],
    ['School Leadership', ofsted.leadership_management],
    ['Early Years (Reception)', ofsted.early_years_provision],
    ['Sixth Form', ofsted.sixth_form_provision],
  ];
  return candidates
    .filter((c): c is [string, number] => c[1] != null && c[1] >= 1 && c[1] <= 4)
    .map(([label, value]) => ({ label, value }));
}

// ─── List-level Ofsted badge ──────────────────────────────────────────────────

export interface OfstedListBadge {
  /** Display text for the badge (e.g. "Outstanding · 2023", "Report Card · 2025") */
  label: string;
  /** CSS module class key — one of: ofsted1 | ofsted2 | ofsted3 | ofsted4 | ofstedRc | ofstedPending */
  cssClass: string;
}

/**
 * Build the Ofsted badge for a school card in the list/map view.
 * States, in priority order:
 *   - Report Card school (ofsted_rc_date set): "Report Card · YYYY" in purple.
 *     Checked FIRST so it wins over any carried-forward legacy grade — the
 *     list has no full report_card object, and ofsted_framework is the raw
 *     event grouping ("Schools - S5"), never "ReportCard".
 *   - OEIF school (ofsted_grade set): grade word + year, colour-keyed
 *   - Inspected without an overall grade (OEIF post-Sept-2024, where Ofsted no
 *     longer issues an overall judgement): "Inspected · YYYY" — mirrors the
 *     detail page's hero chip so a school never reads as both inspected and
 *     "Not yet inspected"
 *   - No inspection on record: "Not yet inspected" in grey
 */
export function buildOfstedListBadge(school: {
  ofsted_grade?: 1 | 2 | 3 | 4 | null;
  ofsted_date?: string | null;
  ofsted_framework?: string | null;
  ofsted_rc_date?: string | null;
}): OfstedListBadge {
  // A report card wins over any carried-forward legacy grade — signalled by
  // ofsted_rc_date. ofsted_framework is the raw event grouping ("Schools -
  // S5"), never "ReportCard", so it can't detect report cards.
  if (school.ofsted_rc_date) {
    const rcYear = new Date(school.ofsted_rc_date).getFullYear();
    return { label: `Report Card · ${rcYear}`, cssClass: 'ofstedRc' };
  }

  const year = school.ofsted_date
    ? new Date(school.ofsted_date).getFullYear()
    : null;
  const yearStr = year ? ` · ${year}` : '';

  if (school.ofsted_grade) {
    const labels: Record<number, string> = {
      1: 'Outstanding',
      2: 'Good',
      3: 'Req. Improvement',
      4: 'Inadequate',
    };
    return {
      label: `${labels[school.ofsted_grade]}${yearStr}`,
      cssClass: `ofsted${school.ofsted_grade}`,
    };
  }

  // An inspection is on record (date or framework present) but carries no
  // overall grade — a post-Sept-2024 OEIF inspection. Distinct from a school
  // that has genuinely never been inspected.
  if (school.ofsted_date != null || school.ofsted_framework != null) {
    return { label: `Inspected${yearStr}`, cssClass: 'ofstedInspected' };
  }

  return { label: 'Not yet inspected', cssClass: 'ofstedPending' };
}

// ============================================================================
// Establishment status
// ============================================================================

export const PROPOSED_TO_CLOSE_STATUS = 'Open, but proposed to close';

/**
 * GIAS lists some operating schools as "Open, but proposed to close".
 * They remain open (and may stay open if the proposal is withdrawn), but the
 * UI marks them so families check with the local authority before applying.
 */
export function isProposedToClose(school: { status?: string | null }): boolean {
  return school.status === PROPOSED_TO_CLOSE_STATUS;
}

/**
 * Special schools, pupil referral units and alternative provision teach pupils
 * with SEND or outside mainstream settings. Their pupils sit the same KS2/KS4
 * assessments but very few reach the mainstream "expected standard", so the
 * headline attainment measures — and any comparison to the England average —
 * are not a fair judgement of the school. Callers use this to drop the
 * mainstream-benchmark framing (deltas, "below England", national markers)
 * rather than portray these schools as failing.
 *
 * Detection is by establishment type: every DfE special-school type contains
 * "special" (e.g. "Community special school", "Academy special converter/sponsor
 * led", "Foundation special school", "Non-maintained special school", "Free
 * schools special", "Other independent special school"); PRUs and alternative
 * provision are matched by name. Special schools carry a mainstream `phase`
 * (Primary/Secondary/All-through), so `phase` alone can't identify them.
 */
export function isSpecialSchool(school: { school_type?: string | null }): boolean {
  const t = (school.school_type ?? '').toLowerCase();
  return /\bspecial\b/.test(t) || /pupil referral/.test(t) || /alternative provision/.test(t);
}
