/**
 * Smart Context Loader – auto-discovers and loads related files
 *
 * When the user asks about or wants to edit a VL file, this module
 * uses the dependency graph to find all relevant files and pre-loads
 * them into context.
 *
 * Strategy:
 *   1. Parse user message for file references (@file, path mentions)
 *   2. Walk dependency graph: upstream (who depends on me) + downstream (who I depend on)
 *   3. Rank files by relevance (direct dep > transitive dep > same type)
 *   4. Load files with smart truncation (Read with offset/limit for large files)
 *   5. Return as system-reminder block for context injection
 */
import fs from 'fs/promises';
import path from 'path';

export class SmartContextLoader {
  constructor(projectContext) {
    this.ctx = projectContext;
    this.depGraph = null;     // {nodes, edges}
    this.adjacency = null;    // nodeId → [connected nodes]
    this.reverseAdj = null;   // nodeId → [nodes that point to me]
    this.recentEdits = [];    // [{filePath, timestamp}] — tracks recently edited files
    this.compileErrorFiles = new Set(); // Files mentioned in compile errors
    this.contextManager = null; // Wired by orchestrator for conversation history analysis
  }

  /** Record a file edit for recency boosting */
  recordEdit(filePath) {
    this.recentEdits.push({ filePath, timestamp: Date.now() });
    // Keep last 50 edits
    if (this.recentEdits.length > 50) this.recentEdits.shift();
  }

  /** Update compile error file set from last-compile.json */
  updateCompileErrors(errList) {
    this.compileErrorFiles.clear();
    if (!errList) return;
    for (const err of errList) {
      if (err.file) this.compileErrorFiles.add(err.file);
      if (err.filePath) this.compileErrorFiles.add(err.filePath);
    }
  }

  /** Build dependency graph from project files */
  async buildGraph() {
    if (!this.ctx.isVLProject()) return;

    const nodes = new Map();
    const edges = [];

    for (const file of this.ctx.getAllFiles()) {
      nodes.set(file.path, { id: file.path, type: file.type, name: file.name });

      try {
        const content = await fs.readFile(file.fullPath, 'utf-8');

        // App → Section/Component references
        if (file.type === 'app') {
          for (const m of content.matchAll(/<Section-(\w+)\s+"[^"]+"/g)) {
            const target = this.ctx.getFilesByType('section').find(f => f.name === m[1]);
            if (target) edges.push({ from: file.path, to: target.path, type: 'hosts', weight: 1 });
          }
          for (const m of content.matchAll(/<Component-(\w+)\s+"[^"]+"/g)) {
            const target = this.ctx.getFilesByType('component').find(f => f.name === m[1]);
            if (target) edges.push({ from: file.path, to: target.path, type: 'uses', weight: 2 });
          }
        }

        // Section → Service/Component references
        if (file.type === 'section') {
          for (const m of content.matchAll(/<ServiceDomain-(\w+)>/g)) {
            const target = this.ctx.getFilesByType('service').find(f => f.name === m[1]);
            if (target) edges.push({ from: file.path, to: target.path, type: 'calls', weight: 1 });
          }
          for (const m of content.matchAll(/<Component-(\w+)\s+"[^"]+"/g)) {
            const target = this.ctx.getFilesByType('component').find(f => f.name === m[1]);
            if (target) edges.push({ from: file.path, to: target.path, type: 'uses', weight: 2 });
          }
        }

        // Service → Database (by sourceTable references)
        if (file.type === 'service') {
          for (const m of content.matchAll(/sourceTable:(\w+)/g)) {
            const target = this.ctx.getFilesByType('database').find(() => true);
            if (target) edges.push({ from: file.path, to: target.path, type: 'queries', weight: 1 });
          }
        }
      } catch {
        continue;
      }
    }

    // Deduplicate edges
    const edgeSet = new Set();
    const uniqueEdges = edges.filter(e => {
      const key = `${e.from}→${e.to}`;
      if (edgeSet.has(key)) return false;
      edgeSet.add(key);
      return true;
    });

    this.depGraph = { nodes: [...nodes.values()], edges: uniqueEdges };

    // Build adjacency lists
    this.adjacency = new Map();
    this.reverseAdj = new Map();
    for (const node of nodes.values()) {
      this.adjacency.set(node.id, []);
      this.reverseAdj.set(node.id, []);
    }
    for (const edge of uniqueEdges) {
      this.adjacency.get(edge.from)?.push({ target: edge.to, weight: edge.weight, type: edge.type });
      this.reverseAdj.get(edge.to)?.push({ target: edge.from, weight: edge.weight, type: edge.type });
    }
  }

  /**
   * Given user message, discover relevant files to load
   * Returns: [{path, type, relevance, reason}]
   */
  discoverRelevantFiles(userMessage) {
    if (!this.depGraph) return [];

    const relevant = new Map(); // path → {relevance, reason}

    // 1. Direct @mentions: "@HeaderSection" or "HeaderSection.sc"
    for (const file of this.ctx.getAllFiles()) {
      if (userMessage.includes(`@${file.name}`) ||
          userMessage.includes(path.basename(file.path)) ||
          userMessage.includes(file.name)) {
        relevant.set(file.path, { relevance: 10, reason: 'directly mentioned' });
      }
    }

    // 2. Keyword matching by file type
    const keywords = {
      section: ['section', 'page', 'layout', 'ui', 'frontend', 'view'],
      component: ['component', 'widget', 'button', 'card', 'input'],
      service: ['service', 'api', 'backend', 'query', 'database', 'crud'],
      app: ['app', 'route', 'navigation', 'routing'],
      database: ['database', 'table', 'schema', 'field', 'entity', 'db'],
      theme: ['theme', 'style', 'color', 'token', 'design'],
    };
    const msgLower = userMessage.toLowerCase();
    for (const [type, words] of Object.entries(keywords)) {
      if (words.some(w => msgLower.includes(w))) {
        for (const file of this.ctx.getFilesByType(type)) {
          if (!relevant.has(file.path)) {
            relevant.set(file.path, { relevance: 3, reason: `type match: ${type}` });
          }
        }
      }
    }

    // 3. Walk dependency graph for directly mentioned files
    for (const [filePath, info] of relevant) {
      if (info.relevance >= 8) {
        // Downstream dependencies (what this file uses)
        const downstream = this.adjacency.get(filePath) || [];
        for (const dep of downstream) {
          if (!relevant.has(dep.target) || relevant.get(dep.target).relevance < 6) {
            relevant.set(dep.target, { relevance: 6, reason: `dependency of ${path.basename(filePath)}` });
          }
        }
        // Upstream dependents (who uses this file)
        const upstream = this.reverseAdj.get(filePath) || [];
        for (const dep of upstream) {
          if (!relevant.has(dep.target) || relevant.get(dep.target).relevance < 5) {
            relevant.set(dep.target, { relevance: 5, reason: `depends on ${path.basename(filePath)}` });
          }
        }
      }
    }

    // 4. Recently edited files: boost relevance (+5 within last 5 min, +3 within 15 min)
    const now = Date.now();
    for (const edit of this.recentEdits) {
      const age = now - edit.timestamp;
      const boost = age < 5 * 60 * 1000 ? 5 : age < 15 * 60 * 1000 ? 3 : 0;
      if (boost > 0) {
        const existing = relevant.get(edit.filePath);
        if (existing) {
          existing.relevance += boost;
          existing.reason += ', recently edited';
        } else {
          relevant.set(edit.filePath, { relevance: boost, reason: 'recently edited' });
        }
      }
    }

    // 5. Compile error files: high relevance boost (+8)
    for (const errFile of this.compileErrorFiles) {
      const existing = relevant.get(errFile);
      if (existing) {
        existing.relevance += 8;
        existing.reason += ', has compile errors';
      } else {
        relevant.set(errFile, { relevance: 8, reason: 'has compile errors' });
      }
    }

    // 6. Conversation history: extract recently discussed files from context
    if (this.contextManager) {
      const recentFiles = this._extractFilesFromConversation();
      for (const filePath of recentFiles) {
        const existing = relevant.get(filePath);
        if (existing) {
          existing.relevance += 2;
        } else {
          relevant.set(filePath, { relevance: 2, reason: 'discussed in conversation' });
        }
      }
    }

    // Sort by relevance (highest first), limit to top 8
    return [...relevant.entries()]
      .map(([p, info]) => ({ path: p, ...info, type: this.ctx.getFile(p)?.type }))
      .sort((a, b) => b.relevance - a.relevance)
      .slice(0, 8);
  }

  /** Extract file paths mentioned in recent conversation messages */
  _extractFilesFromConversation() {
    if (!this.contextManager) return [];
    const messages = this.contextManager.messages || [];
    const recentMsgs = messages.slice(-10); // Only last 10 messages
    const mentioned = new Set();
    const allFiles = this.ctx.getAllFiles();

    for (const msg of recentMsgs) {
      const text = typeof msg.content === 'string' ? msg.content :
        Array.isArray(msg.content) ? msg.content.map(b => b.text || b.content || '').join(' ') : '';
      for (const file of allFiles) {
        if (text.includes(file.name) || text.includes(path.basename(file.path))) {
          mentioned.add(file.path);
        }
      }
    }
    return [...mentioned].slice(0, 5);
  }

  /**
   * Load discovered files into a context block
   * Smart truncation: key sections only for large files
   */
  async loadContext(relevantFiles, userMessageLength) {
    if (relevantFiles.length === 0) return null;

    const blocks = [];
    let totalChars = 0;
    // Dynamic token budget: short user messages get more context, long messages less
    const msgLen = userMessageLength || 200;
    const MAX_CHARS = msgLen < 100 ? 20000 : msgLen > 500 ? 8000 : 15000;

    for (const file of relevantFiles) {
      if (totalChars > MAX_CHARS) break;

      try {
        const fullPath = path.resolve(this.ctx.workDir, file.path);
        const content = await fs.readFile(fullPath, 'utf-8');
        const lines = content.split('\n');

        let loaded;
        if (lines.length <= 80) {
          // Small file: load fully
          loaded = content;
        } else {
          // Large file: load header + section markers + public interface
          loaded = this.extractKeySections(lines, file.type);
        }

        const budget = MAX_CHARS - totalChars;
        const truncated = loaded.length > budget ? loaded.substring(0, budget) + '\n... (truncated)' : loaded;
        totalChars += truncated.length;

        blocks.push(`--- ${file.path} (${file.reason}) ---\n${truncated}`);
      } catch {
        continue;
      }
    }

    if (blocks.length === 0) return null;

    return `<system-reminder>
Auto-loaded relevant VL files (${blocks.length} files):
${blocks.join('\n\n')}
</system-reminder>`;
  }

  /** Extract key sections from a large VL file */
  extractKeySections(lines, fileType) {
    const result = [];
    let inPublicSection = false;
    let inTreeSection = false;
    let treeLineCount = 0;

    for (let i = 0; i < lines.length; i++) {
      const line = lines[i];

      // Always include: version, root component, section headers
      if (i < 3 || line.startsWith('//') || line.match(/^<\w+-\w+/) || line.match(/^#\s/)) {
        result.push(line);
        inPublicSection = line.includes('Public');
        inTreeSection = line.includes('Tree');
        treeLineCount = 0;
        continue;
      }

      // Include full public interface sections
      if (inPublicSection && !line.startsWith('#')) {
        result.push(line);
        if (line.trim() === '') inPublicSection = false;
        continue;
      }

      // Include first 20 lines of tree
      if (inTreeSection && !line.startsWith('#')) {
        treeLineCount++;
        if (treeLineCount <= 20) {
          result.push(line);
        } else if (treeLineCount === 21) {
          result.push(`... (${lines.length - i} more lines in tree)`);
          inTreeSection = false;
        }
        continue;
      }

      // Include SERVICE/METHOD/EVENT declarations
      if (line.match(/^(SERVICE|PUBLIC_SERVICE|METHOD|METHOD_PUB|EVENT|TRANSACTION|PIPE)\s/)) {
        result.push(line);
      }
    }

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