#!/usr/bin/env node
/**
 * Form Validation ARIA Support Tests
 *
 * Tests the FormValidator utility on the research form:
 * - Inline error display with ldr-field-invalid class
 * - aria-invalid attribute toggling
 * - aria-describedby linking to error element
 * - aria-required on query textarea
 * - Error clears after typing valid input
 * - Validation fires on blur
 *
 * Run: node test_form_validation_aria_ci.js
 */

const { setupTest, teardownTest, TestResults, log, delay, navigateTo, withTimeout } = require('./test_lib');

// ============================================================================
// ARIA Attribute Tests
// ============================================================================
const AriaAttributeTests = {
    async queryHasAriaRequired(page, baseUrl) {
        await navigateTo(page, `${baseUrl}/`);

        const result = await page.evaluate(() => {
            const query = document.querySelector('#query');
            return {
                exists: !!query,
                ariaRequired: query?.getAttribute('aria-required'),
                hasHtmlRequired: query?.hasAttribute('required')
            };
        });

        if (!result.exists) {
            return { passed: null, skipped: true, message: 'Query textarea not found' };
        }

        return {
            passed: result.ariaRequired === 'true' && !result.hasHtmlRequired,
            message: `aria-required="${result.ariaRequired}", HTML required=${result.hasHtmlRequired}`
        };
    },

    async formValidatorLoaded(page, baseUrl) {
        await navigateTo(page, `${baseUrl}/`);
        await delay(500);

        const result = await page.evaluate(() => {
            return {
                hasFormValidator: typeof window.FormValidator === 'function',
                hasFormValidators: typeof window.formValidators === 'object' && window.formValidators !== null
            };
        });

        return {
            passed: result.hasFormValidator && result.hasFormValidators,
            message: `FormValidator=${result.hasFormValidator}, formValidators=${result.hasFormValidators}`
        };
    }
};

// ============================================================================
// Inline Validation Tests
// ============================================================================
const InlineValidationTests = {
    async emptySubmitShowsInlineError(page, baseUrl) {
        await navigateTo(page, `${baseUrl}/`);
        await delay(500);

        // Clear query and submit
        await page.evaluate(() => {
            const query = document.querySelector('#query');
            if (query) {
                query.value = '';
                query.dispatchEvent(new Event('input', { bubbles: true }));
            }
        });

        // Click submit
        await page.click('button[type="submit"], .start-research, #start-research').catch(() => {});
        await delay(300);

        const result = await page.evaluate(() => {
            const query = document.querySelector('#query');
            if (!query) return { exists: false };

            const hasInvalidClass = query.classList.contains('ldr-field-invalid');
            const ariaInvalid = query.getAttribute('aria-invalid');

            // Find the error element
            const errorId = query.getAttribute('aria-describedby');
            let errorElement = null;
            let errorText = '';
            let errorVisible = false;

            if (errorId) {
                // aria-describedby may contain multiple IDs
                const ids = errorId.split(' ');
                for (const id of ids) {
                    const el = document.getElementById(id);
                    if (el && el.classList.contains('ldr-field-error')) {
                        errorElement = el;
                        errorText = el.textContent;
                        errorVisible = window.getComputedStyle(el).display !== 'none';
                        break;
                    }
                }
            }

            return {
                exists: true,
                hasInvalidClass,
                ariaInvalid,
                hasErrorElement: !!errorElement,
                errorText,
                errorVisible
            };
        });

        if (!result.exists) {
            return { passed: null, skipped: true, message: 'Query textarea not found' };
        }

        const passed = result.hasInvalidClass &&
                       result.ariaInvalid === 'true' &&
                       result.hasErrorElement &&
                       result.errorVisible &&
                       result.errorText.length > 0;

        return {
            passed,
            message: `invalid-class=${result.hasInvalidClass}, aria-invalid="${result.ariaInvalid}", ` +
                     `error-visible=${result.errorVisible}, error="${result.errorText}"`
        };
    },

    async blurOnEmptyShowsError(page, baseUrl) {
        await navigateTo(page, `${baseUrl}/`);
        await delay(500);

        // Clear query field
        await page.evaluate(() => {
            const query = document.querySelector('#query');
            if (query) {
                query.value = '';
                query.dispatchEvent(new Event('input', { bubbles: true }));
            }
        });

        // Focus then blur the query field
        await page.focus('#query');
        await delay(100);
        // Click somewhere else to trigger blur
        await page.$eval('#query', el => el.blur());
        await delay(300);

        const result = await page.evaluate(() => {
            const query = document.querySelector('#query');
            if (!query) return { exists: false };

            return {
                exists: true,
                hasInvalidClass: query.classList.contains('ldr-field-invalid'),
                ariaInvalid: query.getAttribute('aria-invalid')
            };
        });

        if (!result.exists) {
            return { passed: null, skipped: true, message: 'Query textarea not found' };
        }

        return {
            passed: result.hasInvalidClass && result.ariaInvalid === 'true',
            message: `After blur on empty: invalid-class=${result.hasInvalidClass}, aria-invalid="${result.ariaInvalid}"`
        };
    },

    async errorClearsOnValidSubmit(page, baseUrl) {
        await navigateTo(page, `${baseUrl}/`);
        await delay(500);

        // First trigger the error by submitting empty
        await page.evaluate(() => {
            const query = document.querySelector('#query');
            if (query) {
                query.value = '';
                query.dispatchEvent(new Event('input', { bubbles: true }));
            }
        });
        await page.click('button[type="submit"], .start-research, #start-research').catch(() => {});
        await delay(300);

        // Verify error is shown
        const errorShown = await page.evaluate(() => {
            const query = document.querySelector('#query');
            return query?.classList.contains('ldr-field-invalid') === true;
        });

        if (!errorShown) {
            return { passed: null, skipped: true, message: 'Could not trigger validation error first' };
        }

        // Now type a valid query - this should clear the error on next submit attempt
        await page.focus('#query');
        await page.keyboard.type('Test research query');
        await delay(200);

        // Verify the text was actually entered
        const queryValue = await page.evaluate(() => {
            const query = document.querySelector('#query');
            return query?.value || '';
        });

        if (!queryValue.includes('Test research')) {
            return { passed: null, skipped: true, message: `Text entry failed, value="${queryValue}"` };
        }

        // Intercept form submission to prevent navigation that would destroy page state
        await page.evaluate(() => {
            const form = document.querySelector('#query')?.closest('form');
            if (form) {
                form.addEventListener('submit', (e) => { e.preventDefault(); }, { once: true });
            }
        });

        // Click submit - the validation should pass and clear errors
        await page.click('button[type="submit"], .start-research, #start-research').catch(() => {});
        await delay(1000);

        const result = await page.evaluate(() => {
            const query = document.querySelector('#query');
            if (!query) return { exists: false };

            return {
                exists: true,
                hasInvalidClass: query.classList.contains('ldr-field-invalid'),
                ariaInvalid: query.getAttribute('aria-invalid')
            };
        });

        if (!result.exists) {
            return { passed: null, skipped: true, message: 'Query textarea not found after submit' };
        }

        return {
            passed: !result.hasInvalidClass && result.ariaInvalid !== 'true',
            message: `After valid submit: invalid-class=${result.hasInvalidClass}, aria-invalid="${result.ariaInvalid}"`
        };
    },

    async noLoadingUiOnEmptySubmit(page, baseUrl) {
        await navigateTo(page, `${baseUrl}/`);
        await delay(500);

        // Clear query
        await page.evaluate(() => {
            const query = document.querySelector('#query');
            if (query) {
                query.value = '';
                query.dispatchEvent(new Event('input', { bubbles: true }));
            }
        });

        // Submit and immediately check for loading overlay
        await page.click('button[type="submit"], .start-research, #start-research').catch(() => {});

        // Check immediately - there should be no loading overlay or spinner
        const result = await page.evaluate(() => {
            const overlay = document.querySelector('.ldr-loading-overlay');
            const btn = document.querySelector('button[type="submit"], .start-research, #start-research');
            const btnDisabled = btn?.disabled;
            const btnHasSpinner = btn?.innerHTML?.includes('fa-spinner');

            return {
                hasOverlay: !!overlay,
                btnDisabled: !!btnDisabled,
                btnHasSpinner: !!btnHasSpinner
            };
        });

        return {
            passed: !result.hasOverlay && !result.btnDisabled && !result.btnHasSpinner,
            message: `On empty submit: overlay=${result.hasOverlay}, btn-disabled=${result.btnDisabled}, spinner=${result.btnHasSpinner}`
        };
    }
};

// ============================================================================
// Error Element Structure Tests
// ============================================================================
const ErrorElementTests = {
    async errorElementHasAriaLive(page, baseUrl) {
        await navigateTo(page, `${baseUrl}/`);
        await delay(500);

        // Trigger validation to create error element
        await page.evaluate(() => {
            const query = document.querySelector('#query');
            if (query) {
                query.value = '';
                query.dispatchEvent(new Event('input', { bubbles: true }));
            }
        });
        await page.focus('#query');
        await delay(100);
        await page.$eval('#query', el => el.blur());
        await delay(300);

        const result = await page.evaluate(() => {
            const query = document.querySelector('#query');
            if (!query) return { exists: false };

            const describedBy = query.getAttribute('aria-describedby');
            if (!describedBy) return { exists: true, hasDescribedBy: false };

            const ids = describedBy.split(' ');
            for (const id of ids) {
                const el = document.getElementById(id);
                if (el && el.classList.contains('ldr-field-error')) {
                    return {
                        exists: true,
                        hasDescribedBy: true,
                        errorId: id,
                        ariaLive: el.getAttribute('aria-live'),
                        className: el.className
                    };
                }
            }

            return { exists: true, hasDescribedBy: true, errorElementFound: false };
        });

        if (!result.exists) {
            return { passed: null, skipped: true, message: 'Query textarea not found' };
        }

        if (!result.hasDescribedBy) {
            return { passed: false, message: 'No aria-describedby on query field' };
        }

        return {
            passed: result.ariaLive === 'polite',
            message: `Error element: id="${result.errorId}", aria-live="${result.ariaLive}", class="${result.className}"`
        };
    },

    async errorPositionedAfterTextarea(page, baseUrl) {
        await navigateTo(page, `${baseUrl}/`);
        await delay(500);

        // Trigger validation
        await page.evaluate(() => {
            const query = document.querySelector('#query');
            if (query) query.value = '';
        });
        await page.focus('#query');
        await delay(100);
        await page.$eval('#query', el => el.blur());
        await delay(300);

        const result = await page.evaluate(() => {
            const query = document.querySelector('#query');
            if (!query) return { exists: false };

            const nextSibling = query.nextElementSibling;
            const isErrorElement = nextSibling?.classList.contains('ldr-field-error');
            const searchHints = query.parentNode?.querySelector('.ldr-search-hints');

            return {
                exists: true,
                nextSiblingIsError: isErrorElement,
                nextSiblingClass: nextSibling?.className,
                hintsExist: !!searchHints
            };
        });

        if (!result.exists) {
            return { passed: null, skipped: true, message: 'Query textarea not found' };
        }

        return {
            passed: result.nextSiblingIsError,
            message: `Next sibling after textarea: class="${result.nextSiblingClass}", hints exist=${result.hintsExist}`
        };
    },

    async focusMovesToFieldOnError(page, baseUrl) {
        // Headless Chrome focus management after form submission is unreliable
        if (process.env.CI) {
            return { passed: null, skipped: true, message: 'CI: headless Chrome focus management after form submit is unreliable' };
        }

        await navigateTo(page, `${baseUrl}/`);
        await delay(500);

        // Clear and submit
        await page.evaluate(() => {
            const query = document.querySelector('#query');
            if (query) {
                query.value = '';
                query.dispatchEvent(new Event('input', { bubbles: true }));
                // Focus something else first
                document.body.focus();
            }
        });

        await page.click('button[type="submit"], .start-research, #start-research').catch(() => {});
        await delay(300);

        const result = await page.evaluate(() => {
            const activeElement = document.activeElement;
            return {
                focusedId: activeElement?.id,
                focusedTag: activeElement?.tagName?.toLowerCase()
            };
        });

        return {
            passed: result.focusedId === 'query',
            message: `Focus after empty submit: id="${result.focusedId}", tag="${result.focusedTag}"`
        };
    }
};

// ============================================================================
// Main Test Runner
// ============================================================================
async function main() {
    log.section('Form Validation ARIA Support Tests');

    const ctx = await setupTest({ authenticate: true });
    const results = new TestResults('Form Validation ARIA Tests');
    const { page } = ctx;
    const { baseUrl } = ctx.config;

    const subTestTimeout = ctx.config.isCI ? 60000 : 30000;
    async function run(category, name, testFn) {
        try {
            const result = await withTimeout(
                testFn(page, baseUrl),
                subTestTimeout,
                `${category}/${name}`
            );
            if (result.skipped) {
                results.skip(category, name, result.message);
            } else {
                results.add(category, name, result.passed, result.message);
            }
        } catch (error) {
            results.add(category, name, false, `Error: ${error.message}`);
        }
    }

    try {
        // ARIA Attribute Tests
        log.section('ARIA Attributes');

        await run('ARIA', 'Query has aria-required', (p, u) => AriaAttributeTests.queryHasAriaRequired(p, u));
        await run('ARIA', 'FormValidator loaded', (p, u) => AriaAttributeTests.formValidatorLoaded(p, u));

        // Inline Validation Tests
        log.section('Inline Validation');

        await run('Validation', 'Empty submit shows inline error', (p, u) => InlineValidationTests.emptySubmitShowsInlineError(p, u));
        await run('Validation', 'Blur on empty shows error', (p, u) => InlineValidationTests.blurOnEmptyShowsError(p, u));
        await run('Validation', 'Error clears on valid submit', (p, u) => InlineValidationTests.errorClearsOnValidSubmit(p, u));
        await run('Validation', 'No loading UI on empty submit', (p, u) => InlineValidationTests.noLoadingUiOnEmptySubmit(p, u));

        // Error Element Structure Tests
        log.section('Error Element Structure');

        await run('Structure', 'Error element has aria-live=polite', (p, u) => ErrorElementTests.errorElementHasAriaLive(p, u));
        await run('Structure', 'Error positioned after textarea', (p, u) => ErrorElementTests.errorPositionedAfterTextarea(p, u));
        await run('Structure', 'Focus moves to field on error', (p, u) => ErrorElementTests.focusMovesToFieldOnError(p, u));

    } catch (error) {
        log.error(`Fatal error: ${error.message}`);
        console.error(error.stack);
    } finally {
        results.print();
        results.save();
        await teardownTest(ctx);
        process.exit(results.exitCode());
    }
}

if (require.main === module) {
    main().catch(error => {
        console.error('Test runner failed:', error);
        process.exit(1);
    });
}

module.exports = { AriaAttributeTests, InlineValidationTests, ErrorElementTests };
