import { createCanvas, Image, loadImage } from "@napi-rs/canvas";
import { robotToPixel } from "../../../common/coordTransformation";
import { B01Area, B01DeviceStatus, B01MapData, B01Point } from "./types";

// --- CONSTANTS from original Roborock modules ---
import {
	APP_COLORS,
	CleanModeType,
	JOB_STATUS,
	PALETTE,
	ROOM_TYPE_MAP,
	SC_MAP_COLORS,
	SCCleanType,
	SUBTITLE_STATUS
} from "./constants";

export class MapBuilder {
	private readonly SCALE = 8; // High Res output
	private assets: { robot: Record<string, Image>; charger: Record<string, Image>; rooms: Record<string, Image> } | null = null;
	private assetsLoaded = false;
	private adapter: any; // Optional reference to adapter for logging
	private offsetCache = new Map<any, { x: number; y: number }>();

	constructor(adapter?: any) {
		this.adapter = adapter;
	}

	/** @param needRobotCharger If false (e.g. history map), do not log errors for missing robot/charger assets. */
	private async loadAssets(model?: string, duid?: string, needRobotCharger: boolean = true): Promise<void> {
		if (this.assetsLoaded) return;
		this.assets = { rooms: {}, robot: {}, charger: {} };

		try {
			// assets/<model> from adapter file storage; model comes from the device (e.g. roborock.vacuum.sc05)
			const searchPaths: string[] = [];
			if (model && this.adapter) {
				const modelPath = `assets/${model}`;
				try {
					await this.adapter.readDirAsync(this.adapter.name, modelPath);
					searchPaths.push(modelPath);
				} catch { /* dir missing */ }
				// Fallback namespace (e.g. files/roborock/ when instance is roborock.0)
				if (searchPaths.length === 0 && this.adapter.name && this.adapter.name.includes(".")) {
					try {
						await this.adapter.readDirAsync(this.adapter.name.split(".")[0], modelPath);
						searchPaths.push(modelPath);
					} catch { /* dir missing */ }
				}
				// Always add path so we can try both namespaces with subdirsToTry even if readDirAsync failed for both
				if (searchPaths.length === 0) searchPaths.push(modelPath);
			}

			const storageNamespaces = this.adapter.name && this.adapter.name.includes(".")
				? [this.adapter.name, this.adapter.name.split(".")[0]]
				: [this.adapter.name];

			const subdirsToTry = ["drawable-mdpi", "drawable-hdpi", "drawable-xhdpi", "drawable-xxhdpi", "raw"];
			const findAssetInPaths = async (candidates: string[]) => {
				for (const dir of searchPaths) {
					for (const ns of storageNamespaces) {
						for (const c of candidates) {
							const rootPath = `${dir}/${c}`;
							try {
								if (await this.adapter.fileExistsAsync(ns, rootPath)) {
									const res = await this.adapter.readFileAsync(ns, rootPath);
									const buf = typeof res === "object" && res !== null && "file" in res ? (res as { file: Buffer }).file : Buffer.from(res as ArrayBuffer);
									return await loadImage(buf);
								}
							} catch { /* try next */ }
						}
						// Prefer subdirs from listing; if readDirAsync fails (e.g. other namespace), still try subdirsToTry so we don't skip file checks
						let subdirs = subdirsToTry;
						try {
							const entries = await this.adapter.readDirAsync(ns, dir);
							const dirs: string[] = Array.isArray(entries)
								? entries
								: (entries as { dirs?: string[] }).dirs ?? [];
							const fromList = dirs.filter(d => d.startsWith("drawable-") || d === "raw");
							if (fromList.length > 0) subdirs = fromList;
						} catch { /* use subdirsToTry for this namespace */ }
						for (const subdir of subdirs) {
							for (const c of candidates) {
								const p = `${dir}/${subdir}/${c}`;
								try {
									if (await this.adapter.fileExistsAsync(ns, p)) {
										const res = await this.adapter.readFileAsync(ns, p);
										const buf = typeof res === "object" && res !== null && "file" in res ? (res as { file: Buffer }).file : Buffer.from(res as ArrayBuffer);
										return await loadImage(buf);
									}
								} catch { /* try next */ }
							}
						}
					}
				}
				return null;
			};

			// --- 1. Load Robot Assets (theme_dark preference) ---
			// Load B01-specific robot asset
			const b01RobotImg = await findAssetInPaths(["src_sc_components_resource_images_common_robot.png"]);
			if (b01RobotImg) this.assets!.robot["b01_fixed"] = b01RobotImg;

			const robotStates = ["charging", "cleaning", "error", "sleeping", "offline", "waiting"];
			for (const state of robotStates) {
				const candidates = [
					// sc01 style
					`src_sc_components_resource_images_common_robot_${state === "charging" ? "rechage" : (state === "error" ? "fault" : state)}.png`,
					`src_sc_components_resource_images_common_robot_${state === "charging" ? "rechage" : (state === "error" ? "fault" : state)}_dark.png`,
					// a147 style
					`projects_comroborocktanos_theme_dark_resources_robot_icon_${state}.png`,
					`projects_comroborocktanos_resources_robot_icon_${state}.png`,
					`robot_icon_${state}.png`
				];
				const img = await findAssetInPaths(candidates);
				if (img) this.assets!.robot[state] = img;
			}
			// Robot Default
			if (!Object.keys(this.assets!.robot).length) {
				const fallback = await findAssetInPaths([
					"projects_comroborocktanos_theme_dark_resources_robot_icon_cleaning.png",
					"projects_comroborocktanos_resources_robot_icon_cleaning.png"
				]);
				if (fallback) this.assets!.robot["cleaning"] = fallback;
			}

			// --- 2. Load Charger Assets ---
			const chargerCandidates = [
				// Android style asset
				"src_sc_components_resource_images_common_charge_android.png",
				// sc01 style
				"src_sc_components_resource_images_home_home_charge_charging.png",
				"src_sc_components_resource_images_home_home_charge_charging_dark.png",
				"src_sc_components_resource_images_home_home_charge_reccharg.png",
				// a147 style
				"projects_comroborocktanos_resources_charger_special.png",
				"projects_comroborocktanos_resources_charger_bubble_special.png",
				"projects_comroborocktanos_resources_charger_bubble_normal.png",
				"projects_comroborocktanos_theme_dark_resources_ic_home_topazs_charge_light.png"
			];
			const chargerImg = await findAssetInPaths(chargerCandidates);
			if (chargerImg) this.assets!.charger["normal"] = chargerImg;

			// --- 3. Load Room Icons (Bubble Tags) ---
			// The user has `roomtag_bubble_X.png`. We load specific IDs (1-32 is a safe range for room types)
			// plus the mapped names from ROOM_TYPE_MAP just in case.
			const roomIdsToLoad = Array.from({ length: 32 }, (_, i) => i + 1); // 1 to 32

			for (const id of roomIdsToLoad) {
				// IMPORTANT: The 'tag' version is the white symbol without the blue bubble background.
				// We prioritize it so our colored background (circle) is visible.
				const candidates = [
					`projects_comroborocktanos_resources_roomtag_bubble_tag_${id}.png`,
					`projects_comroborocktanos_resources_roomtag_bubble_${id}.png`,
					`roomtag_bubble_tag_${id}.png`,
					`roomtag_bubble_${id}.png`
				];
				const img = await findAssetInPaths(candidates);
				if (img) {
						this.assets!.rooms[`bubble_${id}`] = img;
						// B01 IDs are often 2000 + assetID
						this.assets!.rooms[`bubble_${id + 2000}`] = img;
				}
			}

			// Load numbered robot icons from snippet
			const snippetRobotIcons = [85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104];
			for (const id of snippetRobotIcons) {
				const candidates = [
					`projects_comroborocktanos_resources_robot_icon_${id}.png`,
					`robot_icon_${id}.png`
				];
				const img = await findAssetInPaths(candidates);
				if (img) {
						this.assets!.robot[`icon_${id}`] = img;
				}
			}

			// Also try the named ones from ROOM_TYPE_MAP (kitchen, bedroom, etc) IF they exist in some form,
			// though file listing suggests they rely on ID-based bubbles.
			// Also try the named ones from ROOM_TYPE_MAP (kitchen, bedroom, etc) IF they exist in some form
			for (const name of Object.values(ROOM_TYPE_MAP)) {
				if (!name || name === "other") continue;
				const candidates = [
					`src_sc_components_resource_images_maproom_select_${name}.png`,
					`src_sc_components_resource_images_mapedit_map_edit_${name}.png`,
					`src_sc_components_resource_images_maproom_select_${name}_select.png`
				];
				const img = await findAssetInPaths(candidates);
				if (img) this.assets!.rooms[name] = img;
			}
			// Load 'other' fallback
			const otherImg = await findAssetInPaths([
				"src_sc_components_resource_images_maproom_select_other.png",
				"projects_comroborocktanos_resources_roomtag_bubble_tag_1.png"
			]);
			if (otherImg) this.assets!.rooms["other"] = otherImg;

			// Only log missing robot/charger when this map needs them (live map); history maps have no robot/station
			if (needRobotCharger) {
				const missingRobot = Object.keys(this.assets!.robot).length === 0;
				const missingCharger = Object.keys(this.assets!.charger).length === 0;
				const pathsHint = searchPaths.length ? ` Searched in: ${searchPaths.join(", ")} (with subdirs drawable-*, raw).` : "";
				const installHint = " Assets are downloaded at adapter start (Cloud/App Plugin) or can be placed in adapter file storage.";
				if (missingCharger && this.adapter) {
					this.adapter.rLog("MapManager", duid, "Error", undefined, undefined, `Charger asset NOT found.${pathsHint}${installHint}`, "error");
				}
				if (missingRobot && this.adapter) {
					this.adapter.rLog("MapManager", duid, "Error", undefined, undefined, `No Robot assets found.${pathsHint}${installHint}`, "error");
				}
			}
		} catch (e: any) {
			if (this.adapter) this.adapter.rLog("MapManager", duid, "Error", undefined, undefined, `Error loading assets: ${e.message}`, "error");
		}
		this.assetsLoaded = true;
	}

	private getRobotIconAssetKey(status: B01DeviceStatus): string {
		// Check for B01 specific asset
		if (this.assets?.robot?.["b01_fixed"]) {
			return "b01_fixed";
		}

		const { deviceState, deviceWorkMode, deviceCleanMode, isDustCollect, deviceFault } = status;
		const isDarkMode = false; // Default to light mode

		let key = "cleaning"; // Default fallback icon

		// Select robot icon based on current operative state
		if (isDustCollect) {
			return isDarkMode ? "icon_85" : "icon_86";
		}

		if (deviceState === SUBTITLE_STATUS.WAIT_INSTRUCTION || deviceState === SUBTITLE_STATUS.IDEL) {
			return "icon_87";
		}

		if (deviceState === SUBTITLE_STATUS.RECHARGING || deviceState === SUBTITLE_STATUS.CHARGE_FULL || deviceState === SUBTITLE_STATUS.BREAK_CHARGING) {
			return "icon_88";
		}

		// Fault handling
		const isFault = deviceFault && deviceFault > 0; // Simplified
		if (isFault) {
			return isDarkMode ? "icon_89" : "icon_90";
		}

		if (deviceState === SUBTITLE_STATUS.SLEEP) {
			return isDarkMode ? "icon_91" : "icon_92";
		}

		// WorkMode logic
		const isCleaning = [JOB_STATUS.CLEANING, JOB_STATUS.ZONED_CLEANING, JOB_STATUS.SPOT_CLEANING].includes(deviceWorkMode);
		const isPause = deviceWorkMode === JOB_STATUS.PAUSED;
		const isGoCharging = deviceWorkMode === 6; // RETURNING
		const isBuildModel = status.deviceCustomType === CleanModeType.ALL; // Map custom type to icon

		if (isBuildModel && isCleaning) {
			key = "icon_93";
		}

		if (isGoCharging || deviceState === SUBTITLE_STATUS.BREAK_RECHARGING) {
			key = isDarkMode ? "icon_94" : "icon_95";
		}

		// Additional operative state logic (Segment/Point modes)

		if (deviceState === SUBTITLE_STATUS.PAUSE) {
			key = isDarkMode ? "icon_96" : "icon_97";
		}

		if (isCleaning || isPause) {
			if (deviceState === SUBTITLE_STATUS.PAUSE) {
				key = isDarkMode ? "icon_96" : "icon_97";
			} else {
				key = isDarkMode ? "icon_98" : "icon_99";
			}
		}

		if (deviceState === SUBTITLE_STATUS.PAUSE) {
			key = isDarkMode ? "icon_100" : "icon_101";
		}

		if (isCleaning) {
			if (deviceCleanMode === SCCleanType.clean) key = "icon_102";
			if (deviceCleanMode === SCCleanType.both) key = "icon_103";
			if (deviceCleanMode === SCCleanType.mop) key = "icon_104";
		}

		if (this.adapter) this.adapter.rLog("MapManager", undefined, "Debug", undefined, undefined, `Selected Robot Icon: ${key} for state=${deviceState}, work=${deviceWorkMode}, clean=${deviceCleanMode}`, "debug");

		return key;
	}

	private getVisibleOffset(img: any): { x: number; y: number } {
		if (this.offsetCache.has(img)) return this.offsetCache.get(img)!;

		const w = img.naturalWidth;
		const h = img.naturalHeight;
		const cnv = createCanvas(w, h);
		const ctx = cnv.getContext("2d");
		ctx.drawImage(img, 0, 0);
		const data = ctx.getImageData(0, 0, w, h).data;

		let minX = w, maxX = 0, minY = h, maxY = 0;
		let found = false;

		for (let y = 0; y < h; y++) {
			for (let x = 0; x < w; x++) {
				const alpha = data[(y * w + x) * 4 + 3];
				if (alpha > 200) { // Threshold (Ignore Shadows)
					if (x < minX) minX = x;
					if (x > maxX) maxX = x;
					if (y < minY) minY = y;
					if (y > maxY) maxY = y;
					found = true;
				}
			}
		}

		if (!found) {
			this.offsetCache.set(img, { x: 0, y: 0 });
			return { x: 0, y: 0 };
		}

		// Visible Center
		const visibleCX = (minX + maxX) / 2;
		const visibleCY = (minY + maxY) / 2;

		// Geometric Center
		const geoCX = w / 2;
		const geoCY = h / 2;

		// We want Visible Center to be at (0,0) relative to drawn POS.
		// Currently Geometric Center is at (0,0) (due to -w/2 translate).
		// Correction: Shift so Visible Center matches Geometric Center
		// Offset = Geometric - Visible
		const offsetX = geoCX - visibleCX;
		const offsetY = geoCY - visibleCY;

		const result = { x: offsetX, y: offsetY };
		this.offsetCache.set(img, result);

		if (this.adapter) {
			this.adapter.rLog("MapManager", undefined, "Debug", undefined, undefined, `Auto-Trim: Found Bounds [${minX}, ${minY}, ${maxX}, ${maxY}] -> Offset(${offsetX}, ${offsetY}) for ${w}x${h} Image`, "debug");
		}

		return result;
	}

	private drawMapAsset(
		ctx: any,
		pos: B01Point,
		phi: number,
		img: any,
		unitWidth: number,
		offsetX: number,
		offsetY: number,
		toPixel: (wx: number, wy: number) => { x: number; y: number }
	) {
		const pt = toPixel(pos.x, pos.y);
		const rx = pt.x;
		const ry = pt.y;

		const scale = unitWidth / img.naturalWidth;
		const w = unitWidth;
		const h = img.naturalHeight * scale;

		// Auto-Center Logic
		const autoOffset = this.getVisibleOffset(img);
		// Scale the offset to map dimensions
		const finalOffsetX = offsetX + (autoOffset.x * scale);
		const finalOffsetY = offsetY + (autoOffset.y * scale);

		ctx.save();
		ctx.translate(rx + finalOffsetX, ry + finalOffsetY);
		ctx.rotate(-(phi * Math.PI) / 180);
		ctx.drawImage(img, -w / 2, -h / 2, w, h);
		ctx.restore();
	}

	public async buildMap(data: B01MapData, robotModel: string, duid?: string, deviceStatus?: B01DeviceStatus): Promise<Buffer> {
		await this.loadAssets(robotModel, duid, !!(data.robotPos || data.chargerPos));

		let { width, height } = { width: data.header.sizeX, height: data.header.sizeY };

		// Fallback dimensions logic
		if (width === 0 || height === 0) {
			const len = data.mapGrid.length;
			if (len > 0) {
				const size = Math.ceil(Math.sqrt(len));
				width = size;
				height = size;
			} else {
				width = 256;
				height = 256;
			}
		}

		const canvas = createCanvas(width * this.SCALE, height * this.SCALE);
		const ctx = canvas.getContext("2d");

		// Disable anti-aliasing for pixel-perfect walls
		ctx.imageSmoothingEnabled = false;
		ctx.scale(this.SCALE, this.SCALE);

		// 1. Fill Background
		ctx.fillStyle = "#000000"; // Black background
		ctx.fillRect(0, 0, width, height);

		// 2. Render Map Pixels (Offscreen Canvas)
		const roomColorMap: Record<number, number> = {};
		if (data.rooms) {
			data.rooms.forEach(r => {
				if (r.colorId !== undefined) roomColorMap[r.gridValue ?? r.roomId] = r.colorId;
			});
		}

		// Create offscreen canvas for correct scaling (putImageData ignores transforms)
		const tempCanvas = createCanvas(width, height);
		const tempCtx = tempCanvas.getContext("2d");
		const imgData = tempCtx.createImageData(width, height);
		const buffer = imgData.data;

		const COLOR_SET = SC_MAP_COLORS.LEVEL_1;

		// Helper to convert hex to [r,g,b,a]
		const hexToBytes = (hex: string): [number, number, number, number] => {
			if (!hex || !hex.startsWith("#")) return [0, 0, 0, 0];
			const r = parseInt(hex.slice(1, 3), 16);
			const g = parseInt(hex.slice(3, 5), 16);
			const b = parseInt(hex.slice(5, 7), 16);
			// Fix alpha logic (hex.length 9 is #RRGGBBAA)
			const a = hex.length === 9 ? parseInt(hex.slice(7, 9), 16) : 255;
			return [r, g, b, a];
		};

		const wallColor = hexToBytes(PALETTE.WALL);
		const floorColor = hexToBytes(PALETTE.FLOOR);
		const unknownColor = hexToBytes(PALETTE.UNKNOWN);
		// Pre-compute room colors
		const roomColors = COLOR_SET.map((c: string) => hexToBytes(c));

		for (let i = 0; i < data.mapGrid.length; i++) {
			const val = data.mapGrid[i];
			if (val === 0) continue; // Skip transparency (default 0)

			const x = i % width;
			const y = Math.floor(i / width);
			const cy = height - 1 - y;
			const idx = (cy * width + x) * 4;

			let color: [number, number, number, number] | undefined;

			if (val >= 128) { // Wall
				color = wallColor;
			} else if (val === 127 || val === 1) { // Floor
				color = floorColor;
			} else { // Room
				const colorIdx = this.getRoomFillColorIndex(val, roomColorMap[val]);
				color = roomColors[colorIdx % roomColors.length] || unknownColor;
			}

			if (color) {
				buffer[idx] = color[0];
				buffer[idx + 1] = color[1];
				buffer[idx + 2] = color[2];
				buffer[idx + 3] = color[3];
			}
		}

		tempCtx.putImageData(imgData, 0, 0);

		// Draw the 1x1 map onto the 8x8 main canvas (scaled)
		ctx.drawImage(tempCanvas, 0, 0);

		const toPixel = (wx: number, wy: number) => {
			return robotToPixel({
				x: wx,
				y: wy,
				minX: data.header.minX,
				minY: data.header.minY,
				sizeY: data.header.sizeY,
				resolution: data.header.resolution,
				scale: 1
			});
		};

		// 3. Zones & Walls
		const drawNoGoZone = (area: B01Area, isForbidden: boolean) => {
			if (!area || !area.points || area.points.length < 4) return;
			const lineWidth = 1.0;
			// 1. Geometry Calculation
			const p = area.points.map(pt => toPixel(pt.x, pt.y));
			const p0 = p[0], p1 = p[1], p2 = p[2], p3 = p[3];
			const getDist = (a: any, b: any) => Math.sqrt(Math.pow(a.x - b.x, 2) + Math.pow(a.y - b.y, 2));
			const width = getDist(p0, p3);
			const height = getDist(p0, p1);

			// Vertical Shift -1.0
			const PIXEL_SNAP = 0.0625;
			const OFFSET_Y = -1.0 + PIXEL_SNAP;
			const OFFSET_X = 0.00 + PIXEL_SNAP;

			const centerX = (p0.x + p2.x) / 2 + OFFSET_X;
			const centerY = (p0.y + p2.y) / 2 + OFFSET_Y;
			const angle = Math.atan2(p3.y - p0.y, p3.x - p0.x);
			const spineW = width;
			const spineH = height;

			ctx.save();
			ctx.translate(centerX, centerY);
			ctx.rotate(angle);

			ctx.beginPath();
			ctx.rect(-spineW / 2, -spineH / 2, spineW, spineH);

			if (isForbidden) {
				ctx.strokeStyle = "#FF453A"; // Roborock Red
				ctx.fillStyle = "rgba(255, 69, 58, 0.15)";
			} else {
				ctx.strokeStyle = "#007AFF"; // Roborock Blue
				ctx.fillStyle = "rgba(0, 122, 255, 0.15)";
			}

			ctx.lineWidth = lineWidth;
			ctx.lineJoin = "miter";
			ctx.lineCap = "butt";

			ctx.fill();
			ctx.stroke();
			ctx.restore();
		};

		const drawVirtualWall = (points: B01Point[]) => {
			if (!points || points.length < 2) return;
			const lineWidth = 1.0;
			ctx.save();
			ctx.beginPath();
			points.forEach((p, idx) => {
				const pix = toPixel(p.x, p.y);
				pix.y -= 1.0; // Vertical Shift (-1.0)
				if (idx===0) ctx.moveTo(pix.x, pix.y);
				else ctx.lineTo(pix.x, pix.y);
			});
			ctx.strokeStyle = "#FF453A";
			ctx.lineWidth = lineWidth;
			ctx.lineCap = "butt";
			ctx.lineJoin = "miter";
			ctx.stroke();
			ctx.restore();
		};

		// Render Zones
		if (data.areasInfo) data.areasInfo.forEach(area => drawNoGoZone(area, false));
		if (data.virtualWalls) {
			data.virtualWalls.forEach(wall => {
				if (wall.points && wall.points.length >= 4) {
					drawNoGoZone(wall, true);
				} else {
					drawVirtualWall(wall.points);
				}
			});
		}
		if (data.recmForbitZone) data.recmForbitZone.forEach(zone => drawNoGoZone(zone, true));

		// Render Carpets
		if (data.carpetInfo) {
			data.carpetInfo.forEach(carpet => {
				if (carpet.points && carpet.points.length > 0) {
					let sumX = 0, sumY = 0;
					carpet.points.forEach(p => {
						sumX += p.x;
						sumY += p.y;
					});
					const cx = sumX / carpet.points.length;
					const cy = sumY / carpet.points.length;
					const pt = toPixel(cx, cy);
					ctx.fillStyle = APP_COLORS.circleColor;
					ctx.beginPath();
					ctx.arc(pt.x, pt.y, 2, 0, 2 * Math.PI);
					ctx.fill();
				}
			});
		}

		// 4. Trajectory (History) - Segmented Logic
		if (data.history && data.history.length > 0) {
			const points = data.history;
			const segments: { type: string, points: {x: number, y: number}[] }[] = [];
			let currentSegment: { type: string, points: {x: number, y: number}[] } | null = null;

			points.forEach((p, idx) => {
				const pt = toPixel(p.x, p.y);
				const prevP = points[idx - 1];
				let breakSegment = false;
				if (prevP) {
					const dist = Math.sqrt(Math.pow(p.x - prevP.x, 2) + Math.pow(p.y - prevP.y, 2));
					if (dist > 0.5) breakSegment = true; // Jump detection
				}

				const type = (p.update === 5) ? "solidPath" :
							 (p.update === 4) ? "mopPaths" :
							 (p.update === 6) ? "mixPaths" : "dottPath";

				if (!currentSegment || currentSegment.type !== type || breakSegment) {
					currentSegment = { type, points: [] };
					segments.push(currentSegment);
				}
				currentSegment.points.push(pt);
			});

			const drawPathSegment = (seg: any, color: string, width: number, dash: number[] = []) => {
				if (seg.points.length < 2) return;
				ctx.beginPath();
				ctx.strokeStyle = color;
				ctx.lineWidth = width;
				ctx.setLineDash(dash);
				ctx.lineCap = "round";
				ctx.lineJoin = "round";
				ctx.moveTo(seg.points[0].x, seg.points[0].y);
				for (let i = 1; i < seg.points.length; i++) ctx.lineTo(seg.points[i].x, seg.points[i].y);
				ctx.stroke();
			};

			// Pass 1: Background Highlights (Mop/Mix)
			segments.forEach(seg => {
				if (seg.type === "mopPaths" || seg.type === "mixPaths") drawPathSegment(seg, "#FFFFFF66", 3.3);
			});

			// Pass 2: Foregrounds
			segments.forEach(seg => {
				if (seg.type === "solidPath") drawPathSegment(seg, "rgba(255, 255, 255, 1)", 0.75);
				else if (seg.type === "mopPaths") drawPathSegment(seg, "#FFFFFF99", 0.75);
				else if (seg.type === "mixPaths") drawPathSegment(seg, "#FFFFFF", 0.75);
				else if (seg.type === "dottPath") drawPathSegment(seg, "rgba(255, 255, 255, 0.6)", 0.45, [1, 2]);
			});
			ctx.setLineDash([]);
		}

		// 5. Charger Drawing logic from User Script
		if (data.chargerPos) {
			const chargerImg = this.assets?.charger?.["normal"];

			if (chargerImg) {
				const drawW = this.SCALE * (21 / 32) * 1.1;

				this.drawMapAsset(
					ctx,
					data.chargerPos,
					data.chargerPos.phi,
					chargerImg,
					drawW,
					0, // Zero Offset
					0, // Zero Offset
					toPixel
				);
			} else {
				// Fallback
				const { x: fx, y: fy } = toPixel(data.chargerPos.x, data.chargerPos.y);
				ctx.fillStyle = "#4CAF50";
				ctx.strokeStyle = "white";
				ctx.lineWidth = 1;
				ctx.beginPath();
				ctx.arc(fx, fy, 4, 0, 2 * Math.PI);
				ctx.fill();
				ctx.stroke();
				ctx.fillStyle = "white";
				ctx.font = "6px sans-serif";
				ctx.textAlign = "center";
				ctx.fillText("⚡", fx, fy + 2);
			}
		}

		// 6. Robot position (only on live maps; history maps and station have no robot position)
		if (data.robotPos) {
			const status = deviceStatus || {
				deviceState: SUBTITLE_STATUS.IDEL,
				deviceWorkMode: JOB_STATUS.IDLE,
				deviceCleanMode: SCCleanType.clean
			};

			// Logic to shift robot position when docked
			if (data.chargerPos) {
				const isChargingState = status.deviceState === 8 || status.deviceState === 6; // Charging or Returning

				if ((Math.abs(data.robotPos.x - data.chargerPos.x) < 0.2 && Math.abs(data.robotPos.y - data.chargerPos.y) < 0.2) || isChargingState) {
					const phi = data.chargerPos.phi || 0;
					const phiRad = (phi * Math.PI) / 180;
					// Shift Robot "Forward" from the charger center
					data.robotPos.x += 0.10 * Math.cos(phiRad);
					data.robotPos.y += 0.10 * Math.sin(phiRad);
				}
			}

			const assetKey = this.getRobotIconAssetKey(status);
			const robotImg = this.assets?.robot?.[assetKey] || this.assets?.robot?.[this.getRobotIconAssetKey(status)] || this.assets?.robot?.["cleaning"];

			if (robotImg) {
				const ROBOT_MAP_SIZE = this.SCALE * 1.1;
				this.drawMapAsset(
					ctx,
					data.robotPos,
					data.robotPos.phi || 0,
					robotImg,
					ROBOT_MAP_SIZE,
					0, // Zero Offset
					0, // Zero Offset
					toPixel
				);
			} else {
				// Fallback circle
				const { x: fx, y: fy } = toPixel(data.robotPos.x, data.robotPos.y);
				ctx.fillStyle = "#FFFFFF";
				ctx.shadowColor = "#00000040";
				ctx.shadowBlur = 2;
				ctx.beginPath();
				ctx.arc(fx, fy, 4.5, 0, 2 * Math.PI);
				ctx.fill();
				ctx.shadowBlur = 0;
			}
		} else if (this.adapter && data.chargerPos) {
			this.adapter.rLog("MapManager", duid, "Warn", "B01", undefined, "No Robot Position in Map Data", "warn");
		}
		// History maps have no robot and no station – nothing to draw, no log

		// 7. Room Labels
		if (data.rooms) {
			const LABEL_SCALE = 0.5;
			const fontSize = 10 * LABEL_SCALE;
			ctx.font = `600 ${fontSize}px sans-serif`;
			ctx.textBaseline = "middle";
			const shadowColor = "#FFFFFFB2";
			const textColor = "#333333ee";
			const iconSize = 12 * LABEL_SCALE;
			const iconMargin = 4 * LABEL_SCALE;

			data.rooms.forEach(r => {
				if (r.labelPos && r.roomName) {
					// 1. Determine Semantic Type: Priority = ID
					const finalType = ROOM_TYPE_MAP[r.roomTypeId || 0] || "other";

					// 2. Select Icon Asset
					// Priority 1: Asset matching Semantic Type (Name-derived or ID-derived)
					let roomIcon = this.assets?.rooms[finalType] || this.assets?.rooms[`bubble_${finalType}`];

					// Priority 2: Asset matching specific ID (if not found above)
					// Only use ID asset if we don't have a semantic override (or if semantic asset missing)
					if (!roomIcon) {
						roomIcon = this.assets?.rooms[`bubble_${r.roomTypeId}`];
					}

					// Priority 3: Fallback "other"
					if (!roomIcon && finalType === "other") {
						roomIcon = this.assets?.rooms["other"];
					}

					// 3. Select Color (Based STRICTLY on Room Color ID, not Name)
					const colorIdx = this.getRoomLabelColorIndex(r.colorId);
					const BG_COLOR_SET = SC_MAP_COLORS.ROOM_ICON_BG;
					const BORDER_COLOR_SET = SC_MAP_COLORS.ROOM_ICON_BORDER;

					const bgColor = BG_COLOR_SET[colorIdx % BG_COLOR_SET.length] || BG_COLOR_SET[0];
					const borderColor = BORDER_COLOR_SET[colorIdx % BORDER_COLOR_SET.length] || BORDER_COLOR_SET[0];

					const pt = toPixel(r.labelPos.x, r.labelPos.y);
					const textWidth = ctx.measureText(r.roomName).width;
					const totalWidth = iconSize + iconMargin + textWidth;

					let startX = pt.x - (totalWidth / 2);
					const centerY = pt.y;

					ctx.beginPath();
					ctx.arc(startX + iconSize / 2, centerY, iconSize / 2, 0, Math.PI * 2);
					ctx.fillStyle = bgColor;
					ctx.fill();
					ctx.strokeStyle = borderColor;
					ctx.lineWidth = 0.5 * LABEL_SCALE;
					ctx.stroke();

					if (roomIcon) {
						const imgSize = 10 * LABEL_SCALE; // Traced: 10px inside 12px
						ctx.drawImage(
							roomIcon,
							startX + (iconSize - imgSize) / 2,
							centerY - imgSize / 2,
							imgSize,
							imgSize
						);
					}

					startX += iconSize + iconMargin;
					const offset = 0.33 * LABEL_SCALE;
					ctx.textAlign = "left";
					ctx.fillStyle = shadowColor;
					ctx.fillText(r.roomName, startX - offset, centerY - offset);
					ctx.fillText(r.roomName, startX + offset, centerY + offset);
					ctx.fillStyle = textColor;
					ctx.fillText(r.roomName, startX, centerY);
				} else {
					if (this.adapter && typeof this.adapter.rLog === "function") {
						this.adapter.rLog("MapManager", null, "Warn", "B01", undefined, `Skipping room label for ${r.roomName}: missing labelPos`, "warn");
					}
				}
			});
		}

		return canvas.toBuffer("image/png");
	}
	private getRoomFillColorIndex(roomId: number, colorId?: number): number {
		if (colorId !== undefined) {
			return colorId > 0 ? colorId - 1 : 0;
		}
		return roomId > 0 ? roomId - 1 : 0;
	}

	private getRoomLabelColorIndex(colorId?: number): number {
		if (colorId !== undefined && colorId > 0) {
			return colorId - 1;
		}
		return 0;
	}
}
