/**
 * VL Symbol Index – language-level intelligence for VL files
 *
 * Parses all VL files and builds a searchable symbol index:
 *   - Components, Sections, Services, Apps (declarations)
 *   - Methods, Events, Pipes, Transactions (callable symbols)
 *   - Variables ($name), Theme Tokens (--name)
 *   - Cross-references (who uses what)
 *
 * Supports: go-to-definition, find-references, auto-complete, hover, file outline
 *
 * This is VL-Code's key structural advantage over Claude Code.
 * Claude Code uses regex Grep to find things – VL-Code has semantic understanding.
 */
import fs from 'fs/promises';
import path from 'path';

export class VLSymbolIndex {
  constructor(projectContext) {
    this.ctx = projectContext;
    this.symbols = new Map();      // symbolId → SymbolInfo
    this.references = [];          // [{from: {file, line}, toName, type}]
    this.fileSymbols = new Map();  // filePath → [symbolId]
    this.themeTokens = new Map();  // tokenName → {value, category, file, line}
    this.buildTime = 0;
  }

  /** Build the complete symbol index from all project files */
  async build() {
    const start = Date.now();
    this.symbols.clear();
    this.references = [];
    this.fileSymbols.clear();
    this.themeTokens.clear();

    for (const file of this.ctx.getAllFiles()) {
      try {
        const content = await fs.readFile(file.fullPath, 'utf-8');
        this.parseFile(file, content);
      } catch { continue; }
    }

    this.buildTime = Date.now() - start;
  }

  /** Incrementally update index for a single changed file */
  async updateFile(filePath) {
    // Remove old symbols for this file
    const oldIds = this.fileSymbols.get(filePath) || [];
    for (const id of oldIds) this.symbols.delete(id);
    this.references = this.references.filter(r => r.from.file !== filePath);

    // Re-parse the file
    const file = this.ctx.getFile(filePath);
    if (!file) return;
    try {
      const content = await fs.readFile(file.fullPath, 'utf-8');
      this.parseFile(file, content);
    } catch { /* file may have been deleted */ }
  }

  /** Parse a single VL file and extract symbols + references */
  parseFile(file, content) {
    const lines = content.split('\n');
    const fileSymbols = [];
    let currentSection = null;

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

      // Section headers: # Frontend Tree
      const sectionMatch = line.match(/^#\s+(.+)$/);
      if (sectionMatch) {
        currentSection = sectionMatch[1].trim();
        const id = `${file.path}:section:${currentSection}`;
        this.symbols.set(id, {
          name: currentSection, type: 'section_header',
          file: file.path, line: lineNum, fileType: file.type,
        });
        fileSymbols.push(id);
        continue;
      }

      // Root component: <Type-Name "instanceId">
      const rootMatch = line.match(/^<(\w+-\w+)\s+"(\w+)"\s*>/);
      if (rootMatch) {
        const id = `${file.path}:root`;
        this.symbols.set(id, {
          name: rootMatch[1], instanceId: rootMatch[2],
          type: 'root_component', file: file.path, line: lineNum,
          section: null, fileType: file.type,
        });
        fileSymbols.push(id);
      }

      // METHOD / METHOD_PUB
      const methodMatch = line.match(/^(METHOD|METHOD_PUB)\s+(\w+)\(([^)]*)\)(?:\s*;\s*RETURN\s+(\{[^}]*\}))?/);
      if (methodMatch) {
        const id = `${file.path}:method:${methodMatch[2]}`;
        this.symbols.set(id, {
          name: methodMatch[2], kind: methodMatch[1],
          type: 'method', file: file.path, line: lineNum,
          section: currentSection, params: methodMatch[3],
          returnType: methodMatch[4] || null, fileType: file.type,
          isPublic: methodMatch[1] === 'METHOD_PUB',
        });
        fileSymbols.push(id);
        continue;
      }

      // SERVICE / PUBLIC_SERVICE
      const serviceMatch = line.match(/^(SERVICE|PUBLIC_SERVICE)\s+(\w+)\(([^)]*)\)(?:\s*;\s*RETURN\s+(\{[^}]*\}))?/);
      if (serviceMatch) {
        const id = `${file.path}:service:${serviceMatch[2]}`;
        this.symbols.set(id, {
          name: serviceMatch[2], kind: serviceMatch[1],
          type: 'service_method', file: file.path, line: lineNum,
          section: currentSection, params: serviceMatch[3],
          returnType: serviceMatch[4] || null, fileType: file.type,
          isPublic: serviceMatch[1] === 'PUBLIC_SERVICE',
        });
        fileSymbols.push(id);
        continue;
      }

      // EVENT @name(params)
      const eventMatch = line.match(/^EVENT\s+@(\w+)\(([^)]*)\)/);
      if (eventMatch) {
        const id = `${file.path}:event:${eventMatch[1]}`;
        this.symbols.set(id, {
          name: `@${eventMatch[1]}`, type: 'event',
          file: file.path, line: lineNum, section: currentSection,
          params: eventMatch[2], fileType: file.type,
        });
        fileSymbols.push(id);
        continue;
      }

      // PIPE / TRANSACTION
      const pipeMatch = line.match(/^(PIPE|TRANSACTION)\s+(\w+)\(([^)]*)\)(?:\s*;\s*RETURN\s+(\{[^}]*\}|\w+))?/);
      if (pipeMatch) {
        const id = `${file.path}:${pipeMatch[1].toLowerCase()}:${pipeMatch[2]}`;
        this.symbols.set(id, {
          name: pipeMatch[2], type: pipeMatch[1].toLowerCase(),
          file: file.path, line: lineNum, section: currentSection,
          params: pipeMatch[3], returnType: pipeMatch[4] || null,
          fileType: file.type,
        });
        fileSymbols.push(id);
        continue;
      }

      // Variable: $name(TYPE) = value
      const varMatch = line.match(/\$(\w+)\(([^)]+)\)\s*=/);
      if (varMatch) {
        const id = `${file.path}:var:${varMatch[1]}`;
        this.symbols.set(id, {
          name: `$${varMatch[1]}`, dataType: varMatch[2],
          type: 'variable', file: file.path, line: lineNum,
          section: currentSection, fileType: file.type,
          isLocal: varMatch[1].startsWith('_'),
        });
        fileSymbols.push(id);
      }

      // Public prop: -propName(TYPE) = default
      const propMatch = line.match(/^-(\w+)\(([^)]+)\)\s*=\s*(.*)/);
      if (propMatch && currentSection?.includes('Public Props')) {
        const id = `${file.path}:prop:${propMatch[1]}`;
        this.symbols.set(id, {
          name: propMatch[1], dataType: propMatch[2],
          type: 'public_prop', file: file.path, line: lineNum,
          section: currentSection, defaultValue: propMatch[3].trim(),
          fileType: file.type,
        });
        fileSymbols.push(id);
      }

      // Theme tokens: --tokenName
      if (file.type === 'theme') {
        const tokenMatches = [...line.matchAll(/(\w+):(#[0-9A-Fa-f]+|\d+(?:px|rem|em|%)|\w+)/g)];
        for (const m of tokenMatches) {
          this.themeTokens.set(`--${m[1]}`, {
            name: `--${m[1]}`, value: m[2],
            file: file.path, line: lineNum,
          });
        }
      }

      // --- Cross-references ---

      // Component/Section usage: <Type-Name "id">
      const compUseMatches = [...line.matchAll(/<(\w+-\w+)\s+"(\w+)"/g)];
      for (const m of compUseMatches) {
        if (m.index === 0 && rootMatch) continue; // skip root declaration
        this.references.push({
          from: { file: file.path, line: lineNum },
          toName: m[1], instanceId: m[2], type: 'uses_component',
        });
      }

      // ServiceDomain call: <ServiceDomain-Name>
      const svcCallMatches = [...line.matchAll(/<ServiceDomain-(\w+)>/g)];
      for (const m of svcCallMatches) {
        this.references.push({
          from: { file: file.path, line: lineNum },
          toName: m[1], type: 'calls_service',
        });
      }

      // Method call: Name.Method()
      const methodCallMatches = [...line.matchAll(/(\w+)\.(\w+)\(/g)];
      for (const m of methodCallMatches) {
        this.references.push({
          from: { file: file.path, line: lineNum },
          toName: `${m[1]}.${m[2]}`, type: 'calls_method',
        });
      }

      // Theme token usage: --tokenName (in non-theme files)
      if (file.type !== 'theme') {
        const themeUseMatches = [...line.matchAll(/--(\w+)/g)];
        for (const m of themeUseMatches) {
          this.references.push({
            from: { file: file.path, line: lineNum },
            toName: `--${m[1]}`, type: 'uses_theme_token',
          });
        }
      }
    }

    this.fileSymbols.set(file.path, fileSymbols);
  }

  // ========== Query API ==========

  /** Go to definition: given a symbol name, find where it's declared */
  goToDefinition(name) {
    const results = [];
    for (const [id, sym] of this.symbols) {
      if (sym.name === name || sym.instanceId === name ||
          id.endsWith(`:${name}`) || sym.name === `$${name}` || sym.name === `@${name}`) {
        results.push({ ...sym, symbolId: id });
      }
    }
    // Also check theme tokens
    if (name.startsWith('--') && this.themeTokens.has(name)) {
      results.push({ ...this.themeTokens.get(name), type: 'theme_token' });
    }
    return results;
  }

  /** Find all references to a symbol name */
  findReferences(name) {
    return this.references.filter(ref =>
      ref.toName === name ||
      ref.toName.endsWith(name) ||
      ref.instanceId === name
    ).map(ref => ({
      file: ref.from.file,
      line: ref.from.line,
      type: ref.type,
      context: ref.toName,
    }));
  }

  /** Auto-complete: given a prefix and context, suggest matching symbols */
  autoComplete(prefix, options = {}) {
    const { fileType, section, limit = 20 } = options;
    const results = [];

    // Variable prefix
    if (prefix.startsWith('$')) {
      const varPrefix = prefix.slice(1).toLowerCase();
      for (const [, sym] of this.symbols) {
        if (sym.type === 'variable' && sym.name.toLowerCase().startsWith(`$${varPrefix}`)) {
          results.push({ label: sym.name, detail: sym.dataType, kind: 'variable', file: sym.file, line: sym.line });
        }
      }
    }
    // Theme token prefix
    else if (prefix.startsWith('--')) {
      const tokenPrefix = prefix.toLowerCase();
      for (const [name, token] of this.themeTokens) {
        if (name.toLowerCase().startsWith(tokenPrefix)) {
          results.push({ label: name, detail: token.value, kind: 'theme_token', file: token.file, line: token.line });
        }
      }
    }
    // Event prefix
    else if (prefix.startsWith('@')) {
      const eventPrefix = prefix.toLowerCase();
      for (const [, sym] of this.symbols) {
        if (sym.type === 'event' && sym.name.toLowerCase().startsWith(eventPrefix)) {
          results.push({ label: sym.name, detail: sym.params, kind: 'event', file: sym.file, line: sym.line });
        }
      }
    }
    // Component/Method/Service prefix
    else {
      const lowerPrefix = prefix.toLowerCase();
      for (const [, sym] of this.symbols) {
        if (['method', 'service_method', 'pipe', 'transaction', 'root_component', 'public_prop'].includes(sym.type)) {
          if (sym.name.toLowerCase().startsWith(lowerPrefix)) {
            results.push({ label: sym.name, detail: sym.params || sym.dataType || '', kind: sym.type, file: sym.file, line: sym.line });
          }
        }
      }
    }

    return results.slice(0, limit);
  }

  /** Get hover info for a symbol */
  getSymbolInfo(name) {
    const defs = this.goToDefinition(name);
    const refs = this.findReferences(name);
    if (defs.length === 0 && refs.length === 0) return null;

    const def = defs[0];
    return {
      definition: def || null,
      references: refs.length,
      referenceLocations: refs.slice(0, 10),
      signature: def ? this.formatSignature(def) : null,
    };
  }

  /** Get file outline (all symbols in a file) */
  getFileOutline(filePath) {
    const ids = this.fileSymbols.get(filePath) || [];
    return ids.map(id => this.symbols.get(id)).filter(Boolean).map(sym => ({
      name: sym.name,
      type: sym.type,
      line: sym.line,
      section: sym.section,
      detail: sym.params || sym.dataType || '',
    }));
  }

  /** Get all symbols of a specific type */
  getSymbolsByType(type) {
    const results = [];
    for (const [id, sym] of this.symbols) {
      if (sym.type === type) results.push({ ...sym, symbolId: id });
    }
    return results;
  }

  /** Get cross-reference summary: how files relate to each other */
  getCrossReferenceMap() {
    const map = new Map(); // fromFile → [{toFile, type, count}]
    for (const ref of this.references) {
      const key = ref.from.file;
      if (!map.has(key)) map.set(key, new Map());
      const targets = map.get(key);

      // Find which file the target is in
      for (const [id, sym] of this.symbols) {
        if (sym.name === ref.toName || sym.instanceId === ref.toName) {
          const targetFile = sym.file;
          const edgeKey = `${targetFile}:${ref.type}`;
          if (!targets.has(edgeKey)) targets.set(edgeKey, { file: targetFile, type: ref.type, count: 0 });
          targets.get(edgeKey).count++;
          break;
        }
      }
    }
    return map;
  }

  /** Get statistics */
  getStats() {
    const typeCounts = {};
    for (const [, sym] of this.symbols) {
      typeCounts[sym.type] = (typeCounts[sym.type] || 0) + 1;
    }
    return {
      totalSymbols: this.symbols.size,
      totalReferences: this.references.length,
      themeTokens: this.themeTokens.size,
      filesIndexed: this.fileSymbols.size,
      buildTimeMs: this.buildTime,
      byType: typeCounts,
    };
  }

  // ========== Internal ==========

  formatSignature(sym) {
    if (sym.type === 'method' || sym.type === 'service_method') {
      const ret = sym.returnType ? `; RETURN ${sym.returnType}` : '';
      return `${sym.kind || sym.type.toUpperCase()} ${sym.name}(${sym.params || ''})${ret}`;
    }
    if (sym.type === 'event') return `EVENT ${sym.name}(${sym.params || ''})`;
    if (sym.type === 'variable') return `$${sym.name.replace('$', '')}(${sym.dataType})`;
    if (sym.type === 'public_prop') return `-${sym.name}(${sym.dataType}) = ${sym.defaultValue || ''}`;
    if (sym.type === 'pipe') return `PIPE ${sym.name}(${sym.params || ''})${sym.returnType ? '; RETURN ' + sym.returnType : ''}`;
    return sym.name;
  }
}
