/**
 * @jest-environment jsdom
 */

/*
 * Runtime Web – Unified Serverless Framework for Full-Stack TypeScript Applications
 * Documentation: https://runtimeweb.com/docs/getting-started
 *
 * @template: ai-chat
 */

/**
 * Property-based tests for InputComposer component.
 *
 * **Property 4: Input Validation and Disabled State**
 * **Validates: Requirements 4.2, 4.4, 4.7, 4.8**
 *
 * Property: For any input string composed entirely of whitespace characters
 * (including empty string), the InputComposer SHALL prevent submission.
 * For any disabled state, the send button SHALL be disabled and the textarea
 * SHALL be disabled. Enter key SHALL submit non-empty input, Shift+Enter
 * SHALL insert newline.
 *
 * @module components/chat/__tests__/InputComposer.property.test
 */

import * as React from 'react';
import * as fc from 'fast-check';
import { render, cleanup, screen, fireEvent } from '@testing-library/react';
import '@testing-library/jest-dom';
import { InputComposer } from '../InputComposer';

afterEach(() => {
    cleanup();
    jest.clearAllMocks();
});

/**
 * Arbitrary generator for whitespace-only strings.
 * Includes empty string and various whitespace combinations.
 */
const whitespaceOnlyArbitrary = (): fc.Arbitrary<string> => {
    return fc.oneof(
        fc.constant(''),
        fc.array(fc.constantFrom(' ', '\t', '\n', '\r', '\f', '\v'), { minLength: 1, maxLength: 50 })
            .map((chars) => chars.join('')),
    );
};

/**
 * Arbitrary generator for non-empty, non-whitespace-only strings.
 * These are valid messages that should be submittable.
 */
const validMessageArbitrary = (): fc.Arbitrary<string> => {
    return fc.string({ minLength: 1, maxLength: 200 })
        .filter((s) => s.trim().length > 0);
};

/**
 * Arbitrary generator for strings with leading/trailing whitespace
 * but non-empty trimmed content.
 */
const paddedMessageArbitrary = (): fc.Arbitrary<string> => {
    return fc.tuple(
        fc.array(fc.constantFrom(' ', '\t'), { minLength: 0, maxLength: 5 }).map((a) => a.join('')),
        fc.string({ minLength: 1, maxLength: 100 }).filter((s) => s.trim().length > 0),
        fc.array(fc.constantFrom(' ', '\t'), { minLength: 0, maxLength: 5 }).map((a) => a.join('')),
    ).map(([prefix, content, suffix]) => prefix + content + suffix);
};

describe('InputComposer Property Tests', () => {
    /**
     * **Property 4: Input Validation and Disabled State**
     * **Validates: Requirements 4.2, 4.4, 4.7, 4.8**
     */
    describe('Property 4: Input Validation and Disabled State', () => {
        /**
         * Validates: Requirement 4.2
         * For any input string composed entirely of whitespace characters
         * (including empty string), the InputComposer SHALL prevent submission.
         */
        describe('Whitespace-only input prevents submission', () => {
            it('does NOT invoke onSend for empty string or whitespace-only input via button click', () => {
                fc.assert(
                    fc.property(whitespaceOnlyArbitrary(), (whitespaceInput) => {
                        const onSend = jest.fn();
                        const { unmount } = render(<InputComposer onSend={onSend} disabled={false} />);

                        const textarea = screen.getByTestId('input-composer-textarea');
                        const sendButton = screen.getByTestId('input-composer-send');

                        fireEvent.change(textarea, { target: { value: whitespaceInput } });
                        fireEvent.click(sendButton);

                        expect(onSend).not.toHaveBeenCalled();

                        unmount();
                    }),
                    { numRuns: 100 },
                );
            });

            it('does NOT invoke onSend for whitespace-only input via Enter key', () => {
                fc.assert(
                    fc.property(whitespaceOnlyArbitrary(), (whitespaceInput) => {
                        const onSend = jest.fn();
                        const { unmount } = render(<InputComposer onSend={onSend} disabled={false} />);

                        const textarea = screen.getByTestId('input-composer-textarea');

                        fireEvent.change(textarea, { target: { value: whitespaceInput } });
                        fireEvent.keyDown(textarea, { key: 'Enter', shiftKey: false });

                        expect(onSend).not.toHaveBeenCalled();

                        unmount();
                    }),
                    { numRuns: 100 },
                );
            });

            it('send button is disabled for whitespace-only input', () => {
                fc.assert(
                    fc.property(whitespaceOnlyArbitrary(), (whitespaceInput) => {
                        const onSend = jest.fn();
                        const { unmount } = render(<InputComposer onSend={onSend} disabled={false} />);

                        const textarea = screen.getByTestId('input-composer-textarea');
                        const sendButton = screen.getByTestId('input-composer-send');

                        fireEvent.change(textarea, { target: { value: whitespaceInput } });

                        expect(sendButton).toBeDisabled();

                        unmount();
                    }),
                    { numRuns: 100 },
                );
            });
        });

        /**
         * Validates: Requirement 4.4
         * For any disabled state, the send button SHALL be disabled.
         */
        describe('Disabled state disables send button', () => {
            it('send button is disabled when disabled prop is true', () => {
                fc.assert(
                    fc.property(validMessageArbitrary(), (message) => {
                        const onSend = jest.fn();
                        const { unmount } = render(<InputComposer onSend={onSend} disabled={true} />);

                        const textarea = screen.getByTestId('input-composer-textarea');
                        const sendButton = screen.getByTestId('input-composer-send');

                        // Even with valid input, button should be disabled
                        fireEvent.change(textarea, { target: { value: message } });

                        expect(sendButton).toBeDisabled();

                        unmount();
                    }),
                    { numRuns: 100 },
                );
            });

            it('clicking disabled send button does NOT invoke onSend', () => {
                fc.assert(
                    fc.property(validMessageArbitrary(), (message) => {
                        const onSend = jest.fn();
                        const { unmount } = render(<InputComposer onSend={onSend} disabled={true} />);

                        const textarea = screen.getByTestId('input-composer-textarea');
                        const sendButton = screen.getByTestId('input-composer-send');

                        fireEvent.change(textarea, { target: { value: message } });
                        fireEvent.click(sendButton);

                        expect(onSend).not.toHaveBeenCalled();

                        unmount();
                    }),
                    { numRuns: 100 },
                );
            });
        });

        /**
         * Validates: Requirement 4.4
         * For any disabled state, the textarea SHALL be disabled.
         */
        describe('Disabled state disables textarea', () => {
            it('textarea is disabled when disabled prop is true', () => {
                fc.assert(
                    fc.property(fc.boolean(), (_) => {
                        const onSend = jest.fn();
                        const { unmount } = render(<InputComposer onSend={onSend} disabled={true} />);

                        const textarea = screen.getByTestId('input-composer-textarea');

                        expect(textarea).toBeDisabled();

                        unmount();
                    }),
                    { numRuns: 50 },
                );
            });

            it('textarea is NOT disabled when disabled prop is false', () => {
                fc.assert(
                    fc.property(fc.boolean(), (_) => {
                        const onSend = jest.fn();
                        const { unmount } = render(<InputComposer onSend={onSend} disabled={false} />);

                        const textarea = screen.getByTestId('input-composer-textarea');

                        expect(textarea).not.toBeDisabled();

                        unmount();
                    }),
                    { numRuns: 50 },
                );
            });
        });

        /**
         * Validates: Requirement 4.7
         * Enter key SHALL submit non-empty input when not disabled.
         */
        describe('Enter key submits non-empty input', () => {
            it('Enter key invokes onSend with trimmed message', () => {
                fc.assert(
                    fc.property(validMessageArbitrary(), (message) => {
                        const onSend = jest.fn();
                        const { unmount } = render(<InputComposer onSend={onSend} disabled={false} />);

                        const textarea = screen.getByTestId('input-composer-textarea') as HTMLTextAreaElement;

                        fireEvent.change(textarea, { target: { value: message } });
                        fireEvent.keyDown(textarea, { key: 'Enter', shiftKey: false });

                        expect(onSend).toHaveBeenCalledTimes(1);
                        expect(onSend).toHaveBeenCalledWith(message.trim());

                        unmount();
                    }),
                    { numRuns: 100 },
                );
            });

            it('Enter key clears input after successful send', () => {
                fc.assert(
                    fc.property(validMessageArbitrary(), (message) => {
                        const onSend = jest.fn();
                        const { unmount } = render(<InputComposer onSend={onSend} disabled={false} />);

                        const textarea = screen.getByTestId('input-composer-textarea') as HTMLTextAreaElement;

                        fireEvent.change(textarea, { target: { value: message } });
                        fireEvent.keyDown(textarea, { key: 'Enter', shiftKey: false });

                        expect(textarea.value).toBe('');

                        unmount();
                    }),
                    { numRuns: 100 },
                );
            });

            it('Enter key does NOT invoke onSend when disabled', () => {
                fc.assert(
                    fc.property(validMessageArbitrary(), (message) => {
                        const onSend = jest.fn();
                        const { unmount } = render(<InputComposer onSend={onSend} disabled={true} />);

                        const textarea = screen.getByTestId('input-composer-textarea');

                        fireEvent.change(textarea, { target: { value: message } });
                        fireEvent.keyDown(textarea, { key: 'Enter', shiftKey: false });

                        expect(onSend).not.toHaveBeenCalled();

                        unmount();
                    }),
                    { numRuns: 100 },
                );
            });

            it('Enter key sends trimmed content for padded messages', () => {
                fc.assert(
                    fc.property(paddedMessageArbitrary(), (paddedMessage) => {
                        const onSend = jest.fn();
                        const { unmount } = render(<InputComposer onSend={onSend} disabled={false} />);

                        const textarea = screen.getByTestId('input-composer-textarea');

                        fireEvent.change(textarea, { target: { value: paddedMessage } });
                        fireEvent.keyDown(textarea, { key: 'Enter', shiftKey: false });

                        expect(onSend).toHaveBeenCalledTimes(1);
                        expect(onSend).toHaveBeenCalledWith(paddedMessage.trim());

                        unmount();
                    }),
                    { numRuns: 100 },
                );
            });
        });

        /**
         * Validates: Requirement 4.8
         * Shift+Enter SHALL insert newline (does not submit).
         */
        describe('Shift+Enter inserts newline', () => {
            it('Shift+Enter does NOT invoke onSend', () => {
                fc.assert(
                    fc.property(validMessageArbitrary(), (message) => {
                        const onSend = jest.fn();
                        const { unmount } = render(<InputComposer onSend={onSend} disabled={false} />);

                        const textarea = screen.getByTestId('input-composer-textarea');

                        fireEvent.change(textarea, { target: { value: message } });
                        fireEvent.keyDown(textarea, { key: 'Enter', shiftKey: true });

                        expect(onSend).not.toHaveBeenCalled();

                        unmount();
                    }),
                    { numRuns: 100 },
                );
            });

            it('Shift+Enter preserves input value (allows newline insertion)', () => {
                fc.assert(
                    fc.property(validMessageArbitrary(), (message) => {
                        const onSend = jest.fn();
                        const { unmount } = render(<InputComposer onSend={onSend} disabled={false} />);

                        const textarea = screen.getByTestId('input-composer-textarea') as HTMLTextAreaElement;

                        fireEvent.change(textarea, { target: { value: message } });
                        fireEvent.keyDown(textarea, { key: 'Enter', shiftKey: true });

                        // Input should NOT be cleared (unlike Enter without Shift)
                        expect(textarea.value).toBe(message);

                        unmount();
                    }),
                    { numRuns: 100 },
                );
            });
        });

        /**
         * Combined property: Disabled state completely prevents interaction.
         */
        describe('Combined: Disabled state prevents all submission methods', () => {
            it('disabled state prevents submission via button click and Enter key', () => {
                fc.assert(
                    fc.property(validMessageArbitrary(), (message) => {
                        const onSend = jest.fn();
                        const { unmount } = render(<InputComposer onSend={onSend} disabled={true} />);

                        const textarea = screen.getByTestId('input-composer-textarea');
                        const sendButton = screen.getByTestId('input-composer-send');

                        fireEvent.change(textarea, { target: { value: message } });

                        // Try button click
                        fireEvent.click(sendButton);
                        expect(onSend).not.toHaveBeenCalled();

                        // Try Enter key
                        fireEvent.keyDown(textarea, { key: 'Enter', shiftKey: false });
                        expect(onSend).not.toHaveBeenCalled();

                        // Both textarea and button should be disabled
                        expect(textarea).toBeDisabled();
                        expect(sendButton).toBeDisabled();

                        unmount();
                    }),
                    { numRuns: 100 },
                );
            });
        });

        /**
         * Combined property: Valid input enables submission.
         */
        describe('Combined: Valid input enables submission', () => {
            it('valid input enables send button and allows submission', () => {
                fc.assert(
                    fc.property(validMessageArbitrary(), (message) => {
                        const onSend = jest.fn();
                        const { unmount } = render(<InputComposer onSend={onSend} disabled={false} />);

                        const textarea = screen.getByTestId('input-composer-textarea');
                        const sendButton = screen.getByTestId('input-composer-send');

                        fireEvent.change(textarea, { target: { value: message } });

                        // Button should be enabled for valid input
                        expect(sendButton).not.toBeDisabled();

                        // Click should invoke onSend
                        fireEvent.click(sendButton);
                        expect(onSend).toHaveBeenCalledWith(message.trim());

                        unmount();
                    }),
                    { numRuns: 100 },
                );
            });
        });
    });
});
