import { describe, test, expect } from '@jest/globals';
import { DocumentWithSource } from '@stackbit/types';
import { fieldPathToString, getDocumentFieldValueAtFieldPath, stringToFieldPath } from '../src';

describe('test fieldPathToString', () => {
    test('should convert single item fieldPath', () => {
        const result = fieldPathToString(['title']);
        expect(result).toEqual('title');
    });

    test('should separate multi item fieldPath with dot', () => {
        const result = fieldPathToString(['section', 'title']);
        expect(result).toEqual('section.title');
    });

    test('should wrap first non-alphanumeric field path item with square brackets and single quotes', () => {
        const result = fieldPathToString(['hero-section', 'title']);
        expect(result).toEqual("['hero-section'].title");
    });

    test('should wrap middle non-alphanumeric field path item with square brackets', () => {
        const result = fieldPathToString(['sections', 'hero-section', 'title']);
        expect(result).toEqual("sections['hero-section'].title");
    });

    test('should wrap successive non-alphanumeric field names with square brackets', () => {
        const result = fieldPathToString(['banner', 'sub-component-1', 'sub.component.2']);
        expect(result).toEqual("banner['sub-component-1']['sub.component.2']");
    });

    test('should wrap list indexes with square brackets', () => {
        const result = fieldPathToString(['sections', 2, 'title']);
        expect(result).toEqual('sections[2].title');
    });

    test('should not wrap string-numbers with square brackets', () => {
        const result = fieldPathToString(['sections', '2', 'title']);
        expect(result).toEqual('sections.2.title');
    });
});

describe('test stringToFieldPath', () => {
    test('should convert single item fieldPath', () => {
        const result = stringToFieldPath('title');
        expect(result).toEqual(['title']);
    });

    test('should separate multi item fieldPath with dot', () => {
        const result = stringToFieldPath('section.title');
        expect(result).toEqual(['section', 'title']);
    });

    test('should unwrap first non-alphanumeric field path item from square brackets', () => {
        const result = stringToFieldPath("['hero-section'].title");
        expect(result).toEqual(['hero-section', 'title']);
    });

    test('should unwrap middle non-alphanumeric field path item from square brackets', () => {
        const result = stringToFieldPath("sections['hero-section'].title");
        expect(result).toEqual(['sections', 'hero-section', 'title']);
    });

    test('should wrap successive non-alphanumeric field names with square brackets', () => {
        const result = stringToFieldPath("banner['sub-component-1']['sub.component.2']");
        expect(result).toEqual(['banner', 'sub-component-1', 'sub.component.2']);
    });

    test('should wrap list indexes with square brackets', () => {
        const result = stringToFieldPath('sections[2].title');
        expect(result).toEqual(['sections', 2, 'title']);
    });

    test('should not wrap string-numbers with square brackets', () => {
        const result = stringToFieldPath('sections.2.title');
        expect(result).toEqual(['sections', '2', 'title']);
    });
});

describe('test getDocumentFieldValueAtFieldPath', () => {
    const documents: DocumentWithSource[] = [
        {
            type: 'document',
            id: 'doc-1',
            srcType: 'src-1',
            srcProjectId: 'src-project-1',
            manageUrl: 'https://www.example.com',
            modelName: 'page',
            status: 'published',
            createdAt: '2025-01-01T10:00:00.000Z',
            updatedAt: '2025-01-01T10:00:00.000Z',
            context: null,
            fields: {
                title: { type: 'string', value: 'Title Value' },
                localizedString: {
                    type: 'string',
                    localized: true,
                    locales: {
                        en: {
                            locale: 'en',
                            value: 'Hello'
                        },
                        es: {
                            locale: 'es',
                            value: 'Hola'
                        }
                    }
                },
                modelField: {
                    type: 'model',
                    modelName: 'heroSection',
                    fields: {
                        label: { type: 'string', value: 'Label Value' }
                    }
                },
                localizedModelField: {
                    type: 'model',
                    localized: true,
                    locales: {
                        en: {
                            locale: 'en',
                            modelName: 'heroSection',
                            fields: {
                                label: { type: 'string', value: 'Localized Label Value' },
                                localizedString: {
                                    type: 'string',
                                    localized: true,
                                    locales: {
                                        es: {
                                            locale: 'es',
                                            value: 'Spanish value'
                                        }
                                    }
                                }
                            }
                        }
                    }
                },
                listField: {
                    type: 'list',
                    items: [
                        {
                            type: 'string',
                            value: 'one'
                        },
                        {
                            type: 'string',
                            value: 'two'
                        }
                    ]
                },
                referenceField: {
                    type: 'reference',
                    refType: 'document',
                    refId: 'doc-2'
                }
            }
        },
        {
            type: 'document',
            id: 'doc-2',
            srcType: 'src-1',
            srcProjectId: 'src-project-1',
            manageUrl: 'https://www.example.com',
            modelName: 'page',
            status: 'published',
            createdAt: '2025-01-01T10:00:00.000Z',
            updatedAt: '2025-01-01T10:00:00.000Z',
            context: null,
            fields: {
                title: { type: 'string', value: 'Doc 2 Title Value' }
            }
        }
    ];
    const document = documents[0]!;

    function getDocumentById(options: { id: string; srcType?: string; srcProjectId?: string }) {
        return documents.find((doc) => doc.id === options.id);
    }

    test('should throw an error if fieldPath is empty', () => {
        expect(() => {
            getDocumentFieldValueAtFieldPath({
                document,
                fieldPath: '',
                getDocumentById
            });
        }).toThrow("Illegal fieldPath ''. The fieldPath cannot be empty");

        expect(() => {
            getDocumentFieldValueAtFieldPath({
                document,
                fieldPath: [],
                getDocumentById
            });
        }).toThrow("Illegal fieldPath ''. The fieldPath cannot be empty");
    });

    test('should return value of a string field', () => {
        const result = getDocumentFieldValueAtFieldPath({
            document,
            fieldPath: 'title',
            getDocumentById
        });
        expect(result).toEqual('Title Value');
    });

    test('should throw an error if a primitive field is followed by another field path', () => {
        expect(() => {
            getDocumentFieldValueAtFieldPath({
                document,
                fieldPath: 'title.noField',
                getDocumentById
            });
        }).toThrow(
            "Illegal fieldPath 'title.noField'. The field at path 'title' is a primitive field of type 'string' and cannot be followed by another field path."
        );
    });

    test('should return undefined for non existing field', () => {
        const result = getDocumentFieldValueAtFieldPath({
            document,
            fieldPath: 'no_such_field',
            getDocumentById
        });
        expect(result).toBeUndefined();
    });

    test('should return value of a localized string field', () => {
        const result = getDocumentFieldValueAtFieldPath({
            document,
            fieldPath: 'localizedString.es',
            getDocumentById
        });
        expect(result).toEqual('Hola');
    });

    test('should return undefined for a non existing locale value of a localized field', () => {
        const result = getDocumentFieldValueAtFieldPath({
            document,
            fieldPath: 'localizedString.fr',
            getDocumentById
        });
        expect(result).toBeUndefined();
    });

    test('should throw if locale for a localized field was not specified', () => {
        expect(() => {
            getDocumentFieldValueAtFieldPath({
                document,
                fieldPath: 'localizedString',
                getDocumentById
            });
        }).toThrow("Illegal fieldPath 'localizedString'. The field at path 'localizedString' is a localized field and must be followed by a locale specifier.");
    });

    test('should return a flattened object of a model field', () => {
        const result = getDocumentFieldValueAtFieldPath({
            document,
            fieldPath: 'modelField',
            getDocumentById
        });
        expect(result).toEqual({
            __metadata: { modelName: 'heroSection' },
            label: 'Label Value'
        });
    });

    test('should return a value of a field in a nested object', () => {
        const result = getDocumentFieldValueAtFieldPath({
            document,
            fieldPath: 'modelField.label',
            getDocumentById
        });
        expect(result).toEqual('Label Value');
    });

    test('should return undefined for a non-existing field in a nested object', () => {
        const result = getDocumentFieldValueAtFieldPath({
            document,
            fieldPath: 'modelField.noField',
            getDocumentById
        });
        expect(result).toBeUndefined();
    });

    test('should return undefined for a non-existing nested fields', () => {
        const result = getDocumentFieldValueAtFieldPath({
            document,
            fieldPath: 'noField1.noField2.noField3',
            getDocumentById
        });
        expect(result).toBeUndefined();
    });

    test('should return a flattened object of a localized model field', () => {
        const result = getDocumentFieldValueAtFieldPath({
            document,
            fieldPath: 'localizedModelField.en',
            getDocumentById
        });
        expect(result).toEqual({
            __metadata: { modelName: 'heroSection' },
            label: 'Localized Label Value'
        });
    });

    test('should return a value of a field in a localized model field', () => {
        const result = getDocumentFieldValueAtFieldPath({
            document,
            fieldPath: 'localizedModelField.en.label',
            getDocumentById
        });
        expect(result).toEqual('Localized Label Value');
    });

    test('should return undefined for a non existing locale of a localized model field', () => {
        const result = getDocumentFieldValueAtFieldPath({
            document,
            fieldPath: 'localizedModelField.es',
            getDocumentById
        });
        expect(result).toBeUndefined();
    });

    test('should return a value for a localized string within a localized model field', () => {
        const result = getDocumentFieldValueAtFieldPath({
            document,
            fieldPath: 'localizedModelField.en.localizedString.es',
            getDocumentById
        });
        expect(result).toEqual('Spanish value');
    });

    test('should throw if locale for a localized model field was not specified', () => {
        expect(() => {
            getDocumentFieldValueAtFieldPath({
                document,
                fieldPath: 'localizedModelField',
                getDocumentById
            });
        }).toThrow(
            "Illegal fieldPath 'localizedModelField'. The field at path 'localizedModelField' is a localized field and must be followed by a locale specifier."
        );
    });

    test('should return an item list value in a list at specified index', () => {
        const result = getDocumentFieldValueAtFieldPath({
            document,
            fieldPath: 'listField[1]',
            getDocumentById
        });
        expect(result).toEqual('two');
    });

    test('should throw an error if list field is not followed by index', () => {
        expect(() => {
            getDocumentFieldValueAtFieldPath({
                document,
                fieldPath: 'listField.fieldName',
                getDocumentById
            });
        }).toThrow("Illegal fieldPath 'listField.fieldName'. The field at path 'listField' is a list field and must be followed by a list item index.");
    });

    test('should return a document id of a reference field', () => {
        const result = getDocumentFieldValueAtFieldPath({
            document,
            fieldPath: 'referenceField',
            getDocumentById
        });
        expect(result).toEqual('doc-2');
    });

    test('should return a value of a field in a referenced document', () => {
        const result = getDocumentFieldValueAtFieldPath({
            document,
            fieldPath: 'referenceField.title',
            getDocumentById
        });
        expect(result).toEqual('Doc 2 Title Value');
    });
});
