import type { PdfGlyphSlim, PdfPageGeometry } from "@embedpdf/models";
import type { FormattedSelection } from "@embedpdf/plugin-selection";
import { describe, expect, it } from "vitest";
import {
	buildTightSelectionRects,
	tightenFormattedSelection,
} from "@/lib/pdf/selection-appearance";

function glyph(
	x: number,
	y: number,
	width = 8,
	height = 20,
	overrides: Partial<PdfGlyphSlim> = {},
): PdfGlyphSlim {
	return {
		x,
		y,
		width,
		height,
		tightX: x + 1,
		tightY: y + 4,
		tightWidth: width - 2,
		tightHeight: height - 8,
		flags: 0,
		...overrides,
	};
}

describe("buildTightSelectionRects", () => {
	it("uses tight glyph bounds and removes whitespace-only phantom rows", () => {
		const geometry: PdfPageGeometry = {
			runs: [
				{
					charStart: 0,
					fontSize: 12,
					rect: { x: 0, y: 0, width: 120, height: 28 },
					glyphs: [
						glyph(0, 0, 120, 28, {
							flags: 2,
							tightX: undefined,
							tightY: undefined,
							tightWidth: undefined,
							tightHeight: undefined,
						}),
					],
				},
				{
					charStart: 1,
					fontSize: 12,
					rect: { x: 10, y: 30, width: 24, height: 20 },
					glyphs: [glyph(10, 30), glyph(18, 30), glyph(26, 30)],
				},
				{
					charStart: 4,
					fontSize: 12,
					rect: { x: 10, y: 55, width: 16, height: 20 },
					glyphs: [glyph(10, 55), glyph(18, 55)],
				},
			],
		};

		expect(
			buildTightSelectionRects(
				geometry,
				{ start: { page: 0, index: 0 }, end: { page: 0, index: 5 } },
				0,
			),
		).toEqual([
			{ origin: { x: 11, y: 34 }, size: { width: 22, height: 12 } },
			{ origin: { x: 11, y: 59 }, size: { width: 14, height: 12 } },
		]);
	});

	it("stops the final row at the last selected glyph", () => {
		const geometry: PdfPageGeometry = {
			runs: [
				{
					charStart: 0,
					fontSize: 12,
					rect: { x: 10, y: 20, width: 40, height: 20 },
					glyphs: Array.from({ length: 5 }, (_, index) =>
						glyph(10 + index * 8, 20),
					),
				},
			],
		};

		expect(
			buildTightSelectionRects(
				geometry,
				{ start: { page: 0, index: 1 }, end: { page: 0, index: 2 } },
				0,
			),
		).toEqual([{ origin: { x: 19, y: 24 }, size: { width: 14, height: 12 } }]);
	});

	it("bridges normal word spacing but does not fill a distant column", () => {
		const geometry: PdfPageGeometry = {
			runs: [
				{
					charStart: 0,
					fontSize: 12,
					rect: { x: 10, y: 20, width: 24, height: 20 },
					glyphs: [glyph(10, 20), glyph(18, 20), glyph(26, 20)],
				},
				{
					charStart: 3,
					fontSize: 12,
					rect: { x: 38, y: 20, width: 16, height: 20 },
					glyphs: [glyph(38, 20), glyph(46, 20)],
				},
				{
					charStart: 5,
					fontSize: 12,
					rect: { x: 120, y: 20, width: 16, height: 20 },
					glyphs: [glyph(120, 20), glyph(128, 20)],
				},
			],
		};

		expect(
			buildTightSelectionRects(
				geometry,
				{ start: { page: 0, index: 0 }, end: { page: 0, index: 6 } },
				0,
			),
		).toEqual([
			{ origin: { x: 11, y: 24 }, size: { width: 42, height: 12 } },
			{ origin: { x: 121, y: 24 }, size: { width: 14, height: 12 } },
		]);
	});

	it("does not merge backward fragments that have large negative gaps", () => {
		const geometry: PdfPageGeometry = {
			runs: [
				{
					charStart: 0,
					fontSize: 12,
					rect: { x: 150, y: 20, width: 24, height: 20 },
					glyphs: [glyph(150, 20), glyph(158, 20)],
				},
				{
					charStart: 2,
					fontSize: 12,
					rect: { x: 20, y: 20, width: 24, height: 20 },
					glyphs: [glyph(20, 20), glyph(28, 20)],
				},
			],
		};

		expect(
			buildTightSelectionRects(
				geometry,
				{ start: { page: 0, index: 0 }, end: { page: 0, index: 3 } },
				0,
			),
		).toEqual([
			{ origin: { x: 151, y: 24 }, size: { width: 14, height: 12 } },
			{ origin: { x: 21, y: 24 }, size: { width: 14, height: 12 } },
		]);
	});
});

describe("tightenFormattedSelection", () => {
	it("tightens loose formatted selection bounds and updates bounding rect", () => {
		const rawPages: FormattedSelection[] = [
			{
				pageIndex: 0,
				rect: { origin: { x: 0, y: 0 }, size: { width: 200, height: 100 } },
				segmentRects: [
					{ origin: { x: 0, y: 0 }, size: { width: 200, height: 100 } },
				],
			},
		];
		const geometry: PdfPageGeometry = {
			runs: [
				{
					charStart: 0,
					fontSize: 12,
					rect: { x: 10, y: 20, width: 40, height: 20 },
					glyphs: [glyph(10, 20), glyph(18, 20)],
				},
			],
		};

		const tightened = tightenFormattedSelection(
			rawPages,
			{ 0: geometry },
			{ start: { page: 0, index: 0 }, end: { page: 0, index: 1 } },
		);

		expect(tightened).toEqual([
			{
				pageIndex: 0,
				rect: { origin: { x: 11, y: 24 }, size: { width: 14, height: 12 } },
				segmentRects: [
					{ origin: { x: 11, y: 24 }, size: { width: 14, height: 12 } },
				],
			},
		]);
	});

	it("preserves raw pages when geometry is unavailable", () => {
		const rawPages: FormattedSelection[] = [
			{
				pageIndex: 0,
				rect: { origin: { x: 0, y: 0 }, size: { width: 100, height: 50 } },
				segmentRects: [
					{ origin: { x: 0, y: 0 }, size: { width: 100, height: 50 } },
				],
			},
		];
		expect(
			tightenFormattedSelection(rawPages, undefined, {
				start: { page: 0, index: 0 },
				end: { page: 0, index: 1 },
			}),
		).toEqual(rawPages);
	});
});