import { beforeEach, describe, expect, it, vi } from 'vitest';
import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { useEntityRecords } from '@wordpress/core-data';
import NavigationMenuSelector from '../navigation-menu-selector';
import useNavigationMenu from '../../use-navigation-menu';

vi.mock( import( '../../use-navigation-menu' ), () => {
	// This allows us to tweak the returned value on each test.
	const mock = vi.fn();
	return { default: mock };
} );

vi.mock( import( '@wordpress/core-data' ), async ( importOriginal ) => ( {
	...( await importOriginal() ),
	useEntityRecords: vi.fn(),
} ) );

const navigationMenu1 = {
	id: 1,
	title: {
		rendered: 'Menu 1',
	},
	status: 'publish',
};
const navigationMenu2 = {
	id: 2,
	title: {
		rendered: 'Menu 2',
	},
	status: 'publish',
};
const navigationMenu3 = {
	id: 3,
	title: {
		rendered: 'Menu 3',
	},
	status: 'publish',
};

const navigationMenusFixture = [
	navigationMenu1,
	navigationMenu2,
	navigationMenu3,
];

const classicMenusFixture = [
	{
		id: 1,
		name: 'Classic Menu 1',
	},
	{
		id: 2,
		name: 'Classic Menu 2',
	},
	{
		id: 3,
		name: 'Classic Menu 3',
	},
];

describe( 'NavigationMenuSelector', () => {
	beforeEach( () => {
		useNavigationMenu.mockReturnValue( {
			navigationMenus: [],
			hasResolvedNavigationMenus: true,
			canUserCreateNavigationMenus: true,
			canSwitchNavigationMenu: true,
		} );
		useEntityRecords.mockReturnValue( {
			records: [],
		} );
	} );

	describe( 'Toggle', () => {
		it( 'should show dropdown toggle with loading message when menus have not resolved', async () => {
			useNavigationMenu.mockReturnValue( {
				navigationMenus: [],
				isResolvingNavigationMenus: true,
				hasResolvedNavigationMenus: false,
				canSwitchNavigationMenu: true,
			} );

			render( <NavigationMenuSelector /> );

			expect(
				screen.getByRole( 'button', {
					name: /Loading/,
				} )
			).toBeInTheDocument();
		} );

		it( 'should show correct dropdown toggle prompt to choose a menu when Navigation Menus have resolved', async () => {
			useNavigationMenu.mockReturnValue( {
				navigationMenus: [],
				hasResolvedNavigationMenus: true,
				canUserCreateNavigationMenus: true,
				canSwitchNavigationMenu: true,
			} );

			render( <NavigationMenuSelector /> );

			expect(
				screen.getByRole( 'button', {
					name: 'Choose or create a Navigation Menu',
				} )
			).toBeInTheDocument();
		} );
	} );

	describe( 'Dropdown', () => {
		it( 'should show in loading state with no options when menus have not resolved and user cannot create menus', async () => {
			const user = userEvent.setup();

			useNavigationMenu.mockReturnValue( {
				navigationMenus: [],
				isResolvingNavigationMenus: true,
				hasResolvedNavigationMenus: false,
				canUserCreateNavigationMenus: false,
				canSwitchNavigationMenu: true,
			} );

			render( <NavigationMenuSelector /> );

			const toggleButton = screen.getByRole( 'button' );
			await user.click( toggleButton );

			expect(
				screen.getByRole( 'menu', {
					name: /Loading/,
				} )
			).toBeInTheDocument();

			// Check that all the option groups are *not* present.
			const menusGroup = screen.queryByRole( 'group', { name: 'Menus' } );
			expect( menusGroup ).not.toBeInTheDocument();

			const classicMenusGroup = screen.queryByRole( 'group', {
				name: 'Import Classic Menus',
			} );
			expect( classicMenusGroup ).not.toBeInTheDocument();

			const toolsGroup = screen.queryByRole( 'group', {
				name: 'Tools',
			} );
			expect( toolsGroup ).not.toBeInTheDocument();
		} );

		describe( 'Creating new menus', () => {
			it( 'should show option to create a menu when no menus exist but user can create menus', async () => {
				const user = userEvent.setup();

				useNavigationMenu.mockReturnValue( {
					navigationMenus: [],
					hasResolvedNavigationMenus: true,
					canUserCreateNavigationMenus: true,
					canSwitchNavigationMenu: true,
				} );

				render( <NavigationMenuSelector /> );

				const toggleButton = screen.getByRole( 'button', {
					name: 'Choose or create a Navigation Menu',
				} );

				await user.click( toggleButton );

				const menuPopover = screen.getByRole( 'menu' );

				expect( menuPopover ).toHaveAttribute(
					'aria-label',
					expect.stringContaining(
						'Choose or create a Navigation Menu'
					)
				);

				// Check that all the option groups are *not* present.
				const menusGroup = screen.queryByRole( 'group', {
					name: 'Menus',
				} );
				expect( menusGroup ).not.toBeInTheDocument();

				const classicMenusGroup = screen.queryByRole( 'group', {
					name: 'Import Classic Menus',
				} );
				expect( classicMenusGroup ).not.toBeInTheDocument();

				// Check the Tools Group and Create Menu Button are present.
				const toolsGroup = screen.queryByRole( 'group', {
					name: 'Tools',
				} );
				expect( toolsGroup ).toBeInTheDocument();

				const createMenuButton = screen.getByRole( 'menuitem', {
					name: 'Create new Menu',
				} );

				expect( createMenuButton ).toBeInTheDocument();
			} );

			it( 'should not show option to create a menu when user does not have permission to create menus', async () => {
				const user = userEvent.setup();

				useNavigationMenu.mockReturnValue( {
					navigationMenus: [],
					hasResolvedNavigationMenus: true,
					canUserCreateNavigationMenus: false,
					canSwitchNavigationMenu: true,
				} );

				render( <NavigationMenuSelector /> );

				const toggleButton = screen.getByRole( 'button' );
				await user.click( toggleButton );

				// Check the Tools Group and Create Menu Button are present.
				const toolsGroup = screen.queryByRole( 'group', {
					name: 'Tools',
				} );
				expect( toolsGroup ).not.toBeInTheDocument();
			} );

			it( 'should call handler callback and close popover when create menu button is clicked', async () => {
				const user = userEvent.setup();
				const handler = vi.fn();

				useNavigationMenu.mockReturnValue( {
					navigationMenus: [],
					hasResolvedNavigationMenus: true,
					canUserCreateNavigationMenus: true,
					canSwitchNavigationMenu: true,
				} );

				render( <NavigationMenuSelector onCreateNew={ handler } /> );

				const toggleButton = screen.getByRole( 'button' );
				await user.click( toggleButton );

				const createMenuButton = screen.getByRole( 'menuitem', {
					name: 'Create new Menu',
				} );

				await user.click( createMenuButton );

				expect( handler ).toHaveBeenCalled();

				expect( screen.queryByRole( 'menu' ) ).not.toBeInTheDocument();
			} );

			it( 'should handle disabled state of the create menu button during the creation process', async () => {
				const user = userEvent.setup();
				const handler = vi.fn();

				// at the start we have the menus and we're not waiting on network
				useNavigationMenu.mockReturnValue( {
					navigationMenus: [],
					hasResolvedNavigationMenus: true,
					canUserCreateNavigationMenus: true,
					canSwitchNavigationMenu: true,
				} );

				const { rerender } = render(
					<NavigationMenuSelector onCreateNew={ handler } />
				);

				const toggleButton = screen.getByRole( 'button' );

				await user.click( toggleButton );

				await user.click(
					screen.getByRole( 'menuitem', {
						name: 'Create new Menu',
					} )
				);

				// creating a menu is a network activity
				// so we have to wait on it
				useNavigationMenu.mockReturnValue( {
					navigationMenus: [],
					hasResolvedNavigationMenus: false,
					isResolvingNavigationMenus: true,
					canUserCreateNavigationMenus: true,
					canSwitchNavigationMenu: true,
				} );

				rerender( <NavigationMenuSelector onCreateNew={ handler } /> );

				// Re-open the dropdown (it's closed when the "Create menu" button is clicked).
				await user.click( toggleButton );

				// Check the dropdown is open again.
				expect( screen.getByRole( 'menu' ) ).toBeInTheDocument();

				// Check the "Create menu" button is disabled.
				expect(
					screen.queryByRole( 'menuitem', {
						name: 'Create new Menu',
					} )
				).toHaveAttribute( 'aria-disabled', 'true' );

				// once the menu is created
				// no more network activity to wait on
				useNavigationMenu.mockReturnValue( {
					navigationMenus: [],
					hasResolvedNavigationMenus: true,
					isResolvingNavigationMenus: false,
					canUserCreateNavigationMenus: true,
					canSwitchNavigationMenu: true,
				} );

				// Simulate the menu being created and component being re-rendered.
				rerender(
					<NavigationMenuSelector
						onCreateNew={ handler }
						createNavigationMenuIsSuccess
					/>
				);

				// Check the button is enabled again.
				expect(
					screen.queryByRole( 'menuitem', {
						name: 'Create new Menu',
					} )
				).toBeEnabled();
			} );
		} );

		describe( 'Navigation Menus', () => {
			it( 'should not show a list of menus when menus exist but user does not have permission to switch menus', async () => {
				const user = userEvent.setup();

				useNavigationMenu.mockReturnValue( {
					navigationMenus: navigationMenusFixture,
					hasResolvedNavigationMenus: true,
					canUserCreateNavigationMenus: true,
					canSwitchNavigationMenu: false,
				} );

				render( <NavigationMenuSelector /> );

				const toggleButton = screen.getByRole( 'button' );
				await user.click( toggleButton );

				const menusGroup = screen.queryByRole( 'group', {
					name: 'Menus',
				} );
				expect( menusGroup ).not.toBeInTheDocument();
			} );

			it( 'should show a list of menus when menus exist', async () => {
				const user = userEvent.setup();

				useNavigationMenu.mockReturnValue( {
					navigationMenus: navigationMenusFixture,
					hasResolvedNavigationMenus: true,
					canUserCreateNavigationMenus: false,
					canSwitchNavigationMenu: true,
				} );

				render( <NavigationMenuSelector /> );

				const toggleButton = screen.getByRole( 'button' );
				await user.click( toggleButton );

				const menusGroup = screen.queryByRole( 'group', {
					name: 'Menus',
				} );
				expect( menusGroup ).toBeInTheDocument();

				navigationMenusFixture.forEach( ( item ) => {
					const menuItem = screen.queryByRole( 'menuitemradio', {
						name: item?.title?.rendered,
					} );
					expect( menuItem ).toBeInTheDocument();
				} );
			} );

			it( 'should render fallback title when menu has no title', async () => {
				const user = userEvent.setup();

				const menusWithNoTitle = [
					{
						id: 1,
						status: 'publish',
					},
					{
						id: 2,
						status: 'publish',
					},
				];

				useNavigationMenu.mockReturnValue( {
					navigationMenus: menusWithNoTitle,
					hasResolvedNavigationMenus: true,
					canUserCreateNavigationMenus: true,
					canSwitchNavigationMenu: true,
				} );

				render( <NavigationMenuSelector /> );

				const toggleButton = screen.getByRole( 'button' );
				await user.click( toggleButton );

				const menusGroup = screen.queryByRole( 'group', {
					name: 'Menus',
				} );
				expect( menusGroup ).toBeInTheDocument();

				// Check for sequentially named fallback titles.
				expect(
					screen.getByRole( 'menuitemradio', {
						name: '(no title 1)',
					} )
				).toBeInTheDocument();

				expect(
					screen.getByRole( 'menuitemradio', {
						name: '(no title 2)',
					} )
				).toBeInTheDocument();
			} );

			it( 'should pre-select the correct menu in the menu list if a menu ID is passed', async () => {
				const user = userEvent.setup();

				useNavigationMenu.mockReturnValue( {
					navigationMenus: navigationMenusFixture,
					hasResolvedNavigationMenus: true,
					canUserCreateNavigationMenus: true,
					canSwitchNavigationMenu: true,
				} );

				render(
					<NavigationMenuSelector
						currentMenuId={ navigationMenusFixture[ 0 ].id }
					/>
				);

				const toggleButton = screen.getByRole( 'button' );
				await user.click( toggleButton );

				const menuItem = screen.getByRole( 'menuitemradio', {
					name: navigationMenusFixture[ 0 ].title.rendered,
				} );

				expect( menuItem ).toBeChecked();
			} );

			it( 'should call the handler when the Navigation Menu is selected', async () => {
				const user = userEvent.setup();

				const handler = vi.fn();

				useNavigationMenu.mockReturnValue( {
					navigationMenus: navigationMenusFixture,
					hasResolvedNavigationMenus: true,
					canUserCreateNavigationMenus: true,
					canSwitchNavigationMenu: true,
				} );

				render(
					<NavigationMenuSelector
						onSelectNavigationMenu={ handler }
					/>
				);
				const toggleButton = screen.getByRole( 'button' );

				await user.click( toggleButton );

				await user.click(
					screen.getByRole( 'menuitemradio', {
						name: navigationMenusFixture[ 0 ].title.rendered,
					} )
				);

				expect( handler ).toHaveBeenCalledWith(
					navigationMenusFixture[ 0 ].id
				);

				//  Check the dropdown has been closed.
				expect( screen.queryByRole( 'menu' ) ).not.toBeInTheDocument();
			} );
		} );

		describe( 'Classic menus', () => {
			it( 'should not show classic menus if there are no classic menus', async () => {
				const user = userEvent.setup();

				useEntityRecords.mockReturnValue( {
					records: [],
				} );

				render( <NavigationMenuSelector /> );

				const toggleButton = screen.getByRole( 'button' );
				await user.click( toggleButton );

				const classicMenusGroup = screen.queryByRole( 'group', {
					name: 'Import Classic Menus',
				} );
				expect( classicMenusGroup ).not.toBeInTheDocument();
			} );

			it( 'should not show classic menus if there are classic menus but the user does not have permission to create menus', async () => {
				const user = userEvent.setup();

				useNavigationMenu.mockReturnValue( {
					canUserCreateNavigationMenus: false,
				} );

				useEntityRecords.mockReturnValue( {
					records: classicMenusFixture,
				} );

				render( <NavigationMenuSelector /> );

				const toggleButton = screen.getByRole( 'button' );
				await user.click( toggleButton );

				const classicMenusGroup = screen.queryByRole( 'group', {
					name: 'Import Classic Menus',
				} );
				expect( classicMenusGroup ).not.toBeInTheDocument();
			} );

			it( 'should show classic menus if there are classic menus and the user has permission to create menus', async () => {
				const user = userEvent.setup();

				useNavigationMenu.mockReturnValue( {
					canUserCreateNavigationMenus: true,
				} );

				useEntityRecords.mockReturnValue( {
					records: classicMenusFixture,
				} );

				render( <NavigationMenuSelector /> );

				const toggleButton = screen.getByRole( 'button' );
				await user.click( toggleButton );

				const classicMenusGroup = screen.queryByRole( 'group', {
					name: 'Import Classic Menus',
				} );

				expect( classicMenusGroup ).toBeInTheDocument();

				// Check for classic menuitems
				classicMenusFixture.forEach( ( item ) => {
					const menuItem = screen.getByRole( 'menuitem', {
						name: `Create from '${ item.name }'`,
					} );
					expect( menuItem ).toBeInTheDocument();
				} );
			} );

			it( 'should call the handler when the classic menu item is selected and disable all options during the import/creation process', async () => {
				const user = userEvent.setup();
				const handler = vi.fn( async () => {} );

				// initially we have the menus, and we're not waiting on network
				useNavigationMenu.mockReturnValue( {
					navigationMenus: [],
					isResolvingNavigationMenus: false,
					hasResolvedNavigationMenus: true,
					canSwitchNavigationMenu: true,
					canUserCreateNavigationMenus: true,
				} );

				useEntityRecords.mockReturnValue( {
					records: classicMenusFixture,
				} );

				const { rerender } = render(
					<NavigationMenuSelector onSelectClassicMenu={ handler } />
				);

				const toggleButton = screen.getByRole( 'button' );

				await user.click( toggleButton );

				await user.click(
					screen.getByRole( 'menuitem', {
						name: `Create from '${ classicMenusFixture[ 0 ].name }'`,
					} )
				);

				expect( handler ).toHaveBeenCalled();

				// Check the dropdown has been closed.
				expect( screen.queryByRole( 'menu' ) ).not.toBeInTheDocument();

				// since we're importing we are doing network activity
				// so we have to wait on it
				useNavigationMenu.mockReturnValue( {
					navigationMenus: [],
					isResolvingNavigationMenus: true,
					hasResolvedNavigationMenus: false,
					canUserCreateNavigationMenus: true,
				} );

				useEntityRecords.mockReturnValue( {
					records: classicMenusFixture,
				} );

				rerender(
					<NavigationMenuSelector onSelectClassicMenu={ handler } />
				);

				// // Re-open the dropdown (it's closed when the "Create menu" button is clicked).
				await user.click( screen.getByRole( 'button' ) );

				// Check the dropdown is open and is in the "loading" state.
				expect(
					screen.getByRole( 'menu', {
						name: /Loading/,
					} )
				).toBeInTheDocument();

				// Check all menu items are present but disabled.
				screen.getAllByRole( 'menuitem' ).forEach( ( item ) => {
					// // Check all menu items are present but disabled.
					expect( item ).toHaveAttribute( 'aria-disabled', 'true' );
				} );

				// once the menu is imported
				// no more network activity to wait on
				useNavigationMenu.mockReturnValue( {
					navigationMenus: [],
					isResolvingNavigationMenus: false,
					hasResolvedNavigationMenus: true,
					canUserCreateNavigationMenus: true,
				} );

				useEntityRecords.mockReturnValue( {
					records: classicMenusFixture,
				} );

				// Simulate the menu being created and component being re-rendered.
				rerender(
					<NavigationMenuSelector
						createNavigationMenuIsSuccess // classic menu import creates a Navigation menu.
					/>
				);

				// Todo: fix bug where aria label is not updated.
				// expect(
				// 	screen.getByRole( 'menu', {
				// 		name: `You are currently editing ${ classicMenusFixture[ 0 ].name }`,
				// 	} )
				// ).toBeInTheDocument();

				// Check all menu items are re-enabled.
				screen.getAllByRole( 'menuitem' ).forEach( ( item ) => {
					// // Check all menu items are present but disabled.
					expect( item ).toBeEnabled();
				} );
			} );
		} );
	} );
} );
