/*!
 * V4Fire Client Core
 * https://github.com/V4Fire/Client
 *
 * Released under the MIT license
 * https://github.com/V4Fire/Client/blob/master/LICENSE
 */

import type { Page, JSHandle } from 'playwright';

import test from 'tests/config/unit/test';

import Component from 'tests/helpers/component';
import BOM from 'tests/helpers/bom';

import type bButton from 'form/b-button/b-button';

test.describe('b-button', () => {
	let
		buttonNode,
		buttonCtx: JSHandle<bButton>;

	test.beforeEach(async ({demoPage}) => {
		await demoPage.goto();
	});

	test.describe('providing `type`', () => {
		test.describe('`file`', () => {
			test.beforeEach(async ({page}) => {
				await renderButton(page, {
					type: 'file'
				});
			});

			test('renders a button with a `file` type', async () => {
				const
					fileEl = await buttonNode.$('[type="file"]');

				test.expect(fileEl).toBeTruthy();
			});

			test('opens a file chooser on click', async ({page}) => {
				const
					event = page.waitForEvent('filechooser');

				await buttonNode.click();
				await test.expect(event).resolves.toBeTruthy();
			});

			test.describe('file chooser has been opened, and the file has been selected', () => {
				let changeEventPr;

				test.beforeEach(async ({page}) => {
					const
						fileChooserPr = page.waitForEvent('filechooser');

					changeEventPr = buttonCtx.evaluate((ctx) => ctx.promisifyOnce('change').then(() => undefined));

					await buttonNode.click();

					const
						fileChooser = await fileChooserPr;

					await fileChooser.setFiles({
						name: 'somefile.pdf',
						mimeType: 'application/pdf',
						buffer: Buffer.from([])
					});
				});

				test('stores the provided file', async () => {
					const
						filesLength = await buttonCtx.evaluate((ctx) => ctx.files?.length);

					test.expect(filesLength).toBe(1);
				});

				test('fires a `change` event', async () => {
					await test.expect(changeEventPr).resolves.toBeUndefined();
				});

				test('resets the provided files on the `reset` method call', async () => {
					await buttonCtx.evaluate((ctx) => ctx.reset());

					const
						filesLength = await buttonCtx.evaluate((ctx) => ctx.files?.length);

					test.expect(filesLength).toBe(0);
				});
			});
		});

		test.describe('`submit`', () => {
			test.beforeEach(async ({page}) => {
				await renderButton(page, {
					type: 'submit'
				});
			});

			test('renders a button with a `submit` type', async () => {
				const
					submitEl = await buttonNode.$('[type="submit"]');

				test.expect(submitEl).toBeTruthy();
			});

			test('fires a `submit` event on click', async ({page}) => {
				const pr = page.evaluate(() => new Promise<void>((res) => {
					const
						form = document.createElement('form');

					document.body.prepend(form);
					form.appendChild(globalThis.buttonNode);
					form.onsubmit = (e) => {
						e.preventDefault();
						res();
					};
				}));

				await buttonNode.click();

				await test.expect(pr).resolves.toBeUndefined();
			});
		});

		test.describe('`link`', () => {
			const
				url = 'https://someurl.com/';

			test.beforeEach(async ({page}) => {
				await renderButton(page, {
					href: url,
					type: 'link'
				});
			});

			test('renders a button with the provided `href`', async () => {
				const
					linkEl = await buttonNode.$(`[href="${url}"]`);

				test.expect(linkEl).toBeTruthy();
			});

			test('navigates to the provided `href`', async ({page}) => {
				const pr = new Promise(async (res) => {
					await page.route('**/*', (r) => {
						if (r.request().isNavigationRequest()) {
							res(r.request().url());
							return r.fulfill({
								status: 200
							});
						}

						return r.continue();
					});
				});

				await buttonNode.click();

				const
					navigationUrl = await pr;

				test.expect(navigationUrl).toBe(url);
			});
		});

	});

	test.describe('`href`', () => {
		test('provides the base URL to a data provider', async ({page}) => {
			const pr = new Promise<void>(async (res) => {
				await page.route('**/api/test/base', (r) => {
					res();

					return r.fulfill({
						status: 200,
						contentType: 'application/json',
						body: JSON.stringify({})
					});
				});
			});

			await renderButton(page, {
				href: 'test/base'
			});

			await buttonNode.click();
			await test.expect(pr).resolves.toBeUndefined();
		});
	});

	test.describe('providing `disabled`', () => {
		test.describe('`true`', () => {
			test('does not fire any click event', async ({page}) => {
				await renderButton(page, {
					disabled: true
				});

				await buttonCtx.evaluate((ctx) => ctx.on('click', () => globalThis._t = 1));
				await buttonNode.click({force: true});
				await BOM.waitForIdleCallback(page);

				const
					testVal = await page.evaluate(() => globalThis._t);

				test.expect(testVal).toBeUndefined();
			});

			test('does not fire a navigation', async ({page}) => {
				await renderButton(page, {
					disabled: true,
					type: 'link',
					href: 'https://someurl.com/'
				});

				let hasNavRequest = false;

				await page.route('**/*', (r) => {
					if (r.request().isNavigationRequest()) {
						hasNavRequest = true;
					}

					return r.continue();
				});

				await buttonNode.click({force: true});
				await BOM.waitForIdleCallback(page, {sleepAfterIdles: 300});

				test.expect(hasNavRequest).toBe(false);
			});
		});

		test.describe('`false`', () => {
			test('fires a `click` event', async ({page}) => {
				await renderButton(page, {
					disabled: false
				});

				await buttonCtx.evaluate((ctx) => ctx.on('click', () => globalThis._t = 1));
				await buttonNode.click();
				await BOM.waitForIdleCallback(page);

				const
					testVal = await page.evaluate(() => globalThis._t);

				test.expect(testVal).toBe(1);
			});
		});
	});

	test.describe('providing `autofocus`', () => {
		test('`true`', async ({page}) => {
			await renderButton(page, {
				autofocus: true
			});

			const
				autofocusEl = await buttonNode.$('[autofocus="autofocus"]');

			test.expect(autofocusEl).toBeTruthy();
		});

		test('`false`', async ({page}) => {
			await renderButton(page, {
				autofocus: false
			});

			const
				autofocusEl = await buttonNode.$('[autofocus="autofocus"]');

			test.expect(autofocusEl).toBeNull();
		});
	});

	test.describe('providing `tabIndex`', () => {
		test('`-1`', async ({page}) => {
			await renderButton(page, {
				tabIndex: -1
			});

			const
				tabIndexEl = await buttonNode.$('[tabindex="-1"]');

			test.expect(tabIndexEl).toBeTruthy();
		});

		test('`1`', async ({page}) => {
			await renderButton(page, {
				tabIndex: 1
			});

			const
				tabIndexEl = await buttonNode.$('[tabindex="1"]');

			test.expect(tabIndexEl).toBeTruthy();
		});
	});

	test.describe('providing `preIcon`', () => {
		test('`dropdown`', async ({page}) => {
			const
				iconName = 'foo';

			await renderButton(page, {
				preIcon: iconName
			});

			const
				bIcon = await buttonNode.$('.b-icon'),
				iconProvidedName = await bIcon.evaluate((ctx) => ctx.component.value);

			test.expect(iconProvidedName).toBe(iconName);
		});

		test('`undefined`', async ({page}) => {
			await renderButton(page, {
				preIcon: undefined
			});

			const
				bIcon = await buttonNode.$('.b-icon');

			test.expect(bIcon).toBeNull();
		});
	});

	test.describe('click event', () => {
		test.beforeEach(async ({page}) => {
			await renderButton(page);
		});

		test('fires on click', async () => {
			const
				pr = buttonCtx.evaluate((ctx) => ctx.promisifyOnce('click').then(() => undefined));

			await buttonNode.click();

			await test.expect(pr).resolves.toBeUndefined();
		});

		test('does not emit an event without a click', async ({page}) => {
			await buttonCtx.evaluate((ctx) => ctx.once('click', () => globalThis._t = 1));
			await BOM.waitForIdleCallback(page, {sleepAfterIdles: 400});

			const
				res = await page.evaluate(() => globalThis._t);

			test.expect(res).toBeUndefined();
		});
	});

	/**
	 * @param page
	 * @param attrs
	 */
	async function renderButton(page: Page, attrs: Dictionary = {}) {
		await Component.createComponent(page, 'b-button', {
			attrs: {
				id: 'target',
				...attrs
			},

			content: {
				default: () => 'Hello there general kenobi'
			}
		});

		buttonNode = await page.waitForSelector('#target');
		buttonCtx = await Component.waitForComponentByQuery<bButton>(page, '#target');

		await page.evaluate(() => globalThis.buttonNode = document.querySelector('#target'));
	}
});
