import {createHeadlessEditor} from '@lexical/headless';
import {assertPrettifiesTo, createDocument, dom, html} from '../test-utils/index.js';
import {$getRoot} from 'lexical';
import type {LexicalEditor} from 'lexical';
import {SignupNode, $createSignupNode, $isSignupNode, $createPaywallNode, type SignupData} from '../../src/index.js';
import {$generateNodesFromDOM} from '@lexical/html';

const editorNodes = [SignupNode];

describe('SignupNode', function () {
    let editor: LexicalEditor;
    let dataset: SignupData;
    let exportOptions: Record<string, unknown>;

    const editorTest = (testFn: () => void) => () => new Promise<void>((resolve, reject) => {
        editor.update(() => {
            try {
                testFn();
                resolve();
            } catch (e) {
                reject(e);
            }
        });
    });

    beforeEach(function () {
        editor = createHeadlessEditor({nodes: editorNodes});

        dataset = {
            backgroundColor: 'transparent',
            backgroundImageSrc: 'https://example.com/image.jpg',
            backgroundSize: 'cover',
            textColor: '#000000',
            buttonColor: '#000000',
            buttonText: 'Button',
            buttonTextColor: '#ffffff',
            disclaimer: 'Disclaimer',
            header: 'Header',
            subheader: 'Subheader',
            labels: ['label 1', 'label 2'],
            layout: 'regular',
            alignment: 'center',
            successMessage: 'Success!',
            swapped: false
        };

        exportOptions = {
            dom
        };
    });

    it('matches node with $isSignupNode', editorTest(function () {
        const signupNode = $createSignupNode(dataset);
        expect($isSignupNode(signupNode)).toBe(true);
    }));

    describe('clone', function () {
        it('clones the node', editorTest(function () {
            dataset.backgroundColor = '#000000';
            const signupNode = $createSignupNode(dataset);
            const clonedSignupNode = SignupNode.clone(signupNode) as SignupNode;
            const cloneDataset = clonedSignupNode.getDataset();
            expect(cloneDataset).toEqual(dataset);
        }));
    });

    describe('hasEditMode', function () {
        it('returns true', editorTest(function () {
            const signupNode = $createSignupNode(dataset);
            expect(signupNode.hasEditMode()).toBe(true);
        }));
    });

    describe('data access', function () {
        it('has getters for all properties', editorTest(function () {
            dataset.backgroundColor = '#000000';
            const signupNode = $createSignupNode(dataset);
            expect(signupNode.alignment).toBe(dataset.alignment);
            expect(signupNode.backgroundColor).toBe(dataset.backgroundColor);
            expect(signupNode.backgroundImageSrc).toBe(dataset.backgroundImageSrc);
            expect(signupNode.backgroundSize).toBe(dataset.backgroundSize);
            expect(signupNode.textColor).toBe(dataset.textColor);
            expect(signupNode.buttonColor).toBe(dataset.buttonColor);
            expect(signupNode.buttonText).toBe(dataset.buttonText);
            expect(signupNode.buttonTextColor).toBe(dataset.buttonTextColor);
            expect(signupNode.disclaimer).toBe(dataset.disclaimer);
            expect(signupNode.header).toBe(dataset.header);
            expect(signupNode.labels).toEqual(dataset.labels);
            expect(signupNode.layout).toBe(dataset.layout);
            expect(signupNode.subheader).toBe(dataset.subheader);
            expect(signupNode.successMessage).toBe(dataset.successMessage);
            expect(signupNode.swapped).toBe(dataset.swapped);
        }));

        it('has setters for all properties', editorTest(function () {
            const node = $createSignupNode(dataset);

            node.alignment = 'left';
            node.backgroundColor = '#f00';
            node.backgroundSize = 'contain';
            node.backgroundImageSrc = 'https://example.com/image2.jpg';
            node.textColor = '#0f0';
            node.buttonColor = '#00f';
            node.buttonTextColor = '#777';
            node.buttonText = 'This is the new button text';
            node.disclaimer = 'This is the new disclaimer';
            node.header = 'This is the new header';
            node.layout = 'compact';
            node.subheader = 'This is the new subheader';
            node.successMessage = 'This is the new success message';
            node.swapped = true;
            // Labels are tested in a separate block below because they are handled differently
        }));

        describe('labels', function () {
            it('can set multiple labels at once', editorTest(function () {
                const node = $createSignupNode(dataset);
                node.setLabels(['new label 1', 'new label 2']);
                expect(node.labels).toEqual(['new label 1', 'new label 2']);
            }));

            it('clones labels passed to the constructor', editorTest(function () {
                const labels = ['label 1', 'label 2'];
                const node = $createSignupNode({...dataset, labels});

                labels.push('label 3');
                expect(node.labels).toEqual(['label 1', 'label 2']);
            }));

            it('clones labels passed to setLabels', editorTest(function () {
                const node = $createSignupNode(dataset);
                const labels = ['new label 1', 'new label 2'];

                node.setLabels(labels);
                labels.push('new label 3');

                expect(node.labels).toEqual(['new label 1', 'new label 2']);
            }));

            it('clones labels returned from the getter', editorTest(function () {
                const node = $createSignupNode(dataset);
                const labels = node.labels;

                labels.push('new label 3');

                expect(node.labels).toEqual(['label 1', 'label 2']);
            }));

            it('only accepts an array of strings for setLabels', editorTest(function () {
                const node = $createSignupNode(dataset);
                expect(() => node.setLabels('label' as unknown as string[])).toThrow();
                expect(() => node.setLabels(['label 1', 2 as unknown as string])).toThrow();
            }));

            it('can add one label to the existing array', editorTest(function () {
                const node = $createSignupNode(dataset);
                node.addLabel('new label 3');
                expect(node.labels).toEqual(['label 1', 'label 2', 'new label 3']);
            }));

            it('only accepts strings for addLabel', editorTest(function () {
                const node = $createSignupNode(dataset);
                expect(() => node.addLabel(2 as unknown as string)).toThrow();
            }));

            it('can remove one label from the existing array', editorTest(function () {
                const node = $createSignupNode(dataset);
                node.removeLabel('label 2');
                expect(node.labels).toEqual(['label 1']);
            }));

            it('only accepts strings for removeLabel', editorTest(function () {
                const node = $createSignupNode(dataset);
                expect(() => node.removeLabel(2 as unknown as string)).toThrow();
            }));
        });

        it('has getDataset() method', editorTest(function () {
            dataset.backgroundColor = '#000000';
            const signupNode = $createSignupNode(dataset);
            const nodeData = signupNode.getDataset();
            expect(nodeData).toEqual(dataset);
        }));
    });

    describe('exportDOM', function () {
        const loadingIcon = `<svg xmlns="http://www.w3.org/2000/svg" height="24" width="24" viewBox="0 0 24 24">
            <g stroke-linecap="round" stroke-width="2" fill="currentColor" stroke="none" stroke-linejoin="round" class="nc-icon-wrapper">
                <g class="nc-loop-dots-4-24-icon-o">
                    <circle cx="4" cy="12" r="3"></circle>
                    <circle cx="12" cy="12" r="3"></circle>
                    <circle cx="20" cy="12" r="3"></circle>
                </g>
                <style data-cap="butt">
                    .nc-loop-dots-4-24-icon-o{--animation-duration:0.8s}
                    .nc-loop-dots-4-24-icon-o *{opacity:.4;transform:scale(.75);animation:nc-loop-dots-4-anim var(--animation-duration) infinite}
                    .nc-loop-dots-4-24-icon-o :nth-child(1){transform-origin:4px 12px;animation-delay:-.3s;animation-delay:calc(var(--animation-duration)/-2.666)}
                    .nc-loop-dots-4-24-icon-o :nth-child(2){transform-origin:12px 12px;animation-delay:-.15s;animation-delay:calc(var(--animation-duration)/-5.333)}
                    .nc-loop-dots-4-24-icon-o :nth-child(3){transform-origin:20px 12px}
                    @keyframes nc-loop-dots-4-anim{0%,100%{opacity:.4;transform:scale(.75)}50%{opacity:1;transform:scale(1)}}
                </style>
            </g>
        </svg>`;

        it('creates signup element', editorTest(function () {
            const signupNode = $createSignupNode(dataset);
            const result = signupNode.exportDOM(editor, exportOptions);
            const element = result.element as HTMLElement;
            assertPrettifiesTo(element.outerHTML, html`
                <div class="kg-card kg-signup-card kg-width-regular" data-lexical-signup-form=""  style="display: none;">
                    <picture><img class="kg-signup-card-image" src="https://example.com/image.jpg" alt=""/></picture>
                    <div class="kg-signup-card-content">
                        <div class="kg-signup-card-text kg-align-center">
                            <h2 class="kg-signup-card-heading" style="color: #000000">Header</h2>
                            <p class="kg-signup-card-subheading" style="color: #000000">Subheader</p>
                            <form class="kg-signup-card-form" data-members-form="signup">
                                <input data-members-label="" type="hidden" value="label 1">
                                <input data-members-label="" type="hidden" value="label 2">
                                <div class="kg-signup-card-fields">
                                    <input class="kg-signup-card-input" id="email" data-members-email="" type="email" required="true" placeholder="jamie@example.com">
                                    <button class="kg-signup-card-button" style="background-color:#000000;color:#ffffff" type="submit">
                                        <span class="kg-signup-card-button-default">Button</span>
                                        <span class="kg-signup-card-button-loading">${loadingIcon}</span>
                                    </button>
                                </div>
                                <div class="kg-signup-card-success" style="color: #000000">Success!</div>
                                <div class="kg-signup-card-error" style="color: #000000" data-members-error=""></div>
                            </form>
                            <p class="kg-signup-card-disclaimer" style="color: #000000">Disclaimer</p>
                        </div>
                    </div>
                </div>
            `);
        }));

        it('does not render text colors with a transparent background', editorTest(function () {
            dataset.backgroundColor = 'transparent';
            dataset.backgroundImageSrc = '';

            const signupNode = $createSignupNode(dataset);
            const result = signupNode.exportDOM(editor, exportOptions);
            const element = result.element as HTMLElement;
            assertPrettifiesTo(element.outerHTML, html`
                <div class="kg-card kg-signup-card kg-width-regular" data-lexical-signup-form="" style="background-color: transparent; display:none">
                <div class="kg-signup-card-content">
                    <div class="kg-signup-card-text kg-align-center">
                        <h2 class="kg-signup-card-heading">Header</h2>
                        <p class="kg-signup-card-subheading">Subheader</p>
                        <form class="kg-signup-card-form" data-members-form="signup">
                            <input data-members-label="" type="hidden" value="label 1">
                            <input data-members-label="" type="hidden" value="label 2">
                            <div class="kg-signup-card-fields">
                                <input class="kg-signup-card-input" id="email" data-members-email="" type="email" required="true" placeholder="jamie@example.com">
                                <button class="kg-signup-card-button" style="background-color:#000000;color:#ffffff" type="submit">
                                    <span class="kg-signup-card-button-default">Button</span>
                                    <span class="kg-signup-card-button-loading">${loadingIcon}</span>
                                </button>
                            </div>
                            <div class="kg-signup-card-success">Success!</div>
                            <div class="kg-signup-card-error" data-members-error=""></div>
                        </form>
                        <p class="kg-signup-card-disclaimer">Disclaimer</p>
                    </div>
                </div>
            </div>
            `);
        }));

        it('returns empty element if target is email', editorTest(function () {
            exportOptions.target = 'email';
            const signupNode = $createSignupNode(dataset);
            const result = signupNode.exportDOM(editor, exportOptions);
            const element = result.element as HTMLElement;
            expect(element.outerHTML).toBe('<div></div>');
        }));
    });
    describe('importDOM', function () {
        it('parses a signup card', editorTest(function () {
            dataset.backgroundColor = '#ffffff';

            const signupNode = $createSignupNode(dataset);
            const result = signupNode.exportDOM(editor, exportOptions);
            const element = result.element as HTMLElement;

            const document = createDocument(element.outerHTML);
            const nodes = $generateNodesFromDOM(editor, document);
            expect(nodes.length).toBe(1);
        }));

        it('parses split layout correctly', editorTest(function () {
            dataset.layout = 'split';

            const signupNode = $createSignupNode(dataset);
            const result = signupNode.exportDOM(editor, exportOptions);
            const element = result.element as HTMLElement;

            const document = createDocument(element.outerHTML);
            const nodes = $generateNodesFromDOM(editor, document);
            expect(nodes.length).toBe(1);
        }));

        it('parses split and swapped correctly', editorTest(function () {
            dataset.layout = 'split';
            dataset.swapped = true;
            const signupNode = $createSignupNode(dataset);
            const result = signupNode.exportDOM(editor, exportOptions);
            const element = result.element as HTMLElement;
            const document = createDocument(element.outerHTML);
            const nodes = $generateNodesFromDOM(editor, document);
            expect(nodes.length).toBe(1);
        }));

        it('parses background size contain correctly', editorTest(function () {
            dataset.layout = 'split';
            dataset.backgroundSize = 'contain';
            const signupNode = $createSignupNode(dataset);
            const result = signupNode.exportDOM(editor, exportOptions);
            const element = result.element as HTMLElement;
            const document = createDocument(element.outerHTML);
            const nodes = $generateNodesFromDOM(editor, document);
            expect(nodes.length).toBe(1);
        }));

        it('parses background size cover correctly', editorTest(function () {
            dataset.layout = 'split';
            dataset.backgroundSize = 'cover';
            const signupNode = $createSignupNode(dataset);
            const result = signupNode.exportDOM(editor, exportOptions);
            const element = result.element as HTMLElement;
            const document = createDocument(element.outerHTML);
            const nodes = $generateNodesFromDOM(editor, document);
            expect(nodes.length).toBe(1);
        }));

        it('parses with empty elements removed', editorTest(function () {
            dataset.header = '';
            dataset.subheader = '';
            dataset.disclaimer = '';
            dataset.backgroundColor = '#ffffff';

            const signupNode = $createSignupNode(dataset);
            const result = signupNode.exportDOM(editor, exportOptions);
            const element = result.element as HTMLElement;

            const document = createDocument(element.outerHTML);
            const nodes = $generateNodesFromDOM(editor, document);
            expect(nodes.length).toBe(1);
        }));

        it('parses without image', editorTest(function () {
            // red background
            dataset.backgroundColor = '#ff0000';
            dataset.backgroundImageSrc = '';

            const signupNode = $createSignupNode(dataset);
            const result = signupNode.exportDOM(editor, exportOptions);
            const element = result.element as HTMLElement;

            const document = createDocument(element.outerHTML);
            const nodes = $generateNodesFromDOM(editor, document);
            expect(nodes.length).toBe(1);
        }));

        it('parses with accent button and background', editorTest(function () {
            dataset.backgroundColor = 'accent';
            dataset.buttonColor = 'accent';
            dataset.buttonTextColor = '#ffffff';

            const signupNode = $createSignupNode(dataset);
            const result = signupNode.exportDOM(editor, exportOptions);
            const element = result.element as HTMLElement;

            const document = createDocument(element.outerHTML);
            const nodes = $generateNodesFromDOM(editor, document);
            expect(nodes.length).toBe(1);
        }));

        it('parses when data-lexical-signup-form has a non-empty value', editorTest(function () {
            const document = createDocument(html`
                <div class="kg-card kg-signup-card kg-width-regular" data-lexical-signup-form="signup" style="display:none">
                    <picture><img class="kg-signup-card-image" src="https://example.com/image.jpg" alt=""/></picture>
                    <div class="kg-signup-card-content">
                        <div class="kg-signup-card-text kg-align-center">
                            <h2 class="kg-signup-card-heading" style="color: #000000">Header</h2>
                            <p class="kg-signup-card-subheading" style="color: #000000">Subheader</p>
                            <form class="kg-signup-card-form" data-members-form="signup">
                                <input data-members-label="" type="hidden" value="label 1">
                                <input data-members-label="" type="hidden" value="label 2">
                                <div class="kg-signup-card-fields">
                                    <input class="kg-signup-card-input" id="email" data-members-email="" type="email" required="true" placeholder="jamie@example.com">
                                    <button class="kg-signup-card-button" style="background-color:#000000;color:#ffffff" type="submit">
                                        <span class="kg-signup-card-button-default">Button</span>
                                    </button>
                                </div>
                                <div class="kg-signup-card-success" style="color: #000000">Success!</div>
                                <div class="kg-signup-card-error" style="color: #000000" data-members-error=""></div>
                            </form>
                            <p class="kg-signup-card-disclaimer" style="color: #000000">Disclaimer</p>
                        </div>
                    </div>
                </div>
            `);

            const nodes = $generateNodesFromDOM(editor, document);
            expect(nodes.length).toBe(1);
            expect($isSignupNode(nodes[0])).toBe(true);
        }));
    });

    describe('exportJSON', function () {
        it('contains all data', editorTest(function () {
            const signupNode = $createSignupNode(dataset);
            const json = signupNode.exportJSON();

            expect(json).toEqual({
                type: 'signup',
                version: 1,
                alignment: dataset.alignment,
                backgroundColor: dataset.backgroundColor,
                backgroundImageSrc: dataset.backgroundImageSrc,
                backgroundSize: dataset.backgroundSize,
                textColor: dataset.textColor,
                buttonColor: dataset.buttonColor,
                buttonText: dataset.buttonText,
                buttonTextColor: dataset.buttonTextColor,
                disclaimer: dataset.disclaimer,
                header: dataset.header,
                labels: dataset.labels,
                layout: dataset.layout,
                subheader: dataset.subheader,
                successMessage: dataset.successMessage,
                swapped: dataset.swapped
            });
        }));
    });

    describe('importJSON', function () {
        it('imports all data', function () {
            return new Promise<void>((resolve, reject) => {
                const serializedState = JSON.stringify({
                    root: {
                        children: [{
                            type: 'signup',
                            ...dataset
                        }],
                        direction: null,
                        format: '',
                        indent: 0,
                        type: 'root',
                        version: 1
                    }
                });

                const editorState = editor.parseEditorState(serializedState);
                editor.setEditorState(editorState);

                editor.getEditorState().read(() => {
                    try {
                        const [signupNode] = $getRoot().getChildren();

                        expect($isSignupNode(signupNode)).toBe(true);
                        resolve();
                    } catch (e) {
                        reject(e);
                    }
                });
            });
        });
    });

    describe('getTextContent', function () {
        it('returns contents', editorTest(function () {
            const node = $createPaywallNode();

            // signup nodes don't have text content
            expect(node.getTextContent()).toBe('');
        }));
    });
});
