import PQueue from "p-queue";
import { BaseDeviceFeatures, DeviceModelConfig, FeatureDependencies } from "../baseDeviceFeatures";
import { Feature } from "../features.enum";
import { StationService } from "./services/StationService";
import { V1ConsumableService } from "./services/V1ConsumableService";
import { V1MapService } from "./services/V1MapService";
import { VACUUM_CONSTANTS } from "./vacuumConstants";

// --- Shared Constants ---
export const BASE_FAN = { 101: "Quiet", 102: "Balanced", 103: "Turbo", 104: "Max" };

export const BASE_WATER = { 200: "Off", 201: "Mild", 202: "Moderate", 203: "Intense" };
export const BASE_MOP = { 300: "Standard", 301: "Deep", 303: "Deep+" };

// --- Profile Interface ---
export interface VacuumProfile {
	mappings: {
		fan_power: Record<number, string>;
		mop_mode?: Record<number, string>;
		water_box_mode?: Record<number, string>;
		error_code?: Record<number, string>;
		state?: Record<number, string>;
	};
	name?: string;
	features?: Record<string, any>;
	cleanMotorModePresets?: Record<string, string>;
	consumableLifeHours?: Record<string, number>;
}

export const DEFAULT_PROFILE: VacuumProfile = {
	mappings: {
		fan_power: BASE_FAN,
		mop_mode: { 300: "Standard", 301: "Deep", 303: "Deep+" },
		water_box_mode: { 200: "Off", 201: "Mild", 202: "Moderate", 203: "Intense" },
	},
};

export class V1VacuumFeatures extends BaseDeviceFeatures {
	private static readonly autoEmptyDockStartCommand = "app_start_collect_dust";

	protected profile: VacuumProfile;
	protected consumableService: V1ConsumableService;
	protected stationService: StationService;
	protected lastMapUpdate = 0;
	protected detectionComplete = false;

	protected mapService: V1MapService;

	constructor(dependencies: FeatureDependencies, duid: string, robotModel: string, config: DeviceModelConfig = { staticFeatures: [] }, profile: VacuumProfile = DEFAULT_PROFILE) {
		super(dependencies, duid, robotModel, config);

		// Deep clone profile to avoid mutating shared static objects
		this.profile = structuredClone(profile);
		this.consumableService = new V1ConsumableService(this.deps, this.duid, this.profile);
		this.stationService = new StationService(this.deps, this.duid);
		this.mapService = new V1MapService(this.deps, this.duid);
	}

	public override async initializeDeviceData(): Promise<void> {
		await this.updateMultiMapsList(); // 1. Load Floor List first (for names/metadata)
		await this.updateStatus();        // 2. Get Status (triggers Room sync via first floor detection)
		await this.updateMap();           // 3. Get Map Image

		// These can still be parallel as they don't depend on each other as much
		await Promise.all([
			this.updateFirmwareFeatures(),
			this.updateConsumables(),
			this.updateNetworkInfo(),
			this.updateTimers(),
		]);
	}

	/**
	 * Configures the standard command set for Protocol V1 devices.
	 * @see test/unit/features_specification.test.ts for the core vacuum command list.
	 */
	public override async setupProtocolFeatures(): Promise<void> {
		await super.setupProtocolFeatures();

		// Add Standard V1 Commands
		const translations = this.deps.adapter.translations;

		this.addCommand("app_start", { type: "boolean", role: "button", name: translations["app_start"] || "Start", def: false });
		this.addCommand("app_stop", { type: "boolean", role: "button", name: translations["app_stop"] || "Stop", def: false });
		this.addCommand("app_pause", { type: "boolean", role: "button", name: translations["app_pause"] || "Pause", def: false });
		this.addCommand("app_charge", { type: "boolean", role: "button", name: translations["app_charge"] || "Charge", def: false });
		this.addCommand("find_me", { type: "boolean", role: "button", name: translations["find_me"] || "Find Me", def: false });
		this.addCommand("app_spot", { type: "boolean", role: "button", name: translations["app_spot"] || "Spot Cleaning", def: false });
		this.addCommand("app_segment_clean", { type: "boolean", role: "button", name: "Segment Cleaning", def: false });

		// Restore missing standard V1 commands
		this.addCommand("app_zoned_clean", { type: "json", role: "json", name: "Zone Clean" }); // No default for JSON usually, or "[]"
		this.addCommand("resume_zoned_clean", { type: "boolean", role: "button", name: "Resume Zone Clean", def: false });
		this.addCommand("stop_zoned_clean", { type: "boolean", role: "button", name: "Stop Zone Clean", def: false });

		this.addCommand("resume_segment_clean", { type: "boolean", role: "button", name: "Resume Segment Clean", def: false });
		this.addCommand("stop_segment_clean", { type: "boolean", role: "button", name: "Stop Segment Clean", def: false });

		this.addCommand("app_goto_target", { type: "json", role: "json", name: "Go To Target" });

		this.addCommand("load_multi_map", { type: "number", role: "level", name: "Load Map", def: 0 });

		this.addCommand("set_custom_mode", {
			type: "number",
			role: "level",
			name: translations["fan_power"] || "Fan Power",
			states: this.profile.mappings.fan_power,
			def: Number(Object.keys(this.profile.mappings.fan_power)[0])
		});

		// Consolidated cleaning mode with all parameters (Custom Mode)
		// We define states (Presets) to make it selectable in UI
		this.addCommand("set_clean_motor_mode", {
			type: "string",
			role: "value", // changed from json to value to support dropdown
			name: "Set Custom Cleaning Mode",
			def: this.profile.cleanMotorModePresets ? Object.keys(this.profile.cleanMotorModePresets)[0] : '{"fan_power":102,"mop_mode":300,"water_box_mode":201}',
			states: this.profile.cleanMotorModePresets || {
				'{"fan_power":102,"mop_mode":300,"water_box_mode":201}': "Indv.",
				'{"fan_power":102,"mop_mode":300,"water_box_mode":200}': "Saugen",
				'{"fan_power":105,"mop_mode":303,"water_box_mode":202}': "Wischen",
				'{"fan_power":102,"mop_mode":301,"water_box_mode":201}': "Vac & Mop",
				'{"fan_power":102,"mop_mode":306,"water_box_mode":201}': "Saugen, dann Wischen",
				'{"fan_power":106,"mop_mode":302,"water_box_mode":204}': "Smart Plan"
			}
		});

		if (this.profile.mappings.water_box_mode) {
			this.addCommand("set_water_box_custom_mode", {
				type: "number",
				role: "level",
				name: translations["water_box_mode"] || "Water Box Mode",
				states: this.profile.mappings.water_box_mode,
				def: Number(Object.keys(this.profile.mappings.water_box_mode)[0])
			});
		}

		if (this.profile.mappings.mop_mode) {
			this.addCommand("set_mop_mode", {
				type: "number",
				role: "level",
				name: translations["mop_mode"] || "Mop Mode",
				states: this.profile.mappings.mop_mode,
				def: Number(Object.keys(this.profile.mappings.mop_mode)[0])
			});
		}

		// A101 Specific: Water Box Distance Off (1-30 -> 230-85)
		if (this.profile.features?.hasDistanceOff) {
			this.addCommand("set_water_box_distance_off", {
				type: "number",
				role: "level",
				name: translations["water_box_distance_off"] || "Water Box Distance Off (1-30)",
				min: 1,
				max: 30,
				unit: "",
				def: 1
			});
		}

		this.addCommand("set_clean_repeat_times", {
			type: "number",
			role: "value",
			name: "Clean Repeat Times",
			min: 1,
			max: 2,
			def: 1,
			states: { 1: "1x", 2: "2x" }
		});
	}

	public async detectAndApplyRuntimeFeatures(statusData: Readonly<Record<string, any>>): Promise<boolean> {
		let changed = false;

		// Detect features based on status keys
		if (("clean_area" in statusData || "clean_time" in statusData) && await this.applyFeature(Feature.CleaningRecords)) {
			changed = true;
		}

		if (("map_status" in statusData) && await this.applyFeature(Feature.Map)) {
			changed = true;
		}

		if (statusData["water_shortage_status"] !== undefined && await this.applyFeature(Feature.WaterShortage)) {
			changed = true;
		}

		// Consumables detection (usually static, but can check for keys)
		if (await this.applyFeature(Feature.Consumables)) changed = true;

		if (statusData["dss"] !== undefined) {
			const dss = Number(statusData["dss"]);
			// DockingStationStatus: no applyFeature – folder/states created lazily in updateDockingStationStatus()

			// Bits 6-7: Dust bag status (0=not supported/missing)
			if (((dss >> 6) & 0b11) > 0) {
				await this.applyFeature(Feature.AutoEmptyDock);
			}
			// Bits 4-5: Dirty water tank status (0=not supported/missing)
			// Bits 10-11: Clean water tank status
			if (((dss >> 4) & 0b11) > 0 || ((dss >> 10) & 0b11) > 0) {
				await this.applyFeature(Feature.MopWash);
			}
		}

		if (!this.runtimeDetectionComplete) {
			this.runtimeDetectionComplete = true;
			changed = true;
		}
		return changed;
	}

	@BaseDeviceFeatures.DeviceFeature(Feature.Consumables)
	public async updateConsumables(): Promise<void> {
		await this.consumableService.updateConsumables();
	}

	@BaseDeviceFeatures.DeviceFeature(Feature.Map)
	public async updateMap(): Promise<void> {
		await this.mapService.updateMap();
	}

	public async getCleaningRecordMap(startTime: number) {
		return this.mapService.getCleaningRecordMap(startTime);
	}

	@BaseDeviceFeatures.DeviceFeature(Feature.DockingStationStatus)
	protected async initDockingStationStatus(): Promise<void> {
		await this.stationService.initDockingStationStatus();
	}

	/**
	 * Updates docking station status from dss bitfield. Fully dynamic: if the device
	 * sends dss in get_status, we ensure folder/states exist (lazy init) and update.
	 * No per-model or feature-guard – presence of dss means the robot supports it.
	 */
	public async updateDockingStationStatus(dss: number): Promise<void> {
		await this.stationService.initDockingStationStatus(); // idempotent: ensure folder + states
		await this.stationService.updateDockingStationStatus(dss);
	}

	@BaseDeviceFeatures.DeviceFeature(Feature.MultiMap)
	public async updateMultiMapsList(): Promise<void> {
		const mapList = await this.mapService.updateMultiMapsList();
		if (mapList && Array.isArray(mapList)) {
			// Update the load_multi_map command states to populate dropdown
			const states: Record<string, string> = {};
			for (const map of mapList) {
				states[String(map.mapFlag)] = map.name || `Map ${map.mapFlag}`;
			}

			await this.deps.adapter.extendObject(`Devices.${this.duid}.commands.load_multi_map`, {
				common: {
					type: "number",
					role: "value",
					states: states
				}
			});
		} else {
			await super.updateMultiMapsList();
		}
	}

	@BaseDeviceFeatures.DeviceFeature(Feature.RoomMapping)
	public async updateRoomMapping(): Promise<void> {
		await this.mapService.updateRoomMapping();
	}

	public override async getCommandParams(method: string, params?: unknown, id?: string): Promise<unknown> {
		if (method === "reset_consumable" && id) {
			const obj = await this.deps.adapter.getObjectAsync(id);
			if (obj && obj.native && obj.native.resetParam) {
				const resetParam = obj.native.resetParam;
				this.deps.adapter.rLog("System", this.duid, "Info", "1.0", undefined, `Resetting consumable: ${resetParam} (via native param)`, "info");
				return [resetParam];
			}
			// Fallback if no native param (should not happen with new setup)
			this.deps.adapter.rLog("System", this.duid, "Warn", "1.0", undefined, `Reset consumable called without native param for ${id}`, "warn");
		}

		if (method === "set_clean_motor_mode") {
			// Log shows "set_clean_motor_mode" works, but expects params as array: [{...}]
			let finalParams = params;

			// If input is a string (e.g. from Dropdown/Presets), parse it first
			if (typeof finalParams === "string") {
				try {
					finalParams = JSON.parse(finalParams);
				} catch (e: any) {
					this.deps.adapter.rLog("Requests", this.duid, "Warn", this.protocolVersion || undefined, undefined, `[getCommandParams] Failed to parse set_clean_motor_mode params: ${finalParams} - Error: ${e.message}`, "warn");
				}
			}

			if (finalParams && !Array.isArray(finalParams)) {
				finalParams = [finalParams];
			}
			return {
				method: "set_clean_motor_mode",
				params: finalParams
			};
		}

		if (method === "load_multi_map") {
			// Reset current map index -> Next status update triggers room refresh
			this.mapService.resetCurrentMapIndex();

			// User request: active fetch status after map load to ensure trigger
			// We trigger these in the background immediately
			(async () => {
				await new Promise(r => setTimeout(r, 2000));
				await this.updateStatus().catch(() => {});
				await this.mapService.updateMap().catch(() => {});
				await this.mapService.updateRoomMapping().catch(() => {});
			})();

			// V1 protocol (0.6.19) expects [number] for load_multi_map
			return [params];
		}

		if (method === "app_segment_clean") {
			const repeat = await this.getCleanRepeatTimes();

			// If params are explicitly provided (e.g. from single room button), use them.
			if (params && (Array.isArray(params) || typeof params === "object")) {
				// If it's just a room ID or array of IDs, wrap it in the correct payload structure
				if (Array.isArray(params) && typeof params[0] === "number") {
					const roomIds = params as number[];
					this.deps.adapter.rLog("System", this.duid, "Info", "1.0", undefined, `Starting segment cleaning for specific rooms: ${roomIds.join(", ")} with repeat ${repeat}`, "info");
					return [{
						segments: roomIds,
						repeat,
						clean_order_mode: 0,
						clean_mop: 0
					}];
				}
				return params;
			}

			// Gather selected rooms from floors
			const namespace = this.deps.adapter.namespace;
			// Pattern to find states under floors. Structure: Devices.<duid>.floors.<floorID>.<roomID>
			const pattern = `${namespace}.Devices.${this.duid}.floors.*.*`;
			const states = await this.deps.adapter.getStatesAsync(pattern);
			const roomIds: number[] = [];

			if (states) {
				for (const [id, state] of Object.entries(states)) {
					if (state && (state.val === true || state.val === "true" || state.val === 1)) {
						// Extract Room ID directly from the state path (last segment)
						const parts = id.split(".");
						const rid = Number(parts[parts.length - 1]);
						if (!isNaN(rid)) {
							roomIds.push(rid);
						}
					}
				}
			}

			if (roomIds.length > 0) {
				this.deps.adapter.rLog("System", this.duid, "Info", "1.0", undefined, `Starting segment cleaning for rooms: ${roomIds.join(", ")} with repeat ${repeat}`, "info");

				// Params:
				// params: [{"clean_mop":0,"clean_order_mode":0,"repeat":2,"segments":[2,1]}]
				const payload = [{
					segments: roomIds,
					repeat,
					clean_order_mode: 0,
					clean_mop: 0
				}];

				return payload;
			} else {
				this.deps.adapter.rLog("System", this.duid, "Warn", "1.0", undefined, `No rooms selected for segment cleaning!`, "warn");
				return [];
			}
		}

		if (method === "set_custom_mode") {
			return [Number(params)];
		}

		if (method === "set_mop_mode") {
			return [Number(params)];
		}

		if (method === "set_water_box_custom_mode") {
			return [Number(params)];
		}

		if (method === "set_water_box_distance_off") {
			// Convert 1-30 slider to 230-85 robot value
			// Formula: 230 - ((val - 1) * 5)
			let val = Number(params);
			if (isNaN(val)) val = 1;
			if (val < 1) val = 1;
			if (val > 30) val = 30;

			const distance_off = 230 - ((val - 1) * 5);
			return { distance_off };
		}

		if (method === "set_clean_repeat_times") {
			let repeat = Number(params);
			if (isNaN(repeat)) repeat = 1;
			return { repeat };
		}

		if (method === V1VacuumFeatures.autoEmptyDockStartCommand) {
			return [];
		}

		return params;
	}

	private normalizeCleanRepeat(value: unknown): number | null {
		const repeat = Number(value);
		return Number.isInteger(repeat) && repeat > 0 ? repeat : null;
	}

	private async getCleanRepeatTimes(): Promise<number> {
		const commandState = await this.deps.adapter.getStateAsync(`Devices.${this.duid}.commands.set_clean_repeat_times`);
		const commandRepeat = this.normalizeCleanRepeat(commandState?.val);
		if (commandRepeat !== null) return commandRepeat;

		const statusState = await this.deps.adapter.getStateAsync(`Devices.${this.duid}.deviceStatus.repeat`);
		return this.normalizeCleanRepeat(statusState?.val) ?? 1;
	}

	public async updateCleanSummary(): Promise<void> {
		try {
			const result = await this.deps.adapter.requestsHandler.sendRequest(this.duid, "get_clean_summary", []);
			const summary = this.normalizeV1CleanSummary(result);

			if (!summary) {
				this.deps.adapter.rLog("System", this.duid, "Warn", "1.0", undefined, "Invalid V1 clean summary format", "warn");
				return;
			}

			await this.processV1CleanSummary(summary);
		} catch (e: unknown) {
			this.deps.adapter.rLog("System", this.duid, "Warn", undefined, undefined, `Failed to update cleaningInfo (method: get_clean_summary): ${this.deps.adapter.errorMessage(e)}`, "warn");
		}
	}

	private normalizeV1CleanSummary(result: unknown): Record<string, unknown> | null {
		const unwrapped = this.unwrapSingleElementArrays(result);

		if (Array.isArray(unwrapped)) {
			const numericEntries = this.getIndexedNumbers(unwrapped);
			if (numericEntries.length === 0) return null;

			const records = this.findRecordStartTimes(unwrapped);
			const summary = this.inferV1CleanSummaryFields(numericEntries, records.length);
			for (const { index, value } of numericEntries) {
				summary[`field_${index}`] = value;
			}
			summary.records = records;
			return summary;
		}

		if (this.isPlainObject(unwrapped)) {
			const summary = { ...unwrapped };
			if (Array.isArray(summary.records)) {
				summary.records = this.normalizeRecordStartTimes(summary.records);
			}
			return summary;
		}

		return null;
	}

	private inferV1CleanSummaryFields(entries: Array<{ index: number; value: number }>, recordCount: number): Record<string, unknown> {
		const summary: Record<string, unknown> = {};
		const used = new Set<number>();

		const area = entries
			.filter(entry => entry.value >= 1_000_000)
			.sort((a, b) => b.value - a.value)[0];
		if (area) {
			summary.clean_area = area.value;
			used.add(area.index);
		}

		const count = entries
			.filter(entry => !used.has(entry.index) && entry.value >= recordCount && entry.value < 1_000_000)
			.sort((a, b) => a.value - b.value)[0];
		if (count) {
			summary.clean_count = count.value;
			used.add(count.index);
		}

		const time = entries
			.filter(entry => !used.has(entry.index))
			.sort((a, b) => b.value - a.value)[0];
		if (time) {
			summary.clean_time = time.value;
		}

		return summary;
	}

	private async processV1CleanSummary(summary: Record<string, unknown>): Promise<void> {
		const records = Array.isArray(summary.records) ? this.normalizeRecordStartTimes(summary.records).sort((a, b) => b - a) : [];
		const recordPayloads = await this.fetchV1CleanRecordPayloads(records);
		const rest = { ...summary };
		delete rest.records;

		await this.deps.ensureFolder(`Devices.${this.duid}.cleaningInfo`);
		for (const key in rest) {
			await this.processResultKey("cleaningInfo", key, rest[key]);
		}

		await this.writeV1CleaningInfoJson(records, recordPayloads);
		await this.syncV1CleanRecords(records, recordPayloads);
	}

	private async fetchV1CleanRecordPayloads(records: number[]): Promise<Map<number, unknown>> {
		const payloads = new Map<number, unknown>();

		for (const startTime of records) {
			try {
				const rawRecord = await this.deps.adapter.requestsHandler.sendRequest(this.duid, "get_clean_record", [startTime]);
				const payload = this.unwrapSingleElementArrays(rawRecord);
				if (!(Array.isArray(payload) && payload.length === 0)) {
					payloads.set(startTime, payload);
				}
			} catch (e: unknown) {
				this.deps.adapter.rLog("System", this.duid, "Warn", "1.0", undefined, `Failed to fetch clean record ${startTime} for cleaningInfo.JSON: ${this.deps.adapter.errorMessage(e)}`, "warn");
			}
		}

		return payloads;
	}

	private async writeV1CleaningInfoJson(records: number[], recordPayloads: Map<number, unknown>): Promise<void> {
		const jsonRecords = records.map(startTime => recordPayloads.get(startTime) ?? null);

		await this.deps.ensureState(`Devices.${this.duid}.cleaningInfo.JSON`, {
			name: "cleaningInfoJSON",
			type: "string",
			role: "json",
			read: true,
			write: false
		});
		await this.deps.adapter.setStateChanged(`Devices.${this.duid}.cleaningInfo.JSON`, {
			val: JSON.stringify(jsonRecords),
			ack: true
		});
	}

	private async syncV1CleanRecords(records: number[], recordPayloads: Map<number, unknown> = new Map()): Promise<void> {
		if (records.length === 0) return;

		const mapQueue = new PQueue({ concurrency: 1 });
		const existingStartTimes: Record<string, number> = {};
		const namespace = this.deps.adapter.namespace;
		const recordIds = records.map((_, i) => `${namespace}.Devices.${this.duid}.cleaningInfo.records.${i}.startTime`);
		const states = await this.deps.adapter.getForeignStatesAsync(recordIds);

		if (states) {
			for (const id in states) {
				if (states[id] && states[id].val) {
					const parts = id.split(".");
					const index = parseInt(parts[parts.length - 2]);
					if (!isNaN(index)) {
						existingStartTimes[String(states[id].val)] = index;
					}
				}
			}
		}

		const sortedRecords = [...records].sort((a, b) => b - a);
		const moves: { old: number, new: number }[] = [];
		const newRecs: { index: number, time: number }[] = [];

		for (let i = 0; i < sortedRecords.length; i++) {
			const time = sortedRecords[i];
			const oldIndex = existingStartTimes[time];

			if (oldIndex !== undefined && oldIndex !== i) {
				moves.push({ old: oldIndex, new: i });
			} else if (oldIndex === undefined) {
				newRecs.push({ index: i, time });
			}
		}

		const leftShifts = moves.filter(m => m.old > m.new).sort((a, b) => a.new - b.new);
		for (const m of leftShifts) {
			await this.copyRecordStates(m.old, m.new);
		}

		const rightShifts = moves.filter(m => m.old < m.new).sort((a, b) => b.new - a.new);
		for (const m of rightShifts) {
			await this.copyRecordStates(m.old, m.new);
		}

		for (const { index, time } of newRecs) {
			await this.fetchAndSaveRecord(time, index, index < 3 ? 10 : 0, mapQueue, recordPayloads.get(time));
		}

		await mapQueue.onIdle();
	}

	private async copyRecordStates(from: number, to: number): Promise<void> {
		const prefix = `Devices.${this.duid}.cleaningInfo.records`;
		const states = await this.deps.adapter.getStatesAsync(`${prefix}.${from}.*`);
		if (!states) return;

		await Promise.all(Object.entries(states).map(async ([id, state]) => {
			if (!state || state.val === null) return;
			const obj = await this.deps.adapter.getObjectAsync(id);
			if (!obj?.common) return;

			// Replace index in path (roborock.0.Devices...records.5... -> ...records.6...)
			const destRel = id.substring(this.deps.adapter.namespace.length + 1).replace(`.records.${from}.`, `.records.${to}.`);
			await this.deps.ensureState(destRel, obj.common as Partial<ioBroker.StateCommon>);
			await this.deps.adapter.setStateChanged(destRel, { val: state.val, ack: true });
		}));
	}

	private async fetchAndSaveRecord(startTime: number, index: number, priority: number, queue: PQueue, prefetchedRecord?: unknown): Promise<void> {
		queue.add(async () => {
			try {
				const fullRecordPath = `cleaningInfo.records.${index}`;

				// 1. Set Timestamp
				await this.deps.ensureState(`Devices.${this.duid}.${fullRecordPath}.startTime`, { name: "Start Time", type: "number", role: "value.time", write: false });
				await this.deps.adapter.setStateChanged(`Devices.${this.duid}.${fullRecordPath}.startTime`, { val: startTime, ack: true });

				// 2. Fetch Metadata
				const recordsDetails = prefetchedRecord !== undefined
					? prefetchedRecord
					: await this.deps.adapter.requestsHandler.sendRequest(this.duid, "get_clean_record", [startTime]);
				const record = this.normalizeV1CleanRecord(recordsDetails);
				if (record) {
					for (const key in record) {
						let val = record[key];
						if (key === "area" || key === "cleaned_area") val = Math.round(Number(val) / 1000000);
						else if (key === "duration") val = Math.round(Number(val) / 60);
						await this.processResultKey(fullRecordPath, key, val);
					}
				}

				// 3. Fetch Map only when map creation is enabled (records metadata is always saved above)
				if (this.deps.config.enable_map_creation) {
					const mapResult = await this.mapService.getCleaningRecordMap(startTime);
					if (mapResult) {
						const mapFolder = `records.${index}.map`;
						await this.deps.ensureFolder(`Devices.${this.duid}.cleaningInfo.${mapFolder}`);

						const saveMap = async (suffix: string, name: string, val: string, role = "text.png") => {
							await this.deps.ensureState(`Devices.${this.duid}.cleaningInfo.${mapFolder}.${suffix}`, { name, type: "string", role });
							await this.deps.adapter.setStateChanged(`Devices.${this.duid}.cleaningInfo.${mapFolder}.${suffix}`, { val, ack: true });
						};

						await saveMap("mapBase64", "Map Image", mapResult.mapBase64);

						await saveMap("mapData", "Map Data", mapResult.mapData, "json");
					} else {
						this.deps.adapter.rLog("MapManager", this.duid, "Warn", "1.0", undefined, `No map found for record ${startTime}`, "warn");
					}
				}
			} catch (e: any) {
				this.deps.adapter.rLog("System", this.duid, "Warn", "1.0", undefined, `Background fetch for record ${startTime} failed: ${e.message}`, "warn");
			}
		}, { priority });
	}

	private normalizeV1CleanRecord(result: unknown): Record<string, unknown> | null {
		const unwrapped = this.unwrapSingleElementArrays(result);

		if (Array.isArray(unwrapped)) {
			const record = this.inferV1CleanRecordFields(unwrapped);
			unwrapped.forEach((value, index) => {
				if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
					record[`field_${index}`] = value;
				}
			});
			return Object.keys(record).length > 0 ? record : null;
		}

		if (this.isPlainObject(unwrapped)) {
			return { ...unwrapped };
		}

		return null;
	}

	private inferV1CleanRecordFields(values: unknown[]): Record<string, unknown> {
		const record: Record<string, unknown> = {};
		const timestampPairIndex = this.findAdjacentTimestampPairIndex(values);
		if (timestampPairIndex === -1) return record;

		record.begin = values[timestampPairIndex];
		record.end = values[timestampPairIndex + 1];

		const duration = values[timestampPairIndex + 2];
		if (typeof duration === "number" && Number.isFinite(duration) && duration >= 0) {
			record.duration = duration;
		}

		const area = values[timestampPairIndex + 3];
		if (typeof area === "number" && Number.isFinite(area) && area >= 0) {
			record.area = area;
		}

		return record;
	}

	private findAdjacentTimestampPairIndex(values: unknown[]): number {
		for (let i = 0; i < values.length - 1; i++) {
			const begin = values[i];
			const end = values[i + 1];
			if (this.isUnixTimestamp(begin) && this.isUnixTimestamp(end) && begin <= end) {
				return i;
			}
		}
		return -1;
	}

	private isUnixTimestamp(value: unknown): value is number {
		return typeof value === "number" && Number.isFinite(value) && value > 946684800 && value < 4102444800;
	}

	private getIndexedNumbers(values: unknown[]): Array<{ index: number; value: number }> {
		const result: Array<{ index: number; value: number }> = [];
		values.forEach((value, index) => {
			if (typeof value === "number" && Number.isFinite(value)) {
				result.push({ index, value });
			}
		});
		return result;
	}

	private findRecordStartTimes(values: unknown[]): number[] {
		for (const value of values) {
			if (Array.isArray(value)) {
				const records = this.normalizeRecordStartTimes(value);
				if (records.length > 0) return records;
			}
		}
		return [];
	}

	private normalizeRecordStartTimes(records: unknown): number[] {
		if (!Array.isArray(records)) return [];
		return records.map(record => Number(record)).filter(Number.isFinite);
	}

	private unwrapSingleElementArrays(value: unknown): unknown {
		let unwrapped = value;
		while (Array.isArray(unwrapped) && unwrapped.length === 1) {
			unwrapped = unwrapped[0];
		}
		return unwrapped;
	}

	private isPlainObject(value: unknown): value is Record<string, unknown> {
		return typeof value === "object" && value !== null && !Array.isArray(value) && !Buffer.isBuffer(value);
	}

	public override async updateStatus(): Promise<void> {
		try {
			const result = await this.deps.adapter.requestsHandler.sendRequest(this.duid, "get_prop", ["get_status"]);
			const statusData = Array.isArray(result) ? result[0] : result;

			if (statusData && typeof statusData === "object") {
				if (!this.runtimeDetectionComplete) {
					await this.detectAndApplyRuntimeFeatures(statusData);
				}
				await this.processStatus(statusData);
				const c = await this.deps.adapter.getStateAsync(`Devices.${this.duid}.cleaningInfo.clean_count`);
				this.deps.adapter.rLog("System", this.duid, "Debug", "1.0", undefined, `status=${statusData.state ?? "?"}, clean_count=${c?.val ?? "?"}`, "debug");
			}
		} catch (e: any) {
			this.deps.adapter.rLog("System", this.duid, "Warn", undefined, undefined, `Failed to update status: ${e.message}`, "warn");
			throw e;
		}
	}

	public async updateTimers(): Promise<void> {
		try {
			const timers = await this.deps.adapter.requestsHandler.sendRequest(this.duid, "get_timer", []);
			if (Array.isArray(timers)) {
				await this.deps.ensureFolder(`Devices.${this.duid}.schedules`);
				await Promise.all(timers.map(async (timer) => {
					// timer structure: [id, enabled, [cron, [cmd, params], createTime]]
					if (Array.isArray(timer) && timer.length >= 3) {
						const id = timer[0];
						const enabled = timer[1] === "on";
						const segments = timer[2];
						const cron = Array.isArray(segments) ? segments[0] : "";

						await this.deps.ensureFolder(`Devices.${this.duid}.schedules.${id}`);

						await this.deps.ensureState(`Devices.${this.duid}.schedules.${id}.enabled`, { name: "Enabled", type: "boolean", role: "switch", write: true });
						await this.deps.adapter.setStateChanged(`Devices.${this.duid}.schedules.${id}.enabled`, { val: enabled, ack: true });

						await this.deps.ensureState(`Devices.${this.duid}.schedules.${id}.cron`, { name: "CRON", type: "string", role: "text", write: false });
						await this.deps.adapter.setStateChanged(`Devices.${this.duid}.schedules.${id}.cron`, { val: cron, ack: true });
					}
				}));
			}
		} catch (e: any) {
			this.deps.adapter.rLog("System", this.duid, "Warn", undefined, undefined, `Failed to update timers: ${e.message}`, "warn");
		}
	}

	public async processStatus(status: any): Promise<void> {
    	const validStatus = status || {};

		if (validStatus.dss !== undefined) {
			await this.updateDockingStationStatus(Number(validStatus.dss));
			delete validStatus.dss;
		}

    	// Define property processing map
    	const processors: Record<string, (val: any) => Promise<void>> = {
    		state: async (val) => {
    			await this.deps.ensureState(`Devices.${this.duid}.deviceStatus.state`, { type: "number", states: this.profile.mappings.state || VACUUM_CONSTANTS.stateCodes });
    			await this.deps.adapter.setStateChanged(`Devices.${this.duid}.deviceStatus.state`, { val, ack: true });
    		},
    		error_code: async (val) => {
    			await this.deps.ensureState(`Devices.${this.duid}.deviceStatus.error_code`, { type: "number", states: this.profile.mappings.error_code || VACUUM_CONSTANTS.errorCodes });
    			await this.deps.adapter.setStateChanged(`Devices.${this.duid}.deviceStatus.error_code`, { val, ack: true });
    		},
    		fan_power: async (val) => {
    			await this.deps.ensureState(`Devices.${this.duid}.deviceStatus.fan_power`, { type: "number", states: this.profile.mappings.fan_power });
    			await this.deps.adapter.setStateChanged(`Devices.${this.duid}.deviceStatus.fan_power`, { val, ack: true });
				// Sync to command state
				await this.deps.adapter.setStateChanged(`Devices.${this.duid}.commands.set_custom_mode`, { val, ack: true });
    		},
    		mop_mode: async (val) => {
    			if (this.profile.mappings.mop_mode) {
    				await this.deps.ensureState(`Devices.${this.duid}.deviceStatus.mop_mode`, { type: "number", states: this.profile.mappings.mop_mode });
    				await this.deps.adapter.setStateChanged(`Devices.${this.duid}.deviceStatus.mop_mode`, { val, ack: true });
					// Sync to command state
					await this.deps.adapter.setStateChanged(`Devices.${this.duid}.commands.set_mop_mode`, { val, ack: true });
    			}
    		},
    		water_box_mode: async (val) => {
    			if (this.profile.mappings.water_box_mode) {
    				await this.deps.ensureState(`Devices.${this.duid}.deviceStatus.water_box_mode`, { type: "number", states: this.profile.mappings.water_box_mode });
    				await this.deps.adapter.setStateChanged(`Devices.${this.duid}.deviceStatus.water_box_mode`, { val, ack: true });
					// Sync to command state
					await this.deps.adapter.setStateChanged(`Devices.${this.duid}.commands.set_water_box_custom_mode`, { val, ack: true });
    			}
    		}
    	};

    	// Parallel processing of remaining status properties
    	const promises: Promise<void>[] = [];
    	for (const key in validStatus) {
    		if (processors[key]) {
    			promises.push(processors[key](validStatus[key]));
    		} else {
    			// Default handler for generic properties
    			promises.push(this.processResultKey("deviceStatus", key, validStatus[key]));
    		}
    	}

    	await Promise.all(promises);
	}

	protected getDynamicFeatures(): Set<Feature> {
		// v1 dynamic features
		const features = new Set<Feature>();
		if (this.config.staticFeatures) {
			this.config.staticFeatures.forEach(f => features.add(f));
		}
		return features;
	}

	// --- Abstract Method Implementations ---

	public getCommonConsumable(attribute: string | number): Partial<ioBroker.StateCommon> | undefined {
		return (VACUUM_CONSTANTS.consumables as any)[attribute];
	}

	public isResetableConsumable(consumable: string): boolean {
		return VACUUM_CONSTANTS.resetConsumables.has(consumable);
	}

	public getCommonDeviceStates(attribute: string | number): Partial<ioBroker.StateCommon> | undefined {
		return (VACUUM_CONSTANTS.deviceStates as any)[attribute];
	}

	public getCommonCleaningRecords(attribute: string | number): Partial<ioBroker.StateCommon> | undefined {
		return (VACUUM_CONSTANTS.cleaningRecords as any)[attribute];
	}

	public getFirmwareFeatureName(featureID: string | number): string {
		return (VACUUM_CONSTANTS.firmwareFeatures as any)[featureID] || `Feature ${featureID}`;
	}

	public getCommonCleaningInfo(attribute: string | number): Partial<ioBroker.StateCommon> | undefined {
		return (VACUUM_CONSTANTS.cleaningInfo as any)[attribute];
	}

	@BaseDeviceFeatures.DeviceFeature(Feature.AutoEmptyDock)
	public async initAutoEmptyDock(): Promise<void> {
		this.addCommand(V1VacuumFeatures.autoEmptyDockStartCommand, {
			type: "boolean",
			role: "button",
			name: "Start Collect Dust",
			def: false
		});
	}

	@BaseDeviceFeatures.DeviceFeature(Feature.MopWash)
	public async initMopWash(): Promise<void> {
		this.addCommand("app_start_wash", {
			type: "boolean",
			role: "button",
			name: "Start Mop Wash",
			def: false
		});
		this.addCommand("app_stop_wash", {
			type: "boolean",
			role: "button",
			name: "Stop Mop Wash",
			def: false
		});
	}

	@BaseDeviceFeatures.DeviceFeature(Feature.MopDry)
	public async initMopDry(): Promise<void> {
		this.addCommand("app_start_mop_drying", {
			type: "boolean",
			role: "button",
			name: "Start Mop Drying",
			def: false
		});
		this.addCommand("app_stop_mop_drying", {
			type: "boolean",
			role: "button",
			name: "Stop Mop Drying",
			def: false
		});
	}

	protected override async processResultKey(folder: string, key: string, val: unknown): Promise<void> {
		if (key === "map_status") {
			const mapIdxChanged = this.mapService.updateCurrentMapIndex(Number(val));

			if (mapIdxChanged) {
				this.deps.adapter.rLog("MapManager", this.duid, "Info", "1.0", undefined, `[MapSync] Map changed to index ${this.mapService.currentIndex}. Updating room mapping.`, "info");
				await this.updateRoomMapping();
			}
		} else if (key === "clean_time") {
			// cleaningInfo (Total) = Hours, deviceStatus (Current) = Minutes
			const divisor = folder.includes("cleaningInfo") ? 3600 : 60;
			val = Math.round(Number(val) / divisor);
		} else if (key === "clean_area") {
			val = Math.round(Number(val) / 1000000); // mm² -> m²
		}

		await super.processResultKey(folder, key, val);
	}

	public override getCurrentMapIndex(): number {
		return this.mapService.currentIndex;
	}
}
