/**
 * Job Management Module for BaoLife
 * Handles jobs, occupations, career progression, and performance tracking.
 * Ported from Python jobs/job_manager.py
 */

import { v4 as uuidv4 } from 'uuid';
import { Player } from '../../models/Player.js';
import { Person } from '../../models/Person.js';
import { createMessageEvent, MessageEventData } from '../../events/base.js';
import { createCoworkers } from '../character/character_manager.js';
import {
  checkAchievementsAsync,
  trackJobObtained,
  trackFired,
  getPlayerStatistics,
} from '../retention/index.js';

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

export interface JobLevel {
  id: string;
  level: string;
  salary: number;
  energy_modifier: number;  // Python uses snake_case
  /**
   * Minimum character prestige required to be promoted into this level. The
   * top rungs of high-status careers (CTO, Managing Partner, Chief Medical
   * Officer, etc.) are gated so prestige (earned via lifestyle items / family
   * legacy) becomes a real promotion currency rather than purely cosmetic.
   * Undefined/0 = no prestige requirement. Enforced in handleJob().
   */
  minPrestige?: number;
}

/**
 * Prestige thresholds applied to the top career tiers. The final level of a
 * "prestige" occupation requires PRESTIGE_GATE_TOP; the second-from-top
 * requires PRESTIGE_GATE_SENIOR. These are deliberately reachable through the
 * shop's prestige items + family legacy so prestige has a mechanical payoff.
 */
export const PRESTIGE_GATE_SENIOR = 100;
export const PRESTIGE_GATE_TOP = 300;

/**
 * Intelligence thresholds that make raised intelligence a real promotion lever
 * (T007). Before this, promotion performance was random + focus-only and read no
 * stat, so the intelligence trajectory Wave 1 builds via `performActivity` was
 * write-only. Two mechanisms (both enforced in handleJob):
 *
 *  1. PERF BONUS — at/above INTELLIGENCE_PERF_BONUS_THRESHOLD the per-tick
 *     performance roll gains +1 to BOTH ends of its range, biasing the character
 *     toward the >90 promotion ceiling (faster, more reliable climbing). This is
 *     a BIAS, never a guarantee: a slacking low performer can still stall/fall.
 *  2. TOP-TIER FLOOR — the single highest rung of a prestige-gated elite career
 *     (already behind PRESTIGE_GATE_TOP) additionally requires
 *     INTELLIGENCE_GATE_TOP. This gates ONLY the very top rung of three elite
 *     ladders, so it can never block a life MILESTONE (graduation, marriage,
 *     children) nor make any ordinary career unreachable — the character holds
 *     just below the ceiling (like the prestige gate) until intelligence is high
 *     enough, and a one-time message explains the requirement.
 */
export const INTELLIGENCE_PERF_BONUS_THRESHOLD = 60;
export const INTELLIGENCE_GATE_TOP = 75;
/**
 * Performance floor for any worker NOT deliberately slacking. Keeps a Balanced /
 * Work Hard worker safely above the <10 termination threshold so they can't be
 * fired by an unlucky neutral random walk — only a chosen 'Slack Off' risks it.
 */
export const BALANCED_PERF_FLOOR = 35;

/**
 * Occupation titles whose upper tiers are prestige-gated. Picked from the
 * highest-status / highest-salary careers so the gate reads as "elite ladder".
 */
const PRESTIGE_GATED_OCCUPATIONS = new Set<string>([
  'Software Engineer',
  'Lawyer',
  'Physician',
]);

/**
 * Apply prestige gates to the top two rungs of the prestige-gated occupations.
 * Mutates the level array in place (called once during catalog construction).
 */
function applyPrestigeGates(title: string, levels: JobLevel[]): JobLevel[] {
  if (!PRESTIGE_GATED_OCCUPATIONS.has(title) || levels.length < 2) {
    return levels;
  }
  levels[levels.length - 1].minPrestige = PRESTIGE_GATE_TOP;
  levels[levels.length - 2].minPrestige = PRESTIGE_GATE_SENIOR;
  return levels;
}

export interface Occupation {
  id: string;
  type: 'job';
  title: string;
  description: string;
  shifts: string;
  hourType: 'full-time' | 'part-time';
  requirements: 'none' | 'high_school' | 'bachelors_degree' | 'doctorate_degree';
  levels: JobLevel[];
  image: string | null;
  people: string[];
}

export interface JobRecord {
  id: string;
  type: 'job';
  date: string;
  level: JobLevel;
  performance: number;
  focus: string;
}

export interface JobApplyResult {
  success: boolean;
  message: string;
  job?: Occupation;
}

export interface JobQuitResult {
  success: boolean;
  message: string;
}

// ============================================================================
// Factories
// ============================================================================

export function createJobLevel(level: string, salary: number, energy_modifier: number): JobLevel {
  return {
    id: uuidv4().replace(/-/g, ''),
    level,
    salary,
    energy_modifier,
  };
}

export function createOccupation(
  title: string,
  description: string,
  shifts: string,
  requirements: Occupation['requirements'],
  levels: JobLevel[],
  image: string | null = null
): Occupation {
  return {
    id: uuidv4().replace(/-/g, ''),
    type: 'job',
    title,
    description,
    shifts,
    hourType: 'full-time',
    requirements,
    levels: applyPrestigeGates(title, levels),
    image,
    people: [],
  };
}

// ============================================================================
// Occupations Catalog
// ============================================================================

let cachedOccupations: Occupation[] | null = null;

export function getOccupations(): Occupation[] {
  if (cachedOccupations) return cachedOccupations;

  cachedOccupations = [
    createOccupation(
      'Software Engineer',
      'Develop, test, and maintain software applications',
      'Day shift',
      'bachelors_degree',
      [
        createJobLevel('Junior Software Engineer', 2000, 30),
        createJobLevel('Software Engineer', 3000, 40),
        createJobLevel('Senior Software Engineer', 4000, 50),
        createJobLevel('Software Engineering Manager', 6000, 60),
        createJobLevel('CTO', 10000, 70),
      ],
      'https://v3b.fal.media/files/b/0a8c2750/oWF2Foex5fpED6yduf5pD.png'
    ),
    createOccupation(
      'Registered Nurse',
      'Provide patient care and educate patients about health conditions',
      'Rotating shifts',
      'bachelors_degree',
      [
        createJobLevel('Junior Registered Nurse', 1500, 30),
        createJobLevel('Registered Nurse', 2000, 40),
        createJobLevel('Senior Registered Nurse', 2500, 50),
        createJobLevel('Nurse Manager', 3000, 60),
        createJobLevel('Director of Nursing', 5000, 70),
      ],
      'https://v3b.fal.media/files/b/0a8c2751/2onNLshXAsiyOODDqy30a.png'
    ),
    createOccupation(
      'Elementary School Teacher',
      'Teach students in a specific subject area',
      'Day shift',
      'bachelors_degree',
      [
        createJobLevel('Assistant Teacher', 800, 30),
        createJobLevel('Elementary School Teacher', 1100, 40),
        createJobLevel('Senior Elementary School Teacher', 1300, 50),
        createJobLevel('Head Teacher', 1500, 60),
        createJobLevel('Principal', 2000, 70),
      ],
      'https://v3b.fal.media/files/b/0a8c2752/_47tT2mZdq8IUo7e2HRwt.png'
    ),
    createOccupation(
      'Police Officer',
      'Maintain public safety and enforce laws',
      'Rotating shifts',
      'high_school',
      [
        createJobLevel('Police Officer', 1400, 30),
        createJobLevel('Sergeant', 1800, 40),
        createJobLevel('Lieutenant', 2200, 50),
        createJobLevel('Captain', 2600, 60),
        createJobLevel('Chief of Police', 3000, 70),
      ],
      'https://v3b.fal.media/files/b/0a8c2753/7oiL-T9ddsWXOcBGl_BNx.png'
    ),
    createOccupation(
      'Accountant',
      'Prepare and examine financial records',
      'Day shift',
      'bachelors_degree',
      [
        createJobLevel('Junior Accountant', 1200, 30),
        createJobLevel('Accountant', 1600, 40),
        createJobLevel('Senior Accountant', 2000, 50),
        createJobLevel('Accounting Manager', 2400, 60),
        createJobLevel('Chief Financial Officer', 3000, 70),
      ],
      'https://v3b.fal.media/files/b/0a8c2755/3dYSSFLyA55GaVyZ8JoAc.png'
    ),
    createOccupation(
      'Chef',
      'Prepare and cook meals in a restaurant',
      'Variable shifts',
      'high_school',
      [
        createJobLevel('Line Cook', 800, 30),
        createJobLevel('Sous Chef', 1100, 40),
        createJobLevel('Head Chef', 1400, 50),
        createJobLevel('Executive Chef', 1700, 60),
        createJobLevel('Master Chef', 2000, 70),
      ],
      'https://v3b.fal.media/files/b/0a8c2756/cw2q5yDl8ZhhO8aVqiwXn.png'
    ),
    createOccupation(
      'Lawyer',
      'Represent clients in legal proceedings',
      'Variable shifts',
      'doctorate_degree',
      [
        createJobLevel('Associate', 2000, 30),
        createJobLevel('Senior Associate', 2500, 40),
        createJobLevel('Partner', 3000, 50),
        createJobLevel('Senior Partner', 3500, 60),
        createJobLevel('Managing Partner', 4000, 70),
      ],
      'https://v3b.fal.media/files/b/0a8c2757/-jmqcIoglDH3ICI0vC2-X.png'
    ),
    createOccupation(
      'Automotive Mechanic',
      'Repair and maintain vehicles',
      'Day shift',
      'high_school',
      [
        createJobLevel('Apprentice Mechanic', 800, 30),
        createJobLevel('Automotive Mechanic', 1000, 40),
        createJobLevel('Master Mechanic', 1200, 50),
        createJobLevel('Shop Foreman', 1400, 60),
        createJobLevel('Service Manager', 1600, 70),
      ],
      'https://v3b.fal.media/files/b/0a8c2758/o4PXN7ZolHvNOoKYsOK1S.png'
    ),
    createOccupation(
      'Sales Representative',
      'Sell goods or services to businesses or consumers',
      'Variable shifts',
      'high_school',
      [
        createJobLevel('Junior Sales Representative', 1000, 30),
        createJobLevel('Sales Representative', 1300, 40),
        createJobLevel('Senior Sales Representative', 1600, 50),
        createJobLevel('Sales Manager', 1900, 60),
        createJobLevel('Director of Sales', 2200, 70),
      ],
      'https://v3b.fal.media/files/b/0a8c2759/DdFMz2Nel0Z1q4sCyy3-h.png'
    ),
    createOccupation(
      'Architect',
      'Design buildings and oversee their construction',
      'Day shift',
      'bachelors_degree',
      [
        createJobLevel('Junior Architect', 1400, 30),
        createJobLevel('Architect', 1800, 40),
        createJobLevel('Senior Architect', 2200, 50),
        createJobLevel('Principal Architect', 2600, 60),
        createJobLevel('Chief Architect', 3000, 70),
      ],
      'https://v3b.fal.media/files/b/0a8c275b/3r4g_WUReD2thDir69XPF.png'
    ),
    createOccupation(
      'Physician',
      'Examine patients, diagnose illnesses, and provide treatments',
      'Rotating shifts',
      'doctorate_degree',
      [
        createJobLevel('Resident Physician', 2000, 30),
        createJobLevel('Physician', 2600, 40),
        createJobLevel('Senior Physician', 3200, 50),
        createJobLevel('Department Head', 3800, 60),
        createJobLevel('Chief Medical Officer', 4400, 70),
      ],
      'https://v3b.fal.media/files/b/0a8c275c/27CIqvuPbV4S2tig1okzM.png'
    ),
    createOccupation(
      'Dentist',
      "Diagnose and treat issues with patients' teeth",
      'Day shift',
      'doctorate_degree',
      [
        createJobLevel('Junior Dentist', 1800, 30),
        createJobLevel('Dentist', 2400, 40),
        createJobLevel('Senior Dentist', 3000, 50),
        createJobLevel('Head of Dentistry', 3600, 60),
        createJobLevel('Chief Dental Officer', 4200, 70),
      ],
      'https://v3b.fal.media/files/b/0a8c275d/pgIh3QKvoSBw4f6P7MFTh.png'
    ),
    createOccupation(
      'Pharmacist',
      'Provide prescription medications to patients and offer health advice',
      'Variable shifts',
      'doctorate_degree',
      [
        createJobLevel('Junior Pharmacist', 1600, 30),
        createJobLevel('Pharmacist', 2100, 40),
        createJobLevel('Senior Pharmacist', 2600, 50),
        createJobLevel('Pharmacy Manager', 3100, 60),
        createJobLevel('Director of Pharmacy', 3600, 70),
      ],
      'https://v3b.fal.media/files/b/0a8c275e/FoKeWJ3ePp_DtoujgFeoZ.png'
    ),
    createOccupation(
      'Librarian',
      'Help patrons find information and conduct research',
      'Day shift',
      'bachelors_degree',
      [
        createJobLevel('Assistant Librarian', 1000, 30),
        createJobLevel('Librarian', 1400, 40),
        createJobLevel('Senior Librarian', 1800, 50),
        createJobLevel('Library Manager', 2200, 60),
        createJobLevel('Director of Library Services', 2600, 70),
      ],
      'https://v3b.fal.media/files/b/0a8c275f/ji97qgN0R_GsM92exGC7H.png'
    ),
    createOccupation(
      'Journalist',
      'Report news stories for newspapers, magazines, or television',
      'Variable shifts',
      'bachelors_degree',
      [
        createJobLevel('Junior Journalist', 1100, 30),
        createJobLevel('Journalist', 1500, 40),
        createJobLevel('Senior Journalist', 1900, 50),
        createJobLevel('Editor', 2300, 60),
        createJobLevel('Editor in Chief', 2700, 70),
      ],
      'https://v3b.fal.media/files/b/0a8c2760/aYKJzmGbzSm-xAKOl25nv.png'
    ),
    createOccupation(
      'Counselor',
      'Help people manage and overcome mental and emotional issues',
      'Day shift',
      'bachelors_degree',
      [
        createJobLevel('Junior Counselor', 1300, 30),
        createJobLevel('Counselor', 1700, 40),
        createJobLevel('Senior Counselor', 2100, 50),
        createJobLevel('Counseling Services Manager', 2500, 60),
        createJobLevel('Director of Counseling Services', 2900, 70),
      ],
      'https://v3b.fal.media/files/b/0a8c2762/WnHdv9b2el07o6a9_ZSIM.png'
    ),
    createOccupation(
      'Veterinarian',
      'Diagnose, treat, and research diseases and injuries in animals',
      'Rotating shifts',
      'doctorate_degree',
      [
        createJobLevel('Veterinary Intern', 1800, 30),
        createJobLevel('Veterinarian', 2200, 40),
        createJobLevel('Senior Veterinarian', 2600, 50),
        createJobLevel('Veterinary Services Manager', 3000, 60),
        createJobLevel('Director of Veterinary Services', 3400, 70),
      ],
      'https://v3b.fal.media/files/b/0a8c2763/LMlnRQFtfxBoqlWDt_KJT.png'
    ),
    createOccupation(
      'Electrician',
      'Install, maintain, and repair electrical systems and equipment',
      'Day shift',
      'high_school',
      [
        createJobLevel('Apprentice Electrician', 1100, 30),
        createJobLevel('Electrician', 1500, 40),
        createJobLevel('Master Electrician', 1900, 50),
        createJobLevel('Electrical Supervisor', 2300, 60),
        createJobLevel('Electrical Contractor', 2700, 70),
      ],
      'https://v3b.fal.media/files/b/0a8c2764/WjSl3B5wCbmJLrO_WaY69.png'
    ),
    createOccupation(
      'Real Estate Agent',
      'Rent, buy, or sell property for clients',
      'Variable shifts',
      'high_school',
      [
        createJobLevel('Junior Real Estate Agent', 1200, 30),
        createJobLevel('Real Estate Agent', 1600, 40),
        createJobLevel('Senior Real Estate Agent', 2000, 50),
        createJobLevel('Real Estate Broker', 2400, 60),
        createJobLevel('Real Estate Agency Owner', 2800, 70),
      ],
      'https://v3b.fal.media/files/b/0a8c276a/L4FziTfsa4ytgjq6wf-ia.png'
    ),
    createOccupation(
      'Graphic Designer',
      'Create visual concepts to communicate ideas',
      'Day shift',
      'bachelors_degree',
      [
        createJobLevel('Junior Graphic Designer', 1400, 30),
        createJobLevel('Graphic Designer', 1800, 40),
        createJobLevel('Senior Graphic Designer', 2200, 50),
        createJobLevel('Art Director', 2600, 60),
        createJobLevel('Creative Director', 3000, 70),
      ],
      'https://v3b.fal.media/files/b/0a8c2771/NLT5DOmfRa9eWaLfY2QYt.png'
    ),
    createOccupation(
      'Janitor',
      'Perform general cleaning of buildings',
      'Variable shifts',
      'none',
      [
        createJobLevel('Janitorial Assistant', 600, 30),
        createJobLevel('Janitor', 800, 40),
        createJobLevel('Senior Janitor', 1000, 50),
        createJobLevel('Janitorial Supervisor', 1200, 60),
        createJobLevel('Janitorial Manager', 1400, 70),
      ],
      'https://v3b.fal.media/files/b/0a8c2792/t079h3HXAh8OkAsqWjQQo.png'
    ),
    createOccupation(
      'Fast Food Worker',
      'Serve food to customers',
      'Variable shifts',
      'none',
      [
        createJobLevel('Fast Food Associate', 500, 30),
        createJobLevel('Fast Food Worker', 600, 40),
        createJobLevel('Fast Food Shift Leader', 700, 50),
        createJobLevel('Fast Food Manager', 800, 60),
        createJobLevel('Fast Food General Manager', 900, 70),
      ],
      'https://v3b.fal.media/files/b/0a8c2793/2xcdeZ9cZaumbsrCDR31J.png'
    ),
    createOccupation(
      'Retail Worker',
      'Assist customers in a retail store',
      'Variable shifts',
      'none',
      [
        createJobLevel('Sales Associate', 600, 30),
        createJobLevel('Retail Worker', 700, 40),
        createJobLevel('Senior Sales Associate', 800, 50),
        createJobLevel('Retail Manager', 900, 60),
        createJobLevel('Store Owner', 1000, 70),
      ],
      'https://v3b.fal.media/files/b/0a8c2794/rBHMCguVle_ihgysY0Q-J.png'
    ),
    createOccupation(
      'Construction Laborer',
      'Perform physical labor at construction sites',
      'Day shift',
      'none',
      [
        createJobLevel('Junior Construction Laborer', 800, 30),
        createJobLevel('Construction Laborer', 1000, 40),
        createJobLevel('Senior Construction Laborer', 1200, 50),
        createJobLevel('Construction Foreman', 1400, 60),
        createJobLevel('Construction Manager', 1600, 70),
      ],
      'https://v3b.fal.media/files/b/0a8c2796/a7QDq21undUGqqMPDDDaP.png'
    ),
    createOccupation(
      'Truck Driver',
      'Drive a truck or other large vehicle to transport goods',
      'Variable shifts',
      'none',
      [
        createJobLevel('Junior Truck Driver', 900, 30),
        createJobLevel('Truck Driver', 1200, 40),
        createJobLevel('Senior Truck Driver', 1500, 50),
        createJobLevel('Truck Fleet Supervisor', 1800, 60),
        createJobLevel('Truck Fleet Manager', 2100, 70),
      ],
      'https://v3b.fal.media/files/b/0a8c2797/TS7f_-oIYa4Jw_iuhKsuu.png'
    ),
    createOccupation(
      'Farm Worker',
      'Plant, cultivate, and harvest agricultural crops',
      'Day shift',
      'none',
      [
        createJobLevel('Farm Helper', 700, 30),
        createJobLevel('Farm Worker', 900, 40),
        createJobLevel('Senior Farm Worker', 1100, 50),
        createJobLevel('Farm Supervisor', 1300, 60),
        createJobLevel('Farm Manager', 1500, 70),
      ],
      'https://v3b.fal.media/files/b/0a8c2798/su1Ig-xMdUtd_6wx-8E1Q.png'
    ),
    createOccupation(
      'Landscaper',
      'Maintain the appearance of outdoor areas and structures',
      'Day shift',
      'none',
      [
        createJobLevel('Junior Landscaper', 800, 30),
        createJobLevel('Landscaper', 1000, 40),
        createJobLevel('Senior Landscaper', 1200, 50),
        createJobLevel('Landscaping Supervisor', 1400, 60),
        createJobLevel('Landscaping Manager', 1600, 70),
      ],
      'https://v3b.fal.media/files/b/0a8c2799/DIDq13kjc_niWWiucBY4D.png'
    ),
    createOccupation(
      'Home Health Aide',
      'Assist people with disabilities, chronic illness, or cognitive impairment',
      'Rotating shifts',
      'none',
      [
        createJobLevel('Junior Home Health Aide', 700, 30),
        createJobLevel('Home Health Aide', 900, 40),
        createJobLevel('Senior Home Health Aide', 1100, 50),
        createJobLevel('Home Health Care Supervisor', 1300, 60),
        createJobLevel('Home Health Care Manager', 1500, 70),
      ],
      'https://v3b.fal.media/files/b/0a8c279a/3VDq-5A2uUfeie9JRrswo.png'
    ),
    createOccupation(
      'Restaurant Server',
      'Take orders and serve food and beverages at restaurants',
      'Variable shifts',
      'none',
      [
        createJobLevel('Junior Server', 600, 30),
        createJobLevel('Restaurant Server', 800, 40),
        createJobLevel('Senior Server', 1000, 50),
        createJobLevel('Restaurant Shift Manager', 1200, 60),
        createJobLevel('Restaurant Manager', 1400, 70),
      ],
      'https://v3b.fal.media/files/b/0a8c279b/qR05tYcFrStVZgQ_R5lbj.png'
    ),
    createOccupation(
      'Cashier',
      'Handle customer payments in a retail or other business',
      'Variable shifts',
      'none',
      [
        createJobLevel('Junior Cashier', 550, 30),
        createJobLevel('Cashier', 700, 40),
        createJobLevel('Senior Cashier', 850, 50),
        createJobLevel('Shift Supervisor', 1000, 60),
        createJobLevel('Store Manager', 1150, 70),
      ],
      'https://v3b.fal.media/files/b/0a8c279c/dIqPhS7wQJ-73vyFYhqYT.png'
    ),
  ];

  return cachedOccupations;
}

// ============================================================================
// Job Assignment
// ============================================================================

export function setJob(person: Person, occupation: Occupation, date: string): void {
  person.job = occupation;
  person.occupation = 'work';

  // Set salary from the entry-level position
  const entryLevel = occupation.levels[0];
  person.salary = entryLevel.salary;

  if (!person.activities) {
    person.activities = [];
  }
  person.activities.push(occupation);

  // Create job record
  const record: JobRecord = {
    id: occupation.id,
    type: 'job',
    date,
    level: entryLevel,
    performance: 50,
    focus: 'Balanced',
  };

  if (!person.activityRecords) {
    person.activityRecords = [];
  }
  person.activityRecords.push(record);
}

export function randomJob(player: Player, person: Person): Person {
  if (person.ageYears > 22) {
    const occupations = getOccupations();
    const job = occupations[Math.floor(Math.random() * occupations.length)];
    setJob(person, job, player.date);
  }
  return person;
}

// ============================================================================
// Job Performance and Progression
// ============================================================================

export interface JobUpdateResult {
  promoted: boolean;
  fired: boolean;
  newLevel?: JobLevel;
  message?: string;
  event?: MessageEventData;
}

/**
 * Updates job performance and handles promotions/terminations.
 *
 * This function:
 * - Updates performance based on focus level (Work Hard, Balanced, Slack Off)
 * - Handles promotions when performance > 90
 * - Handles terminations when performance < 10
 * - Adds appropriate messages to player's message queue
 *
 * Performance modifiers:
 * - Work Hard: +2
 * - Balanced: 0
 * - Slack Off: -1
 *
 * @param player - The player object (for message queue and date info)
 * @param person - The person whose job performance to update
 * @returns JobUpdateResult with promotion/termination info, or null if no job
 */
export function handleJob(
  player: Player,
  person: Person,
  rng: () => number = Math.random
): JobUpdateResult | null {
  if (!person.activities || !person.activityRecords) {
    return null;
  }

  // Find job activity
  const jobActivity = person.activities.find((a: { type: string }) => a.type === 'job') as Occupation | undefined;
  if (!jobActivity) {
    return null;
  }

  // Find job record
  const recordIndex = person.activityRecords.findIndex(
    (r: { id: string }) => r.id === jobActivity.id
  );
  if (recordIndex === -1) {
    return null;
  }

  const record = person.activityRecords[recordIndex] as JobRecord;

  // Calculate performance modifier based on focus
  // Work Hard: +2, Balanced: 0, Slack Off: -1
  let perfModifier = 0;
  if (record.focus === 'Work Hard') {
    perfModifier = 2;
  } else if (record.focus === 'Slack Off') {
    perfModifier = -1;
  }

  // INTELLIGENCE PERF BONUS (T007): a smart worker performs measurably better.
  // At/above the threshold the roll range shifts up by +1 on BOTH ends, so a
  // high-intelligence character climbs toward the >90 promotion ceiling faster
  // and more reliably than an equally-focused low-intelligence one. It is a bias,
  // not a guarantee — the lower bound of a "Slack Off" smart worker can still be
  // negative. This makes the intelligence trajectory built by performActivity
  // (Wave 1) pay off at the promotion lever.
  const intelligenceBonus =
    (person.intelligence ?? 50) >= INTELLIGENCE_PERF_BONUS_THRESHOLD ? 1 : 0;

  // Random performance update matching Python: random.randint(-1+perf_modifier, 1+perf_modifier)
  // This gives a range from (min: -1+perfModifier) to (max: 1+perfModifier) inclusive,
  // shifted up by the intelligence bonus.
  const min = -1 + perfModifier + intelligenceBonus;
  const max = 1 + perfModifier + intelligenceBonus;
  const perfUpdate = Math.floor(rng() * (max - min + 1)) + min;

  // Log for debugging if this is the player character
  if (person.id === player.c.id) {
    console.log(`updating performance: ${record.performance} + ${perfUpdate}`);
  }

  record.performance += perfUpdate;

  // Clamp performance to 0-100
  if (record.performance > 100) {
    record.performance = 100;
  }
  if (record.performance < 0) {
    record.performance = 0;
  }

  // Fairness floor: a 'Balanced' (neutral, the default) or 'Work Hard' worker
  // should not random-walk into a no-warning firing. Floor their performance well
  // above the <10 termination threshold so only a DELIBERATE 'Slack Off' choice
  // puts the job at real risk. (Shared by both loops via handleJob.)
  if (record.focus !== 'Slack Off' && record.performance < BALANCED_PERF_FLOOR) {
    record.performance = BALANCED_PERF_FLOOR;
  }

  // Check for promotion (performance > 90)
  if (record.performance > 90) {
    const currentLevelIndex = jobActivity.levels.findIndex(
      (l) => l.level === record.level.level
    );

    if (currentLevelIndex >= 0 && currentLevelIndex < jobActivity.levels.length - 1) {
      const newLevel = jobActivity.levels[currentLevelIndex + 1];

      // Prestige gate: elite tiers require a minimum prestige to be promoted
      // into. If the character is performing well but lacks the prestige, hold
      // them just below the promotion threshold (so they don't get fired) and
      // surface a one-time message explaining the requirement.
      const requiredPrestige = newLevel.minPrestige ?? 0;
      if (requiredPrestige > 0 && (person.prestige ?? 0) < requiredPrestige) {
        record.performance = 90; // hold at the ceiling without promoting
        const gateMessageKey = `prestige_gate_${jobActivity.id}_${newLevel.id}`;
        if (!player.events.has(gateMessageKey)) {
          player.events.add(gateMessageKey);
          const gateMessage = `You're ready for ${newLevel.level}, but it requires ${requiredPrestige} prestige (you have ${person.prestige ?? 0}). Build your reputation to advance.`;
          player.messageQueue.push(gateMessage);
        }
        return {
          promoted: false,
          fired: false,
          message: `Promotion to ${newLevel.level} is gated behind ${requiredPrestige} prestige.`,
        };
      }

      // Intelligence floor (T007): the SINGLE top rung of an elite (prestige-
      // gated TOP) ladder also demands a minimum intelligence. This gates only
      // the very top of three elite careers — it never blocks an ordinary career
      // or any life milestone. Like the prestige gate, the character holds just
      // below the promotion ceiling (so they aren't fired) and a one-time message
      // explains the requirement, so raising intelligence is the unlock.
      const isTopTier = requiredPrestige >= PRESTIGE_GATE_TOP;
      if (isTopTier && (person.intelligence ?? 50) < INTELLIGENCE_GATE_TOP) {
        record.performance = 90; // hold at the ceiling without promoting
        const intelGateKey = `intelligence_gate_${jobActivity.id}_${newLevel.id}`;
        if (!player.events.has(intelGateKey)) {
          player.events.add(intelGateKey);
          const intelGateMessage = `You're ready for ${newLevel.level}, but this elite role demands ${INTELLIGENCE_GATE_TOP} intelligence (you have ${Math.round(person.intelligence ?? 50)}). Keep sharpening your mind to reach the top.`;
          player.messageQueue.push(intelGateMessage);
        }
        return {
          promoted: false,
          fired: false,
          message: `Promotion to ${newLevel.level} is gated behind ${INTELLIGENCE_GATE_TOP} intelligence.`,
        };
      }

      record.level = newLevel;
      // Reset performance to the same mid-range baseline a freshly-hired worker
      // starts at (setJob uses 50). Resetting to 0 was a latent bug: once the
      // weekly handleJob tick is actually wired into the live game loop (T005),
      // a promotion that dropped performance to 0 would IMMEDIATELY satisfy the
      // termination check (performance < 10) on the very next weekly tick,
      // firing the worker right after every promotion and collapsing career
      // progression into a promote-then-fire sawtooth. Baseline 50 lets the
      // worker climb the next rung instead of being fired off the ladder.
      record.performance = 50;

      // Update person's salary to new level
      person.salary = newLevel.salary;

      const message = `You have been promoted to the position of ${newLevel.level} at ${jobActivity.title}.`;

      // Create promotion message event with job image
      const promotionEvent = createMessageEvent(
        'promotion',
        message,
        player,
        true,
        {
          title: 'Promotion',
          image: jobActivity.image || '',
        }
      );

      // Track promotion achievement
      checkAchievementsAsync(
        player.userId,
        'promotion',
        { title: newLevel.level },
        {},
        { jobCount: getPlayerStatistics(player.userId).jobCount },
        player
      ).catch((err) => console.error('Error checking promotion achievements:', err));

      return {
        promoted: true,
        fired: false,
        newLevel,
        message,
        event: promotionEvent || undefined,
      };
    }
  }

  // Check for termination (performance < 10)
  if (record.performance < 10) {
    console.log(`${person.firstname} was fired from ${jobActivity.title}`);

    // Remove job from activities
    person.activities = person.activities.filter(
      (a: { id: string }) => a.id !== jobActivity.id
    );

    // Remove job record
    person.activityRecords = person.activityRecords.filter(
      (r: { id: string }) => r.id !== jobActivity.id
    );

    person.job = null;
    person.occupation = 'unemployed';
    person.salary = 0;

    const message = `You have been fired from your job as ${jobActivity.title}.`;

    // Create termination message event
    const terminationEvent = createMessageEvent(
      'termination',
      message,
      player,
      true,
      {
        title: 'Terminated',
        image: jobActivity.image || '',
      }
    );

    // Track fired statistics and check achievements
    trackFired(player.userId);
    const stats = getPlayerStatistics(player.userId);
    checkAchievementsAsync(player.userId, 'fired', {}, {}, stats, player).catch((err) =>
      console.error('Error checking fired achievements:', err)
    );

    return {
      promoted: false,
      fired: true,
      message,
      event: terminationEvent || undefined,
    };
  }

  return {
    promoted: false,
    fired: false,
  };
}

// ============================================================================
// Job Application and Quitting
// ============================================================================

/**
 * Allows the player to apply for a specific job.
 *
 * This function:
 * - Finds the job by ID in available occupations
 * - Checks if player meets education requirements
 * - Sets the job for the player's character
 * - Creates coworkers for the new job
 * - Adds a confirmation message
 *
 * @param player - The player object
 * @param jobId - The unique ID of the job to apply for
 * @returns JobApplyResult with success status and message
 */
export function applyForJob(player: Player, jobId: string): JobApplyResult {
  const occupations = getOccupations();
  const job = occupations.find((o) => o.id === jobId);

  if (!job) {
    return {
      success: false,
      message: 'Job not found',
    };
  }

  // Check education requirements
  const education = player.c.education || '';
  const meetsRequirements = checkEducationRequirement(education, job.requirements);

  if (!meetsRequirements) {
    return {
      success: false,
      message: `You do not meet the education requirements for ${job.title}. Required: ${job.requirements}`,
    };
  }

  // Set the job for the player
  setJob(player.c, job, player.date);

  const message = `You have applied for the position of ${job.title}.`;
  player.messageQueue.push(message);

  // Create coworkers for the new job
  createCoworkers(player, { title: job.title, income: job.levels[0].salary });

  // Track job statistics and check achievements
  trackJobObtained(player.userId);
  const stats = getPlayerStatistics(player.userId);
  // Note: Achievement unlocks are checked, but notifications need to be sent
  // by the handler that has access to the PlayerSession
  checkAchievementsAsync(player.userId, 'get_job', {}, {}, stats, player).catch((err) =>
    console.error('Error checking job achievements:', err)
  );

  return {
    success: true,
    message,
    job,
  };
}

/**
 * Allows the player to quit their current job.
 *
 * This function:
 * - Finds the job by ID in player's activities or occupation catalog
 * - Removes it from activities list
 * - Removes the activity record
 * - Sets occupation to 'unemployed'
 * - Adds a confirmation message
 *
 * @param player - The player object
 * @param jobId - The unique ID of the job to quit
 * @returns JobQuitResult with success status and message
 */
export function quitJob(player: Player, jobId: string): JobQuitResult {
  // First check player's activities for the job
  let jobTitle = '';
  let foundJob = false;

  if (player.c.activities) {
    const jobActivity = player.c.activities.find(
      (a: { id: string; type: string; title?: string }) => a.id === jobId && a.type === 'job'
    ) as Occupation | undefined;

    if (jobActivity) {
      jobTitle = jobActivity.title;
      foundJob = true;

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

  // If not found in activities, check occupations catalog
  if (!foundJob) {
    const occupations = getOccupations();
    const job = occupations.find((o) => o.id === jobId);

    if (job) {
      jobTitle = job.title;
      foundJob = true;

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

  if (!foundJob) {
    return {
      success: false,
      message: 'Job not found',
    };
  }

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

  player.c.job = null;
  player.c.occupation = 'unemployed';
  player.c.salary = 0;

  const message = `You have quit your job as ${jobTitle}.`;
  player.messageQueue.push(message);

  return {
    success: true,
    message,
  };
}

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

function checkEducationRequirement(
  education: string,
  requirement: Occupation['requirements']
): boolean {
  if (requirement === 'none') return true;

  const educationLevels = [
    'none',
    'high_school',
    'associate_degree',
    'bachelors_degree',
    'doctorate_degree',
  ];

  const educationIndex = educationLevels.indexOf(education);
  const requirementIndex = educationLevels.indexOf(requirement);

  // If education is not in list, check if it matches common education strings
  if (educationIndex === -1) {
    if (
      requirement === 'high_school' &&
      (education.includes('high_school') ||
        education.includes('10th') ||
        education.includes('11th') ||
        education.includes('12th'))
    ) {
      return true;
    }
    if (requirement === 'bachelors_degree' && education.includes('bachelor')) {
      return true;
    }
    if (requirement === 'doctorate_degree' && education.includes('doctor')) {
      return true;
    }
    return false;
  }

  return educationIndex >= requirementIndex;
}

export function getOccupationById(id: string): Occupation | undefined {
  return getOccupations().find((o) => o.id === id);
}

export function getOccupationByTitle(title: string): Occupation | undefined {
  return getOccupations().find((o) => o.title === title);
}

export function getOccupationsByRequirement(
  requirement: Occupation['requirements']
): Occupation[] {
  return getOccupations().filter((o) => o.requirements === requirement);
}

export function getRandomOccupation(): Occupation {
  const occupations = getOccupations();
  return occupations[Math.floor(Math.random() * occupations.length)];
}

/**
 * Focus level type for job performance
 */
export type JobFocus = 'Work Hard' | 'Balanced' | 'Slack Off';

/**
 * Set the focus level for a person's job.
 *
 * Focus levels affect weekly performance updates:
 * - Work Hard: +2 modifier (faster promotions, more energy cost)
 * - Balanced: 0 modifier (steady progress)
 * - Slack Off: -1 modifier (slower progress, risk of termination)
 *
 * @param person - The person whose job focus to set
 * @param focus - The focus level to set
 * @returns true if focus was set, false if no job found
 */
export function setJobFocus(person: Person, focus: JobFocus): boolean {
  if (!person.activityRecords) {
    return false;
  }

  // Find job record
  const record = person.activityRecords.find(
    (r: { type: string }) => r.type === 'job'
  ) as JobRecord | undefined;

  if (!record) {
    return false;
  }

  record.focus = focus;
  return true;
}

/**
 * Get the job record for a person.
 *
 * @param person - The person to get job record for
 * @returns The job record or null if not found
 */
export function getJobRecord(person: Person): JobRecord | null {
  if (!person.activityRecords) {
    return null;
  }

  const record = person.activityRecords.find(
    (r: { type: string }) => r.type === 'job'
  ) as JobRecord | undefined;

  return record || null;
}

/**
 * Get the current job occupation for a person.
 *
 * @param person - The person to get job for
 * @returns The occupation or null if not employed
 */
export function getCurrentJob(person: Person): Occupation | null {
  if (!person.activities) {
    return null;
  }

  const job = person.activities.find(
    (a: { type: string }) => a.type === 'job'
  ) as Occupation | undefined;

  return job || null;
}

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

export function clearJobCaches(): void {
  cachedOccupations = null;
}

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

export const jobManager = {
  // Factories
  createJobLevel,
  createOccupation,
  // Occupations
  getOccupations,
  getOccupationById,
  getOccupationByTitle,
  getOccupationsByRequirement,
  getRandomOccupation,
  // Job Assignment
  setJob,
  randomJob,
  // Job Performance
  handleJob,
  setJobFocus,
  getJobRecord,
  getCurrentJob,
  // Job Application/Quitting
  applyForJob,
  quitJob,
  // Cache
  clearJobCaches,
};
