import type { Props, Tracked } from 'ripple';
import { RippleObject, effect, flushSync, track } from 'ripple';

describe('composite > reactivity', () => {
	it('renders composite components with object state', () => {
		function Button({ obj }: { obj: { count: Tracked<number> } }) {
			return <>
				<button
					class="count2"
					onClick={() => {
						obj.count.value++;
					}}
				>
					{obj.count.value}
				</button>
			</>;
		}

		function App() {
			return <>
				<div>
					let obj = {
						count: track(0),
					};

					<span class="count">{obj.count.value}</span>
					<Button {obj} />
				</div>
			</>;
		}

		render(App);

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

		button.click();
		flushSync();

		expect(container.querySelector('.count').textContent).toBe('1');
		expect(container.querySelector('.count2').textContent).toBe('1');
	});

	it('renders composite components with object state wrapped in an if statement', () => {
		function Button({ obj }: { obj: { count: Tracked<number> } }) {
			return <>
				<button
					class="count2"
					onClick={() => {
						obj.count.value++;
					}}
				>
					{obj.count.value}
				</button>
			</>;
		}

		function OtherComponent({ obj }: { obj: { count: Tracked<number> } }) {
			return <><div class="count3">{obj.count.value}</div></>;
		}

		function App() {
			return <>
				<div>
					let obj = {
						count: track(0),
					};

					<span class="count">{obj.count.value}</span>
					<span>{' '}</span>
					if (obj) {
						<Button {obj} />
					}

					if (obj) {
						<OtherComponent {obj} />
					}
				</div>
			</>;
		}

		render(App);

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

		button.click();
		flushSync();

		expect(container.querySelector('.count').textContent).toBe('1');
		expect(container.querySelector('.count2').textContent).toBe('1');
		expect(container.querySelector('.count3').textContent).toBe('1');
	});

	it('parents and children have isolated state', () => {
		function Button(props: { count: number }) {
			return <>
				let &[count] = track(() => props.count);
				<button
					onClick={() => {
						count++;
					}}
				>
					{'child: ' + count}
				</button>
			</>;
		}

		function App() {
			return <>
				<div>
					let &[count] = track(0);

					<button
						onClick={() => {
							count++;
						}}
					>
						{'parent: ' + count}
					</button>
					<Button {count} />
				</div>
			</>;
		}

		render(App);

		const buttons = container.querySelectorAll('button');

		expect(buttons[0].textContent).toBe('parent: 0');
		expect(buttons[1].textContent).toBe('child: 0');

		buttons[0].click();
		flushSync();

		expect(buttons[0].textContent).toBe('parent: 1');
		expect(buttons[1].textContent).toBe('child: 1');

		buttons[1].click();
		flushSync();

		expect(buttons[0].textContent).toBe('parent: 1');
		expect(buttons[1].textContent).toBe('child: 2');
	});

	it('parents and children have isolated connected state (destructured props)', () => {
		function Button(&{ count }: { count: number }) {
			return <>
				let &[local_count] = track(() => count);
				<button
					onClick={() => {
						local_count++;
					}}
				>
					{'child: ' + local_count}
				</button>
			</>;
		}

		function App() {
			return <>
				<div>
					let &[count] = track(0);

					<button
						onClick={() => {
							count++;
						}}
					>
						{'parent: ' + count}
					</button>
					<Button {count} />
				</div>
			</>;
		}

		render(App);

		const buttons = container.querySelectorAll('button');

		expect(buttons[0].textContent).toBe('parent: 0');
		expect(buttons[1].textContent).toBe('child: 0');

		buttons[0].click();
		flushSync();

		expect(buttons[0].textContent).toBe('parent: 1');
		expect(buttons[1].textContent).toBe('child: 1');

		buttons[1].click();
		flushSync();

		expect(buttons[0].textContent).toBe('parent: 1');
		expect(buttons[1].textContent).toBe('child: 2');
	});

	it('handles spreading of props', () => {
		let logs: string[] = [];

		function App() {
			return <>
				let &[a] = track(1);
				let &[b] = track(2);
				let &[c] = track(3);
				let &[obj] = track(
					() => ({
						a,
						b,
						c,
					}),
				);
				<Child {...obj} />
				<button
					onClick={() => {
						a++;
						b++;
						c++;
					}}
				>
					{'Increment all'}
				</button>
			</>;
		}

		function Child(&{ a, b, c }: { a: number; b: number; c: number }) {
			return <>
				effect(() => {
					logs.push(`Child effect: ${a}, ${b}, ${c}`);
				});
				<div>{a + ' ' + b + ' ' + c}</div>
			</>;
		}

		render(App);
		flushSync();

		expect(container.querySelector('div').textContent).toBe('1 2 3');
		expect(logs).toEqual(['Child effect: 1, 2, 3']);

		const button = container.querySelector('button');
		button.click();
		flushSync();

		expect(container.querySelector('div').textContent).toBe('2 3 4');
		expect(logs).toEqual(['Child effect: 1, 2, 3', 'Child effect: 2, 3, 4']);
	});

	it('keeps reactivity for spread props via intermediate components and lazy destructuring', () => {
		function App() {
			return <>
				let &[count] = track(0);
				<CounterWrapper {count} up={() => count++} down={() => count--} />
			</>;
		}

		function CounterWrapper(props: Props) {
			return <>
				<div>
					<Counter {...props} />
				</div>
			</>;
		}

		function Counter(&{ count, up, down, ...rest }: Props) {
			return <>
				<button onClick={() => up()}>{'UP'}</button>
				<button onClick={() => down()}>{'DOWN'}</button>
				<span {...rest}>{`Counter: ${count}`}</span>
			</>;
		}

		render(App);

		const buttonIncrement = container.querySelectorAll('button')[0];
		const buttonDecrement = container.querySelectorAll('button')[1];
		const span = container.querySelector('span');

		expect(span.textContent).toBe('Counter: 0');

		buttonIncrement.click();
		flushSync();

		expect(span.textContent).toBe('Counter: 1');

		buttonDecrement.click();
		flushSync();

		expect(span.textContent).toBe('Counter: 0');
	});

	it('keeps reactivity on elements for element spreads and adds / removes dynamic props', () => {
		function App() {
			return <>
				let &[count] = track(0);
				<CounterWrapper {count} up={() => count++} />
			</>;
		}

		function CounterWrapper(props: { count: number; up: () => void }) {
			return <>
				const more: {
					double: Tracked<number>;
					another?: number;
					extra: number;
				} = new RippleObject({
					double: track(() => props.count * 2),
					another: 0,
					extra: 100,
				});
				effect(() => {
					props.count;
					if (props.count === 1) {
						delete more.another;
					} else if (props.count === 2) {
						more.another = 0;
					}
				});
				<div>
					<Counter {...props} double={more.double} another={more.another} extra={more.extra} />
				</div>
			</>;
		}

		function Counter(&{
			count,
			up,
			...rest
		}: {
			count: number;
			up: () => void;
			double: Tracked<number>;
			another?: number;
			extra: number;
		}) {
			return <>
				<div {...rest}>{`Counter: ${count} Double: ${rest.double.value}`}</div>
				<button onClick={() => up()}>{'UP'}</button>
			</>;
		}

		render(App);

		const buttonIncrement = container.querySelectorAll('button')[0];
		const div = container.querySelectorAll('div')[1];

		expect(div.getAttribute('double')).toBe('0');
		expect(div.getAttribute('another')).toBe('0');
		expect(div.getAttribute('extra')).toBe('100');
		expect(div.textContent).toBe('Counter: 0 Double: 0');

		buttonIncrement.click();
		flushSync();

		expect(div.getAttribute('double')).toBe('2');
		expect(div.hasAttribute('another')).toBe(false);
		expect(div.getAttribute('extra')).toBe('100');
		expect(div.textContent).toBe('Counter: 1 Double: 2');

		buttonIncrement.click();
		flushSync();

		expect(div.getAttribute('double')).toBe('4');
		expect(div.getAttribute('another')).toBe('0');
		expect(div.getAttribute('extra')).toBe('100');
		expect(div.textContent).toBe('Counter: 2 Double: 4');
	});
});
