/* ──────────────────────────────────────────────────────────────
   ESP32 Web Flasher  –  app.js
   Web Serial API + esp-web-tools 연동
   ────────────────────────────────────────────────────────────── */

// 백엔드 API 베이스 URL (nginx 리버스 프록시 경유)
const API = '';

// ── 상태 ──────────────────────────────────────────────────────
const state = {
  port:            null,    // 현재 열린 시리얼 포트
  selectedFwId:    null,    // 선택된 펌웨어 ID
  firmwareList:    [],
};

// ── DOM 헬퍼 ──────────────────────────────────────────────────
const $  = (sel, ctx = document) => ctx.querySelector(sel);
const $$ = (sel, ctx = document) => [...ctx.querySelectorAll(sel)];

// ── 시리얼 모니터 ─────────────────────────────────────────────
const monitor = $('#monitor');

function log(text, type = '') {
  const line = document.createElement('span');
  if (type) line.className = `line-${type}`;
  line.textContent = text + '\n';
  monitor.appendChild(line);
  monitor.scrollTop = monitor.scrollHeight;
}

function logClear() {
  monitor.innerHTML = '';
}

// ── 브라우저 호환성 체크 ──────────────────────────────────────
function checkBrowserCompat() {
  const supported = 'serial' in navigator;
  const banner = $('#browser-warning');

  if (!supported) {
    banner.style.display = 'flex';
    $$('.needs-serial').forEach(el => el.disabled = true);
    log('⚠ Web Serial API 미지원 브라우저입니다. Chrome 또는 Edge를 사용하세요.', 'err');
  } else {
    log('✓ 브라우저 Web Serial API 지원 확인', 'ok');
    banner.style.display = 'none';
  }

  return supported;
}

// ── STEP 1: 시리얼 연결 확인 ──────────────────────────────────
const btnConnect    = $('#btn-connect');
const btnDisconnect = $('#btn-disconnect');
const connDot       = $('#conn-dot');
const connText      = $('#conn-text');
const deviceInfo    = $('#device-info');

function setConnStatus(status) {
  connDot.className = 'dot';
  if (status === 'ok') {
    connDot.classList.add('dot-ok');
    connText.textContent = '연결됨';
    btnConnect.style.display    = 'none';
    btnDisconnect.style.display = 'inline-flex';
  } else if (status === 'loading') {
    connDot.classList.add('dot-loading');
    connText.textContent = '연결 중…';
  } else if (status === 'error') {
    connDot.classList.add('dot-error');
    connText.textContent = '연결 실패';
    btnConnect.style.display    = 'inline-flex';
    btnDisconnect.style.display = 'none';
  } else {
    connDot.classList.add('dot-idle');
    connText.textContent = '연결 안 됨';
    btnConnect.style.display    = 'inline-flex';
    btnDisconnect.style.display = 'none';
    deviceInfo.textContent = '';
  }
}

btnConnect.addEventListener('click', async () => {
  if (!('serial' in navigator)) return;

  setConnStatus('loading');
  logClear();
  log('시리얼 포트 선택 대화상자 열기…', 'info');

  try {
    // ESP32S3 USB VID: 0x303A (Espressif)
    // 필터 없이 모든 포트 허용 (테스트용)
    state.port = await navigator.serial.requestPort();
    await state.port.open({ baudRate: 115200 });

    const info = state.port.getInfo();
    const vid  = info.usbVendorId  != null ? `0x${info.usbVendorId.toString(16).toUpperCase().padStart(4,'0')}` : 'N/A';
    const pid  = info.usbProductId != null ? `0x${info.usbProductId.toString(16).toUpperCase().padStart(4,'0')}` : 'N/A';

    const isEspressif = info.usbVendorId === 0x303A;

    setConnStatus('ok');
    deviceInfo.textContent = `VID: ${vid}  PID: ${pid}  ${isEspressif ? '(Espressif ✓)' : ''}`;

    log(`✓ 포트 연결 성공`, 'ok');
    log(`  VID: ${vid}  PID: ${pid}`, 'info');
    if (isEspressif) {
      log('  Espressif 장치 감지됨 (ESP32S3 가능성 높음)', 'ok');
    } else {
      log('⚠ Espressif VID가 아닙니다. USB-UART 변환기일 수 있습니다.', 'warn');
    }

    // 연결 확인 후 포트 닫기 (esp-web-tools가 재사용할 수 있도록)
    await state.port.close();
    log('  포트를 닫았습니다 (플래시 시 자동 재연결)', 'info');

    updateStep1Badge('done');
  } catch (err) {
    if (err.name === 'NotFoundError') {
      log('ℹ 포트 선택이 취소되었습니다.', 'info');
      setConnStatus('idle');
    } else {
      log(`✗ 연결 실패: ${err.message}`, 'err');
      setConnStatus('error');
    }
    state.port = null;
  }
});

btnDisconnect.addEventListener('click', async () => {
  try {
    if (state.port) {
      if (state.port.readable || state.port.writable) {
        await state.port.close();
      }
      state.port = null;
    }
  } catch {}
  setConnStatus('idle');
  updateStep1Badge('pending');
  log('포트 연결 해제됨', 'info');
});

function updateStep1Badge(status) {
  const badge = $('#step1-badge');
  badge.className = 'step-badge' + (status === 'done' ? ' done' : '');
  badge.textContent = status === 'done' ? '✓' : '1';
}

// ── STEP 2: 펌웨어 업로드 ─────────────────────────────────────
const uploadForm  = $('#upload-form');
const progressWrap = $('#upload-progress');
const progressBar  = $('#upload-bar');
const progressPct  = $('#upload-pct');

// Drag & Drop 처리
function setupDropZone(zoneId, inputId) {
  const zone  = $(`#${zoneId}`);
  const input = $(`#${inputId}`);
  const label = zone.querySelector('.drop-label');

  zone.addEventListener('click', () => input.click());

  zone.addEventListener('dragover', e => {
    e.preventDefault();
    zone.classList.add('dragover');
  });

  zone.addEventListener('dragleave', () => zone.classList.remove('dragover'));

  zone.addEventListener('drop', e => {
    e.preventDefault();
    zone.classList.remove('dragover');
    const file = e.dataTransfer.files[0];
    if (file && file.name.endsWith('.bin')) {
      const dt = new DataTransfer();
      dt.items.add(file);
      input.files = dt.files;
      label.innerHTML = `<span class="file-selected">📎 ${file.name} (${formatBytes(file.size)})</span>`;
    }
  });

  input.addEventListener('change', () => {
    if (input.files[0]) {
      label.innerHTML = `<span class="file-selected">📎 ${input.files[0].name} (${formatBytes(input.files[0].size)})</span>`;
    }
  });
}

setupDropZone('drop-firmware',   'file-firmware');
setupDropZone('drop-bootloader', 'file-bootloader');
setupDropZone('drop-partitions', 'file-partitions');
setupDropZone('drop-boot-app0',  'file-boot-app0');
setupDropZone('drop-app',        'file-app');

// ── 업로드 모드 토글 ─────────────────────────────────────────
const modeMerged = $('#mode-merged');
const modeSplit  = $('#mode-split');
const splitFiles = $('#split-files');
const lblMerged  = $('#lbl-merged');
const lblSplit   = $('#lbl-split');
const firmwareHintText = $('#firmware-hint-text');

function applyUploadMode() {
  const isMerged = modeMerged.checked;
  splitFiles.style.display = isMerged ? 'none' : 'flex';
  lblMerged.style.border = isMerged ? '2px solid var(--accent)' : '2px solid var(--border)';
  lblMerged.style.background = isMerged ? 'rgba(0,200,150,.06)' : '';
  lblSplit.style.border  = isMerged ? '2px solid var(--border)' : '2px solid var(--accent)';
  lblSplit.style.background = isMerged ? '' : 'rgba(0,200,150,.06)';
  firmwareHintText.textContent = isMerged
    ? '*.merged.bin 파일을 드래그하거나 클릭하세요'
    : '앱 바이너리 (*.ino.bin)를 드래그하거나 클릭하세요';
}

modeMerged.addEventListener('change', applyUploadMode);
modeSplit.addEventListener('change', applyUploadMode);

uploadForm.addEventListener('submit', async e => {
  e.preventDefault();

  const isMerged = modeMerged.checked;
  const fwFile   = $('#file-firmware').files[0];

  if (isMerged) {
    if (!fwFile) { alert('merged.bin 파일을 선택하세요.'); return; }
  } else {
    const appFile = $('#file-app').files[0];
    if (!fwFile && !appFile) { alert('앱 바이너리(.bin) 파일을 선택하세요.'); return; }
    // 분리 모드에서는 app 드롭존 파일을 firmware로 사용
    if (!fwFile && appFile) {
      const dt = new DataTransfer();
      dt.items.add(appFile);
      $('#file-firmware').files = dt.files;
    }
  }

  const finalFwFile = $('#file-firmware').files[0] || $('#file-app').files[0];

  const fd = new FormData();
  fd.append('name',       $('#fw-name').value    || finalFwFile.name.replace(/\.bin$/i,''));
  fd.append('version',    $('#fw-version').value || '1.0.0');
  fd.append('description', $('#fw-desc').value);
  fd.append('chipFamily', $('#fw-chip').value);
  fd.append('firmware',   finalFwFile);
  // flashAddress 미전송 → 서버가 bootloader 유무로 자동 결정 (merged:0x0 / split:0x10000)

  if (!isMerged) {
    const blFile = $('#file-bootloader').files[0];
    if (blFile) fd.append('bootloader', blFile);

    const ptFile = $('#file-partitions').files[0];
    if (ptFile) fd.append('partitions', ptFile);

    const baFile = $('#file-boot-app0').files[0];
    if (baFile) fd.append('boot_app0', baFile);
  }

  progressWrap.style.display = 'block';
  progressBar.style.width    = '0%';
  progressPct.textContent    = '업로드 중…';

  try {
    const resp = await fetchWithProgress(`${API}/api/firmware/upload`, {
      method: 'POST',
      body:   fd,
    }, pct => {
      progressBar.style.width = `${pct}%`;
      progressPct.textContent = `${pct}%`;
    });

    if (!resp.ok) {
      const err = await resp.json();
      throw new Error(err.error || '업로드 실패');
    }

    const data = await resp.json();
    progressPct.textContent = '✓ 업로드 완료';
    progressBar.style.width = '100%';
    log(`✓ 펌웨어 업로드 완료: ${data.firmware.name} (ID: ${data.id})`, 'ok');

    uploadForm.reset();
    $$('.drop-label').forEach(l => {
      if (!l.classList.contains('file-selected')) return;
      l.innerHTML = l.closest('[id^=drop-firmware]')
        ? '📁 펌웨어 .bin 파일을 드래그하거나 클릭하세요'
        : '📁 선택 (선택사항)';
    });

    await loadFirmwareList();
    switchTab('tab-flash');
  } catch (err) {
    progressPct.textContent = `✗ ${err.message}`;
    progressBar.style.background = 'var(--danger)';
    log(`✗ 업로드 오류: ${err.message}`, 'err');
  }
});

function fetchWithProgress(url, options, onProgress) {
  return new Promise((resolve, reject) => {
    const xhr = new XMLHttpRequest();
    xhr.open(options.method || 'GET', url);

    xhr.upload.onprogress = e => {
      if (e.lengthComputable) onProgress(Math.round((e.loaded / e.total) * 100));
    };

    xhr.onload = () => {
      const r = new Response(xhr.responseText, { status: xhr.status });
      resolve(r);
    };

    xhr.onerror = () => reject(new Error('네트워크 오류'));
    xhr.send(options.body);
  });
}

// ── STEP 3: 펌웨어 목록 & 플래시 ────────────────────────────────
const fwListEl     = $('#fw-list');
const installBtn   = $('#esp-install-btn');

async function loadFirmwareList() {
  try {
    const resp = await fetch(`${API}/api/firmware`);
    state.firmwareList = await resp.json();
    renderFwList();
  } catch (err) {
    log(`✗ 펌웨어 목록 로드 실패: ${err.message}`, 'err');
  }
}

function renderFwList() {
  if (!state.firmwareList.length) {
    fwListEl.innerHTML = `
      <div class="empty-state">
        <div class="icon">📦</div>
        <div>업로드된 펌웨어가 없습니다.</div>
        <div style="margin-top:6px;font-size:12px;">먼저 <strong>펌웨어 업로드</strong> 탭에서 .bin 파일을 업로드하세요.</div>
      </div>`;
    return;
  }

  fwListEl.innerHTML = '';
  state.firmwareList.forEach(fw => {
    const el = document.createElement('div');
    el.className = 'fw-item' + (fw.id === state.selectedFwId ? ' selected' : '');
    el.dataset.id = fw.id;
    el.innerHTML = `
      <div class="fw-icon">💾</div>
      <div class="fw-info">
        <div class="fw-name">${escHtml(fw.name)}</div>
        <div class="fw-meta">
          v${escHtml(fw.version)} · ${escHtml(fw.chipFamily)} ·
          <span class="chip-badge">${fw.parts.length}개 파트</span> ·
          ${new Date(fw.createdAt).toLocaleString('ko-KR')}
        </div>
      </div>
      <div class="fw-actions">
        <button class="btn btn-danger btn-sm btn-delete" data-id="${fw.id}">삭제</button>
      </div>`;

    el.addEventListener('click', e => {
      if (e.target.classList.contains('btn-delete')) return;
      selectFirmware(fw.id);
    });

    el.querySelector('.btn-delete').addEventListener('click', e => {
      e.stopPropagation();
      deleteFirmware(fw.id);
    });

    fwListEl.appendChild(el);
  });
}

function selectFirmware(id) {
  state.selectedFwId = id;
  $$('.fw-item').forEach(el => el.classList.toggle('selected', el.dataset.id === id));

  const manifestUrl = `${location.origin}/api/firmware/${id}/manifest`;
  installBtn.setAttribute('manifest', manifestUrl);
  $('#selected-fw-info').textContent = `선택됨: ${state.firmwareList.find(f=>f.id===id)?.name}`;

  log(`✓ 플래시 대상 선택: ${state.firmwareList.find(f=>f.id===id)?.name}`, 'ok');
  log(`  Manifest: ${manifestUrl}`, 'info');
}

async function deleteFirmware(id) {
  if (!confirm('이 펌웨어를 삭제하시겠습니까?')) return;
  try {
    await fetch(`${API}/api/firmware/${id}`, { method: 'DELETE' });
    if (state.selectedFwId === id) {
      state.selectedFwId = null;
      installBtn.removeAttribute('manifest');
      $('#selected-fw-info').textContent = '선택된 펌웨어 없음';
    }
    await loadFirmwareList();
    log(`✓ 펌웨어 삭제 완료`, 'ok');
  } catch (err) {
    log(`✗ 삭제 실패: ${err.message}`, 'err');
  }
}

// ── 탭 전환 ───────────────────────────────────────────────────
function switchTab(tabId) {
  $$('.tab-btn').forEach(b => b.classList.toggle('active', b.dataset.tab === tabId));
  $$('.tab-panel').forEach(p => p.classList.toggle('active', p.id === tabId));
}

$$('.tab-btn').forEach(btn => {
  btn.addEventListener('click', () => switchTab(btn.dataset.tab));
});

// ── 로그 지우기 ───────────────────────────────────────────────
$('#btn-clear-log').addEventListener('click', logClear);

// ── 유틸 ──────────────────────────────────────────────────────
function formatBytes(bytes) {
  if (bytes < 1024) return `${bytes} B`;
  if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
  return `${(bytes / 1024 / 1024).toFixed(2)} MB`;
}

function escHtml(str) {
  return String(str)
    .replace(/&/g,'&amp;').replace(/</g,'&lt;')
    .replace(/>/g,'&gt;').replace(/"/g,'&quot;');
}

// ── 초기화 ────────────────────────────────────────────────────
(async () => {
  checkBrowserCompat();
  setConnStatus('idle');
  btnDisconnect.style.display = 'none';
  await loadFirmwareList();
  log('ESP32 Web Flasher 준비 완료', 'ok');
})();
