/**
 * Context Service
 *
 * High-level facade for context management.
 */

import { simpleGit } from 'simple-git';
import type { KuzuConnection } from '../graph/connection.js';
import type {
  ContextItem,
  ContextConfig,
  ContextUpdateOptions,
  ContextRetrievalOptions,
  ExplorationStep,
} from './types.js';
import { DEFAULT_CONTEXT_CONFIG, getContextConfig } from './types.js';
import { SessionContextManager, getTerminalId } from './session.js';
import { createEnabledProviders } from './registry.js';
import { rerankContextItems } from './reranker.js';
import { ContextProvider } from './providers/base.js';
import { createLogger, type Logger } from '../utils/logger.js';

/**
 * Context Service Options
 */
export interface ContextServiceOptions {
  /** Kuzu connection */
  conn: KuzuConnection;
  /** Repository root for git operations */
  repoRoot?: string;
  /** Override config */
  config?: Partial<ContextConfig>;
  /** Logger instance */
  logger?: Logger;
}

/**
 * Context Service
 *
 * High-level API for context management:
 * - Update context from exploration steps
 * - Update context from modified files
 * - Get context formatted for LLM prompts
 * - Rerank context by query
 */
export class ContextService {
  private conn: KuzuConnection;
  private sessionManager: SessionContextManager;
  private providers: ContextProvider[];
  private config: ContextConfig;
  private repoRoot: string;
  private logger: Logger;

  constructor(options: ContextServiceOptions) {
    this.conn = options.conn;
    this.repoRoot = options.repoRoot ?? process.cwd();
    this.config = { ...DEFAULT_CONTEXT_CONFIG, ...getContextConfig(), ...options.config };
    this.logger = options.logger ?? createLogger({ name: 'context-service' });
    this.sessionManager = new SessionContextManager(this.conn, this.logger);
    this.providers = createEnabledProviders(this.conn, this.config);
  }

  /**
   * Get terminal ID for current session
   */
  getTerminalId(): string {
    return getTerminalId();
  }

  /**
   * Update context for a terminal
   */
  async updateContext(
    terminalId: string,
    options?: ContextUpdateOptions
  ): Promise<void> {
    this.logger.debug({ terminalId }, 'Updating context');

    // Get or create session
    const session = await this.sessionManager.getOrCreateSession(terminalId);

    // Collect items from all providers
    const allItems: ContextItem[] = [];

    // Run exploration provider if steps provided
    if (options?.explorationSteps && options.explorationSteps.length > 0) {
      const exploredProvider = this.providers.find(
        (p) => p.spec.name === 'explored_areas'
      );
      if (exploredProvider) {
        const items = await exploredProvider.extractContext(options.explorationSteps);
        allItems.push(...items);
      }
    }

    // Run touched areas provider
    let modifiedFiles = options?.modifiedFiles;

    // Auto-detect modified files if not provided
    if (!modifiedFiles) {
      modifiedFiles = await this.detectModifiedFiles();
    }

    if (modifiedFiles && modifiedFiles.length > 0) {
      const touchedProvider = this.providers.find(
        (p) => p.spec.name === 'touched_areas'
      );
      if (touchedProvider) {
        const items = await touchedProvider.extractContext(modifiedFiles);
        allItems.push(...items);
      }
    }

    // Add items to session
    if (allItems.length > 0) {
      await this.sessionManager.addContextItems(
        session.sessionId,
        allItems,
        options?.decayFactor ?? this.config.decayFactor
      );
    }

    // Prune low-score items
    await this.sessionManager.pruneItems(session.sessionId, this.config.minScore);

    this.logger.debug(
      { terminalId, itemsAdded: allItems.length },
      'Context updated'
    );
  }

  /**
   * Get context items for a terminal
   */
  async getContext(
    terminalId: string,
    options?: ContextRetrievalOptions
  ): Promise<ContextItem[]> {
    const session = await this.sessionManager.getOrCreateSession(terminalId);
    let items = session.items;

    // Filter by minimum score
    const minScore = options?.minScore ?? this.config.minScore;
    items = items.filter((item) => item.score >= minScore);

    // Rerank if query provided and reranking enabled
    const shouldRerank = options?.rerank ?? (this.config.rerankEnabled && !!options?.query);
    if (shouldRerank && options?.query) {
      items = rerankContextItems(
        items,
        options.query,
        options.maxItems ?? this.config.rerankTopK,
        this.conn
      );
    }

    // Limit items
    const maxItems = options?.maxItems ?? this.config.maxItems;
    items = items.slice(0, maxItems);

    return items;
  }

  /**
   * Get context formatted as markdown for system prompt
   */
  async getContextPrompt(
    terminalId: string,
    options?: ContextRetrievalOptions
  ): Promise<string> {
    const items = await this.getContext(terminalId, options);

    if (items.length === 0) {
      return '';
    }

    // Use provider's format method
    const provider = this.providers[0];
    if (provider) {
      return provider.formatForPrompt(items);
    }

    // Fallback formatting
    return this.formatContextPrompt(items);
  }

  /**
   * Format context items as markdown
   */
  private formatContextPrompt(items: ContextItem[]): string {
    const lines: string[] = [
      '## Session Context',
      '',
      'The following code entities are relevant to recent work:',
      '',
    ];

    for (const item of items) {
      const indicator = item.score > 0.8 ? '***' : item.score > 0.5 ? '**' : '*';
      lines.push(`### ${indicator}${item.entityType}: ${item.qualifiedName}${indicator}`);

      if (item.filePath) {
        lines.push(`- File: \`${item.filePath}\``);
      }

      if (item.description) {
        const desc = item.description.length > 200
          ? item.description.slice(0, 200) + '...'
          : item.description;
        lines.push(`- Description: ${desc}`);
      }

      if (item.neighbors && item.neighbors.length > 0) {
        lines.push(`- Related: ${item.neighbors.slice(0, 5).join(', ')}`);
      }

      lines.push('');
    }

    return lines.join('\n');
  }

  /**
   * Detect modified files using git
   */
  private async detectModifiedFiles(): Promise<string[]> {
    try {
      const git = simpleGit(this.repoRoot);
      const status = await git.status();

      const files: string[] = [];

      // Modified files
      for (const file of status.modified) {
        files.push(`${this.repoRoot}/${file}`);
      }

      // New files
      for (const file of status.not_added) {
        files.push(`${this.repoRoot}/${file}`);
      }

      // Staged files
      for (const file of status.staged) {
        files.push(`${this.repoRoot}/${file}`);
      }

      return [...new Set(files)]; // Dedupe
    } catch {
      return [];
    }
  }

  /**
   * Clear context for a terminal
   */
  async clearContext(terminalId: string): Promise<void> {
    const session = await this.sessionManager.getOrCreateSession(terminalId);
    await this.sessionManager.clearSession(session.sessionId);
    this.logger.debug({ terminalId }, 'Context cleared');
  }

  /**
   * Record exploration steps (convenience method)
   */
  async recordExploration(
    terminalId: string,
    steps: ExplorationStep[]
  ): Promise<void> {
    await this.updateContext(terminalId, { explorationSteps: steps });
  }

  /**
   * Record file modifications (convenience method)
   */
  async recordModifications(
    terminalId: string,
    filePaths: string[]
  ): Promise<void> {
    await this.updateContext(terminalId, { modifiedFiles: filePaths });
  }
}

/**
 * Create a context service
 */
export function createContextService(options: ContextServiceOptions): ContextService {
  return new ContextService(options);
}
