/**
 * Impact Analyzer – cross-file break detection
 *
 * Given a proposed change to a VL file, analyzes what other files would break.
 * This is VL-Code's unique capability: VL's strict type system and cross-reference
 * patterns make it possible to detect breaking changes BEFORE they happen.
 *
 * Claude Code has no equivalent – it cannot know that renaming a method
 * will break callers in other files without manually searching.
 *
 * Impact types:
 *   - rename: symbol renamed → all references break
 *   - remove: symbol removed → all references break
 *   - type-change: parameter/return type changed → callers may break
 *   - prop-change: public prop added/removed → parent components affected
 *   - service-change: service contract changed → all calling sections break
 */

export class ImpactAnalyzer {
  constructor(symbolIndex, projectContext) {
    this.index = symbolIndex;
    this.ctx = projectContext;
  }

  /**
   * Analyze impact of removing a symbol
   * @param {string} symbolName - The symbol being removed
   * @returns {Object} Impact report
   */
  analyzeRemoval(symbolName) {
    const defs = this.index.goToDefinition(symbolName);
    const refs = this.index.findReferences(symbolName);

    if (defs.length === 0) {
      return { impact: 'none', message: `Symbol "${symbolName}" not found in index.` };
    }

    const affectedFiles = new Set(refs.map(r => r.file));
    const def = defs[0];

    return {
      impact: refs.length > 0 ? 'breaking' : 'safe',
      symbol: { name: symbolName, type: def.type, file: def.file, line: def.line },
      references: refs.length,
      affectedFiles: [...affectedFiles],
      details: refs.map(r => ({
        file: r.file,
        line: r.line,
        type: r.type,
        message: `Reference to "${symbolName}" at ${r.file}:${r.line} will break`,
      })),
      suggestion: refs.length > 0
        ? `Update ${refs.length} reference(s) in ${affectedFiles.size} file(s) before removing.`
        : `Safe to remove – no references found.`,
    };
  }

  /**
   * Analyze impact of renaming a symbol
   * @param {string} oldName - Current name
   * @param {string} newName - Proposed new name
   * @returns {Object} Impact report with all locations that need updating
   */
  analyzeRename(oldName, newName) {
    const defs = this.index.goToDefinition(oldName);
    const refs = this.index.findReferences(oldName);

    // Check if new name conflicts
    const conflicts = this.index.goToDefinition(newName);

    const affectedFiles = new Set([
      ...defs.map(d => d.file),
      ...refs.map(r => r.file),
    ]);

    return {
      impact: refs.length > 0 ? 'requires_update' : 'safe',
      hasConflict: conflicts.length > 0,
      conflictWith: conflicts.length > 0 ? conflicts[0] : null,
      definitions: defs.map(d => ({ file: d.file, line: d.line, type: d.type })),
      references: refs.length,
      affectedFiles: [...affectedFiles],
      updates: [
        ...defs.map(d => ({ file: d.file, line: d.line, action: 'rename_definition', from: oldName, to: newName })),
        ...refs.map(r => ({ file: r.file, line: r.line, action: 'rename_reference', from: oldName, to: newName })),
      ],
      suggestion: conflicts.length > 0
        ? `Cannot rename: "${newName}" already exists in ${conflicts[0].file}.`
        : `Rename will update ${defs.length} definition(s) and ${refs.length} reference(s) across ${affectedFiles.size} file(s).`,
    };
  }

  /**
   * Analyze impact of changing a service contract
   * (changing input/output params of a SERVICE/PUBLIC_SERVICE)
   */
  analyzeServiceChange(serviceDomainName, methodName) {
    const refs = this.index.findReferences(serviceDomainName);
    const methodRefs = this.index.findReferences(`${serviceDomainName}.${methodName}`);

    const callingSections = refs.filter(r => r.type === 'calls_service');

    return {
      impact: callingSections.length > 0 ? 'breaking' : 'safe',
      service: `${serviceDomainName}.${methodName}`,
      callingSections: callingSections.map(r => ({
        file: r.file, line: r.line,
        message: `Section "${r.file}" calls this service and may need input/output mapping updates`,
      })),
      methodCallSites: methodRefs.map(r => ({
        file: r.file, line: r.line,
        message: `Method call at ${r.file}:${r.line} may need parameter updates`,
      })),
      suggestion: callingSections.length > 0
        ? `${callingSections.length} section(s) call this service. Update their service bindings.`
        : `No sections call this service directly.`,
    };
  }

  /**
   * Analyze impact of changing a component's public interface
   * (adding/removing public props, events, or methods)
   */
  analyzeComponentChange(componentName, changeType, symbolName) {
    const refs = this.index.findReferences(componentName);
    const parentFiles = refs
      .filter(r => r.type === 'uses_component')
      .map(r => r.file);

    return {
      impact: parentFiles.length > 0 ? 'requires_update' : 'safe',
      component: componentName,
      changeType,
      changedSymbol: symbolName,
      parentFiles: [...new Set(parentFiles)],
      details: parentFiles.map(f => ({
        file: f,
        message: changeType === 'remove_prop'
          ? `Parent "${f}" may be passing prop "${symbolName}" which no longer exists`
          : changeType === 'add_required_prop'
          ? `Parent "${f}" needs to provide new required prop "${symbolName}"`
          : changeType === 'remove_event'
          ? `Parent "${f}" may be handling event "${symbolName}" which no longer exists`
          : `Parent "${f}" may need updates due to interface change`,
      })),
      suggestion: parentFiles.length > 0
        ? `${parentFiles.length} parent file(s) use this component. Review their bindings.`
        : `No parent files use this component.`,
    };
  }

  /**
   * Full project impact scan: find all potential issues
   * (unused symbols, missing references, type mismatches)
   */
  fullProjectScan() {
    const issues = [];

    // 1. Find unused symbols (defined but never referenced)
    for (const [id, sym] of this.index.symbols) {
      if (['root_component', 'section_header'].includes(sym.type)) continue;
      if (sym.isLocal) continue;

      const refs = this.index.findReferences(sym.name);
      if (refs.length === 0 && sym.isPublic) {
        issues.push({
          type: 'unused_public',
          severity: 'warning',
          file: sym.file,
          line: sym.line,
          message: `Public ${sym.type} "${sym.name}" is never referenced from other files`,
        });
      }
    }

    // 2. Find broken references (referenced but not defined)
    const definedNames = new Set();
    for (const [, sym] of this.index.symbols) {
      definedNames.add(sym.name);
      if (sym.instanceId) definedNames.add(sym.instanceId);
    }
    for (const [name] of this.index.themeTokens) {
      definedNames.add(name);
    }

    for (const ref of this.index.references) {
      if (ref.type === 'calls_method') continue; // Method calls are harder to validate
      if (!definedNames.has(ref.toName)) {
        issues.push({
          type: 'broken_reference',
          severity: 'error',
          file: ref.from.file,
          line: ref.from.line,
          message: `Reference to "${ref.toName}" but no definition found (${ref.type})`,
        });
      }
    }

    // 3. Find theme tokens used but not defined
    for (const ref of this.index.references) {
      if (ref.type === 'uses_theme_token' && !this.index.themeTokens.has(ref.toName)) {
        issues.push({
          type: 'undefined_token',
          severity: 'warning',
          file: ref.from.file,
          line: ref.from.line,
          message: `Theme token "${ref.toName}" is used but not defined in theme file`,
        });
      }
    }

    return {
      issues,
      summary: {
        errors: issues.filter(i => i.severity === 'error').length,
        warnings: issues.filter(i => i.severity === 'warning').length,
        total: issues.length,
      },
    };
  }
}
