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 { useDispatch, useSelect } from '@wordpress/data';
import OverlayTemplatePartSelector from '../overlay-template-part-selector';
import useCreateOverlayTemplatePart from '../use-create-overlay';

globalThis.wpVitest.mockMatchMedia();

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

// Mock useCreateOverlayTemplatePart hook
vi.mock( import( '../use-create-overlay' ), () => ( {
	__esModule: true,
	default: vi.fn(),
} ) );

// Mock useDispatch and useSelect specifically to avoid needing to set up full data store
vi.mock( import( '@wordpress/data' ), () => ( {
	useDispatch: vi.fn(),
	useSelect: vi.fn(),
	createSelector: vi.fn( ( fn ) => fn ),
	createRegistrySelector: vi.fn( ( fn ) => fn ),
	createReduxStore: vi.fn( () => ( {} ) ),
	combineReducers: vi.fn( ( reducers ) => ( state = {}, action ) => {
		const newState = {};
		Object.keys( reducers ).forEach( ( key ) => {
			newState[ key ] = reducers[ key ]( state[ key ], action );
		} );
		return newState;
	} ),
	register: vi.fn(),
	keyedReducer: vi.fn( () => ( reducer ) => reducer ),
} ) );

const mockSetAttributes = vi.fn();
const mockOnNavigateToEntityRecord = vi.fn();

const defaultProps = {
	overlay: undefined,
	setAttributes: mockSetAttributes,
	onNavigateToEntityRecord: mockOnNavigateToEntityRecord,
};

const templatePart1 = {
	id: 1,
	theme: 'twentytwentyfive',
	slug: 'my-overlay',
	title: {
		rendered: 'My Overlay',
	},
	area: 'navigation-overlay',
};

const templatePart2 = {
	id: 2,
	theme: 'twentytwentyfive',
	slug: 'another-overlay',
	title: {
		rendered: 'Another Overlay',
	},
	area: 'navigation-overlay',
};

const templatePartOtherArea = {
	id: 3,
	theme: 'twentytwentyfive',
	slug: 'header-part',
	title: {
		rendered: 'Header Part',
	},
	area: 'header',
};

describe( 'OverlayTemplatePartSelector', () => {
	const mockCreateOverlayTemplatePart = vi.fn();
	const mockCreateErrorNotice = vi.fn();

	beforeEach( () => {
		vi.clearAllMocks();
		useEntityRecords.mockReturnValue( {
			records: [],
			isResolving: false,
			hasResolved: false,
		} );
		useCreateOverlayTemplatePart.mockReturnValue(
			mockCreateOverlayTemplatePart
		);
		// Mock useDispatch to return createErrorNotice for noticesStore
		useDispatch.mockReturnValue( {
			createErrorNotice: mockCreateErrorNotice,
		} );
		// Mock useSelect to return current theme
		// The component calls: select( coreStore ).getCurrentTheme()?.stylesheet
		useSelect.mockReturnValue( 'twentytwentyfive' );
	} );

	describe( 'Loading state', () => {
		it( 'should disable select control when template parts are resolving', () => {
			useEntityRecords.mockReturnValue( {
				records: null,
				isResolving: true,
				hasResolved: false,
			} );

			render( <OverlayTemplatePartSelector { ...defaultProps } /> );

			const select = screen.getByRole( 'combobox', {
				name: 'Overlay template',
			} );
			expect( select ).toBeDisabled();
		} );
	} );

	describe( 'Overlay selection', () => {
		it( 'should show Create Overlay button when no overlays exist', () => {
			useEntityRecords.mockReturnValue( {
				records: [],
				isResolving: false,
				hasResolved: true,
			} );

			render( <OverlayTemplatePartSelector { ...defaultProps } /> );

			const createButton = screen.getByRole( 'button', {
				name: 'Create overlay',
			} );
			expect( createButton ).toBeInTheDocument();
			expect(
				screen.getByText(
					'An overlay template allows you to customize the appearance of the dialog that opens when the menu button is pressed.'
				)
			).toBeInTheDocument();
			expect(
				screen.queryByRole( 'combobox', { name: 'Overlay template' } )
			).not.toBeInTheDocument();
		} );

		it( 'should show dropdown selector when theme overlays exist', () => {
			// Theme overlays have a theme property
			const themeOverlay = {
				...templatePart1,
				theme: 'twentytwentyfive',
			};

			useEntityRecords.mockReturnValue( {
				records: [ themeOverlay, templatePartOtherArea ],
				isResolving: false,
				hasResolved: true,
			} );

			render( <OverlayTemplatePartSelector { ...defaultProps } /> );

			expect(
				screen.getByRole( 'combobox', { name: 'Overlay template' } )
			).toBeInTheDocument();
			expect(
				screen.getByRole( 'button', {
					name: 'Create new overlay template',
				} )
			).toBeInTheDocument();
		} );

		it( 'should show dropdown selector when any overlays exist', () => {
			useEntityRecords.mockReturnValue( {
				records: [
					templatePart1,
					templatePart2,
					templatePartOtherArea,
				],
				isResolving: false,
				hasResolved: true,
			} );

			render( <OverlayTemplatePartSelector { ...defaultProps } /> );

			const select = screen.getByRole( 'combobox', {
				name: 'Overlay template',
			} );
			expect( select ).toBeInTheDocument();

			// Should have Default + 2 overlays (not the header one)
			const options = screen.getAllByRole( 'option' );
			expect( options ).toHaveLength( 3 );

			expect(
				screen.getByRole( 'option', { name: 'My Overlay' } )
			).toBeInTheDocument();
			expect(
				screen.getByRole( 'option', { name: 'Another Overlay' } )
			).toBeInTheDocument();
			expect(
				screen.queryByRole( 'option', { name: 'Header Part' } )
			).not.toBeInTheDocument();

			// Should show "Default" option (not "None (default)")
			expect(
				screen.getByRole( 'option', { name: 'Default' } )
			).toBeInTheDocument();
		} );

		it( 'should display overlay slug when title is missing', () => {
			const templatePartNoTitle = {
				...templatePart1,
				title: null,
			};

			useEntityRecords.mockReturnValue( {
				records: [ templatePartNoTitle ],
				isResolving: false,
				hasResolved: true,
			} );

			render( <OverlayTemplatePartSelector { ...defaultProps } /> );

			expect(
				screen.getByRole( 'option', { name: 'my-overlay' } )
			).toBeInTheDocument();
		} );

		it( 'should store slug only when an overlay is selected', async () => {
			const user = userEvent.setup();

			useEntityRecords.mockReturnValue( {
				records: [ templatePart1 ],
				isResolving: false,
				hasResolved: true,
			} );

			render( <OverlayTemplatePartSelector { ...defaultProps } /> );

			const select = screen.getByRole( 'combobox', {
				name: 'Overlay template',
			} );

			await user.selectOptions( select, 'my-overlay' );

			expect( mockSetAttributes ).toHaveBeenCalledWith( {
				overlay: 'my-overlay',
			} );
		} );

		it( 'unsets overlay when "Default" is selected', async () => {
			const user = userEvent.setup();

			useEntityRecords.mockReturnValue( {
				records: [ templatePart1 ],
				isResolving: false,
				hasResolved: true,
			} );

			render(
				<OverlayTemplatePartSelector
					{ ...defaultProps }
					overlay="my-overlay"
				/>
			);

			const select = screen.getByRole( 'combobox', {
				name: 'Overlay template',
			} );

			await user.selectOptions( select, '' );

			expect( mockSetAttributes ).toHaveBeenCalledWith( {
				overlay: undefined,
			} );
		} );

		it( 'should display selected overlay by slug', () => {
			useEntityRecords.mockReturnValue( {
				records: [ templatePart1 ],
				isResolving: false,
				hasResolved: true,
			} );

			render(
				<OverlayTemplatePartSelector
					{ ...defaultProps }
					overlay="my-overlay"
				/>
			);

			const select = screen.getByRole( 'combobox', {
				name: 'Overlay template',
			} );

			expect( select ).toHaveValue( 'my-overlay' );
		} );
	} );

	describe( 'Edit button', () => {
		it( 'should not render when no overlay is selected', () => {
			useEntityRecords.mockReturnValue( {
				records: [ templatePart1 ],
				isResolving: false,
				hasResolved: true,
			} );

			render( <OverlayTemplatePartSelector { ...defaultProps } /> );

			const editButton = screen.queryByRole( 'button', {
				name: 'Edit overlay',
			} );

			expect( editButton ).not.toBeInTheDocument();
		} );

		it( 'should not display edit button while overlays templates are loading', () => {
			useEntityRecords.mockReturnValue( {
				records: [ templatePart1 ],
				isResolving: true,
				hasResolved: false,
			} );

			render(
				<OverlayTemplatePartSelector
					{ ...defaultProps }
					overlay="my-overlay"
				/>
			);

			// Component shows disabled select and disabled button when loading
			const select = screen.getByRole( 'combobox', {
				name: 'Overlay template',
			} );
			expect( select ).toBeDisabled();

			// Expect Edit button to not be in the document
			expect(
				screen.queryByRole( 'button', {
					name: ( accessibleName ) =>
						accessibleName.startsWith( 'Edit overlay' ),
				} )
			).not.toBeInTheDocument();
		} );

		it( 'should be enabled when a valid overlay is selected', () => {
			useEntityRecords.mockReturnValue( {
				records: [ templatePart1 ],
				isResolving: false,
				hasResolved: true,
			} );

			render(
				<OverlayTemplatePartSelector
					{ ...defaultProps }
					overlay="my-overlay"
				/>
			);

			const editButton = screen.getByRole( 'button', {
				name: ( accessibleName ) =>
					accessibleName.startsWith( 'Edit overlay' ),
			} );

			expect( editButton ).toBeEnabled();
			expect( editButton ).toHaveAccessibleName();
		} );

		it( 'should be disabled when navigation to focused overlay editor is not available', () => {
			useEntityRecords.mockReturnValue( {
				records: [ templatePart1 ],
				isResolving: false,
				hasResolved: true,
			} );

			render(
				<OverlayTemplatePartSelector
					{ ...defaultProps }
					overlay="my-overlay"
					onNavigateToEntityRecord={ undefined }
				/>
			);

			const editButton = screen.getByRole( 'button', {
				name: ( accessibleName ) =>
					accessibleName.startsWith( 'Edit overlay' ),
			} );

			// Button uses accessibleWhenDisabled, so it has aria-disabled instead of disabled
			expect( editButton ).toHaveAttribute( 'aria-disabled', 'true' );
		} );

		it( 'should navigate to focused overlay editor with full ID when edit button is clicked', async () => {
			const user = userEvent.setup();

			useEntityRecords.mockReturnValue( {
				records: [ templatePart1 ],
				isResolving: false,
				hasResolved: true,
			} );

			render(
				<OverlayTemplatePartSelector
					{ ...defaultProps }
					overlay="my-overlay"
				/>
			);

			const editButton = screen.getByRole( 'button', {
				name: ( accessibleName ) =>
					accessibleName.startsWith( 'Edit overlay' ),
			} );

			await user.click( editButton );

			// Should construct full ID from theme and slug
			expect( mockOnNavigateToEntityRecord ).toHaveBeenCalledWith( {
				postId: 'twentytwentyfive//my-overlay',
				postType: 'wp_template_part',
			} );
		} );

		it( 'should not navigate to focused overlay editor when button is disabled', async () => {
			const user = userEvent.setup();

			useEntityRecords.mockReturnValue( {
				records: [ templatePart1 ],
				isResolving: false,
				hasResolved: true,
			} );

			render(
				<OverlayTemplatePartSelector
					{ ...defaultProps }
					overlay="my-overlay"
					onNavigateToEntityRecord={ undefined }
				/>
			);

			const editButton = screen.getByRole( 'button', {
				name: ( accessibleName ) =>
					accessibleName.startsWith( 'Edit overlay' ),
			} );

			// Button uses accessibleWhenDisabled, so it has aria-disabled instead of disabled
			expect( editButton ).toHaveAttribute( 'aria-disabled', 'true' );

			// Even if clicked, the handler checks for onNavigateToEntityRecord and won't call it
			await user.click( editButton );

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

	describe( 'Help text', () => {
		it( 'should show prominent create button with help text when no overlays exist', () => {
			useEntityRecords.mockReturnValue( {
				records: [],
				isResolving: false,
				hasResolved: true,
			} );

			render( <OverlayTemplatePartSelector { ...defaultProps } /> );

			// Should show prominent create button
			expect(
				screen.getByRole( 'button', {
					name: 'Create overlay',
				} )
			).toBeInTheDocument();

			// Should show help text explaining overlays
			expect(
				screen.getByText(
					'An overlay template allows you to customize the appearance of the dialog that opens when the menu button is pressed.'
				)
			).toBeInTheDocument();
		} );

		it( 'should show dropdown with help text when overlays are available', () => {
			useEntityRecords.mockReturnValue( {
				records: [ templatePart1 ],
				isResolving: false,
				hasResolved: true,
			} );

			render( <OverlayTemplatePartSelector { ...defaultProps } /> );

			// Should show dropdown selector
			expect(
				screen.getByRole( 'combobox', { name: 'Overlay template' } )
			).toBeInTheDocument();

			// Should show default help text
			expect(
				screen.getByText( 'Select an overlay for navigation.' )
			).toBeInTheDocument();

			// Should also show small create button
			expect(
				screen.getByRole( 'button', {
					name: 'Create new overlay template',
				} )
			).toBeInTheDocument();
		} );
	} );

	describe( 'Create overlay', () => {
		it( 'should store slug only and navigate with full ID when creating overlay via prominent button', async () => {
			const user = userEvent.setup();
			const newOverlay = {
				id: 'twentytwentyfive//overlay',
				theme: 'twentytwentyfive',
				slug: 'overlay',
				title: {
					rendered: 'Overlay',
				},
				area: 'navigation-overlay',
			};

			mockCreateOverlayTemplatePart.mockResolvedValue( newOverlay );

			useEntityRecords.mockReturnValue( {
				records: [],
				isResolving: false,
				hasResolved: true,
			} );

			render( <OverlayTemplatePartSelector { ...defaultProps } /> );

			// Click prominent create button (shown when no custom overlays)
			const createButton = screen.getByRole( 'button', {
				name: 'Create overlay',
			} );

			await user.click( createButton );

			expect( mockCreateOverlayTemplatePart ).toHaveBeenCalled();
			// Should store slug only
			expect( mockSetAttributes ).toHaveBeenCalledWith( {
				overlay: 'overlay',
			} );
			// Should navigate with full ID constructed from theme and slug
			expect( mockOnNavigateToEntityRecord ).toHaveBeenCalledWith( {
				postId: 'twentytwentyfive//overlay',
				postType: 'wp_template_part',
			} );
		} );

		it( 'should show error notice when creation fails', async () => {
			const user = userEvent.setup();

			const error = new Error( 'Failed to create overlay' );
			error.code = 'create_error';

			mockCreateOverlayTemplatePart.mockRejectedValue( error );

			useEntityRecords.mockReturnValue( {
				records: [],
				isResolving: false,
				hasResolved: true,
			} );

			render( <OverlayTemplatePartSelector { ...defaultProps } /> );

			const createButton = screen.getByRole( 'button', {
				name: 'Create overlay',
			} );

			await user.click( createButton );

			// Wait for async operations
			await new Promise( ( resolve ) => setTimeout( resolve, 0 ) );

			expect( mockCreateErrorNotice ).toHaveBeenCalledWith(
				'Failed to create overlay',
				{ type: 'snackbar' }
			);
			expect( mockSetAttributes ).not.toHaveBeenCalled();
			expect( mockOnNavigateToEntityRecord ).not.toHaveBeenCalled();
		} );

		it( 'should disable create button when overlays are resolving', () => {
			useEntityRecords.mockReturnValue( {
				records: [],
				isResolving: true,
				hasResolved: false,
			} );

			render( <OverlayTemplatePartSelector { ...defaultProps } /> );

			// When resolving, show dropdown interface with small create button
			const createButton = screen.getByRole( 'button', {
				name: 'Create new overlay template',
			} );

			expect( createButton ).toHaveAttribute( 'aria-disabled', 'true' );
		} );
	} );
} );
