import { json } from '@codemirror/lang-json';
import { EditorState } from '@codemirror/state';
import { computeJsonPath } from './bms-input-code.helper';

function makeState(doc: string): EditorState {
  return EditorState.create({ doc, extensions: [json()] });
}

function pos(doc: string, search: string, offset = 0): number {
  return doc.indexOf(search) + offset;
}

describe('computeJsonPath', () => {
  describe('flat object', () => {
    const doc = '{"name":"Alice","age":30}';
    const state = makeState(doc);

    it('returns the key path when clicking on a property name', () => {
      expect(computeJsonPath(state, pos(doc, '"name"') + 1)).toBe('$.name');
    });

    it('returns the key path when clicking on a string value', () => {
      expect(computeJsonPath(state, pos(doc, '"Alice"') + 1)).toBe('$.name');
    });

    it('returns the key path when clicking on a numeric value', () => {
      expect(computeJsonPath(state, pos(doc, '30'))).toBe('$.age');
    });

    it('returns $ when clicking on the root object bracket', () => {
      expect(computeJsonPath(state, 0)).toBe('$');
    });
  });

  describe('nested object', () => {
    const doc = '{"user":{"address":{"city":"Paris","zip":"75001"}}}';
    const state = makeState(doc);

    it('returns a two-level path', () => {
      expect(computeJsonPath(state, pos(doc, '"address"') + 1)).toBe(
        '$.user.address',
      );
    });

    it('returns a three-level path on a deeply nested key', () => {
      expect(computeJsonPath(state, pos(doc, '"city"') + 1)).toBe(
        '$.user.address.city',
      );
    });

    it('returns a three-level path on a deeply nested value', () => {
      expect(computeJsonPath(state, pos(doc, '"Paris"') + 1)).toBe(
        '$.user.address.city',
      );
    });
  });

  describe('array', () => {
    const doc = '{"tags":["admin","editor","viewer"]}';
    const state = makeState(doc);

    it('returns index 0 for the first array element', () => {
      expect(computeJsonPath(state, pos(doc, '"admin"') + 1)).toBe('$.tags[0]');
    });

    it('returns index 1 for the second array element', () => {
      expect(computeJsonPath(state, pos(doc, '"editor"') + 1)).toBe(
        '$.tags[1]',
      );
    });

    it('returns index 2 for the third array element', () => {
      expect(computeJsonPath(state, pos(doc, '"viewer"') + 1)).toBe(
        '$.tags[2]',
      );
    });
  });

  describe('array of objects', () => {
    const doc = '{"items":[{"id":1},{"id":2}]}';
    const state = makeState(doc);

    it('returns the path for a key inside the first object', () => {
      expect(computeJsonPath(state, pos(doc, '"id"') + 1)).toBe(
        '$.items[0].id',
      );
    });

    it('returns the path for a key inside the second object', () => {
      const secondId = doc.indexOf('"id"', doc.indexOf('"id"') + 1);
      expect(computeJsonPath(state, secondId + 1)).toBe('$.items[1].id');
    });
  });

  describe('boolean and null values', () => {
    const doc = '{"active":true,"deleted":false,"ref":null}';
    const state = makeState(doc);

    it('returns the path for a boolean true value', () => {
      expect(computeJsonPath(state, pos(doc, 'true'))).toBe('$.active');
    });

    it('returns the path for a boolean false value', () => {
      expect(computeJsonPath(state, pos(doc, 'false'))).toBe('$.deleted');
    });

    it('returns the path for a null value', () => {
      expect(computeJsonPath(state, pos(doc, 'null'))).toBe('$.ref');
    });
  });
});
