import { describe, expect, it, vi } from 'vitest';
import { scrollToCommentFromQuery, type DeepLinkScrollOptions } from './deepLinkScroll';

const COMMENT_ID = 'cccc1111-1111-1111-1111-111111111111';
const ANNOTATION_ID = 'aaaa2222-2222-2222-2222-222222222222';

function fakeElement() {
	return {
		scrollIntoView: vi.fn(),
		focus: vi.fn()
	} as unknown as HTMLElement;
}

type Overrides = Partial<DeepLinkScrollOptions>;

function buildOpts(overrides: Overrides = {}): DeepLinkScrollOptions {
	const el = overrides.getElement ? null : fakeElement();
	return {
		transcribeMode: true,
		setTranscribeMode: vi.fn(),
		setPanelMode: vi.fn(),
		loadBlocks: vi.fn().mockResolvedValue(undefined),
		setActiveAnnotationId: vi.fn(),
		flashAnnotation: vi.fn(),
		prefersReducedMotion: false,
		afterTick: vi.fn().mockResolvedValue(undefined),
		getElement: vi.fn().mockReturnValue(el),
		onStripUrl: vi.fn(),
		...overrides
	};
}

describe('scrollToCommentFromQuery', () => {
	it('is a no-op when commentId query param is absent', async () => {
		const url = new URL('https://app/documents/doc-1');
		const opts = buildOpts();

		await scrollToCommentFromQuery(url, opts);

		expect(opts.setActiveAnnotationId).not.toHaveBeenCalled();
		expect(opts.getElement).not.toHaveBeenCalled();
		expect(opts.onStripUrl).not.toHaveBeenCalled();
	});

	it('is a no-op when annotationId query param is absent even if commentId is present', async () => {
		const url = new URL(`https://app/documents/doc-1?commentId=${COMMENT_ID}`);
		const opts = buildOpts();

		await scrollToCommentFromQuery(url, opts);

		expect(opts.setActiveAnnotationId).not.toHaveBeenCalled();
		expect(opts.getElement).not.toHaveBeenCalled();
	});

	it('scrolls to the comment element and focuses it when both params are present', async () => {
		const url = new URL(
			`https://app/documents/doc-1?commentId=${COMMENT_ID}&annotationId=${ANNOTATION_ID}`
		);
		const el = fakeElement();
		const opts = buildOpts({ getElement: vi.fn().mockReturnValue(el) });

		await scrollToCommentFromQuery(url, opts);

		expect(opts.getElement).toHaveBeenCalledWith(`comment-${COMMENT_ID}`);
		expect(el.scrollIntoView).toHaveBeenCalledWith({ behavior: 'smooth', block: 'center' });
		expect(el.focus).toHaveBeenCalledWith({ preventScroll: true });
	});

	it('triggers the annotation flash after scrolling', async () => {
		const url = new URL(
			`https://app/documents/doc-1?commentId=${COMMENT_ID}&annotationId=${ANNOTATION_ID}`
		);
		const el = fakeElement();
		const opts = buildOpts({ getElement: vi.fn().mockReturnValue(el) });

		await scrollToCommentFromQuery(url, opts);

		expect(opts.flashAnnotation).toHaveBeenCalledWith(ANNOTATION_ID);
	});

	it('enters transcribe mode and awaits loadBlocks when transcribe mode is off', async () => {
		const url = new URL(
			`https://app/documents/doc-1?commentId=${COMMENT_ID}&annotationId=${ANNOTATION_ID}`
		);
		const opts = buildOpts({ transcribeMode: false });

		await scrollToCommentFromQuery(url, opts);

		expect(opts.setTranscribeMode).toHaveBeenCalledWith(true);
		expect(opts.loadBlocks).toHaveBeenCalled();
	});

	it('is a graceful no-op when the target element is not in the DOM', async () => {
		const url = new URL(
			`https://app/documents/doc-1?commentId=${COMMENT_ID}&annotationId=${ANNOTATION_ID}`
		);
		const opts = buildOpts({ getElement: vi.fn().mockReturnValue(null) });

		// Must not throw. Flash should not fire — nothing to highlight.
		await expect(scrollToCommentFromQuery(url, opts)).resolves.toBeUndefined();

		expect(opts.flashAnnotation).not.toHaveBeenCalled();
	});

	it('uses behavior "instant" when prefers-reduced-motion is set', async () => {
		const url = new URL(
			`https://app/documents/doc-1?commentId=${COMMENT_ID}&annotationId=${ANNOTATION_ID}`
		);
		const el = fakeElement();
		const opts = buildOpts({
			prefersReducedMotion: true,
			getElement: vi.fn().mockReturnValue(el)
		});

		await scrollToCommentFromQuery(url, opts);

		expect(el.scrollIntoView).toHaveBeenCalledWith({ behavior: 'instant', block: 'center' });
	});

	it('strips both commentId and annotationId from the URL after handling', async () => {
		const url = new URL(
			`https://app/documents/doc-1?commentId=${COMMENT_ID}&annotationId=${ANNOTATION_ID}`
		);
		const opts = buildOpts();

		await scrollToCommentFromQuery(url, opts);

		expect(opts.onStripUrl).toHaveBeenCalled();
	});

	it('forces panel mode to "edit" so the comment DOM exists on reviewed documents', async () => {
		const url = new URL(
			`https://app/documents/doc-1?commentId=${COMMENT_ID}&annotationId=${ANNOTATION_ID}`
		);
		const opts = buildOpts();

		await scrollToCommentFromQuery(url, opts);

		expect(opts.setPanelMode).toHaveBeenCalledWith('edit');
	});

	it('forces panel mode to "edit" even when transcribe mode is already on', async () => {
		const url = new URL(
			`https://app/documents/doc-1?commentId=${COMMENT_ID}&annotationId=${ANNOTATION_ID}`
		);
		const opts = buildOpts({ transcribeMode: true });

		await scrollToCommentFromQuery(url, opts);

		expect(opts.setPanelMode).toHaveBeenCalledWith('edit');
	});
});
