/**
 * Entity Resolver Service
 * Resolves players, teams, dates, and events with fuzzy matching
 */

import {
  ResolvedEntity,
  PlayerEntity,
  TeamEntity,
  DateEntity,
  EventEntity,
  MetricEntity,
  EntityType,
  PLAYER_NICKNAMES,
  TEAM_ALIASES,
  LEAGUE_SPORT_MAP,
} from './types';

// Prisma clients - lazy loaded
let mainPrisma: any = null;
let sportsPrisma: any = null;

async function getMainPrisma() {
  if (!mainPrisma) {
    try {
      const { PrismaClient } = await import('@prisma/client');
      mainPrisma = new PrismaClient();
    } catch (e) {
      console.warn('[EntityResolver] Main Prisma unavailable:', e);
      return null;
    }
  }
  return mainPrisma;
}

async function getSportsPrisma() {
  if (!sportsPrisma) {
    try {
      // Use the sports Prisma client generated in prisma_sports
      const sportsMod = require('../../../prisma_sports/generated/sports-client');
      sportsPrisma = new sportsMod.PrismaClient();
    } catch {
      // Fallback to main Prisma
      sportsPrisma = await getMainPrisma();
    }
  }
  return sportsPrisma;
}

/**
 * Levenshtein distance for fuzzy matching
 */
function levenshteinDistance(a: string, b: string): number {
  const matrix: number[][] = [];

  for (let i = 0; i <= b.length; i++) {
    matrix[i] = [i];
  }
  for (let j = 0; j <= a.length; j++) {
    matrix[0][j] = j;
  }

  for (let i = 1; i <= b.length; i++) {
    for (let j = 1; j <= a.length; j++) {
      if (b.charAt(i - 1) === a.charAt(j - 1)) {
        matrix[i][j] = matrix[i - 1][j - 1];
      } else {
        matrix[i][j] = Math.min(
          matrix[i - 1][j - 1] + 1,
          matrix[i][j - 1] + 1,
          matrix[i - 1][j] + 1
        );
      }
    }
  }

  return matrix[b.length][a.length];
}

/**
 * Calculate similarity score (0-1)
 */
function similarityScore(a: string, b: string): number {
  const aLower = a.toLowerCase().trim();
  const bLower = b.toLowerCase().trim();

  if (aLower === bLower) return 1;

  const maxLen = Math.max(aLower.length, bLower.length);
  if (maxLen === 0) return 1;

  const distance = levenshteinDistance(aLower, bLower);
  return 1 - distance / maxLen;
}

/**
 * Check if string contains another (partial match)
 */
function containsMatch(haystack: string, needle: string): boolean {
  return haystack.toLowerCase().includes(needle.toLowerCase());
}

export class EntityResolver {
  private playerCache: Map<string, PlayerEntity[]> = new Map();
  private teamCache: Map<string, TeamEntity[]> = new Map();
  private cacheExpiry: number = 5 * 60 * 1000; // 5 minutes
  private lastCacheUpdate: number = 0;

  /**
   * Resolve a player name to canonical form
   */
  async resolvePlayer(
    rawName: string,
    leagueHint?: string
  ): Promise<PlayerEntity | null> {
    const normalized = rawName.toLowerCase().trim();

    // Check nicknames first
    const nicknameMatch = PLAYER_NICKNAMES[normalized];
    if (nicknameMatch) {
      return this.searchPlayerInDatabase(nicknameMatch, leagueHint);
    }

    // Direct database search
    return this.searchPlayerInDatabase(rawName, leagueHint);
  }

  /**
   * Search for player in sports database with fuzzy matching
   */
  private async searchPlayerInDatabase(
    name: string,
    leagueHint?: string
  ): Promise<PlayerEntity | null> {
    try {
      const prisma = await getSportsPrisma();

      // Build where clause
      const whereClause: any = {
        name: {
          contains: name.split(' ')[0], // Search by first name/partial
        },
      };

      if (leagueHint) {
        whereClause.league = leagueHint.toUpperCase();
      }

      // Fetch candidates
      const candidates = await prisma.player.findMany({
        where: whereClause,
        take: 20,
      });

      // Also try the main database for NBA players
      const mainDb = await getMainPrisma();
      let nbaPlayers: any[] = [];
      try {
        nbaPlayers = await mainDb.nBAPlayer.findMany({
          where: {
            OR: [
              { fullName: { contains: name } },
              { firstName: { contains: name.split(' ')[0] } },
              { lastName: { contains: name.split(' ').slice(-1)[0] } },
            ],
          },
          take: 10,
        });
      } catch {
        // NBA table might not exist
      }

      // Combine and score candidates
      const scoredCandidates: Array<{ player: any; score: number; source: string }> = [];

      for (const player of candidates) {
        const score = similarityScore(name, player.name);
        if (score > 0.5) {
          scoredCandidates.push({ player, score, source: 'sports' });
        }
      }

      for (const player of nbaPlayers) {
        const score = similarityScore(name, player.fullName);
        if (score > 0.5) {
          scoredCandidates.push({
            player: { ...player, name: player.fullName, league: 'NBA' },
            score,
            source: 'main'
          });
        }
      }

      // Sort by score
      scoredCandidates.sort((a, b) => b.score - a.score);

      if (scoredCandidates.length === 0) {
        return null;
      }

      const best = scoredCandidates[0];
      const alternatives = scoredCandidates.slice(1, 4).map(c => ({
        value: c.player.name,
        confidence: c.score,
      }));

      // Check for injury status - include league filter to avoid cross-sport false positives
      let injuryStatus: string | undefined;
      try {
        const playerLeague = best.player.league?.toLowerCase();
        const whereClause: any = {
          status: { not: 'Healthy' },
        };

        // Add league filter if available (critical to avoid matching NHL Fox for NBA Fox)
        if (playerLeague) {
          whereClause.league = playerLeague;
        }

        // Try full name match first
        let injury = await prisma.playerInjury.findFirst({
          where: {
            ...whereClause,
            playerName: { contains: best.player.name, mode: 'insensitive' },
          },
          orderBy: { updatedAt: 'desc' },
        });

        // Only fall back to partial name if we have league filter
        if (!injury && playerLeague) {
          const namePart = best.player.name.split(' ')[1] || best.player.name;
          if (namePart.length > 2) {
            injury = await prisma.playerInjury.findFirst({
              where: {
                ...whereClause,
                playerName: { contains: namePart, mode: 'insensitive' },
              },
              orderBy: { updatedAt: 'desc' },
            });
          }
        }

        if (injury) {
          injuryStatus = injury.status;
        }
      } catch {
        // Injury table might not have data
      }

      return {
        type: 'player',
        raw: name,
        canonical: best.player.name,
        id: best.player.id?.toString(),
        playerId: best.player.id?.toString(),
        externalPlayerId: best.player.externalPlayerId,
        team: best.player.team,
        position: best.player.position,
        league: best.player.league?.toLowerCase() || 'unknown',
        confidence: best.score,
        alternatives,
        isActive: best.player.isActive,
        injuryStatus,
      };
    } catch (error) {
      console.error('Error searching for player:', error);
      return null;
    }
  }

  /**
   * Resolve a team name to canonical form
   */
  async resolveTeam(
    rawName: string,
    leagueHint?: string
  ): Promise<TeamEntity | null> {
    const normalized = rawName.toLowerCase().trim();

    // Check aliases first
    const aliasMatch = TEAM_ALIASES[normalized];
    if (aliasMatch) {
      if (!leagueHint || aliasMatch.league === leagueHint.toLowerCase()) {
        return {
          type: 'team',
          raw: rawName,
          canonical: aliasMatch.name,
          league: aliasMatch.league,
          confidence: 1.0,
        };
      }
    }

    // Search database
    return this.searchTeamInDatabase(rawName, leagueHint);
  }

  /**
   * Search for team in database
   */
  private async searchTeamInDatabase(
    name: string,
    leagueHint?: string
  ): Promise<TeamEntity | null> {
    try {
      const prisma = await getSportsPrisma();
      const mainDb = await getMainPrisma();

      // Search sports games for team names
      const games = await prisma.sportsGame.findMany({
        where: {
          OR: [
            { homeTeam: { contains: name } },
            { awayTeam: { contains: name } },
          ],
          ...(leagueHint ? { league: leagueHint.toUpperCase() } : {}),
        },
        take: 5,
        distinct: ['homeTeam'],
      });

      // Also check NBA teams table
      let nbaTeams: any[] = [];
      try {
        nbaTeams = await mainDb.nBATeam.findMany({
          where: {
            OR: [
              { fullName: { contains: name } },
              { nickname: { contains: name } },
              { abbreviation: { equals: name.toUpperCase() } },
              { city: { contains: name } },
            ],
          },
          take: 5,
        });
      } catch {
        // NBA table might not exist
      }

      const candidates: Array<{ team: string; score: number; league: string; abbrev?: string }> = [];

      for (const game of games) {
        const homeScore = similarityScore(name, game.homeTeam);
        const awayScore = similarityScore(name, game.awayTeam);

        if (homeScore > 0.4) {
          candidates.push({ team: game.homeTeam, score: homeScore, league: game.league });
        }
        if (awayScore > 0.4) {
          candidates.push({ team: game.awayTeam, score: awayScore, league: game.league });
        }
      }

      for (const team of nbaTeams) {
        const score = Math.max(
          similarityScore(name, team.fullName),
          similarityScore(name, team.nickname),
          similarityScore(name, team.city)
        );
        if (score > 0.4) {
          candidates.push({
            team: team.fullName,
            score,
            league: 'nba',
            abbrev: team.abbreviation,
          });
        }
      }

      // Sort and dedupe
      candidates.sort((a, b) => b.score - a.score);
      const seen = new Set<string>();
      const unique = candidates.filter(c => {
        if (seen.has(c.team)) return false;
        seen.add(c.team);
        return true;
      });

      if (unique.length === 0) {
        return null;
      }

      const best = unique[0];

      return {
        type: 'team',
        raw: name,
        canonical: best.team,
        league: best.league.toLowerCase(),
        abbreviation: best.abbrev,
        confidence: best.score,
        alternatives: unique.slice(1, 4).map(c => ({
          value: c.team,
          confidence: c.score,
        })),
      };
    } catch (error) {
      console.error('Error searching for team:', error);
      return null;
    }
  }

  /**
   * Parse and resolve date/time references
   */
  resolveDate(rawDate: string, referenceDate?: Date): DateEntity | null {
    const now = referenceDate || new Date();
    const normalized = rawDate.toLowerCase().trim();

    // Relative dates
    const relativeDates: Record<string, () => { start: Date; end: Date }> = {
      'today': () => {
        const start = new Date(now);
        start.setHours(0, 0, 0, 0);
        const end = new Date(now);
        end.setHours(23, 59, 59, 999);
        return { start, end };
      },
      'yesterday': () => {
        const start = new Date(now);
        start.setDate(start.getDate() - 1);
        start.setHours(0, 0, 0, 0);
        const end = new Date(start);
        end.setHours(23, 59, 59, 999);
        return { start, end };
      },
      'last night': () => relativeDates['yesterday'](),
      'last game': () => {
        // Return last 7 days to find most recent game
        const end = new Date(now);
        const start = new Date(now);
        start.setDate(start.getDate() - 7);
        return { start, end };
      },
      'this week': () => {
        const start = new Date(now);
        start.setDate(start.getDate() - start.getDay());
        start.setHours(0, 0, 0, 0);
        const end = new Date(start);
        end.setDate(end.getDate() + 6);
        end.setHours(23, 59, 59, 999);
        return { start, end };
      },
      'last week': () => {
        const start = new Date(now);
        start.setDate(start.getDate() - start.getDay() - 7);
        start.setHours(0, 0, 0, 0);
        const end = new Date(start);
        end.setDate(end.getDate() + 6);
        end.setHours(23, 59, 59, 999);
        return { start, end };
      },
      'this month': () => {
        const start = new Date(now.getFullYear(), now.getMonth(), 1);
        const end = new Date(now.getFullYear(), now.getMonth() + 1, 0, 23, 59, 59, 999);
        return { start, end };
      },
      'last month': () => {
        const start = new Date(now.getFullYear(), now.getMonth() - 1, 1);
        const end = new Date(now.getFullYear(), now.getMonth(), 0, 23, 59, 59, 999);
        return { start, end };
      },
      'this season': () => {
        // Assume current season based on month
        const year = now.getMonth() >= 9 ? now.getFullYear() : now.getFullYear() - 1;
        const start = new Date(year, 9, 1); // October 1
        const end = new Date(year + 1, 5, 30); // June 30
        return { start, end };
      },
      'last season': () => {
        const year = now.getMonth() >= 9 ? now.getFullYear() - 1 : now.getFullYear() - 2;
        const start = new Date(year, 9, 1);
        const end = new Date(year + 1, 5, 30);
        return { start, end };
      },
      'last year': () => {
        const start = new Date(now.getFullYear() - 1, 0, 1);
        const end = new Date(now.getFullYear() - 1, 11, 31, 23, 59, 59, 999);
        return { start, end };
      },
    };

    // Check relative dates
    for (const [key, fn] of Object.entries(relativeDates)) {
      if (normalized.includes(key)) {
        const { start, end } = fn();
        return {
          type: 'date',
          raw: rawDate,
          canonical: `${start.toISOString().split('T')[0]} to ${end.toISOString().split('T')[0]}`,
          startDate: start,
          endDate: end,
          isRange: true,
          confidence: 0.95,
        };
      }
    }

    // Day of week parsing
    const daysOfWeek = ['sunday', 'monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday'];
    const lastDayMatch = normalized.match(/last\s+(sunday|monday|tuesday|wednesday|thursday|friday|saturday)/);
    if (lastDayMatch) {
      const targetDay = daysOfWeek.indexOf(lastDayMatch[1]);
      const date = new Date(now);
      const currentDay = date.getDay();
      const daysAgo = currentDay >= targetDay
        ? currentDay - targetDay
        : 7 - (targetDay - currentDay);
      date.setDate(date.getDate() - (daysAgo || 7));
      date.setHours(0, 0, 0, 0);
      const endDate = new Date(date);
      endDate.setHours(23, 59, 59, 999);

      return {
        type: 'date',
        raw: rawDate,
        canonical: date.toISOString().split('T')[0],
        startDate: date,
        endDate: endDate,
        isRange: false,
        confidence: 0.9,
      };
    }

    // Week number parsing (NFL style)
    const weekMatch = normalized.match(/week\s*(\d{1,2})(?:,?\s*(\d{4}))?/);
    if (weekMatch) {
      const weekNum = parseInt(weekMatch[1]);
      const year = weekMatch[2] ? parseInt(weekMatch[2]) : now.getFullYear();
      // NFL season starts first Thursday of September
      const seasonStart = new Date(year, 8, 1); // September 1
      while (seasonStart.getDay() !== 4) {
        seasonStart.setDate(seasonStart.getDate() + 1);
      }
      const weekStart = new Date(seasonStart);
      weekStart.setDate(weekStart.getDate() + (weekNum - 1) * 7);
      const weekEnd = new Date(weekStart);
      weekEnd.setDate(weekEnd.getDate() + 6);

      return {
        type: 'date',
        raw: rawDate,
        canonical: `Week ${weekNum}, ${year}`,
        startDate: weekStart,
        endDate: weekEnd,
        isRange: true,
        week: weekNum,
        season: year,
        confidence: 0.85,
      };
    }

    // Season year parsing
    const seasonMatch = normalized.match(/(\d{4})(?:\s*-\s*(\d{2,4}))?\s*(?:season|playoffs?)?/);
    if (seasonMatch) {
      const startYear = parseInt(seasonMatch[1]);
      const endYear = seasonMatch[2]
        ? parseInt(seasonMatch[2].length === 2 ? `20${seasonMatch[2]}` : seasonMatch[2])
        : startYear + 1;

      const isPlayoffs = normalized.includes('playoff');

      let start: Date, end: Date;
      if (isPlayoffs) {
        start = new Date(endYear, 3, 1); // April
        end = new Date(endYear, 5, 30); // June
      } else {
        start = new Date(startYear, 9, 1); // October
        end = new Date(endYear, 5, 30); // June
      }

      return {
        type: 'date',
        raw: rawDate,
        canonical: `${startYear}-${endYear} ${isPlayoffs ? 'Playoffs' : 'Season'}`,
        startDate: start,
        endDate: end,
        isRange: true,
        season: startYear,
        periodType: isPlayoffs ? 'playoffs' : 'season',
        confidence: 0.9,
      };
    }

    // Specific date parsing
    const datePatterns = [
      /(\d{1,2})\/(\d{1,2})\/(\d{2,4})/,  // MM/DD/YYYY
      /(\d{4})-(\d{2})-(\d{2})/,           // YYYY-MM-DD
      /(jan|feb|mar|apr|may|jun|jul|aug|sep|oct|nov|dec)[a-z]*\s+(\d{1,2})(?:st|nd|rd|th)?,?\s*(\d{4})?/i,
    ];

    for (const pattern of datePatterns) {
      const match = normalized.match(pattern);
      if (match) {
        let date: Date;

        if (pattern.source.includes('jan|feb')) {
          const months: Record<string, number> = {
            jan: 0, feb: 1, mar: 2, apr: 3, may: 4, jun: 5,
            jul: 6, aug: 7, sep: 8, oct: 9, nov: 10, dec: 11,
          };
          const month = months[match[1].toLowerCase().slice(0, 3)];
          const day = parseInt(match[2]);
          const year = match[3] ? parseInt(match[3]) : now.getFullYear();
          date = new Date(year, month, day);
        } else if (match[1].length === 4) {
          date = new Date(parseInt(match[1]), parseInt(match[2]) - 1, parseInt(match[3]));
        } else {
          const year = match[3].length === 2 ? 2000 + parseInt(match[3]) : parseInt(match[3]);
          date = new Date(year, parseInt(match[1]) - 1, parseInt(match[2]));
        }

        const endDate = new Date(date);
        endDate.setHours(23, 59, 59, 999);

        return {
          type: 'date',
          raw: rawDate,
          canonical: date.toISOString().split('T')[0],
          startDate: date,
          endDate: endDate,
          isRange: false,
          confidence: 0.95,
        };
      }
    }

    return null;
  }

  /**
   * Resolve a metric/stat reference
   */
  resolveMetric(rawMetric: string): MetricEntity | null {
    const normalized = rawMetric.toLowerCase().trim();

    const metricMap: Record<string, { key: string; category: 'stat' | 'betting' | 'derived' }> = {
      // Stats
      'points': { key: 'points', category: 'stat' },
      'pts': { key: 'points', category: 'stat' },
      'rebounds': { key: 'rebounds', category: 'stat' },
      'rebs': { key: 'rebounds', category: 'stat' },
      'boards': { key: 'rebounds', category: 'stat' },
      'assists': { key: 'assists', category: 'stat' },
      'dimes': { key: 'assists', category: 'stat' },
      'steals': { key: 'steals', category: 'stat' },
      'blocks': { key: 'blocks', category: 'stat' },
      'turnovers': { key: 'turnovers', category: 'stat' },
      'minutes': { key: 'minutes', category: 'stat' },
      'passing yards': { key: 'passing_yards', category: 'stat' },
      'rushing yards': { key: 'rushing_yards', category: 'stat' },
      'touchdowns': { key: 'touchdowns', category: 'stat' },
      'tds': { key: 'touchdowns', category: 'stat' },
      'interceptions': { key: 'interceptions', category: 'stat' },
      'ints': { key: 'interceptions', category: 'stat' },
      'home runs': { key: 'home_runs', category: 'stat' },
      'hrs': { key: 'home_runs', category: 'stat' },
      'rbis': { key: 'rbi', category: 'stat' },
      'goals': { key: 'goals', category: 'stat' },

      // Betting
      'spread': { key: 'spread', category: 'betting' },
      'point spread': { key: 'spread', category: 'betting' },
      'ats': { key: 'spread', category: 'betting' },
      'over/under': { key: 'total', category: 'betting' },
      'o/u': { key: 'total', category: 'betting' },
      'total': { key: 'total', category: 'betting' },
      'moneyline': { key: 'moneyline', category: 'betting' },
      'ml': { key: 'moneyline', category: 'betting' },
      'odds': { key: 'odds', category: 'betting' },
      'props': { key: 'props', category: 'betting' },
      'player props': { key: 'player_props', category: 'betting' },

      // Derived
      'win rate': { key: 'win_rate', category: 'derived' },
      'avg': { key: 'average', category: 'derived' },
      'average': { key: 'average', category: 'derived' },
      'roi': { key: 'roi', category: 'derived' },
      'record': { key: 'record', category: 'derived' },
    };

    for (const [key, value] of Object.entries(metricMap)) {
      if (normalized.includes(key)) {
        return {
          type: 'metric',
          raw: rawMetric,
          canonical: value.key,
          metricKey: value.key,
          category: value.category,
          confidence: normalized === key ? 1.0 : 0.85,
        };
      }
    }

    return null;
  }

  /**
   * Resolve an event reference (Super Bowl, Finals, etc.)
   */
  async resolveEvent(
    rawEvent: string,
    leagueHint?: string
  ): Promise<EventEntity | null> {
    const normalized = rawEvent.toLowerCase().trim();

    const majorEvents: Record<string, { league: string; type: 'championship' | 'tournament' }> = {
      'super bowl': { league: 'nfl', type: 'championship' },
      'nba finals': { league: 'nba', type: 'championship' },
      'world series': { league: 'mlb', type: 'championship' },
      'stanley cup': { league: 'nhl', type: 'championship' },
      'march madness': { league: 'ncaab', type: 'tournament' },
      'ncaa tournament': { league: 'ncaab', type: 'tournament' },
      'playoffs': { league: leagueHint || 'unknown', type: 'tournament' },
      'championship': { league: leagueHint || 'unknown', type: 'championship' },
    };

    for (const [key, value] of Object.entries(majorEvents)) {
      if (normalized.includes(key)) {
        // Extract year if present
        const yearMatch = normalized.match(/(\d{4})/);
        const year = yearMatch ? parseInt(yearMatch[1]) : new Date().getFullYear();

        return {
          type: 'event',
          raw: rawEvent,
          canonical: `${key.split(' ').map(w => w.charAt(0).toUpperCase() + w.slice(1)).join(' ')} ${year}`,
          eventType: value.type,
          league: value.league,
          date: new Date(year, 1, 1), // Approximate
          confidence: 0.9,
        };
      }
    }

    return null;
  }

  /**
   * Detect league from context
   */
  detectLeague(text: string): string | null {
    const normalized = text.toLowerCase();

    const leaguePatterns: Record<string, RegExp[]> = {
      'nba': [/\bnba\b/, /basketball/, /lakers|celtics|warriors|heat|bulls|knicks|nets|bucks|suns|nuggets/],
      'nfl': [/\bnfl\b/, /football/, /chiefs|eagles|cowboys|49ers|patriots|bills|bengals|ravens/],
      'mlb': [/\bmlb\b/, /baseball/, /yankees|dodgers|red sox|cubs|mets|braves|astros/],
      'nhl': [/\bnhl\b/, /hockey/, /maple leafs|bruins|oilers|rangers|penguins|lightning/],
      'ncaab': [/\bncaa\s*b/, /college basketball/, /march madness/],
      'ncaaf': [/\bncaa\s*f/, /college football/, /cfb/],
      'ufc': [/\bufc\b/, /\bmma\b/, /octagon/],
    };

    for (const [league, patterns] of Object.entries(leaguePatterns)) {
      for (const pattern of patterns) {
        if (pattern.test(normalized)) {
          return league;
        }
      }
    }

    return null;
  }

  /**
   * Batch resolve multiple entities from text
   */
  async resolveAllEntities(
    text: string,
    leagueHint?: string
  ): Promise<ResolvedEntity[]> {
    const entities: ResolvedEntity[] = [];
    const detectedLeague = leagueHint || this.detectLeague(text);

    // Extract potential player names (capitalized words)
    const playerPattern = /\b([A-Z][a-z]+(?:\s+[A-Z][a-z]+)+)\b/g;
    const potentialPlayers = text.match(playerPattern) || [];

    for (const name of potentialPlayers) {
      const resolved = await this.resolvePlayer(name, detectedLeague || undefined);
      if (resolved && resolved.confidence > 0.6) {
        entities.push(resolved);
      }
    }

    // Check for nicknames
    for (const [nickname] of Object.entries(PLAYER_NICKNAMES)) {
      if (text.toLowerCase().includes(nickname)) {
        const resolved = await this.resolvePlayer(nickname, detectedLeague || undefined);
        if (resolved) {
          entities.push(resolved);
        }
      }
    }

    // Check for team aliases
    for (const [alias] of Object.entries(TEAM_ALIASES)) {
      if (text.toLowerCase().includes(alias)) {
        const resolved = await this.resolveTeam(alias, detectedLeague || undefined);
        if (resolved) {
          entities.push(resolved);
        }
      }
    }

    // Extract date references
    const datePatterns = [
      /\b(today|yesterday|last night|last game|this week|last week|this month|last month|this season|last season|last year)\b/gi,
      /\blast\s+(sunday|monday|tuesday|wednesday|thursday|friday|saturday)\b/gi,
      /\bweek\s*\d{1,2}(?:,?\s*\d{4})?\b/gi,
      /\b\d{4}(?:\s*-\s*\d{2,4})?\s*(?:season|playoffs?)?\b/gi,
      /\b\d{1,2}\/\d{1,2}\/\d{2,4}\b/g,
      /\b(?:jan|feb|mar|apr|may|jun|jul|aug|sep|oct|nov|dec)[a-z]*\s+\d{1,2}(?:st|nd|rd|th)?,?\s*\d{4}?\b/gi,
    ];

    for (const pattern of datePatterns) {
      const matches = text.match(pattern) || [];
      for (const match of matches) {
        const resolved = this.resolveDate(match);
        if (resolved) {
          entities.push(resolved);
        }
      }
    }

    // Extract metric references
    const metricKeywords = [
      'points', 'pts', 'rebounds', 'assists', 'steals', 'blocks',
      'spread', 'over/under', 'o/u', 'moneyline', 'ml', 'props',
      'passing yards', 'rushing yards', 'touchdowns', 'home runs',
    ];

    for (const keyword of metricKeywords) {
      if (text.toLowerCase().includes(keyword)) {
        const resolved = this.resolveMetric(keyword);
        if (resolved) {
          entities.push(resolved);
        }
      }
    }

    // Check for major events
    const eventKeywords = ['super bowl', 'nba finals', 'world series', 'stanley cup', 'march madness', 'playoffs'];
    for (const keyword of eventKeywords) {
      if (text.toLowerCase().includes(keyword)) {
        const resolved = await this.resolveEvent(keyword, detectedLeague || undefined);
        if (resolved) {
          entities.push(resolved);
        }
      }
    }

    return entities;
  }
}

export const entityResolver = new EntityResolver();
