/**
 * Tutorial System
 * Manages tutorial progress, tooltips, and onboarding flow.
 * Ported from Python retention/tutorial.py
 *
 * Features:
 * - Tutorial step progression with database persistence
 * - Tooltip tracking to prevent showing tooltips multiple times
 * - Onboarding completion with achievement rewards
 * - Tutorial mode detection based on game hours played
 */

import { Player } from '../../models/Player.js';
import { awardDiamonds } from '../../monetization/diamondEconomy.js';
import { query, queryOne, execute, getConnection } from '../../database/index.js';
import { RowDataPacket, PoolConnection } from 'mysql2/promise';
import { checkAndUnlock } from './achievements.js';

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

export interface TutorialState {
  step: number;
  isComplete: boolean;
  onboardingComplete: boolean;
  tooltipsShown: Set<string>;
  tooltipsSeen: Record<string, boolean>;  // For JSON serialization
  skipTutorial: boolean;
  startedAt: Date;
  completedAt: Date | null;
  firstSessionDate: Date | null;
}

export interface TooltipConfig {
  id: string;
  title: string;
  message: string;
  targetElement: string;
  position: 'top' | 'bottom' | 'left' | 'right';
  arrowDirection: 'up' | 'down' | 'left' | 'right';
  order: number;
}

export interface TutorialMessage {
  type: string;
  step?: number;
  message?: string;
  isComplete?: boolean;
  reward?: number;
  error?: string;
  [key: string]: unknown;
}

/**
 * Database row interface for player_tutorial_progress table
 */
interface TutorialProgressRow extends RowDataPacket {
  player_id: number;
  tutorial_step: number;
  onboarding_complete: number;
  skip_tutorial: number;
  tooltips_seen: string;
  first_session_date: Date | null;
  onboarding_completed_date: Date | null;
}

/**
 * Result types for database operations
 */
export interface TutorialOperationResult {
  success: boolean;
  error?: string;
  tutorialStep?: number;
  onboardingComplete?: boolean;
  newStep?: number;
  rewardDiamonds?: number;
}

// Regex pattern for validating tooltip IDs (alphanumeric, underscore, hyphen only)
const TOOLTIP_ID_PATTERN = /^[a-zA-Z0-9_-]+$/;

// ============================================================================
// Tutorial Steps
// ============================================================================

export const TUTORIAL_STEPS = [
  {
    step: 1,
    title: 'Welcome to BaoLife!',
    message: 'This is your life simulator. Let\'s learn the basics!',
    targetElement: 'home_screen',
  },
  {
    step: 2,
    title: 'Your Character',
    message: 'This is your character. Watch their stats and make choices that affect their life.',
    targetElement: 'avatar_card',
  },
  {
    step: 3,
    title: 'Resources',
    message: 'Keep an eye on your energy, money, and diamonds. You\'ll need them!',
    targetElement: 'resource_bar',
  },
  {
    step: 4,
    title: 'Life Events',
    message: 'Events will appear here. Tap to claim rewards from positive events!',
    targetElement: 'life_timeline',
  },
  {
    step: 5,
    title: 'Activities',
    message: 'Visit the Activities tab to enroll in school, get a job, or pick up hobbies.',
    targetElement: 'activities_tab',
  },
  {
    step: 6,
    title: 'Dating',
    message: 'Ready to find love? Swipe through potential matches in the Dating tab.',
    targetElement: 'dating_tab',
  },
  {
    step: 7,
    title: 'Game Speed',
    message: 'Control how fast time passes with these speed controls.',
    targetElement: 'speed_controls',
  },
  {
    step: 8,
    title: 'You\'re Ready!',
    message: 'That\'s the basics! Enjoy living your virtual life. Here\'s a reward to get started!',
    targetElement: 'complete',
  },
];

export const TUTORIAL_COMPLETION_REWARD = 50; // Diamonds
export const ONBOARDING_COMPLETION_REWARD = 25; // Diamonds for completing onboarding
export const TUTORIAL_MODE_HOURS = 24; // Hours before tutorial mode ends
export const TUTORIAL_GAME_SPEED_MULTIPLIER = 2; // Slower game speed during tutorial (2x slower)

// ============================================================================
// Tutorial Store (per-player state) - Memory cache backed by database
// ============================================================================

const tutorialStates: Map<string, TutorialState> = new Map();

// ============================================================================
// Input Validation
// ============================================================================

/**
 * Validate tooltip ID contains only safe characters
 * Prevents JSON path injection in database queries
 */
export function validateTooltipId(tooltipId: string): boolean {
  return TOOLTIP_ID_PATTERN.test(tooltipId);
}

/**
 * Validate player ID is a positive integer or valid string
 */
function validatePlayerId(playerId: string | number): boolean {
  if (typeof playerId === 'number') {
    return Number.isInteger(playerId) && playerId > 0;
  }
  // For string IDs, allow alphanumeric and common separators
  return typeof playerId === 'string' && playerId.length > 0 && playerId.length <= 255;
}

function parseLegacyNumericPlayerId(playerId: string): number | null {
  if (!/^\d+$/.test(playerId)) {
    return null;
  }

  const numericId = Number(playerId);
  return validatePlayerId(numericId) ? numericId : null;
}

// ============================================================================
// Tutorial Mode Detection
// ============================================================================

/**
 * Check if player is in tutorial mode (first 24 in-game hours)
 * Ported from Python is_tutorial_mode()
 *
 * @param player - Player object
 * @param gameHours - Optional override for testing
 * @returns true if player is in tutorial mode
 */
export function isTutorialMode(player: Player, gameHours?: number): boolean {
  if (gameHours !== undefined) {
    return gameHours < TUTORIAL_MODE_HOURS;
  }

  // Calculate hours played based on character age
  const ageHours = player.c.ageHours ?? 0;
  return ageHours < TUTORIAL_MODE_HOURS;
}

/**
 * Apply tutorial game speed modifier
 * Makes the game run slower during tutorial mode to give players time to learn
 * Ported from Python apply_tutorial_modifiers() (game speed portion)
 *
 * @param player - Player object
 * @param requestedSpeed - The speed the player requested
 * @returns Modified game speed (slower during tutorial)
 */
export function applyTutorialGameSpeedModifier(player: Player, requestedSpeed: number): number {
  if (!isTutorialMode(player)) {
    return requestedSpeed;
  }

  // During tutorial, cap the maximum speed to prevent overwhelming new players
  // Multiply the speed value (higher = slower, so we multiply to slow down)
  const tutorialSpeed = requestedSpeed * TUTORIAL_GAME_SPEED_MULTIPLIER;

  // Don't make it absurdly slow - cap at a reasonable maximum
  const maxSlowSpeed = 20000; // Very slow but not completely frozen
  return Math.min(tutorialSpeed, maxSlowSpeed);
}

/**
 * Get the recommended tutorial game speed
 * Returns a slower default speed for new players
 *
 * @param player - Player object
 * @param defaultSpeed - The normal default speed
 * @returns Tutorial-appropriate game speed
 */
export function getTutorialGameSpeed(player: Player, defaultSpeed: number): number {
  if (!isTutorialMode(player)) {
    return defaultSpeed;
  }

  // Tutorial mode uses a slower default speed
  return defaultSpeed * TUTORIAL_GAME_SPEED_MULTIPLIER;
}

/**
 * Check if the game should auto-pause for a tutorial moment
 * Some events during tutorial should pause the game to let players read
 *
 * @param player - Player object
 * @param eventType - Type of event being triggered
 * @returns true if game should pause
 */
export function shouldPauseForTutorial(player: Player, eventType: string): boolean {
  if (!isTutorialMode(player)) {
    return false;
  }

  // Always pause for question events during tutorial
  if (eventType === 'questionEvent') {
    return true;
  }

  // Pause for important tutorial milestone messages
  const pauseEvents = [
    'tutorialComplete',
    'firstConversation',
    'firstActivityChoice',
    'tutorialEnergyExplained',
    'tutorialGameSpeedExplained',
    'tutorialStatsExplained',
    'tutorialDiamondsEarned',
    'tutorialFirstMilestone',
  ];

  return pauseEvents.includes(eventType);
}

// ============================================================================
// Database Operations - Ported from Python retention/tutorial.py
// ============================================================================

/**
 * Initialize tutorial tracking for new player in database
 * Ported from Python initialize_tutorial()
 *
 * @param playerId - Player ID (number for database)
 * @returns Operation result with tutorial state
 */
export async function initializeTutorialInDb(playerId: number): Promise<TutorialOperationResult> {
  if (!validatePlayerId(playerId)) {
    return { success: false, error: 'Invalid player_id: must be positive integer' };
  }

  let conn: PoolConnection | null = null;

  try {
    conn = await getConnection();

    // Check if already initialized
    const [existing] = await conn.execute<TutorialProgressRow[]>(
      'SELECT * FROM player_tutorial_progress WHERE player_id = ?',
      [playerId]
    );

    if (existing.length > 0) {
      const row = existing[0];
      return {
        success: true,
        tutorialStep: row.tutorial_step,
        onboardingComplete: Boolean(row.onboarding_complete),
      };
    }

    // Initialize new record
    await conn.execute(
      `INSERT INTO player_tutorial_progress
       (player_id, tutorial_step, onboarding_complete, first_session_date, tooltips_seen)
       VALUES (?, 0, FALSE, NOW(), '{}')`,
      [playerId]
    );

    console.log(`Initialized tutorial for player ${playerId}`);

    return {
      success: true,
      tutorialStep: 0,
      onboardingComplete: false,
    };
  } catch (error) {
    console.error(`Error initializing tutorial for player ${playerId}:`, error);
    return { success: false, error: String(error) };
  } finally {
    if (conn) {
      conn.release();
    }
  }
}

/**
 * Get tutorial progress from database
 * Ported from Python get_tutorial_progress()
 *
 * @param playerId - Player ID
 * @returns Tutorial state or null if not found
 */
export async function getTutorialProgressFromDb(playerId: number): Promise<TutorialState | null> {
  if (!validatePlayerId(playerId)) {
    console.error(`Invalid player_id: ${playerId}`);
    return null;
  }

  try {
    const row = await queryOne<TutorialProgressRow>(
      'SELECT * FROM player_tutorial_progress WHERE player_id = ?',
      [playerId]
    );

    if (!row) {
      return null;
    }

    // Parse JSON tooltips_seen
    let tooltipsSeen: Record<string, boolean> = {};
    try {
      tooltipsSeen = row.tooltips_seen ? JSON.parse(row.tooltips_seen) : {};
    } catch {
      tooltipsSeen = {};
    }

    return {
      step: row.tutorial_step,
      isComplete: row.tutorial_step >= TUTORIAL_STEPS.length,
      onboardingComplete: Boolean(row.onboarding_complete),
      tooltipsShown: new Set(Object.keys(tooltipsSeen).filter(k => tooltipsSeen[k])),
      tooltipsSeen,
      skipTutorial: Boolean(row.skip_tutorial),
      startedAt: row.first_session_date ?? new Date(),
      completedAt: row.onboarding_completed_date,
      firstSessionDate: row.first_session_date,
    };
  } catch (error) {
    console.error(`Error getting tutorial progress for player ${playerId}:`, error);
    return null;
  }
}

/**
 * Update tutorial step in database
 * Ported from Python update_tutorial_step()
 *
 * @param playerId - Player ID
 * @param newStep - New tutorial step (0-99)
 * @returns Operation result
 */
export async function updateTutorialStepInDb(
  playerId: number,
  newStep: number
): Promise<TutorialOperationResult> {
  if (!validatePlayerId(playerId)) {
    return { success: false, error: 'Invalid player_id: must be positive integer' };
  }
  if (!Number.isInteger(newStep) || newStep < 0 || newStep >= 100) {
    return { success: false, error: 'Invalid new_step: must be between 0 and 99' };
  }

  let conn: PoolConnection | null = null;

  try {
    conn = await getConnection();

    await conn.execute(
      'UPDATE player_tutorial_progress SET tutorial_step = ? WHERE player_id = ?',
      [newStep, playerId]
    );

    // Log milestone
    await conn.execute(
      `INSERT INTO tutorial_milestones
       (player_id, milestone_type, milestone_data)
       VALUES (?, 'step_complete', ?)`,
      [playerId, JSON.stringify({ step: newStep })]
    );

    console.log(`Player ${playerId} advanced to tutorial step ${newStep}`);

    return {
      success: true,
      newStep,
    };
  } catch (error) {
    console.error(`Error updating tutorial step for player ${playerId}:`, error);
    return { success: false, error: String(error) };
  } finally {
    if (conn) {
      conn.release();
    }
  }
}

/**
 * Mark a tooltip as seen in database
 * Ported from Python mark_tooltip_seen()
 *
 * Uses JSON_SET for atomic update to prevent race conditions
 *
 * @param playerId - Player ID
 * @param tooltipId - Tooltip identifier (must match pattern)
 * @returns Operation result
 */
export async function markTooltipSeenInDb(
  playerId: number,
  tooltipId: string
): Promise<TutorialOperationResult> {
  if (!validatePlayerId(playerId)) {
    return { success: false, error: 'Invalid player_id: must be positive integer' };
  }
  if (!tooltipId || typeof tooltipId !== 'string' || !tooltipId.trim()) {
    return { success: false, error: 'Invalid tooltip_id: must be non-empty string' };
  }
  if (!validateTooltipId(tooltipId)) {
    return { success: false, error: `Invalid tooltip_id format: ${tooltipId}` };
  }

  let conn: PoolConnection | null = null;

  try {
    conn = await getConnection();

    // Use JSON_SET for atomic update - creates path if it doesn't exist
    const [result] = await conn.execute(
      `UPDATE player_tutorial_progress
       SET tooltips_seen = JSON_SET(
         COALESCE(tooltips_seen, '{}'),
         CONCAT('$.', ?),
         TRUE
       )
       WHERE player_id = ?`,
      [tooltipId, playerId]
    );

    const affectedRows = (result as any).affectedRows ?? 0;
    if (affectedRows === 0) {
      return { success: false, error: 'Player tutorial not initialized' };
    }

    // Log milestone
    await conn.execute(
      `INSERT INTO tutorial_milestones
       (player_id, milestone_type, milestone_data)
       VALUES (?, 'tooltip_seen', ?)`,
      [playerId, JSON.stringify({ tooltip_id: tooltipId })]
    );

    console.log(`Player ${playerId} saw tooltip: ${tooltipId}`);

    return { success: true };
  } catch (error) {
    console.error(`Error marking tooltip for player ${playerId}:`, error);
    return { success: false, error: String(error) };
  } finally {
    if (conn) {
      conn.release();
    }
  }
}

/**
 * Mark onboarding as complete and award achievement
 * Ported from Python complete_onboarding()
 *
 * @param playerId - Player ID
 * @returns Operation result with reward amount
 */
export async function completeOnboardingInDb(playerId: number): Promise<TutorialOperationResult> {
  if (!validatePlayerId(playerId)) {
    return { success: false, error: 'Invalid player_id: must be positive integer' };
  }

  let conn: PoolConnection | null = null;

  try {
    conn = await getConnection();
    await conn.beginTransaction();

    // Mark complete
    await conn.execute(
      `UPDATE player_tutorial_progress
       SET onboarding_complete = TRUE, onboarding_completed_date = NOW()
       WHERE player_id = ?`,
      [playerId]
    );

    // Award "First Steps" achievement (25 diamonds)
    checkAndUnlock(String(playerId), 'first_steps');

    await conn.commit();

    console.log(`Player ${playerId} completed onboarding`);

    return {
      success: true,
      rewardDiamonds: ONBOARDING_COMPLETION_REWARD,
    };
  } catch (error) {
    if (conn) {
      await conn.rollback();
    }
    console.error(`Error completing onboarding for player ${playerId}:`, error);
    return { success: false, error: String(error) };
  } finally {
    if (conn) {
      conn.release();
    }
  }
}

// ============================================================================
// Tutorial Functions (In-Memory with Database Sync)
// ============================================================================

/**
 * Initialize tutorial state for a new player
 * Creates in-memory state and optionally syncs to database
 *
 * @param playerId - Player ID (string for in-memory, converted to number for DB)
 * @param syncToDb - Whether to also initialize in database (default: false)
 */
export function initializeTutorial(playerId: string, syncToDb = false): TutorialState {
  const state: TutorialState = {
    step: 1,
    isComplete: false,
    onboardingComplete: false,
    tooltipsShown: new Set(),
    tooltipsSeen: {},
    skipTutorial: false,
    startedAt: new Date(),
    completedAt: null,
    firstSessionDate: new Date(),
  };

  tutorialStates.set(playerId, state);

  // Optionally sync to database
  if (syncToDb) {
    const numericId = parseLegacyNumericPlayerId(playerId);
    if (numericId !== null) {
      initializeTutorialInDb(numericId).catch(err => {
        console.error(`Failed to sync tutorial init to DB for player ${playerId}:`, err);
      });
    }
  }

  return state;
}

/**
 * Load tutorial state from database into memory cache
 *
 * @param playerId - Player ID
 * @returns Tutorial state or null if not found
 */
export async function loadTutorialState(playerId: string): Promise<TutorialState | null> {
  const numericId = parseLegacyNumericPlayerId(playerId);
  if (numericId === null) {
    return null;
  }

  const dbState = await getTutorialProgressFromDb(numericId);
  if (dbState) {
    tutorialStates.set(playerId, dbState);
  }
  return dbState;
}

/**
 * Get current tutorial state for a player
 */
export function getTutorialState(playerId: string): TutorialState | null {
  return tutorialStates.get(playerId) || null;
}

/**
 * Check if tutorial is complete
 */
export function isTutorialComplete(playerId: string): boolean {
  const state = tutorialStates.get(playerId);
  return state?.isComplete ?? false;
}

/**
 * Get current tutorial step
 */
export function getCurrentStep(playerId: string): number {
  const state = tutorialStates.get(playerId);
  return state?.step ?? 1;
}

/**
 * Advance to next tutorial step
 */
export function advanceTutorialStep(playerId: string): TutorialMessage {
  let state = tutorialStates.get(playerId);

  if (!state) {
    state = initializeTutorial(playerId);
  }

  if (state.isComplete) {
    return {
      type: 'tutorialAlreadyComplete',
      isComplete: true,
    };
  }

  const currentStep = TUTORIAL_STEPS[state.step - 1];
  state.step++;

  // Check if tutorial is now complete
  if (state.step > TUTORIAL_STEPS.length) {
    return completeTutorial(playerId);
  }

  const nextStep = TUTORIAL_STEPS[state.step - 1];

  return {
    type: 'tutorialStep',
    step: state.step,
    message: nextStep.message,
  };
}

/**
 * Complete the tutorial and award reward
 */
export function completeTutorial(playerId: string): TutorialMessage {
  let state = tutorialStates.get(playerId);

  if (!state) {
    state = initializeTutorial(playerId);
  }

  if (state.isComplete) {
    return {
      type: 'tutorialAlreadyComplete',
      isComplete: true,
    };
  }

  state.isComplete = true;
  state.completedAt = new Date();

  // Award completion reward
  awardDiamonds(playerId, 'tutorial_completion', TUTORIAL_COMPLETION_REWARD);

  console.log(`Player ${playerId} completed tutorial, awarded ${TUTORIAL_COMPLETION_REWARD} diamonds`);

  return {
    type: 'tutorialComplete',
    isComplete: true,
    reward: TUTORIAL_COMPLETION_REWARD,
    message: 'Congratulations! You\'ve completed the tutorial!',
  };
}

/**
 * Complete the onboarding phase (distinct from tutorial completion)
 * Marks onboarding as complete and awards the "First Steps" achievement
 * Ported from Python complete_onboarding()
 *
 * @param playerId - Player ID
 * @param syncToDb - Whether to sync to database (default: true)
 * @returns Tutorial message with reward info
 */
export async function completeOnboarding(
  playerId: string,
  syncToDb = true,
  playerState?: Parameters<typeof awardDiamonds>[3]
): Promise<TutorialMessage> {
  let state = tutorialStates.get(playerId);

  if (!state) {
    state = initializeTutorial(playerId);
  }

  if (state.onboardingComplete) {
    return {
      type: 'onboardingAlreadyComplete',
      isComplete: true,
    };
  }

  state.onboardingComplete = true;

  // Award diamonds
  awardDiamonds(playerId, 'onboarding_completion', ONBOARDING_COMPLETION_REWARD, playerState);

  // Sync to database
  if (syncToDb) {
    const numericId = parseLegacyNumericPlayerId(playerId);
    if (numericId !== null) {
      const result = await completeOnboardingInDb(numericId);
      if (!result.success) {
        console.error(`Failed to sync onboarding completion to DB: ${result.error}`);
      }
    }
  }

  console.log(`Player ${playerId} completed onboarding, awarded ${ONBOARDING_COMPLETION_REWARD} diamonds`);

  return {
    type: 'onboardingComplete',
    isComplete: true,
    reward: ONBOARDING_COMPLETION_REWARD,
    message: 'Welcome! You\'ve completed the onboarding!',
  };
}

/**
 * Skip the tutorial
 */
export function skipTutorial(playerId: string): TutorialMessage {
  let state = tutorialStates.get(playerId);

  if (!state) {
    state = initializeTutorial(playerId);
  }

  state.isComplete = true;
  state.completedAt = new Date();

  // No reward for skipping
  console.log(`Player ${playerId} skipped tutorial`);

  return {
    type: 'tutorialSkipped',
    isComplete: true,
    message: 'Tutorial skipped. You can replay it from settings.',
  };
}

/**
 * Reset tutorial for a player (for replay)
 */
export function resetTutorial(playerId: string): TutorialMessage {
  const state = initializeTutorial(playerId);

  return {
    type: 'tutorialReset',
    step: state.step,
    message: TUTORIAL_STEPS[0].message,
  };
}

// ============================================================================
// Tooltip Management
// ============================================================================

/**
 * Mark a tooltip as shown
 * Updates both in-memory state and database
 *
 * @param playerId - Player ID
 * @param tooltipId - Tooltip identifier
 * @param syncToDb - Whether to sync to database (default: true)
 */
export function markTooltipShown(playerId: string, tooltipId: string, syncToDb = true): void {
  // Validate tooltip ID before processing
  if (!validateTooltipId(tooltipId)) {
    console.warn(`Invalid tooltip ID format: ${tooltipId}`);
    return;
  }

  const state = tutorialStates.get(playerId);
  if (state) {
    state.tooltipsShown.add(tooltipId);
    state.tooltipsSeen[tooltipId] = true;
  }

  // Sync to database
  if (syncToDb) {
    const numericId = parseLegacyNumericPlayerId(playerId);
    if (numericId !== null) {
      markTooltipSeenInDb(numericId, tooltipId).catch(err => {
        console.error(`Failed to sync tooltip seen to DB for player ${playerId}:`, err);
      });
    }
  }
}

/**
 * Check if a tooltip has been shown
 */
export function hasTooltipBeenShown(playerId: string, tooltipId: string): boolean {
  const state = tutorialStates.get(playerId);
  return state?.tooltipsShown.has(tooltipId) ?? false;
}

/**
 * Get list of tooltips not yet shown
 */
export function getUnshownTooltips(playerId: string, tooltipIds: string[]): string[] {
  const state = tutorialStates.get(playerId);
  if (!state) return tooltipIds;

  return tooltipIds.filter((id) => !state.tooltipsShown.has(id));
}

// ============================================================================
// WebSocket Handlers
// ============================================================================

export interface TutorialActionMessage {
  action?: string;
  tooltipId?: string;
  step?: number;
}

export type SendToClientFn = (
  playerId: string,
  message: Record<string, unknown>
) => void;

// ============================================================================
// Tutorial Trigger System - Ported from Python check_tutorial_triggers()
// ============================================================================

/**
 * Tutorial trigger condition
 */
export interface TutorialTrigger {
  name: string;
  condition: (player: Player) => boolean;
  eventType: string;
}

/**
 * Define all tutorial trigger conditions
 * Each trigger checks a specific game state condition
 */
export const TUTORIAL_TRIGGERS: TutorialTrigger[] = [
  {
    name: 'firstConversation',
    condition: (player) =>
      !player.askedQuestions.has('firstConversation') &&
      isTutorialMode(player) &&
      (player.r?.length ?? 0) > 0,
    eventType: 'firstConversation',
  },
  {
    name: 'firstActivityChoice',
    condition: (player) =>
      !player.askedQuestions.has('firstActivityChoice') &&
      isTutorialMode(player) &&
      player.c.ageYears >= 5,
    eventType: 'firstActivityChoice',
  },
  {
    name: 'tutorialComplete',
    condition: (player) =>
      !player.askedQuestions.has('tutorialComplete') &&
      (player.c.ageHours ?? 0) >= 24 &&
      (player.c.ageHours ?? 0) < 25,
    eventType: 'tutorialComplete',
  },
  {
    name: 'tutorialGameSpeedExplained',
    condition: (player) =>
      !player.askedQuestions.has('tutorialGameSpeedExplained') &&
      isTutorialMode(player) &&
      (player.c.ageHours ?? 0) >= 1,
    eventType: 'tutorialGameSpeedExplained',
  },
  {
    name: 'tutorialStatsExplained',
    condition: (player) =>
      !player.askedQuestions.has('tutorialStatsExplained') &&
      isTutorialMode(player) &&
      (player.c.ageHours ?? 0) >= 2,
    eventType: 'tutorialStatsExplained',
  },
  {
    name: 'tutorialEventsIntro',
    condition: (player) =>
      !player.askedQuestions.has('tutorialEventsIntro') &&
      isTutorialMode(player) &&
      player.askedQuestions.size >= 3,
    eventType: 'tutorialEventsIntro',
  },
  {
    name: 'tutorialEnergyExplained',
    condition: (player) =>
      !player.askedQuestions.has('tutorialEnergyExplained') &&
      isTutorialMode(player) &&
      (player.c.energy ?? 100) < 20,
    eventType: 'tutorialEnergyExplained',
  },
  {
    name: 'tutorialRelationshipExplained',
    condition: (player) =>
      !player.askedQuestions.has('tutorialRelationshipExplained') &&
      isTutorialMode(player) &&
      (player.r?.length ?? 0) > 0 &&
      player.r.some((p) => p.affinity !== undefined),
    eventType: 'tutorialRelationshipExplained',
  },
  {
    name: 'tutorialDiamondsEarned',
    condition: (player) =>
      !player.askedQuestions.has('tutorialDiamondsEarned') &&
      isTutorialMode(player) &&
      (player.c.diamonds ?? 0) > 0,
    eventType: 'tutorialDiamondsEarned',
  },
  {
    name: 'tutorialSchedulesIntro',
    condition: (player) =>
      !player.askedQuestions.has('tutorialSchedulesIntro') &&
      isTutorialMode(player) &&
      (player.c.schedules?.length ?? 0) > 0,
    eventType: 'tutorialSchedulesIntro',
  },
  {
    name: 'tutorialOneTimeEvents',
    condition: (player) =>
      !player.askedQuestions.has('tutorialOneTimeEvents') &&
      isTutorialMode(player) &&
      (player.c.oneTimeEvents?.length ?? 0) > 0,
    eventType: 'tutorialOneTimeEvents',
  },
];

/**
 * Check which tutorial events should be triggered based on player state
 * Ported from Python check_tutorial_triggers()
 *
 * @param player - Player object
 * @returns Array of trigger names that should fire
 */
export function checkTutorialTriggers(player: Player): string[] {
  if (!isTutorialMode(player)) {
    return [];
  }

  const triggersToFire: string[] = [];

  for (const trigger of TUTORIAL_TRIGGERS) {
    if (trigger.condition(player)) {
      triggersToFire.push(trigger.name);
    }
  }

  return triggersToFire;
}

/**
 * Get tutorial state for client including current triggers
 *
 * @param player - Player object
 * @returns Full tutorial state with triggers
 */
export function getTutorialStateForPlayer(player: Player): {
  step: number;
  isComplete: boolean;
  onboardingComplete: boolean;
  isTutorialMode: boolean;
  pendingTriggers: string[];
  totalSteps: number;
  currentStepInfo: (typeof TUTORIAL_STEPS)[number] | null;
} {
  const state = getTutorialState(player.userId);
  const inTutorialMode = isTutorialMode(player);
  const triggers = inTutorialMode ? checkTutorialTriggers(player) : [];

  return {
    step: state?.step ?? 1,
    isComplete: state?.isComplete ?? false,
    onboardingComplete: state?.onboardingComplete ?? false,
    isTutorialMode: inTutorialMode,
    pendingTriggers: triggers,
    totalSteps: TUTORIAL_STEPS.length,
    currentStepInfo:
      state?.isComplete || !state
        ? null
        : TUTORIAL_STEPS[(state.step ?? 1) - 1] ?? null,
  };
}

// ============================================================================
// WebSocket Handlers
// ============================================================================

/**
 * Handle tutorial-related WebSocket messages
 */
export function handleTutorialAction(
  playerId: string,
  messageData: TutorialActionMessage,
  sendToClient: SendToClientFn
): void {
  const action = messageData.action || 'next';

  let result: TutorialMessage;

  switch (action) {
    case 'next':
      result = advanceTutorialStep(playerId);
      break;

    case 'skip':
      result = skipTutorial(playerId);
      break;

    case 'reset':
      result = resetTutorial(playerId);
      break;

    case 'complete':
      result = completeTutorial(playerId);
      break;

    case 'completeOnboarding':
      // Handle async onboarding completion
      completeOnboarding(playerId).then((onboardingResult) => {
        sendToClient(playerId, onboardingResult as Record<string, unknown>);
      }).catch((err) => {
        sendToClient(playerId, {
          type: 'error',
          message: `Failed to complete onboarding: ${err}`,
        });
      });
      return; // Early return - response sent asynchronously

    case 'tooltipShown':
      if (messageData.tooltipId) {
        markTooltipShown(playerId, messageData.tooltipId);
      }
      result = {
        type: 'tooltipAcknowledged',
      };
      break;

    case 'stepComplete':
      // Handle step completion with database sync
      if (typeof messageData.step === 'number') {
        const numericId = parseLegacyNumericPlayerId(playerId);
        if (numericId !== null) {
          updateTutorialStepInDb(numericId, messageData.step).then((stepResult) => {
            if (stepResult.success) {
              sendToClient(playerId, {
                type: 'tutorialStepUpdated',
                step: messageData.step,
              });
            } else {
              sendToClient(playerId, {
                type: 'error',
                message: `Failed to update step: ${stepResult.error}`,
              });
            }
          }).catch((err) => {
            sendToClient(playerId, {
              type: 'error',
              message: `Failed to update step: ${err}`,
            });
          });
          return;
        }
      }
      result = advanceTutorialStep(playerId);
      break;

    case 'getState':
      const state = getTutorialState(playerId);
      if (state) {
        sendToClient(playerId, {
          type: 'tutorialState',
          step: state.step,
          isComplete: state.isComplete,
          onboardingComplete: state.onboardingComplete,
          totalSteps: TUTORIAL_STEPS.length,
          currentStepInfo: state.isComplete
            ? null
            : TUTORIAL_STEPS[state.step - 1],
        });
        return;
      } else {
        result = {
          type: 'tutorialState',
          step: 1,
          isComplete: false,
          onboardingComplete: false,
        };
      }
      break;

    default:
      result = {
        type: 'error',
        message: `Unknown tutorial action: ${action}`,
      };
  }

  sendToClient(playerId, result as Record<string, unknown>);
}

/**
 * Handle tutorialStepComplete message from client
 * Ported from Python handle_tutorial_step_complete()
 */
export async function handleTutorialStepComplete(
  playerId: string,
  messageData: { step: number },
  sendToClient: SendToClientFn
): Promise<void> {
  if (!Number.isInteger(messageData.step) || messageData.step < 1) {
    sendToClient(playerId, {
      type: 'error',
      message: 'Invalid tutorial step',
    });
    return;
  }

  let state = tutorialStates.get(playerId);
  if (!state) {
    state = initializeTutorial(playerId);
  }
  state.step = messageData.step;

  const numericId = parseLegacyNumericPlayerId(playerId);
  if (numericId !== null) {
    const result = await updateTutorialStepInDb(numericId, messageData.step);
    if (!result.success) {
      sendToClient(playerId, {
        type: 'error',
        message: result.error,
      });
      return;
    }
  }

  sendToClient(playerId, {
    type: 'tutorialStepUpdated',
    step: messageData.step,
  });
}

/**
 * Handle tooltipSeen message from client
 * Ported from Python handle_tooltip_seen()
 */
export function handleTooltipSeen(
  playerId: string,
  messageData: { tooltipId: string }
): void {
  markTooltipShown(playerId, messageData.tooltipId);
}

/**
 * Handle completeOnboarding message from client
 * Ported from Python handle_complete_onboarding()
 */
export async function handleCompleteOnboarding(
  playerId: string,
  sendToClient: SendToClientFn,
  playerState?: Parameters<typeof awardDiamonds>[3]
): Promise<void> {
  const result = await completeOnboarding(playerId, true, playerState);

  if (result.type === 'onboardingComplete') {
    sendToClient(playerId, {
      type: 'onboardingComplete',
      reward: result.reward,
    });
  } else {
    sendToClient(playerId, result as Record<string, unknown>);
  }
}

// ============================================================================
// Cleanup
// ============================================================================

/**
 * Clear tutorial state for a player
 */
export function clearPlayerTutorial(playerId: string): void {
  tutorialStates.delete(playerId);
}

/**
 * Clear all tutorial states (for testing)
 */
export function clearAllTutorials(): void {
  tutorialStates.clear();
}

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

export const tutorialManager = {
  // Core functions
  initializeTutorial,
  getTutorialState,
  isTutorialComplete,
  getCurrentStep,
  advanceTutorialStep,
  completeTutorial,
  skipTutorial,
  resetTutorial,

  // Tutorial mode detection
  isTutorialMode,

  // Tutorial modifiers (ported from Python apply_tutorial_modifiers)
  applyTutorialGameSpeedModifier,
  getTutorialGameSpeed,
  shouldPauseForTutorial,

  // Onboarding
  completeOnboarding,

  // Tooltip management
  markTooltipShown,
  hasTooltipBeenShown,
  getUnshownTooltips,
  validateTooltipId,

  // Tutorial triggers
  checkTutorialTriggers,
  getTutorialStateForPlayer,
  TUTORIAL_TRIGGERS,

  // WebSocket handlers
  handleTutorialAction,
  handleTutorialStepComplete,
  handleTooltipSeen,
  handleCompleteOnboarding,

  // Database operations
  initializeTutorialInDb,
  getTutorialProgressFromDb,
  updateTutorialStepInDb,
  markTooltipSeenInDb,
  completeOnboardingInDb,
  loadTutorialState,

  // Cleanup
  clearPlayerTutorial,
  clearAllTutorials,

  // Constants
  TUTORIAL_STEPS,
  TUTORIAL_COMPLETION_REWARD,
  ONBOARDING_COMPLETION_REWARD,
  TUTORIAL_MODE_HOURS,
  TUTORIAL_GAME_SPEED_MULTIPLIER,
};
