/**
 * Tests for utils/form-validation.js
 *
 * Tests the FormValidator class and built-in validator functions
 * used for inline form validation with ARIA support.
 */

import '@js/utils/form-validation.js';

const FormValidator = window.FormValidator;
const validators = window.formValidators;

describe('FormValidator', () => {
    let form;
    let input;
    let validator;

    beforeEach(() => {
        form = document.createElement('form');
        input = document.createElement('input');
        input.type = 'text';
        input.id = 'test-field';
        form.appendChild(input);
        document.body.appendChild(form);
        validator = new FormValidator();
    });

    afterEach(() => {
        document.body.removeChild(form);
    });

    describe('addValidation', () => {
        it('creates an error element next to the field', () => {
            validator.addValidation(input, () => null);
            const errorEl = document.getElementById('test-field-error');
            expect(errorEl).not.toBeNull();
            expect(errorEl.className).toBe('ldr-field-error');
        });

        it('sets aria-describedby on the field', () => {
            validator.addValidation(input, () => null);
            expect(input.getAttribute('aria-describedby')).toContain('test-field-error');
        });

        it('sets aria-live on error element', () => {
            validator.addValidation(input, () => null);
            const errorEl = document.getElementById('test-field-error');
            expect(errorEl.getAttribute('aria-live')).toBe('polite');
        });

        it('accepts selector string instead of element', () => {
            validator.addValidation('#test-field', () => null);
            const errorEl = document.getElementById('test-field-error');
            expect(errorEl).not.toBeNull();
        });

        it('does nothing for non-existent selector', () => {
            expect(() => {
                validator.addValidation('#nonexistent', () => null);
            }).not.toThrow();
        });

        it('generates an id for fields without one', () => {
            const noIdInput = document.createElement('input');
            noIdInput.type = 'text';
            form.appendChild(noIdInput);
            validator.addValidation(noIdInput, () => null);
            expect(noIdInput.id).toMatch(/^form-field-/);
        });
    });

    describe('validateField', () => {
        it('returns true when validator returns null (valid)', () => {
            validator.addValidation(input, () => null);
            expect(validator.validateField(input)).toBe(true);
        });

        it('returns false when validator returns error message', () => {
            validator.addValidation(input, () => 'Error!');
            expect(validator.validateField(input)).toBe(false);
        });

        it('adds ldr-field-invalid class on error', () => {
            validator.addValidation(input, () => 'Error!');
            validator.validateField(input);
            expect(input.classList.contains('ldr-field-invalid')).toBe(true);
        });

        it('sets aria-invalid on error', () => {
            validator.addValidation(input, () => 'Error!');
            validator.validateField(input);
            expect(input.getAttribute('aria-invalid')).toBe('true');
        });

        it('displays error message in error element', () => {
            validator.addValidation(input, () => 'Field is required');
            validator.validateField(input);
            const errorEl = document.getElementById('test-field-error');
            expect(errorEl.textContent).toBe('Field is required');
            expect(errorEl.style.display).toBe('block');
        });

        it('clears error state when field becomes valid', () => {
            validator.addValidation(input, (val) => (val ? null : 'Required'));
            input.value = '';
            validator.validateField(input);
            expect(input.classList.contains('ldr-field-invalid')).toBe(true);

            input.value = 'filled';
            validator.validateField(input);
            expect(input.classList.contains('ldr-field-invalid')).toBe(false);
            expect(input.hasAttribute('aria-invalid')).toBe(false);
        });

        it('returns true for unregistered elements', () => {
            const unknownInput = document.createElement('input');
            expect(validator.validateField(unknownInput)).toBe(true);
        });
    });

    describe('validateAll', () => {
        it('returns true when all fields are valid', () => {
            const input2 = document.createElement('input');
            input2.id = 'field-2';
            form.appendChild(input2);

            validator.addValidation(input, () => null);
            validator.addValidation(input2, () => null);

            expect(validator.validateAll()).toBe(true);
        });

        it('returns false when any field is invalid', () => {
            const input2 = document.createElement('input');
            input2.id = 'field-2';
            form.appendChild(input2);

            validator.addValidation(input, () => null);
            validator.addValidation(input2, () => 'Error');

            expect(validator.validateAll()).toBe(false);
        });

        it('validates all fields (not short-circuit)', () => {
            const input2 = document.createElement('input');
            input2.id = 'field-2';
            form.appendChild(input2);

            validator.addValidation(input, () => 'Error 1');
            validator.addValidation(input2, () => 'Error 2');

            validator.validateAll();

            // Both should show errors
            expect(input.classList.contains('ldr-field-invalid')).toBe(true);
            expect(input2.classList.contains('ldr-field-invalid')).toBe(true);
        });
    });

    describe('clearErrors', () => {
        it('removes error classes and messages from all fields', () => {
            validator.addValidation(input, () => 'Error');
            validator.validateField(input);
            expect(input.classList.contains('ldr-field-invalid')).toBe(true);

            validator.clearErrors();
            expect(input.classList.contains('ldr-field-invalid')).toBe(false);
            expect(input.hasAttribute('aria-invalid')).toBe(false);
            const errorEl = document.getElementById('test-field-error');
            expect(errorEl.textContent).toBe('');
            expect(errorEl.style.display).toBe('none');
        });
    });

    describe('showError', () => {
        it('shows custom error on a field', () => {
            validator.addValidation(input, () => null);
            validator.showError(input, 'Custom error');
            expect(input.classList.contains('ldr-field-invalid')).toBe(true);
            expect(input.getAttribute('aria-invalid')).toBe('true');
            const errorEl = document.getElementById('test-field-error');
            expect(errorEl.textContent).toBe('Custom error');
        });

        it('accepts selector string', () => {
            validator.addValidation(input, () => null);
            validator.showError('#test-field', 'Custom error');
            expect(input.classList.contains('ldr-field-invalid')).toBe(true);
        });

        it('does nothing for non-existent selector', () => {
            expect(() => {
                validator.showError('#nonexistent', 'Error');
            }).not.toThrow();
        });
    });
});

describe('Built-in validators', () => {
    describe('required', () => {
        const required = validators.required();

        it('returns null for non-empty value', () => {
            expect(required('hello')).toBeNull();
        });

        it('returns error for empty string', () => {
            expect(required('')).toBe('This field is required');
        });

        it('returns error for whitespace-only string', () => {
            expect(required('   ')).toBe('This field is required');
        });

        it('returns error for null/undefined', () => {
            expect(required(null)).toBe('This field is required');
            expect(required(undefined)).toBe('This field is required');
        });

        it('accepts custom message', () => {
            const custom = validators.required('Please fill this in');
            expect(custom('')).toBe('Please fill this in');
        });
    });

    describe('minLength', () => {
        const minLen = validators.minLength(3);

        it('returns null for values meeting minimum', () => {
            expect(minLen('abc')).toBeNull();
            expect(minLen('abcd')).toBeNull();
        });

        it('returns error for short values', () => {
            expect(minLen('ab')).toBe('Must be at least 3 characters');
        });

        it('returns null for empty value (let required handle it)', () => {
            expect(minLen('')).toBeNull();
            expect(minLen(null)).toBeNull();
        });

        it('accepts custom message', () => {
            const custom = validators.minLength(5, 'Too short!');
            expect(custom('abc')).toBe('Too short!');
        });
    });

    describe('maxLength', () => {
        const maxLen = validators.maxLength(5);

        it('returns null for values within limit', () => {
            expect(maxLen('abc')).toBeNull();
            expect(maxLen('abcde')).toBeNull();
        });

        it('returns error for values exceeding limit', () => {
            expect(maxLen('abcdef')).toBe('Must be no more than 5 characters');
        });

        it('returns null for empty value', () => {
            expect(maxLen('')).toBeNull();
            expect(maxLen(null)).toBeNull();
        });
    });

    describe('pattern', () => {
        const emailLike = validators.pattern(/^[^@]+@[^@]+$/, 'Invalid email');

        it('returns null for matching values', () => {
            expect(emailLike('user@example.com')).toBeNull();
        });

        it('returns error for non-matching values', () => {
            expect(emailLike('not-an-email')).toBe('Invalid email');
        });

        it('returns null for empty value', () => {
            expect(emailLike('')).toBeNull();
            expect(emailLike(null)).toBeNull();
        });

        it('uses default message when none provided', () => {
            const digits = validators.pattern(/^\d+$/);
            expect(digits('abc')).toBe('Invalid format');
        });
    });
});
