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

describe('CSS :global additional use cases', () => {
	it('handles :global as modifier with dot notation', () => {
		const source = `
export function Test() @{
	<>
		<div class="x">{'content'}</div>

		<style>
			div :global.x {
				color: red;
			}
		</style>
	</>
}`;
		const { css } = compile(source, 'test.tsrx');

		expect(css).toMatch(/div\.tsrx-[a-z0-9]+\.x {/);
	});

	it('handles :global modifier in nested syntax', () => {
		const source = `
export function Test() @{
	<>
		<div class="x">{'content'}</div>

		<style>
			div {
				:global.x {
					color: green;
				}
			}
		</style>
	</>
}`;
		const { css } = compile(source, 'test.tsrx');

		expect(css).toContain('&.x {');
	});

	it('handles :global with & nesting selector inside block', () => {
		const source = `
export function Test() @{
	<>
		<div class="x">{'content'}</div>

		<style>
			div {
				& :global.x {
					color: green;
				}
			}
		</style>
	</>
}`;
		const { css } = compile(source, 'test.tsrx');

		expect(css).toContain('&.x {');
	});

	it('handles :global with :is pseudo-class modifier', () => {
		const source = `
export function Test() @{
	<>
		<div>{'content'}</div>

		<style>
			div :global:is(html.dark-mode *) {
				color: green;
			}
		</style>
	</>
}`;
		const { css } = compile(source, 'test.tsrx');

		expect(css).toMatch(/div\.tsrx-[a-z0-9]+:is\(html\.dark-mode \*\) {/);
	});

	it('handles :global with & inside :global block', () => {
		const source = `
export function Test() @{
	<>
		<div class="class">{'content'}</div>

		<style>
			div {
				&:global(.class) {
					color: green;
				}
			}
		</style>
	</>
}`;
		const { css } = compile(source, 'test.tsrx');

		expect(css).toMatch(/div\.tsrx-[a-z0-9]+ {/);
		expect(css).toContain('&.class {');
	});

	it('handles :global(*) with & and descendant', () => {
		const source = `
export function Test() @{
	<>
		<div class="class">{'content'}</div>

		<style>
			:global(*) {
				&:hover .class {
					color: green;
				}
			}
		</style>
	</>
}`;
		const { css } = compile(source, 'test.tsrx');

		expect(css).toContain('* {');
		expect(css).toMatch(/&:hover \.class\.tsrx-[a-z0-9]+ {/);
	});

	it('handles multiple :global selectors in list', () => {
		const source = `
export function Test() @{
	<>
		<x>{'content'}</x>
		<y>{'content'}</y>

		<style>
			:global x, :global y {
				color: green;
			}
		</style>
	</>
}`;
		const { css } = compile(source, 'test.tsrx');

		expect(css).toContain(' x,  y {');
	});

	it('handles :global block with nested selector list', () => {
		const source = `
export function Test() @{
	<>
		<div>
			<y>{'content'}</y>
		</div>

		<style>
			div :global, div :global y, unused :global {
				z {
					color: green;
				}
			}
		</style>
	</>
}`;
		const { css } = compile(source, 'test.tsrx');

		expect(css).toMatch(/div\.tsrx-[a-z0-9]+/);
		expect(css).toContain('z {');
	});

	it('handles keyframes inside :global block', () => {
		const source = `
export function Test() @{
	<>
		<div class="x">{'animated'}</div>

		<style>
			:global {
				.x {
					animation: test 1s;
				}

				.y {
					animation: test-in 1s;
				}

				@keyframes test-in {
					to {
						opacity: 1;
					}
				}
			}

			@keyframes test {
				to {
					opacity: 1;
				}
			}
		</style>
	</>
}`;
		const { css } = compile(source, 'test.tsrx');

		expect(css).toContain('@keyframes test-in {');
		expect(css).toContain('.x {');
		expect(css).toContain('.y {');
		expect(css).toMatch(/@keyframes tsrx-[a-z0-9]+-test/);
		expect(css).not.toMatch(/\.x\.tsrx-[a-z0-9]+/);
	});

	it('handles global keyframes with no component elements', () => {
		const source = `
export function Test() @{
	<>
		<div>{'content'}</div>

		<style>
			@keyframes -global-orphan {
				0% { color: red; }
				100% { color: blue; }
			}
		</style>
	</>
}`;
		const { css } = compile(source, 'test.tsrx');

		expect(css).toContain('@keyframes orphan');
		expect(css).not.toContain('-global-orphan');
	});

	it('handles :global with class modifier syntax', () => {
		const source = `
export function Test() @{
	<>
		<div class="blue">{'might be programmatically added'}</div>
		<span class="x blue">{'span content'}</span>

		<style>
			div:global(.blue) {
				color: blue;
			}
			span:global(.blue).x {
				color: blue;
			}
			span.x:global(.bg) {
				background: red;
			}
		</style>
	</>
}`;
		const { css } = compile(source, 'test.tsrx');

		expect(css).toMatch(/div\.tsrx-[a-z0-9]+\.blue {/);
		expect(css).toMatch(/span\.blue\.x\.tsrx-[a-z0-9]+ {/);
		expect(css).toMatch(/span\.x\.tsrx-[a-z0-9]+\.bg {/);
	});

	it('handles multiple :global() in descendant sequence', () => {
		const source = `
export function Test() @{
	<>
		<p>{'this may or may not be styled'}</p>

		<style>
			:global(div) > :global(section) > p {
				color: red;
			}
		</style>
	</>
}`;
		const { css } = compile(source, 'test.tsrx');

		expect(css).toContain('div > section > p');
		expect(css).toMatch(/p\.tsrx-[a-z0-9]+ {/);
	});

	it('handles :is with :global and html context', () => {
		const source = `
export function Test() @{
	<>
		<x>
			<y>
				<z>{'content'}</z>
			</y>
		</x>

		<style>
			x :is(:global(html *)) {
				color: green;
			}
		</style>
	</>
}`;
		const { css } = compile(source, 'test.tsrx');

		expect(css).toMatch(/x\.tsrx-[a-z0-9]+ :is\(html \*\) {/);
	});

	it('handles :global block with :has inside', () => {
		const source = `
export function Test() @{
	<>
		<div>
			<x>{'content'}</x>
		</div>

		<style>
			:global(.foo) {
				:has(x) {
					color: green;
				}
				&:has(x) {
					color: green;
				}
			}

			:global(.foo):has(x) {
				color: green;
			}
		</style>
	</>
}`;
		const { css } = compile(source, 'test.tsrx');

		expect(css).toContain('.foo {');
		expect(css).toMatch(/\.tsrx-[a-z0-9]+:has\(x:where\(\.tsrx-[a-z0-9]+\)\) {/);
		expect(css).toMatch(/&:has\(x\.tsrx-[a-z0-9]+\) {/);
		expect(css).toMatch(/.foo:has\(x\.tsrx-[a-z0-9]+\) {/);
	});

	it('handles :not with :global in complex nesting', () => {
		const source = `
export function Test() @{
	<>
		<p class="foo">{'foo'}</p>
		<p class="bar">
			{'bar'}
			<span>{'baz'}</span>
		</p>
		<span>{'buzz'}</span>

		<style>
			:not(:global(.foo)) {
				color: green;
			}
			:not(.foo):not(:global(.unused)) {
				color: green;
			}
			:global(.x):not(.foo) {
				color: green;
			}
			:global(.x) :not(p) {
				color: green;
			}
			:global(.x):not(p) {
				color: green;
			}

			:global(span):not(p span) {
				color: green;
			}
			span:not(:global(p span)) {
				color: green;
			}
			:global(span:not(p span)) {
				color: green;
			}

			:global(.x) {
				:not(.foo) {
					color: green;
				}
				&:not(.foo) {
					color: green;
				}
			}
		</style>
	</>
}`;
		const { css } = compile(source, 'test.tsrx');

		expect(css).toContain(':not(.foo)');
		expect(css).toContain('span');
	});

	it('handles sibling combinators with children and :global before scoped elements', () => {
		const source = `
export function Test({ children }) @{
	<>
		<div>
			<p class="before">{'before'}</p>

			{children}

			<p class="foo">
				<span>{'foo'}</span>
			</p>
			<p class="bar">{'bar'}</p>
		</div>

		<style>
			.before + .foo { color: green; }
			.before ~ .foo { color: green; }
			.before ~ .bar { color: green; }

			:global(.x) + .foo { color: green; }
			:global(.x) + .foo span { color: green; }
			:global(.x) ~ .foo { color: green; }
			:global(.x) ~ .foo span { color: green; }
			:global(.x) ~ .bar { color: green; }

			/* should be unused as this is not a possibility */
			:global(.x) + .bar { color: green; }
		</style>
	</>
}`;
		const { css } = compile(source, 'test.tsrx');

		expect(css).toMatch(/\.before\.tsrx-[a-z0-9]+ \+ \.foo:where\(\.tsrx-[a-z0-9]+\) {/);
		expect((css.match(/\.x\ /g) || []).length).toBe(0);
		expect((css.match(/\(unused\) :global\(\.x\) /g) || []).length).toBe(6);
		expect(css).toContain('(unused) :global(.x) + .bar {');
	});

	it('handles sibling combinators with component and :global before scoped elements', () => {
		const source = `
export function Test({ children }) @{
	<>
		<div>
			<p class="before">{'before'}</p>

			<Child1 />

			<p class="foo">
				<span>{'foo'}</span>
			</p>
			<p class="bar">{'bar'}</p>
		</div>

		<style>
			.before + .foo { color: green; }
			.before ~ .foo { color: green; }
			.before ~ .bar { color: green; }

			:global(.x) + .foo { color: green; }
			:global(.x) + .foo span { color: green; }
			:global(.x) ~ .foo { color: green; }
			:global(.x) ~ .foo span { color: green; }
			:global(.x) ~ .bar { color: green; }

			/* should be unused as this is not a possibility */
			:global(.x) + .bar { color: green; }
		</style>
	</>
}

function Child1() @{
	<div>{'child1'}</div>
}`;
		const { css } = compile(source, 'test.tsrx');

		expect(css).toMatch(/\.before\.tsrx-[a-z0-9]+ \+ \.foo:where\(\.tsrx-[a-z0-9]+\) {/);
		expect((css.match(/\.x\ /g) || []).length).toBe(5);
		expect((css.match(/\(unused\) :global\(\.x\) /g) || []).length).toBe(1);
		expect(css).toContain('(unused) :global(.x) + .bar {');
	});

	it(
		'handles sibling combinators with dynamic component and :global before scoped elements',
		() => {
			const source = `
import { track } from 'ripple';

export function Test({ children }) @{
	const DynamicComponent = track(() => Child1);
	<>
		<div>
			<p class="before">{'before'}</p>

			<{DynamicComponent} />

			<p class="foo">
				<span>{'foo'}</span>
			</p>
			<p class="bar">{'bar'}</p>
		</div>

		<style>
			.before + .foo { color: green; }
			.before ~ .foo { color: green; }
			.before ~ .bar { color: green; }

			:global(.x) + .foo { color: green; }
			:global(.x) + .foo span { color: green; }
			:global(.x) ~ .foo { color: green; }
			:global(.x) ~ .foo span { color: green; }
			:global(.x) ~ .bar { color: green; }

			/* should be unused as this is not a possibility */
			:global(.x) + .bar { color: green; }
		</style>
	</>
}

function Child1() @{
	<div>{'child1'}</div>
}`;
			const { css } = compile(source, 'test.tsrx');

			expect(css).toMatch(/\.before\.tsrx-[a-z0-9]+ \+ \.foo:where\(\.tsrx-[a-z0-9]+\) {/);
			expect((css.match(/\.x\ /g) || []).length).toBe(5);
			expect((css.match(/\(unused\) :global\(\.x\) /g) || []).length).toBe(1);
			expect(css).toContain('(unused) :global(.x) + .bar {');
		},
	);

	it(
		'handles sibling combinators with dynamic element or regular element and :global before scoped elements',
		() => {
			const source = `
import { track } from 'ripple';

export function Test({ children, classes }) @{
	const dynamicElement = track('div');
	<>
		<div>
			<p class="before">{'before'}</p>
			// Use Dynamic Element but it's the same with a regular one
			<{dynamicElement}  class={classes} />

			<p class="foo">
				<span>{'foo'}</span>
			</p>
			<p class="bar">{'bar'}</p>
		</div>

		<style>
			.before + .foo { color: green; }
			.before ~ .foo { color: green; }
			.before ~ .bar { color: green; }

			:global(.x) + .foo { color: green; }
			:global(.x) + .foo span { color: green; }
			:global(.x) ~ .foo { color: green; }
			:global(.x) ~ .foo span { color: green; }
			:global(.x) ~ .bar { color: green; }

			/* should be unused as this is not a possibility */
			:global(.x) + .bar { color: green; }
		</style>
	</>
}`;

			const { css } = compile(source, 'test.tsrx');

			expect(css).toMatch(/\.before\.tsrx-[a-z0-9]+ \+ \.foo:where\(\.tsrx-[a-z0-9]+\) {/);
			expect((css.match(/\.x\ /g) || []).length).toBe(5);
			expect((css.match(/\(unused\) :global\(\.x\) /g) || []).length).toBe(1);
			expect(css).toContain('(unused) :global(.x) + .bar {');
		},
	);

	it('handles :global with multiple global descendants', () => {
		const source = `
export function Test() @{
	<>
		<div class="root">
			<section class="whatever">
				<p>{'hello'}</p>
			</section>
		</div>

		<style>
			:global(html) :global(body) .root p {
				color: red;
			}
		</style>
	</>
}`;
		const { css } = compile(source, 'test.tsrx');

		expect(css).toMatch(/html body \.root\.tsrx-[a-z0-9]+ p:where\(\.tsrx-[a-z0-9]+\) {/);
	});

	it('handles nested @media with :global blocks', () => {
		const source = `
export function Test() @{
	<>
		<div>{'content'}</div>

		<style>
			div {
				color: black;

				@media (min-width: 768px) {
					:global {
						.foo {
							color: red;
						}
					}

					color: blue;
				}
			}
		</style>
	</>
}`;
		const { css } = compile(source, 'test.tsrx');

		expect(css).toContain('@media (min-width: 768px) {');
		expect(css).toContain('.foo {');
		expect(css).not.toMatch(/\.foo\.tsrx-[a-z0-9]+/);
	});

	it('handles :has with complex combinators', () => {
		const source = `
export function Test() @{
	<>
		<g>
			<h>
				<i>{'content'}</i>
			</h>
			<j>
				<k>{'content'}</k>
			</j>
		</g>

		<style>
			g:has(> h > i) {
				color: green;
			}
			h:has(> h > i) {
				color: red;
			}
			g:has(+ j > k) {
				color: green;
			}
		</style>
	</>
}`;
		const { css } = compile(source, 'test.tsrx');

		expect(css).toMatch(
			/g\.tsrx-[a-z0-9]+:has\(> h:where\(\.tsrx-[a-z0-9]+\) > i:where\(\.tsrx-[a-z0-9]+\)\)/,
		);
		expect(css).toContain('(unused) h:has(> h > i)');
	});

	it('handles :global with attribute selectors containing special characters', () => {
		const source = `
export function Test() @{
	<>
		<div>
			<h1 data-title="Hello, world!">{'hello world'}</h1>
		</div>

		<style>
			div :global(h1[data-title="Hello, world!"]) {
				color: red;
			}
			div :global(h1[attribute], video[autoplay]) {
				color: red;
			}
		</style>
	</>
}`;
		const { css } = compile(source, 'test.tsrx');

		expect(css).toMatch(/div\.tsrx-[a-z0-9]+ h1\[data-title="Hello, world!"\]/);
		expect(css).toContain('h1[attribute], video[autoplay]');
	});

	it('handles escaped commas in :global class names', () => {
		const source = `
export function Test() @{
	<>
		<div>
			<h1 class="h1,h2,h3">{'hello world'}</h1>
		</div>

		<style>
			div :global(.h1\\,h2\\,h3) {
				color: red;
			}
		</style>
	</>
}`;
		const { css } = compile(source, 'test.tsrx');

		expect(css).toMatch(/div\.tsrx-[a-z0-9]+ \.h1\\,h2\\,h3 {/);
	});

	/**
 * :global WITH :is/:where CONTAINING MULTIPLE SELECTORS
 */
	it('handles :global with :is containing multiple selectors', () => {
		const source = `
export function Test() @{
	<>
		<div>
			<h1>{'hello world'}</h1>
			<h2>{'subtitle'}</h2>
		</div>

		<style>
			div :global(:is(h1, h2)) {
				color: red;
			}
			div :global(:where(h1, h2)) {
				color: red;
			}
		</style>
	</>
}`;
		const { css } = compile(source, 'test.tsrx');

		expect(css).toMatch(/div\.tsrx-[a-z0-9]+ :is\(h1, h2\)/);
		expect(css).toMatch(/div\.tsrx-[a-z0-9]+ :where\(h1, h2\)/);
	});

	it('handles :global with :is containing compound selectors', () => {
		const source = `
export function Test() @{
	<>
		<div>
			<h1>{'hello world'}</h1>
			<h2>{'subtitle'}</h2>
			<h3>{'sub-subtitle'}</h3>
		</div>

		<style>
			div :global(h1 ~ :is(h2, h3)) {
				color: red;
			}
		</style>
	</>
}`;
		const { css } = compile(source, 'test.tsrx');

		expect(css).toMatch(/div\.tsrx-[a-z0-9]+ h1 ~ :is\(h2, h3\)/);
	});

	it('handles :global with pseudo-elements', () => {
		const source = `
export function Test() @{
	<>
		<div>
			<h1 class="foo">{'hello world'}</h1>
		</div>

		<style>
			.foo :global(.bar)::after {
				color: red;
			}
			.foo :global(.bar)::after .baz {
				color: red;
			}
		</style>
	</>
}`;
		expect(() => compile(source, 'test.tsrx')).toThrow(
			':global(...) can be at the start or end of a selector sequence, but not in the middle',
		);
	});
});
