/**
 * Grading Scales — TypeScript mirror of server/src/utils/gradeScales.js
 *
 * Same five scales (ZIMSEC Primary/O/A-Level + Cambridge IGCSE/AS-A-Level)
 * and the same resolver precedence:
 *   rowScale  →  examGroupScale  →  defaultScale  →  DEFAULT_SCALE_KEY
 *
 * The two implementations MUST stay byte-for-byte equivalent (band edges
 * included) — the server is the source of truth, the client uses this
 * for the resolver chain when rendering on the same data without a
 * network round-trip. A divergence would surface as a "percentage
 * says A, server says A*" bug.
 */

export const DEFAULT_SCALE_KEY = 'zimsec_olevel';

/**
 * Branded string type for scale keys. Lets TypeScript catch typos at the
 * call site (e.g. `gradingScale="zimsec_oleves"`) without a runtime check.
 */
export type GradeScaleKey =
  | 'zimsec_primary'
  | 'zimsec_olevel'
  | 'zimsec_alevel'
  | 'cambridge_igcse'
  | 'cambridge_alevel';

export const ALL_SCALE_KEYS: GradeScaleKey[] = [
  'zimsec_primary',
  'zimsec_olevel',
  'zimsec_alevel',
  'cambridge_igcse',
  'cambridge_alevel',
];

export interface GradeBand {
  letter: string;
  descriptor?: string | null;
  min: number;
  max: number;
  points?: number | null;
}

export interface GradeScale {
  key: string;
  label: string;
  description: string;
  bands: GradeBand[];
}

export interface LetterResult {
  letter: string;
  descriptor: string | null;
  points: number | null;
  scale: string;
  label: string;
}

export interface ScaleOption {
  key: string;
  label: string;
  description: string;
}

export const SCALES: Record<string, GradeScale> = {
  zimsec_primary: {
    key: 'zimsec_primary',
    label: 'ZIMSEC Primary',
    description: 'Grade 1-7 (lower is better). Primary school banding.',
    bands: [
      { letter: '1', min: 80, max: 100, points: 1 },
      { letter: '2', min: 70, max: 79, points: 2 },
      { letter: '3', min: 60, max: 69, points: 3 },
      { letter: '4', min: 55, max: 59, points: 4 },
      { letter: '5', min: 50, max: 54, points: 5 },
      { letter: '6', min: 40, max: 49, points: 6 },
      { letter: '7', min: 0, max: 39, points: 7 },
    ],
  },

  zimsec_olevel: {
    key: 'zimsec_olevel',
    label: 'ZIMSEC O-Level',
    description: 'Form 1-4 numeric grade with letter descriptor (1/A* to 9/U).',
    bands: [
      { letter: '1', descriptor: 'A*', min: 80, max: 100, points: 1 },
      { letter: '2', descriptor: 'A', min: 70, max: 79, points: 2 },
      { letter: '3', descriptor: 'B', min: 65, max: 69, points: 3 },
      { letter: '4', descriptor: 'C', min: 60, max: 64, points: 4 },
      { letter: '5', descriptor: 'D', min: 55, max: 59, points: 5 },
      { letter: '6', descriptor: 'E', min: 50, max: 54, points: 6 },
      { letter: '7', descriptor: 'F', min: 40, max: 49, points: 7 },
      { letter: '8', descriptor: 'G', min: 30, max: 39, points: 8 },
      { letter: '9', descriptor: 'U', min: 0, max: 29, points: 9 },
    ],
  },

  zimsec_alevel: {
    key: 'zimsec_alevel',
    label: 'ZIMSEC A-Level',
    description: 'Form 5-6 letter grade (A to U).',
    bands: [
      { letter: 'A', min: 80, max: 100 },
      { letter: 'B', min: 70, max: 79 },
      { letter: 'C', min: 60, max: 69 },
      { letter: 'D', min: 50, max: 59 },
      { letter: 'E', min: 40, max: 49 },
      { letter: 'U', min: 0, max: 39 },
    ],
  },

  cambridge_igcse: {
    key: 'cambridge_igcse',
    label: 'Cambridge IGCSE',
    description: 'IGCSE O-Level equivalent (A* down to U).',
    bands: [
      { letter: 'A*', min: 90, max: 100 },
      { letter: 'A', min: 80, max: 89 },
      { letter: 'B', min: 70, max: 79 },
      { letter: 'C', min: 60, max: 69 },
      { letter: 'D', min: 50, max: 59 },
      { letter: 'E', min: 40, max: 49 },
      { letter: 'F', min: 30, max: 39 },
      { letter: 'G', min: 20, max: 29 },
      { letter: 'U', min: 0, max: 19 },
    ],
  },

  cambridge_alevel: {
    key: 'cambridge_alevel',
    label: 'Cambridge AS/A-Level',
    description: 'Cambridge AS/A-Level (lowercase a-e, u).',
    bands: [
      { letter: 'a', min: 80, max: 100 },
      { letter: 'b', min: 70, max: 79 },
      { letter: 'c', min: 60, max: 69 },
      { letter: 'd', min: 50, max: 59 },
      { letter: 'e', min: 40, max: 49 },
      { letter: 'u', min: 0, max: 39 },
    ],
  },
};

export const SCALE_KEYS: string[] = Object.keys(SCALES);

export function isValidScaleKey(key: unknown): key is string {
  return typeof key === 'string' && Object.prototype.hasOwnProperty.call(SCALES, key);
}

export function scaleOptions(): ScaleOption[] {
  return SCALE_KEYS.map((k) => ({
    key: k,
    label: SCALES[k].label,
    description: SCALES[k].description,
  }));
}

export interface ResolveArgs {
  rowScale?: string | null;
  parentScale?: string | null;
  defaultScale?: string | null;
}

export interface ResolvedScale {
  key: string;
  label: string;
  source: 'resolved' | 'fallback';
}

export function resolveScale(args: ResolveArgs = {}): ResolvedScale {
  const candidates = [args.rowScale, args.parentScale, args.defaultScale];
  for (const c of candidates) {
    if (isValidScaleKey(c)) {
      return { key: c, label: SCALES[c].label, source: 'resolved' };
    }
  }
  return { key: DEFAULT_SCALE_KEY, label: SCALES[DEFAULT_SCALE_KEY].label, source: 'fallback' };
}

/**
 * Compute percentage (0..100, two decimals) from marks + total.
 * Mirrors the server helper so client-side previews match server output.
 */
export function percentageFromMarks(marks: number, total: number): number {
  const t = Number(total) || 0;
  if (t <= 0) return 0;
  return Math.round(((Number(marks) || 0) / t) * 10000) / 100;
}

export function letterFor(percentage: number, scaleKey?: string | null): LetterResult {
  const resolved = resolveScale({ rowScale: scaleKey });
  const scale = SCALES[resolved.key];
  // Mirror server: use Math.floor so a 79.5% mark stays in band 2 (A),
  // not band 1 (A*). See server/src/utils/gradeScales.js for the full note.
  const p = Math.floor(Number(percentage) || 0);
  const clamped = Math.max(0, Math.min(100, p));
  const band =
    scale.bands.find((b) => clamped >= b.min && clamped <= b.max) ||
    scale.bands[scale.bands.length - 1];

  return {
    letter: band.letter,
    descriptor: band.descriptor || null,
    points: typeof band.points === 'number' ? band.points : null,
    scale: resolved.key,
    label: scale.label,
  };
}

/**
 * Display formatter used everywhere a grade renders next to a percentage.
 * Spec: "XX% (1 / A*)" — percentage first, letter in parens.
 *
 *   percentageLetter(75, 'zimsec_olevel')  // "75% (2 / A)"
 *   percentageLetter(75, 'zimsec_primary') // "75% (2)"
 *   percentageLetter(null, 'zimsec_alevel') // "—"
 */
export function percentageLetter(percentage: number | null | undefined, scaleKey?: string | null): string {
  if (percentage == null || Number.isNaN(percentage)) return '—';
  const result = letterFor(percentage, scaleKey);
  const pct = `${Math.round(percentage * 100) / 100}%`;
  if (result.descriptor && result.descriptor !== result.letter) {
    return `${pct} (${result.letter} / ${result.descriptor})`;
  }
  return `${pct} (${result.letter})`;
}
