// Questo file fa parte di MyTown.
// MyTown è software libero: puoi ridistribuirlo e/o modificarlo secondo
// i termini della GNU Affero General Public License come pubblicata dalla
// Free Software Foundation, versione 3.
// Distribuito senza ALCUNA GARANZIA; vedi la GNU AGPL v3 per i dettagli.
// Copyright (C) Kondor Consulting & Services S.r.l.
// SPDX-License-Identifier: AGPL-3.0-or-later

import { CommonModule } from '@angular/common';
import { Component, ViewChild, ElementRef, HostListener, Input, Output, EventEmitter, AfterViewInit, OnDestroy } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { IonicModule, LoadingController } from '@ionic/angular';
import { ColorPickerModule } from 'primeng/colorpicker';
import { HttpClient } from '@angular/common/http';
import imageCompression from 'browser-image-compression';
import { ConfigService } from 'src/app/providers/config.service';
import { LavagnaService } from 'src/app/providers/lavagna.service';
import * as LZString from 'lz-string';
import * as Hammer from 'hammerjs';

declare const fabric: any;

interface PageData {
	json: string; // Fabric.js canvas JSON for user drawings
	backgroundUrl?: string; // Server URL for background image
	backgroundIsPattern?: boolean; // Per-page flag for pattern vs. single image
	isServerImage?: boolean; // Flag to indicate if this is a server-uploaded image
	thumbnail?: string; // Base64 image of the page
}

interface SavedDrawingData {
	pages: PageData[];
	currentPage: number;
}

@Component({
	selector: 'app-drawingboard',
	templateUrl: './app-drawingboard.component.html',
	styleUrls: ['./app-drawingboard.component.scss'],
	imports: [CommonModule, FormsModule, IonicModule, ColorPickerModule],
})
export class DrawingBoardComponent implements AfterViewInit, OnDestroy {
	//TODO Quando viene cambiato il background viene elminato quello di prima, ma se l'utente non salva si perde il background di prima.

	@ViewChild('drawingCanvas', { static: true }) drawingCanvas!: ElementRef<HTMLCanvasElement>;

	@Input() allowDrawing: boolean = true;
	@Output() saveData = new EventEmitter<string[]>();
	@Input() backgroundImageUrl: string = 'assets/images/grid-pattern.png'; // Default grid pattern URL
	@Input() isPattern: boolean = true; // Default is pattern (for grid)
	@ViewChild('fileInput') fileInput!: ElementRef<HTMLInputElement>;

	@Input() prt_id: number = 0;
	@Input() sopralluogo_id: number = 0;

	//private canvas!: fabric.Canvas;

	private canvas!: any;

	private pages: fabric.Object[][] = [[]]; // Keep private, use getter for template
	private pagesData: PageData[] = [{ json: '' }];
	private undoneStrokes: fabric.Object[] = [];
	public currentPage: number = 0;

	public selectedColor: string = '#000000';
	public selectedSize: number = 2;
	public isEraserSelected: boolean = false;
	public isHandToolActive: boolean = false;

	private isPanning: boolean = false;
	private lastPanPosition: { x: number; y: number } | null = null;
	private touchStartDistance: number | null = null;
	private initialScale: number | null = null;
	private lastTouchCenter: { x: number; y: number } | null = null;
	private scale: number = 1;
	private offset: { x: number; y: number } = { x: 0, y: 0 };
	private readonly MIN_SCALE: number = 0.5;
	private readonly MAX_SCALE: number = 3;

	// Logical canvas dimensions (what users draw on)
	private readonly LOGICAL_WIDTH: number = 600;
	private readonly LOGICAL_HEIGHT: number = 400;
	private readonly BORDER_WIDTH: number = 3;

	// Actual canvas dimensions (responsive)
	private canvasScale: number = 1;

	private backgroundPattern?: fabric.Pattern;
	private backgroundImg?: fabric.Image;

	// Track server images for cleanup
	private serverImageUrls: Set<string> = new Set();

	// Getter for pages length to use in template
	public get pageCount(): number {
		return this.pages.length;
	}

	constructor(private http: HttpClient, private configService: ConfigService, private sopralluoghiService: LavagnaService, private loadingCtrl: LoadingController) {}

	ngAfterViewInit() {
		setTimeout(() => {
			this.initializeCanvas();
			window.addEventListener('resize', this.onResize.bind(this));
		}, 0);
	}

	ngOnDestroy() {
		window.removeEventListener('resize', this.onResize.bind(this));
		const canvasElement = this.drawingCanvas.nativeElement;
		canvasElement.removeEventListener('touchstart', this.onTouchStart.bind(this));
		canvasElement.removeEventListener('touchmove', this.onTouchMove.bind(this));
		canvasElement.removeEventListener('touchend', this.onTouchEnd.bind(this));
		if (this.canvas) {
			this.canvas.dispose();
		}
	}

	private cleanupAllServerImages() {
		// Call delete API for all server images
		this.serverImageUrls.forEach((url) => {
			this.deleteImageFromServer(url);
		});
		this.serverImageUrls.clear();
	}

	private deleteImageFromServer(imageUrl: string) {
		const partialPath = `${this.sopralluogo_id}/${this.currentPage}/${imageUrl}`;
		console.log('Deleting image from server:', partialPath);
		this.sopralluoghiService.lavagnaImageDelete(partialPath, this.prt_id).subscribe();
	}

	usePencilTool() {
		this.isHandToolActive = false;
		this.isEraserSelected = false;
		this.updateCanvasInteractivity();
	}

	useHandTool() {
		this.isHandToolActive = !this.isHandToolActive;
		this.isEraserSelected = false;
		this.updateCanvasInteractivity();
	}

	// Ricalcola le dimensioni del canvas e ridisegna la pagina corrente.
	// Usato dal modale dopo l'apertura per lavagne vuote (sopralluogo nuovo),
	// dove non c'è alcun loadDrawingData a triggerare il ridisegno.
	public refresh() {
		if (!this.canvas) return;
		this.onResize();
	}

	@HostListener('window:resize')
	onResize() {
		this.setupCanvasDimensions();
		this.canvas.setZoom(this.scale * this.canvasScale);
		this.canvas.setViewportTransform([this.scale * this.canvasScale, 0, 0, this.scale * this.canvasScale, this.offset.x * this.canvasScale, this.offset.y * this.canvasScale]);
		this.applyClipping();
		this.drawPage();
	}

	private setupCanvasDimensions() {
		const canvasElement = this.drawingCanvas.nativeElement;
		const parent = canvasElement.parentElement!;

		// Larghezza disponibile: affidabile (il contenitore ha width:100%).
		const containerWidth = Math.min(parent.clientWidth, window.innerWidth);
		// Altezza: NON uso parent.clientHeight perché il contenitore non ha
		// altezza propria (dipende dal canvas) → sarebbe 0 e il canvas verrebbe
		// dimensionato a 0×0. Uso il viewport come limite massimo.
		const containerHeight = window.innerHeight * 0.7;

		// Calculate scale to fit the logical canvas in the container
		const scaleX = containerWidth / this.LOGICAL_WIDTH;
		const scaleY = containerHeight / this.LOGICAL_HEIGHT;
		this.canvasScale = Math.min(scaleX, scaleY, 1); // Never scale up beyond 1

		// Calculate actual canvas dimensions
		const actualWidth = this.LOGICAL_WIDTH * this.canvasScale;
		const actualHeight = this.LOGICAL_HEIGHT * this.canvasScale;

		// Center the canvas if it's smaller than container
		const marginLeft = (containerWidth - actualWidth) / 2;

		// Set CSS dimensions and centering
		canvasElement.style.width = `${actualWidth}px`;
		canvasElement.style.height = `${actualHeight}px`;
		canvasElement.style.marginLeft = marginLeft > 0 ? `${marginLeft}px` : '1px';
		canvasElement.style.display = 'block';

		// Set canvas internal dimensions to logical size
		// This ensures consistent drawing coordinates across all devices
		canvasElement.width = this.LOGICAL_WIDTH;
		canvasElement.height = this.LOGICAL_HEIGHT;

		// Update fabric.js canvas dimensions
		if (this.canvas) {
			this.canvas.setDimensions({
				width: this.LOGICAL_WIDTH,
				height: this.LOGICAL_HEIGHT,
			});

			// Apply the canvas scale to make it fit the container
			this.canvas.setZoom(this.scale * this.canvasScale);
		}
		//Adjust canvas parent element height ( zoom works for canvas but the div remain desktop size )
		if (this.drawingCanvas.nativeElement.parentElement) this.drawingCanvas.nativeElement.parentElement.style.height = this.LOGICAL_HEIGHT * this.canvasScale + 'px';
	}

	initializeCanvas() {
		const canvasElement = this.drawingCanvas.nativeElement;
		this.canvas = new fabric.Canvas(
			canvasElement,
			this.allowDrawing
				? {
						backgroundColor: 'white',
						selection: true,
						preserveObjectStacking: true,
						width: this.LOGICAL_WIDTH,
						height: this.LOGICAL_HEIGHT,
						allowTouchScrolling: false,
						stopContextMenu: true,
				  }
				: {
						backgroundColor: 'white',
						selection: false,
						preserveObjectStacking: true,
						width: this.LOGICAL_WIDTH,
						height: this.LOGICAL_HEIGHT,
						interactive: false,
						selectable: false,
						isDrawingMode: false,
						enablePointerEvents: false,
						hoverCursor: 'default',
						moveCursor: 'default',
				  },
		);

		if (this.allowDrawing) {
			const hammertime = new Hammer(this.canvas.upperCanvasEl);
			hammertime.get('pinch').set({ enable: true });

			this.canvas.on('mouse:down', this.onMouseDown.bind(this));
			this.canvas.on('mouse:move', this.onMouseMove.bind(this));
			this.canvas.on('mouse:up', this.onMouseUp.bind(this));
			this.canvas.on('mouse:wheel', this.onWheel.bind(this));

			hammertime.on('tap', (e: HammerInput) => {
				const fabricEvent = this.convertHammerEvent(e);
				this.onMouseDown(fabricEvent);
			});

			hammertime.on('panstart', (e: HammerInput) => {
				const fabricEvent = this.convertHammerEvent(e);
				this.onMouseDown(fabricEvent);
			});

			hammertime.on('panmove', (e: HammerInput) => {
				const fabricEvent = this.convertHammerEvent(e);
				this.onMouseMove(fabricEvent);
			});

			hammertime.on('panend', (e: HammerInput) => {
				const fabricEvent = this.convertHammerEvent(e);
				this.onMouseUp(fabricEvent);
			});

			/*hammertime.on('pinchstart pinchmove', (e: HammerInput) => {
				const fabricEvent = this.convertPinchEvent(e);
				this.onWheel(fabricEvent);
			});*/

			// Store reference for cleanup
			(this.canvas as any).hammerInstance = hammertime;
		}

		// Disable interactions for all objects
		this.canvas.getObjects().forEach((obj: fabric.Object) => {
			obj.selectable = false;
			obj.evented = false;
		});

		this.setupCanvasDimensions();
		this.applyClipping();
		this.updateCanvasInteractivity();
		this.drawPage();
	}

	// Convert Hammer.js event to Fabric.js-compatible mouse event
	private convertHammerEvent(e: HammerInput): any {
		const point = e.center;
		return {
			e: e.srcEvent,
			pointer: { x: point.x, y: point.y },
			target: (this.canvas as any).findTarget?.(e.srcEvent, false), // Type assertion to bypass TypeScript error
		};
	}

	// Convert pinch event to Fabric.js wheel event
	private convertPinchEvent(e: HammerInput): any {
		return {
			e: e.srcEvent,
			deltaY: e.scale < 1 ? 1 : -1, // Simulate wheel direction based on pinch
		};
	}

	private setBackgroundForPage(callback?: () => void) {
		const pageData = this.pagesData[this.currentPage];
		const url = pageData.backgroundUrl || this.backgroundImageUrl;
		const isPattern = pageData.backgroundIsPattern ?? this.isPattern;

		// Build full URL if it's a server image
		const fullUrl = pageData.isServerImage && url ? this.buildFullImageUrl(url) : url;

		fabric.Image.fromURL(
			fullUrl,
			(img: fabric.Image) => {
				// No need to scale by DPR since we're working with logical dimensions
				if (isPattern && !pageData.isServerImage) {
					this.backgroundPattern = new fabric.Pattern({
						source: img.getElement() as HTMLImageElement,
						repeat: 'repeat',
						offsetX: this.BORDER_WIDTH,
						offsetY: this.BORDER_WIDTH,
					});
					this.canvas.backgroundColor = this.backgroundPattern!;
					this.canvas.backgroundImage = undefined;
				} else {
					const drawableWidth = this.LOGICAL_WIDTH - 2 * this.BORDER_WIDTH;
					const drawableHeight = this.LOGICAL_HEIGHT - 2 * this.BORDER_WIDTH;
					const scaleX = drawableWidth / img.width!;
					const scaleY = drawableHeight / img.height!;
					const scale = Math.min(scaleX, scaleY);

					img.set({
						left: this.BORDER_WIDTH + (drawableWidth - img.width! * scale) / 2,
						top: this.BORDER_WIDTH + (drawableHeight - img.height! * scale) / 2,
						scaleX: scale,
						scaleY: scale,
						erasable: false,
					});
					this.backgroundImg = img;
					this.canvas.backgroundImage = img;
					this.canvas.backgroundColor = 'white';
				}

				this.canvas.renderAll();
				if (callback) callback();
			},
			{},
		);
	}

	private buildFullImageUrl(fileName: string): string {
		const lavagnaDest = `${this.sopralluogo_id}/${this.currentPage}/`;
		return this.configService.imageBaseURL + '/lavagna/' + lavagnaDest + fileName;
	}

	setCustomBackgroundImage(url: string, isPattern: boolean = false, isServerImage: boolean = false) {
		const pageData = this.pagesData[this.currentPage];

		// If replacing a server image, delete the old one
		if (pageData.isServerImage && pageData.backgroundUrl) {
			this.deleteImageFromServer(pageData.backgroundUrl);
			this.serverImageUrls.delete(pageData.backgroundUrl);
		}

		// Set new background
		pageData.backgroundUrl = url;
		pageData.backgroundIsPattern = isPattern;
		pageData.isServerImage = isServerImage;

		// Track server images
		if (isServerImage) {
			this.serverImageUrls.add(url);
		}

		this.setBackgroundForPage(() => this.drawPage());
	}

	private applyClipping() {
		const clipPath = new fabric.Rect({
			left: this.BORDER_WIDTH,
			top: this.BORDER_WIDTH,
			width: this.LOGICAL_WIDTH - 2 * this.BORDER_WIDTH,
			height: this.LOGICAL_HEIGHT - 2 * this.BORDER_WIDTH,
			absolutePositioned: true,
		});
		this.canvas.clipPath = clipPath;
		this.canvas.renderAll();
	}

	public updateCanvasInteractivity() {
		this.canvas.isDrawingMode = !this.isHandToolActive && this.allowDrawing;
		if (this.canvas.isDrawingMode) {
			if (this.isEraserSelected) {
				this.canvas.freeDrawingBrush = new fabric.EraserBrush(this.canvas);
			} else {
				this.canvas.freeDrawingBrush = new fabric.PencilBrush(this.canvas);
				this.canvas.freeDrawingBrush.color = this.selectedColor;
			}
			// Adjust brush width based on canvas scale
			this.canvas.freeDrawingBrush.width = this.selectedSize / this.canvasScale;
		}

		// Update cursor classes
		const canvasEl = (this.canvas as any).lowerCanvasEl;
		canvasEl.classList.remove('black_pointer', 'red_pointer', 'blue_pointer', 'green_pointer', 'yellow_pointer', 'purple_pointer', 'eraser_2', 'eraser_5', 'eraser_10', 'eraser_20', 'hand_pointer');
		if (this.isHandToolActive) {
			canvasEl.classList.add('hand_pointer');
		} else if (this.canvas.isDrawingMode) {
			if (this.isEraserSelected) {
				let eraserClass: string;
				if (this.selectedSize <= 3) {
					eraserClass = 'eraser_2';
				} else if (this.selectedSize <= 7) {
					eraserClass = 'eraser_5';
				} else if (this.selectedSize <= 12) {
					eraserClass = 'eraser_10';
				} else {
					eraserClass = 'eraser_20';
				}
				canvasEl.classList.add(eraserClass);
			} else {
				let colorClass: string = '';
				switch (this.selectedColor) {
					case '#000000':
						colorClass = 'black_pointer';
						break;
					case '#ff0000':
						colorClass = 'red_pointer';
						break;
					case '#0000ff':
						colorClass = 'blue_pointer';
						break;
					case '#008000':
						colorClass = 'green_pointer';
						break;
					case '#ffff00':
						colorClass = 'yellow_pointer';
						break;
					case '#800080':
						colorClass = 'purple_pointer';
						break;
				}
				if (colorClass) {
					canvasEl.classList.add(colorClass);
				}
			}
		}
	}

	startDrawing(event: fabric.TEvent<MouseEvent | TouchEvent>) {
		if (!this.allowDrawing || this.isHandToolActive) return;
	}

	stopDrawing() {
		if (!this.allowDrawing || this.isHandToolActive) return;

		this.pages[this.currentPage] = this.canvas.getObjects().filter((obj: any) => obj.type === 'path');
		this.undoneStrokes = [];
		this.updatePageData();
	}

	changeColor() {
		this.isEraserSelected = false;
		this.isHandToolActive = false;
		this.updateCanvasInteractivity();
	}

	resetTransformations() {
		this.scale = 1;
		this.offset = { x: 0, y: 0 };
		this.canvas.setZoom(this.scale * this.canvasScale);
		this.canvas.setViewportTransform([this.scale * this.canvasScale, 0, 0, this.scale * this.canvasScale, this.offset.x * this.canvasScale, this.offset.y * this.canvasScale]);
		this.applyClipping();
		this.drawPage();
	}

	changeSize(newSize: number) {
		this.selectedSize = newSize;
		this.isHandToolActive = false;
		this.updateCanvasInteractivity();
		if (this.isEraserSelected) {
			this.useEraser();
		}
	}

	useEraser() {
		this.isEraserSelected = true;
		this.isHandToolActive = false;
		this.updateCanvasInteractivity();
	}

	undo() {
		if (this.pages[this.currentPage].length > 0) {
			const undoneStroke = this.pages[this.currentPage].pop()!;
			this.undoneStrokes.push(undoneStroke);
			this.updatePageData();
			this.drawPage();
		}
	}

	redo() {
		if (this.undoneStrokes.length > 0) {
			const redoneStroke = this.undoneStrokes.pop()!;
			this.pages[this.currentPage].push(redoneStroke);
			this.updatePageData();
			this.drawPage();
		}
	}

	createNewPage() {
		this.pages.push([]);
		this.pagesData.push({ json: '' }); // New page without custom background
		this.currentPage = this.pages.length - 1;
		this.undoneStrokes = [];
		this.drawPage();
	}

	deletePage() {
		if (this.pages.length > 1) {
			const confirmDeletion = window.confirm(`Sei sicuro di voler eliminare la pagina ${this.currentPage + 1}?`);
			if (confirmDeletion) {
				// Delete server image if exists
				const pageData = this.pagesData[this.currentPage];
				if (pageData.isServerImage && pageData.backgroundUrl) {
					this.deleteImageFromServer(pageData.backgroundUrl);
					this.serverImageUrls.delete(pageData.backgroundUrl);
				}

				this.pages.splice(this.currentPage, 1);
				this.pagesData.splice(this.currentPage, 1);
				if (this.currentPage >= this.pages.length) {
					this.currentPage = this.pages.length - 1;
				}
				this.drawPage();
			}
		} else {
			window.alert("Non puoi eliminare l'unica pagina rimasta.");
		}
	}

	private captureCanvasAsBase64(): Promise<string> {
		return new Promise((resolve) => {
			// Store current state
			const currentScale = this.scale;
			const currentOffset = { ...this.offset };

			// Reset transformations for clean capture
			this.scale = 1;
			this.offset = { x: 0, y: 0 };
			this.canvas.setZoom(this.canvasScale);
			this.canvas.setViewportTransform([this.canvasScale, 0, 0, this.canvasScale, 0, 0]);

			// Render and capture
			this.canvas.renderAll();

			// Use toDataURL with quality settings
			const dataURL = this.canvas.toDataURL({
				format: 'png',
				quality: 0.8,
				multiplier: 1, // Adjust this for higher/lower resolution
			});

			// Restore original state
			this.scale = currentScale;
			this.offset = currentOffset;
			this.canvas.setZoom(this.scale * this.canvasScale);
			this.canvas.setViewportTransform([this.scale * this.canvasScale, 0, 0, this.scale * this.canvasScale, this.offset.x * this.canvasScale, this.offset.y * this.canvasScale]);
			this.canvas.renderAll();

			resolve(dataURL);
		});
	}

	async savePage() {
		const loading = await this.loadingCtrl.create({
			message: 'Salvataggio dati...',
		});
		await loading.present();

		try {
			// Store current page to restore later
			const originalPage = this.currentPage;
			const thumbnails: string[] = [];

			// Update loading message
			await loading.dismiss();
			const savingLoading = await this.loadingCtrl.create({
				message: 'Cattura immagini delle pagine...',
			});
			await savingLoading.present();

			// Capture each page as base64
			for (let i = 0; i < this.pages.length; i++) {
				// Update loading message with progress
				savingLoading.message = `Cattura pagina ${i + 1} di ${this.pages.length}...`;

				// Switch to the page
				this.currentPage = i;
				this.undoneStrokes = [];
				await this.drawPageAsync(); // New async version of drawPage

				// Capture the current canvas state
				const base64Image = await this.captureCanvasAsBase64();
				thumbnails.push(base64Image);

				// Store thumbnail in page data
				this.pagesData[i].thumbnail = base64Image;
			}

			// Restore original page
			this.currentPage = originalPage;
			this.drawPage();

			// Create saved data with thumbnails
			const savedData: SavedDrawingData = {
				pages: this.pagesData,
				currentPage: this.currentPage,
			};

			// Emit compressed data
			this.saveData.emit([this.compress(JSON.stringify(savedData)), JSON.stringify(thumbnails)]);

			await savingLoading.dismiss();

			// Optional: Show success message
			console.log(`Successfully captured ${thumbnails.length} page thumbnails`);
		} catch (error) {
			console.error('Error saving drawing data:', error);
			await loading.dismiss();
		}
	}

	private drawPageAsync(): Promise<void> {
		return new Promise((resolve) => {
			this.canvas.clear();
			this.setBackgroundForPage(() => {
				const userObjects = this.pages[this.currentPage].filter((obj) => obj.type === 'path');
				this.canvas.add(...userObjects);
				this.canvas.setViewportTransform([this.scale * this.canvasScale, 0, 0, this.scale * this.canvasScale, this.offset.x * this.canvasScale, this.offset.y * this.canvasScale]);
				this.canvas.renderAll();

				// Draw border
				const width = this.LOGICAL_WIDTH;
				const height = this.LOGICAL_HEIGHT;
				const border = new fabric.Rect({
					left: 0,
					top: 0,
					width,
					height,
					fill: 'transparent',
					stroke: '#000',
					strokeWidth: 2,
					selectable: false,
					evented: false,
				});
				this.canvas.add(border);
				this.canvas.renderAll();

				// Wait a frame to ensure rendering is complete
				setTimeout(() => resolve(), 50);
			});
		});
	}

	public loadDrawingData(compressed_data: string) {
		try {
			//this.cleanupAllServerImages();

			const data = this.decompress(compressed_data);
			const parsedData: SavedDrawingData = JSON.parse(data);

			// Load pages
			this.pages = parsedData.pages.map((page) => {
				const tempCanvas = new fabric.Canvas(undefined);
				tempCanvas.loadFromJSON(page.json, () => {});
				return tempCanvas.getObjects().filter((obj: fabric.Object) => obj.type === 'path');
			});

			this.pagesData = parsedData.pages;
			this.currentPage = parsedData.currentPage;

			// Track server images from loaded data
			this.pagesData.forEach((page) => {
				if (page.isServerImage && page.backgroundUrl) {
					this.serverImageUrls.add(page.backgroundUrl);
				}
			});

			this.resetTransformations();
			this.drawPage();
		} catch (error) {
			console.error('Error loading drawing data:', error);
		}
	}

	previousPage() {
		if (this.currentPage > 0) {
			this.currentPage--;
			this.undoneStrokes = [];
			this.drawPage();
		}
	}

	nextPage() {
		if (this.currentPage < this.pages.length - 1) {
			this.currentPage++;
			this.undoneStrokes = [];
			this.drawPage();
		}
	}

	private onMouseDown(event: fabric.TEvent<fabric.TPointerEvent>) {
		if (!event.e) return;

		if (this.isHandToolActive) {
			this.isPanning = true;
			this.lastPanPosition = { x: event.e.clientX, y: event.e.clientY };
		} else {
			this.startDrawing(event as fabric.TEvent<MouseEvent | TouchEvent>);
		}
	}

	private onMouseMove(event: fabric.TEvent<fabric.TPointerEvent>) {
		if (!event.e) return;
		if (this.isPanning && this.lastPanPosition) {
			const e = event.e;
			const deltaX = (e.clientX - this.lastPanPosition.x) / this.canvasScale;
			const deltaY = (e.clientY - this.lastPanPosition.y) / this.canvasScale;
			this.offset.x += deltaX;
			this.offset.y += deltaY;
			this.lastPanPosition = { x: e.clientX, y: e.clientY };
			this.canvas.setViewportTransform([this.scale * this.canvasScale, 0, 0, this.scale * this.canvasScale, this.offset.x * this.canvasScale, this.offset.y * this.canvasScale]);
			this.applyClipping();
			this.drawPage();
		}
	}

	private onMouseUp(event: fabric.TEvent<fabric.TPointerEvent>) {
		if (!event.e) return;
		if (this.isPanning) {
			this.isPanning = false;
			this.lastPanPosition = null;
		} else {
			this.stopDrawing();
		}
	}

	private onTouchStart(event: TouchEvent) {
		event.preventDefault();

		if (this.isHandToolActive) {
			if (event.touches.length === 1) {
				this.isPanning = true;
				this.lastPanPosition = {
					x: event.touches[0].clientX,
					y: event.touches[0].clientY,
				};
			} else if (event.touches.length === 2) {
				const touch1 = event.touches[0];
				const touch2 = event.touches[1];
				this.touchStartDistance = Math.hypot(touch2.clientX - touch1.clientX, touch2.clientY - touch1.clientY);
				this.initialScale = this.scale;
				this.lastTouchCenter = {
					x: (touch1.clientX + touch2.clientX) / 2,
					y: (touch1.clientY + touch2.clientY) / 2,
				};
			}
		} else {
			this.startDrawing({ e: event } as fabric.TEvent<TouchEvent>);
		}
	}

	private onTouchMove(event: TouchEvent) {
		event.preventDefault();

		if (this.isHandToolActive) {
			if (event.touches.length === 1 && this.lastPanPosition) {
				const touch = event.touches[0];
				const deltaX = (touch.clientX - this.lastPanPosition.x) / this.canvasScale;
				const deltaY = (touch.clientY - this.lastPanPosition.y) / this.canvasScale;
				this.offset.x += deltaX;
				this.offset.y += deltaY;
				this.lastPanPosition = { x: touch.clientX, y: touch.clientY };
				this.canvas.setViewportTransform([this.scale * this.canvasScale, 0, 0, this.scale * this.canvasScale, this.offset.x * this.canvasScale, this.offset.y * this.canvasScale]);
				this.applyClipping();
				this.drawPage();
			} else if (event.touches.length === 2) {
				const touch1 = event.touches[0];
				const touch2 = event.touches[1];
				const newDistance = Math.hypot(touch2.clientX - touch1.clientX, touch2.clientY - touch1.clientY);
				const newCenter = {
					x: (touch1.clientX + touch2.clientX) / 2,
					y: (touch1.clientY + touch2.clientY) / 2,
				};

				if (this.touchStartDistance && this.initialScale && this.lastTouchCenter) {
					const scaleChange = newDistance / this.touchStartDistance;
					const newScale = Math.min(Math.max(this.initialScale * scaleChange, this.MIN_SCALE), this.MAX_SCALE);

					if (this.scale !== newScale) {
						const rect = this.drawingCanvas.nativeElement.getBoundingClientRect();
						const pinchCenterX = (newCenter.x - rect.left) / this.canvasScale;
						const pinchCenterY = (newCenter.y - rect.top) / this.canvasScale;
						const scaleRatio = newScale / this.scale;
						this.offset.x = pinchCenterX - (pinchCenterX - this.offset.x) * scaleRatio;
						this.offset.y = pinchCenterY - (pinchCenterY - this.offset.y) * scaleRatio;
						this.scale = newScale;
						this.canvas.setZoom(this.scale * this.canvasScale);
						this.canvas.setViewportTransform([this.scale * this.canvasScale, 0, 0, this.scale * this.canvasScale, this.offset.x * this.canvasScale, this.offset.y * this.canvasScale]);
						this.applyClipping();
						this.drawPage();
					}

					const deltaX = (newCenter.x - this.lastTouchCenter.x) / this.canvasScale;
					const deltaY = (newCenter.y - this.lastTouchCenter.y) / this.canvasScale;
					this.offset.x += deltaX;
					this.offset.y += deltaY;
					this.lastTouchCenter = newCenter;
					this.canvas.setViewportTransform([this.scale * this.canvasScale, 0, 0, this.scale * this.canvasScale, this.offset.x * this.canvasScale, this.offset.y * this.canvasScale]);
					this.applyClipping();
					this.drawPage();
				}
				this.touchStartDistance = newDistance;
				this.lastTouchCenter = newCenter;
			}
		} else {
			this.startDrawing({ e: event } as fabric.TEvent<TouchEvent>);
		}
	}

	private onTouchEnd(event: TouchEvent) {
		event.preventDefault();

		if (this.isHandToolActive) {
			if (event.touches.length === 0) {
				this.isPanning = false;
				this.lastPanPosition = null;
				this.touchStartDistance = null;
				this.initialScale = null;
				this.lastTouchCenter = null;
			} else if (event.touches.length === 1) {
				this.touchStartDistance = null;
				this.initialScale = null;
				this.lastTouchCenter = null;
				this.lastPanPosition = {
					x: event.touches[0].clientX,
					y: event.touches[0].clientY,
				};
			}
		} else {
			this.stopDrawing();
		}
	}

	private onWheel(event: fabric.TEvent<WheelEvent>) {
		event.e.preventDefault();
		if (this.isHandToolActive) {
			const rect = this.drawingCanvas.nativeElement.getBoundingClientRect();
			const mouseX = (event.e.clientX - rect.left) / this.canvasScale;
			const mouseY = (event.e.clientY - rect.top) / this.canvasScale;
			const zoomFactor = event.e.deltaY > 0 ? 0.9 : 1.1;
			const newScale = Math.min(Math.max(this.scale * zoomFactor, this.MIN_SCALE), this.MAX_SCALE);
			if (this.scale !== newScale) {
				const scaleRatio = newScale / this.scale;
				this.offset.x = mouseX - (mouseX - this.offset.x) * scaleRatio;
				this.offset.y = mouseY - (mouseY - this.offset.y) * scaleRatio;
				this.scale = newScale;
				this.canvas.setZoom(this.scale * this.canvasScale);
				this.canvas.setViewportTransform([this.scale * this.canvasScale, 0, 0, this.scale * this.canvasScale, this.offset.x * this.canvasScale, this.offset.y * this.canvasScale]);
				this.applyClipping();
				this.drawPage();
			}
		}
	}

	private updatePageData() {
		const userObjects = this.canvas.getObjects().filter((obj: fabric.Object) => obj.type === 'path');
		this.pagesData[this.currentPage].json = JSON.stringify({ objects: userObjects });
	}

	drawPage() {
		this.canvas.clear();
		this.setBackgroundForPage(() => {
			const userObjects = this.pages[this.currentPage].filter((obj) => obj.type === 'path');
			this.canvas.add(...userObjects);
			this.canvas.setViewportTransform([this.scale * this.canvasScale, 0, 0, this.scale * this.canvasScale, this.offset.x * this.canvasScale, this.offset.y * this.canvasScale]);
			this.canvas.renderAll();

			// Draw border (keep as is)
			const width = this.LOGICAL_WIDTH;
			const height = this.LOGICAL_HEIGHT;
			const border = new fabric.Rect({
				left: 0,
				top: 0,
				width,
				height,
				fill: 'transparent',
				stroke: '#000',
				strokeWidth: 2,
				selectable: false,
				evented: false,
			});
			this.canvas.add(border);
			this.canvas.renderAll();
		});
	}

	triggerFileInput() {
		this.fileInput.nativeElement.click();
	}

	loadBackgroundImage(event: Event) {
		const input = event.target as HTMLInputElement;
		if (input.files && input.files[0]) {
			const file = input.files[0];
			const options = { maxSizeMB: 1, maxWidthOrHeight: 1920 };

			const upload = (compressed: any) => {
				const formData = new FormData();
				formData.append('file', compressed, file.name);
				// Upload to server and get URL back
				const lavagnaDest = `${this.sopralluogo_id}/${this.currentPage}`;
				this.sopralluoghiService.lavagnaImageUpload(formData, lavagnaDest, this.prt_id).subscribe({
					// Store only the partial URL returned from server (marked as server image)
					next: (url: any) => this.setCustomBackgroundImage(url.imagePath, false, true),
					error: () => this.notifyImgError('Errore nel caricamento dell\'immagine.'),
				});
			};

			// imageCompression usa internamente FileReader: a volte fallisce con NotReadableError
			// (problema di lettura file lato OS/browser). Catturo, riprovo una volta, poi avviso.
			imageCompression(file, options)
				.then(upload)
				.catch(() => {
					imageCompression(file, options)
						.then(upload)
						.catch(() => {
							this.notifyImgError('Impossibile leggere l\'immagine. Riprova a selezionarla.');
						});
				})
				.finally(() => {
					input.value = ''; // consente di riselezionare lo stesso file
				});
		}
	}

	private notifyImgError(msg: string) {
		try {
			this.configService.presentToastError(msg);
		} catch {
			/* noop */
		}
	}

	pinFormatter(value: number) {
		return `${value}px`;
	}

	compress(json_string: string): string {
		return LZString.compressToUTF16(json_string); // UTF16 safe for text storage
	}

	decompress(compressed: string): string {
		return LZString.decompressFromUTF16(compressed);
	}
}
