import { findNonFiniteNumber, inferMimeType, sanitizeToJson } from './content';

describe('Content Utils', () => {
  describe('sanitizeToJson', () => {
    it('should pass through primitives', () => {
      expect(sanitizeToJson('hello')).toBe('hello');
      expect(sanitizeToJson(42)).toBe(42);
      expect(sanitizeToJson(true)).toBe(true);
      expect(sanitizeToJson(null)).toBe(null);
    });

    it('should convert BigInt to string', () => {
      expect(sanitizeToJson(BigInt(9007199254740991))).toBe('9007199254740991');
    });

    it('should convert Date to ISO string', () => {
      const date = new Date('2024-01-15T10:30:00.000Z');
      expect(sanitizeToJson(date)).toBe('2024-01-15T10:30:00.000Z');
    });

    it('should convert Error to object with name, message, stack', () => {
      const error = new Error('Test error');
      const result = sanitizeToJson(error) as Record<string, unknown>;

      expect(result['name']).toBe('Error');
      expect(result['message']).toBe('Test error');
      expect(typeof result['stack']).toBe('string');
    });

    it('should convert Map to object', () => {
      const map = new Map([
        ['a', 1],
        ['b', 2],
      ]);
      expect(sanitizeToJson(map)).toEqual({ a: 1, b: 2 });
    });

    it('should convert Set to array', () => {
      const set = new Set([1, 2, 3]);
      expect(sanitizeToJson(set)).toEqual([1, 2, 3]);
    });

    it('should drop functions', () => {
      expect(sanitizeToJson(() => 42)).toBeUndefined();
      expect(
        sanitizeToJson(function test() {
          return 42;
        }),
      ).toBeUndefined();
    });

    it('should drop symbols', () => {
      expect(sanitizeToJson(Symbol('test'))).toBeUndefined();
    });

    it('should handle circular references', () => {
      const obj: any = { a: 1 };
      obj.self = obj;

      const result = sanitizeToJson(obj) as Record<string, unknown>;
      expect(result['a']).toBe(1);
      expect(result['self']).toBeUndefined();
    });

    it('should recursively sanitize nested objects', () => {
      const obj = {
        date: new Date('2024-01-15T10:30:00.000Z'),
        nested: {
          bigint: BigInt(123),
          set: new Set([1, 2]),
        },
      };

      const result = sanitizeToJson(obj);
      expect(result).toEqual({
        date: '2024-01-15T10:30:00.000Z',
        nested: {
          bigint: '123',
          set: [1, 2],
        },
      });
    });

    it('should handle arrays', () => {
      const arr = [1, new Date('2024-01-01T00:00:00.000Z'), { a: 1 }];
      const result = sanitizeToJson(arr);

      expect(result).toEqual([1, '2024-01-01T00:00:00.000Z', { a: 1 }]);
    });

    it('should drop undefined values in objects', () => {
      const obj = { a: 1, b: undefined, c: 3 };
      const result = sanitizeToJson(obj) as Record<string, unknown>;

      expect(result).toEqual({ a: 1, c: 3 });
      expect('b' in result).toBe(false);
    });

    it('should handle circular references in arrays', () => {
      const arr: any[] = [1, 2];
      arr.push(arr);

      const result = sanitizeToJson(arr) as unknown[];
      expect(result).toEqual([1, 2, undefined]);
    });
  });

  describe('inferMimeType', () => {
    it('should infer from .json extension', () => {
      expect(inferMimeType('file:///data.json')).toBe('application/json');
    });

    it('should infer from .md extension', () => {
      expect(inferMimeType('file:///readme.md')).toBe('text/markdown');
    });

    it('should infer from .png extension', () => {
      expect(inferMimeType('file:///image.png')).toBe('image/png');
    });

    it('should infer from .jpg extension', () => {
      expect(inferMimeType('file:///photo.jpg')).toBe('image/jpeg');
    });

    it('should infer from .html extension', () => {
      expect(inferMimeType('file:///page.html')).toBe('text/html');
    });

    it('should infer from .css extension', () => {
      expect(inferMimeType('file:///style.css')).toBe('text/css');
    });

    it('should infer from .js extension', () => {
      expect(inferMimeType('file:///script.js')).toBe('application/javascript');
    });

    it('should infer from .ts extension', () => {
      expect(inferMimeType('file:///code.ts')).toBe('application/typescript');
    });

    it('should infer from .yaml extension', () => {
      expect(inferMimeType('file:///config.yaml')).toBe('application/yaml');
    });

    it('should infer from .yml extension', () => {
      expect(inferMimeType('file:///config.yml')).toBe('application/yaml');
    });

    it('should infer from .xml extension', () => {
      expect(inferMimeType('file:///data.xml')).toBe('application/xml');
    });

    it('should infer from .pdf extension', () => {
      expect(inferMimeType('file:///document.pdf')).toBe('application/pdf');
    });

    it('should infer JSON from content starting with {', () => {
      expect(inferMimeType('unknown://uri', '{"key": "value"}')).toBe('application/json');
    });

    it('should infer JSON from content starting with [', () => {
      expect(inferMimeType('unknown://uri', '[1, 2, 3]')).toBe('application/json');
    });

    it('should infer HTML from content with <!DOCTYPE', () => {
      expect(inferMimeType('unknown://uri', '<!DOCTYPE html><html>')).toBe('text/html');
    });

    it('should infer HTML from content with <html', () => {
      expect(inferMimeType('unknown://uri', '<html><body>')).toBe('text/html');
    });

    it('should infer XML from content starting with <', () => {
      expect(inferMimeType('unknown://uri', '<root><item/></root>')).toBe('application/xml');
    });

    it('should default to text/plain', () => {
      expect(inferMimeType('unknown://uri')).toBe('text/plain');
      expect(inferMimeType('unknown://uri', 'plain text content')).toBe('text/plain');
    });

    it('should handle URIs without extension', () => {
      expect(inferMimeType('api://resource')).toBe('text/plain');
    });

    it('should handle case insensitive extensions', () => {
      expect(inferMimeType('file:///data.JSON')).toBe('application/json');
      expect(inferMimeType('file:///image.PNG')).toBe('image/png');
    });
  });

  describe('findNonFiniteNumber', () => {
    it('returns undefined for finite primitives', () => {
      expect(findNonFiniteNumber(0)).toBeUndefined();
      expect(findNonFiniteNumber(42)).toBeUndefined();
      expect(findNonFiniteNumber(-3.14)).toBeUndefined();
      expect(findNonFiniteNumber(Number.MAX_VALUE)).toBeUndefined();
    });

    it('returns undefined for non-numeric values', () => {
      expect(findNonFiniteNumber('Infinity')).toBeUndefined();
      expect(findNonFiniteNumber(null)).toBeUndefined();
      expect(findNonFiniteNumber(undefined)).toBeUndefined();
      expect(findNonFiniteNumber(true)).toBeUndefined();
    });

    it('detects Infinity at the root', () => {
      expect(findNonFiniteNumber(Infinity)).toEqual({ path: '', value: Infinity });
    });

    it('detects -Infinity at the root', () => {
      expect(findNonFiniteNumber(-Infinity)).toEqual({ path: '', value: -Infinity });
    });

    it('detects NaN at the root', () => {
      expect(findNonFiniteNumber(NaN)).toEqual({ path: '', value: NaN });
    });

    it('reports the property path for nested objects', () => {
      const found = findNonFiniteNumber({ a: { b: { c: Infinity } } });
      expect(found?.path).toBe('a.b.c');
      expect(found?.value).toBe(Infinity);
    });

    it('reports the array index for nested arrays', () => {
      const found = findNonFiniteNumber({ items: [1, 2, NaN, 3] });
      expect(found?.path).toBe('items[2]');
    });

    it('returns the first occurrence in object key order', () => {
      const found = findNonFiniteNumber({ first: 1, second: NaN, third: Infinity });
      expect(found?.path).toBe('second');
    });

    it('handles cycles without infinite recursion', () => {
      const a: Record<string, unknown> = { name: 'a' };
      a['self'] = a;
      expect(() => findNonFiniteNumber(a)).not.toThrow();
      // No non-finite anywhere → returns undefined
      expect(findNonFiniteNumber(a)).toBeUndefined();
    });
  });
});
