import React from 'react';
import { fireEvent, render as renderDom } from '@testing-library/react';
import { GlyphIcon, StoreIcon } from '@theorchard/suite-icons';
import { testComponent } from 'lib/test-utils/common';
import { Dropdown } from '../dropdown';
import { DropdownProps } from '../types';
import { defaultOptionLabelFormatter } from '../utils';

describe('<Dropdown>', () => {
    const name = 'country';
    const option1 = { label: 'Option1', value: '1', data: { id: 1 } };
    const option2 = { label: 'Option2', value: '2', data: { id: 2 } };
    const options = [option1, option2];

    const render = (props?: Partial<DropdownProps>) =>
        renderDom(<Dropdown name={name} options={options} {...props} />);

    testComponent(Dropdown);

    test('renders dropdown with items', () => {
        const { container, getByText } = render();
        const input = container.getElementsByTagName('input').item(0);

        if (input) fireEvent.mouseDown(input);

        options.forEach((option) => expect(getByText(option.label)).toBeVisible());
    });

    test('renders placeholder', () => {
        const placeholder = 'PLACEHOLDER TEXT';
        const { getByText } = render({ placeholder, options: [] });

        expect(getByText(placeholder)).toBeVisible();
    });

    test('renders "alignRight" classname', () => {
        const { container } = render({ alignRight: true, options: [] });
        expect(container.getElementsByClassName('alignRight').item(0)).toBeVisible();
    });

    test('renders "icons"', () => {
        const { container } = render({
            options: [
                { value: '1', icon: <GlyphIcon name="info" size={16} /> },
                { value: '2', icon: <GlyphIcon name="warning" size={16} /> },
            ],
        });

        const input = container.getElementsByTagName('input').item(0);
        if (input) fireEvent.mouseDown(input);

        expect(container.getElementsByClassName('InfoGlyphIcon').item(0)).toBeVisible();
        expect(container.getElementsByClassName('WarningGlyphIcon').item(0)).toBeVisible();
    });

    test('renders "separateFirstOption" classname', () => {
        const { container } = render({
            separateFirstOption: true,
            options: [],
        });
        expect(container.getElementsByClassName('separateFirstOption').item(0)).toBeVisible();
    });

    test('renders dropdown with value set', () => {
        const { getByText } = render({ value: option1 });

        expect(getByText(option1.label)).toBeVisible();
    });

    test('invokes callback when item selected', () => {
        const onChange = vi.fn();

        const { container, getByText } = render({
            onChange,
            options: [option1],
        });
        const input = container.getElementsByTagName('input').item(0);

        if (input) fireEvent.mouseDown(input);
        fireEvent.click(getByText(option1.label));

        expect(onChange).toHaveBeenCalledWith(option1, {
            action: 'select-option',
            name,
        });
    });

    test('cannot select item if DropdownOption.disabled is true', () => {
        const onChange = vi.fn();
        const disabledOption = {
            label: 'Option1',
            value: '1',
            data: { id: 1 },
            disabled: true,
        };

        const { container, getByText } = render({
            onChange,
            options: [disabledOption],
        });

        const input = container.getElementsByTagName('input').item(0);

        if (input) fireEvent.mouseDown(input);

        const optionElement = getByText(disabledOption.label);

        fireEvent.click(optionElement);

        expect(onChange).not.toHaveBeenCalled();
    });

    describe('isSelectable', () => {
        test('when true, options are selectable', () => {
            const onValueChanged = vi.fn();
            const props = {
                name,
                options,
                placeholder: 'select something',
                isSelectable: true,
                onValueChanged,
            };

            const { getByText, container } = render(props);

            const input = container.getElementsByTagName('input').item(0);
            if (input) fireEvent.mouseDown(input);
            fireEvent.click(getByText(option1.label));

            expect(onValueChanged).toHaveBeenCalledWith(name, option1.value, option1);

            expect(getByText(option1.label)).toBeVisible();
        });

        test('when false, options are not selectable', () => {
            const onValueChanged = vi.fn();
            const props = {
                name,
                options,
                placeholder: 'select something',
                isSelectable: false,
                onValueChanged,
            };

            const { getByText, queryByText, container } = render(props);

            const input = container.getElementsByTagName('input').item(0);
            if (input) fireEvent.mouseDown(input);
            fireEvent.click(getByText(option1.label));

            expect(onValueChanged).toHaveBeenCalledWith(name, option1.value, option1);

            expect(queryByText(option1.label)).toBeNull();
        });
    });

    test('selected option is controllable with the "selectedValue" property', () => {
        const props = {
            name,
            options,
            placeholder: 'select something',
            selectedValue: undefined,
        };

        const { getByText, queryByText, rerender } = render(props);

        expect(queryByText(option1.label)).toBeNull();

        rerender(<Dropdown {...props} selectedValue={option1.value} />);

        expect(getByText(option1.label)).toBeVisible();

        rerender(<Dropdown {...props} selectedValue={undefined} />);

        expect(queryByText(option1.label)).toBeNull();
    });

    test('selected option is controllable with the "value" property', () => {
        const props = {
            name,
            options,
            placeholder: 'select something',
            value: undefined,
        };

        const { getByText, queryByText, rerender } = render(props);

        expect(queryByText(option1.label)).toBeNull();

        rerender(<Dropdown {...props} value={option1} />);

        expect(getByText(option1.label)).toBeVisible();

        rerender(<Dropdown {...props} value={undefined} />);

        expect(queryByText(option1.label)).toBeNull();
    });

    describe('with "onValueChanged" callback', () => {
        test('is invoked with the matching value and option', () => {
            const onValueChanged = vi.fn();
            const { container, getByText } = render({ onValueChanged });

            const input = container.getElementsByTagName('input').item(0);

            if (input) fireEvent.mouseDown(input);
            fireEvent.click(getByText(option1.label));

            expect(onValueChanged).toHaveBeenCalledWith(name, option1.value, option1);

            expect(getByText(option1.label)).toBeVisible();
        });
    });

    describe('with "selectedValue"', () => {
        test('selects the matching option', () => {
            const { getByText } = render({ selectedValue: option1.value });

            expect(getByText(option1.label)).toBeVisible();
        });

        test('defining multiple values will select the matching options', () => {
            const { getByText } = renderDom(
                <Dropdown
                    name={name}
                    options={options}
                    isMulti
                    selectedValue={[option1.value, option2.value]}
                />
            );

            expect(getByText(option1.label)).toBeVisible();
            expect(getByText(option2.label)).toBeVisible();
        });

        test('defining "value" will override selected option', () => {
            const { getByText, queryByText } = render({
                value: option2,
                selectedValue: option1.value,
            });

            expect(queryByText(option1.label)).toBeNull();
            expect(getByText(option2.label)).toBeVisible();
        });
    });
});

describe('defaultOptionLabelFormatter', () => {
    const option1 = { value: '1', label: 'option1' };
    const option2 = {
        value: '2',
        label: 'option2',
        icon: <StoreIcon name="spotify" />,
    };
    const option21 = { value: '21', label: 'option21' };
    const option22 = { value: '22', label: 'option22' };

    const options = [option1, option2];
    const groups = [
        { label: 'group1', options: [option1, option2] },
        { label: 'group2', options: [option21, option22] },
    ];

    test('renders option label', () => {
        const renderer = defaultOptionLabelFormatter(options);
        const Element = () => renderer(option1);

        const { container, getByText } = renderDom(<Element />);
        expect(getByText(option1.label)).toBeVisible();
        expect(container.querySelector('.Dropdown-option-icon')).toBeNull();
    });

    test('renders option icon', () => {
        const renderer = defaultOptionLabelFormatter(options);
        const Element = () => renderer(option2);

        const { container, getByText } = renderDom(<Element />);
        expect(getByText(option2.label)).toBeVisible();
        expect(container.querySelector('.SpotifyStoreIcon')).toBeVisible();
    });

    test('supports option groups', () => {
        const renderer = defaultOptionLabelFormatter(groups);
        const Element = () => renderer(option22);

        const { getByText } = renderDom(<Element />);
        expect(getByText(option22.label)).toBeVisible();
    });
});
