<?php
$title = 'Flashear ESP8266 - ESP Admin';
ob_start();
?>

<!-- Header -->
<div class="mb-6">
  <div class="flex flex-col lg:flex-row lg:items-center lg:justify-between gap-4">
    <div>
      <h1 class="text-3xl font-bold text-slate-900 flex items-center gap-2">
        <svg class="w-8 h-8 text-emerald-600" fill="none" viewBox="0 0 24 24" stroke="currentColor">
          <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 10V3L4 14h7v7l9-11h-7z" />
        </svg>
        Flashear ESP8266
      </h1>
      <p class="text-slate-600 mt-1">Actualiza el firmware de tus dispositivos vía USB o OTA</p>
    </div>
    <a href="/" class="inline-flex items-center gap-2 px-4 py-2.5 text-sm font-semibold rounded-lg bg-white text-slate-700 hover:bg-slate-50 border-2 border-slate-200 shadow-sm transition-all">
      <svg class="w-5 h-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
        <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 19l-7-7m0 0l7-7m-7 7h18" />
      </svg>
      Volver
    </a>
  </div>
</div>

<!-- Contenedor principal -->
<div class="max-w-5xl mx-auto space-y-6">
  
  <!-- Flasheo USB -->
  <div class="bg-white rounded-xl border-2 border-slate-200 shadow-sm hover:shadow-md hover:-translate-y-0.5 transition-all duration-300 overflow-hidden">
    <div class="px-6 py-4 border-b border-slate-200 bg-gradient-to-r from-blue-50 to-blue-100">
      <h2 class="text-lg font-bold text-slate-900 flex items-center gap-2">
        <svg class="w-5 h-5 text-blue-600" fill="none" viewBox="0 0 24 24" stroke="currentColor">
          <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 9l3 3-3 3m5 0h3M5 20h14a2 2 0 002-2V6a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z" />
        </svg>
        Flasheo USB (Web Serial)
      </h2>
      <p class="text-xs text-slate-600 mt-1">Conecta tu ESP8266 vía USB y flashea directamente desde el navegador</p>
    </div>
    
    <div class="p-6 space-y-4">
      <!-- Requisitos -->
      <div class="bg-blue-50 border-2 border-blue-200 rounded-lg p-4">
        <div class="flex items-start gap-3">
          <svg class="w-5 h-5 text-blue-600 mt-0.5 flex-shrink-0" fill="none" viewBox="0 0 24 24" stroke="currentColor">
            <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
          </svg>
          <div class="flex-1">
            <h3 class="font-semibold text-blue-900 mb-1">Requisitos</h3>
            <ul class="text-sm text-blue-800 space-y-1">
              <li>• Navegador Chrome/Edge (basado en Chromium)</li>
              <li>• Ejecutar en <strong>HTTPS</strong> o <strong>localhost</strong></li>
              <li>• Cable USB conectado al ESP8266</li>
            </ul>
          </div>
        </div>
      </div>

      <!-- Drivers -->
      <div class="bg-amber-50 border-2 border-amber-200 rounded-lg p-4">
        <div class="flex items-start gap-3">
          <svg class="w-5 h-5 text-amber-600 mt-0.5 flex-shrink-0" fill="none" viewBox="0 0 24 24" stroke="currentColor">
            <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z" />
          </svg>
          <div class="flex-1">
            <h3 class="font-semibold text-amber-900 mb-2">Drivers Requeridos</h3>
            <p class="text-sm text-amber-800 mb-3">Si tu ESP usa chip <strong>CH340</strong> (común en clones), instala el driver:</p>
            <a href="http://www.wch.cn/downloads/CH341SER_EXE.html" target="_blank" class="inline-flex items-center gap-2 px-4 py-2 text-sm font-medium rounded-lg bg-amber-600 text-white hover:bg-amber-700 transition-all">
              <svg class="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
                <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-4l-4 4m0 0l-4-4m4 4V4" />
              </svg>
              Descargar Driver CH340
            </a>
            <p class="text-xs text-amber-700 mt-2">
              Para NodeMCU originales (CP2102): 
              <a class="font-medium underline hover:text-amber-900" href="https://www.silabs.com/developers/usb-to-uart-bridge-vcp-drivers" target="_blank">Silicon Labs</a>
            </p>
          </div>
        </div>
      </div>

      <!-- Info Firmware -->
      <div class="bg-slate-50 border-2 border-slate-200 rounded-lg p-4">
        <h3 class="font-semibold text-slate-900 mb-2">Firmware</h3>
        <p class="text-sm text-slate-600 mb-2">Se usará el archivo definido en <code class="px-2 py-0.5 bg-slate-200 rounded text-xs font-mono">/assets/flash/esp8266-manifest.json</code></p>
        <div id="otaFirmwareInfo_usb" class="text-xs text-slate-500">Cargando manifest...</div>
      </div>

      <!-- Botón de flasheo -->
      <div class="flex flex-col items-center gap-3 py-4">
        <esp-web-install-button id="installBtn" manifest="/assets/flash/esp8266-manifest.json"></esp-web-install-button>
        <p class="text-xs text-slate-500 text-center max-w-md">
          Si el botón aparece deshabilitado, verifica el navegador y el contexto seguro (HTTPS/localhost)
        </p>
      </div>
    </div>
  </div>

  <!-- OTA (Over-The-Air) -->
  <div class="bg-white rounded-xl border-2 border-slate-200 shadow-sm hover:shadow-md hover:-translate-y-0.5 transition-all duration-300 overflow-hidden">
    <div class="px-6 py-4 border-b border-slate-200 bg-gradient-to-r from-emerald-50 to-emerald-100">
      <h2 class="text-lg font-bold text-slate-900 flex items-center gap-2">
        <svg class="w-5 h-5 text-emerald-600" fill="none" viewBox="0 0 24 24" stroke="currentColor">
          <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8.111 16.404a5.5 5.5 0 017.778 0M12 20h.01m-7.08-7.071c3.904-3.905 10.236-3.905 14.141 0M1.394 9.393c5.857-5.857 15.355-5.857 21.213 0" />
        </svg>
        OTA (Over-The-Air)
      </h2>
      <p class="text-xs text-slate-600 mt-1">Actualiza el firmware remotamente sin cables</p>
    </div>
    
    <div class="p-6 space-y-4">
      <!-- Info OTA -->
      <div class="bg-emerald-50 border-2 border-emerald-200 rounded-lg p-4">
        <div class="flex items-start gap-3">
          <svg class="w-5 h-5 text-emerald-600 mt-0.5 flex-shrink-0" fill="none" viewBox="0 0 24 24" stroke="currentColor">
            <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
          </svg>
          <div class="flex-1">
            <h3 class="font-semibold text-emerald-900 mb-1">Cómo funciona</h3>
            <p class="text-sm text-emerald-800">
              El dispositivo descargará el firmware desde la URL especificada y se actualizará automáticamente. 
              Asegúrate de que la URL sea accesible desde la red del dispositivo.
            </p>
          </div>
        </div>
      </div>

      <!-- Selector de dispositivo -->
      <div>
        <label for="otaChipidSelect" class="block text-sm font-semibold text-slate-700 mb-2">
          Dispositivo
        </label>
        <select id="otaChipidSelect" class="w-full px-4 py-2.5 border-2 border-slate-200 rounded-lg focus:outline-none focus:border-emerald-500 focus:ring-2 focus:ring-emerald-200 transition-all">
          <option value="" disabled selected>Cargando dispositivos...</option>
        </select>
        <p class="text-xs text-slate-500 mt-2" id="otaOnlineHint">
          Solo se muestran dispositivos <strong>online</strong> que <strong>necesitan actualización</strong> (se ocultan los que ya tienen FW OK)
        </p>
        <div id="otaDeviceStatus" class="mt-3 inline-flex items-center gap-2 px-3 py-1.5 text-xs font-semibold rounded-full bg-slate-100 text-slate-700 border border-slate-200">
          <span class="w-2 h-2 rounded-full bg-slate-400"></span>
          Selecciona un dispositivo
        </div>
      </div>

      <!-- Info Firmware OTA -->
      <div class="bg-slate-50 border-2 border-slate-200 rounded-lg p-4">
        <h3 class="font-semibold text-slate-900 mb-2">Firmware</h3>
        <p class="text-sm text-slate-600 mb-2">Se usará el archivo definido en <code class="px-2 py-0.5 bg-slate-200 rounded text-xs font-mono">/assets/flash/esp8266-manifest.json</code></p>
        <div id="otaFirmwareInfo_ota" class="text-xs text-slate-500">Cargando manifest...</div>
      </div>

      <!-- Botones de acción -->
      <div class="flex flex-wrap items-center gap-3 pt-2">
        <button id="btnStartOTA" class="inline-flex items-center gap-2 px-4 py-2.5 text-sm font-semibold rounded-lg bg-emerald-600 text-white hover:bg-emerald-700 shadow-sm transition-all">
          <svg class="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
            <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-8l-4-4m0 0L8 8m4-4v12" />
          </svg>
          Iniciar OTA
        </button>
        <button id="btnRebootAfterOTA" class="inline-flex items-center gap-2 px-4 py-2.5 text-sm font-medium rounded-lg bg-white text-slate-700 hover:bg-slate-50 border-2 border-slate-200 transition-all" title="Reiniciar dispositivo después de la actualización">
          <svg class="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
            <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15" />
          </svg>
          Reiniciar Después
        </button>
      </div>
      
      <!-- Indicador de progreso OTA -->
      <div id="otaProgress" class="hidden mt-4 p-4 bg-blue-50 border-2 border-blue-200 rounded-lg">
        <div class="flex items-center gap-3">
          <div class="flex-shrink-0">
            <svg class="animate-spin h-6 w-6 text-blue-600" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
              <circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
              <path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
            </svg>
          </div>
          <div class="flex-1">
            <p id="otaProgressText" class="text-sm font-semibold text-blue-900">Procesando actualización OTA...</p>
            <p class="text-xs text-blue-700 mt-1">El dispositivo descargará e instalará el firmware. Esto puede tomar hasta 3 minutos.</p>
          </div>
        </div>
      </div>
    </div>
  </div>

</div>

<!-- ESP Web Tools: botón de instalación/flasheo -->
<script type="module" src="https://unpkg.com/esp-web-tools@latest/dist/web/install-button.js?module"></script>
<script>
  // OTA logic
  (function(){
    const MANIFEST_URL = '/assets/flash/esp8266-manifest.json';
    let firmwareUrl = '';
    let firmwareVersion = '';
    let lastOtaChipid = '';
    let otaTimeoutId = null;
    const OTA_INACTIVITY_TIMEOUT_MS = 300000;

    // Datos de dispositivos
    let dispositivos = {}; // chipid -> {chipid, tag, nombre, ...}
    let dispositivosLoadError = false;

    const $chipSel = document.getElementById('otaChipidSelect');
    const $btn = document.getElementById('btnStartOTA');
    const $reboot = document.getElementById('btnRebootAfterOTA');
    const $fwInfo_usb = document.getElementById('otaFirmwareInfo_usb');
    const $fwInfo_ota = document.getElementById('otaFirmwareInfo_ota');
    const $otaProgress = document.getElementById('otaProgress');
    const $otaProgressText = document.getElementById('otaProgressText');
    const $otaDeviceStatus = document.getElementById('otaDeviceStatus');

    if (!$btn) return;
    try { if (window.applyIcons) window.applyIcons(document); } catch {}

    function escapeHtml(value) {
      return String(value ?? '').replace(/[&<>"']/g, ch => ({
        '&': '&amp;',
        '<': '&lt;',
        '>': '&gt;',
        '"': '&quot;',
        "'": '&#039;'
      }[ch]));
    }

    function normalizeChip(value) {
      return String(value || '').trim().toUpperCase();
    }

    function findDevice(chipid) {
      const normalized = normalizeChip(chipid);
      const key = Object.keys(dispositivos || {}).find(item => normalizeChip(item) === normalized || normalizeChip(dispositivos[item]?.chipid) === normalized);
      return key ? dispositivos[key] : null;
    }

    function setDeviceOnline(chipid, online) {
      const normalized = normalizeChip(chipid);
      const key = Object.keys(dispositivos || {}).find(item => normalizeChip(item) === normalized || normalizeChip(dispositivos[item]?.chipid) === normalized);
      if (key) dispositivos[key].online = !!online;
    }

    function updateSelectedDeviceStatus() {
      if (!$otaDeviceStatus) return;
      const selectedChipid = normalizeChip($chipSel?.value);
      if (!selectedChipid) {
        $otaDeviceStatus.className = 'mt-3 inline-flex items-center gap-2 px-3 py-1.5 text-xs font-semibold rounded-full bg-slate-100 text-slate-700 border border-slate-200';
        $otaDeviceStatus.innerHTML = '<span class="w-2 h-2 rounded-full bg-slate-400"></span> Selecciona un dispositivo';
        return;
      }

      const device = findDevice(selectedChipid);
      const online = device && (device.online === true || String(device.online || '').toLowerCase() === 'online');
      $otaDeviceStatus.className = online
        ? 'mt-3 inline-flex items-center gap-2 px-3 py-1.5 text-xs font-semibold rounded-full bg-emerald-100 text-emerald-700 border border-emerald-200'
        : 'mt-3 inline-flex items-center gap-2 px-3 py-1.5 text-xs font-semibold rounded-full bg-red-100 text-red-700 border border-red-200';
      $otaDeviceStatus.innerHTML = online
        ? '<span class="w-2 h-2 rounded-full bg-emerald-500 animate-pulse"></span> Online ahora'
        : '<span class="w-2 h-2 rounded-full bg-red-500"></span> Offline ahora';
    }

    function clearOtaTimeout() {
      if (otaTimeoutId) clearTimeout(otaTimeoutId);
      otaTimeoutId = null;
    }

    function armOtaTimeout() {
      clearOtaTimeout();
      otaTimeoutId = setTimeout(function(){
        if ($otaProgressText) $otaProgressText.textContent = 'OTA sin actividad reciente. Verifica el estado del dispositivo.';
        try { mostrarToast('No se detecto actividad OTA reciente. Verifica manualmente.', { type: 'warning' }); } catch{}
        lastOtaChipid = '';
        if ($btn) $btn.disabled = false;
      }, OTA_INACTIVITY_TIMEOUT_MS);
    }

    // Cargar manifest para obtener la URL del .bin
    function cargarManifest() {
      if ($fwInfo_ota) $fwInfo_ota.textContent = 'Cargando manifest...';
      if ($fwInfo_usb) $fwInfo_usb.textContent = 'Cargando manifest...';
      return fetch(MANIFEST_URL, { cache: 'no-store' })
        .then(r => r.json())
        .then(json => {
          try {
            firmwareVersion = (typeof json.version === 'string') ? json.version : '';
            const builds = Array.isArray(json.builds) ? json.builds : [];
            const first = builds[0];
            const parts = Array.isArray(first?.parts) ? first.parts : [];
            const part = parts.find(p => typeof p?.path === 'string') || null;
            if (part) {
              const base = new URL(MANIFEST_URL, window.location.origin);
              firmwareUrl = new URL(part.path, base).href;
              const infoText = firmwareVersion ? `Firmware: ${firmwareUrl} (v${firmwareVersion})` : `Firmware: ${firmwareUrl}`;
              if ($fwInfo_ota) $fwInfo_ota.textContent = infoText;
              if ($fwInfo_usb) $fwInfo_usb.textContent = infoText;
              return;
            }
          } catch {}
          firmwareUrl = '';
          if ($fwInfo_ota) $fwInfo_ota.textContent = 'No se pudo determinar el firmware desde el manifest.';
          if ($fwInfo_usb) $fwInfo_usb.textContent = 'No se pudo determinar el firmware desde el manifest.';
        })
        .catch(() => {
          firmwareUrl = '';
          if ($fwInfo_ota) $fwInfo_ota.textContent = 'No se pudo cargar el manifest.';
          if ($fwInfo_usb) $fwInfo_usb.textContent = 'No se pudo cargar el manifest.';
        });
    }

    // Cargar dispositivos
    function cargarDispositivos() {
      return fetch('/api/get_dispositivos', { credentials: 'same-origin' })
        .then(r => {
          if (!r.ok) throw new Error('HTTP ' + r.status);
          return r.json();
        })
        .then(data => {
          dispositivosLoadError = false;
          dispositivos = data || {};
        })
        .catch(() => {
          dispositivosLoadError = true;
          dispositivos = {};
          try { mostrarToast('No se pudo cargar la lista de dispositivos.', { type: 'danger' }); } catch{}
        });
    }

    function renderSelect() {
      if (!$chipSel) return;
      if (dispositivosLoadError) {
        $chipSel.innerHTML = '<option value="" disabled selected>Error al cargar dispositivos</option>';
        return;
      }

      const opciones = [];
      let totalOnline = 0;
      let filtradosPorFW = 0;
      
      for (const chip in dispositivos) {
        const d = dispositivos[chip];
        const isOnline = d.online === true || String(d.online || '').toLowerCase() === 'online';
        if (isOnline) {
          totalOnline++;
          // Filtrar dispositivos que ya tienen el firmware actualizado
          const deviceFW = (d.firmware || '').trim();
          const isFWUpdated = firmwareVersion && deviceFW && deviceFW === firmwareVersion;
          
          if (!isFWUpdated) {
            const fwInfo = deviceFW ? ` - FW: ${deviceFW}` : '';
            const label = `${d.tag || chip} (${d.chipid || chip})${fwInfo}`;
            opciones.push(`<option value="${escapeHtml(d.chipid || chip)}">${escapeHtml(label)}</option>`);
          } else {
            filtradosPorFW++;
          }
        }
      }
      
      if (opciones.length === 0) {
        if (totalOnline === 0) {
          $chipSel.innerHTML = '<option value="" disabled selected>No hay dispositivos online</option>';
        } else {
          $chipSel.innerHTML = '<option value="" disabled selected>Todos los dispositivos tienen FW actualizado</option>';
        }
      } else {
        const hint = filtradosPorFW > 0 ? ` (${filtradosPorFW} con FW OK ocultos)` : '';
        $chipSel.innerHTML = `<option value="" disabled selected>Seleccione un dispositivo${hint}</option>` + opciones.join('');
      }
      
      // Preseleccionar desde query si existe y está online
      try {
        const qp = new URLSearchParams(window.location.search);
        const qchip = normalizeChip(qp.get('chipid'));
        if (qchip && opciones.length) {
          const opt = Array.from($chipSel.options).find(o => normalizeChip(o.value) === qchip);
          if (opt) $chipSel.value = opt.value;
        }
      } catch {}
      updateSelectedDeviceStatus();
    }

    Promise.all([cargarManifest(), cargarDispositivos()]).then(renderSelect);
    $chipSel?.addEventListener('change', updateSelectedDeviceStatus);

    if (window.mqttRealtime) {
      window.mqttRealtime.onMessage(function(evt) {
        if (evt.type === 'state') {
          setDeviceOnline(evt.chipid, evt.estado === 'online');
          updateSelectedDeviceStatus();
        }

        if (evt.type !== 'ota' || normalizeChip(evt.chipid) !== normalizeChip(lastOtaChipid)) return;

        const status = String(evt.status || '').trim();
        if ($otaProgress) $otaProgress.classList.remove('hidden');
        if ($otaProgressText) $otaProgressText.textContent = `OTA: ${status}`;
        armOtaTimeout();

        if (/^(success|failed|no_updates|error:)/i.test(status)) {
          clearOtaTimeout();
          lastOtaChipid = '';
          if ($btn) $btn.disabled = false;
          if (status === 'success') {
            try { mostrarToast('OTA completada. El dispositivo se reiniciara.', { type: 'success' }); } catch{}
          } else {
            try { mostrarToast(`OTA finalizo con estado: ${status}`, { type: status.indexOf('error:') === 0 || status === 'failed' ? 'danger' : 'warning' }); } catch{}
          }
        }
      });
    }

    $btn.addEventListener('click', function(){
      const chipid = ($chipSel?.value || '').trim();
      const fw = firmwareUrl;
      if (!chipid) { try { mostrarToast('Selecciona un dispositivo online', { type: 'danger' }); } catch{} return; }
      if (!fw || !fw.endsWith('.bin')) { try { mostrarToast('El manifest no define un .bin válido', { type: 'danger' }); } catch{} return; }

      const topic = `dispositivo/${chipid}/comando/ota`;
      const payload = { url: fw };

      $btn.disabled = true;
      clearOtaTimeout();
      
      // Mostrar progreso
      if ($otaProgress) $otaProgress.classList.remove('hidden');
      if ($otaProgressText) $otaProgressText.textContent = 'Enviando comando OTA al dispositivo...';

      lastOtaChipid = chipid;

      const publisher = api.postJSON('/api/mqtt_publish', { topic, payload, qos: 0, retain: false });

      publisher
        .then(function(){
          try { mostrarToast('Comando OTA enviado'); } catch{}
          if ($otaProgressText) $otaProgressText.textContent = 'Esperando que el dispositivo descargue e instale el firmware...';
          
          armOtaTimeout();
          /*
            if ($otaProgress) $otaProgress.classList.add('hidden');
            try { mostrarToast('⏱️ No se detectó respuesta del dispositivo. Verifica manualmente.', { type: 'warning' }); } catch{}
            lastOtaChipid = '';
            $btn.disabled = false;
          }, 180000); // 3 minutos
          */
        })
        .catch(function(err){
          const msg = err.data?.message || err.data?.msg || err.message || 'Error al enviar OTA';
          try { mostrarToast(msg, { type: 'danger' }); } catch{}
          if ($otaProgress) $otaProgress.classList.add('hidden');
          lastOtaChipid = '';
          $btn.disabled = false;
        });
    });

    $reboot?.addEventListener('click', function(){
      const chipid = ($chipSel?.value || '').trim();
      if (!chipid) { try { mostrarToast('Selecciona un dispositivo', { type: 'danger' }); } catch{} return; }
      const topic = `dispositivo/${chipid}/comando/reboot`;
      const payload = '1';
      const publisher = window.mqttRealtime
        ? window.mqttRealtime.publish(topic, payload)
        : api.postJSON('/api/mqtt_publish', { topic, payload });

      publisher
        .then(function(){ try { mostrarToast('Comando de reinicio enviado'); } catch{} })
        .catch(function(){ try { mostrarToast('No se pudo enviar reinicio', { type: 'danger' }); } catch{} });
    });
  })();
</script>
<?php
$content = ob_get_clean();
if (!defined('RENDER_PARTIAL')) {
  include __DIR__ . '/../layouts/layout.php';
}
