import { DEFAULT_THEMES, HEADER_METADATA_SLOT_ID, UNSAFE_CSS_ATTRIBUTE } from "./constants.js";
import { getLineAnnotationName } from "./utils/getLineAnnotationName.js";
import { DiffHunksRenderer } from "./DiffHunksRenderer.js";
import { LineSelectionManager, pluckLineSelectionOptions } from "./LineSelectionManager.js";
import { MouseEventManager, pluckMouseEventOptions } from "./MouseEventManager.js";
import { ResizeManager } from "./ResizeManager.js";
import { PJSContainerLoaded } from "./custom-components/Container.js";
import { SVGSpriteSheet } from "./sprite.js";
import { createAnnotationWrapperNode } from "./utils/createAnnotationWrapperNode.js";
import { createCodeNode } from "./utils/createCodeNode.js";
import { createHoverContentNode } from "./utils/createHoverContentNode.js";
import { createUnsafeCSSStyleNode } from "./utils/createUnsafeCSSStyleNode.js";
import { wrapUnsafeCSS } from "./utils/cssWrappers.js";
import { prerenderHTMLIfNecessary } from "./utils/prerenderHTMLIfNecessary.js";
import { setPreNodeProperties } from "./utils/setWrapperNodeProps.js";
import { ScrollSyncManager } from "./ScrollSyncManager.js";
import { parseDiffFromFile } from "./utils/parseDiffFromFile.js";
import deepEqual from "fast-deep-equal";
import { toHtml } from "hast-util-to-html";

//#region src/FileDiff.ts
let instanceId = -1;
var FileDiff = class {
	static LoadedCustomComponent = PJSContainerLoaded;
	__id = ++instanceId;
	fileContainer;
	spriteSVG;
	pre;
	unsafeCSSStyle;
	hoverContent;
	headerElement;
	headerMetadata;
	customHunkElements = [];
	errorWrapper;
	hunksRenderer;
	resizeManager;
	scrollSyncManager;
	mouseEventManager;
	lineSelectionManager;
	annotationElements = [];
	lineAnnotations = [];
	oldFile;
	newFile;
	fileDiff;
	constructor(options = { theme: DEFAULT_THEMES }, workerManager, isContainerManaged = false) {
		this.options = options;
		this.workerManager = workerManager;
		this.isContainerManaged = isContainerManaged;
		this.hunksRenderer = new DiffHunksRenderer({
			...options,
			hunkSeparators: typeof options.hunkSeparators === "function" ? "custom" : options.hunkSeparators
		}, this.handleHighlightRender, this.workerManager);
		this.resizeManager = new ResizeManager();
		this.scrollSyncManager = new ScrollSyncManager();
		this.mouseEventManager = new MouseEventManager("diff", pluckMouseEventOptions(options, typeof options.hunkSeparators === "function" || (options.hunkSeparators ?? "line-info") === "line-info" ? this.handleExpandHunk : void 0));
		this.lineSelectionManager = new LineSelectionManager(pluckLineSelectionOptions(options));
		this.workerManager?.subscribeToThemeChanges(this);
	}
	handleHighlightRender = () => {
		this.rerender();
	};
	setOptions(options) {
		if (options == null) return;
		this.options = options;
		this.hunksRenderer.setOptions({
			...this.options,
			hunkSeparators: typeof options.hunkSeparators === "function" ? "custom" : options.hunkSeparators
		});
		this.mouseEventManager.setOptions(pluckMouseEventOptions(options, typeof options.hunkSeparators === "function" || (options.hunkSeparators ?? "line-info") === "line-info" ? this.handleExpandHunk : void 0));
		this.lineSelectionManager.setOptions(pluckLineSelectionOptions(options));
	}
	mergeOptions(options) {
		this.options = {
			...this.options,
			...options
		};
	}
	setThemeType(themeType) {
		if ((this.options.themeType ?? "system") === themeType) return;
		this.mergeOptions({ themeType });
		this.hunksRenderer.setThemeType(themeType);
		if (this.headerElement != null) if (themeType === "system") delete this.headerElement.dataset.themeType;
		else this.headerElement.dataset.themeType = themeType;
		if (this.pre != null) switch (themeType) {
			case "system":
				delete this.pre.dataset.themeType;
				break;
			case "light":
			case "dark":
				this.pre.dataset.themeType = themeType;
				break;
		}
	}
	getHoveredLine = () => {
		return this.mouseEventManager.getHoveredLine();
	};
	setLineAnnotations(lineAnnotations) {
		this.lineAnnotations = lineAnnotations;
	}
	setSelectedLines(range) {
		this.lineSelectionManager.setSelection(range);
	}
	cleanUp() {
		this.hunksRenderer.cleanUp();
		this.resizeManager.cleanUp();
		this.mouseEventManager.cleanUp();
		this.scrollSyncManager.cleanUp();
		this.lineSelectionManager.cleanUp();
		this.workerManager?.unsubscribeToThemeChanges(this);
		this.workerManager = void 0;
		this.fileDiff = void 0;
		this.oldFile = void 0;
		this.newFile = void 0;
		if (!this.isContainerManaged) this.fileContainer?.parentNode?.removeChild(this.fileContainer);
		if (this.fileContainer?.shadowRoot != null) this.fileContainer.shadowRoot.innerHTML = "";
		this.fileContainer = void 0;
		this.pre = void 0;
		this.headerElement = void 0;
		this.errorWrapper = void 0;
	}
	hydrate(props) {
		const { fileContainer, prerenderedHTML } = props;
		prerenderHTMLIfNecessary(fileContainer, prerenderedHTML);
		for (const element of Array.from(fileContainer.shadowRoot?.children ?? [])) {
			if (element instanceof SVGElement) {
				this.spriteSVG = element;
				continue;
			}
			if (!(element instanceof HTMLElement)) continue;
			if (element instanceof HTMLPreElement) {
				this.pre = element;
				continue;
			}
			if ("pjsHeader" in element.dataset) {
				this.headerElement = element;
				continue;
			}
			if (element instanceof HTMLStyleElement && element.hasAttribute(UNSAFE_CSS_ATTRIBUTE)) {
				this.unsafeCSSStyle = element;
				continue;
			}
		}
		if (this.pre == null) this.render(props);
		else {
			const { lineAnnotations, oldFile, newFile, fileDiff } = props;
			this.fileContainer = fileContainer;
			delete this.pre.dataset.dehydrated;
			this.lineAnnotations = lineAnnotations ?? this.lineAnnotations;
			this.newFile = newFile;
			this.oldFile = oldFile;
			this.fileDiff = fileDiff ?? (oldFile != null && newFile != null ? parseDiffFromFile(oldFile, newFile) : void 0);
			this.hunksRenderer.hydrate(this.fileDiff);
			this.renderAnnotations();
			this.renderHoverUtility();
			this.injectUnsafeCSS();
			this.mouseEventManager.setup(this.pre);
			this.lineSelectionManager.setup(this.pre);
			if ((this.options.overflow ?? "scroll") === "scroll") {
				this.resizeManager.setup(this.pre);
				this.scrollSyncManager.setup(this.pre);
			}
		}
	}
	rerender() {
		if (this.fileDiff == null && this.newFile == null && this.oldFile == null) return;
		this.render({
			oldFile: this.oldFile,
			newFile: this.newFile,
			fileDiff: this.fileDiff,
			forceRender: true
		});
	}
	handleExpandHunk = (hunkIndex, direction) => {
		this.expandHunk(hunkIndex, direction);
	};
	expandHunk(hunkIndex, direction) {
		this.hunksRenderer.expandHunk(hunkIndex, direction);
		this.rerender();
	}
	render({ oldFile, newFile, fileDiff, forceRender = false, lineAnnotations, fileContainer, containerWrapper }) {
		const filesDidChange = oldFile != null && newFile != null && (!deepEqual(oldFile, this.oldFile) || !deepEqual(newFile, this.newFile));
		const annotationsChanged = lineAnnotations != null && (lineAnnotations.length > 0 || this.lineAnnotations.length > 0) ? lineAnnotations !== this.lineAnnotations : false;
		if (!forceRender && !annotationsChanged && (fileDiff != null && fileDiff === this.fileDiff || fileDiff == null && !filesDidChange)) return;
		this.oldFile = oldFile;
		this.newFile = newFile;
		if (fileDiff != null) this.fileDiff = fileDiff;
		else if (oldFile != null && newFile != null && filesDidChange) this.fileDiff = parseDiffFromFile(oldFile, newFile);
		if (lineAnnotations != null) this.setLineAnnotations(lineAnnotations);
		if (this.fileDiff == null) return;
		this.hunksRenderer.setOptions({
			...this.options,
			hunkSeparators: typeof this.options.hunkSeparators === "function" ? "custom" : this.options.hunkSeparators
		});
		this.hunksRenderer.setLineAnnotations(this.lineAnnotations);
		const { disableFileHeader = false } = this.options;
		if (disableFileHeader) {
			if (this.headerElement != null) {
				this.headerElement.parentNode?.removeChild(this.headerElement);
				this.headerElement = void 0;
			}
		}
		fileContainer = this.getOrCreateFileContainer(fileContainer, containerWrapper);
		try {
			const hunksResult = this.hunksRenderer.renderDiff(this.fileDiff);
			if (hunksResult == null) {
				if (this.workerManager != null && !this.workerManager.isInitialized()) this.workerManager.initialize().then(() => this.rerender());
				return;
			}
			if (hunksResult.headerElement != null) this.applyHeaderToDOM(hunksResult.headerElement, fileContainer);
			const pre = this.getOrCreatePreNode(fileContainer);
			this.applyHunksToDOM(pre, hunksResult);
			this.renderSeparators(hunksResult.hunkData);
			this.renderAnnotations();
			this.renderHoverUtility();
		} catch (error) {
			if (error instanceof Error) this.applyErrorToDOM(error, fileContainer);
		}
	}
	renderSeparators(hunkData) {
		const { hunkSeparators } = this.options;
		if (this.isContainerManaged || this.fileContainer == null || typeof hunkSeparators !== "function") return;
		for (const element of this.customHunkElements) element.parentNode?.removeChild(element);
		this.customHunkElements.length = 0;
		for (const hunk of hunkData) {
			const element = document.createElement("div");
			element.style.display = "contents";
			element.slot = hunk.slotName;
			element.appendChild(hunkSeparators(hunk, this));
			this.fileContainer.appendChild(element);
			this.customHunkElements.push(element);
		}
	}
	renderAnnotations() {
		if (this.isContainerManaged || this.fileContainer == null) return;
		for (const element of this.annotationElements) element.parentNode?.removeChild(element);
		this.annotationElements.length = 0;
		const { renderAnnotation } = this.options;
		if (renderAnnotation != null && this.lineAnnotations.length > 0) for (const annotation of this.lineAnnotations) {
			const content = renderAnnotation(annotation);
			if (content == null) continue;
			const el = createAnnotationWrapperNode(getLineAnnotationName(annotation));
			el.appendChild(content);
			this.annotationElements.push(el);
			this.fileContainer.appendChild(el);
		}
	}
	renderHoverUtility() {
		const { renderHoverUtility } = this.options;
		if (this.fileContainer == null || renderHoverUtility == null) return;
		if (this.hoverContent == null) {
			this.hoverContent = createHoverContentNode();
			this.fileContainer.appendChild(this.hoverContent);
		}
		const element = renderHoverUtility(this.mouseEventManager.getHoveredLine);
		this.hoverContent.innerHTML = "";
		if (element != null) this.hoverContent.appendChild(element);
	}
	getOrCreateFileContainer(fileContainer, parentNode) {
		this.fileContainer = fileContainer ?? this.fileContainer ?? document.createElement("file-diff");
		if (parentNode != null && this.fileContainer.parentNode !== parentNode) parentNode.appendChild(this.fileContainer);
		if (this.spriteSVG == null) {
			const fragment = document.createElement("div");
			fragment.innerHTML = SVGSpriteSheet;
			const firstChild = fragment.firstChild;
			if (firstChild instanceof SVGElement) {
				this.spriteSVG = firstChild;
				this.fileContainer.shadowRoot?.appendChild(this.spriteSVG);
			}
		}
		return this.fileContainer;
	}
	getFileContainer() {
		return this.fileContainer;
	}
	getOrCreatePreNode(container) {
		if (this.pre == null) {
			this.pre = document.createElement("pre");
			container.shadowRoot?.appendChild(this.pre);
		} else if (this.pre.parentNode !== container) container.shadowRoot?.appendChild(this.pre);
		return this.pre;
	}
	applyHeaderToDOM(headerAST, container) {
		this.cleanupErrorWrapper();
		const tempDiv = document.createElement("div");
		tempDiv.innerHTML = toHtml(headerAST);
		const newHeader = tempDiv.firstElementChild;
		if (!(newHeader instanceof HTMLElement)) return;
		if (this.headerElement != null) container.shadowRoot?.replaceChild(newHeader, this.headerElement);
		else container.shadowRoot?.prepend(newHeader);
		this.headerElement = newHeader;
		if (this.isContainerManaged) return;
		const { renderHeaderMetadata } = this.options;
		if (this.headerMetadata != null) this.headerMetadata.parentNode?.removeChild(this.headerMetadata);
		const content = renderHeaderMetadata?.({
			oldFile: this.oldFile,
			newFile: this.newFile,
			fileDiff: this.fileDiff
		}) ?? void 0;
		if (content != null) {
			this.headerMetadata = document.createElement("div");
			this.headerMetadata.slot = HEADER_METADATA_SLOT_ID;
			if (content instanceof Element) this.headerMetadata.appendChild(content);
			else this.headerMetadata.innerText = `${content}`;
			container.appendChild(this.headerMetadata);
		}
	}
	injectUnsafeCSS() {
		if (this.fileContainer?.shadowRoot == null) return;
		const { unsafeCSS } = this.options;
		if (unsafeCSS == null || unsafeCSS === "") return;
		if (this.unsafeCSSStyle == null) {
			this.unsafeCSSStyle = createUnsafeCSSStyleNode();
			this.fileContainer.shadowRoot.appendChild(this.unsafeCSSStyle);
		}
		this.unsafeCSSStyle.innerText = wrapUnsafeCSS(unsafeCSS);
	}
	applyHunksToDOM(pre, result) {
		this.cleanupErrorWrapper();
		this.applyPreNodeAttributes(pre, result);
		pre.innerHTML = "";
		let codeDeletions;
		let codeAdditions;
		if (result.unifiedAST != null) {
			const codeUnified = createCodeNode({ columnType: "unified" });
			codeUnified.innerHTML = this.hunksRenderer.renderPartialHTML(result.unifiedAST);
			pre.appendChild(codeUnified);
		} else {
			if (result.deletionsAST != null) {
				codeDeletions = createCodeNode({ columnType: "deletions" });
				codeDeletions.innerHTML = this.hunksRenderer.renderPartialHTML(result.deletionsAST);
				pre.appendChild(codeDeletions);
			}
			if (result.additionsAST != null) {
				codeAdditions = createCodeNode({ columnType: "additions" });
				codeAdditions.innerHTML = this.hunksRenderer.renderPartialHTML(result.additionsAST);
				pre.appendChild(codeAdditions);
			}
		}
		this.injectUnsafeCSS();
		this.mouseEventManager.setup(pre);
		this.lineSelectionManager.setup(pre);
		if ((this.options.overflow ?? "scroll") === "scroll") {
			this.resizeManager.setup(pre);
			this.scrollSyncManager.setup(pre, codeDeletions, codeAdditions);
		} else {
			this.resizeManager.cleanUp();
			this.scrollSyncManager.cleanUp();
		}
	}
	applyPreNodeAttributes(pre, { themeStyles, baseThemeType, additionsAST, deletionsAST, totalLines }) {
		const { diffIndicators = "bars", disableBackground = false, disableLineNumbers = false, overflow = "scroll", themeType = "system", diffStyle = "split" } = this.options;
		setPreNodeProperties({
			pre,
			diffIndicators,
			disableBackground,
			disableLineNumbers,
			overflow,
			split: diffStyle === "unified" ? false : additionsAST != null && deletionsAST != null,
			themeStyles,
			themeType: baseThemeType ?? themeType,
			totalLines
		});
	}
	applyErrorToDOM(error, container) {
		this.cleanupErrorWrapper();
		const pre = this.getOrCreatePreNode(container);
		pre.innerHTML = "";
		pre.parentNode?.removeChild(pre);
		this.pre = void 0;
		const shadowRoot = container.shadowRoot ?? container.attachShadow({ mode: "open" });
		this.errorWrapper ??= document.createElement("div");
		this.errorWrapper.dataset.errorWrapper = "";
		this.errorWrapper.innerHTML = "";
		shadowRoot.appendChild(this.errorWrapper);
		const errorMessage = document.createElement("div");
		errorMessage.dataset.errorMessage = "";
		errorMessage.innerText = error.message;
		this.errorWrapper.appendChild(errorMessage);
		const errorStack = document.createElement("pre");
		errorStack.dataset.errorStack = "";
		errorStack.innerText = error.stack ?? "No Error Stack";
		this.errorWrapper.appendChild(errorStack);
	}
	cleanupErrorWrapper() {
		this.errorWrapper?.parentNode?.removeChild(this.errorWrapper);
		this.errorWrapper = void 0;
	}
};

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