import { describe, expect, test, beforeEach } from 'vitest';
import { SourceMapService, sourceMapService, SourceLocation } from '@core/utils/SourceMapService.js';
import { extractErrorLocation, extractLocationFromErrorObject, addMapping, resetSourceMaps } from '@core/utils/sourceMapUtils.js';
import { MeldError } from '@core/errors/MeldError.js';
import { enhanceMeldErrorWithSourceInfo } from '@core/utils/sourceMapUtils.js';

describe('SourceMapService', () => {
  beforeEach(() => {
    // Reset source maps before each test
    resetSourceMaps();
  });

  test('should register source files', () => {
    const service = new SourceMapService();
    service.registerSource('/path/to/file.md', 'line 1\nline 2\nline 3');
    
    // This is just testing that it doesn't throw
    expect(service).toBeDefined();
  });

  test('should add mappings', () => {
    const service = new SourceMapService();
    const sourceLocation: SourceLocation = {
      filePath: '/path/to/file.md',
      line: 1,
      column: 0
    };
    
    service.addMapping(sourceLocation, 10, 5);
    
    // This is just testing that it doesn't throw
    expect(service).toBeDefined();
  });

  test('should find original location for a given combined location', () => {
    const service = new SourceMapService();
    
    // Register source
    service.registerSource('/path/to/file.md', 'line 1\nline 2\nline 3');
    
    // Add mapping from source line 1 to combined line 10
    service.addMapping(
      { filePath: '/path/to/file.md', line: 1, column: 0 },
      10,
      0
    );
    
    // Find original location for combined line 12
    const originalLocation = service.findOriginalLocation(12, 0);
    
    // Should map to source line 3 (1 + (12 - 10))
    expect(originalLocation).toEqual({
      filePath: '/path/to/file.md',
      line: 3,
      column: 0
    });
  });

  test('should handle multiple mappings', () => {
    const service = new SourceMapService();
    
    // Register sources
    service.registerSource('/path/to/file1.md', 'source1 line 1\nsource1 line 2');
    service.registerSource('/path/to/file2.md', 'source2 line 1\nsource2 line 2');
    
    // Add mappings
    service.addMapping(
      { filePath: '/path/to/file1.md', line: 1, column: 0 },
      10,
      0
    );
    
    service.addMapping(
      { filePath: '/path/to/file2.md', line: 1, column: 0 },
      20,
      0
    );
    
    // Find original locations
    const location1 = service.findOriginalLocation(11, 0);
    const location2 = service.findOriginalLocation(21, 0);
    
    // Should map to the correct source files and lines
    expect(location1).toEqual({
      filePath: '/path/to/file1.md',
      line: 2,
      column: 0
    });
    
    expect(location2).toEqual({
      filePath: '/path/to/file2.md',
      line: 2,
      column: 0
    });
  });

  test('should return null if no mapping exists', () => {
    const service = new SourceMapService();
    
    // No mappings registered
    const location = service.findOriginalLocation(10, 0);
    
    expect(location).toBeNull();
  });

  test('should provide debug info', () => {
    const service = new SourceMapService();
    
    // Add a mapping
    service.addMapping(
      { filePath: '/path/to/file.md', line: 1, column: 0 },
      10,
      0
    );
    
    // Get debug info
    const debug = service.getDebugInfo();
    
    // Should contain mapping info
    expect(debug).toContain('/path/to/file.md:1:0 -> 10:0');
  });
});

describe('sourceMapUtils', () => {
  beforeEach(() => {
    // Reset source maps before each test
    resetSourceMaps();
  });

  test('should extract line and column from error message', () => {
    const error = new Error('Error at line 10, column 5');
    const location = extractErrorLocation(error);
    
    expect(location).toEqual({
      line: 10,
      column: 5
    });
  });

  test('should extract location from error object', () => {
    const error = {
      message: 'Error message',
      location: {
        start: {
          line: 10,
          column: 5
        },
        end: {
          line: 10,
          column: 10
        }
      }
    };
    
    const location = extractLocationFromErrorObject(error);
    
    expect(location).toEqual({
      line: 10,
      column: 5
    });
  });

  test('should enhance MeldError with source information', () => {
    // Set up source mapping
    addMapping('/path/to/source.md', 1, 0, 10, 0);
    
    // Create error with location in message
    const error = new MeldError('Error at line 10, column 5');
    
    // Enhance error
    const enhanced = enhanceMeldErrorWithSourceInfo(error);
    
    // Check that the error is enhanced with source info
    expect(enhanced.message).toContain('/path/to/source.md:1');
    expect(enhanced.filePath).toEqual('/path/to/source.md');
    expect(enhanced.context?.sourceLocation).toEqual({
      filePath: '/path/to/source.md',
      line: 1,
      column: 5
    });
  });

  test('should respect preferExistingSourceInfo option', () => {
    // Set up source mapping
    addMapping('/path/to/source.md', 1, 0, 10, 0);
    
    // Create error with existing filePath
    const error = new MeldError('Error at line 10, column 5', {
      filePath: '/existing/path.md'
    });
    
    // Enhance error with preferExistingSourceInfo
    const enhanced = enhanceMeldErrorWithSourceInfo(error, {
      preferExistingSourceInfo: true
    });
    
    // Check that the error keeps existing filePath
    expect(enhanced.filePath).toEqual('/existing/path.md');
  });
});