import { RippleObject, track } from 'ripple';

describe('basic server > attribute rendering', () => {
	it('render static attributes', async () => {
		function Basic() {
			return <><div class="foo" id="bar" style="color: red;">{'Hello World'}</div></>;
		}

		const { body } = await render(Basic);
		const { document } = parseHtml(body);

		const div = document.querySelector('div');

		expect(div.className).toBe('foo');
		expect(div.id).toBe('bar');
		expect(div.style.color).toBe('red');
		expect(div.innerHTML).toBe('Hello World');
	});

	it('render dynamic class attribute', async () => {
		function Basic() {
			return <>
				let &[active] = track(false);
				<div class={active ? 'active' : 'inactive'}>{'Dynamic Class'}</div>
				<style>
					.active {
						color: green;
					}
				</style>
			</>;
		}

		const { body } = await render(Basic);
		const { document } = parseHtml(body);

		const div = document.querySelector('div');

		expect(
			Array.from(div.classList).filter((className) => className.startsWith('tsrx-')),
		).toHaveLength(1);
		expect(div.classList.contains('inactive')).toBe(true);
	});

	it('render class attribute with array, nested array, nested object', async () => {
		function Basic() {
			return <>
				<div
					class={[
						'foo',
						'bar',
						true && 'baz',
						false && 'aaa',
						null && 'bbb',
						[
							'ccc',
							'ddd',
							{ eee: true, fff: false },
						],
					]}
				>
					{'Class Array'}
				</div>
				<style>
					.foo {
						color: red;
					}
				</style>
			</>;
		}

		const { body } = await render(Basic);
		const { document } = parseHtml(body);

		const div = document.querySelector('div');

		expect(
			Array.from(div.classList).filter((className) => className.startsWith('tsrx-')),
		).toHaveLength(1);

		expect(div.classList.contains('foo')).toBe(true);
		expect(div.classList.contains('bar')).toBe(true);
		expect(div.classList.contains('baz')).toBe(true);
		expect(div.classList.contains('aaa')).toBe(false);
		expect(div.classList.contains('bbb')).toBe(false);
		expect(div.classList.contains('ccc')).toBe(true);
		expect(div.classList.contains('ddd')).toBe(true);
		expect(div.classList.contains('eee')).toBe(true);
		expect(div.classList.contains('fff')).toBe(false);
	});

	it('applies scoped classes inside tsx blocks and fragment shorthand', async () => {
		function App() {
			return <>
				<tsx>
					<div class="card">
						<h2>
							{'tsx block'}
						</h2>
					</div>
				</tsx>
				<>
					<div class="card">
						<h2>{'fragment shorthand'}</h2>
					</div>
				</>
				<style>
					.card {
						padding: 1rem;
					}

					h2 {
						color: red;
					}
				</style>
			</>;
		}

		const { body } = await render(App);
		const { document } = parseHtml(body);

		const cards = Array.from(document.querySelectorAll('.card'));
		const headings = Array.from(document.querySelectorAll('h2'));

		expect(cards).toHaveLength(2);
		expect(headings).toHaveLength(2);

		cards.forEach((card) => {
			expect(
				Array.from(card.classList).filter((className) => className.startsWith('tsrx-')),
			).toHaveLength(1);
		});

		headings.forEach((heading) => {
			expect(
				Array.from(heading.classList).filter((className) => className.startsWith('tsrx-')),
			).toHaveLength(1);
		});
	});

	it('render dynamic class object', async () => {
		function Basic() {
			return <>
				let &[active] = track(false);
				<div class={{ active: active, inactive: !active }}>{'Dynamic Class'}</div>
				<style>
					.active {
						color: green;
					}
				</style>
			</>;
		}

		const { body } = await render(Basic);
		const { document } = parseHtml(body);

		const div = document.querySelector('div');

		expect(
			Array.from(div.classList).filter((className) => className.startsWith('tsrx-')),
		).toHaveLength(1);
		expect(div.classList.contains('inactive')).toBe(true);
		expect(div.classList.contains('active')).toBe(false);
	});

	it(
		'applies scoped ripple class to multiple elements with dynamic class expressions',
		async () => {
			function Basic() {
				return <>
					let &[selected] = track(1);
					<div class={selected === 0 ? 'selected' : ''}>{`div 1`}</div>
					<div class={selected === 0 ? 'selected' : ''}>{`div 2`}</div>
					<style>
						div {
							background: green;
							color: white;
						}
						div.selected {
							background: indigo;
						}
					</style>
				</>;
			}

			const { body } = await render(Basic);
			const { document } = parseHtml(body);

			const divs = document.querySelectorAll('div');

			divs.forEach((div) => {
				expect(
					Array.from(div.classList).filter((className) => className.startsWith('tsrx-')),
				).toHaveLength(1);
			});
		},
	);

	it('render dynamic id attribute', async () => {
		function Basic() {
			return <>
				let &[count] = track(0);
				<div id={`item-${count}`}>{'Dynamic ID'}</div>
			</>;
		}

		const { body } = await render(Basic);
		const { document } = parseHtml(body);

		const div = document.querySelector('div');

		expect(div.id).toBe('item-0');
	});

	it('render dynamic style attribute', async () => {
		function Basic() {
			return <>
				let &[color] = track('red');
				<div style={`color: ${color}; font-weight: bold;`}>{'Dynamic Style'}</div>
			</>;
		}

		const { body } = await render(Basic);
		const { document } = parseHtml(body);

		const div = document.querySelector('div');

		expect(div.style.color).toBe('red');
		expect(div.style.fontWeight).toBe('bold');
	});

	it('render style attribute as dynamic object', async () => {
		function Basic() {
			return <>
				let &[color] = track('red');
				<div style={{ color: color, fontWeight: 'bold' }}>{'Dynamic Style'}</div>
			</>;
		}

		const { body } = await render(Basic);
		const { document } = parseHtml(body);

		const div = document.querySelector('div');

		expect(div.style.color).toBe('red');
		expect(div.style.fontWeight).toBe('bold');
	});

	it('render tracked variable as style attribute', async () => {
		function Basic() {
			return <>
				let &[style] = track({ color: 'red', fontWeight: 'bold' });
				<div {style}>{'Dynamic Style'}</div>
			</>;
		}

		const { body } = await render(Basic);
		const { document } = parseHtml(body);

		const div = document.querySelector('div');

		expect(div.style.color).toBe('red');
		expect(div.style.fontWeight).toBe('bold');
	});

	it('render tracked object as style attribute', async () => {
		function Basic() {
			return <>
				let style = new RippleObject({
					color: 'red',
					fontWeight: 'bold',
				});
				<div style={{ color: style.color, fontWeight: style.fontWeight }}>{'Dynamic Style'}</div>
			</>;
		}

		const { body } = await render(Basic);
		const { document } = parseHtml(body);

		const div = document.querySelector('div');

		expect(div.style.color).toBe('red');
		expect(div.style.fontWeight).toBe('bold');
	});

	it('render spread attributes with style and class', async () => {
		function Basic() {
			return <>
				const attributes = {
					style: { color: 'red', fontWeight: 'bold' },
					class: ['foo', false && 'bar'],
				};
				<div {...attributes}>{'Attributes with style and class'}</div>
			</>;
		}

		const { body } = await render(Basic);
		const { document } = parseHtml(body);

		const div = document.querySelector('div');

		expect(div.style.color).toBe('red');
		expect(div.style.fontWeight).toBe('bold');

		expect(div.classList.contains('foo')).toBe(true);
		expect(div.classList.contains('bar')).toBe(false);
	});

	it('renders host innerHTML as content instead of an attribute', async () => {
		function Basic() {
			return <>
				const html = '<span>Direct HTML</span>';
				<code innerHTML={html} />
			</>;
		}

		const { body } = await render(Basic);
		const { document } = parseHtml(body);
		const code = document.querySelector('code');

		expect(code.innerHTML).toBe('<span>Direct HTML</span>');
		expect(code.hasAttribute('innerhtml')).toBe(false);
		expect(body).toBeHtml('<code><span>Direct HTML</span></code>');
	});

	it('renders spread innerHTML as content instead of an attribute', async () => {
		function Basic() {
			return <>
				const attributes = { innerHTML: '<span>Spread HTML</span>' };
				<code {...attributes} />
			</>;
		}

		const { body } = await render(Basic);
		const { document } = parseHtml(body);
		const code = document.querySelector('code');

		expect(code.innerHTML).toBe('<span>Spread HTML</span>');
		expect(code.hasAttribute('innerhtml')).toBe(false);
		expect(body).toBeHtml('<code><span>Spread HTML</span></code>');
	});

	it('uses the last innerHTML value when spreads and direct props are mixed', async () => {
		function Basic() {
			return <>
				const attributes = { innerHTML: '<span>Spread HTML</span>' };
				<code {...attributes} innerHTML="<em>Direct HTML</em>" />
			</>;
		}

		const { body } = await render(Basic);
		const { document } = parseHtml(body);
		const code = document.querySelector('code');

		expect(code.innerHTML).toBe('<em>Direct HTML</em>');
		expect(code.hasAttribute('innerhtml')).toBe(false);
	});

	it('render spread props without duplication', async () => {
		function Basic() {
			return <>
				const checkBoxProp = { name: 'car' };
				<div>
					<input {...checkBoxProp} type="checkbox" id="vehicle1" value="Bike" />
				</div>
			</>;
		}

		const { body } = await render(Basic);
		const { document } = parseHtml(body);

		const input = document.querySelector('input');
		const div = document.querySelector('div');
		const html = div.innerHTML;

		expect(input.getAttribute('name')).toBe('car');
		expect(input.getAttribute('type')).toBe('checkbox');
		expect(input.getAttribute('id')).toBe('vehicle1');
		expect(input.getAttribute('value')).toBe('Bike');

		expect(html).not.toContain('type="checkbox"type="checkbox"');
		expect(html).not.toContain('value="Bike"value="Bike"');

		expect(body).toBeHtml(
			'<div><input name="car" type="checkbox" id="vehicle1" value="Bike" /></div>',
		);
	});

	it('render dynamic boolean attributes as false', async () => {
		function Basic() {
			return <>
				let &[disabled] = track(false);
				let &[checked] = track(false);
				<input type="checkbox" {disabled} {checked} />
			</>;
		}

		const { body } = await render(Basic);
		const { document } = parseHtml(body);

		const input = document.querySelector('input');

		expect(input.hasAttribute('disabled')).toBe(false);
		expect(input.hasAttribute('checked')).toBe(false);
		expect(body).toBeHtml('<input type="checkbox" />');
	});

	it('render dynamic boolean attributes as false via spread', async () => {
		function Basic() {
			return <>
				const spread = { disabled: false, checked: false };
				<input type="checkbox" {...spread} />
			</>;
		}

		const { body } = await render(Basic);
		const { document } = parseHtml(body);

		const input = document.querySelector('input');

		expect(input.hasAttribute('disabled')).toBe(false);
		expect(input.hasAttribute('checked')).toBe(false);
		expect(body).toBeHtml('<input type="checkbox" />');
	});

	it('render dynamic boolean attributes as true', async () => {
		function Basic() {
			return <>
				let &[disabled] = track(true);
				let &[checked] = track(true);
				<input type="checkbox" {disabled} {checked} />
			</>;
		}

		const { body } = await render(Basic);
		const { document } = parseHtml(body);

		const input = document.querySelector('input');

		expect(input.hasAttribute('disabled')).toBe(true);
		expect(input.hasAttribute('checked')).toBe(true);
		expect(body).toBeHtml('<input type="checkbox" disabled checked />');
	});

	it('render dynamic boolean attributes as true via spread', async () => {
		function Basic() {
			return <>
				const spread = { disabled: true, checked: true };
				<input type="checkbox" {...spread} />
			</>;
		}

		const { body } = await render(Basic);
		const { document } = parseHtml(body);

		const input = document.querySelector('input');

		expect(input.hasAttribute('disabled')).toBe(true);
		expect(input.hasAttribute('checked')).toBe(true);
		expect(body).toBeHtml('<input type="checkbox" disabled checked />');
	});

	it('renders formnovalidate as a boolean attribute', async () => {
		function Basic() {
			return <>
				let &[formnovalidate] = track(true);
				<button {formnovalidate}>{'Submit'}</button>
			</>;
		}

		const { body } = await render(Basic);
		const { document } = parseHtml(body);

		const button = document.querySelector('button');

		expect(button.hasAttribute('formnovalidate')).toBe(true);
		expect(button.getAttribute('formnovalidate')).toBe('');
		expect(body).toBeHtml('<button formnovalidate>Submit</button>');
	});

	it('renders hidden as a boolean attribute when true', async () => {
		function Basic() {
			return <>
				let &[hidden] = track(true);
				<div {hidden}>{'Hidden content'}</div>
			</>;
		}

		const { body } = await render(Basic);
		const { document } = parseHtml(body);

		const div = document.querySelector('div');

		expect(div.hasAttribute('hidden')).toBe(true);
		expect(div.getAttribute('hidden')).toBe('');
		expect(body).toBeHtml('<div hidden>Hidden content</div>');
	});

	it('does not render hidden when false', async () => {
		function Basic() {
			return <>
				let &[hidden] = track(false);
				<div {hidden}>{'Visible content'}</div>
			</>;
		}

		const { body } = await render(Basic);
		const { document } = parseHtml(body);

		const div = document.querySelector('div');

		expect(div.hasAttribute('hidden')).toBe(false);
		expect(body).toBeHtml('<div>Visible content</div>');
	});

	it('render multiple dynamic attributes', async () => {
		function Basic() {
			return <>
				let &[theme] = track('light');
				let &[size] = track('medium');
				<div class={`theme-${theme} size-${size}`} data-theme={theme} data-size={size}>
					{'Multiple Dynamic Attributes'}
				</div>
			</>;
		}

		const { body } = await render(Basic);
		const { document } = parseHtml(body);

		const div = document.querySelector('div');

		expect(div.className).toBe('theme-light size-medium');
		expect(div.getAttribute('data-theme')).toBe('light');
		expect(div.getAttribute('data-size')).toBe('medium');
	});

	it('render conditional attributes', async () => {
		function Basic() {
			return <>
				let &[showTitle] = track(false);
				let &[showAria] = track(false);
				<div
					title={showTitle ? 'This is a title' : undefined}
					aria-label={showAria ? 'Accessible label' : undefined}
				>
					{'Conditional Attributes'}
				</div>
			</>;
		}

		const { body } = await render(Basic);
		const { document } = parseHtml(body);

		const div = document.querySelector('div');

		expect(div.hasAttribute('title')).toBe(false);
		expect(div.hasAttribute('aria-label')).toBe(false);
	});

	it('render spread attributes', async () => {
		function Basic() {
			return <>
				let &[attrs] = track<TestAttributes>({
					class: 'initial',
					id: 'test-1',
				});
				<div {...attrs}>{'Spread Attributes'}</div>
			</>;
		}

		const { body } = await render(Basic);
		const { document } = parseHtml(body);

		const div = document.querySelector('div');

		expect(div.className).toBe('initial');
		expect(div.id).toBe('test-1');
		expect(div.hasAttribute('data-extra')).toBe(false);
	});

	it('renders with reactive attributes with nested reactive attributes', async () => {
		function Basic() {
			return <>
				let &[value] = track('parent-class');
				<p class={value}>{'Colored parent value'}</p>
				<div>
					let &[nested] = track('nested-class');

					<p class={nested}>{'Colored nested value'}</p>
				</div>
			</>;
		}

		const { body } = await render(Basic);
		const { document } = parseHtml(body);

		const paragraphs = document.querySelectorAll('p');

		expect(paragraphs[0].className).toBe('parent-class');
		expect(paragraphs[1].className).toBe('nested-class');
	});

	it('handles boolean attributes with no prop value provides', async () => {
		function Basic() {
			return <>
				<div class="container">
					<input type="checkbox" checked />
				</div>
			</>;
		}

		const { body } = await render(Basic);
		expect(body).toBeHtml('<div class="container"><input type="checkbox" checked /></div>');
	});

	it('handles boolean props correctly', async () => {
		function Basic() {
			return <>
				<div data-disabled />
				<Child isDisabled />
			</>;
		}

		function Child({ isDisabled }: { isDisabled: boolean }) {
			return <><input disabled={isDisabled} /></>;
		}

		const { body } = await render(Basic);
		expect(body).toBeHtml('<div data-disabled=""></div><input disabled />');
	});
});
