// services/algolia-enhanced.ts
// DEPRECATED: This service is no longer used by components
// All search functionality now goes through the backend service via naturalLanguageSearchEnhanced.ts
import { algoliasearch } from 'algoliasearch';
import type { PokemonData, MoveData, AbilityData, TypeEffectivenessData } from '../types/pokemon';
// Interfaces for enhanced Algolia service
interface QueryIntent {
originalQuery: string;
queryType: 'counter' | 'team_build' | 'general';
targetPokemon?: string;
teamRequirements?: TeamRequirementsData;
filters: QueryFilters;
}
interface TeamRequirementsData {
strategy?: string;
types?: string[];
tier?: string;
}
interface QueryFilters {
types?: string[];
tiers?: string[];
minSpeed?: number;
minAttack?: number;
minDefense?: number;
generation?: number;
}
interface SearchResult {
hits: unknown[];
facets?: unknown;
}
interface ConsolidatedResults {
pokemon: unknown[];
moves: unknown[];
abilities: unknown[];
typeMatchups: unknown[];
targetPokemon?: unknown; // For counter queries, the Pokemon being countered
counterPokemon?: unknown[]; // For counter queries, the Pokemon that counter the target
}
const ALGOLIA_APP_ID = import.meta.env.VITE_ALGOLIA_APP_ID || 'demo_app_id';
const ALGOLIA_API_KEY = import.meta.env.VITE_ALGOLIA_API_KEY || 'demo_api_key';
// Index names
const INDICES = {
POKEMON: 'pokemon',
MOVES: 'pokemon_moves',
ABILITIES: 'abilities',
TYPE_EFFECTIVENESS: 'type_effectiveness',
ITEMS: 'items',
COMPETITIVE_STATS: 'competitive_stats',
TEAM_COMPOSITIONS: 'team_compositions',
TEAM_SYNERGIES: 'team_synergies',
META_ANALYSIS: 'meta_analysis'
};
const client = algoliasearch(ALGOLIA_APP_ID, ALGOLIA_API_KEY);
export interface BattleQueryResult {
pokemon: PokemonData[];
moves: MoveData[];
abilities: AbilityData[];
typeMatchups: TypeEffectivenessData[];
recommendations: BattleRecommendation[];
}
export interface BattleRecommendation {
pokemon: string;
types: string[];
battleRole: string;
tier: string;
matchupRating: number;
reasoning: string;
recommendedMoves: string[];
recommendedAbility: string;
recommendedItem: string;
stats: { hp: number; attack: number; defense: number; specialAttack: number; specialDefense: number; speed: number; total: number; };
abilities: string[];
synergies?: string[];
threats?: string[];
}
export class EnhancedAlgoliaService {
// Natural language query processor
async processBattleQuery(query: string): Promise<BattleQueryResult> {
const intent = this.parseQueryIntent(query);
const multiIndexResults = await this.performMultiIndexSearch(intent);
const recommendations = await this.generateBattleRecommendations(multiIndexResults, intent);
return {
pokemon: multiIndexResults.pokemon as PokemonData[],
moves: multiIndexResults.moves as MoveData[],
abilities: multiIndexResults.abilities as AbilityData[],
typeMatchups: multiIndexResults.typeMatchups as TypeEffectivenessData[],
recommendations
};
}
private parseQueryIntent(query: string): QueryIntent {
const lowerQuery = query.toLowerCase();
const intent: QueryIntent = {
originalQuery: query,
queryType: 'general',
filters: {}
};
// Detect counter/matchup queries
if (lowerQuery.includes('counter') || lowerQuery.includes('beat') || lowerQuery.includes('vs') ||
lowerQuery.includes('against') || lowerQuery.includes('wall') || lowerQuery.includes('resist')) {
intent.queryType = 'counter';
// Try multiple patterns for target Pokemon/type
const patterns = [
/(?:counter|beat|vs\.?|against)\s+([a-zA-Z]+)/i,
/(?:wall|resist).*?(?:against)\s+([a-zA-Z]+)/i,
/(?:defensive|wall).*?([a-zA-Z]+)\s+type/i,
/what\s+(?:counter|beat|vs\.?|against)\s+([a-zA-Z]+)/i,
/counters?\s+([a-zA-Z]+)/i
];
for (const pattern of patterns) {
const match = query.match(pattern);
if (match) {
intent.targetPokemon = match[1];
break;
}
}
}
// Detect team building queries
if (lowerQuery.includes('team') || lowerQuery.includes('build')) {
intent.queryType = 'team_build';
intent.teamRequirements = this.extractTeamRequirements(query);
}
// Extract filters
intent.filters = this.extractQueryFilters(query);
return intent;
}
private extractTeamRequirements(query: string) {
return {
strategy: this.extractStrategy(query),
types: this.extractTypes(query),
tier: this.extractTier(query)
};
}
private extractStrategy(query: string): string {
const strategies = ['rain', 'sun', 'sand', 'hail', 'trick room', 'hyper offense',
'stall', 'balance', 'bulky offense'];
const lowerQuery = query.toLowerCase();
return strategies.find(s => lowerQuery.includes(s)) || '';
}
private extractTypes(query: string): string[] {
const types = ['normal', 'fire', 'water', 'electric', 'grass', 'ice', 'fighting',
'poison', 'ground', 'flying', 'psychic', 'bug', 'rock', 'ghost',
'dragon', 'dark', 'steel', 'fairy'];
const found: string[] = [];
const lowerQuery = query.toLowerCase();
types.forEach(type => {
if (lowerQuery.includes(type)) {
found.push(type.charAt(0).toUpperCase() + type.slice(1));
}
});
return found;
}
private extractTier(query: string): string {
const tiers = ['OU', 'UU', 'RU', 'NU', 'PU', 'LC', 'Ubers'];
const upperQuery = query.toUpperCase();
return tiers.find(tier => upperQuery.includes(tier)) || '';
}
private extractQueryFilters(query: string): QueryFilters {
const filters: QueryFilters = {};
// Type filters
const typeRegex = /\b(normal|fire|water|electric|grass|ice|fighting|poison|ground|flying|psychic|bug|rock|ghost|dragon|dark|steel|fairy)\b/gi;
const types = query.match(typeRegex);
if (types) {
filters.types = [...new Set(types.map(t => t.charAt(0).toUpperCase() + t.slice(1).toLowerCase()))];
}
// Tier filters
const tierRegex = /\b(OU|UU|RU|NU|PU|LC|Ubers)\b/g;
const tiers = query.match(tierRegex);
if (tiers) {
filters.tiers = tiers;
}
// Stat filters
if (query.includes('fast') || query.includes('speed')) {
filters.minSpeed = 100;
}
if (query.includes('bulky') || query.includes('defensive')) {
filters.minDefense = 100;
}
if (query.includes('offensive') || query.includes('attacker')) {
filters.minAttack = 100;
}
// Generation filters
const genRegex = /gen(?:eration)?\s*(\d+)/i;
const genMatch = query.match(genRegex);
if (genMatch) {
filters.generation = parseInt(genMatch[1]);
}
return filters;
}
private buildGeneralFilters(filters: QueryFilters): string {
const filterStrings = [];
if (filters.types && filters.types.length > 0) {
const typeFilters = filters.types.map((type: string) => `types:"${type}"`).join(' OR ');
filterStrings.push(`(${typeFilters})`);
}
if (filters.tiers && filters.tiers.length > 0) {
const tierFilters = filters.tiers.map((tier: string) => `competitiveTier:"${tier}"`).join(' OR ');
filterStrings.push(`(${tierFilters})`);
}
if (filters.minSpeed) {
filterStrings.push(`stats.speed >= ${filters.minSpeed}`);
}
if (filters.minAttack) {
filterStrings.push(`stats.attack >= ${filters.minAttack}`);
}
if (filters.minDefense) {
filterStrings.push(`stats.defense >= ${filters.minDefense}`);
}
if (filters.generation) {
filterStrings.push(`generation:${filters.generation}`);
}
return filterStrings.join(' AND ');
}
private async performMultiIndexSearch(intent: QueryIntent): Promise<ConsolidatedResults> {
const searches = [];
if (intent.targetPokemon) {
// Check if target is a type (like "Dragon") or Pokemon name
const isType = this.isValidType(intent.targetPokemon);
if (isType) {
// Handle type-based counter queries (e.g., "walls against Dragon types")
const typeCounterFilters = this.buildTypeCounterFilters(intent.targetPokemon, intent.originalQuery);
searches.push(
client.searchSingleIndex({
indexName: INDICES.POKEMON,
searchParams: {
query: '',
filters: typeCounterFilters,
hitsPerPage: 20,
facets: ['types', 'competitiveTier', 'battleRole']
}
})
);
} else {
// Handle Pokemon-specific counter queries
const targetSearch = await client.searchSingleIndex({
indexName: INDICES.POKEMON,
searchParams: {
query: intent.targetPokemon,
hitsPerPage: 1
}
});
if (targetSearch.hits.length > 0) {
const targetPokemon = targetSearch.hits[0] as PokemonData;
// Find Pokemon that counter the target
const counterFilters = this.buildCounterFilters(targetPokemon);
const counterSearch = await client.searchSingleIndex({
indexName: INDICES.POKEMON,
searchParams: {
query: '',
filters: counterFilters,
hitsPerPage: 20,
facets: ['types', 'competitiveTier', 'battleRole']
}
});
// Find moves that are super effective
const moveFilters = this.buildMoveFilters(targetPokemon);
const moveSearch = await client.searchSingleIndex({
indexName: INDICES.MOVES,
searchParams: {
query: '',
filters: moveFilters,
hitsPerPage: 30
}
});
// Return consolidated results with proper separation
return this.consolidateCounterResults(targetSearch, counterSearch, moveSearch);
}
}
} else {
// Fallback to general counter search based on other query terms
const generalFilters = this.buildGeneralFilters(intent.filters);
searches.push(
client.searchSingleIndex({
indexName: INDICES.POKEMON,
searchParams: {
query: intent.originalQuery,
filters: generalFilters,
hitsPerPage: 20,
facets: ['types', 'competitiveTier', 'battleRole']
}
})
);
}
// Search abilities if mentioned
if (intent.originalQuery.toLowerCase().includes('ability')) {
searches.push(
client.searchSingleIndex({
indexName: INDICES.ABILITIES,
searchParams: {
query: intent.originalQuery,
hitsPerPage: 10
}
})
);
}
const results = await Promise.all(searches);
return this.consolidateResults(results, intent);
}
private consolidateResults(results: SearchResult[], intent: QueryIntent): ConsolidatedResults {
const consolidated: ConsolidatedResults = {
pokemon: [],
moves: [],
abilities: [],
typeMatchups: []
};
// Consolidate results based on intent type
if (intent.queryType === 'counter') {
consolidated.pokemon = results[0]?.hits || [];
consolidated.moves = results[1]?.hits || [];
} else {
consolidated.pokemon = results[0]?.hits || [];
if (results[1]) {
consolidated.abilities = results[1].hits || [];
}
}
return consolidated;
}
private consolidateCounterResults(
targetResult: SearchResult,
counterResult: SearchResult,
moveResult: SearchResult
): ConsolidatedResults {
return {
pokemon: [targetResult.hits[0]], // Show target Pokemon in search results
moves: moveResult.hits || [],
abilities: [],
typeMatchups: [],
targetPokemon: targetResult.hits[0],
counterPokemon: counterResult.hits || []
};
}
private buildCounterFilters(targetPokemon: PokemonData): string {
const filters = [];
// Find Pokemon with types that are super effective against target
if (targetPokemon.typeEffectiveness?.weakTo) {
const typeFilters = targetPokemon.typeEffectiveness.weakTo
.map(type => `types:"${type}"`)
.join(' OR ');
filters.push(`(${typeFilters})`);
}
// Filter by competitive viability
if (targetPokemon.competitiveTier) {
const tierPriority = ['OU', 'UU', 'RU', 'NU'];
const targetTierIndex = tierPriority.indexOf(targetPokemon.competitiveTier);
if (targetTierIndex !== -1) {
const viableTiers = tierPriority.slice(0, targetTierIndex + 2);
filters.push(`(${viableTiers.map(tier => `competitiveTier:"${tier}"`).join(' OR ')})`);
}
}
// Ensure we don't get the same Pokemon
filters.push(`NOT name:"${targetPokemon.name}"`);
return filters.join(' AND ');
}
private buildMoveFilters(targetPokemon: PokemonData): string {
const filters = [];
console.log('Building move filters for target Pokemon:', targetPokemon.name);
// Find moves that are super effective
if (targetPokemon.types) {
const effectiveTypes = this.getEffectiveTypes(targetPokemon.types);
console.log('Target Pokemon types:', targetPokemon.types, 'Effective types against it:', effectiveTypes);
if (effectiveTypes.length > 0) {
const typeFilter = `(${effectiveTypes.map(type => `type:"${type}"`).join(' OR ')})`;
console.log('Adding type effectiveness filter:', typeFilter);
filters.push(typeFilter);
}
}
const finalFilter = filters.join(' AND ');
console.log('Final move filter:', finalFilter);
return finalFilter;
}
private getEffectiveTypes(defenderTypes: string[]): string[] {
// Simplified type effectiveness chart
const typeChart: Record<string, string[]> = {
'Normal': ['Fighting'],
'Fire': ['Water', 'Ground', 'Rock'],
'Water': ['Electric', 'Grass'],
'Electric': ['Ground'],
'Grass': ['Fire', 'Ice', 'Poison', 'Flying', 'Bug'],
'Ice': ['Fire', 'Fighting', 'Rock', 'Steel'],
'Fighting': ['Flying', 'Psychic', 'Fairy'],
'Poison': ['Ground', 'Psychic'],
'Ground': ['Water', 'Grass', 'Ice'],
'Flying': ['Electric', 'Ice', 'Rock'],
'Psychic': ['Bug', 'Ghost', 'Dark'],
'Bug': ['Fire', 'Flying', 'Rock'],
'Rock': ['Water', 'Grass', 'Fighting', 'Ground', 'Steel'],
'Ghost': ['Ghost', 'Dark'],
'Dragon': ['Ice', 'Dragon', 'Fairy'],
'Dark': ['Fighting', 'Bug', 'Fairy'],
'Steel': ['Fire', 'Fighting', 'Ground'],
'Fairy': ['Poison', 'Steel']
};
const effectiveTypes = new Set<string>();
defenderTypes.forEach(type => {
if (typeChart[type]) {
typeChart[type].forEach(effective => effectiveTypes.add(effective));
}
});
return Array.from(effectiveTypes);
}
private async generateBattleRecommendations(
results: ConsolidatedResults,
intent: QueryIntent
): Promise<BattleRecommendation[]> {
const recommendations: BattleRecommendation[] = [];
if (intent.queryType === 'counter' && results.counterPokemon && results.counterPokemon.length > 0) {
// Generate counter recommendations from the counter Pokemon, not the target
for (const pokemon of results.counterPokemon.slice(0, 5)) {
const pokemonData = pokemon as PokemonData;
const recommendation = await this.createCounterRecommendation(
pokemonData,
intent.targetPokemon || 'target',
results.moves as MoveData[]
);
recommendations.push(recommendation);
}
} else if (intent.queryType === 'team_build') {
// Generate team building recommendations
const teamRoles = ['Physical Sweeper', 'Special Sweeper', 'Tank', 'Support', 'Pivot', 'Wallbreaker'];
const roleFilledMap = new Map<string, PokemonData>();
for (const pokemon of results.pokemon) {
const pokemonData = pokemon as PokemonData;
const role = this.determineBattleRole(pokemonData);
if (!roleFilledMap.has(role) && teamRoles.includes(role)) {
roleFilledMap.set(role, pokemonData);
const recommendation = await this.createTeamRecommendation(pokemonData, role);
recommendations.push(recommendation);
}
if (roleFilledMap.size >= 6) break;
}
} else if (results.pokemon && results.pokemon.length > 0) {
// Generate general recommendations for any Pokemon found
for (const pokemon of results.pokemon.slice(0, 5)) {
const pokemonData = pokemon as PokemonData;
const recommendation = await this.createGeneralRecommendation(pokemonData);
recommendations.push(recommendation);
}
}
// Sort by matchup rating
return recommendations.sort((a, b) => b.matchupRating - a.matchupRating);
}
private async createCounterRecommendation(
counter: PokemonData,
targetName: string,
availableMoves: MoveData[]
): Promise<BattleRecommendation> {
// Find best moves for this counter
const suggestedMoves = this.selectOptimalMoves(counter, availableMoves);
// Calculate matchup score
const matchupScore = this.calculateDetailedMatchupScore(counter);
// Generate reasoning
const reasoning = this.generateCounterReasoning(counter, targetName);
return {
pokemon: counter.name,
types: counter.types,
battleRole: counter.battleRole || 'Mixed Attacker',
tier: counter.competitiveTier || 'OU',
matchupRating: matchupScore,
reasoning,
recommendedMoves: suggestedMoves.map((m: MoveData) => m.name),
recommendedAbility: counter.abilities?.[0] || 'Unknown',
recommendedItem: this.suggestItem(counter),
stats: counter.stats,
abilities: counter.abilities || []
};
}
private determineBattleRole(pokemon: PokemonData): string {
const stats = pokemon.stats;
if (stats.attack > stats.specialAttack && stats.speed > 95) {
return 'Physical Sweeper';
} else if (stats.specialAttack > stats.attack && stats.speed > 95) {
return 'Special Sweeper';
} else if (stats.defense > 100 && stats.specialDefense > 100) {
return 'Tank';
} else if (pokemon.keyMoves?.some(move =>
['Stealth Rock', 'Spikes', 'Toxic Spikes', 'Heal Bell', 'Aromatherapy'].includes(move)
)) {
return 'Support';
} else if (stats.speed > 100 && pokemon.keyMoves?.some(move =>
['U-turn', 'Volt Switch', 'Flip Turn', 'Parting Shot'].includes(move)
)) {
return 'Pivot';
} else if (stats.attack > 120 || stats.specialAttack > 120) {
return 'Wallbreaker';
}
return 'Mixed Attacker';
}
private suggestItem(pokemon: PokemonData): string {
// Item suggestion logic based on Pokemon characteristics
if (pokemon.stats.speed > 100) {
return 'Choice Scarf';
} else if (pokemon.stats.attack > 120) {
return 'Choice Band';
} else if (pokemon.stats.specialAttack > 120) {
return 'Choice Specs';
} else if (pokemon.stats.hp > 90) {
return 'Leftovers';
}
return 'Life Orb';
}
private async createTeamRecommendation(pokemon: PokemonData, role: string): Promise<BattleRecommendation> {
return {
pokemon: pokemon.name,
types: pokemon.types,
battleRole: role,
tier: pokemon.competitiveTier || 'OU',
matchupRating: 75, // Default rating for team recommendations
reasoning: `Strong ${role} option with good stats and movepool`,
recommendedMoves: pokemon.keyMoves?.slice(0, 4) || [],
recommendedAbility: pokemon.abilities?.[0] || 'Unknown',
recommendedItem: this.suggestItem(pokemon),
stats: pokemon.stats,
abilities: pokemon.abilities || []
};
}
private async createGeneralRecommendation(pokemon: PokemonData): Promise<BattleRecommendation> {
const role = this.determineBattleRole(pokemon);
const matchupScore = this.calculateGeneralScore(pokemon);
return {
pokemon: pokemon.name,
types: pokemon.types,
battleRole: role,
tier: pokemon.competitiveTier || 'OU',
matchupRating: matchupScore,
reasoning: this.generateGeneralReasoning(pokemon),
recommendedMoves: pokemon.keyMoves?.slice(0, 4) || [],
recommendedAbility: pokemon.abilities?.[0] || 'Unknown',
recommendedItem: this.suggestItem(pokemon),
stats: pokemon.stats,
abilities: pokemon.abilities || []
};
}
private calculateGeneralScore(pokemon: PokemonData): number {
let score = 50;
// Competitive tier bonus
const tierBonus: Record<string, number> = { 'OU': 20, 'UU': 15, 'RU': 10, 'NU': 5 };
score += tierBonus[pokemon.competitiveTier || ''] || 0;
// Stats bonus
score += Math.min((pokemon.stats.total - 400) / 10, 25);
return Math.max(Math.min(score, 95), 30);
}
private generateGeneralReasoning(pokemon: PokemonData): string {
const reasons = [];
if (pokemon.competitiveTier === 'OU') {
reasons.push('Top-tier competitive Pokemon');
}
if (pokemon.stats.total > 500) {
reasons.push(`Strong overall stats (${pokemon.stats.total} BST)`);
}
if (pokemon.keyMoves && pokemon.keyMoves.length > 0) {
reasons.push('Versatile movepool with key competitive moves');
}
if (pokemon.abilities && pokemon.abilities.length > 1) {
reasons.push('Multiple ability options for different strategies');
}
return reasons.length > 0 ? reasons.join('. ') + '.' : `Solid competitive option with good potential.`;
}
private selectOptimalMoves(pokemon: PokemonData, availableMoves: MoveData[]): MoveData[] {
// Simple implementation - return first 4 available moves or key moves
if (availableMoves && availableMoves.length > 0) {
return availableMoves.slice(0, 4);
}
// Fallback to creating move objects from key moves
return (pokemon.keyMoves || []).slice(0, 4).map((moveName: string) => ({
name: moveName,
type: 'Normal',
category: 'Physical' as const,
power: 80,
accuracy: 100,
pp: 15,
priority: 0,
description: `Move: ${moveName}`,
effect: 'Deals damage',
target: 'Single',
makesContact: true,
generation: 1
}));
}
private calculateDetailedMatchupScore(counter: PokemonData): number {
let score = 50;
// Type effectiveness bonus
if (counter.typeEffectiveness?.weakTo?.some(type => counter.types.includes(type))) {
score += 25;
}
// Speed tier bonus
score += Math.min((counter.stats.speed - 80) / 4, 20);
// Defensive capability
const defensiveRating = (counter.stats.defense + counter.stats.specialDefense) / 2;
score += Math.min((defensiveRating - 80) / 5, 15);
return Math.max(Math.min(score, 95), 20);
}
private generateCounterReasoning(counter: PokemonData, targetName: string): string {
const reasons = [];
// Type advantage
if (counter.typeEffectiveness?.weakTo?.some(type => counter.types.includes(type))) {
reasons.push(`Has type advantage with ${counter.types.join('/')}`);
}
// Speed advantage
if (counter.stats.speed > 100) {
reasons.push(`High speed (${counter.stats.speed}) allows it to outspeed most threats`);
}
// Defensive capabilities
if (counter.stats.defense > 100 || counter.stats.specialDefense > 100) {
reasons.push(`Strong defensive stats allow it to tank hits`);
}
// Offensive power
if (counter.stats.attack > 100 || counter.stats.specialAttack > 100) {
reasons.push(`High offensive stats to deal significant damage`);
}
return reasons.length > 0 ? reasons.join('. ') + '.' : `Solid counter option against ${targetName}.`;
}
private isValidType(typeStr: string): boolean {
const types = ['normal', 'fire', 'water', 'electric', 'grass', 'ice', 'fighting',
'poison', 'ground', 'flying', 'psychic', 'bug', 'rock', 'ghost',
'dragon', 'dark', 'steel', 'fairy'];
return types.includes(typeStr.toLowerCase());
}
private buildTypeCounterFilters(targetType: string, originalQuery: string): string {
const filters = [];
const lowerQuery = originalQuery.toLowerCase();
// Get types that resist or are immune to the target type
const resistantTypes = this.getResistantTypes(targetType);
if (resistantTypes.length > 0) {
const typeFilters = resistantTypes.map(type => `types:"${type}"`).join(' OR ');
filters.push(`(${typeFilters})`);
}
// If query mentions "defensive" or "wall", prioritize defensive Pokemon
if (lowerQuery.includes('defensive') || lowerQuery.includes('wall')) {
filters.push('(stats.defense >= 100 OR stats.specialDefense >= 100)');
filters.push('battleRole:"Tank"');
}
// Filter by competitive viability
filters.push('(competitiveTier:"OU" OR competitiveTier:"UU" OR competitiveTier:"RU")');
return filters.join(' AND ');
}
private getResistantTypes(attackingType: string): string[] {
// Simplified type resistance chart - types that resist the attacking type
const resistanceChart: Record<string, string[]> = {
'Normal': [],
'Fire': ['Fire', 'Water', 'Rock', 'Dragon'],
'Water': ['Water', 'Grass', 'Dragon'],
'Electric': ['Electric', 'Grass', 'Dragon'],
'Grass': ['Fire', 'Grass', 'Poison', 'Flying', 'Bug', 'Dragon', 'Steel'],
'Ice': ['Fire', 'Water', 'Ice', 'Steel'],
'Fighting': ['Flying', 'Poison', 'Psychic', 'Bug', 'Ghost', 'Fairy'],
'Poison': ['Fighting', 'Poison', 'Bug', 'Rock', 'Ghost'],
'Ground': ['Grass', 'Bug', 'Flying'],
'Flying': ['Electric', 'Rock', 'Steel'],
'Psychic': ['Fighting', 'Psychic', 'Steel'],
'Bug': ['Fire', 'Fighting', 'Poison', 'Flying', 'Ghost', 'Steel', 'Fairy'],
'Rock': ['Fighting', 'Ground', 'Steel'],
'Ghost': ['Normal', 'Dark'],
'Dragon': ['Steel'],
'Dark': ['Fighting', 'Dark', 'Fairy'],
'Steel': ['Fire', 'Water', 'Electric', 'Steel'],
'Fairy': ['Fire', 'Poison', 'Steel']
};
const typeKey = attackingType.charAt(0).toUpperCase() + attackingType.slice(1).toLowerCase();
return resistanceChart[typeKey] || [];
}
}
// Export singleton instance
export const enhancedAlgoliaService = new EnhancedAlgoliaService();