import { DEFAULT_COLLAPSED_CONTEXT_THRESHOLD } from "../constants.js";
import { iterateOverDiff } from "../utils/iterateOverDiff.js";
import { parseDiffFromFile } from "../utils/parseDiffFromFile.js";
import { FileDiff } from "./FileDiff.js";
import { resolveVirtualFileMetrics } from "../utils/resolveVirtualFileMetrics.js";

//#region src/components/VirtualizedFileDiff.ts
let instanceId = -1;
var VirtualizedFileDiff = class extends FileDiff {
	__id = `little-virtualized-file-diff:${++instanceId}`;
	top;
	height = 0;
	metrics;
	heightCache = /* @__PURE__ */ new Map();
	isVisible = false;
	virtualizer;
	constructor(options, virtualizer, metrics, workerManager, isContainerManaged = false) {
		super(options, workerManager, isContainerManaged);
		const { hunkSeparators = "line-info" } = this.options;
		this.virtualizer = virtualizer;
		this.metrics = resolveVirtualFileMetrics(typeof hunkSeparators === "function" ? "custom" : hunkSeparators, metrics);
	}
	getLineHeight(lineIndex, hasMetadataLine = false) {
		const cached = this.heightCache.get(lineIndex);
		if (cached != null) return cached;
		const multiplier = hasMetadataLine ? 2 : 1;
		return this.metrics.lineHeight * multiplier;
	}
	setOptions(options) {
		if (options == null) return;
		const previousDiffStyle = this.options.diffStyle;
		const previousOverflow = this.options.overflow;
		super.setOptions(options);
		if (previousDiffStyle !== this.options.diffStyle || previousOverflow !== this.options.overflow) {
			this.heightCache.clear();
			this.computeApproximateSize();
			this.renderRange = void 0;
		}
		this.virtualizer.instanceChanged(this);
	}
	reconcileHeights() {
		const { overflow = "scroll" } = this.options;
		if (this.fileContainer != null) this.top = this.virtualizer.getOffsetInScrollContainer(this.fileContainer);
		if (this.fileContainer == null || this.fileDiff == null) {
			this.height = 0;
			return;
		}
		if (overflow === "scroll" && this.lineAnnotations.length === 0 && !this.virtualizer.config.resizeDebugging) return;
		const diffStyle = this.getDiffStyle();
		let hasLineHeightChange = false;
		const codeGroups = diffStyle === "split" ? [this.codeDeletions, this.codeAdditions] : [this.codeUnified];
		for (const codeGroup of codeGroups) {
			if (codeGroup == null) continue;
			const content = codeGroup.children[1];
			if (!(content instanceof HTMLElement)) continue;
			for (const line of content.children) {
				if (!(line instanceof HTMLElement)) continue;
				const lineIndexAttr = line.dataset.lineIndex;
				if (lineIndexAttr == null) continue;
				const lineIndex = parseLineIndex(lineIndexAttr, diffStyle);
				let measuredHeight = line.getBoundingClientRect().height;
				let hasMetadata = false;
				if (line.nextElementSibling instanceof HTMLElement && ("lineAnnotation" in line.nextElementSibling.dataset || "noNewline" in line.nextElementSibling.dataset)) {
					if ("noNewline" in line.nextElementSibling.dataset) hasMetadata = true;
					measuredHeight += line.nextElementSibling.getBoundingClientRect().height;
				}
				const expectedHeight = this.getLineHeight(lineIndex, hasMetadata);
				if (measuredHeight === expectedHeight) continue;
				hasLineHeightChange = true;
				if (measuredHeight === this.metrics.lineHeight * (hasMetadata ? 2 : 1)) this.heightCache.delete(lineIndex);
				else this.heightCache.set(lineIndex, measuredHeight);
			}
		}
		if (hasLineHeightChange || this.virtualizer.config.resizeDebugging) this.computeApproximateSize();
	}
	onRender = (dirty) => {
		if (this.fileContainer == null) return false;
		if (dirty) this.top = this.virtualizer.getOffsetInScrollContainer(this.fileContainer);
		return this.render();
	};
	cleanUp() {
		if (this.fileContainer != null) this.virtualizer.disconnect(this.fileContainer);
		super.cleanUp();
	}
	expandHunk(hunkIndex, direction) {
		this.hunksRenderer.expandHunk(hunkIndex, direction);
		this.computeApproximateSize();
		this.renderRange = void 0;
		this.virtualizer.instanceChanged(this);
	}
	setVisibility(visible) {
		if (this.fileContainer == null) return;
		this.renderRange = void 0;
		if (visible && !this.isVisible) {
			this.top = this.virtualizer.getOffsetInScrollContainer(this.fileContainer);
			this.isVisible = true;
		} else if (!visible && this.isVisible) {
			this.isVisible = false;
			this.rerender();
		}
	}
	computeApproximateSize() {
		const isFirstCompute = this.height === 0;
		this.height = 0;
		if (this.fileDiff == null) return;
		const { disableFileHeader = false, expandUnchanged = false, collapsedContextThreshold = DEFAULT_COLLAPSED_CONTEXT_THRESHOLD, hunkSeparators = "line-info" } = this.options;
		const { diffHeaderHeight, fileGap, hunkSeparatorHeight } = this.metrics;
		const diffStyle = this.getDiffStyle();
		const separatorGap = hunkSeparators !== "simple" && hunkSeparators !== "metadata" && hunkSeparators !== "line-info-basic" ? fileGap : 0;
		if (!disableFileHeader) this.height += diffHeaderHeight;
		else if (hunkSeparators !== "simple" && hunkSeparators !== "metadata") this.height += fileGap;
		iterateOverDiff({
			diff: this.fileDiff,
			diffStyle,
			expandedHunks: expandUnchanged ? true : this.hunksRenderer.getExpandedHunksMap(),
			collapsedContextThreshold,
			callback: ({ hunkIndex, collapsedBefore, collapsedAfter, deletionLine, additionLine }) => {
				const splitLineIndex = additionLine != null ? additionLine.splitLineIndex : deletionLine.splitLineIndex;
				const unifiedLineIndex = additionLine != null ? additionLine.unifiedLineIndex : deletionLine.unifiedLineIndex;
				const hasMetadata = (additionLine?.noEOFCR ?? false) || (deletionLine?.noEOFCR ?? false);
				if (collapsedBefore > 0) {
					if (hunkIndex > 0) this.height += separatorGap;
					this.height += hunkSeparatorHeight + separatorGap;
				}
				this.height += this.getLineHeight(diffStyle === "split" ? splitLineIndex : unifiedLineIndex, hasMetadata);
				if (collapsedAfter > 0 && hunkSeparators !== "simple") this.height += separatorGap + hunkSeparatorHeight;
			}
		});
		if (this.fileDiff.hunks.length > 0) this.height += fileGap;
		if (this.fileContainer != null && this.virtualizer.config.resizeDebugging && !isFirstCompute) {
			const rect = this.fileContainer.getBoundingClientRect();
			if (rect.height !== this.height) console.log("VirtualizedFileDiff.computeApproximateSize: computed height doesnt match", {
				name: this.fileDiff.name,
				elementHeight: rect.height,
				computedHeight: this.height
			});
			else console.log("VirtualizedFileDiff.computeApproximateSize: computed height IS CORRECT");
		}
	}
	render({ fileContainer, oldFile, newFile, fileDiff,...props } = {}) {
		const isFirstRender = this.fileContainer == null;
		this.fileDiff ??= fileDiff ?? (oldFile != null && newFile != null ? parseDiffFromFile(oldFile, newFile) : void 0);
		fileContainer = this.getOrCreateFileContainer(fileContainer);
		if (this.fileDiff == null) {
			console.error("VirtualizedFileDiff.render: attempting to virtually render when we dont have the correct data");
			return false;
		}
		if (isFirstRender) {
			this.computeApproximateSize();
			this.virtualizer.connect(fileContainer, this);
			this.top ??= this.virtualizer.getOffsetInScrollContainer(fileContainer);
			this.isVisible = this.virtualizer.isInstanceVisible(this.top, this.height);
		} else this.top ??= this.virtualizer.getOffsetInScrollContainer(fileContainer);
		if (!this.isVisible) return this.renderPlaceholder(this.height);
		const windowSpecs = this.virtualizer.getWindowSpecs();
		const renderRange = this.computeRenderRangeFromWindow(this.fileDiff, this.top, windowSpecs);
		return super.render({
			fileDiff: this.fileDiff,
			fileContainer,
			renderRange,
			oldFile,
			newFile,
			...props
		});
	}
	getDiffStyle() {
		return this.options.diffStyle ?? "split";
	}
	getExpandedRegion(isPartial, hunkIndex, rangeSize) {
		if (rangeSize <= 0 || isPartial) return {
			fromStart: 0,
			fromEnd: 0,
			collapsedLines: Math.max(rangeSize, 0),
			renderAll: false
		};
		const { expandUnchanged = false, collapsedContextThreshold = DEFAULT_COLLAPSED_CONTEXT_THRESHOLD } = this.options;
		if (expandUnchanged || rangeSize <= collapsedContextThreshold) return {
			fromStart: rangeSize,
			fromEnd: 0,
			collapsedLines: 0,
			renderAll: true
		};
		const region = this.hunksRenderer.getExpandedHunk(hunkIndex);
		const fromStart = Math.min(Math.max(region.fromStart, 0), rangeSize);
		const fromEnd = Math.min(Math.max(region.fromEnd, 0), rangeSize);
		const expandedCount = fromStart + fromEnd;
		const renderAll = expandedCount >= rangeSize;
		return {
			fromStart,
			fromEnd,
			collapsedLines: Math.max(rangeSize - expandedCount, 0),
			renderAll
		};
	}
	getExpandedLineCount(fileDiff, diffStyle) {
		let count = 0;
		if (fileDiff.isPartial) {
			for (const hunk of fileDiff.hunks) count += diffStyle === "split" ? hunk.splitLineCount : hunk.unifiedLineCount;
			return count;
		}
		for (const [hunkIndex, hunk] of fileDiff.hunks.entries()) {
			const hunkCount = diffStyle === "split" ? hunk.splitLineCount : hunk.unifiedLineCount;
			count += hunkCount;
			const collapsedBefore = Math.max(hunk.collapsedBefore, 0);
			const { fromStart, fromEnd, renderAll } = this.getExpandedRegion(fileDiff.isPartial, hunkIndex, collapsedBefore);
			if (collapsedBefore > 0) count += renderAll ? collapsedBefore : fromStart + fromEnd;
		}
		const lastHunk = fileDiff.hunks.at(-1);
		if (lastHunk != null && hasFinalHunk(fileDiff)) {
			const additionRemaining = fileDiff.additionLines.length - (lastHunk.additionLineIndex + lastHunk.additionCount);
			const deletionRemaining = fileDiff.deletionLines.length - (lastHunk.deletionLineIndex + lastHunk.deletionCount);
			if (lastHunk != null && additionRemaining !== deletionRemaining) throw new Error(`VirtualizedFileDiff: trailing context mismatch (additions=${additionRemaining}, deletions=${deletionRemaining}) for ${fileDiff.name}`);
			const trailingRangeSize = Math.min(additionRemaining, deletionRemaining);
			if (lastHunk != null && trailingRangeSize > 0) {
				const { fromStart, renderAll } = this.getExpandedRegion(fileDiff.isPartial, fileDiff.hunks.length, trailingRangeSize);
				count += renderAll ? trailingRangeSize : fromStart;
			}
		}
		return count;
	}
	computeRenderRangeFromWindow(fileDiff, fileTop, { top, bottom }) {
		const { disableFileHeader = false, expandUnchanged = false, collapsedContextThreshold = DEFAULT_COLLAPSED_CONTEXT_THRESHOLD, hunkSeparators = "line-info" } = this.options;
		const { diffHeaderHeight, fileGap, hunkLineCount, hunkSeparatorHeight, lineHeight } = this.metrics;
		const diffStyle = this.getDiffStyle();
		const fileHeight = this.height;
		const lineCount = this.getExpandedLineCount(fileDiff, diffStyle);
		const headerRegion = disableFileHeader ? fileGap : diffHeaderHeight;
		if (fileTop < top - fileHeight || fileTop > bottom) return {
			startingLine: 0,
			totalLines: 0,
			bufferBefore: 0,
			bufferAfter: fileHeight - headerRegion - fileGap
		};
		if (lineCount <= hunkLineCount || fileDiff.hunks.length === 0) return {
			startingLine: 0,
			totalLines: hunkLineCount,
			bufferBefore: 0,
			bufferAfter: 0
		};
		const estimatedTargetLines = Math.ceil(Math.max(bottom - top, 0) / lineHeight);
		const totalLines = Math.ceil(estimatedTargetLines / hunkLineCount) * hunkLineCount + hunkLineCount;
		const totalHunks = totalLines / hunkLineCount;
		const overflowHunks = totalHunks;
		const hunkOffsets = [];
		const viewportCenter = (top + bottom) / 2;
		const separatorGap = hunkSeparators === "simple" || hunkSeparators === "metadata" || hunkSeparators === "line-info-basic" ? 0 : fileGap;
		let absoluteLineTop = fileTop + headerRegion;
		let currentLine = 0;
		let firstVisibleHunk;
		let centerHunk;
		let overflowCounter;
		iterateOverDiff({
			diff: fileDiff,
			diffStyle,
			expandedHunks: expandUnchanged ? true : this.hunksRenderer.getExpandedHunksMap(),
			collapsedContextThreshold,
			callback: ({ hunkIndex, collapsedBefore, collapsedAfter, deletionLine, additionLine }) => {
				const splitLineIndex = additionLine != null ? additionLine.splitLineIndex : deletionLine.splitLineIndex;
				const unifiedLineIndex = additionLine != null ? additionLine.unifiedLineIndex : deletionLine.unifiedLineIndex;
				const hasMetadata = (additionLine?.noEOFCR ?? false) || (deletionLine?.noEOFCR ?? false);
				let gapAdjustment = collapsedBefore > 0 ? hunkSeparatorHeight + separatorGap + (hunkIndex > 0 ? separatorGap : 0) : 0;
				if (hunkIndex === 0 && hunkSeparators === "simple") gapAdjustment = 0;
				absoluteLineTop += gapAdjustment;
				const isAtHunkBoundary = currentLine % hunkLineCount === 0;
				if (isAtHunkBoundary) {
					hunkOffsets.push(absoluteLineTop - (fileTop + headerRegion + gapAdjustment));
					if (overflowCounter != null) {
						if (overflowCounter <= 0) return true;
						overflowCounter--;
					}
				}
				const lineHeight$1 = this.getLineHeight(diffStyle === "split" ? splitLineIndex : unifiedLineIndex, hasMetadata);
				const currentHunk = Math.floor(currentLine / hunkLineCount);
				if (absoluteLineTop > top - lineHeight$1 && absoluteLineTop < bottom) firstVisibleHunk ??= currentHunk;
				if (centerHunk == null && absoluteLineTop + lineHeight$1 > viewportCenter) centerHunk = currentHunk;
				if (overflowCounter == null && absoluteLineTop >= bottom && isAtHunkBoundary) overflowCounter = overflowHunks;
				currentLine++;
				absoluteLineTop += lineHeight$1;
				if (collapsedAfter > 0 && hunkSeparators !== "simple") absoluteLineTop += hunkSeparatorHeight + separatorGap;
				return false;
			}
		});
		if (firstVisibleHunk == null) return {
			startingLine: 0,
			totalLines: 0,
			bufferBefore: 0,
			bufferAfter: fileHeight - headerRegion - fileGap
		};
		const collectedHunks = hunkOffsets.length;
		centerHunk ??= firstVisibleHunk;
		const idealStartHunk = Math.round(centerHunk - totalHunks / 2);
		const maxStartHunk = Math.max(0, collectedHunks - totalHunks);
		const startHunk = Math.max(0, Math.min(idealStartHunk, maxStartHunk));
		const startingLine = startHunk * hunkLineCount;
		const clampedTotalLines = idealStartHunk < 0 ? totalLines + idealStartHunk * hunkLineCount : totalLines;
		const bufferBefore = hunkOffsets[startHunk] ?? 0;
		const finalHunkIndex = startHunk + clampedTotalLines / hunkLineCount;
		return {
			startingLine,
			totalLines: clampedTotalLines,
			bufferBefore,
			bufferAfter: finalHunkIndex < hunkOffsets.length ? fileHeight - headerRegion - hunkOffsets[finalHunkIndex] - fileGap : fileHeight - (absoluteLineTop - fileTop) - fileGap
		};
	}
};
function hasFinalHunk(fileDiff) {
	const lastHunk = fileDiff.hunks.at(-1);
	if (lastHunk == null || fileDiff.isPartial || fileDiff.additionLines.length === 0 || fileDiff.deletionLines.length === 0) return false;
	return lastHunk.additionLineIndex + lastHunk.additionCount < fileDiff.additionLines.length || lastHunk.deletionLineIndex + lastHunk.deletionCount < fileDiff.deletionLines.length;
}
function parseLineIndex(lineIndexAttr, diffStyle) {
	const [unifiedIndex, splitIndex] = lineIndexAttr.split(",").map(Number);
	return diffStyle === "split" ? splitIndex : unifiedIndex;
}

//#endregion
export { VirtualizedFileDiff };
//# sourceMappingURL=VirtualizedFileDiff.js.map