import { describe, it, expect, beforeEach, afterEach } from 'vitest';
import { ContentSerializer } from '../../../../assets/admin/editor/ContentSerializer';

describe('ContentSerializer', () => {
    afterEach(() => {
        document.body.innerHTML = '';
    });

    describe('serialize() - flat mode (no [data-section] elements)', () => {
        beforeEach(() => {
            document.body.innerHTML = `
                <div data-editable="true" data-field="introText">Hello <b>world</b></div>
                <div data-editable-image="true" data-field="heroImage" data-media-id="media-123"></div>
                <a data-editable-href="true" data-field="ctaLink" href="/contact">Contact</a>
            `;
        });

        it('serializes flat editable fields, image fields and href fields', () => {
            const result = new ContentSerializer().serialize();

            expect(result.introText).toBe('Hello <b>world</b>');
            expect(result.heroImage).toBe('media-123');
            expect(result.ctaLink).toBe('/contact');
        });
    });

    describe('serialize() - Hiver (dark) image variants', () => {
        it('emits <field>Dark only for themable slots that have a Hiver image', () => {
            document.body.innerHTML = `
                <div data-editable-image="true" data-field="introImage" data-media-id="summer" data-media-id-dark="winter"></div>
                <div data-editable-image="true" data-field="equipeImage" data-media-id="summer-only" data-media-id-dark=""></div>
                <div data-editable-image="true" data-field="legacyImage" data-media-id="legacy"></div>
            `;

            const result = new ContentSerializer().serialize();

            expect(result.introImage).toBe('summer');
            expect(result.introImageDark).toBe('winter');
            expect(result.equipeImage).toBe('summer-only');
            expect(result).not.toHaveProperty('equipeImageDark');
            expect(result).not.toHaveProperty('legacyImageDark');
        });

        it('keeps images[] and imagesDark[] index-aligned, including Hiver-only slots', () => {
            document.body.innerHTML = `
                <div data-section="g1" data-section-type="gallery">
                    <div data-editable-image="true" data-field="images[]" data-media-id="a" data-media-id-dark="a-d"></div>
                    <div data-editable-image="true" data-field="images[]" data-media-id="" data-media-id-dark="b-d"></div>
                    <div data-editable-image="true" data-field="images[]" data-media-id="c" data-media-id-dark=""></div>
                    <div data-editable-image="true" data-field="images[]" data-media-id="" data-media-id-dark=""></div>
                </div>
            `;

            const result = new ContentSerializer().serialize();
            const fields = (result.sections as any[])[0].fields;

            expect(fields.images).toEqual(['a', '', 'c']);
            expect(fields.imagesDark).toEqual(['a-d', 'b-d', '']);
        });
    });

    describe('serialize() - cadrage (Frame / DarkFrame / Ratio)', () => {
        it('emits frames and ratio only for slots rendered with data-image-frame', () => {
            document.body.innerHTML = `
                <div data-editable-image="true" data-field="introImage" data-media-id="s" data-media-id-dark="w"
                    data-image-frame='{"x":30,"zoom":1.5}' data-image-frame-dark="" data-image-ratio="4/5"></div>
                <div data-editable-image="true" data-field="beforeImage" data-media-id="b" data-media-id-dark=""
                    data-image-frame="" data-image-frame-dark='{"y":10}' data-image-ratio="" data-frame-ratio="false"></div>
                <div data-editable-image="true" data-field="legacyImage" data-media-id="l"></div>
            `;

            const result = new ContentSerializer().serialize();

            expect(result.introImageFrame).toEqual({ x: 30, zoom: 1.5 });
            expect(result.introImageDarkFrame).toBeNull();
            expect(result.introImageRatio).toBe('4/5');
            expect(result.beforeImageFrame).toBeNull();
            expect(result.beforeImageDarkFrame).toEqual({ y: 10 });
            expect(result).not.toHaveProperty('beforeImageRatio');
            expect(result).not.toHaveProperty('legacyImageFrame');
        });

        it('keeps imagesFrame[] / imagesDarkFrame[] aligned with images[]', () => {
            document.body.innerHTML = `
                <div data-section="g1" data-section-type="gallery">
                    <div data-editable-image="true" data-field="images[]" data-media-id="a" data-media-id-dark=""
                        data-image-frame='{"x":10}' data-image-frame-dark="" data-image-ratio="" data-frame-ratio="false"></div>
                    <div data-editable-image="true" data-field="images[]" data-media-id="" data-media-id-dark=""
                        data-image-frame='{"x":99}' data-image-frame-dark="" data-image-ratio="" data-frame-ratio="false"></div>
                    <div data-editable-image="true" data-field="images[]" data-media-id="c" data-media-id-dark="c-d"
                        data-image-frame="" data-image-frame-dark='{"zoom":2}' data-image-ratio="" data-frame-ratio="false"></div>
                </div>
            `;

            const fields = (new ContentSerializer().serialize().sections as any[])[0].fields;

            expect(fields.images).toEqual(['a', 'c']);
            expect(fields.imagesFrame).toEqual([{ x: 10 }, null]);
            expect(fields.imagesDarkFrame).toEqual([null, { zoom: 2 }]);
            expect(fields).not.toHaveProperty('imagesRatio');
        });
    });

    describe('serialize() - sectioned mode, Actualité blocks', () => {
        beforeEach(() => {
            document.body.innerHTML = `
                <div data-editable-image="true" data-field="mainImage" data-media-id="hero-media-id"></div>

                <h1 data-editable="true" data-editor-type="simple" data-field="title" data-section="header" data-section-type="header">
                    Mon titre d'article
                </h1>

                <div id="actualite-blocks" data-blocks-container="true">
                    <div data-section="block-1" data-section-type="text">
                        <h2 data-editable="true" data-editor-type="simple" data-field="title">Titre bloc 1</h2>
                        <p data-editable="true" data-editor-type="simple" data-field="subtitle">Sous-titre 1</p>
                        <div data-editable="true" data-field="content"><p>Contenu 1</p></div>
                    </div>

                    <div data-section="block-2" data-section-type="text-image-left" data-mobile-image-position="after">
                        <div data-editable-image="true" data-field="image" data-media-id="block-2-media-id"></div>
                        <h2 data-editable="true" data-editor-type="simple" data-field="title">Titre bloc 2</h2>
                        <div data-editable="true" data-field="content"><p>Contenu 2</p></div>
                    </div>
                </div>

                <div data-section="related-posts" data-section-type="related-posts">
                    <h2 data-editable="true" data-editor-type="simple" data-field="relatedPostsTitle">Autres news à découvrir</h2>
                </div>
            `;
        });

        it('excludes fixed non-block sections (e.g. related-posts) from the blocks array', () => {
            const result = new ContentSerializer().serialize();

            expect(result.blocks).toHaveLength(2);
            expect(result.blocks.every((block: any) => block.type !== 'related-posts')).toBe(true);
        });

        it('extracts the header title via extractTitle() and excludes it from blocks', () => {
            const serializer = new ContentSerializer();

            expect(serializer.extractTitle()!.trim()).toBe("Mon titre d'article");

            const result = serializer.serialize();
            expect(result.blocks).toHaveLength(2);
            expect(result.blocks.every((block: any) => block.type !== 'header')).toBe(true);
        });

        it('produces {mainImage, blocks} matching ActualitePageContentSaver expectations', () => {
            const result = new ContentSerializer().serialize();

            expect(result.mainImage).toBe('hero-media-id');
            expect(result.blocks).toEqual([
                {
                    id: 'block-1',
                    type: 'text',
                    fields: {
                        title: 'Titre bloc 1',
                        subtitle: 'Sous-titre 1',
                        content: '<p>Contenu 1</p>',
                    },
                },
                {
                    id: 'block-2',
                    type: 'text-image-left',
                    fields: {
                        image: 'block-2-media-id',
                        title: 'Titre bloc 2',
                        content: '<p>Contenu 2</p>',
                        mobileImagePosition: 'after',
                    },
                },
            ]);
        });

        it('defaults mobileImagePosition to "before" when the attribute is absent', () => {
            const block = document.querySelector('[data-section="block-2"]') as HTMLElement;
            delete block.dataset.mobileImagePosition;

            const result = new ContentSerializer().serialize();
            const block2 = result.blocks.find((b: any) => b.id === 'block-2');

            expect(block2.fields.mobileImagePosition).toBe('before');
        });

        it('reflects live DOM order, including client-added and client-removed blocks', () => {
            const blocksContainer = document.getElementById('actualite-blocks')!;
            const block1 = document.querySelector('[data-section="block-1"]')!;
            const block2 = document.querySelector('[data-section="block-2"]')!;

            // Remove block-1
            block1.remove();

            // Add a new block-3, inserted before block-2 (simulating a client-side reorder)
            const block3 = document.createElement('div');
            block3.dataset.section = 'block-3';
            block3.dataset.sectionType = 'text';
            const titleEl = document.createElement('h2');
            titleEl.dataset.editable = 'true';
            titleEl.dataset.field = 'title';
            titleEl.innerHTML = 'Titre bloc 3';
            block3.appendChild(titleEl);
            blocksContainer.insertBefore(block3, block2);

            const result = new ContentSerializer().serialize();

            expect(result.blocks.map((b: any) => b.id)).toEqual(['block-3', 'block-2']);
        });
    });

    describe('serialize() - flat template reusing _page_hero.html.twig (About/Expertise)', () => {
        beforeEach(() => {
            // _page_hero.html.twig always stamps data-section="header" on the title when
            // edit_mode is true, even for templates with no other sections (see
            // ContentSerializer.serialize() doc comment). Must not force serializeSectioned().
            document.body.innerHTML = `
                <h1 data-editable="true" data-editor-type="simple" data-field="title" data-section="header" data-section-type="header">
                    Savoir-faire
                </h1>

                <div data-editable="true" data-field="introTitle">Intro title</div>
                <div data-editable="true" data-field="item1Title">Item 1</div>
                <div data-editable-image="true" data-field="itemsImage" data-media-id="items-media-id"></div>
            `;
        });

        it('serializes flat fields instead of collapsing to {sections: []}', () => {
            const result = new ContentSerializer().serialize();

            expect(result.sections).toBeUndefined();
            expect(result.introTitle).toBe('Intro title');
            expect(result.item1Title).toBe('Item 1');
            expect(result.itemsImage).toBe('items-media-id');
        });

        it('still extracts the title via extractTitle()', () => {
            expect(new ContentSerializer().extractTitle()!.trim()).toBe('Savoir-faire');
        });
    });

    describe('serialize() - indexed-bracket "repeatable object list" convention (decision (b))', () => {
        afterEach(() => {
            document.body.innerHTML = '';
        });

        it('parses 0 items into an empty array when no matching fields exist', () => {
            document.body.innerHTML = `
                <div data-editable="true" data-field="introText">Hello</div>
            `;

            const result = new ContentSerializer().serialize();

            expect(result.projectInfoItems).toBeUndefined();
        });

        it('keeps Hiver / cadrage of an image subfield (no ratio when data-frame-ratio="false") inside the item, plus its stable id', () => {
            document.body.innerHTML = `
                <span data-editable-value="true" data-field="galtaItems[0][id]" data-value="a1"></span>
                <div data-editable-image="true" data-field="galtaItems[0][image]" data-media-id="s" data-media-id-dark="w"
                    data-image-frame='{"x":30}' data-image-frame-dark="" data-image-ratio="" data-frame-ratio="false"></div>
                <div data-editable="true" data-field="galtaItems[0][text]"><p>Hi</p></div>
                <span data-editable-value="true" data-field="galtaItems[1][id]" data-value="b2"></span>
                <div data-editable-image="true" data-field="galtaItems[1][image]" data-media-id="" data-media-id-dark=""></div>
                <div data-editable="true" data-field="galtaItems[1][text]"></div>
            `;

            const result = new ContentSerializer().serialize();

            expect(result.galtaItems).toEqual([
                { id: 'a1', image: 's', imageDark: 'w', imageFrame: { x: 30 }, imageDarkFrame: null, text: '<p>Hi</p>' },
                { id: 'b2', text: '' },
            ]);
        });

        it('parses a single item into fields.name = [{subfield: value}]', () => {
            document.body.innerHTML = `
                <dt data-editable="true" data-field="projectInfoItems[0][label]">Client</dt>
                <dd data-editable="true" data-field="projectInfoItems[0][value]">Acme Corp</dd>
            `;

            const result = new ContentSerializer().serialize();

            expect(result.projectInfoItems).toEqual([{ label: 'Client', value: 'Acme Corp' }]);
        });

        it('parses N items into fields.name = [{...}, {...}, ...] by array index', () => {
            document.body.innerHTML = `
                <dt data-editable="true" data-field="projectInfoItems[0][label]">Client</dt>
                <dd data-editable="true" data-field="projectInfoItems[0][value]">Acme Corp</dd>
                <dt data-editable="true" data-field="projectInfoItems[1][label]">Date</dt>
                <dd data-editable="true" data-field="projectInfoItems[1][value]">2026</dd>
                <dt data-editable="true" data-field="projectInfoItems[2][label]">Lieu</dt>
                <dd data-editable="true" data-field="projectInfoItems[2][value]">Chamonix</dd>
            `;

            const result = new ContentSerializer().serialize();

            expect(result.projectInfoItems).toEqual([
                { label: 'Client', value: 'Acme Corp' },
                { label: 'Date', value: '2026' },
                { label: 'Lieu', value: 'Chamonix' },
            ]);
        });

        it('reflects index renumbering after item removal from the middle (live DOM order)', () => {
            document.body.innerHTML = `
                <dt data-editable="true" data-field="projectInfoItems[0][label]">Client</dt>
                <dd data-editable="true" data-field="projectInfoItems[0][value]">Acme Corp</dd>
                <dt data-editable="true" data-field="projectInfoItems[1][label]">Date</dt>
                <dd data-editable="true" data-field="projectInfoItems[1][value]">2026</dd>
                <dt data-editable="true" data-field="projectInfoItems[2][label]">Lieu</dt>
                <dd data-editable="true" data-field="projectInfoItems[2][value]">Chamonix</dd>
            `;

            // Simulate RepeatableObjectListEditor's reindex-on-remove: the middle item
            // ("Date", originally index 1) was removed from the DOM and the trailing item's
            // ("Lieu") indices rewritten to close the gap (2 -> 1), exactly like
            // RepeatableObjectListEditor.reindex() does.
            document.querySelectorAll('[data-field^="projectInfoItems[1]"]').forEach(el => el.remove());
            document.querySelectorAll<HTMLElement>('[data-field^="projectInfoItems[2]"]').forEach(el => {
                el.dataset.field = el.dataset.field!.replace('projectInfoItems[2]', 'projectInfoItems[1]');
            });

            const result = new ContentSerializer().serialize();

            expect(result.projectInfoItems).toEqual([
                { label: 'Client', value: 'Acme Corp' },
                { label: 'Lieu', value: 'Chamonix' },
            ]);
        });

        it('does not confuse a name[index][subfield] field with an unrelated name[] flat array field', () => {
            document.body.innerHTML = `
                <span data-editable="true" data-field="tags[]">alpine</span>
                <span data-editable="true" data-field="tags[]">expedition</span>
                <dt data-editable="true" data-field="projectInfoItems[0][label]">Client</dt>
                <dd data-editable="true" data-field="projectInfoItems[0][value]">Acme Corp</dd>
            `;

            const result = new ContentSerializer().serialize();

            expect(result.tags).toEqual(['alpine', 'expedition']);
            expect(result.projectInfoItems).toEqual([{ label: 'Client', value: 'Acme Corp' }]);
        });
    });

    describe('serialize() - flat `name[]` scalar array for plain data-editable fields (hero tag pills)', () => {
        afterEach(() => {
            document.body.innerHTML = '';
        });

        it('collects every data-editable element sharing a `field[]` name into a single array', () => {
            document.body.innerHTML = `
                <span data-editable="true" data-field="tags[]">alpine</span>
                <span data-editable="true" data-field="tags[]">expedition</span>
                <span data-editable="true" data-field="tags[]">hiver</span>
            `;

            const result = new ContentSerializer().serialize();

            expect(result.tags).toEqual(['alpine', 'expedition', 'hiver']);
        });

        it('produces an empty array when zero elements share the field name (none present)', () => {
            document.body.innerHTML = `<div data-editable="true" data-field="title">Titre</div>`;

            const result = new ContentSerializer().serialize();

            expect(result.tags).toBeUndefined();
        });
    });

    describe('extractTitle()', () => {
        it('returns null when there is no [data-section="header"] element', () => {
            document.body.innerHTML = '<div data-editable="true" data-field="foo">bar</div>';

            expect(new ContentSerializer().extractTitle()).toBeNull();
        });
    });
});
