/**
 * Education Management Module for BaoLife
 * Handles schools, colleges, majors, focuses, and extracurriculars.
 * Ported from Python education/education_manager.py
 */

import { v4 as uuidv4 } from 'uuid';
import { Player } from '../../models/Player.js';
import { Person } from '../../models/Person.js';
import { createActivityClassmates, createClassmates } from '../character/character_manager.js';
import { FOCUS_ENERGY_MODIFIERS, FOCUS_GPA_MODIFIERS, FOCUS_DEFINITIONS } from '../../constants/focus.js';
import { checkAchievementsAsync, updateQuestProgress } from '../retention/index.js';

// ============================================================================
// Types
// ============================================================================

export interface ElementarySchool {
  id: string;
  type: 'elementary_school';
  title: string;
  public_private: 'Public' | 'Private';  // Python uses snake_case
  student_count: number;
  teacher_student_ratio: number;
  description: string;
  energyModifier: number;
  image: string | null;
}

export interface HighSchool {
  id: string;
  type: 'high_school';
  title: string;
  public_private: 'Public' | 'Private';  // Python uses snake_case
  student_count: number;
  teacher_student_ratio: number;
  GPA_avg: number;  // Python uses GPA_avg
  description: string;
  energyModifier: number;
  image: string | null;
}

export interface College {
  id: string;
  type: 'college';
  title: string;
  public_private: 'Public' | 'Private';  // Python uses snake_case
  attendance: number;
  GPA_req: number;  // Python uses GPA_req
  ACT_req: number;  // Python uses ACT_req
  specialization: string;
  cost: number;
  description: string;
  energyModifier: number;
  image: string | null;
}

export interface CollegeMajor {
  id: string;
  title: string;
  related_jobs: string[];  // Python uses snake_case
  colleges: string[];  // Python uses 'colleges' not 'collegeIds'
}

export interface Focus {
  id: number;
  focus_name: string;  // Python uses snake_case
  description: string;
  energyModifier: number;
}

export interface Extracurricular {
  id: string;
  title: string;
  type: 'extracurricular';
  focus: string;
  description: string;
  energyModifier: number;
  image: string | null;
}

export interface EducationRecord {
  id: string;
  educationLevel: string;
  locationId: string;
  locationType: string;
  date: string;
  gpa: number;
  focus: string;
}

export interface ActivityRecord {
  id: string;
  type: string;
  date: string;
  focus?: string;
  gpa?: number;
}

export type School = ElementarySchool | HighSchool | College;

// ============================================================================
// School Factories
// ============================================================================

export function createElementarySchool(
  schoolName: string,
  public_private: 'Public' | 'Private',
  student_count: number,
  teacher_student_ratio: number,
  image: string | null = null
): ElementarySchool {
  return {
    id: uuidv4().replace(/-/g, ''),
    type: 'elementary_school',
    title: schoolName,
    public_private,
    student_count,
    teacher_student_ratio,
    description: `A ${public_private} elementary school with ${student_count} students and a teacher-student ratio of ${teacher_student_ratio}.`,
    energyModifier: 15,
    image,
  };
}

export function createHighSchool(
  schoolName: string,
  public_private: 'Public' | 'Private',
  student_count: number,
  teacher_student_ratio: number,
  GPA_avg: number,
  image: string | null = null
): HighSchool {
  return {
    id: uuidv4().replace(/-/g, ''),
    type: 'high_school',
    title: schoolName,
    public_private,
    student_count,
    teacher_student_ratio,
    GPA_avg,
    description: `A ${public_private} high school with ${student_count} students, a teacher-student ratio of ${teacher_student_ratio}, and an average GPA of ${GPA_avg}.`,
    energyModifier: 20,
    image,
  };
}

export function createCollege(
  title: string,
  public_private: 'Public' | 'Private',
  attendance: number,
  GPA_req: number,
  ACT_req: number,
  specialization: string,
  cost: number,
  image: string | null = null
): College {
  return {
    id: uuidv4().replace(/-/g, ''),
    type: 'college',
    title,
    public_private,
    attendance,
    GPA_req,
    ACT_req,
    specialization,
    cost,
    description: `A ${public_private} college with a focus on ${specialization}. The cost of attendance is $${cost} per year.`,
    energyModifier: 20,
    image,
  };
}

// ============================================================================
// School Generation
// ============================================================================

let cachedElementarySchools: ElementarySchool[] | null = null;
let cachedHighSchools: HighSchool[] | null = null;
let cachedColleges: College[] | null = null;
let cachedMajors: CollegeMajor[] | null = null;

export function getElementarySchools(): ElementarySchool[] {
  if (cachedElementarySchools) return cachedElementarySchools;

  cachedElementarySchools = [
    createElementarySchool('Maple Elementary School', 'Public', 350, 20, 'https://v3b.fal.media/files/b/lion/CpB2aNqv0hZSAt6LAXMPc_output.png'),
    createElementarySchool('Eagle Private Academy', 'Private', 200, 15, 'https://v3b.fal.media/files/b/zebra/5XWdF5HbjNA-9b4ywu4AZ_output.png'),
    createElementarySchool('Greenwood Elementary', 'Public', 400, 22, 'https://v3b.fal.media/files/b/koala/AIsZQawDL7vB7Jknjpo9Y_output.png'),
    createElementarySchool('Bright Stars School', 'Private', 150, 12, 'https://v3b.fal.media/files/b/lion/Bh0Hd9irvCMROr-p_G-84_output.png'),
    createElementarySchool('Lakeview Elementary', 'Public', 320, 18, 'https://v3b.fal.media/files/b/zebra/IeYo5Q6UCJVUxrgQGLYZL_output.png'),
    createElementarySchool('Grand Oak Elementary', 'Public', 500, 25, 'https://v3b.fal.media/files/b/kangaroo/NnrqQFCtX_r5TIaqzcicC_output.png'),
  ];

  return cachedElementarySchools;
}

export function getHighSchools(): HighSchool[] {
  if (cachedHighSchools) return cachedHighSchools;

  cachedHighSchools = [
    createHighSchool('Valley High School', 'Public', 1200, 25, 3.2, 'https://v3b.fal.media/files/b/zebra/-KUYF_m7ogEpGacZpibpE_output.png'),
    createHighSchool('Prestige Prep School', 'Private', 600, 10, 3.8, 'https://v3b.fal.media/files/b/zebra/xnj1-iTDaxz9WJEuiovrV_output.png'),
    createHighSchool('Northview High', 'Public', 1500, 28, 2.8, 'https://v3b.fal.media/files/b/koala/okbmoh_kANj1YeEDdDfso_output.png'),
    createHighSchool('Academic Excellence Institute', 'Private', 450, 8, 4.0, 'https://v3b.fal.media/files/b/tiger/PLKjjZXyeUCPu37Ys-YVx_output.png'),
    createHighSchool('Harmony High', 'Public', 1400, 27, 3.0, 'https://v3b.fal.media/files/b/panda/DcGawkSRWjfBC7Vvzg7r__output.png'),
    createHighSchool('Mountain Ridge High', 'Public', 1300, 24, 3.4, 'https://v3b.fal.media/files/b/rabbit/MEKUq9oBoHDLVlkyUFaQs_output.png'),
  ];

  return cachedHighSchools;
}

export function getColleges(): College[] {
  if (cachedColleges) return cachedColleges;

  cachedColleges = [
    createCollege('University of Science', 'Public', 15000, 3.5, 28, 'Science and Engineering', 10000, 'https://v3b.fal.media/files/b/tiger/RZeOANHNvGzGJo63zWZii_output.png'),
    createCollege('Artistic Minds University', 'Private', 6000, 3.2, 24, 'Arts and Humanities', 20000, 'https://v3b.fal.media/files/b/zebra/3pim3xDjmslzE5G3tmpPB_output.png'),
    createCollege('Eastern Commerce College', 'Public', 20000, 3.1, 23, 'Business and Economics', 8000, 'https://v3b.fal.media/files/b/monkey/m6iy3jaNX4QQQ1fc8t6uq_output.png'),
    createCollege('Law and Governance University', 'Private', 7000, 3.6, 30, 'Law and Politics', 25000, 'https://v3b.fal.media/files/b/kangaroo/xwKoxESWFmglFxTzHryfn_output.png'),
    createCollege('Institute of Tech Innovation', 'Public', 12000, 3.7, 29, 'Technology and Computer Science', 15000, 'https://v3b.fal.media/files/b/panda/u6oYxXNtV9YzEW3dj-eu3_output.png'),
    createCollege('Liberal Arts Academy', 'Private', 4000, 3.3, 25, 'Liberal Arts', 18000, 'https://v3b.fal.media/files/b/zebra/3pim3xDjmslzE5G3tmpPB_output.png'),
    createCollege('Northern Agriculture College', 'Public', 14000, 3.0, 22, 'Agriculture and Environmental Science', 12000, 'https://v3b.fal.media/files/b/rabbit/zaYHyx9Odqx_n3nNmd0NS_output.png'),
    createCollege('Health and Medicine University', 'Private', 8000, 3.7, 31, 'Health and Medicine', 30000, 'https://v3b.fal.media/files/b/kangaroo/9ZXtAvZoy1qhbdPU91GqE_output.png'),
    createCollege('Oceanography Institute', 'Public', 10000, 3.4, 26, 'Marine Science', 9000, 'https://v3b.fal.media/files/b/monkey/Vn-IR_yaQ9tk2iQzQa5-s_output.png'),
    createCollege('Performing Arts School', 'Private', 5000, 3.2, 24, 'Performing Arts', 22000, 'https://v3b.fal.media/files/b/koala/q8_eoVhSeDNGyCk8kyFzY_output.png'),
  ];

  return cachedColleges;
}

export function getMajors(): CollegeMajor[] {
  if (cachedMajors) return cachedMajors;

  const colleges = getColleges();

  cachedMajors = [
    { id: uuidv4().replace(/-/g, ''), title: 'Computer Science', related_jobs: ['Software Engineer'], colleges: [colleges[0].id, colleges[4].id] },
    { id: uuidv4().replace(/-/g, ''), title: 'Nursing', related_jobs: ['Registered Nurse'], colleges: [colleges[7].id] },
    { id: uuidv4().replace(/-/g, ''), title: 'Education', related_jobs: ['High School Teacher', 'Elementary School Teacher', 'Counselor'], colleges: [colleges[5].id] },
    { id: uuidv4().replace(/-/g, ''), title: 'Criminal Justice', related_jobs: ['Police Officer'], colleges: [colleges[3].id] },
    { id: uuidv4().replace(/-/g, ''), title: 'Accounting', related_jobs: ['Accountant'], colleges: [colleges[2].id] },
    { id: uuidv4().replace(/-/g, ''), title: 'Culinary Arts', related_jobs: ['Chef'], colleges: [colleges[1].id, colleges[9].id] },
    { id: uuidv4().replace(/-/g, ''), title: 'Law', related_jobs: ['Lawyer'], colleges: [colleges[3].id] },
    { id: uuidv4().replace(/-/g, ''), title: 'Automotive Technology', related_jobs: ['Automotive Mechanic'], colleges: [colleges[0].id] },
    { id: uuidv4().replace(/-/g, ''), title: 'Business', related_jobs: ['Sales Representative', 'Real Estate Agent'], colleges: [colleges[2].id] },
    { id: uuidv4().replace(/-/g, ''), title: 'Architecture', related_jobs: ['Architect'], colleges: [colleges[0].id, colleges[4].id] },
    { id: uuidv4().replace(/-/g, ''), title: 'Medicine', related_jobs: ['Physician', 'Dentist', 'Pharmacist'], colleges: [colleges[7].id] },
    { id: uuidv4().replace(/-/g, ''), title: 'Library Science', related_jobs: ['Librarian'], colleges: [colleges[5].id] },
    { id: uuidv4().replace(/-/g, ''), title: 'Journalism', related_jobs: ['Journalist'], colleges: [colleges[1].id] },
    { id: uuidv4().replace(/-/g, ''), title: 'Psychology', related_jobs: ['Counselor'], colleges: [colleges[5].id, colleges[7].id] },
    { id: uuidv4().replace(/-/g, ''), title: 'Veterinary Medicine', related_jobs: ['Veterinarian'], colleges: [colleges[6].id] },
  ];

  return cachedMajors;
}

// ============================================================================
// Focus Management
// ============================================================================

// Use centralized focus definitions, converted to local Focus type
const FOCUSES: Focus[] = FOCUS_DEFINITIONS.map((f) => ({
  id: f.id,
  focus_name: f.name,
  description: f.description,
  energyModifier: f.energyModifier,
}));

export function getFocuses(): Focus[] {
  return FOCUSES;
}

export function getFocus(focusName: string): Focus | undefined {
  return FOCUSES.find((f) => f.focus_name === focusName);
}

export function getRandomFocus(): Focus {
  return FOCUSES[Math.floor(Math.random() * FOCUSES.length)];
}

export function updateFocus(player: Player, activityId: string, newFocusName: string): boolean {
  const newFocus = getFocus(newFocusName);
  if (!newFocus) return false;

  const person = player.c;
  if (!person.activityRecords) return false;

  for (const record of person.activityRecords) {
    if (String(record.id).trim() === String(activityId).trim()) {
      console.log(`${record.focus} -> ${newFocusName}`);
      record.focus = newFocus.focus_name;
      return true;
    }
  }

  console.log('No matching activity found.');
  return false;
}

// ============================================================================
// Extracurricular Management
// ============================================================================

let cachedExtracurriculars: Extracurricular[] | null = null;

export function getExtracurriculars(): Extracurricular[] {
  if (cachedExtracurriculars) return cachedExtracurriculars;

  cachedExtracurriculars = [
    {
      id: uuidv4().replace(/-/g, ''),
      title: 'Choir',
      type: 'extracurricular',
      focus: 'Work Hard',
      description: 'Perfect your singing skills and perform in concerts',
      energyModifier: 20,
      image: 'https://v3b.fal.media/files/b/0a8c273b/34KV06iexx3cKoreX88hK.png',
    },
    {
      id: uuidv4().replace(/-/g, ''),
      title: 'Musical Theater',
      type: 'extracurricular',
      focus: 'Socialize',
      description: 'Perform in musicals and bond with the cast',
      energyModifier: 30,
      image: 'https://v3b.fal.media/files/b/0a8c273c/YJYeYbEwGz8gnESUSF5pQ.png',
    },
    {
      id: uuidv4().replace(/-/g, ''),
      title: 'Debate Team',
      type: 'extracurricular',
      focus: 'Work Hard',
      description: 'Develop your debating skills and compete against other teams',
      energyModifier: 20,
      image: 'https://v3b.fal.media/files/b/0a8c273d/TiUKm36kVF9ephDcwpiub.png',
    },
    {
      id: uuidv4().replace(/-/g, ''),
      title: 'Writing Club',
      type: 'extracurricular',
      focus: 'Balanced',
      description: 'Improve your writing and share ideas with other members',
      energyModifier: 10,
      image: 'https://v3b.fal.media/files/b/0a8c273f/ltKcngY3-VHUf6zaLjpSm.png',
    },
    {
      id: uuidv4().replace(/-/g, ''),
      title: 'Robotics Team',
      type: 'extracurricular',
      focus: 'Work Hard',
      description: 'Design and build robots for competitions',
      energyModifier: 40,
      image: 'https://v3b.fal.media/files/b/0a8c2740/XxVeZUZJvvpRNPaZWP71K.png',
    },
    {
      id: uuidv4().replace(/-/g, ''),
      title: 'Baseball',
      type: 'extracurricular',
      focus: 'Socialize',
      description: 'Play baseball and spend time with teammates',
      energyModifier: 20,
      image: 'https://v3b.fal.media/files/b/0a8c2741/x07MNLPiIpUu0nD9AK1Mf.png',
    },
    {
      id: uuidv4().replace(/-/g, ''),
      title: 'Basketball',
      type: 'extracurricular',
      focus: 'Socialize',
      description: 'Play basketball and bond with teammates',
      energyModifier: 30,
      image: 'https://v3b.fal.media/files/b/0a8c2743/pGvzD3WMbc5PAm3LxYZ4T.png',
    },
    {
      id: uuidv4().replace(/-/g, ''),
      title: 'Soccer',
      type: 'extracurricular',
      focus: 'Socialize',
      description: 'Play soccer and spend time with teammates',
      energyModifier: 30,
      image: 'https://v3b.fal.media/files/b/0a8c2744/q7KZVnmiXTwCX0osKyll3.png',
    },
    {
      id: uuidv4().replace(/-/g, ''),
      title: 'Football',
      type: 'extracurricular',
      focus: 'Socialize',
      description: 'Play football and bond with teammates',
      energyModifier: 40,
      image: 'https://v3b.fal.media/files/b/0a8c2745/xjHfVhawUh-1hf5kji-ts.png',
    },
    // Music Instruments
    {
      id: uuidv4().replace(/-/g, ''),
      title: 'Piano',
      type: 'extracurricular',
      focus: 'Work Hard',
      description: 'Learn to play the piano and perform beautiful melodies',
      energyModifier: 25,
      image: 'https://v3b.fal.media/files/b/0a8c2747/lbOYI7D7kbE3xoiRnLGTj.png',
    },
    {
      id: uuidv4().replace(/-/g, ''),
      title: 'Guitar',
      type: 'extracurricular',
      focus: 'Balanced',
      description: 'Learn to play guitar and jam with friends',
      energyModifier: 20,
      image: 'https://v3b.fal.media/files/b/0a8c274a/6CH8i4tL-ZaRCKCp17qyY.png',
    },
    {
      id: uuidv4().replace(/-/g, ''),
      title: 'Violin',
      type: 'extracurricular',
      focus: 'Work Hard',
      description: 'Master the violin and join orchestras',
      energyModifier: 30,
      image: 'https://v3b.fal.media/files/b/0a8c274c/QsaJYcIFB_vpcwk6Och2Z.png',
    },
    {
      id: uuidv4().replace(/-/g, ''),
      title: 'Drums',
      type: 'extracurricular',
      focus: 'Socialize',
      description: 'Learn drums and join a band',
      energyModifier: 35,
      image: 'https://v3b.fal.media/files/b/0a8c274e/CPi6cTV0QHcmxVFATFbFV.png',
    },
    // Dance Class
    {
      id: uuidv4().replace(/-/g, ''),
      title: 'Dance Class',
      type: 'extracurricular',
      focus: 'Balanced',
      description: 'Learn various dance styles and perform on stage',
      energyModifier: 30,
      image: 'https://v3b.fal.media/files/b/0a8c274f/hJs3m1If_bQ_hdHtbm_3x.png',
    },
  ];

  return cachedExtracurriculars;
}

export function getRandomExtracurricular(): Extracurricular {
  const extras = getExtracurriculars();
  return extras[Math.floor(Math.random() * extras.length)];
}

/**
 * Assign an extracurricular activity to a person.
 * This is the low-level function that adds the activity and creates a record.
 * Use applyForExtracurricular for player-facing applications.
 *
 * @param person - Person to assign activity to
 * @param extracurricular - Extracurricular activity to assign
 * @param date - Date the activity was started
 */
export function setExtracurricular(
  person: Person,
  extracurricular: Extracurricular,
  date: string
): void {
  console.log(`Setting extracurricular: ${extracurricular.title} on ${date}`);

  // Add to activities
  if (!person.activities) {
    person.activities = [];
  }
  person.activities.push(extracurricular);

  // Create activity record
  if (!person.activityRecords) {
    person.activityRecords = [];
  }
  person.activityRecords.push({
    id: extracurricular.id,
    type: extracurricular.type,
    date: date,
    focus: extracurricular.focus,
  });
}

export function getExtracurricularByTitle(title: string): Extracurricular | undefined {
  const extras = getExtracurriculars();
  return extras.find((e) => e.title.toLowerCase() === title.toLowerCase());
}

export function applyForExtracurricular(player: Player, extracurricularId: string): boolean {
  const extracurriculars = getExtracurriculars();
  const extracurricular = extracurriculars.find((e) => e.id === extracurricularId);

  if (!extracurricular) {
    return false;
  }

  // Check if already enrolled
  const alreadyEnrolled = (player.c.activities ?? []).some(
    (a: { id?: string }) => a.id === extracurricularId
  );
  if (alreadyEnrolled) {
    player.messageQueue.push(`You are already enrolled in ${extracurricular.title}.`);
    return false;
  }

  // Add to activities
  if (!player.c.activities) {
    player.c.activities = [];
  }
  player.c.activities.push(extracurricular);

  // Create activity record
  if (!player.c.activityRecords) {
    player.c.activityRecords = [];
  }
  player.c.activityRecords.push({
    id: extracurricular.id,
    type: 'extracurricular',
    date: player.date,
    focus: extracurricular.focus,
  });

  // Create activity classmates/teammates (matches Python create_classmates)
  // This creates 3-5 NPCs who share the activity, providing social connections
  createActivityClassmates(player, extracurricular);

  player.messageQueue.push(`You have joined ${extracurricular.title}!`);
  return true;
}

export function quitExtracurricular(player: Player, extracurricularId: string): boolean {
  const extracurriculars = getExtracurriculars();
  const extracurricular = extracurriculars.find((e) => e.id === extracurricularId);

  if (!extracurricular) {
    return false;
  }

  // Check if actually enrolled
  const isEnrolled = (player.c.activities ?? []).some(
    (a: { id?: string }) => a.id === extracurricularId
  );
  if (!isEnrolled) {
    player.messageQueue.push(`You are not enrolled in ${extracurricular.title}.`);
    return false;
  }

  // Remove from activities
  if (player.c.activities) {
    player.c.activities = player.c.activities.filter(
      (a: { id: string }) => a.id !== extracurricular.id
    );
  }

  // Remove activity record
  if (player.c.activityRecords) {
    player.c.activityRecords = player.c.activityRecords.filter(
      (r: { id: string }) => r.id !== extracurricular.id
    );
  }

  // Clean up related schedules (remove any practice schedules)
  if (player.c.schedules) {
    player.c.schedules = player.c.schedules.filter(
      (s: { title?: string }) => !s.title?.toLowerCase().includes(extracurricular.title.toLowerCase())
    );
  }

  // Apply affinity penalty to teammates who shared the activity
  // This represents disappointment from teammates when you quit
  const activityTitleLower = extracurricular.title.toLowerCase();
  const teammateRelationship = `${activityTitleLower}_teammate`;

  for (const person of player.r ?? []) {
    // Check if person was a teammate for this activity
    if (person.relationships?.includes(teammateRelationship) ||
        person.relationships?.includes('classmate')) {
      // Check if they have the same activity
      const sharedActivity = (person.activities ?? []).some(
        (a: { id?: string }) => a.id === extracurricularId
      );
      if (sharedActivity) {
        // Apply affinity penalty (-5 to -15)
        const penalty = Math.floor(Math.random() * 11) + 5; // 5-15
        person.affinity = Math.max(-100, (person.affinity ?? 50) - penalty);
      }
    }
  }

  player.messageQueue.push(`You have quit ${extracurricular.title}. Your teammates are disappointed.`);
  return true;
}

// ============================================================================
// Education Management
// ============================================================================

const GRADE_LEVELS: Record<number, string> = {
  5: 'kindergarten',
  6: '1st',
  7: '2nd',
  8: '3rd',
  9: '4th',
  10: '5th',
  11: '6th',
  12: '7th',
  13: '8th',
  14: '9th',
  15: '10th',
  16: '11th',
  17: '12th',
};

export function getEducationLevelForAge(age: number): string | null {
  return GRADE_LEVELS[age] || null;
}

/**
 * Weekly GPA update based on focus level.
 * Called during game loop to update student academic performance.
 *
 * Focus effects on GPA:
 * - Work Hard: GPA increases more often (+1 modifier)
 * - Slack Off: GPA decreases more often (-1 modifier)
 * - Balanced/Socialize: Neutral changes
 *
 * @param person - Person with current_education
 */
export function handleEducation(person: Person): void {
  if (!person.current_education) return;

  // Find activity record and update GPA
  if (!person.activityRecords) return;

  for (const record of person.activityRecords) {
    if (record.id === person.current_education.id) {
      // Get GPA modifier from centralized constants
      const focusName = person.current_education.focus ?? 'Balanced';
      const gpaModifier = FOCUS_GPA_MODIFIERS[focusName as keyof typeof FOCUS_GPA_MODIFIERS] ?? 0;

      // Python: person.activityRecords[index].GPA += random.randint(-1, 1 + GPA_modifier)
      // This means: Work Hard -> random(-1, 2), Slack Off -> random(-1, 0), Balanced -> random(-1, 1)
      const minChange = -1;
      const maxChange = 1 + gpaModifier;
      const change = Math.floor(Math.random() * (maxChange - minChange + 1)) + minChange;

      // Update GPA (use record.gpa or record.GPA for compatibility)
      const currentGPA = record.gpa ?? record.GPA ?? 50;
      const newGPA = Math.max(0, Math.min(100, currentGPA + change));

      // Update both fields for compatibility with different record types
      record.gpa = newGPA;
      if ('GPA' in record) {
        record.GPA = newGPA;
      }
      break;
    }
  }
}

/**
 * Advance the player's education credential by age, on a daily tick (idempotent).
 *
 * Before this, c.education was set once at character creation and NEVER advanced
 * — the character never progressed past their starting grade and never
 * graduated, which permanently locked every credential-gated career (~2/3 of the
 * catalog, incl. the whole aspirational tier) behind an unobtainable diploma.
 *
 * This drives a deterministic education track and emits `graduation` lifecycle
 * events (→ the `graduate` / `straight_a` achievements + life goals):
 *   - ages 5-17: keep the school grade current (kindergarten … 12th)
 *   - age 18: graduate high school → 'high_school' (unlocks the HS job tier)
 *   - age 22: graduate college → 'bachelors_degree' (unlocks the aspirational tier)
 *
 * NOTE (design default, flagged for follow-up): education is currently an
 * automatic track for everyone. Player-driven choice — dropping out, skipping or
 * choosing college, majors mapping to specific careers, and GPA-gated admission —
 * is a natural future enhancement layered on top of this progression.
 */
export function advanceEducation(player: Player): void {
  const c = player.c;
  if (!c || c.status === 'dead') return;
  const age = c.ageYears ?? 0;

  // School-age: keep the grade level current.
  const grade = getEducationLevelForAge(age);
  if (grade) {
    if (c.education !== grade) c.education = grade;
    return;
  }
  if (age < 18) return; // pre-school toddlers

  // GPA on the 0-4 scale for graduation credit (straight_a achievement). The
  // record stores 0-100 (default 50); convert at the source like graduationDay.
  const gpa100 = (c.current_education?.gpa as number | undefined) ?? 0;
  const gpa4 = (gpa100 / 100) * 4.0;
  player.lifecycleQueue = player.lifecycleQueue ?? [];
  player.messageQueue = player.messageQueue ?? [];

  const hasDegreeWord = (s: string | undefined): boolean =>
    !!s && (s.includes('bachelor') || s.includes('doctor') || s.endsWith('_degree'));

  // High-school graduation at 18.
  if (!player.events.has('graduated_high_school')) {
    player.events.add('graduated_high_school');
    if (!hasDegreeWord(c.education) && c.education !== 'high_school') {
      c.education = 'high_school';
    }
    player.lifecycleQueue.push({ type: 'graduation', data: { level: 'high school', gpa: gpa4 } });
    // Only celebrate for natural-age graduates; backfill older starts silently.
    if (age <= 21) {
      player.messageQueue.push('You graduated from high school!');
      applyGpaGraduationReward(player, gpa100, 'high school');
    }
    return; // one transition per call
  }

  // College graduation at 22 → bachelors_degree (the aspirational-career unlock).
  if (age >= 22 && !player.events.has('graduated_college')) {
    player.events.add('graduated_college');
    if (!hasDegreeWord(c.education)) {
      c.education = 'bachelors_degree';
    }
    player.lifecycleQueue.push({ type: 'graduation', data: { level: 'college', gpa: gpa4 } });
    if (age <= 25) {
      player.messageQueue.push("You graduated from college with a bachelor's degree!");
      applyGpaGraduationReward(player, gpa100, 'college');
    }
  }
}

/**
 * GPA-scaled graduation payoff (T10 — make GPA actually matter). The weekly GPA
 * grind (handleEducation, driven by job/study focus) now pays off at graduation:
 *   - honors (GPA >= 85): a real scholarship — money + diamonds + happiness +
 *     prestige.
 *   - solid (60-84): a happiness bump.
 *   - struggled (< 60): minimal — the OPPORTUNITY COST of a low GPA is the
 *     scholarship you didn't earn.
 * GPA is stored 0-100 on the education record (default 50).
 */
function applyGpaGraduationReward(player: Player, gpa100: number, level: 'high school' | 'college'): void {
  const c = player.c as unknown as { money?: number; diamonds?: number; happiness?: number; prestige?: number };
  if (gpa100 >= 85) {
    const money = level === 'college' ? 8000 : 3000;
    const diamonds = level === 'college' ? 30 : 15;
    c.money = (c.money ?? 0) + money;
    c.diamonds = (c.diamonds ?? 0) + diamonds;
    c.happiness = Math.min(100, (c.happiness ?? 50) + 15);
    c.prestige = Math.min(100, (c.prestige ?? 0) + (level === 'college' ? 10 : 5));
    player.messageQueue.push(
      `Graduated with honors! A $${money.toLocaleString()} scholarship and ${diamonds} diamonds reward your hard work.`
    );
  } else if (gpa100 >= 60) {
    c.happiness = Math.min(100, (c.happiness ?? 50) + 8);
  } else {
    c.happiness = Math.min(100, (c.happiness ?? 50) + 3);
    player.messageQueue.push('You scraped through with a low GPA - no honors this time, but you made it.');
  }
}

export function setEducation(player: Player, person: Person): Person {
  const age = person.ageYears;

  // Set education level based on age
  const educationLevel = getEducationLevelForAge(age);
  if (educationLevel) {
    person.education = educationLevel;
  }

  // Assign school based on age
  if (age >= 5 && age < 14) {
    if (!person.activities) person.activities = [];
    if (!person.activityRecords) person.activityRecords = [];

    // Reuse the existing elementary enrollment if there is one (idempotent).
    // Only pick a new random school when the character is not yet enrolled.
    let school = person.activities.find((a: { type?: string }) => a?.type === 'elementary_school');
    if (!school) {
      const elementarySchools = getElementarySchools();
      school = elementarySchools[Math.floor(Math.random() * elementarySchools.length)];
      person.activities.push(school);
    }
    person.elementary_school = school;

    // Ensure exactly one education record for this enrollment. Records link to
    // activities by locationId (legacy records may have id !== locationId).
    let record = person.activityRecords.find(
      (r: { id?: string; locationId?: string }) => r?.locationId === school.id || r?.id === school.id
    );
    if (!record) {
      record = {
        id: school.id,
        educationLevel: person.education || 'elementary',
        locationId: school.id,
        locationType: school.type,
        date: player.date,
        gpa: 50,
        focus: 'Balanced',
      };
      person.activityRecords.push(record);
    }
    person.current_education = record;
    person.occupation = 'student';

    // Check start_school achievement (first day of school)
    if (person.id === player.c.id) {
      checkAchievementsAsync(player.userId, 'start_school', {}, {}, {}, player).catch((err) =>
        console.error('Error checking start_school achievement:', err)
      );
    }
  } else if (age >= 14 && age < 18) {
    if (!person.activities) person.activities = [];
    if (!person.activityRecords) person.activityRecords = [];

    // Reuse the existing high-school enrollment if there is one (idempotent).
    let school = person.activities.find((a: { type?: string }) => a?.type === 'high_school');
    if (!school) {
      const highSchools = getHighSchools();
      school = highSchools[Math.floor(Math.random() * highSchools.length)];
      person.activities.push(school);
    }
    person.high_school = school;

    let record = person.activityRecords.find(
      (r: { id?: string; locationId?: string }) => r?.locationId === school.id || r?.id === school.id
    );
    if (!record) {
      record = {
        id: school.id,
        educationLevel: person.education || 'high_school',
        locationId: school.id,
        locationType: school.type,
        date: player.date,
        gpa: 50,
        focus: 'Balanced',
      };
      person.activityRecords.push(record);
    }
    person.current_education = record;
    person.occupation = 'student';
  } else if (age >= 18) {
    if (!person.activities) person.activities = [];
    if (!person.activityRecords) person.activityRecords = [];

    // Reuse the existing college enrollment if there is one (idempotent): an
    // already-enrolled student stays enrolled rather than re-rolling each call.
    const existingCollege = person.activities.find((a: { type?: string }) => a?.type === 'college');
    if (existingCollege) {
      person.occupation = 'student';
      person.college = existingCollege;

      let record = person.activityRecords.find(
        (r: { id?: string; locationId?: string }) =>
          r?.locationId === existingCollege.id || r?.id === existingCollege.id
      );
      if (!record) {
        record = {
          id: existingCollege.id,
          educationLevel: person.education || 'college',
          locationId: existingCollege.id,
          locationType: existingCollege.type,
          date: player.date,
          gpa: 50,
          focus: 'Balanced',
        };
        person.activityRecords.push(record);
      }
      person.current_education = record;
    } else {
      // Always enroll a newly created adult in college. Test characters are
      // created at 18+, so they should start in school; production characters
      // start younger and age in. (Characters created at an older age will need
      // their earlier life history backfilled separately.)
      const collegeRatio = Math.random();
      person.occupation = 'student';

      if (age === 18) {
        person.education = 'college yr 1';
      } else if (age >= 19 && collegeRatio < 0.65) {
        person.education = 'associate_degree';
      } else if (age === 20) {
        person.education = 'college yr 3';
      } else if (age >= 21 && collegeRatio < 0.8) {
        person.education = 'bachelors_degree';
      } else if (age >= 21) {
        person.education = 'doctorate_degree';
      } else {
        person.education = person.education || 'college';
      }

      const colleges = getColleges();
      const college = colleges[Math.floor(Math.random() * colleges.length)];
      person.college = college;
      person.activities.push(college);

      const record: EducationRecord = {
        id: college.id,
        educationLevel: person.education || 'college',
        locationId: college.id,
        locationType: college.type,
        date: player.date,
        gpa: 50,
        focus: 'Balanced',
      };
      person.activityRecords.push(record);
      person.current_education = record;
    }
  }

  return person;
}

// ============================================================================
// Utility Functions
// ============================================================================

export function getRandomElementarySchool(): ElementarySchool {
  const schools = getElementarySchools();
  return schools[Math.floor(Math.random() * schools.length)];
}

export function getRandomHighSchool(): HighSchool {
  const schools = getHighSchools();
  return schools[Math.floor(Math.random() * schools.length)];
}

export function getRandomCollege(): College {
  const colleges = getColleges();
  return colleges[Math.floor(Math.random() * colleges.length)];
}

export function getRandomMajor(): CollegeMajor {
  const majors = getMajors();
  return majors[Math.floor(Math.random() * majors.length)];
}

export function getMajorByTitle(title: string): CollegeMajor | undefined {
  return getMajors().find((m) => m.title === title);
}

export function getCollegeById(id: string): College | undefined {
  return getColleges().find((c) => c.id === id);
}

export function getSchoolById(id: string): School | undefined {
  const elementary = getElementarySchools().find((s) => s.id === id);
  if (elementary) return elementary;

  const high = getHighSchools().find((s) => s.id === id);
  if (high) return high;

  return getColleges().find((c) => c.id === id);
}

// ============================================================================
// Migration: Collapse duplicate school enrollments
// ============================================================================

const SCHOOL_ACTIVITY_TYPES = new Set(['elementary_school', 'high_school', 'college']);

function schoolObjectField(
  type: string
): 'elementary_school' | 'high_school' | 'college' | null {
  if (type === 'elementary_school') return 'elementary_school';
  if (type === 'high_school') return 'high_school';
  if (type === 'college') return 'college';
  return null;
}

/**
 * Collapses duplicate school enrollments down to a single current enrollment per
 * school type, and removes stale/orphaned education records.
 *
 * A character should only be enrolled in ONE school of a given level at a time.
 * A historical bug had setEducation() push a new random school + record on every
 * load, leaving some players with many school activities + education records.
 *
 * For each school type present, the canonical enrollment to keep is chosen in
 * priority order: the school referenced by current_education, then the
 * elementary_school/high_school/college object, then the most recent activity.
 * Every other school activity of that type is dropped, along with any education
 * record whose locationId no longer points at a surviving school activity. One
 * education record is kept per surviving school (preferring the current_education
 * record). Non-school activities (extracurriculars) and their records are left
 * untouched. Dangling current_education / school-object references are repointed
 * to the surviving enrollment.
 *
 * @returns true if any duplicate/orphan data was removed or a reference repointed.
 */
export function dedupeSchoolEnrollments(person: Person): boolean {
  const activities = Array.isArray(person.activities) ? person.activities : [];
  const records = Array.isArray(person.activityRecords) ? person.activityRecords : [];
  const currentEducation = person.current_education as
    | { id?: string; locationId?: string; locationType?: string }
    | undefined;

  // 1) Choose the canonical school id to keep for each school type present.
  const keepIdByType = new Map<string, string>();
  for (const type of SCHOOL_ACTIVITY_TYPES) {
    const ofType = activities.filter(
      (a: { id?: string; type?: string }) => a?.type === type && a?.id
    ) as Array<{ id: string; type?: string }>;
    if (ofType.length === 0) continue;

    const ids = new Set(ofType.map((a) => a.id));
    let keepId: string | undefined;

    if (currentEducation && currentEducation.locationType === type) {
      const target = currentEducation.locationId ?? currentEducation.id;
      if (target && ids.has(target)) keepId = target;
    }
    if (!keepId) {
      const field = schoolObjectField(type);
      const obj = field ? (person[field] as { id?: string } | undefined) : undefined;
      if (obj?.id && ids.has(obj.id)) keepId = obj.id;
    }
    if (!keepId) keepId = ofType[ofType.length - 1].id;

    keepIdByType.set(type, keepId);
  }
  const keptSchoolIds = new Set(keepIdByType.values());

  // 2) Rebuild activities: keep one school per type plus all non-school
  //    activities, de-duplicated by id and preserving order.
  const seenActivityIds = new Set<string>();
  const newActivities: unknown[] = [];
  for (const activity of activities) {
    const a = activity as { id?: string; type?: string };
    if (!a || !a.id) continue;
    if (seenActivityIds.has(a.id)) continue;
    if (a.type && SCHOOL_ACTIVITY_TYPES.has(a.type) && a.id !== keepIdByType.get(a.type)) {
      continue;
    }
    seenActivityIds.add(a.id);
    newActivities.push(activity);
  }

  // 3) Choose one education record per surviving school (prefer the record that
  //    current_education points at), then rebuild records preserving order.
  const chosenRecordForSchool = new Map<string, unknown>();
  for (const schoolId of keptSchoolIds) {
    const candidates = records.filter((r: { id?: string; locationId?: string }) => {
      const target = r?.locationId ?? r?.id;
      return target === schoolId;
    });
    if (candidates.length === 0) continue;
    const preferred =
      (currentEducation &&
        candidates.find((r: { id?: string }) => r.id === currentEducation.id)) ||
      candidates[0];
    chosenRecordForSchool.set(schoolId, preferred);
  }
  const keptRecords = new Set(chosenRecordForSchool.values());

  const seenRecordIds = new Set<string>();
  const newRecords: unknown[] = [];
  for (const record of records) {
    const r = record as { id?: string; locationId?: string; locationType?: string };
    if (!r) continue;
    const isEducationRecord =
      (r.locationType != null && SCHOOL_ACTIVITY_TYPES.has(r.locationType)) ||
      r.locationId != null;
    if (isEducationRecord) {
      if (!keptRecords.has(record)) continue;
    } else {
      if (r.id && seenRecordIds.has(r.id)) continue;
      if (r.id) seenRecordIds.add(r.id);
    }
    newRecords.push(record);
  }

  // 4) Repoint dangling references to the surviving enrollment.
  let refsChanged = false;
  for (const [type, keepId] of keepIdByType) {
    const field = schoolObjectField(type);
    if (!field) continue;
    const obj = person[field] as { id?: string } | undefined;
    if (obj && obj.id !== keepId) {
      const keptActivity = newActivities.find((a) => (a as { id?: string }).id === keepId);
      if (keptActivity) {
        person[field] = keptActivity;
        refsChanged = true;
      }
    }
  }
  if (currentEducation) {
    const ceTarget = currentEducation.locationId ?? currentEducation.id;
    if (!ceTarget || !keptSchoolIds.has(ceTarget)) {
      const sameTypeKeepId = currentEducation.locationType
        ? keepIdByType.get(currentEducation.locationType)
        : undefined;
      const replacement = sameTypeKeepId
        ? chosenRecordForSchool.get(sameTypeKeepId)
        : undefined;
      const fallback = newRecords.find((r) => {
        const rr = r as { locationType?: string; locationId?: string };
        return (
          (rr.locationType != null && SCHOOL_ACTIVITY_TYPES.has(rr.locationType)) ||
          rr.locationId != null
        );
      });
      const next = replacement ?? fallback ?? null;
      if (next !== person.current_education) {
        person.current_education = next;
        refsChanged = true;
      }
    }
  }

  const changed =
    newActivities.length !== activities.length ||
    newRecords.length !== records.length ||
    refsChanged;

  if (changed) {
    person.activities = newActivities;
    person.activityRecords = newRecords;
    console.log(
      `[Education Migration] Collapsed school enrollments for ${person.firstname}: ` +
        `${activities.length} -> ${newActivities.length} activities, ` +
        `${records.length} -> ${newRecords.length} records`
    );
  }

  return changed;
}

// ============================================================================
// Migration: Ensure school-age characters have proper education
// ============================================================================

/**
 * Ensures a school-age character (5-17) has proper education set up.
 * This is used for migrating existing players who may be missing education data.
 *
 * @param player - The player object
 * @param person - The person to check/fix education for
 * @returns true if migration was needed and applied, false if already set up
 */
export function ensureEducationSetup(player: Player, person: Person): boolean {
  const age = person.ageYears ?? 0;

  // Collapse duplicate / orphaned school enrollments for any age.
  let migrationNeeded = dedupeSchoolEnrollments(person);

  // The remaining setup only applies to school-age characters (5-17).
  if (age < 5 || age >= 18) {
    return migrationNeeded;
  }

  // Check if character already has a school in activities
  const hasSchoolActivity = (person.activities ?? []).some(
    (activity: { type?: string }) =>
      activity?.type === 'elementary_school' || activity?.type === 'high_school'
  );

  // Also check if they have school objects set
  const hasSchoolObject =
    (age < 14 && person.elementary_school) ||
    (age >= 14 && person.high_school);

  // If missing school setup, apply education
  if (!hasSchoolActivity || !hasSchoolObject || person.occupation !== 'student') {
    console.log(
      `[Education Migration] Setting up education for ${person.firstname} (age ${age})`
    );
    setEducation(player, person);
    migrationNeeded = true;
  }

  // Check if classmates exist (should have at least one 'classmate' relationship)
  const hasClassmates = (player.r ?? []).some(
    (rel: { relationships?: string[] }) =>
      rel?.relationships?.includes('classmate')
  );

  if (!hasClassmates) {
    console.log(
      `[Education Migration] Creating classmates for ${person.firstname}`
    );
    createClassmates(player);
    migrationNeeded = true;
  }

  return migrationNeeded;
}

// ============================================================================
// Cache Clear (for testing)
// ============================================================================

export function clearEducationCaches(): void {
  cachedElementarySchools = null;
  cachedHighSchools = null;
  cachedColleges = null;
  cachedMajors = null;
  cachedExtracurriculars = null;
}

// ============================================================================
// Export
// ============================================================================

export const educationManager = {
  // Factories
  createElementarySchool,
  createHighSchool,
  createCollege,
  // Schools
  getElementarySchools,
  getHighSchools,
  getColleges,
  getMajors,
  getRandomElementarySchool,
  getRandomHighSchool,
  getRandomCollege,
  getRandomMajor,
  getSchoolById,
  getCollegeById,
  getMajorByTitle,
  // Focus
  getFocuses,
  getFocus,
  getRandomFocus,
  updateFocus,
  // Extracurriculars
  getExtracurriculars,
  getRandomExtracurricular,
  getExtracurricularByTitle,
  setExtracurricular,
  applyForExtracurricular,
  quitExtracurricular,
  // Education
  getEducationLevelForAge,
  handleEducation,
  setEducation,
  // Migration
  ensureEducationSetup,
  dedupeSchoolEnrollments,
  // Cache
  clearEducationCaches,
};
