const { app, BrowserWindow, ipcMain, screen, shell } = require('electron');
const path = require('path');
const os = require('os');
const fs = require('fs');
const wifi = require('node-wifi');
const { fork, spawn } = require('child_process');
const { ensureLatest } = require('../scripts/update');

// Inicializa node-wifi usando la interfaz por defecto
wifi.init({ iface: null });

const isWindows = process.platform === 'win32';
const DEFAULT_UPDATE_INTERVAL_MS = 5 * 60 * 1000;
const DEFAULT_LINUX_RFID_PORT = '/dev/rfid';
const DEFAULT_LINUX_SCALE_PORT = '/dev/scale';

function shouldDisableGpu() {
  if (process.argv.includes('--disable-gpu')) {
    return 'CLI flag --disable-gpu';
  }
  if (process.env.DISABLE_GPU === '1') {
    return 'DISABLE_GPU=1';
  }
  if (process.env.DISABLE_GPU === '0') {
    return null;
  }

  const isLinux = process.platform === 'linux';
  const isArm = process.arch.startsWith('arm');
  const runningHeadless = isLinux && !process.env.DISPLAY;

  if (isLinux && (isArm || runningHeadless)) {
    return 'Linux device sin GPU fiable (auto-detectado)';
  }

  return null;
}

const disableGpuReason = shouldDisableGpu();
if (disableGpuReason) {
  console.log(`[start] Deshabilitando aceleración GPU: ${disableGpuReason}`);
  app.disableHardwareAcceleration();
}

// Config file path for persistent settings
const CONFIG_FILE = path.join(app.getPath('userData'), 'scale-config.json');
const SETUP_LOG_FILE = path.join(app.getPath('userData'), 'scale-setup.log');

function appendSetupLogLine(line) {
  try {
    fs.appendFileSync(SETUP_LOG_FILE, `${line}\n`, 'utf8');
  } catch (error) {
    console.error('[Setup log] No se pudo escribir log:', error);
  }
}

function loadScaleConfig() {
  try {
    if (fs.existsSync(CONFIG_FILE)) {
      const data = fs.readFileSync(CONFIG_FILE, 'utf8');
      return JSON.parse(data);
    }
  } catch (error) {
    console.error('[Config] Error loading scale config:', error);
  }
  return {};
}

function saveScaleConfig(config) {
  try {
    fs.writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2), 'utf8');
    return true;
  } catch (error) {
    console.error('[Config] Error saving scale config:', error);
    return false;
  }
}

function resolvePositiveInt(rawValue, fallback) {
  const parsed = Number.parseInt(rawValue, 10);
  if (Number.isFinite(parsed) && parsed > 0) {
    return parsed;
  }
  return fallback;
}

function isPeriodicUpdateEnabled() {
  if (process.env.PI_ENABLE_PERIODIC_UPDATES === '1') {
    return true;
  }
  if (process.env.PI_ENABLE_PERIODIC_UPDATES === '0') {
    return false;
  }
  return process.platform === 'linux';
}

function normalizeSerialPath(rawPath) {
  if (!rawPath || typeof rawPath !== 'string') {
    return '';
  }

  const trimmed = rawPath.trim();
  if (!trimmed) {
    return '';
  }

  const byIdPrefix = '/dev/serial/by-id/';
  if (!trimmed.startsWith(byIdPrefix)) {
    return trimmed;
  }

  let alias = trimmed.slice(byIdPrefix.length).trim();
  if (!alias) {
    return '';
  }

  if (alias.includes('->')) {
    alias = alias.split('->')[0].trim();
  }

  if (/\s/.test(alias)) {
    const parts = alias.split(/\s+/).filter(Boolean);
    alias = parts[parts.length - 1] || alias;
  }

  return `${byIdPrefix}${alias}`;
}

function getExistingDevicePath(devicePath) {
  if (!devicePath || typeof devicePath !== 'string') {
    return '';
  }
  try {
    return fs.existsSync(devicePath) ? devicePath : '';
  } catch (_) {
    return '';
  }
}

function getDefaultRfidPort() {
  if (isWindows) {
    return 'COM3';
  }
  if (process.platform === 'linux') {
    return getExistingDevicePath(DEFAULT_LINUX_RFID_PORT);
  }
  return '';
}

function getDefaultScalePort() {
  if (process.platform === 'linux') {
    return getExistingDevicePath(DEFAULT_LINUX_SCALE_PORT);
  }
  return '';
}

// Load saved scale config
const savedScaleConfig = loadScaleConfig();
const savedPortNormalized = normalizeSerialPath(savedScaleConfig.port || '');
const savedRfidPortNormalized = normalizeSerialPath(savedScaleConfig.rfidPort || '');
if (savedPortNormalized !== (savedScaleConfig.port || '') || savedRfidPortNormalized !== (savedScaleConfig.rfidPort || '')) {
  savedScaleConfig.port = savedPortNormalized;
  savedScaleConfig.rfidPort = savedRfidPortNormalized;
  saveScaleConfig(savedScaleConfig);
}

const RFID_CONFIG = {
  path:
    (savedScaleConfig && savedScaleConfig.rfidPort) ||
    process.env.RFID_PORT ||
    getDefaultRfidPort(),
  baudRate: resolvePositiveInt(process.env.RFID_BAUD_RATE, 115200),
  interval: Math.max(resolvePositiveInt(process.env.RFID_INTERVAL, 500), 250),
  reconnectInterval: Math.max(
    resolvePositiveInt(process.env.RFID_RECONNECT_INTERVAL, 3000),
    1000
  ),
  debug: process.env.RFID_DEBUG === '1',
  execPath: process.env.RFID_NODE_PATH || 'node',
};

const SCALE_CONFIG = {
  port: savedScaleConfig.port || process.env.SCALE_PORT || getDefaultScalePort(),
  baudRate: resolvePositiveInt(savedScaleConfig.baudRate || process.env.SCALE_BAUD, 9600),
  slaveId: resolvePositiveInt(savedScaleConfig.slaveId || process.env.SCALE_SLAVE_ID, 1),
  startAddr: resolvePositiveInt(savedScaleConfig.startAddr || process.env.SCALE_START_ADDR, 0),
  pollInterval: Math.max(resolvePositiveInt(savedScaleConfig.pollInterval || process.env.SCALE_POLL_MS, 500), 100),
  wordOrder: (savedScaleConfig.wordOrder || process.env.SCALE_WORD_ORDER || 'BA').toUpperCase(),
  registers: (savedScaleConfig.registers || process.env.SCALE_REGISTERS || 'holding').toLowerCase(),
  registerCount: Math.max(
    resolvePositiveInt(savedScaleConfig.registerCount || process.env.SCALE_REGISTER_COUNT, 3),
    1
  ),
  divisor: parseFloat(savedScaleConfig.divisor || process.env.SCALE_DIV || '1000'),
  debug: process.env.SCALE_DEBUG === '1',
  execPath: process.env.SCALE_NODE_PATH || 'node',
};

if (!Number.isFinite(SCALE_CONFIG.divisor) || SCALE_CONFIG.divisor <= 0) {
  SCALE_CONFIG.divisor = 1000;
}

/** Tras este tiempo con báscula en cero se borra la etiqueta RFID mostrada. */
const RFID_CLEAR_WHEN_ZERO_IDLE_MS = 5000;
/** Por encima de este valor (kg) se considera que hay carga y no se limpia el RFID. */
const RFID_ZERO_WEIGHT_MAX_KG = 0.1;

/** @type {BrowserWindow | null} */
let mainWindow = null;
let rfidWorker = null;
let rfidReconnectTimer = null;
/** Limpia el RFID leído si el peso sigue en ~0 durante este tiempo (báscula vacía). */
let rfidClearZeroIdleTimer = null;
let scaleWorker = null;
let scaleReconnectTimer = null; 
let isAppQuitting = false;
let scaleRequestSeq = 0;
const pendingScaleRequests = new Map();
let periodicUpdateTimer = null;
let isUpdateCheckInProgress = false;
let isRestartingForUpdate = false;

const PERIODIC_UPDATE_INTERVAL_MS = Math.max(
  resolvePositiveInt(process.env.PI_UPDATE_INTERVAL_MS, DEFAULT_UPDATE_INTERVAL_MS),
  60000
);

const rfidState = {
  ready: null,
  tag: null,
  rssi: null,
  tags: [],
  updatedAt: null,
  error: null,
  port: RFID_CONFIG.path,
};

const scaleState = {
  ready: null,
  weight: null,
  rawWeight: null,
  updatedAt: null,
  error: null,
  port: SCALE_CONFIG.port,
};

async function installUpdatedDependencies() {
  const cmd = process.platform === 'win32' ? 'npm.cmd' : 'npm';
  const args = ['install', '--omit=dev'];

  // En Raspberry Pi evitamos dependencias de desarrollo salvo que se pidan expresamente.
  if (process.env.PI_INSTALL_DEV === '1') {
    args.pop();
  }

  await new Promise((resolve, reject) => {
    const child = spawn(cmd, args, {
      cwd: path.resolve(__dirname, '..'),
      stdio: 'inherit',
      env: process.env,
    });

    child.on('error', reject);
    child.on('exit', (code) => {
      if (code === 0) {
        resolve();
      } else {
        reject(new Error(`npm install exited with code ${code}`));
      }
    });
  });
}

function stopPeriodicUpdateChecks() {
  if (periodicUpdateTimer) {
    clearInterval(periodicUpdateTimer);
    periodicUpdateTimer = null;
  }
}

function restartApplication(reason = 'manual') {
  if (isRestartingForUpdate || isAppQuitting) {
    return;
  }

  isRestartingForUpdate = true;
  stopPeriodicUpdateChecks();
  console.log(`[update] Restarting application (${reason})...`);
  app.relaunch();
  app.exit(0);
}

async function checkForUpdatesAndRestart(source = 'timer') {
  if (
    (source === 'timer' && !isPeriodicUpdateEnabled()) ||
    isAppQuitting ||
    isRestartingForUpdate ||
    isUpdateCheckInProgress
  ) {
    return null;
  }

  isUpdateCheckInProgress = true;

  try {
    const updateResult = await ensureLatest();
    if (!updateResult || !updateResult.updated) {
      return updateResult;
    }

    console.log(
      `[update] Updated application to version ${updateResult.targetVersion} (${source}).`
    );

    if (updateResult.requiresInstall) {
      console.log('[update] Installing dependencies after update...');
      await installUpdatedDependencies();
    }

    restartApplication(`updated via ${source}`);
    return { ...updateResult, restarted: true };
  } catch (error) {
    console.warn(`[update] ${source} check failed: ${error.message}`);
    return { updated: false, reason: 'check-failed', error: error.message };
  } finally {
    isUpdateCheckInProgress = false;
  }
}

function startPeriodicUpdateChecks() {
  if (!isPeriodicUpdateEnabled() || periodicUpdateTimer) {
    return;
  }

  const minutes = PERIODIC_UPDATE_INTERVAL_MS / 60000;
  console.log(`[update] Periodic update checks enabled every ${minutes} minute(s).`);

  periodicUpdateTimer = setInterval(() => {
    void checkForUpdatesAndRestart('timer');
  }, PERIODIC_UPDATE_INTERVAL_MS);

  if (typeof periodicUpdateTimer.unref === 'function') {
    periodicUpdateTimer.unref();
  }
}

function hasSerialPortConflict() {
  const rfidPort = (RFID_CONFIG.path || '').trim();
  const scalePort = (SCALE_CONFIG.port || '').trim();
  if (!rfidPort || !scalePort) {
    return false;
  }
  return rfidPort === scalePort;
}

function listStableSerialPorts() {
  return new Promise((resolve) => {
    // En Windows y macOS usar SerialPort.list(); en Linux usar nombres estables.
    if (process.platform !== 'linux') {
      const { SerialPort } = require('serialport');
      SerialPort.list()
        .then((list) => {
          const ports = (list || []).map((p) => {
            const pathVal = (p.path || '').trim();
            const extra = [p.friendlyName, p.manufacturer].filter(Boolean).join(' – ');
            return {
              path: pathVal,
              alias: pathVal,
              target: null,
              label: extra ? `${pathVal} (${extra})` : pathVal,
            };
          });
          resolve({ ok: true, ports, source: 'serialport-list' });
        })
        .catch((error) => {
          resolve({ ok: false, error: error.message || String(error), ports: [] });
        });
      return;
    }

    try {
      const ports = [];
      const seenPaths = new Set();

      const addPort = ({ path: portPath, alias, target = null, label }) => {
        const normalizedPath = normalizeSerialPath(portPath);
        if (!normalizedPath || seenPaths.has(normalizedPath)) {
          return;
        }
        seenPaths.add(normalizedPath);
        ports.push({
          path: normalizedPath,
          alias: alias || normalizedPath,
          target,
          label: label || (target ? `${alias || normalizedPath} (${target})` : normalizedPath),
        });
      };

      [
        { path: DEFAULT_LINUX_RFID_PORT, alias: 'rfid', label: '/dev/rfid (RFID)' },
        { path: DEFAULT_LINUX_SCALE_PORT, alias: 'scale', label: '/dev/scale (Bascula)' },
      ].forEach((entry) => {
        if (getExistingDevicePath(entry.path)) {
          addPort(entry);
        }
      });

      const addDirectoryLinks = (dirPath, sourceLabel) => {
        if (!fs.existsSync(dirPath)) {
          return;
        }
        const aliases = fs.readdirSync(dirPath).filter(Boolean).sort();
        for (const alias of aliases) {
          const fullPath = path.join(dirPath, alias);
          let target = null;
          try {
            target = fs.readlinkSync(fullPath);
          } catch (_) {
            continue;
          }
          addPort({
            path: fullPath,
            alias,
            target,
            label: `${sourceLabel}: ${alias} (${target})`,
          });
        }
      };

      addDirectoryLinks('/dev/serial/by-id', 'by-id');
      addDirectoryLinks('/dev/serial/by-path', 'by-path');

      resolve({ ok: true, ports, source: ports.length ? 'stable-linux' : 'stable-linux-empty' });
    } catch (error) {
      resolve({ ok: false, error: error.message || String(error), ports: [] });
    }
  });
}

function hasValidAddress(address) {
  if (!address || typeof address !== 'string') {
    return false;
  }
  if (address === '0.0.0.0' || address === '127.0.0.1') {
    return false;
  }
  if (address.startsWith('169.254.')) {
    return false;
  }
  return true;
}

function detectNetworkConnectivity() {
  const interfaces = os.networkInterfaces();
  const fallback = { hasNetwork: false };

  if (!interfaces || typeof interfaces !== 'object') {
    return fallback;
  }

  for (const [name, infos] of Object.entries(interfaces)) {
    if (!Array.isArray(infos)) {
      continue;
    }

    const validInfo = infos.find(
      (info) =>
        info &&
        info.family === 'IPv4' &&
        hasValidAddress(info.address) &&
        !info.internal
    );

    if (!validInfo) {
      continue;
    }

    const normalizedName = name.toLowerCase();
    const isWifiInterface =
      normalizedName.includes('wi-fi') ||
      normalizedName.includes('wifi') ||
      normalizedName.includes('wlan') ||
      normalizedName.includes('wireless');

    return {
      hasNetwork: true,
      interfaceName: name,
      type: isWifiInterface ? 'wifi' : 'ethernet',
    };
  }

  return fallback;
}

function notifyRfidUpdate() {
  if (!mainWindow || mainWindow.isDestroyed()) {
    return;
  }
  mainWindow.webContents.send('rfid:update', { ...rfidState });
}

function notifyScaleUpdate() {
  if (!mainWindow || mainWindow.isDestroyed()) {
    return;
  }
  mainWindow.webContents.send('scale:update', { ...scaleState });
}

function updateScaleState(patch) {
  if (!patch || typeof patch !== 'object') {
    return;
  }

  let changed = false;
  for (const [key, value] of Object.entries(patch)) {
    if (!(key in scaleState)) {
      continue;
    }
    if (!Object.is(scaleState[key], value)) {
      scaleState[key] = value;
      changed = true;
    }
  }

  if (changed) {
    notifyScaleUpdate();
    maybeClearRfidAfterZeroIdle();
  }
}

function clearRfidZeroIdleTimer() {
  if (rfidClearZeroIdleTimer) {
    clearTimeout(rfidClearZeroIdleTimer);
    rfidClearZeroIdleTimer = null;
  }
}

function maybeClearRfidAfterZeroIdle() {
  const w = scaleState.weight;
  if (w == null || !Number.isFinite(w)) {
    clearRfidZeroIdleTimer();
    return;
  }
  if (Math.abs(w) > RFID_ZERO_WEIGHT_MAX_KG) {
    clearRfidZeroIdleTimer();
    return;
  }
  if (!rfidState.tag) {
    clearRfidZeroIdleTimer();
    return;
  }
  if (rfidClearZeroIdleTimer) {
    return;
  }
  rfidClearZeroIdleTimer = setTimeout(() => {
    rfidClearZeroIdleTimer = null;
    const w2 = scaleState.weight;
    if (w2 == null || !Number.isFinite(w2) || Math.abs(w2) > RFID_ZERO_WEIGHT_MAX_KG) {
      return;
    }
    if (!rfidState.tag) {
      return;
    }
    updateRfidState({
      tag: null,
      rssi: null,
      tags: [],
      updatedAt: Date.now(),
    });
  }, RFID_CLEAR_WHEN_ZERO_IDLE_MS);
}

function scheduleScaleReconnect() {
  if (isAppQuitting) {
    return;
  }
  if (scaleReconnectTimer) {
    return;
  }
  scaleReconnectTimer = setTimeout(() => {
    scaleReconnectTimer = null;
    startScaleReader();
  }, 3000);
}

function clearScaleTimers() {
  if (scaleReconnectTimer) {
    clearTimeout(scaleReconnectTimer);
    scaleReconnectTimer = null;
  }
}

function handleScaleWorkerMessage(message) {
  if (!message || typeof message !== 'object') {
    return;
  }

  if (message.type === 'calibrationResult') {
    const requestId = message.requestId;
    if (requestId && pendingScaleRequests.has(requestId)) {
      const entry = pendingScaleRequests.get(requestId);
      pendingScaleRequests.delete(requestId);
      clearTimeout(entry.timeoutId);
      entry.resolve(message);
    }
    return;
  }

  if (message.type === 'clearTareResult') {
    const requestId = message.requestId;
    if (requestId && pendingScaleRequests.has(requestId)) {
      const entry = pendingScaleRequests.get(requestId);
      pendingScaleRequests.delete(requestId);
      clearTimeout(entry.timeoutId);
      entry.resolve(message);
    }
    return;
  }

  if (message.type === 'setupResult') {
    const requestId = message.requestId;
    if (requestId && pendingScaleRequests.has(requestId)) {
      const entry = pendingScaleRequests.get(requestId);
      pendingScaleRequests.delete(requestId);
      clearTimeout(entry.timeoutId);
      entry.resolve(message);
    }
    return;
  }

  if (message.type === 'setupLog') {
    const payload = {
      message: message.message ? String(message.message) : '[setup] (sin mensaje)',
      meta: message.meta && typeof message.meta === 'object' ? message.meta : null,
      timestamp: Number.isFinite(message.timestamp) ? message.timestamp : Date.now(),
    };
    console.log('[Scale setup]', payload.message, payload.meta || '');
    const iso = new Date(payload.timestamp).toISOString();
    appendSetupLogLine(`[${iso}] ${payload.message}${payload.meta ? ` ${JSON.stringify(payload.meta)}` : ''}`);
    if (mainWindow && !mainWindow.isDestroyed()) {
      mainWindow.webContents.send('scale:setup-log', payload);
    }
    return;
  }

  if (message.type === 'update' && message.payload) {
    updateScaleState(message.payload);
    return;
  }

  if (message.type === 'log' && SCALE_CONFIG.debug && message.message) {
    console.log('[Scale worker]', message.message);
    return;
  }

  if (message.type === 'error') {
    const errorText = message.error ? String(message.error) : 'Error en báscula';
    if (SCALE_CONFIG.debug) {
      console.error('[Scale worker] Error:', errorText);
    }
    updateScaleState({ ready: false, error: errorText });
  }
}

function sendScaleWorkerRequest(request, timeoutMs = 60000) {
  if (!scaleWorker) {
    return Promise.resolve({ ok: false, error: 'Servicio de báscula no iniciado' });
  }

  scaleRequestSeq += 1;
  const requestId = `req_${Date.now()}_${scaleRequestSeq}`;

  return new Promise((resolve) => {
    const requestTag = request?.type || 'unknown';
    console.log(`[Scale IPC] request start: ${requestTag} timeout=${timeoutMs}ms`);
    appendSetupLogLine(`[${new Date().toISOString()}] [IPC] start ${requestTag} timeout=${timeoutMs}ms`);
    const timeoutId = setTimeout(() => {
      pendingScaleRequests.delete(requestId);
      console.error(`[Scale IPC] timeout: ${requestTag}`);
      appendSetupLogLine(`[${new Date().toISOString()}] [IPC] timeout ${requestTag}`);
      resolve({
        ok: false,
        error: `Timed out: ${requestTag} superó ${Math.round(timeoutMs / 1000)}s`,
      });
    }, timeoutMs);

    pendingScaleRequests.set(requestId, { resolve, timeoutId });

    try {
      scaleWorker.send({ ...request, requestId });
    } catch (error) {
      clearTimeout(timeoutId);
      pendingScaleRequests.delete(requestId);
      console.error(`[Scale IPC] send error: ${requestTag}`, error);
      resolve({ ok: false, error: error.message || String(error) });
    }
  });
}

function startScaleReader() {
  if (isAppQuitting) {
    return;
  }
  if (scaleWorker) {
    return;
  }
  if (!(SCALE_CONFIG.port || '').trim()) {
    updateScaleState({ ready: false, error: 'Báscula no configurada', port: '' });
    return;
  }

  try {
    scaleWorker = fork(path.join(__dirname, 'scaleWorker.js'), [], {
      stdio: ['inherit', 'inherit', 'inherit', 'ipc'],
      execPath: SCALE_CONFIG.execPath,
      env: {
        ...process.env,
        SCALE_PORT: SCALE_CONFIG.port,
        SCALE_BAUD: String(SCALE_CONFIG.baudRate),
        SCALE_SLAVE_ID: String(SCALE_CONFIG.slaveId),
        SCALE_START_ADDR: String(SCALE_CONFIG.startAddr),
        SCALE_POLL_MS: String(SCALE_CONFIG.pollInterval),
        SCALE_WORD_ORDER: SCALE_CONFIG.wordOrder,
        SCALE_REGISTERS: SCALE_CONFIG.registers,
        SCALE_REGISTER_COUNT: String(SCALE_CONFIG.registerCount),
        SCALE_DIV: String(SCALE_CONFIG.divisor),
        SCALE_DEBUG: SCALE_CONFIG.debug ? '1' : '0',
      },
    });
  } catch (error) {
    console.error('[Scale] No se pudo iniciar el proceso lector:', error);
    updateScaleState({ ready: false, error: error.message });
    scheduleScaleReconnect();
    return;
  }

  updateScaleState({ ready: null, error: null, weight: null, port: SCALE_CONFIG.port });

  scaleWorker.on('message', handleScaleWorkerMessage);
  scaleWorker.on('error', (error) => {
    console.error('[Scale] Error en el proceso lector:', error);
  });
  scaleWorker.on('exit', (code, signal) => {
    if (SCALE_CONFIG.debug) {
      console.log('[Scale] Proceso lector finalizado', { code, signal });
    }
    scaleWorker = null;
    if (!isAppQuitting) {
      updateScaleState({ ready: false });
      scheduleScaleReconnect();
    }
  });
}

function stopScaleReader() {
  clearScaleTimers();
  for (const [requestId, entry] of pendingScaleRequests.entries()) {
    clearTimeout(entry.timeoutId);
    entry.resolve({ ok: false, error: 'Lector de báscula reiniciado' });
    pendingScaleRequests.delete(requestId);
  }
  const worker = scaleWorker;
  scaleWorker = null;
  if (worker) {
    try {
      worker.removeAllListeners();
      worker.kill('SIGTERM');
    } catch (error) {
      console.error('[Scale] No se pudo finalizar el proceso lector:', error);
    }
  }
}

function restartScaleReader() {
  clearScaleTimers();
  const worker = scaleWorker;
  scaleWorker = null;
  let started = false;
  const doStart = () => {
    if (started) return;
    started = true;
    setTimeout(() => startScaleReader(), 400);
  };
  if (worker) {
    worker.removeAllListeners();
    worker.once('exit', doStart);
    try {
      worker.kill('SIGTERM');
    } catch (error) {
      console.error('[Scale] No se pudo finalizar el proceso lector:', error);
      doStart();
    }
    setTimeout(doStart, 5000);
  } else {
    doStart();
  }
}

function updateRfidState(patch) {
  if (!patch || typeof patch !== 'object') {
    return;
  }

  let changed = false;
  for (const [key, value] of Object.entries(patch)) {
    if (!(key in rfidState)) {
      continue;
    }
    if (!Object.is(rfidState[key], value)) {
      rfidState[key] = value;
      changed = true;
    }
  }

  if (changed) {
    notifyRfidUpdate();
    maybeClearRfidAfterZeroIdle();
  }
}

function scheduleRfidReconnect() {
  if (isAppQuitting) {
    return;
  }
  if (rfidReconnectTimer) {
    return;
  }
  rfidReconnectTimer = setTimeout(() => {
    rfidReconnectTimer = null;
    startRfidReader();
  }, RFID_CONFIG.reconnectInterval);
}

function clearRfidTimers() {
  clearRfidZeroIdleTimer();
  if (rfidReconnectTimer) {
    clearTimeout(rfidReconnectTimer);
    rfidReconnectTimer = null;
  }
}

function handleRfidWorkerMessage(message) {
  if (!message || typeof message !== 'object') {
    return;
  }

  if (message.type === 'update' && message.payload) {
    updateRfidState(message.payload);
    return;
  }

  if (message.type === 'log' && RFID_CONFIG.debug && message.message) {
    console.log('[RFID worker]', message.message);
    return;
  }

  if (message.type === 'error') {
    const errorText = message.error ? String(message.error) : 'Error en lector RFID';
    if (RFID_CONFIG.debug) {
      console.error('[RFID worker] Error:', errorText);
    }
    updateRfidState({ ready: false, error: errorText });
  }
}

function startRfidReader() {
  if (isAppQuitting) {
    return;
  }
  if (rfidWorker) {
    return;
  }
  if (!(RFID_CONFIG.path || '').trim()) {
    updateRfidState({ ready: false, error: 'Configura el puerto RFID en Configurar Báscula', port: null });
    return;
  }

  if (hasSerialPortConflict()) {
    const message = `RFID y bascula comparten el puerto ${RFID_CONFIG.path}. Ajusta uno de los puertos.`;
    updateRfidState({ ready: false, error: message, port: RFID_CONFIG.path });
    scheduleRfidReconnect();
    return;
  }

  try {
    rfidWorker = fork(path.join(__dirname, 'rfidWorker.js'), [], {
      stdio: ['inherit', 'inherit', 'inherit', 'ipc'],
      execPath: RFID_CONFIG.execPath,
      env: {
        ...process.env,
        RFID_PORT: RFID_CONFIG.path,
        RFID_BAUD_RATE: String(RFID_CONFIG.baudRate),
        RFID_INTERVAL: String(RFID_CONFIG.interval),
        RFID_RECONNECT_INTERVAL: String(RFID_CONFIG.reconnectInterval),
        RFID_DEBUG: RFID_CONFIG.debug ? '1' : '0',
      },
    });
  } catch (error) {
    console.error('[RFID] No se pudo iniciar el proceso lector:', error);
    updateRfidState({ ready: false, error: error.message });
    scheduleRfidReconnect();
    return;
  }

  updateRfidState({ ready: null, error: null, tag: null, rssi: null, tags: [], port: RFID_CONFIG.path });

  rfidWorker.on('message', handleRfidWorkerMessage);
  rfidWorker.on('error', (error) => {
    console.error('[RFID] Error en el proceso lector:', error);
  });
  rfidWorker.on('exit', (code, signal) => {
    if (RFID_CONFIG.debug) {
      console.log('[RFID] Proceso lector finalizado', { code, signal });
    }
    rfidWorker = null;
    if (!isAppQuitting) {
      updateRfidState({ ready: false });
      scheduleRfidReconnect();
    }
  });
}

function stopRfidReader() {
  clearRfidTimers();
  const worker = rfidWorker;
  rfidWorker = null;
  if (worker) {
    try {
      worker.removeAllListeners();
      worker.kill();
    } catch (error) {
      console.error('[RFID] No se pudo finalizar el proceso lector:', error);
    }
  }
}

function createMainWindow() {
  // Configuración fija para pantalla 640x480
  const windowWidth = 640;
  const windowHeight = 480;

  mainWindow = new BrowserWindow({
    width: windowWidth,
    height: windowHeight,
    resizable: false,
    fullscreen: true,
    kiosk: false,
    autoHideMenuBar: true,
    backgroundColor: '#101419',
    webPreferences: {
      preload: path.join(__dirname, 'preload.js'),
      nodeIntegration: false,
      contextIsolation: true,
      sandbox: false
    }
  });

  mainWindow.loadFile(path.join(__dirname, '../public/index.html'));

  mainWindow.on('closed', () => {
    mainWindow = null;
  });

  mainWindow.webContents.on('did-finish-load', () => {
    notifyRfidUpdate();
notifyScaleUpdate();
  });
}

app.whenReady().then(() => {
  startRfidReader();
  startScaleReader();
  createMainWindow();
  startPeriodicUpdateChecks();
});

app.on('activate', () => {
  if (BrowserWindow.getAllWindows().length === 0) {
    createMainWindow();
  }
});

app.on('window-all-closed', () => {
  if (process.platform !== 'darwin') {
    app.quit();
  }
});

app.on('before-quit', () => {
  isAppQuitting = true;
  stopPeriodicUpdateChecks();
  stopRfidReader();
  stopScaleReader();
});

ipcMain.handle('wifi:get-status', async () => {
  try {
    const connections = await wifi.getCurrentConnections();
    const first = connections?.find((conn) => conn?.ssid);
    const networkStatus = detectNetworkConnectivity();

    if (first && first.ssid) {
      return {
        connected: true,
        ssid: first.ssid,
        hasNetwork: true,
        type: 'wifi',
        interfaceName: networkStatus.interfaceName ?? null,
      };
    }

    return {
      connected: false,
      hasNetwork: Boolean(networkStatus.hasNetwork),
      type: networkStatus.type ?? null,
      interfaceName: networkStatus.interfaceName ?? null,
    };
  } catch (error) {
    const networkStatus = detectNetworkConnectivity();
    return {
      connected: false,
      hasNetwork: Boolean(networkStatus.hasNetwork),
      type: networkStatus.type ?? null,
      interfaceName: networkStatus.interfaceName ?? null,
      error: error.message || String(error),
    };
  }
});

ipcMain.handle('rfid:get-status', async () => {
  return { ...rfidState };
});

ipcMain.handle('wifi:scan', async () => {
  try {
    const networks = await wifi.scan();
    const sorted = networks
      .filter((net) => net.ssid)
      .sort((a, b) => (b.signal_level || 0) - (a.signal_level || 0));
    return { ok: true, networks: sorted };
  } catch (error) {
    return { ok: false, error: error.message || String(error) };
  }
});

ipcMain.handle('wifi:connect', async (_event, payload) => {
  const { ssid, password } = payload || {};
  if (!ssid || typeof ssid !== 'string') {
    return { ok: false, error: 'SSID invalido' };
  }
  try {
    await wifi.connect({ ssid, password: password ?? undefined });
    return { ok: true };
  } catch (error) {
    return { ok: false, error: error.message || String(error) };
  }
});

// Scale IPC handlers
ipcMain.handle('scale:get-status', async () => {
  return { ...scaleState };
});

ipcMain.handle('scale:get-config', async () => {
  return {
    port: SCALE_CONFIG.port,
    baudRate: SCALE_CONFIG.baudRate,
    slaveId: SCALE_CONFIG.slaveId,
    startAddr: SCALE_CONFIG.startAddr,
    pollInterval: SCALE_CONFIG.pollInterval,
    wordOrder: SCALE_CONFIG.wordOrder,
    registers: SCALE_CONFIG.registers,
    divisor: SCALE_CONFIG.divisor,
    registerCount: SCALE_CONFIG.registerCount,
    rfidPort: RFID_CONFIG.path,
  };
});

ipcMain.handle('scale:set-config', async (_event, payload) => {
  if (!payload || typeof payload !== 'object') {
    return { ok: false, error: 'Configuración inválida' };
  }

  const previousRfidPort = RFID_CONFIG.path;

  // Update config (permite port vacío para usar solo RFID)
  if (typeof payload.port === 'string') {
    const trimmed = normalizeSerialPath(payload.port);
    SCALE_CONFIG.port = trimmed;
    savedScaleConfig.port = trimmed;
  }
  if (payload.baudRate && Number.isFinite(payload.baudRate)) {
    SCALE_CONFIG.baudRate = payload.baudRate;
  }
  if (payload.slaveId && Number.isFinite(payload.slaveId)) {
    SCALE_CONFIG.slaveId = payload.slaveId;
  }
  if (payload.startAddr !== undefined && Number.isFinite(payload.startAddr)) {
    SCALE_CONFIG.startAddr = payload.startAddr;
  }
  if (payload.pollInterval && Number.isFinite(payload.pollInterval)) {
    SCALE_CONFIG.pollInterval = Math.max(payload.pollInterval, 100);
  }
  if (payload.wordOrder && typeof payload.wordOrder === 'string') {
    SCALE_CONFIG.wordOrder = payload.wordOrder.toUpperCase();
  }
  if (payload.registers && typeof payload.registers === 'string') {
    SCALE_CONFIG.registers = payload.registers.toLowerCase();
  }
  if (payload.divisor && Number.isFinite(payload.divisor)) {
    SCALE_CONFIG.divisor = payload.divisor;
  }
  if (payload.registerCount && Number.isFinite(payload.registerCount)) {
    SCALE_CONFIG.registerCount = Math.max(payload.registerCount, 1);
  }
  if (payload.rfidPort && typeof payload.rfidPort === 'string') {
    const trimmed = normalizeSerialPath(payload.rfidPort);
    if (trimmed) {
      RFID_CONFIG.path = trimmed;
      savedScaleConfig.rfidPort = trimmed;
    }
  }

  // Save to file
  const saved = saveScaleConfig({
    port: SCALE_CONFIG.port,
    baudRate: SCALE_CONFIG.baudRate,
    slaveId: SCALE_CONFIG.slaveId,
    startAddr: SCALE_CONFIG.startAddr,
    pollInterval: SCALE_CONFIG.pollInterval,
    wordOrder: SCALE_CONFIG.wordOrder,
    registers: SCALE_CONFIG.registers,
    divisor: SCALE_CONFIG.divisor,
    registerCount: SCALE_CONFIG.registerCount,
    rfidPort: RFID_CONFIG.path,
  });

  const hasConflict = hasSerialPortConflict();
  const rfidPortChanged = RFID_CONFIG.path !== previousRfidPort;
  if (hasConflict || rfidPortChanged) {
    stopRfidReader();
  }

  // Restart scale reader with new config
  restartScaleReader();

  startRfidReader();

  return { ok: true, saved };
});

ipcMain.handle('scale:calibrate', async () => {
  if (!scaleWorker) {
    return { ok: false, error: 'Servicio de báscula no iniciado' };
  }
  
  if (!scaleState.port) {
      return { ok: false, error: 'Báscula no configurada' };
  }

  // Send message to worker
  scaleWorker.send({ type: 'calibrate' });
  
  // We don't wait for specific response from worker for now, 
  // as the worker will log errors or just continue updating weight which will eventually be 0.
  // But to be nicer we might want to return success immediately if message sent.
  return { ok: true };
});

ipcMain.handle('scale:calibrate-known-weight', async (_event, payload) => {
  const knownWeightKg = Number(payload?.knownWeightKg);
  if (!Number.isFinite(knownWeightKg) || knownWeightKg <= 0) {
    return { ok: false, error: 'Peso patrón inválido' };
  }

  const response = await sendScaleWorkerRequest(
    { type: 'calibrate-known-weight', knownWeightKg },
    60000
  );
  return response;
});

ipcMain.handle('scale:clear-tare', async () => {
  const response = await sendScaleWorkerRequest({ type: 'clear-tare' }, 60000);
  return response;
});

ipcMain.handle('scale:setup-init', async () => {
  const response = await sendScaleWorkerRequest({ type: 'setup-init' }, 60000);
  return response;
});

ipcMain.handle('scale:setup-capture-stable', async () => {
  const response = await sendScaleWorkerRequest({ type: 'setup-capture-stable' }, 60000);
  return response;
});

ipcMain.handle('scale:setup-apply-gain', async (_event, payload) => {
  const knownWeightKg = Number(payload?.knownWeightKg);
  const zeroRaw = Number(payload?.zeroRaw);
  const loadRaw = Number(payload?.loadRaw);
  if (!Number.isFinite(knownWeightKg) || knownWeightKg <= 0) {
    return { ok: false, error: 'Peso patrón inválido' };
  }
  if (!Number.isFinite(zeroRaw) || !Number.isFinite(loadRaw)) {
    return { ok: false, error: 'Lecturas inválidas para ajustar ganancia' };
  }
  const response = await sendScaleWorkerRequest(
    { type: 'setup-apply-gain', knownWeightKg, zeroRaw, loadRaw },
    60000
  );
  return response;
});

ipcMain.handle('scale:setup-save-dead-weight', async () => {
  const response = await sendScaleWorkerRequest({ type: 'setup-save-dead-weight' }, 60000);
  return response;
});

ipcMain.handle('scale:setup-clear-offsets', async (_event, payload) => {
  const preserveDeadWeight = Boolean(payload?.preserveDeadWeight);
  const response = await sendScaleWorkerRequest(
    { type: 'setup-clear-offsets', preserveDeadWeight },
    60000
  );
  return response;
});

ipcMain.handle('scale:setup-read-state', async () => {
  const response = await sendScaleWorkerRequest({ type: 'setup-read-state' }, 60000);
  return response;
});

ipcMain.handle('scale:list-ports', async () => {
  try {
    const { SerialPort } = require('serialport');
    const ports = await SerialPort.list();
    return {
      ok: true,
      ports: ports.map((p) => ({
        path: p.path,
        manufacturer: p.manufacturer || null,
        serialNumber: p.serialNumber || null,
        pnpId: p.pnpId || null,
        friendlyName: p.friendlyName || null,
      })),
    };
  } catch (error) {
    return { ok: false, error: error.message || String(error), ports: [] };
  }
});

ipcMain.handle('scale:list-stable-ports', async () => {
  try {
    return await listStableSerialPorts();
  } catch (error) {
    return { ok: false, error: error.message || String(error), ports: [] };
  }
});

ipcMain.handle('scale:get-setup-log-path', async () => {
  return { ok: true, path: SETUP_LOG_FILE };
});

ipcMain.handle('scale:open-setup-log', async () => {
  try {
    if (!fs.existsSync(SETUP_LOG_FILE)) {
      fs.writeFileSync(SETUP_LOG_FILE, '', 'utf8');
    }
    const openResult = await shell.openPath(SETUP_LOG_FILE);
    if (openResult) {
      return { ok: false, error: openResult, path: SETUP_LOG_FILE };
    }
    return { ok: true, path: SETUP_LOG_FILE };
  } catch (error) {
    return { ok: false, error: error.message || String(error), path: SETUP_LOG_FILE };
  }
});

ipcMain.handle('app:restart-with-update', async () => {
  console.log('[App] Restart requested by user.');

  const updateResult = await checkForUpdatesAndRestart('manual');
  if (!isRestartingForUpdate) {
    restartApplication('manual');
  }

  return {
    ok: true,
    restarted: true,
    updated: Boolean(updateResult?.updated),
    targetVersion: updateResult?.targetVersion || null,
  };
});
