/**
 * Base Event Classes and Helper Functions
 * Ported from Python events/base.py
 *
 * Contains:
 * - Event class definitions (QuestionEvent, MessageEvent, TimeEvent, DilemmaClass, AnswerOption)
 * - Helper functions (createMessageEvent, createQuestionEvent)
 */

import { config } from '../config.js';
import type { Player } from '../models/Player.js';

/**
 * Event mode determines when an event can fire based on game speed.
 * - 'realtime': Only fires at slow speeds (gameSpeed >= 1000)
 * - 'fast': Only fires at fast speeds (gameSpeed < 1000)
 * - 'both': Fires at any speed (default)
 */
export type EventMode = 'realtime' | 'fast' | 'both';

/** Speed threshold: gameSpeed >= this value is considered "realtime" */
export const REALTIME_SPEED_THRESHOLD = 1000;

/**
 * Check if an event should be skipped based on its mode and current game speed.
 * Returns true if the event should NOT fire.
 */
export function shouldSkipForMode(mode: EventMode | undefined, gameSpeed: number): boolean {
  const isRealtimeSpeed = gameSpeed >= REALTIME_SPEED_THRESHOLD;
  const effectiveMode = mode ?? 'both';
  if (effectiveMode === 'realtime' && !isRealtimeSpeed) return true;
  if (effectiveMode === 'fast' && isRealtimeSpeed) return true;
  return false;
}

/**
 * Simplified character data for event payloads
 */
export interface SimpleCharacter {
  id: string;
  firstname: string;
  lastname: string;
  image: string;
}

/**
 * Answer option for question events
 * All fields except 'option' are optional for convenience
 */
export interface AnswerOption {
  option: string;
  data?: string;
  id?: string;
  energyCost?: number;
  diamondCost?: number;
  moneyCost?: number;
}

/**
 * Event configuration for class-based events
 */
export interface EventConfig {
  minAge: number;
  maxAge: number;
  baseChance: number;
  triggerType: 'random' | 'scheduled' | 'conditional';
}

/**
 * Base class for all game events
 * Provides common structure for activity, tutorial, and dilemma events
 */
export abstract class BaseEvent {
  abstract readonly id: string;

  /**
   * Event mode: controls when this event can fire based on game speed.
   * Override in subclasses to restrict to 'realtime' or 'fast'.
   */
  get mode(): EventMode {
    return 'both';
  }

  /**
   * Get event configuration (age range, trigger type, probability)
   */
  abstract getConfig(): EventConfig;

  /**
   * Check if event conditions are met for the player
   */
  abstract checkConditions(player: Player): boolean;

  /**
   * Get the question text to display
   */
  abstract getQuestion(player?: Player): string;

  /**
   * Get available answer options
   */
  abstract getAnswerOptions(): AnswerOption[];

  /**
   * Process the player's answer and return the result
   */
  abstract processAnswer(player: Player, selectedOption: number): EventResult;

  /**
   * Check if this event should trigger based on probability
   */
  shouldTrigger(): boolean {
    const config = this.getConfig();
    return Math.random() < config.baseChance;
  }

  /**
   * Check if player is within age range for this event
   */
  isAgeValid(player: Player): boolean {
    const config = this.getConfig();
    const age = player.c.ageYears;
    return age >= config.minAge && age <= config.maxAge;
  }
}

/**
 * Create an answer option
 */
export function createAnswerOption(
  option: string,
  data: string = '',
  energyCost: number = 0,
  diamondCost: number = 0,
  moneyCost: number = 0
): AnswerOption {
  return {
    option,
    data,
    id: data + option,
    energyCost,
    diamondCost,
    moneyCost,
  };
}

/**
 * Question event requiring player choice
 * @deprecated Active runtime uses `events/v2` envelopes.
 */
export interface QuestionEvent {
  id: string;
  type: 'questionEvent';
  message: string;
  objectId?: string;
  answers: AnswerOption[];
  characters: SimpleCharacter[];
  image: string;
}

/**
 * Message event (notification)
 */
export interface MessageEventData {
  id: string;
  type: 'messageEvent';
  date: string;
  hour: number;
  message: string;
  title: string;
  image: string;
  energyCost: number;
  diamondCost: number;
  moneyCost: number;
  affinityChange: number;
  characters: SimpleCharacter[];
}

/**
 * Time-based event
 */
export interface TimeEvent {
  type: 'timeEvent';
}

/**
 * Dilemma with multiple steps
 * @deprecated Legacy v1 dilemma shape retained for compatibility modules only.
 */
export interface DilemmaClass {
  type: 'dilemma';
  function: string;
  answer: AnswerOption | null;
  answerOptions: AnswerOption[];
  step: number;
}

/**
 * Create a dilemma
 * @deprecated Legacy v1 helper retained for compatibility modules only.
 */
export function createDilemma(
  fname: string,
  answerOptions: AnswerOption[]
): DilemmaClass {
  return {
    type: 'dilemma',
    function: fname,
    answer: null,
    answerOptions,
    step: 2,
  };
}

/**
 * Character interface for serialization
 */
interface CharacterLike {
  id: string;
  firstname: string;
  lastname: string;
  image: string;
}

/**
 * Serialize characters to simplified format
 */
function serializeCharacters(characters?: CharacterLike[]): SimpleCharacter[] {
  if (!characters) return [];
  return characters.map((char) => ({
    id: char.id,
    firstname: char.firstname,
    lastname: char.lastname,
    image: char.image,
  }));
}

/**
 * Create a message event
 * Ported from Python messageFunction
 */
export function createMessageEvent(
  fname: string,
  message: string,
  player: Player,
  check: boolean,
  options: {
    title?: string;
    image?: string;
    energyCost?: number;
    diamondCost?: number;
    moneyCost?: number;
    affinityChange?: number;
    characters?: CharacterLike[];
  } = {}
): MessageEventData | null {
  if (!check) return null;

  const {
    title = '',
    image = '',
    energyCost = 0,
    diamondCost = 0,
    moneyCost = 0,
    affinityChange = 0,
    characters,
  } = options;

  return {
    id: fname,
    type: 'messageEvent',
    date: player.date,
    hour: player.hourOfDay,
    message,
    title,
    image,
    energyCost,
    diamondCost,
    moneyCost,
    affinityChange,
    characters: serializeCharacters(characters),
  };
}

/**
 * Create a question event
 * Ported from Python questionFunction
 */
export function createQuestionEvent(
  fname: string,
  message: string | { message: string; id: string },
  player: Player,
  check: boolean,
  options: {
    answerOptions?: AnswerOption[] | string[];
    characters?: CharacterLike[];
    image?: string;
  } = {}
): QuestionEvent | null {
  if (!check) return null;

  const { answerOptions, characters, image = '' } = options;

  // Pause game for question
  player.previousGameSpeed = player.gameSpeed;
  player.gameSpeed = config.SPEED_QUESTION_PAUSE;

  if (process.env.NODE_ENV !== 'test') {
    console.log(`question! ${fname}`);
  }

  const event: QuestionEvent = {
    id: fname,
    type: 'questionEvent',
    message: typeof message === 'object' ? message.message : message,
    answers: [createAnswerOption('Yes'), createAnswerOption('No')],
    characters: serializeCharacters(characters),
    image,
  };

  // Set objectId if message is an object
  if (typeof message === 'object') {
    event.objectId = message.id;
  }

  // Handle answer options
  if (answerOptions) {
    if (typeof answerOptions[0] === 'string') {
      // Convert string array to AnswerOption array
      event.answers = (answerOptions as string[]).map((option, i) =>
        createAnswerOption(option, String(i))
      );
    } else {
      event.answers = answerOptions as AnswerOption[];
    }
  }

  return event;
}

/**
 * Simple activity event result - minimal fields for activity events
 * Can be converted to full MessageEventData when needed
 */
export interface ActivityEventResult {
  type: 'messageEvent';
  message: string;
  title?: string;
  image?: string;
  energyCost?: number;
  diamondCost?: number;
  moneyCost?: number;
  affinityChange?: number;
}

/**
 * Event result type - what event functions return
 */
export type EventResult = MessageEventData | QuestionEvent | ActivityEventResult | null;

/**
 * Event function signature
 */
export type EventFunction = (
  player: Player,
  type?: 'message' | 'question'
) => EventResult;

/**
 * Check probability - replaces Python's `1 >= random.random() * X` pattern
 */
export function checkProbability(oneInX: number): boolean {
  return Math.random() < 1 / oneInX;
}

/**
 * Safely modify a stat value, clamping to valid range (0-100)
 * @param currentValue - Current stat value
 * @param delta - Amount to add (positive) or subtract (negative)
 * @param min - Minimum allowed value (default 0)
 * @param max - Maximum allowed value (default 100)
 * @returns Clamped value
 */
export function modifyStat(currentValue: number | undefined, delta: number, min = 0, max = 100): number {
  const current = currentValue ?? 50;
  return Math.max(min, Math.min(max, current + delta));
}

/**
 * Check milestone probability with increasing chance over time.
 *
 * Probability increases as the age window closes, guaranteeing the event
 * triggers before maxAge. Uses a "time remaining" formula:
 * - Early in window: low probability (natural variation)
 * - As window closes: probability automatically increases
 * - Near end: mathematically guaranteed to trigger
 *
 * @param currentAge - Current age (days or years, must match min/max units)
 * @param minAge - Minimum age for event to trigger
 * @param maxAge - Maximum age for event (deadline)
 * @param unit - 'days' or 'years' (affects hours calculation)
 * @returns true if event should trigger
 *
 * @example
 * // For learnedWalk (270-720 days)
 * checkMilestoneProbability(300, 270, 720, 'days')  // ~0.02% per hour
 * checkMilestoneProbability(700, 270, 720, 'days')  // ~0.2% per hour
 * checkMilestoneProbability(719, 270, 720, 'days')  // ~4% per hour
 */
export function checkMilestoneProbability(
  currentAge: number,
  minAge: number,
  maxAge: number,
  unit: 'days' | 'years' = 'years'
): boolean {
  // Not yet in window
  if (currentAge < minAge) return false;

  // Past window (shouldn't happen if event is marked as triggered)
  if (currentAge > maxAge) return false;

  // Calculate hours remaining until maxAge
  const ageRemaining = maxAge - currentAge;
  const hoursPerUnit = unit === 'days' ? 24 : 24 * 365;
  const hoursRemaining = ageRemaining * hoursPerUnit;

  // Probability = 1 / hoursRemaining
  // As deadline approaches, probability increases toward 100%
  // Add minimum of 1 hour to avoid division by zero
  const probability = 1 / Math.max(hoursRemaining, 1);

  return Math.random() < probability;
}
