// Define interfaces
export interface DotConfig {
	id: number;
	value: number;
	x: number;
	link?: string; // URL to navigate to when dot is clicked
	onClick?: () => void; // Function to call when dot is clicked
	imageUrl?: string; // Image to display in tooltip
	title?: string; // Optional title for the tooltip
	description?: string; // Optional description for the tooltip
}
export interface Config {
	totalWidth: number;
	height: number;
	dotRadius: number;
	xUnitSize: number;
	tension: number;
	showGrid: boolean;
	tooltipWidth: number;
	tooltipHeight: number;
}
interface ControlPoints {
	x1: number;
	y1: number;
	x2: number;
	y2: number;
}
interface TooltipEdges {
	leftmost: number;
	rightmost: number;
}
export class ConnectedDotsVisualization {
	private config: Config;
	private dots: DotConfig[];
	private preloadedImages: Map<string, HTMLImageElement> = new Map();
	// DOM Elements
	private scrollContainer: HTMLElement;
	private svg: SVGElement;
	private gridGroup: SVGGElement;
	private curvePath: SVGPathElement;
	private dotsGroup: SVGGElement;
	private tooltipGroup: SVGGElement;
	// Active tooltip
	private activeTooltip: SVGElement | null = null;
	constructor(
		containerId: string,
		dots: DotConfig[],
		config?: Partial<Config>
	) {
		// Use the provided dots or empty array
		this.dots = dots || [];
		// Calculate the total width based on dots data
		const xUnitSize = config?.xUnitSize || 100;
		let calculatedWidth = 0;
		if (this.dots.length > 0) {
			// Find the minimum and maximum x values
			const minX = Math.min(...this.dots.map((dot) => dot.x));
			const maxX = Math.max(...this.dots.map((dot) => dot.x));
			// Calculate width based on the range of x values
			// Add padding on both sides (3 units on each side)
			calculatedWidth = (maxX - minX + 6) * xUnitSize;
		} else {
			calculatedWidth = 6 * xUnitSize; // Default width if no dots
		}
		// Default configuration
		this.config = {
			totalWidth: calculatedWidth,
			height: window.innerHeight,
			dotRadius: 6,
			xUnitSize: xUnitSize,
			tension: 0.5,
			showGrid: false,
			tooltipWidth: 128,
			tooltipHeight: 128,
			...config,
		};
		// Initialize DOM elements
		this.scrollContainer = document.getElementById(containerId) as HTMLElement;

		// Calculate the container height dynamically
		const containerHeight =
			this.scrollContainer.clientHeight ||
			this.scrollContainer.offsetHeight ||
			window.innerHeight;

		// Default configuration
		this.config = {
			totalWidth: calculatedWidth,
			height: containerHeight, // Use the calculated container height
			dotRadius: 6,
			xUnitSize: xUnitSize,
			tension: 0.5,
			showGrid: false,
			tooltipWidth: 128,
			tooltipHeight: 128,
			...config,
		};

		// Create SVG elements
		this.svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
		this.gridGroup = document.createElementNS(
			"http://www.w3.org/2000/svg",
			"g"
		);
		this.curvePath = document.createElementNS(
			"http://www.w3.org/2000/svg",
			"path"
		);
		this.dotsGroup = document.createElementNS(
			"http://www.w3.org/2000/svg",
			"g"
		);
		this.tooltipGroup = document.createElementNS(
			"http://www.w3.org/2000/svg",
			"g"
		);
		// Initialize the visualization
		this.addStyles();
		this.initializeSVG();
		this.setupEventListeners();
		this.preloadImages();
		this.render();
	}
	private preloadImages(): void {
		// Extract all unique image URLs from dots
		const imageUrls: string[] = this.dots
			.filter((dot) => dot.imageUrl)
			// biome-ignore lint/style/noNonNullAssertion: <explanation>
			.map((dot) => dot.imageUrl!)
			.filter((url, index, self) => self.indexOf(url) === index); // Remove duplicates
		// Create a loading indicator (optional)
		const loadingCount = { current: 0, total: imageUrls.length };
		if (imageUrls.length > 0) {
			console.log(`Preloading ${imageUrls.length} images...`);
		}
		// Preload each image
		for (const url of imageUrls) {
			const img = new Image();
			// Optional loading events
			img.onload = () => {
				loadingCount.current++;
				if (loadingCount.current === loadingCount.total) {
					console.log("All images preloaded successfully");
				}
			};
			img.onerror = () => {
				loadingCount.current++;
				console.error(`Failed to preload image: ${url}`);
			};
			// Set src to start loading
			img.src = url;
			// Store in map for potential later use
			this.preloadedImages.set(url, img);
		}
	}
	private addStyles(): void {
		// Add necessary styles for tooltips and interactions
		const styleId = "connected-dots-styles";
		if (!document.getElementById(styleId)) {
			const style = document.createElement("style");
			style.id = styleId;
			// style.textContent = `

			// `;
			document.head.appendChild(style);
		}
	}
	private initializeSVG(): void {
		// Configure SVG
		this.svg.setAttribute("width", `${this.config.totalWidth}`);
		this.svg.setAttribute("height", `${this.config.height}`);
		this.svg.style.overflow = "visible";
		this.scrollContainer.appendChild(this.svg);
		// Configure grid group
		this.gridGroup.classList.add("grid");
		this.svg.appendChild(this.gridGroup);
		// Configure curve path
		this.curvePath.setAttribute("fill", "none");
		this.curvePath.setAttribute("stroke", "white");
		this.curvePath.setAttribute("stroke-width", "2");
		this.curvePath.setAttribute("stroke-linecap", "round");
		this.curvePath.classList.add("curve-path");
		this.svg.appendChild(this.curvePath);
		// Configure dots group
		this.svg.appendChild(this.dotsGroup);
		// Configure tooltip group (always on top)
		this.tooltipGroup.classList.add("tooltips");
		this.svg.appendChild(this.tooltipGroup);
	}
	private setupEventListeners(): void {
		// Event listeners removed as the controls were removed
	}
	private getDotX(x: number): number {
		return (x + 3) * this.config.xUnitSize;
	}
	private getDotY(value: number): number {
		const centerY = this.config.height / 1.95;
		// Calculate raw Y position
		// height of the amplitude
		const rawY = centerY - (value / 3) * ((this.config.height / 2) * 0.6);
		// Calculate minimum Y position to ensure tooltip fits
		const minY = this.config.tooltipHeight + 40; // tooltip height + some padding
		// Ensure Y is never less than minimum (never too high on screen)
		return Math.max(rawY, minY);
	}
	private calculateBezierControlPoints(
		dots: DotConfig[],
		index: number
	): ControlPoints {
		const tension = this.config.tension * 500; // Scale tension for Bezier curve Rundung Kurve
		// Get current point and its neighbors
		const curr = dots[index];
		const next = dots[index + 1];
		// Calculate control points for a smooth bezier curve
		const x1 = this.getDotX(curr.x) + tension;
		const y1 = this.getDotY(curr.value);
		const x2 = this.getDotX(next.x) - tension;
		const y2 = this.getDotY(next.value);
		return { x1, y1, x2, y2 };
	}
	private generateBezierPath(): string {
		if (this.dots.length < 2) return "";
		let path = `M ${this.getDotX(this.dots[0].x)} ${this.getDotY(
			this.dots[0].value
		)}`;
		for (let i = 0; i < this.dots.length - 1; i++) {
			const { x1, y1, x2, y2 } = this.calculateBezierControlPoints(
				this.dots,
				i
			);
			const nextX = this.getDotX(this.dots[i + 1].x);
			const nextY = this.getDotY(this.dots[i + 1].value);
			path += ` C ${x1} ${y1}, ${x2} ${y2}, ${nextX} ${nextY}`;
		}
		return path;
	}
	private drawGrid(): void {
		// Clear previous grid
		while (this.gridGroup.firstChild) {
			this.gridGroup.removeChild(this.gridGroup.firstChild);
		}
		if (!this.config.showGrid) return;
		// Horizontal grid lines
		for (const value of [-3, -2, -1, 0, 1, 2, 3]) {
			const line = document.createElementNS(
				"http://www.w3.org/2000/svg",
				"line"
			);
			line.setAttribute("x1", "0");
			line.setAttribute("y1", this.getDotY(value).toString());
			line.setAttribute("x2", this.config.totalWidth.toString());
			line.setAttribute("y2", this.getDotY(value).toString());
			line.setAttribute("stroke", "rgba(219, 39, 119, 0.4)");
			line.setAttribute("stroke-width", "1");
			this.gridGroup.appendChild(line);
			const text = document.createElementNS(
				"http://www.w3.org/2000/svg",
				"text"
			);
			text.setAttribute("x", "10");
			text.setAttribute("y", (this.getDotY(value) + 4).toString());
			text.setAttribute("fill", "rgba(219, 39, 119, 0.8)");
			text.setAttribute("font-size", "12");
			text.textContent = value.toString();
			this.gridGroup.appendChild(text);
		}
		// Vertical grid lines
		const numVertLines = Math.ceil(
			this.config.totalWidth / this.config.xUnitSize
		);
		for (let i = 0; i < numVertLines; i++) {
			const x = i * this.config.xUnitSize;
			const xValue = i - 3; // Starting from -3
			const line = document.createElementNS(
				"http://www.w3.org/2000/svg",
				"line"
			);
			line.setAttribute("x1", x.toString());
			line.setAttribute("y1", "0");
			line.setAttribute("x2", x.toString());
			line.setAttribute("y2", this.config.height.toString());
			line.setAttribute("stroke", "rgba(219, 39, 119, 0.4)");
			line.setAttribute("stroke-width", "1");
			this.gridGroup.appendChild(line);
			if (xValue !== 0) {
				const text = document.createElementNS(
					"http://www.w3.org/2000/svg",
					"text"
				);
				text.setAttribute("x", x.toString());
				text.setAttribute("y", (this.config.height / 2 + 20).toString());
				text.setAttribute("fill", "rgba(219, 39, 119, 0.8)");
				text.setAttribute("font-size", "12");
				text.setAttribute("text-anchor", "middle");
				text.textContent = xValue.toString();
				this.gridGroup.appendChild(text);
			}
		}
	}

	private createTooltip(dot: DotConfig, x: number, y: number): SVGElement {
		const tooltip = document.createElementNS("http://www.w3.org/2000/svg", "g");
		tooltip.classList.add("dot-tooltip");
		tooltip.setAttribute("data-dot-id", dot.id.toString());

		// Calculate tooltip dimensions and position
		const tooltipWidth = 128; // Base width for your tooltip
		const tooltipHeight = (4 / 3) * tooltipWidth;
		const tooltipX = x - tooltipWidth / 2;
		let tooltipY = y - tooltipHeight - 10; // Positioned above the dot
		tooltipY = Math.max(tooltipY, 10); // Ensure it doesn't go above the view

		// Create background rectangle
		const bg = document.createElementNS("http://www.w3.org/2000/svg", "rect");
		bg.setAttribute("x", tooltipX.toString());
		bg.setAttribute("y", tooltipY.toString());
		bg.setAttribute("width", tooltipWidth.toString());
		bg.setAttribute("height", tooltipHeight.toString());
		bg.setAttribute("rx", "0"); // Rounded corners
		bg.classList.add("tooltip-background");
		tooltip.appendChild(bg);

		// Create foreignObject for the content
		const contentContainer = document.createElementNS(
			"http://www.w3.org/2000/svg",
			"foreignObject"
		);
		contentContainer.setAttribute("x", tooltipX.toString());
		contentContainer.setAttribute("y", tooltipY.toString());
		contentContainer.setAttribute("width", tooltipWidth.toString());
		contentContainer.setAttribute("height", tooltipHeight.toString());

		// Create a div to contain the content
		const div = document.createElement("div");
		div.classList.add("tooltip-content");

		// Add title if available
		if (dot.title) {
			const title = document.createElement("div");
			title.textContent = dot.title;
			title.classList.add("tooltip-title");
			div.appendChild(title);
		}

		// Add description if available
		if (dot.description) {
			const desc = document.createElement("div");
			desc.textContent = dot.description;
			desc.classList.add("tooltip-description");
			div.appendChild(desc);
		}

		// Add image if available
		// Create a container div
		const imageContainer = document.createElement("div");
		imageContainer.classList.add("image_container"); // Add image_container class

		// Define a variable for handling case with or without link
		let imgWrapper: HTMLElement;

		// if (dot.imageUrl) {
			if (dot.link || dot.onClick) {
				const link = document.createElement("a");
				if (dot.link) {
					link.href = dot.link;
				} else {
					link.href = "#"; // Prevent default href for onClick
				}
				link.target = "_self"; // Opens in the same window

				const imgElement = document.createElement("img");
				imgElement.src = dot.imageUrl;
				imgElement.classList.add("tooltip-image");

				// Append the image element to the link
				link.appendChild(imgElement);
				imgWrapper = link; // Use the link as the wrapper

				// Add the event listener to the link
				link.addEventListener("click", (e) => {
					if (dot.onClick) {
						e.preventDefault(); // Prevent default navigation
						dot.onClick();
					} else if (dot.link) {
						window.location.href = dot.link;
					} else {
						console.error("Dot has no link or onClick handler");
						throw new Error("Dot has no link or onClick handler");
					}
				});
			} else {
				const img = document.createElement("img");
				img.src = dot.imageUrl;
				img.classList.add("tooltip-image");
				imgWrapper = img; // Use the image directly as the wrapper
			}
		// } else {
		// 	console.error("Dot has no image URL");
		// 	throw new Error("Dot has no image URL");
		// }

		// Append imageWrapper to the container
		imageContainer.appendChild(imgWrapper);

	
		// Append the image container to the main div
		div.appendChild(imageContainer);
		
		const arrow = document.createElement("div");

		arrow.classList.add("tooltip-arrow");

		div.appendChild(arrow); // Append the arrow to the tooltip-content div

		contentContainer.appendChild(div);
		tooltip.appendChild(contentContainer);

		return tooltip;
	}

	private showTooltip(dot: DotConfig, x: number, y: number): void {
		// Create tooltip
		const tooltip = this.createTooltip(dot, x, y);
		this.tooltipGroup.appendChild(tooltip);
		this.activeTooltip = tooltip;
	}
	private hideTooltip(): void {
		// This method is kept for compatibility but doesn't hide tooltips anymore
	}
	private drawCurve(): void {
		const pathData = this.generateBezierPath();
		this.curvePath.setAttribute("d", pathData);
	}
	private calculateTooltipEdges(): TooltipEdges {
		let leftmost = 0;
		let rightmost = 0;
		let firstTooltipFound = false;
		// If no dots with tooltips, return default values
		if (this.dots.length === 0) {
			return { leftmost: 0, rightmost: this.config.totalWidth };
		}
		// Calculate the leftmost and rightmost edges of all tooltips
		for (const dot of this.dots) {
			// Skip dots without tooltip content
			if (!dot.imageUrl && !dot.title && !dot.description) {
				continue;
			}
			const x = this.getDotX(dot.x);
			const tooltipWidth = this.config.tooltipWidth;
			const tooltipX = x - tooltipWidth / 2;
			if (!firstTooltipFound) {
				leftmost = tooltipX;
				rightmost = tooltipX + tooltipWidth;
				firstTooltipFound = true;
			} else {
				// Update leftmost and rightmost values
				leftmost = Math.min(leftmost, tooltipX);
				rightmost = Math.max(rightmost, tooltipX + tooltipWidth);
			}
		}
		// If no dots with tooltips were found, use default values
		if (!firstTooltipFound) {
			return { leftmost: 0, rightmost: this.config.totalWidth };
		}
		return { leftmost, rightmost };
	}
	private drawDots(): void {
		// Clear previous dots
		while (this.dotsGroup.firstChild) {
			this.dotsGroup.removeChild(this.dotsGroup.firstChild);
		}
		// Clear previous tooltips
		while (this.tooltipGroup.firstChild) {
			this.tooltipGroup.removeChild(this.tooltipGroup.firstChild);
		}
		for (const dot of this.dots) {
			const x = this.getDotX(dot.x);
			const y = this.getDotY(dot.value);
			const circle = document.createElementNS(
				"http://www.w3.org/2000/svg",
				"circle"
			);
			circle.setAttribute("cx", x.toString());
			circle.setAttribute("cy", y.toString());
			circle.setAttribute("r", this.config.dotRadius.toString());
			circle.setAttribute("fill", "white");
			circle.setAttribute("data-dot-id", dot.id.toString());
			circle.classList.add("dot");
			// Always show tooltip if it has content
			if (dot.imageUrl || dot.title || dot.description) {
				this.showTooltip(dot, x, y);
			}
			// Click event for navigation or custom function
			if (dot.link || dot.onClick) {
				circle.addEventListener("click", () => {
					if (dot.onClick) {
						dot.onClick();
					} else if (dot.link) {
						window.location.href = dot.link;
					} else {
						console.error("Dot has no link or onClick handler");
						throw new Error("Dot has no link or onClick handler");
					}
				});
			}
			this.dotsGroup.appendChild(circle);
		}
	}
	public render(): void {
		this.drawGrid();
		this.drawCurve();
		this.drawDots();
		// Calculate tooltip edges and set SVG width
		const { leftmost, rightmost } = this.calculateTooltipEdges();
		// Set the SVG width based on the rightmost tooltip edge
		if (rightmost > 0) {
			// Add some padding
			const padding = 40;
			this.config.totalWidth = rightmost + padding;
			this.svg.setAttribute("width", `${this.config.totalWidth}`);
			// Update grid width
			this.drawGrid();
		}
	}
	// Public API methods for external use
	public updateDots(newDots: DotConfig[]): void {
    this.dots = newDots;
    // Initial width calculation based on dot positions (for grid)
    if (this.dots.length > 0) {
        // Find the minimum and maximum x values
        const minX = Math.min(...this.dots.map((dot) => dot.x));
        const maxX = Math.max(...this.dots.map((dot) => dot.x));
        // Calculate width based on the range of x values
        // Add padding on both sides (3 units on each side)
        this.config.totalWidth = (maxX - minX + 6) * this.config.xUnitSize;
    }
    // Render will calculate the tooltip edges and update the SVG width
    this.render();
}
	public updateConfig(newConfig: Partial<Config>): void {
		this.config = { ...this.config, ...newConfig };
		this.render();
	}
	public resize(): void {
		const containerHeight = this.scrollContainer.clientHeight || this.scrollContainer.offsetHeight || window.innerHeight;
		this.config.height = containerHeight;
		this.svg.setAttribute("height", `${this.config.height}`);
		this.render();
	}
}
