// @lifecycle canonical - Resolves dependencies and merges execution context state.
/**
 * Context Resolution System
 *
 * Intelligent context resolution with priority-based fallbacks that replaces
 * the hardcoded {{previous_message}} pattern with flexible, multi-source context aggregation.
 *
 * Features:
 * - Priority-based context resolution strategies
 * - Multi-source context aggregation
 * - Context validation and sanitization
 * - Smart fallback context generation
 * - Context caching for performance
 */

import { Logger } from '../../../infra/logging/index.js';

import type { PromptArgument } from '../../../shared/types/index.js';

/**
 * Context source types
 */
export type ContextSource =
  | 'user_provided'
  | 'conversation_history'
  | 'environment_variables'
  | 'prompt_defaults'
  | 'system_context'
  | 'cached_context'
  | 'generated_placeholder'
  | 'empty_fallback';

/**
 * Context resolution result
 */
export interface ContextResolution {
  value: any;
  source: ContextSource;
  confidence: number;
  metadata: {
    resolvedAt: number;
    strategy: string;
    alternativeValues?: Array<{ value: any; source: ContextSource; confidence: number }>;
    warnings: string[];
  };
}

/**
 * Context provider interface
 */
export interface ContextProvider {
  name: string;
  priority: number;
  isAvailable: () => boolean;
  resolve: (key: string, hint?: any) => Promise<ContextResolution | null>;
}

/**
 * Context aggregation options
 */
export interface ContextAggregationOptions {
  preferredSources?: ContextSource[];
  excludedSources?: ContextSource[];
  cacheResults?: boolean;
  includeAlternatives?: boolean;
  minimumConfidence?: number;
}

/**
 * Context resolver class
 */
export class ContextResolver {
  private logger: Logger;
  private providers: Map<string, ContextProvider> = new Map();
  private cache: Map<string, ContextResolution> = new Map();
  private cacheTimeout: number = 30000; // 30 seconds

  // Resolution statistics
  private stats = {
    totalResolutions: 0,
    successfulResolutions: 0,
    cacheHits: 0,
    cacheMisses: 0,
    providerUsage: new Map<string, number>(),
    averageConfidence: 0,
    averageResolutionTime: 0,
  };

  constructor(logger: Logger) {
    this.logger = logger;
    this.initializeDefaultProviders();
    this.logger.debug(`ContextResolver initialized with ${this.providers.size} providers`);
  }

  /**
   * Resolve context value using priority-based strategy
   */
  async resolveContext(
    key: string,
    hint?: any,
    options: ContextAggregationOptions = {}
  ): Promise<ContextResolution> {
    const startTime = Date.now();
    this.stats.totalResolutions++;

    this.logger.debug(`Resolving context for key: "${key}"`);

    // Check cache first
    if (options.cacheResults !== false) {
      const cached = this.getCachedResolution(key);
      if (cached) {
        this.stats.cacheHits++;
        this.logger.debug(`Context resolved from cache: ${key} -> ${cached.source}`);
        return cached;
      }
    }

    this.stats.cacheMisses++;

    // Get available providers sorted by priority
    const availableProviders = this.getAvailableProviders(options);
    const alternatives: Array<{ value: any; source: ContextSource; confidence: number }> = [];

    // Try each provider in priority order
    for (const provider of availableProviders) {
      try {
        const resolution = await provider.resolve(key, hint);
        if (resolution && this.meetsMinimumConfidence(resolution, options)) {
          // Collect alternatives if requested
          if (options.includeAlternatives) {
            // Continue trying other providers for alternatives
            await this.collectAlternatives(key, hint, availableProviders, provider, alternatives);
            resolution.metadata.alternativeValues = alternatives;
          }

          // Cache the result
          if (options.cacheResults !== false) {
            this.cacheResolution(key, resolution);
          }

          // Update statistics
          this.updateStats(provider.name, resolution, startTime);

          this.logger.debug(
            `Context resolved: ${key} -> ${resolution.source} (confidence: ${resolution.confidence})`
          );
          return resolution;
        } else if (resolution) {
          alternatives.push({
            value: resolution.value,
            source: resolution.source,
            confidence: resolution.confidence,
          });
        }
      } catch (error) {
        this.logger.debug(`Provider ${provider.name} failed for key ${key}:`, error);
        continue;
      }
    }

    // If no provider succeeded, create a fallback resolution
    const fallbackResolution = this.createFallbackResolution(key, hint, alternatives);

    // Cache fallback if enabled
    if (options.cacheResults !== false) {
      this.cacheResolution(key, fallbackResolution);
    }

    this.updateStats('fallback', fallbackResolution, startTime);
    this.logger.debug(`Context resolved using fallback: ${key} -> ${fallbackResolution.source}`);

    return fallbackResolution;
  }

  /**
   * Register a custom context provider
   */
  registerProvider(provider: ContextProvider): void {
    this.providers.set(provider.name, provider);
    this.stats.providerUsage.set(provider.name, 0);
    this.logger.debug(
      `Registered context provider: ${provider.name} (priority: ${provider.priority})`
    );
  }

  /**
   * Unregister a context provider
   */
  unregisterProvider(name: string): boolean {
    const removed = this.providers.delete(name);
    this.stats.providerUsage.delete(name);
    if (removed) {
      this.logger.debug(`Unregistered context provider: ${name}`);
    }
    return removed;
  }

  /**
   * Initialize default context providers
   */
  private initializeDefaultProviders(): void {
    // Conversation history provider
    this.registerProvider({
      name: 'conversation_history',
      priority: 80,
      isAvailable: () => true,
      resolve: async (key: string, hint?: any): Promise<ContextResolution | null> => {
        if (hint?.conversationHistory && hint.conversationHistory.length > 0) {
          const lastMessage = hint.conversationHistory[hint.conversationHistory.length - 1];
          if (lastMessage?.content) {
            return {
              value: lastMessage.content,
              source: 'conversation_history',
              confidence: 0.8,
              metadata: {
                resolvedAt: Date.now(),
                strategy: 'last_message',
                warnings: [],
              },
            };
          }
        }
        return null;
      },
    });

    // Environment variables provider
    this.registerProvider({
      name: 'environment_variables',
      priority: 70,
      isAvailable: () => true,
      resolve: async (key: string): Promise<ContextResolution | null> => {
        const envKey = `PROMPT_${key.toUpperCase()}`;
        const value = process.env[envKey];
        if (value) {
          return {
            value,
            source: 'environment_variables',
            confidence: 0.9,
            metadata: {
              resolvedAt: Date.now(),
              strategy: 'env_var',
              warnings: [],
            },
          };
        }
        return null;
      },
    });

    // Prompt defaults provider
    this.registerProvider({
      name: 'prompt_defaults',
      priority: 60,
      isAvailable: () => true,
      resolve: async (key: string, hint?: any): Promise<ContextResolution | null> => {
        if (hint?.promptDefaults?.[key] !== undefined) {
          return {
            value: hint.promptDefaults[key],
            source: 'prompt_defaults',
            confidence: 0.7,
            metadata: {
              resolvedAt: Date.now(),
              strategy: 'prompt_specific',
              warnings: [],
            },
          };
        }
        return null;
      },
    });

    // System context provider
    this.registerProvider({
      name: 'system_context',
      priority: 50,
      isAvailable: () => true,
      resolve: async (key: string, hint?: any): Promise<ContextResolution | null> => {
        if (hint?.systemContext?.[key] !== undefined) {
          return {
            value: hint.systemContext[key],
            source: 'system_context',
            confidence: 0.6,
            metadata: {
              resolvedAt: Date.now(),
              strategy: 'system_provided',
              warnings: [],
            },
          };
        }
        return null;
      },
    });

    // Smart placeholder generator
    this.registerProvider({
      name: 'placeholder_generator',
      priority: 30,
      isAvailable: () => true,
      resolve: async (key: string, hint?: any): Promise<ContextResolution | null> => {
        const placeholder = this.generateSmartPlaceholder(key, hint);
        return {
          value: placeholder.value,
          source: 'generated_placeholder',
          confidence: placeholder.confidence,
          metadata: {
            resolvedAt: Date.now(),
            strategy: 'smart_generation',
            warnings: placeholder.warnings,
          },
        };
      },
    });
  }

  /**
   * Get available providers based on options
   */
  private getAvailableProviders(options: ContextAggregationOptions): ContextProvider[] {
    return Array.from(this.providers.values())
      .filter((provider) => {
        // Check if provider is available
        if (!provider.isAvailable()) return false;

        // Check preferred sources
        if (options.preferredSources?.length) {
          // This is a simplistic check - in practice you'd map provider names to sources
          const providerSource = this.mapProviderToSource(provider.name);
          if (!options.preferredSources.includes(providerSource)) return false;
        }

        // Check excluded sources
        if (options.excludedSources?.length) {
          const providerSource = this.mapProviderToSource(provider.name);
          if (options.excludedSources.includes(providerSource)) return false;
        }

        return true;
      })
      .sort((a, b) => b.priority - a.priority); // Higher priority first
  }

  /**
   * Map provider name to context source
   */
  private mapProviderToSource(providerName: string): ContextSource {
    const mapping: Record<string, ContextSource> = {
      conversation_history: 'conversation_history',
      environment_variables: 'environment_variables',
      prompt_defaults: 'prompt_defaults',
      system_context: 'system_context',
      placeholder_generator: 'generated_placeholder',
    };

    return mapping[providerName] || 'system_context';
  }

  /**
   * Check if resolution meets minimum confidence
   */
  private meetsMinimumConfidence(
    resolution: ContextResolution,
    options: ContextAggregationOptions
  ): boolean {
    if (options.minimumConfidence === undefined) return true;
    return resolution.confidence >= options.minimumConfidence;
  }

  /**
   * Collect alternative values from other providers
   */
  private async collectAlternatives(
    key: string,
    hint: any,
    allProviders: ContextProvider[],
    usedProvider: ContextProvider,
    alternatives: Array<{ value: any; source: ContextSource; confidence: number }>
  ): Promise<void> {
    const remainingProviders = allProviders.filter((p) => p !== usedProvider);

    for (const provider of remainingProviders.slice(0, 3)) {
      // Limit to 3 alternatives
      try {
        const resolution = await provider.resolve(key, hint);
        if (resolution) {
          alternatives.push({
            value: resolution.value,
            source: resolution.source,
            confidence: resolution.confidence,
          });
        }
      } catch (error) {
        // Ignore errors when collecting alternatives
        continue;
      }
    }
  }

  /**
   * Generate smart placeholder based on key characteristics
   */
  private generateSmartPlaceholder(
    key: string,
    hint?: any
  ): { value: string; confidence: number; warnings: string[] } {
    const keyLower = key.toLowerCase();
    const warnings: string[] = [];

    // Argument-specific placeholders
    if (hint?.argumentDef) {
      const arg = hint.argumentDef as PromptArgument;
      const description = (arg.description || '').toLowerCase();

      if (description.includes('file') || keyLower.includes('file')) {
        return { value: '[File path required]', confidence: 0.4, warnings };
      }

      if (description.includes('url') || keyLower.includes('url')) {
        return { value: '[URL required]', confidence: 0.4, warnings };
      }

      if (description.includes('number') || keyLower.includes('count')) {
        return { value: '1', confidence: 0.5, warnings };
      }
    }

    // Generic semantic placeholders
    if (keyLower.includes('content') || keyLower.includes('text') || keyLower.includes('input')) {
      return {
        value: '[Content to be provided]',
        confidence: 0.3,
        warnings: ['Generic content placeholder - consider providing specific content'],
      };
    }

    if (keyLower.includes('name') || keyLower.includes('title')) {
      return { value: `[${key} required]`, confidence: 0.3, warnings };
    }

    if (keyLower.includes('format') || keyLower.includes('style')) {
      return { value: 'default', confidence: 0.4, warnings };
    }

    if (keyLower.includes('language') || keyLower.includes('lang')) {
      return { value: 'en', confidence: 0.4, warnings };
    }

    // Ultra-generic fallback
    warnings.push(`No semantic match found for "${key}" - using generic placeholder`);
    return {
      value: `[${key.replace(/_/g, ' ')} to be specified]`,
      confidence: 0.2,
      warnings,
    };
  }

  /**
   * Create fallback resolution when no providers succeed
   */
  private createFallbackResolution(
    key: string,
    hint: any,
    alternatives: Array<{ value: any; source: ContextSource; confidence: number }>
  ): ContextResolution {
    // If we have alternatives, use the best one
    if (alternatives.length > 0) {
      const best = [...alternatives].sort((a, b) => b.confidence - a.confidence)[0];
      if (!best) {
        return {
          value: '',
          source: 'empty_fallback',
          confidence: 0.1,
          metadata: {
            resolvedAt: Date.now(),
            strategy: 'empty_fallback',
            warnings: [`No context available for "${key}" - using empty value`],
          },
        };
      }
      return {
        value: best.value,
        source: best.source,
        confidence: best.confidence,
        metadata: {
          resolvedAt: Date.now(),
          strategy: 'best_alternative',
          alternativeValues: alternatives,
          warnings: ['Used alternative resolution after primary strategies failed'],
        },
      };
    }

    // Last resort: empty fallback
    return {
      value: '',
      source: 'empty_fallback',
      confidence: 0.1,
      metadata: {
        resolvedAt: Date.now(),
        strategy: 'empty_fallback',
        warnings: [`No context available for "${key}" - using empty value`],
      },
    };
  }

  /**
   * Get cached resolution if available and not expired
   */
  private getCachedResolution(key: string): ContextResolution | null {
    const cached = this.cache.get(key);
    if (cached && Date.now() - cached.metadata.resolvedAt < this.cacheTimeout) {
      return cached;
    } else if (cached) {
      // Remove expired cache entry
      this.cache.delete(key);
    }
    return null;
  }

  /**
   * Cache resolution result
   */
  private cacheResolution(key: string, resolution: ContextResolution): void {
    this.cache.set(key, resolution);

    // Cleanup old entries periodically
    if (this.cache.size > 1000) {
      const cutoff = Date.now() - this.cacheTimeout;
      for (const [k, v] of this.cache.entries()) {
        if (v.metadata.resolvedAt < cutoff) {
          this.cache.delete(k);
        }
      }
    }
  }

  /**
   * Update resolution statistics
   */
  private updateStats(
    providerName: string,
    resolution: ContextResolution,
    startTime: number
  ): void {
    this.stats.successfulResolutions++;

    const current = this.stats.providerUsage.get(providerName) || 0;
    this.stats.providerUsage.set(providerName, current + 1);

    // Update average confidence
    const totalSuccessful = this.stats.successfulResolutions;
    this.stats.averageConfidence =
      (this.stats.averageConfidence * (totalSuccessful - 1) + resolution.confidence) /
      totalSuccessful;

    // Update average resolution time
    const resolutionTime = Date.now() - startTime;
    this.stats.averageResolutionTime =
      (this.stats.averageResolutionTime * (totalSuccessful - 1) + resolutionTime) / totalSuccessful;
  }

  /**
   * Clear cache
   */
  clearCache(): void {
    this.cache.clear();
    this.logger.debug('Context cache cleared');
  }

  /**
   * Get context resolution statistics
   */
  getStats(): typeof this.stats {
    return {
      ...this.stats,
      providerUsage: new Map(this.stats.providerUsage),
    };
  }

  /**
   * Reset statistics
   */
  resetStats(): void {
    this.stats = {
      totalResolutions: 0,
      successfulResolutions: 0,
      cacheHits: 0,
      cacheMisses: 0,
      providerUsage: new Map(Array.from(this.providers.keys()).map((name) => [name, 0])),
      averageConfidence: 0,
      averageResolutionTime: 0,
    };
  }
}

/**
 * Factory function to create context resolver
 */
export function createContextResolver(logger: Logger): ContextResolver {
  return new ContextResolver(logger);
}
