import { RippleArray, flushSync, track } from 'ripple';
import { compile } from '@tsrx/ripple';

describe('for statements', () => {
	it('renders a simple static array', () => {
		function App() {
			return <>
				const items = ['Item 1', 'Item 2', 'Item 3'];
				for (const item of items) {
					<div class={item}>{item}</div>
				}
			</>;
		}

		render(App);

		expect(container).toMatchSnapshot();
	});

	it('allows continue to skip an iteration', () => {
		function App() {
			return <>
				const items = ['Item 1', '', 'Item 3'];
				for (const item of items) {
					if (!item) continue;
					<div class="item">{item}</div>
				}
			</>;
		}

		render(App);

		expect(Array.from(container.querySelectorAll('.item')).map((el) => el.textContent)).toEqual([
			'Item 1',
			'Item 3',
		]);
	});

	it('allows continue after setup statements to skip an iteration', () => {
		const skipped = [];

		function App() {
			return <>
				const items = ['Item 1', '', 'Item 3'];
				for (const item of items) {
					if (!item) {
						skipped.push('skip');
						continue;
					}
					<div class="item">{item}</div>
				}
			</>;
		}

		render(App);

		expect(skipped).toEqual(['skip']);
		expect(Array.from(container.querySelectorAll('.item')).map((el) => el.textContent)).toEqual([
			'Item 1',
			'Item 3',
		]);
	});

	it('does not emit JavaScript continue in for...of skip callbacks', () => {
		const { code } = compile(
			`function App() { return <>
				const items = ['Item 1', '', 'Item 3'];
				const skipped = [];

				for (const item of items) {
					if (!item) {
						skipped.push('skip');
						continue;
					}
					<div class="item">{item}</div>
				}
			</>; }`,
			'App.tsrx',
			{ mode: 'client' },
		);

		expect(code).toContain('skipped.push(\'skip\')');
		expect(code).not.toContain('continue;');
		expect(code).not.toMatch(/continue;\s*return/);
	});

	it('renders a simple dynamic array', () => {
		function App() {
			return <>
				const items = new RippleArray('Item 1', 'Item 2', 'Item 3');
				for (const item of items) {
					<div class={item}>{item}</div>
				}
				<button onClick={() => items.push(`Item ${items.length + 1}`)}>{'Add Item'}</button>
			</>;
		}

		render(App);
		expect(container).toMatchSnapshot();

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

		button.click();
		flushSync();

		expect(container).toMatchSnapshot();
	});

	it('correctly handles intermediate statements in for block', () => {
		function App() {
			return <>
				const items = new RippleArray(1, 2, 3);
				<div>
					for (const item of items) {
						<div>
							<div>{item}</div>
							const some_text = item;
							<div>{some_text}</div>
						</div>
					}
				</div>
				<button onClick={() => items.push(items.length + 1)}>{'Add Item'}</button>
			</>;
		}

		render(App);

		expect(container).toMatchSnapshot();

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

		button.click();
		flushSync();

		expect(container).toMatchSnapshot();
	});

	it('correctly handles the index in a for...of loop', () => {
		function App() {
			return <>
				const items = new RippleArray('a', 'b', 'c');
				<div>
					for (let item of items; index i) {
						<div>{i + ' : ' + item}</div>
					}
				</div>
				<button onClick={() => items.push(String.fromCharCode(97 + items.length))}>
					{'Add Item'}
				</button>
				<button onClick={() => items.reverse()}>{'Reverse'}</button>
			</>;
		}

		render(App);

		expect(container).toMatchSnapshot();

		const [button, button2] = container.querySelectorAll('button');

		button.click();
		flushSync();

		expect(container).toMatchSnapshot();

		button2.click();
		flushSync();

		expect(container).toMatchSnapshot();
	});

	it('correctly handles keyed for...of loops', () => {
		function App() {
			return <>
				let &[items] = track([
					{ id: 1, text: 'Item 1' },
					{ id: 2, text: 'Item 2' },
					{ id: 3, text: 'Item 3' },
				]);
				for (let item of items; index i; key item.id) {
					<div>{i + ':' + item.text}</div>
				}
				<button
					onClick={() => {
						items = items.toReversed();
					}}
				>
					{'Reverse'}
				</button>
			</>;
		}

		render(App);

		expect(container).toMatchSnapshot();

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

		button.click();
		flushSync();

		expect(container).toMatchSnapshot();
	});

	it('keyed for over derived updates sibling text nodes', () => {
		function App() {
			return <>
				let &[count] = track(0);
				let &[items] = track(
					() => Array.from({ length: count }).map((_, id) => ({ id, label: `Item ${id}` })),
				);
				<button
					onClick={() => {
						count++;
					}}
				>
					{'Add'}
				</button>
				for (const item of items; key item.id) {
					<div class="item">{item.label}</div>
				}
				<p class="count">{count}</p>
			</>;
		}

		render(App);

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

		expect(container.querySelector('.count').textContent).toBe('0');
		expect(container.querySelectorAll('.item').length).toBe(0);

		button.click();
		flushSync();

		expect(container.querySelector('.count').textContent).toBe('1');
		expect(container.querySelectorAll('.item').length).toBe(1);

		button.click();
		flushSync();

		expect(container.querySelector('.count').textContent).toBe('2');
		expect(container.querySelectorAll('.item').length).toBe(2);
	});

	it('keyed for with 32+ items: full reversal updates values via Map path', () => {
		function App() {
			return <>
				let &[items] = track(Array.from({ length: 40 }, (_, i) => ({ id: i, text: `Item ${i}` })));
				<div>
					for (let item of items; index idx; key item.id) {
						<span class="item">{idx + ':' + item.text}</span>
					}
				</div>
				<button
					onClick={() => {
						items = items.toReversed();
					}}
				>
					{'Reverse'}
				</button>
			</>;
		}

		render(App);

		const getTexts = () => Array.from(container.querySelectorAll('.item')).map(
			(el) => el.textContent,
		);

		expect(getTexts()).toEqual(Array.from({ length: 40 }, (_, i) => `${i}:Item ${i}`));

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

		expect(getTexts()).toEqual(Array.from({ length: 40 }, (_, i) => `${i}:Item ${39 - i}`));
	});

	it('handles updating with new objects with same key', () => {
		function App() {
			return <>
				let &[items] = track([
					{ id: 1, text: 'Item 1' },
					{ id: 2, text: 'Item 2' },
					{ id: 3, text: 'Item 3' },
				]);
				for (let item of items; index i; key item.id) {
					<div>{i + ':' + item.text}</div>
				}
				<button
					onClick={() => {
						items[0].id = 3;
						items[1].id = 2;
						items[2].id = 1;

						items = [
							{ ...items[0], text: 'Item 1!' },
							{ ...items[1], text: 'Item 2!' },
							{ ...items[2], text: 'Item 3!' },
						];
					}}
				>
					{'Reverse'}
				</button>
			</>;
		}

		render(App);
		expect(container).toMatchSnapshot();

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

		expect(container).toMatchSnapshot();
	});

	it('ref-based for with 32+ items: remove from start with shared refs via Map path', () => {
		const objects = Array.from({ length: 50 }, (_, i) => ({ id: i, text: `Obj ${i}` }));

		function App() {
			return <>
				let &[items] = track(objects.slice());
				<div>
					for (const item of items) {
						<span class="item">{item.text}</span>
					}
				</div>
				<button
					onClick={() => {
						items = objects.slice(15).reverse();
					}}
				>
					{'Trim and reverse'}
				</button>
			</>;
		}

		render(App);

		const getTexts = () => Array.from(container.querySelectorAll('.item')).map(
			(el) => el.textContent,
		);

		expect(getTexts()).toEqual(objects.map((o) => o.text));

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

		expect(getTexts()).toEqual(objects.slice(15).reverse().map((o) => o.text));
	});
});
