/**
 * TypeScript types for timeline data from the record-daemon.
 */

import { getDdragonVersion, gameVersionToDdragonVersion } from "./ddragon";

/**
 * A timestamped event in the timeline.
 * Video timestamp is serialized as [seconds, nanos] tuple from Rust.
 */
export interface TimestampedEvent {
  /** Video timestamp (offset from recording start) as [seconds, nanos]. */
  video_timestamp: [number, number];
  /** Game timestamp (in-game time) as [seconds, nanos]. */
  game_timestamp: [number, number] | null;
  /** Real-world timestamp (ISO 8601). */
  timestamp: string;
  /** Event type name (e.g. "game_start", "lp_change", "kill"). */
  event_type: string;
  /** Human-readable description. */
  description: string;
  /** Raw JSON data from the League Client API — the single source of truth. */
  raw_data: Record<string, unknown>;
  /** URI of the endpoint that triggered this event. */
  uri: string;
}

/**
 * LP change information extracted from an lp_change event's raw_data.
 */
export interface LpChangeInfo {
  /** LP delta (positive = gain, negative = loss). */
  leaguePointsDelta: number;
  /** Current league points after the change. */
  leaguePoints: number;
  /** Tier (e.g. "EMERALD", "DIAMOND", "GOLD"). */
  tier: string;
  /** Division (e.g. "I", "II", "III", "IV"). */
  division: string;
  /** Queue type (e.g. "RANKED_SOLO_5x5", "RANKED_FLEX_SR"). */
  queueType: string;
  /** Total wins. */
  wins: number;
  /** Total losses. */
  losses: number;
  /** Current win streak. */
  winStreak: number;
  /** Whether the player is in promos. */
  inPromos: boolean;
}

/**
 * Final game statistics for the player.
 */
export interface GameFinalStats {
  /** Kills. */
  kills: number;
  /** Deaths. */
  deaths: number;
  /** Assists. */
  assists: number;
  /** Creep score. */
  creep_score: number;
  /** Gold earned. */
  gold_earned: number;
  /** Damage dealt. */
  damage_dealt: number;
  /** Damage taken. */
  damage_taken: number;
  /** Vision score. */
  vision_score: number;
  /** Game duration in seconds. */
  game_duration: number;
}

/**
 * Rune page configuration.
 */
export interface RunePage {
  /** Primary rune style (keystone tree). */
  primary_style_id: number;
  /** Secondary rune style. */
  secondary_style_id: number;
  /** Selected rune perk IDs. */
  selected_perks: number[];
  /** Stat modifier IDs. */
  stat_modifiers: number[];
  /** Rune page name. */
  name: string | null;
  /** Whether this is the current page. */
  current: boolean;
}

/**
 * Summoner spell information.
 */
export interface SummonerSpells {
  /** First summoner spell ID. */
  spell1Id: number;
  /** Second summoner spell ID. */
  spell2Id: number;
}

/**
 * Individual item information.
 */
export interface ItemInfo {
  /** Item ID. */
  itemId: number;
  /** Item name. */
  name: string | null;
  /** Slot index (0-5 for main items, 6 for trinket). */
  slot: number;
}

/**
 * Item build at game end.
 */
export interface ItemBuild {
  /** Final items (up to 6 item slots). */
  items: ItemInfo[];
  /** Trinket item. */
  trinket: ItemInfo | null;
}

/**
 * Player identity information for puuid to summoner name mapping.
 */
export interface PlayerIdentityInfo {
  /** Player's PUUID. */
  puuid: string;
  /** Player's summoner name. */
  summoner_name: string;
  /** Player's champion name. */
  champion_name: string | null;
  /** Player's team (100 or 200). */
  team: number | null;
}

/**
 * Game result type.
 */
export type GameResult = 'Victory' | 'Defeat' | 'Terminated';

/**
 * A timeline of events for a recording.
 * This is the main data structure returned by the backend.
 * Stores raw API responses for maximum flexibility.
 */
export interface Timeline {
  /** Recording ID (UUID). */
  recording_id: string;
  /** Game start time (ISO 8601). */
  start_time: string;
  /** Game end time (ISO 8601). */
  end_time: string | null;
  /** Duration in seconds. */
  duration_secs: number;
  /** Events in the timeline. */
  events: TimestampedEvent[];
  
  // Game information
  /** Game ID. */
  game_id: number | null;
  
  // Raw API responses - frontend can parse these as needed
  /** Raw session data from `/lol-gameflow/v1/session`. */
  raw_session: Record<string, unknown> | null;
  /** Raw summoner data from `/lol-summoner/v1/current-summoner`. */
  raw_summoner: Record<string, unknown> | null;
  /** Raw champion select data from `/lol-champ-select/v1/session`. */
  raw_champion_select: Record<string, unknown> | null;
  /** Raw rune page data from `/lol-perks/v1/currentpage`. */
  raw_rune_page: Record<string, unknown> | null;
  /** Raw live client data from `/liveclientdata/allgamedata`. */
  raw_live_client_data: Record<string, unknown> | null;
  /** Raw end-of-game stats from `/lol-end-of-game/v1/eog-stats-block`. */
  raw_end_game_stats: Record<string, unknown> | null;
  /** Video file name (just the filename, not the full path). */
  video_file: string | null;
}

/**
 * Raw end-of-game stats from the League Client API.
 * This is the full response from the end-of-game stats endpoint.
 * Note: Properties use camelCase to match the actual API response.
 */
export interface RawEndGameStats {
  /** Game ID. */
  gameId?: number;
  /** Game length in seconds. */
  gameLength?: number;
  /** Match ID. */
  matchId?: number | null;
  /** Game result. */
  gameResult?: string | null;
  /** Local player data. */
  localPlayer?: EndGamePlayer;
  /** Teams data. */
  teams?: EndGameTeam[];
  /** Players (legacy format). */
  players?: EndGamePlayer[] | null;
}

/**
 * Player in end-of-game stats.
 * Note: Properties use camelCase to match the actual API response.
 */
export interface EndGamePlayer {
  /** Whether this is the local player. */
  isLocalPlayer?: boolean;
  /** Player stats. */
  stats?: PlayerStats;
  /** Items (array of item IDs). */
  items?: number[];
}

/**
 * Team in end-of-game stats.
 * Note: Properties use camelCase to match the actual API response.
 */
export interface EndGameTeam {
  /** Whether this is the player's team. */
  isPlayerTeam?: boolean;
  /** Whether this is the winning team. */
  isWinningTeam?: boolean;
  /** Players on the team. */
  players?: EndGamePlayer[];
}

/**
 * Player stats in end-of-game.
 */
export interface PlayerStats {
  /** Kills. */
  CHAMPIONS_KILLED?: number;
  /** Deaths. */
  NUM_DEATHS?: number;
  /** Assists. */
  ASSISTS?: number;
  /** Minions killed (CS). */
  MINIONS_KILLED?: number;
  /** Gold earned. */
  GOLD_EARNED?: number;
  /** Total damage dealt to champions. */
  TOTAL_DAMAGE_DEALT_TO_CHAMPIONS?: number;
  /** Total damage taken. */
  TOTAL_DAMAGE_TAKEN?: number;
  /** Vision score. */
  VISION_SCORE?: number;
  /** Win/Lose status (0/1 = False/True). */
  WIN?: number;
  LOSE?: number;
}

/**
 * Game history item for display in the UI.
 * Alias for Timeline since the backend returns full timeline data.
 */
export type GameHistoryItem = Timeline;

/**
 * Get the game result as a display string.
 * Extracts from raw_end_game_stats.
 */
export function getGameResult(game: GameHistoryItem): GameResult {
  // Try to extract victory from raw_end_game_stats
  const stats = game.raw_end_game_stats as RawEndGameStats | null;
  if (stats?.localPlayer?.stats?.WIN === 1) return 'Victory';
  if (stats?.localPlayer?.stats?.LOSE === 1) return 'Defeat';
  return 'Terminated';
}

/**
 * Extract champion name from raw API data.
 */
export function getChampionName(game: GameHistoryItem): string | null {
  // Try raw_end_game_stats first (most reliable)
  const endStats = game.raw_end_game_stats as { localPlayer?: { championName?: string } } | null;
  if (endStats?.localPlayer?.championName) {
    return endStats.localPlayer.championName;
  }
  
  // Try raw_live_client_data
  const liveData = game.raw_live_client_data as { activePlayer?: { championName?: string } } | null;
  if (liveData?.activePlayer?.championName) {
    return liveData.activePlayer.championName;
  }
  
  return null;
}

/**
 * Extract summoner name from raw API data.
 */
export function getSummonerName(game: GameHistoryItem): string | null {
  // Try raw_end_game_stats first (most reliable for Riot ID)
  const endStats = game.raw_end_game_stats as { localPlayer?: { riotIdGameName?: string; riotIdTagLine?: string } } | null;
  if (endStats?.localPlayer?.riotIdGameName) {
    const tagLine = endStats.localPlayer.riotIdTagLine;
    return tagLine ? `${endStats.localPlayer.riotIdGameName}#${tagLine}` : endStats.localPlayer.riotIdGameName;
  }
  
  // Try raw_summoner
  const summoner = game.raw_summoner as { displayName?: string; gameName?: string; tagLine?: string; name?: string } | null;
  if (summoner?.gameName) {
    const tagLine = summoner.tagLine;
    return tagLine ? `${summoner.gameName}#${tagLine}` : summoner.gameName;
  }
  return summoner?.displayName || summoner?.name || null;
}

/**
 * Extract queue type from raw session data.
 */
export function getQueueType(game: GameHistoryItem): string | null {
  const session = game.raw_session as { gameData?: { queue?: { name?: string } } } | null;
  return session?.gameData?.queue?.name || null;
}

/**
 * Extract queue ID from raw session data.
 */
export function getQueueId(game: GameHistoryItem): number | null {
  const session = game.raw_session as { gameData?: { queue?: { id?: number } } } | null;
  return session?.gameData?.queue?.id || null;
}

/**
 * Extract game mode from raw session data.
 */
export function getGameMode(game: GameHistoryItem): string | null {
  const session = game.raw_session as { gameData?: { queue?: { gameMode?: string } } } | null;
  return session?.gameData?.queue?.gameMode || null;
}

/**
 * Extract map name from raw session data.
 */
export function getMapName(game: GameHistoryItem): string | null {
  const session = game.raw_session as { map?: { name?: string } } | null;
  return session?.map?.name || null;
}

/**
 * Extract the game version from raw session data.
 * The League Client reports versions like "16.10.1" or "16.10.1.1234".
 * Returns null if no version is available in the recording.
 */
export function getGameVersion(game: GameHistoryItem): string | null {
  // Try raw_session.gameData.gameVersion
  const session = game.raw_session as { gameData?: { gameVersion?: string } } | null;
  if (session?.gameData?.gameVersion) {
    return gameVersionToDdragonVersion(session.gameData.gameVersion);
  }

  // Try raw_end_game_stats.gameVersion
  const stats = game.raw_end_game_stats as { gameVersion?: string } | null;
  if (stats?.gameVersion) {
    return gameVersionToDdragonVersion(stats.gameVersion);
  }

  return null;
}

/**
 * Extract final stats from raw end game stats.
 */
export function getFinalStats(game: GameHistoryItem): GameFinalStats | null {
  const stats = game.raw_end_game_stats as RawEndGameStats | null;
  const player = stats?.localPlayer;
  const playerStats = player?.stats;
  
  if (!playerStats) return null;
  
  return {
    kills: playerStats.CHAMPIONS_KILLED || 0,
    deaths: playerStats.NUM_DEATHS || 0,
    assists: playerStats.ASSISTS || 0,
    creep_score: playerStats.MINIONS_KILLED || 0,
    gold_earned: playerStats.GOLD_EARNED || 0,
    damage_dealt: playerStats.TOTAL_DAMAGE_DEALT_TO_CHAMPIONS || 0,
    damage_taken: playerStats.TOTAL_DAMAGE_TAKEN || 0,
    vision_score: playerStats.VISION_SCORE || 0,
    game_duration: stats?.gameLength || 0,
  };
}

/**
 * Extract summoner spells from raw live client data.
 */
export function getSummonerSpells(game: GameHistoryItem): SummonerSpells | null {
  const player : any = game.raw_end_game_stats?.localPlayer;
  const spell1Id : number = player?.spell1Id;
  const spell2Id : number = player?.spell2Id;
  return {
    spell1Id,
    spell2Id,
  };
}

/**
 * Extract items from raw end game stats.
 */
export function getItems(game: GameHistoryItem): (ItemInfo | null)[] {
  const result: (ItemInfo | null)[] = [null, null, null, null, null, null, null];
  
  const stats = game.raw_end_game_stats as RawEndGameStats | null;
  const items = stats?.localPlayer?.items;
  
  if (items) {
    for (let i = 0; i < Math.min(items.length, 7); i++) {
      const itemId = items[i];
      if (itemId && itemId > 0) {
        result[i] = {
          itemId,
          name: null,
          slot: i,
        };
      }
    }
  }
  
  return result;
}

/**
 * Extract LP change info from lp_change events in a game's timeline.
 * Multiple lp_change events may exist — we pick the one with a ranked tier
 * (non-UNRANKED) or a non-zero leaguePointsDelta, since the others record zeros.
 * Returns null if no lp_change event is found or data is incomplete.
 */
export function getLpChange(game: GameHistoryItem): LpChangeInfo | null {
  const lpEvents = game.events.filter(e => e.event_type === 'lp_change');
  if (lpEvents.length === 0) return null;

  // Prefer the event with a non-UNRANKED tier (the actual ranked one)
  let lpEvent = lpEvents.find(e => {
    const raw = e.raw_data as Record<string, unknown>;
    return raw?.tier && raw.tier !== 'UNRANKED';
  });

  // Fallback: pick the event with a non-zero leaguePointsDelta
  if (!lpEvent) {
    lpEvent = lpEvents.find(e => {
      const raw = e.raw_data as Record<string, unknown>;
      return typeof raw?.leaguePointsDelta === 'number' && raw.leaguePointsDelta !== 0;
    });
  }

  // Last resort: use the first event
  if (!lpEvent) {
    lpEvent = lpEvents[0];
  }

  const raw = lpEvent.raw_data as Record<string, unknown>;
  if (!raw) return null;

  // Use leaguePointsDelta from raw_data (the actual LP change amount)
  const leaguePointsDelta = typeof raw.leaguePointsDelta === 'number' ? raw.leaguePointsDelta : null;
  if (leaguePointsDelta === null) return null;

  return {
    leaguePointsDelta,
    leaguePoints: typeof raw.leaguePoints === 'number' ? raw.leaguePoints : 0,
    tier: typeof raw.tier === 'string' ? raw.tier : 'UNRANKED',
    division: typeof raw.division === 'string' ? raw.division : '',
    queueType: typeof raw.queueType === 'string' ? raw.queueType : '',
    wins: typeof raw.wins === 'number' ? raw.wins : 0,
    losses: typeof raw.losses === 'number' ? raw.losses : 0,
    winStreak: typeof raw.winStreak === 'number' ? raw.winStreak : 0,
    inPromos: typeof raw.miniseriesProgress === 'string' ? raw.miniseriesProgress.length > 0 : false,
  };
}

/**
 * Format an LP delta as a signed string (e.g. "+19", "-15").
 */
export function formatLpDelta(delta: number): string {
  if (delta > 0) return `+${delta}`;
  return `${delta}`;
}

/**
 * Format a rank string from tier and division (e.g. "Emerald II", "Diamond I").
 */
export function formatRank(tier: string, division: string): string {
  if (tier === 'UNRANKED' || !tier) return 'Unranked';
  // Capitalize first letter of tier, lowercase rest
  const tierDisplay = tier.charAt(0) + tier.slice(1).toLowerCase();
  if (!division) return tierDisplay;
  // Convert Roman numerals: I, II, III, IV
  return `${tierDisplay} ${division}`;
}

/**
 * Get the result color class.
 */
export function getResultColor(result: GameResult): string {
  switch (result) {
    case 'Victory': return '#4ade80'; // green
    case 'Defeat': return '#f87171'; // red
    case 'Terminated': return '#9ca3af'; // gray
  }
}

/**
 * Get the result background color (with alpha).
 */
export function getResultBgColor(result: GameResult): string {
  switch (result) {
    case 'Victory': return 'rgba(74, 222, 128, 0.15)';
    case 'Defeat': return 'rgba(248, 113, 113, 0.15)';
    case 'Terminated': return 'rgba(156, 163, 175, 0.15)';
  }
}

/**
 * Format duration in MM:SS format.
 */
export function formatDuration(seconds: number): string {
  const mins = Math.floor(seconds / 60);
  const secs = Math.floor(seconds % 60);
  return `${mins}:${secs.toString().padStart(2, '0')}`;
}

/**
 * Format KDA as "K/D/A" string.
 */
export function formatKDA(stats: GameFinalStats | null): string {
  if (!stats) return '0/0/0';
  return `${stats.kills}/${stats.deaths}/${stats.assists}`;
}

/**
 * Calculate KDA ratio.
 */
export function calculateKDA(stats: GameFinalStats | null): string {
  if (!stats) return '0.0';
  const kda = stats.deaths === 0 
    ? stats.kills + stats.assists 
    : (stats.kills + stats.assists) / stats.deaths;
  return kda.toFixed(1);
}

/**
 * Format CS per minute.
 */
export function formatCSPerMin(stats: GameFinalStats | null): string {
  if (!stats || stats.game_duration === 0) return '0.0';
  const csPerMin = stats.creep_score / (stats.game_duration / 60);
  return csPerMin.toFixed(1);
}

/**
 * Format gold per minute.
 */
export function formatGoldPerMin(stats: GameFinalStats | null): string {
  if (!stats || stats.game_duration === 0) return '0';
  const goldPerMin = stats.gold_earned / (stats.game_duration / 60);
  return Math.round(goldPerMin).toLocaleString();
}

/**
 * Format large numbers (e.g., damage, gold).
 */
export function formatNumber(num: number): string {
  if (num >= 1000000) {
    return (num / 1000000).toFixed(1) + 'M';
  } else if (num >= 1000) {
    return (num / 1000).toFixed(1) + 'K';
  }
  return num.toLocaleString();
}

/**
 * Get champion image URL from Data Dragon.
 * @param championName - Champion name (e.g. "Kai'Sa").
 * @param size - Image size: 'square' for icon, 'loading' for splash.
 * @param version - Optional DDragon patch version (e.g. "16.10.1").
 *                  Falls back to the latest DDragon version if not provided.
 */
export function getChampionImageUrl(championName: string | null, size: 'square' | 'loading' = 'square', version?: string): string {
  const v = version || getDdragonVersion();
  if (!championName) {
    // Return placeholder for unknown champion
    return `https://ddragon.leagueoflegends.com/cdn/${v}/img/champion/Aatrox.png`;
  }
  
  // Data Dragon uses specific champion name formatting
  const formattedName = formatChampionName(championName);
  
  if (size === 'loading') {
    return `https://ddragon.leagueoflegends.com/cdn/${v}/img/champion/${formattedName}_0.jpg`;
  }
  return `https://ddragon.leagueoflegends.com/cdn/${v}/img/champion/${formattedName}.png`;
}

/**
 * Format champion name for Data Dragon URL.
 */
function formatChampionName(name: string): string {
  // Handle special cases
  const specialCases: Record<string, string> = {
    'Wukong': 'MonkeyKing',
    'Nunu & Willump': 'Nunu',
    'Renata Glasc': 'Renata',
    'K\'Tanthi Aatrox': 'Aatrox',
    'K\'Tanthi Varus': 'Varus',
  };
  
  if (specialCases[name]) {
    return specialCases[name];
  }
  
  // Remove spaces and special characters, capitalize first letter of each word
  return name
    .split(/[\s&]+/)
    .map(word => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase())
    .join('')
    .replace(/[^a-zA-Z]/g, '');
}

/**
 * Get summoner spell image URL from Data Dragon.
 * @param spellId - Summoner spell ID.
 * @param version - Optional DDragon patch version. Falls back to latest.
 */
export function getSummonerSpellUrl(spellId: number, version?: string): string {
  const v = version || getDdragonVersion();
  // Map common spell IDs to names
  // Reference: https://github.com/RiotGames/developer-relations/issues/478
  const spellNames: Record<number, string> = {
    1: 'SummonerBoost',      // Cleanse
    3: 'SummonerExhaust',    // Exhaust
    4: 'SummonerFlash',      // Flash
    6: 'SummonerHaste',      // Ghost (ID 6 is Ghost, not Heal!)
    7: 'SummonerHeal',       // Heal
    11: 'SummonerSmite',     // Smite
    12: 'SummonerTeleport',  // Teleport
    13: 'SummonerClarity',   // Clarity
    14: 'SummonerIgnite',    // Ignite
    21: 'SummonerBarrier',   // Barrier
    32: 'SummonerMark',      // Mark (ARAM snowball)
    39: 'SummonerSnowURFSnowball_Mark', // Mark (URF)
    54: 'SummonerPod',       // Porcelain
    55: 'SummonerPoroRecall', // Poro Recall
    56: 'SummonerPoroThrow', // Poro Throw
  };
  
  const spellName = spellNames[spellId] || 'SummonerFlash';
  return `https://ddragon.leagueoflegends.com/cdn/${v}/img/spell/${spellName}.png`;
}

/**
 * Get item image URL from Data Dragon.
 * @param itemId - Item ID.
 * @param version - Optional DDragon patch version. Falls back to latest.
 */
export function getItemImageUrl(itemId: number, version?: string): string {
  if (itemId === 0 || itemId === null) {
    return ''; // Empty item slot
  }
  const v = version || getDdragonVersion();
  return `https://ddragon.leagueoflegends.com/cdn/${v}/img/item/${itemId}.png`;
}

/**
 * Get queue type display name.
 */
export function getQueueDisplayName(queueType: string | null, queueId: number | null): string {
  if (queueType) {
    // Clean up queue type names
    if (queueType.includes('Practice Tool')) return 'Practice Tool';
    if (queueType.includes('Ranked Solo')) return 'Ranked Solo';
    if (queueType.includes('Ranked Flex')) return 'Ranked Flex';
    if (queueType.includes('ARAM')) return 'ARAM';
    if (queueType.includes('Normal')) return 'Normal';
    if (queueType.includes('Co-op')) return 'Co-op vs AI';
    return queueType;
  }
  
  // Fallback to queue ID mapping
  const queueNames: Record<number, string> = {
    400: 'Normal Draft',
    420: 'Ranked Solo',
    430: 'Normal Blind',
    440: 'Ranked Flex',
    450: 'ARAM',
    700: 'Clash',
    800: 'Co-op vs AI',
    830: 'Co-op vs AI',
    840: 'Co-op vs AI',
    850: 'Co-op vs AI',
    900: 'URF',
    1020: 'One for All',
    1300: 'Nexus Blitz',
    1400: 'Ultimate Spellbook',
    1900: 'URF',
    2000: 'Tutorial',
    2010: 'Tutorial',
    2020: 'Tutorial',
    3140: 'Practice Tool',
  };
  
  if (queueId && queueNames[queueId]) {
    return queueNames[queueId];
  }
  
  return 'Custom Game';
}

/**
 * Event type definitions for styling.
 */
export type EventCategory =
  | "kill"
  | "death"
  | "objective"
  | "game"
  | "phase"
  | "unknown";

// ---------------------------------------------------------------------------
// Highlights
// ---------------------------------------------------------------------------

/**
 * The type of highlight clip.
 */
export type HighlightType = "kill" | "death" | "assist" | "multi_kill" | "trade_kill";

/**
 * Configuration for highlight generation.
 */
export interface HighlightSettings {
  /** Seconds before the event to include in the clip. */
  buffer_before: number;
  /** Seconds after the event to include in the clip. */
  buffer_after: number;
  /** Minimum clip duration in seconds. */
  min_duration: number;
  /** Which highlight types to include. */
  included_types: HighlightType[];
  /** Whether to merge overlapping clips into one. */
  merge_overlapping: boolean;
  /** Gap in seconds below which adjacent clips are merged (when merge_overlapping is true). */
  merge_gap_secs: number;
}

/**
 * Default highlight settings.
 */
export const DEFAULT_HIGHLIGHT_SETTINGS: HighlightSettings = {
  buffer_before: 10,
  buffer_after: 3,
  min_duration: 5,
  included_types: ["kill", "death", "multi_kill", "trade_kill", "assist"],
  merge_overlapping: true,
  merge_gap_secs: 5,
};

/**
 * A single highlight clip derived from one or more timeline events.
 */
export interface HighlightClip {
  /** Unique ID for this clip (index-based). */
  id: number;
  /** Start time in seconds (video time). */
  start_time: number;
  /** End time in seconds (video time). */
  end_time: number;
  /** The primary highlight type. */
  highlight_type: HighlightType;
  /** All timeline events that contributed to this clip. */
  events: TimestampedEvent[];
  /** Human-readable title. */
  title: string;
  /** Human-readable subtitle (e.g. "Double Kill"). */
  subtitle: string | null;
}

/**
 * Determine the highlight type of a kill event based on player involvement.
 */
export function getKillHighlightType(
  event: TimestampedEvent,
  localPlayerName: string | null,
): HighlightType | null {
  const rawData = event.raw_data as {
    KillerName?: string;
    VictimName?: string;
    Assisters?: string[];
  } | null;

  if (!rawData) return null;

  const killer = rawData.KillerName;
  const victim = rawData.VictimName;
  const assisters = rawData.Assisters || [];

  if (!localPlayerName) return null;

  if (killer === localPlayerName) return "kill";
  if (victim === localPlayerName) return "death";
  if (assisters.includes(localPlayerName)) return "assist";

  return null; // Not involved — skip
}

/**
 * Result of multi-kill detection, including whether it's a trade kill.
 */
export interface MultiKillGroup {
  /** Kill events in the group. */
  kills: TimestampedEvent[];
  /** Death event if the player died during the multi-kill window. */
  death: TimestampedEvent | null;
  /** Whether this is a trade kill (player died during the sequence). */
  isTradeKill: boolean;
}

/**
 * Detect multi-kills from a sequence of kill events.
 * Returns groups of kills that happened within `windowSecs` seconds.
 * Also checks if the player died during the multi-kill window (trade kill).
 */
export function detectMultiKills(
  killEvents: TimestampedEvent[],
  deathEvents: TimestampedEvent[] = [],
  windowSecs: number = 12,
  localPlayerName?: string,
): MultiKillGroup[] {
  // Filter to only include kills where the player is the killer
  const playerKills = localPlayerName
    ? killEvents.filter((event) => {
        const rawData = event.raw_data as { KillerName?: string } | null;
        return rawData?.KillerName === localPlayerName;
      })
    : killEvents;

  if (playerKills.length === 0) return [];

  const sorted = [...playerKills].sort((a, b) => {
    const aTime = a.video_timestamp[0] + a.video_timestamp[1] / 1e9;
    const bTime = b.video_timestamp[0] + b.video_timestamp[1] / 1e9;
    return aTime - bTime;
  });

  const groups: MultiKillGroup[] = [];
  let currentGroup: TimestampedEvent[] = [sorted[0]];

  for (let i = 1; i < sorted.length; i++) {
    const prevTime =
      currentGroup[currentGroup.length - 1].video_timestamp[0] +
      currentGroup[currentGroup.length - 1].video_timestamp[1] / 1e9;
    const currTime = sorted[i].video_timestamp[0] + sorted[i].video_timestamp[1] / 1e9;

    if (currTime - prevTime <= windowSecs) {
      currentGroup.push(sorted[i]);
    } else {
      if (currentGroup.length >= 2) {
        // Check for death in this window
        const firstTime = currentGroup[0].video_timestamp[0] + currentGroup[0].video_timestamp[1] / 1e9;
        const lastTime = currentGroup[currentGroup.length - 1].video_timestamp[0] + currentGroup[currentGroup.length - 1].video_timestamp[1] / 1e9;
        const death = findDeathInWindow(deathEvents, firstTime, lastTime, windowSecs);
        
        groups.push({
          kills: currentGroup,
          death,
          isTradeKill: death !== null,
        });
      }
      currentGroup = [sorted[i]];
    }
  }

  // Handle last group
  if (currentGroup.length >= 2) {
    const firstTime = currentGroup[0].video_timestamp[0] + currentGroup[0].video_timestamp[1] / 1e9;
    const lastTime = currentGroup[currentGroup.length - 1].video_timestamp[0] + currentGroup[currentGroup.length - 1].video_timestamp[1] / 1e9;
    const death = findDeathInWindow(deathEvents, firstTime, lastTime, windowSecs);
    
    groups.push({
      kills: currentGroup,
      death,
      isTradeKill: death !== null,
    });
  }

  return groups;
}

/**
 * Find a death event within the multi-kill window.
 * Checks from the first kill to the last kill, plus the window buffer.
 */
function findDeathInWindow(
  deathEvents: TimestampedEvent[],
  firstKillTime: number,
  lastKillTime: number,
  windowSecs: number,
): TimestampedEvent | null {
  for (const death of deathEvents) {
    const deathTime = death.video_timestamp[0] + death.video_timestamp[1] / 1e9;
    // Death must occur within the window starting from first kill
    if (deathTime >= firstKillTime && deathTime <= lastKillTime + windowSecs) {
      return death;
    }
  }
  return null;
}

/**
 * Get the multi-kill label based on number of kills.
 */
export function getMultiKillLabel(count: number): string | null {
  switch (count) {
    case 2: return "Double Kill";
    case 3: return "Triple Kill";
    case 4: return "Quadra Kill";
    case 5: return "Penta Kill";
    default: return count > 5 ? `${count}x Kill` : null;
  }
}

/**
 * Get the highlight type color for display.
 */
export function getHighlightTypeColor(type: HighlightType): string {
  switch (type) {
    case "kill": return "#4ade80";       // green
    case "death": return "#f87171";       // red
    case "assist": return "#a78bfa";      // purple
    case "multi_kill": return "#f97316";  // orange
    case "trade_kill": return "#fbbf24";  // amber/yellow
  }
}

/**
 * Get a short icon/emoji for a highlight type.
 */
export function getHighlightTypeIcon(type: HighlightType): string {
  switch (type) {
    case "kill": return "⚔️";
    case "death": return "💀";
    case "assist": return "🤝";
    case "multi_kill": return "🔥";
    case "trade_kill": return "🔄";
  }
}

/**
 * Compute highlight clips from a game's timeline events.
 *
 * @param events - All timeline events for the game.
 * @param duration - Total video duration in seconds.
 * @param localPlayerName - The local player's summoner name (for kill/death classification).
 * @param settings - Highlight generation settings.
 * @returns Array of HighlightClip sorted by start_time.
 */
export function computeHighlights(
  events: TimestampedEvent[],
  duration: number,
  localPlayerName: string | null,
  settings: HighlightSettings = DEFAULT_HIGHLIGHT_SETTINGS,
): HighlightClip[] {
  const clips: HighlightClip[] = [];
  let clipId = 0;

  // Collect individual kill/assist/death events
  const playerKills: TimestampedEvent[] = [];
  const playerDeaths: TimestampedEvent[] = [];
  const playerAssists: TimestampedEvent[] = [];

  for (const event of events) {
    if (event.event_type === "kill") {
      const type = getKillHighlightType(event, localPlayerName);
      if (type === "kill" && settings.included_types.includes("kill")) {
        playerKills.push(event);
      } else if (type === "death" && settings.included_types.includes("death")) {
        playerDeaths.push(event);
      } else if (type === "assist" && settings.included_types.includes("assist")) {
        playerAssists.push(event);
      }
    }
  }

  // Create clips for individual kills
  for (const event of playerKills) {
    const eventTime = event.video_timestamp[0] + event.video_timestamp[1] / 1e9;
    const rawData = event.raw_data as { VictimName?: string } | null;
    const victimName = rawData?.VictimName || "enemy";

    clips.push({
      id: clipId++,
      start_time: Math.max(0, eventTime - settings.buffer_before),
      end_time: Math.min(duration, eventTime + settings.buffer_after),
      highlight_type: "kill",
      events: [event],
      title: `Kill`,
      subtitle: `Killed ${victimName}`,
    });
  }

  // Create clips for deaths
  for (const event of playerDeaths) {
    const eventTime = event.video_timestamp[0] + event.video_timestamp[1] / 1e9;
    const rawData = event.raw_data as { KillerName?: string } | null;
    const killerName = rawData?.KillerName || "enemy";

    clips.push({
      id: clipId++,
      start_time: Math.max(0, eventTime - settings.buffer_before),
      end_time: Math.min(duration, eventTime + settings.buffer_after),
      highlight_type: "death",
      events: [event],
      title: `Death`,
      subtitle: `Killed by ${killerName}`,
    });
  }

  // Create clips for assists
  for (const event of playerAssists) {
    const eventTime = event.video_timestamp[0] + event.video_timestamp[1] / 1e9;
    const rawData = event.raw_data as { KillerName?: string; VictimName?: string } | null;
    const killerName = rawData?.KillerName || "ally";
    const victimName = rawData?.VictimName || "enemy";

    clips.push({
      id: clipId++,
      start_time: Math.max(0, eventTime - settings.buffer_before),
      end_time: Math.min(duration, eventTime + settings.buffer_after),
      highlight_type: "assist",
      events: [event],
      title: `Assist`,
      subtitle: `Assisted ${killerName} vs ${victimName}`,
    });
  }

  // Detect multi-kills and trade kills (only if enabled)
  if (settings.included_types.includes("multi_kill") || settings.included_types.includes("trade_kill")) {
    const multiKillGroups = detectMultiKills(playerKills, playerDeaths);
    for (const group of multiKillGroups) {
      // Skip if neither multi_kill nor trade_kill is enabled
      if (!settings.included_types.includes("multi_kill") && !settings.included_types.includes("trade_kill")) {
        continue;
      }
      
      // If it's a trade kill but trade_kill is not enabled, skip
      if (group.isTradeKill && !settings.included_types.includes("trade_kill")) {
        continue;
      }
      
      // If it's a multi kill but multi_kill is not enabled, skip
      if (!group.isTradeKill && !settings.included_types.includes("multi_kill")) {
        continue;
      }
      
      const firstTime = group.kills[0].video_timestamp[0] + group.kills[0].video_timestamp[1] / 1e9;
      const lastTime =
        group.kills[group.kills.length - 1].video_timestamp[0] +
        group.kills[group.kills.length - 1].video_timestamp[1] / 1e9;
      const label = getMultiKillLabel(group.kills.length);

      // Include death event if present
      const allEvents = group.death ? [...group.kills, group.death] : group.kills;
      
      // Extend end time if death occurred after last kill
      const endTime = group.death
        ? Math.max(lastTime, group.death.video_timestamp[0] + group.death.video_timestamp[1] / 1e9)
        : lastTime;

      clips.push({
        id: clipId++,
        start_time: Math.max(0, firstTime - settings.buffer_before),
        end_time: Math.min(duration, endTime + settings.buffer_after),
        highlight_type: group.isTradeKill ? "trade_kill" : "multi_kill",
        events: allEvents,
        title: group.isTradeKill ? `Trade Kill` : (label || `${group.kills.length}x Kill`),
        subtitle: group.isTradeKill
          ? `${group.kills.length} kills but died in the trade`
          : `${group.kills.length} kills in quick succession`,
      });
    }
  }

  // Sort by start time
  clips.sort((a, b) => a.start_time - b.start_time);

  // Merge overlapping clips if enabled
  if (settings.merge_overlapping && clips.length > 1) {
    const merged: HighlightClip[] = [];
    let current = { ...clips[0] };

    for (let i = 1; i < clips.length; i++) {
      const next = clips[i];
      // Merge if overlapping or within merge gap
      if (next.start_time <= current.end_time + settings.merge_gap_secs) {
        // Extend current clip
        current.end_time = Math.max(current.end_time, next.end_time);
        current.events = [...current.events, ...next.events];
        // Keep the "more important" highlight type
        const typePriority: HighlightType[] = ["multi_kill", "trade_kill", "kill", "death", "assist"];
        const currentIdx = typePriority.indexOf(current.highlight_type);
        const nextIdx = typePriority.indexOf(next.highlight_type);
        if (nextIdx < currentIdx) {
          current.highlight_type = next.highlight_type;
          current.title = next.title;
          current.subtitle = next.subtitle;
        }
        // If both are kills, check if combined they form a multi-kill or trade kill
        // Only count kills where the player is the killer, not all kill events
        // Deduplicate by video_timestamp since same event can appear in multiple merged clips
        const seenTimestamps = new Set<string>();
        const playerKillEvents = current.events.filter(e => {
          if (e.event_type !== "kill") return false;
          const rawData = e.raw_data as { KillerName?: string } | null;
          if (rawData?.KillerName !== localPlayerName) return false;
          // Deduplicate by timestamp
          const key = `${e.video_timestamp[0]}-${e.video_timestamp[1]}`;
          if (seenTimestamps.has(key)) return false;
          seenTimestamps.add(key);
          return true;
        });
        const playerDeathEvents = current.events.filter(e => {
          if (e.event_type !== "kill") return false;
          const rawData = e.raw_data as { VictimName?: string } | null;
          if (rawData?.VictimName !== localPlayerName) return false;
          // Deduplicate by timestamp
          const key = `${e.video_timestamp[0]}-${e.video_timestamp[1]}`;
          if (seenTimestamps.has(key)) return false;
          seenTimestamps.add(key);
          return true;
        });
        if (playerKillEvents.length >= 2) {
          const label = getMultiKillLabel(playerKillEvents.length);
          if (label) {
            const isTradeKill = playerDeathEvents.length > 0;
            current.highlight_type = isTradeKill ? "trade_kill" : "multi_kill";
            current.title = isTradeKill ? `Trade Kill` : label;
            current.subtitle = isTradeKill
              ? `${playerKillEvents.length} kills but died in the trade`
              : `${playerKillEvents.length} kills`;
          }
        }
      } else {
        merged.push(current);
        current = { ...next };
      }
    }
    merged.push(current);

    // Re-enforce minimum duration and re-ID
    return merged
      .filter(c => c.end_time - c.start_time >= settings.min_duration)
      .map((c, idx) => ({ ...c, id: idx }));
  }

  // Filter by minimum duration and re-ID
  return clips
    .filter(c => c.end_time - c.start_time >= settings.min_duration)
    .map((c, idx) => ({ ...c, id: idx }));
}

/**
 * Get the category of an event for styling purposes.
 */
export function getEventCategory(eventType: string): EventCategory {
  switch (eventType.toLowerCase()) {
    case "kill":
      return "kill";
    case "death":
      return "death";
    case "objective":
      return "objective";
    case "game_end":
    case "game_start":
    case "match_found":
      return "game";
    case "phase_change":
      return "phase";
    case "lp_change":
      return "game";
    case "champ_select_start":
    case "champion_pick":
      return "phase";
    case "stats_update":
      return "unknown";
    default:
      return "unknown";
  }
}

/**
 * Get a human-readable label for an event type.
 */
export function getEventLabel(eventType: string): string {
  switch (eventType.toLowerCase()) {
    case "kill":
      return "Kill";
    case "death":
      return "Death";
    case "objective":
      return "Objective";
    case "game_end":
      return "Game End";
    case "game_start":
      return "Game Start";
    case "match_found":
      return "Match Found";
    case "phase_change":
      return "Phase Change";
    case "lp_change":
      return "LP Change";
    case "champ_select_start":
      return "Champ Select";
    case "champion_pick":
      return "Champion Pick";
    case "stats_update":
      return "Stats Update";
    default:
      return eventType;
  }
}

/**
 * Format a date string for display.
 */
export function formatDateTime(isoString: string): string {
  const date = new Date(isoString);
  return date.toLocaleString(undefined, {
    year: "numeric",
    month: "short",
    day: "numeric",
    hour: "2-digit",
    minute: "2-digit",
  });
}

/**
 * Format a relative time (e.g., "2 hours ago").
 */
export function formatRelativeTime(isoString: string): string {
  const date = new Date(isoString);
  const now = new Date();
  const diffMs = now.getTime() - date.getTime();
  const diffMins = Math.floor(diffMs / 60000);
  const diffHours = Math.floor(diffMins / 60);
  const diffDays = Math.floor(diffHours / 24);

  if (diffDays > 7) {
    return formatDateTime(isoString);
  } else if (diffDays > 0) {
    return `${diffDays} day${diffDays > 1 ? "s" : ""} ago`;
  } else if (diffHours > 0) {
    return `${diffHours} hour${diffHours > 1 ? "s" : ""} ago`;
  } else if (diffMins > 0) {
    return `${diffMins} minute${diffMins > 1 ? "s" : ""} ago`;
  } else {
    return "Just now";
  }
}

/**
 * Format game start time for display (e.g., "Today at 3:45 PM").
 */
export function formatGameStartTime(isoString: string): string {
  const date = new Date(isoString);
  const now = new Date();
  const isToday = date.toDateString() === now.toDateString();
  const yesterday = new Date(now);
  yesterday.setDate(yesterday.getDate() - 1);
  const isYesterday = date.toDateString() === yesterday.toDateString();
  
  const timeStr = date.toLocaleTimeString(undefined, {
    hour: "2-digit",
    minute: "2-digit",
  });
  
  if (isToday) {
    return `Today at ${timeStr}`;
  } else if (isYesterday) {
    return `Yesterday at ${timeStr}`;
  } else {
    return date.toLocaleDateString(undefined, {
      month: "short",
      day: "numeric",
    }) + ` at ${timeStr}`;
  }
}
