/**
 * Worker process for reading weight from an industrial scale via Modbus RTU
 * Based on leer_peso.js - communicates with main process via IPC
 */

const ModbusRTU = require("modbus-serial");

// Configuration from environment variables
const PORT = process.env.SCALE_PORT || "";
const BAUD = parseInt(process.env.SCALE_BAUD || "9600", 10);
const SLAVE_ID = parseInt(process.env.SCALE_SLAVE_ID || "1", 10);
const START_ADDR = parseInt(process.env.SCALE_START_ADDR || "0", 10);
const POLL_MS = parseInt(process.env.SCALE_POLL_MS || "500", 10);
const WORD_ORDER = (process.env.SCALE_WORD_ORDER || "BA").toUpperCase();
const REGISTER_KIND = (process.env.SCALE_REGISTERS || "holding").toLowerCase();
const REGISTER_COUNT = parseInt(process.env.SCALE_REGISTER_COUNT || "3", 10);
const parsedDivisor = parseFloat(process.env.SCALE_DIV || "1000");
const SCALE_DIVISOR = Number.isFinite(parsedDivisor) && parsedDivisor > 0 ? parsedDivisor : 1000;
const DEBUG = process.env.SCALE_DEBUG === "1";
const SETUP_ADDRESS_MODE = (process.env.SCALE_SETUP_ADDR_MODE || "auto").toLowerCase();

const client = new ModbusRTU();
let pollTimer = null;
let isConnected = false;
let isExiting = false;
const REG_DEC = {
	filtro: 101,
	peso_nominal: 102,
	sens_cel1: 201,
	sens_cel2: 202,
	sens_cel3: 203,
	sens_cel4: 204,
	peso_proceso: 1000,
	tara: 1001,
	peso_muerto: 1002,
	ganancia: 1003,
	tarar: 1200,
	coger_peso_muerto: 1300,
};
const REG_HEX = {
	filtro: 0x0101,
	peso_nominal: 0x0102,
	sens_cel1: 0x0201,
	sens_cel2: 0x0202,
	sens_cel3: 0x0203,
	sens_cel4: 0x0204,
	peso_proceso: 0x1000,
	tara: 0x1001,
	peso_muerto: 0x1002,
	ganancia: 0x1003,
	tarar: 0x1200,
	coger_peso_muerto: 0x1300,
};
let setupRegs = REG_DEC;
let setupAddressModeActive = "dec";
let setupMapDetected = false;
const WG_REG = {
	WEIGHT_INT_HI: 0x00,
	WEIGHT_INT_LO: 0x01,
	WEIGHT_DECIMAL: 0x02,
	TARE: 0xef,
	CALIBRATION: 0xf0,
	WRITE_PROTECTION: 0xf2,
};

function log(message) {
	if (DEBUG) {
		console.log("[ScaleWorker]", message);
	}
	sendMessage({ type: "log", message });
}

function sendMessage(message) {
	if (process.send && typeof process.send === "function") {
		try {
			process.send(message);
		} catch (error) {
			console.error("[ScaleWorker] Failed to send message:", error);
		}
	}
}

function sendSetupLog(message, meta = null) {
	sendMessage({
		type: "setupLog",
		message: String(message),
		meta: meta && typeof meta === "object" ? meta : null,
		timestamp: Date.now(),
	});
}

function sendUpdate(payload) {
	sendMessage({ type: "update", payload });
}

function sendError(error) {
	sendMessage({ type: "error", error: String(error) });
}

function toSigned32(hi, lo) {
	const unsigned = ((hi << 16) >>> 0) + (lo & 0xffff);
	return unsigned > 0x7fffffff ? unsigned - 0x100000000 : unsigned;
}

function toSigned16(value) {
	const unsigned = value & 0xffff;
	return unsigned > 0x7fff ? unsigned - 0x10000 : unsigned;
}

async function readRawWeight() {
	const method = REGISTER_KIND === "input" ? client.readInputRegisters.bind(client) : client.readHoldingRegisters.bind(client);

	// WG18A02: registers 0x00, 0x01, 0x02 contain signed integer + one decimal.
	if (START_ADDR === 0) {
		const { data } = await method(START_ADDR, 3);
		const intHi = data[0] & 0xffff;
		const intLo = data[1] & 0xffff;
		const decimalRaw = data[2] & 0xffff;
		const integerPart = toSigned32(intHi, intLo);
		const decimalPart = Math.max(0, decimalRaw % 10);
		const sign = integerPart < 0 ? -1 : 1;
		// Returns value with one decimal in the module unit (WG18 manual uses grams).
		return integerPart + sign * (decimalPart / 10);
	}

	// Legacy FORCE-RS4 single/dual-register modes.
	if (REGISTER_COUNT <= 1) {
		const { data } = await method(START_ADDR, 1);
		return toSigned16(data[0]);
	}

	const { data } = await method(START_ADDR, 2);
	const [r0, r1] = data;
	const [hi, lo] = WORD_ORDER === "BA" ? [r1, r0] : [r0, r1];
	return toSigned32(hi, lo);
}

async function readGainRegister() {
	return readUnsignedRegister(setupRegs.ganancia, "GANANCIA");
}

async function readUnsignedRegister(register, label = `READ_${register}`) {
	const errors = [];

	try {
		const { data } = await client.readHoldingRegisters(register, 1); // FC03
		sendSetupLog(`${label}: leído por FC03`, { register, value: data[0] & 0xffff });
		return data[0] & 0xffff;
	} catch (error) {
		errors.push(`FC03=${error.message || String(error)}`);
		sendSetupLog(`${label}: FC03 falló`, { register, error: error.message || String(error) });
	}

	try {
		const { data } = await client.readInputRegisters(register, 1); // FC04
		sendSetupLog(`${label}: leído por FC04`, { register, value: data[0] & 0xffff });
		return data[0] & 0xffff;
	} catch (error) {
		errors.push(`FC04=${error.message || String(error)}`);
		sendSetupLog(`${label}: FC04 falló`, { register, error: error.message || String(error) });
	}

	throw new Error(`${label}: sin respuesta válida (${errors.join(" | ")})`);
}

async function readSignedRegister(register) {
	const value = await readUnsignedRegister(register);
	return toSigned16(value);
}

async function readTareRegister() {
	return readSignedRegister(setupRegs.tara);
}

async function writeGainRegister(gain) {
	await writeRegister(setupRegs.ganancia, gain, "WRITE_GAIN");
}

async function setWriteProtection(enabled) {
	// WG18A02: 0=write protection ON, 1=write protection OFF.
	const raw = enabled ? 0 : 1;
	await writeRegister(WG_REG.WRITE_PROTECTION, raw, "WG_WRITE_PROTECT");
}

async function withWriteUnlocked(action) {
	await setWriteProtection(false);
	try {
		return await action();
	} finally {
		try {
			await setWriteProtection(true);
		} catch (_) {
			// Non-fatal: module may restart or be busy briefly.
		}
	}
}

async function writeRegister(register, value, label = `WRITE_${register}`) {
	const errors = [];

	try {
		await client.writeRegister(register, value); // FC06
		sendSetupLog(`${label}: escrito por FC06`, { register, value });
		return;
	} catch (error) {
		errors.push(`FC06=${error.message || String(error)}`);
		sendSetupLog(`${label}: FC06 falló`, { register, value, error: error.message || String(error) });
	}

	try {
		await client.writeRegisters(register, [value]); // FC16
		sendSetupLog(`${label}: escrito por FC16`, { register, value });
		return;
	} catch (error) {
		errors.push(`FC16=${error.message || String(error)}`);
		sendSetupLog(`${label}: FC16 falló`, { register, value, error: error.message || String(error) });
	}

	throw new Error(`${label}: sin respuesta válida (${errors.join(" | ")})`);
}

async function clearTareRegister() {
	await withWriteUnlocked(async () => {
		// WG18A02: 0xEF => 1 tare, 0 not-peeled.
		await writeRegister(WG_REG.TARE, 0, "WG_CLEAR_TARE");
	});
}

async function pulseTareCommand() {
	await withWriteUnlocked(async () => {
		// WG18A02: write 1 to 0xEF to execute tare.
		await writeRegister(WG_REG.TARE, 1, "WG_TARE");
	});
}

function sleep(ms) {
	return new Promise((resolve) => setTimeout(resolve, ms));
}

function isTimeoutError(error) {
	const message = (error && error.message ? error.message : String(error)).toLowerCase();
	return message.includes("timed out") || message.includes("timeout");
}

async function writeRegisterWithRetry(register, value, label, { attempts = 3, delayMs = 250, allowTimeout = false } = {}) {
	let lastError = null;
	for (let attempt = 1; attempt <= attempts; attempt += 1) {
		try {
			await writeRegister(register, value, label);
			return true;
		} catch (error) {
			lastError = error;
			if (attempt < attempts) {
				sendSetupLog(`${label}: reintento`, {
					register,
					value,
					attempt,
					attempts,
					error: error.message || String(error),
				});
				await sleep(delayMs);
			}
		}
	}

	if (allowTimeout && isTimeoutError(lastError)) {
		sendSetupLog(`${label}: timeout tolerado, continuo`, {
			register,
			value,
			error: lastError && (lastError.message || String(lastError)),
		});
		return false;
	}
	throw lastError;
}

async function readCalibrationSnapshot(regMap) {
	const keys = ["filtro", "peso_nominal", "sens_cel1", "sens_cel2", "sens_cel3", "sens_cel4", "ganancia", "tara", "peso_muerto", "peso_proceso"];
	const out = {};
	for (const key of keys) {
		try {
			out[key] = await readSignedRegister(regMap[key]);
		} catch (_) {
			out[key] = null;
		}
	}
	return out;
}

function scoreCalibrationSnapshot(values) {
	let score = 0;
	if (values.filtro !== null && values.filtro >= 1 && values.filtro <= 20) score += 2;
	if (values.peso_nominal !== null && values.peso_nominal >= 5000 && values.peso_nominal <= 200000) score += 2;
	else if (values.peso_nominal !== null && values.peso_nominal > 0) score += 1;
	if (values.ganancia !== null && values.ganancia >= 5000 && values.ganancia <= 30000) score += 2;
	else if (values.ganancia !== null && values.ganancia >= 1000 && values.ganancia <= 50000) score += 1;
	for (const key of ["sens_cel1", "sens_cel2", "sens_cel3", "sens_cel4"]) {
		const value = values[key];
		if (value !== null && value >= 1000 && value <= 3000) {
			score += 1;
		} else if (value !== null && value >= 500 && value <= 5000) {
			score += 0;
		}
	}

	const grouped = [values.peso_nominal, values.ganancia, values.sens_cel1, values.sens_cel2, values.sens_cel3, values.sens_cel4].filter((v) => v !== null);
	const uniqueCount = new Set(grouped).size;
	if (grouped.length >= 4 && uniqueCount <= 2) {
		score -= 2;
	}
	return score;
}

async function selectSetupRegisterMap(force = false) {
	if (!force && setupMapDetected) {
		return;
	}

	if (SETUP_ADDRESS_MODE === "dec") {
		setupRegs = REG_DEC;
		setupAddressModeActive = "dec";
		setupMapDetected = true;
		sendSetupLog("Mapa setup forzado: DEC");
		return;
	}
	if (SETUP_ADDRESS_MODE === "hex") {
		setupRegs = REG_HEX;
		setupAddressModeActive = "hex";
		setupMapDetected = true;
		sendSetupLog("Mapa setup forzado: HEX");
		return;
	}

	const dec = await readCalibrationSnapshot(REG_DEC);
	const hex = await readCalibrationSnapshot(REG_HEX);
	const scoreDec = scoreCalibrationSnapshot(dec);
	const scoreHex = scoreCalibrationSnapshot(hex);

	if (scoreDec >= scoreHex) {
		setupRegs = REG_DEC;
		setupAddressModeActive = "dec";
	} else {
		setupRegs = REG_HEX;
		setupAddressModeActive = "hex";
	}
	setupMapDetected = true;
	sendSetupLog("Mapa setup AUTO detectado", {
		active: setupAddressModeActive,
		scoreDec,
		scoreHex,
	});
}

async function applyManualRs4Table() {
	sendSetupLog("Aplicando tabla base RS4...");
	const table = [
		[setupRegs.filtro, 4], // filtro
		[setupRegs.peso_nominal, 16000], // peso nominal
		[setupRegs.sens_cel1, 2000], // sens cel1
		[setupRegs.sens_cel2, 2000], // sens cel2
		[setupRegs.sens_cel3, 2000], // sens cel3
		[setupRegs.sens_cel4, 2000], // sens cel4
		[setupRegs.tara, 0], // tara
		[setupRegs.peso_muerto, 0], // peso muerto
		[setupRegs.ganancia, 10000], // ganancia
		[setupRegs.tarar, 0], // cmd tarar
		[setupRegs.coger_peso_muerto, 0], // cmd peso muerto
	];

	for (const [register, value] of table) {
		await writeRegister(register, value, `TABLE_SET_${register}`);
		const readBack = await readSignedRegister(register);
		sendSetupLog("Verificación de tabla", { register, set: value, read: readBack });
	}
}

async function clearOffsets(preserveDeadWeight = false) {
	sendSetupLog("Limpiando comandos y offsets...", { preserveDeadWeight });
	if (preserveDeadWeight) {
		// After 1300 pulse some units keep bus busy for a short window.
		await sleep(450);
		sendSetupLog("Preservando peso muerto: reset comandos en modo tolerante");
		await writeRegisterWithRetry(setupRegs.tarar, 0, "CLEAR_OFFSETS_CMD_TARE", {
			attempts: 4,
			delayMs: 300,
			allowTimeout: true,
		});
		await writeRegisterWithRetry(setupRegs.coger_peso_muerto, 0, "CLEAR_OFFSETS_CMD_PM", {
			attempts: 4,
			delayMs: 300,
			allowTimeout: true,
		});
	} else {
		await writeRegisterWithRetry(setupRegs.tarar, 0, "CLEAR_OFFSETS_CMD_TARE", {
			attempts: 3,
			delayMs: 200,
		});
		await writeRegisterWithRetry(setupRegs.coger_peso_muerto, 0, "CLEAR_OFFSETS_CMD_PM", {
			attempts: 3,
			delayMs: 200,
		});
	}

	await writeRegisterWithRetry(setupRegs.tara, 0, "CLEAR_OFFSETS_TARE", {
		attempts: 3,
		delayMs: 200,
	});
	if (!preserveDeadWeight) {
		await writeRegisterWithRetry(setupRegs.peso_muerto, 0, "CLEAR_OFFSETS_DEAD", {
			attempts: 3,
			delayMs: 200,
		});
	}
	await sleep(200);
}

async function captureStableProcessSample(seconds = 3.0, pauseMs = 100) {
	sendSetupLog("Captura estable iniciada", { seconds, pauseMs });
	const totalSamples = Math.max(5, Math.floor((seconds * 1000) / pauseMs));
	const values = [];

	for (let i = 0; i < totalSamples; i += 1) {
		values.push(await readSignedRegister(setupRegs.peso_proceso));
		if (i < totalSamples - 1) {
			await sleep(pauseMs);
		}
	}

	const sorted = [...values].sort((a, b) => a - b);
	const trim = Math.max(1, Math.floor(sorted.length * 0.1));
	const core = sorted.length > 2 * trim ? sorted.slice(trim, sorted.length - trim) : sorted;
	const averageRaw = core.reduce((sum, value) => sum + value, 0) / core.length;
	const dispersionRaw = Math.max(...core) - Math.min(...core);

	sendSetupLog("Captura estable finalizada", {
		averageRaw,
		dispersionRaw,
		samples: values.length,
	});

	return {
		averageRaw,
		dispersionRaw,
		samples: values.length,
	};
}

async function saveDeadWeightCurrent() {
	sendSetupLog("Guardando peso muerto actual (1300 pulso 1->0)");
	await writeRegister(setupRegs.coger_peso_muerto, 1, "SAVE_DEAD_ON");
	await sleep(200);
	await writeRegister(setupRegs.coger_peso_muerto, 0, "SAVE_DEAD_OFF");
	await writeRegister(setupRegs.tarar, 0, "SAVE_DEAD_CLEAR_TARE");
	await writeRegister(setupRegs.coger_peso_muerto, 0, "SAVE_DEAD_CLEAR_PM");
	const deadWeightRaw = await readSignedRegister(setupRegs.peso_muerto);
	sendSetupLog("Peso muerto guardado", { deadWeightRaw });
	return { deadWeightRaw };
}

async function applyGainFromSamples(knownWeightKg, zeroRaw, loadRaw) {
	const referenceWeight = Number(knownWeightKg);
	const zero = Number(zeroRaw);
	const load = Number(loadRaw);
	if (!Number.isFinite(referenceWeight) || referenceWeight <= 0) {
		throw new Error("Peso patrón inválido");
	}
	if (!Number.isFinite(zero) || !Number.isFinite(load)) {
		throw new Error("Lecturas inválidas para ajuste de ganancia");
	}

	const deltaRaw = load - zero;
	const measuredKg = deltaRaw / 100;
	if (Math.abs(measuredKg) < 0.05) {
		throw new Error("No hay variación suficiente entre vacío y carga");
	}

	let currentGain = await readGainRegister();
	if (!Number.isFinite(currentGain) || currentGain <= 0) {
		currentGain = 10000;
	}

	const factor = referenceWeight / measuredKg;
	const computedGain = Math.round(currentGain * factor);
	const nextGain = Math.max(1, Math.min(computedGain, 32767));

	sendSetupLog("Ajuste de ganancia calculado", {
		knownWeightKg: referenceWeight,
		zeroRaw: zero,
		loadRaw: load,
		deltaRaw,
		measuredKg,
		currentGain,
		factor,
		nextGain,
	});

	await writeGainRegister(nextGain);
	await sleep(200);

	return {
		currentGain,
		nextGain,
		factor,
		measuredKg,
		deltaRaw,
	};
}

async function readSetupState() {
	sendSetupLog("Leyendo estado final RS4...");
	const processRaw = await readSignedRegister(setupRegs.peso_proceso);
	const tareRaw = await readSignedRegister(setupRegs.tara);
	const deadWeightRaw = await readSignedRegister(setupRegs.peso_muerto);
	const gain = await readSignedRegister(setupRegs.ganancia);
	const cmdTare = await readUnsignedRegister(setupRegs.tarar);
	const cmdDeadWeight = await readUnsignedRegister(setupRegs.coger_peso_muerto);

	return {
		processRaw,
		tareRaw,
		deadWeightRaw,
		gain,
		cmdTare,
		cmdDeadWeight,
	};
}

function sendSetupResult(requestId, payload) {
	sendMessage({
		type: "setupResult",
		requestId,
		...payload,
	});
}

async function readBestCalibrationSample() {
	const attempts = 18; // ~3.6s at 200ms
	const delayMs = 200;
	let bestRaw = 0;

	for (let i = 0; i < attempts; i += 1) {
		const raw = await readRawWeight();
		if (Math.abs(raw) > Math.abs(bestRaw)) {
			bestRaw = raw;
		}
		if (i < attempts - 1) {
			await sleep(delayMs);
		}
	}

	return bestRaw;
}

async function calibrateKnownWeight(knownWeightKg) {
	const referenceWeight = Number(knownWeightKg);
	if (!Number.isFinite(referenceWeight) || referenceWeight <= 0) {
		throw new Error("Peso de referencia inválido");
	}

	const calibrationValue = Math.max(1, Math.round(referenceWeight * SCALE_DIVISOR));
	await withWriteUnlocked(async () => {
		await writeRegister(WG_REG.CALIBRATION, calibrationValue, "WG_CALIBRATION");
	});

	return {
		calibrationValue,
		knownWeightKg: referenceWeight,
	};
}

async function connect() {
	if (isExiting) return false;

	try {
		log(`Connecting to scale on ${PORT} at ${BAUD} baud...`);

		await client.connectRTUBuffered(PORT, {
			baudRate: BAUD,
			dataBits: 8,
			stopBits: 1,
			parity: "none",
		});

		client.setID(SLAVE_ID);
		client.setTimeout(2000);

		isConnected = true;
		log(`Connected to scale on ${PORT}`);

		sendUpdate({
			ready: true,
			error: null,
			port: PORT,
		});

		return true;
	} catch (error) {
		isConnected = false;
		log(`Connection failed: ${error.message}`);
		sendUpdate({
			ready: false,
			error: `No se pudo conectar al puerto ${PORT}: ${error.message}`,
			port: PORT,
		});
		return false;
	}
}

async function readWeight() {
	if (!isConnected || isExiting) return;

	try {
		const raw = await readRawWeight();
		const kg = raw / SCALE_DIVISOR;
		// Round to 1 decimal place using toFixed to avoid floating point precision issues
		const kg1 = Number(kg.toFixed(1));

		sendUpdate({
			weight: kg1,
			rawWeight: raw,
			ready: true,
			error: null,
			updatedAt: Date.now(),
		});

		if (DEBUG) {
			log(`Weight: ${kg1.toFixed(1)} kg (raw: ${raw})`);
		}
	} catch (error) {
		log(`Read error: ${error.message}`);

		// Check if it's a connection error
		if (error.message.includes("Port Not Open") || error.message.includes("disconnected") || error.message.includes("ECONNRESET")) {
			isConnected = false;
			sendUpdate({
				ready: false,
				error: `Conexión perdida: ${error.message}`,
			});

			// Stop polling and try to reconnect
			stopPolling();
			scheduleReconnect();
		} else {
			sendUpdate({
				error: `Error de lectura: ${error.message}`,
			});
		}
	}
}

function startPolling({ immediateRead = false } = {}) {
	if (pollTimer) return;

	pollTimer = setInterval(() => {
		readWeight();
	}, POLL_MS);

	// Optional immediate read. Disabled by default to avoid Modbus request overlap.
	if (immediateRead) {
		readWeight();
	}
}

function stopPolling() {
	if (pollTimer) {
		clearInterval(pollTimer);
		pollTimer = null;
	}
}

async function stopPollingAndDrain(delayMs = 220) {
	stopPolling();
	// Let any in-flight Modbus request complete before next command.
	await sleep(delayMs);
}

let reconnectTimer = null;

function closeClient() {
	return new Promise((resolve) => {
		try {
			if (client.isOpen) {
				client.close(() => resolve());
			} else {
				resolve();
			}
		} catch (_) {
			resolve();
		}
	});
}

function scheduleReconnect() {
	if (isExiting || reconnectTimer) return;

	reconnectTimer = setTimeout(async () => {
		reconnectTimer = null;

		if (isExiting) return;

		await closeClient();
		const connected = await connect();
		if (connected) {
			startPolling({ immediateRead: true });
		} else {
			scheduleReconnect();
		}
	}, 3000);
}

function cleanup() {
	isExiting = true;
	stopPolling();

	if (reconnectTimer) {
		clearTimeout(reconnectTimer);
		reconnectTimer = null;
	}

	try {
		client.close(() => {
			process.exit(0);
		});
	} catch (error) {
		process.exit(0);
	}
}

// Handle messages from main process
process.on("message", async (message) => {
	if (!message || typeof message !== "object") return;

	if (message.type === "stop") {
		cleanup();
		return;
	}

	if (message.type === "calibrate") {
		if (!isConnected) {
			sendError("Báscula desconectada");
			return;
		}

		// Pause polling during calibration to avoid conflicts
		await stopPollingAndDrain();

		try {
			log("Ejecutando tara (registro 0xEF = 1)...");
			await pulseTareCommand();

			log("Tara ejecutada correctamente");
		} catch (error) {
			log(`Tara falló: ${error.message}`);
			sendError(`Error al tarar: ${error.message}`);
		} finally {
			// Resume polling
			startPolling();
		}
	}

	if (message.type === "setup-init") {
		const requestId = message.requestId ?? null;
		if (!isConnected) {
			sendSetupResult(requestId, { ok: false, error: "Báscula desconectada" });
			return;
		}
		await stopPollingAndDrain();
		try {
			sendSetupLog("SETUP STEP: init");
			await selectSetupRegisterMap(true);
			await applyManualRs4Table();
			await clearOffsets(false);
			sendSetupResult(requestId, { ok: true });
		} catch (error) {
			sendSetupResult(requestId, { ok: false, error: error.message || String(error) });
		} finally {
			startPolling();
		}
	}

	if (message.type === "setup-capture-stable") {
		const requestId = message.requestId ?? null;
		if (!isConnected) {
			sendSetupResult(requestId, { ok: false, error: "Báscula desconectada" });
			return;
		}
		await stopPollingAndDrain();
		try {
			sendSetupLog("SETUP STEP: capture-stable");
			await selectSetupRegisterMap();
			const result = await captureStableProcessSample(3.0, 100);
			sendSetupResult(requestId, { ok: true, ...result });
		} catch (error) {
			sendSetupResult(requestId, { ok: false, error: error.message || String(error) });
		} finally {
			startPolling();
		}
	}

	if (message.type === "setup-apply-gain") {
		const requestId = message.requestId ?? null;
		if (!isConnected) {
			sendSetupResult(requestId, { ok: false, error: "Báscula desconectada" });
			return;
		}
		await stopPollingAndDrain();
		try {
			sendSetupLog("SETUP STEP: apply-gain");
			await selectSetupRegisterMap();
			const result = await applyGainFromSamples(message.knownWeightKg, message.zeroRaw, message.loadRaw);
			sendSetupResult(requestId, { ok: true, ...result });
		} catch (error) {
			sendSetupResult(requestId, { ok: false, error: error.message || String(error) });
		} finally {
			startPolling();
		}
	}

	if (message.type === "setup-save-dead-weight") {
		const requestId = message.requestId ?? null;
		if (!isConnected) {
			sendSetupResult(requestId, { ok: false, error: "Báscula desconectada" });
			return;
		}
		await stopPollingAndDrain();
		try {
			sendSetupLog("SETUP STEP: save-dead-weight");
			await selectSetupRegisterMap();
			const result = await saveDeadWeightCurrent();
			sendSetupResult(requestId, { ok: true, ...result });
		} catch (error) {
			sendSetupResult(requestId, { ok: false, error: error.message || String(error) });
		} finally {
			startPolling();
		}
	}

	if (message.type === "setup-clear-offsets") {
		const requestId = message.requestId ?? null;
		if (!isConnected) {
			sendSetupResult(requestId, { ok: false, error: "Báscula desconectada" });
			return;
		}
		await stopPollingAndDrain();
		try {
			sendSetupLog("SETUP STEP: clear-offsets", {
				preserveDeadWeight: Boolean(message.preserveDeadWeight),
			});
			await selectSetupRegisterMap();
			await clearOffsets(Boolean(message.preserveDeadWeight));
			sendSetupResult(requestId, { ok: true });
		} catch (error) {
			sendSetupResult(requestId, { ok: false, error: error.message || String(error) });
		} finally {
			startPolling();
		}
	}

	if (message.type === "setup-read-state") {
		const requestId = message.requestId ?? null;
		if (!isConnected) {
			sendSetupResult(requestId, { ok: false, error: "Báscula desconectada" });
			return;
		}
		await stopPollingAndDrain();
		try {
			sendSetupLog("SETUP STEP: read-state");
			await selectSetupRegisterMap();
			const state = await readSetupState();
			sendSetupResult(requestId, { ok: true, ...state });
		} catch (error) {
			sendSetupResult(requestId, { ok: false, error: error.message || String(error) });
		} finally {
			startPolling();
		}
	}

	if (message.type === "calibrate-known-weight") {
		const requestId = message.requestId ?? null;

		if (!isConnected) {
			sendMessage({
				type: "calibrationResult",
				requestId,
				ok: false,
				error: "Báscula desconectada",
			});
			return;
		}

		await stopPollingAndDrain();
		try {
			const result = await calibrateKnownWeight(message.knownWeightKg);
			sendMessage({
				type: "calibrationResult",
				requestId,
				ok: true,
				...result,
			});
		} catch (error) {
			sendMessage({
				type: "calibrationResult",
				requestId,
				ok: false,
				error: error.message || String(error),
			});
		} finally {
			startPolling();
		}
	}

	if (message.type === "clear-tare") {
		const requestId = message.requestId ?? null;

		if (!isConnected) {
			sendMessage({
				type: "clearTareResult",
				requestId,
				ok: false,
				error: "Báscula desconectada",
			});
			return;
		}

		await stopPollingAndDrain();
		try {
			await clearTareRegister();
			const processRaw = await readRawWeight();
			sendMessage({
				type: "clearTareResult",
				requestId,
				ok: true,
				processRaw,
			});
		} catch (error) {
			sendMessage({
				type: "clearTareResult",
				requestId,
				ok: false,
				error: error.message || String(error),
			});
		} finally {
			startPolling();
		}
	}
});

process.on("SIGINT", cleanup);
process.on("SIGTERM", cleanup);
process.on("disconnect", cleanup);

// Main entry point
async function main() {
	log(`Starting scale worker - Port: ${PORT}, Baud: ${BAUD}, Poll: ${POLL_MS}ms`);

	sendUpdate({
		ready: null,
		weight: null,
		rawWeight: null,
		error: null,
		port: PORT,
	});

	const connected = await connect();

	if (connected) {
		startPolling({ immediateRead: true });
	} else {
		scheduleReconnect();
	}
}

main().catch((error) => {
	console.error("[ScaleWorker] Unexpected error:", error);
	sendError(error.message || String(error));
	process.exitCode = 1;
});
