/* ════════════════════════════════════════════════════════════════
   core.js — ExperionCrawler Web UI Shared Core
   - Global utilities: esc, setGlobal, log, api, fmt*
   - Tab router: activateTab, paneInit map
   - Shared types/dt helpers
   ════════════════════════════════════════════════════════════════ */

/* ── Helpers ────────────────────────────────────────────────── */
function esc(s) {
  return String(s ?? '')
    .replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;');
}

function setGlobal(state, text) {
  document.getElementById('g-dot').className = `dot ${state}`;
  document.getElementById('g-txt').textContent = text.toUpperCase();
}

function log(id, lines) {
  const el = document.getElementById(id);
  if (!el) return;
  el.classList.remove('hidden');
  el.innerHTML = lines.map(l => `<div class="ll ${l.c}">${esc(l.t)}</div>`).join('');
  el.scrollTop = el.scrollHeight;
}

async function api(method, path, body) {
  const opt = { method, headers: { 'Content-Type': 'application/json' } };
  if (body) opt.body = JSON.stringify(body);
  const res = await fetch(path, opt);
  if (!res.ok) throw new Error(`HTTP ${res.status}: ${await res.text()}`);
  return res.json();
}

/* ── Formatters ─────────────────────────────────────────────── */
function fmtTs(v) {
  if (v == null || v === '') return '-';
  const d = new Date(v);
  if (isNaN(d.getTime())) return String(v);
  const KST = new Date(d.getTime() + 9 * 3600000);
  return KST.toISOString().replace('T',' ').replace('Z','').slice(0,21);
}

function fmtVal(v) {
  if (v == null) return '-';
  if (typeof v === 'number' && isFinite(v))
    return Number.isInteger(v) ? String(v) : v.toFixed(2);
  return String(v);
}

function parseEnumPv(v) {
  if (!v || typeof v !== 'string') return v;
  const m = v.match(/\{\d+\s*\|\s*([^|]+?)\s*\|\s*\}/);
  return m ? m[1].trim() : v;
}

/* ── Tab Router ─────────────────────────────────────────────── */
const paneInit = {};

async function activateTab(tab) {
  const el = document.getElementById(`pane-${tab}`);
  if (!el) return;

  // lazy-load pane HTML if not loaded
  if (el.dataset.loaded !== '1' && el.dataset.src) {
    try {
      const res = await fetch(el.dataset.src, { cache: 'no-cache' });
      if (!res.ok) throw new Error(`HTTP ${res.status}`);
      el.innerHTML = await res.text();
      el.dataset.loaded = '1';
    } catch (e) {
      el.innerHTML = `<div class="pane-error" style="padding:40px;text-align:center;color:var(--t2)">
        <p>⚠️ 파셜 로드 실패: <code>${el.dataset.src}</code></p>
        <p style="font-size:13px">${esc(e.message)}</p>
      </div>`;
      el.dataset.loaded = 'err';
    }
  }

  document.querySelectorAll('.nav-item').forEach(n => n.classList.remove('active'));
  document.querySelectorAll('.pane').forEach(p => p.classList.remove('active'));
  document.querySelector(`.nav-item[data-tab="${tab}"]`)?.classList.add('active');
  el.classList.add('active');

  paneInit[tab]?.();
}

/* ── Date Time Picker (공용 — hist/evt 탭에서 사용) ────────── */
const _dtp = { target: null, year: 0, month: 0,
               selYear: null, selMonth: null, selDay: null,
               selHour: 0, selMin: 0 };

const _DTP_DAYS = ['일','월','화','수','목','금','토'];

function dtOpen(target) {
  _dtp.target = target;
  const hidden = document.getElementById(`hf-${target}`).value;
  const d = hidden ? new Date(hidden) : new Date();
  _dtp.year  = d.getFullYear();
  _dtp.month = d.getMonth();
  if (hidden && !isNaN(d)) {
    _dtp.selYear = d.getFullYear(); _dtp.selMonth = d.getMonth();
    _dtp.selDay  = d.getDate();
    _dtp.selHour = d.getHours();    _dtp.selMin   = d.getMinutes();
  } else {
    _dtp.selYear = null; _dtp.selMonth = null; _dtp.selDay = null;
    _dtp.selHour = 0;    _dtp.selMin   = 0;
  }
  document.getElementById('dt-hour').value = String(_dtp.selHour).padStart(2,'0');
  document.getElementById('dt-min').value  = String(_dtp.selMin).padStart(2,'0');
  dtRenderCal();

  const popup   = document.getElementById('dt-popup');
  const display = document.getElementById(`dtp-${target}-display`);
  const rect    = display.getBoundingClientRect();
  popup.classList.remove('hidden');
  document.getElementById('dt-overlay').classList.remove('hidden');

  const spaceBelow = window.innerHeight - rect.bottom;
  if (spaceBelow < popup.offsetHeight + 8) {
    popup.style.top  = (rect.top - popup.offsetHeight - 4) + 'px';
  } else {
    popup.style.top  = (rect.bottom + 4) + 'px';
  }
  popup.style.left = Math.min(rect.left, window.innerWidth - 296) + 'px';
}

function dtRenderCal() {
  document.getElementById('dt-month-label').textContent =
    `${_dtp.year}년 ${_dtp.month + 1}월`;
  const first      = new Date(_dtp.year, _dtp.month, 1).getDay();
  const daysInMon  = new Date(_dtp.year, _dtp.month + 1, 0).getDate();
  const daysInPrev = new Date(_dtp.year, _dtp.month,     0).getDate();
  const today      = new Date();
  let html = _DTP_DAYS.map(d => `<div class="dt-dow">${d}</div>`).join('');

  for (let i = first - 1; i >= 0; i--)
    html += `<div class="dt-day other-month">${daysInPrev - i}</div>`;

  for (let d = 1; d <= daysInMon; d++) {
    let cls = 'dt-day';
    if (_dtp.year === today.getFullYear() && _dtp.month === today.getMonth() && d === today.getDate())
      cls += ' today';
    if (_dtp.selYear === _dtp.year && _dtp.selMonth === _dtp.month && _dtp.selDay === d)
      cls += ' selected';
    html += `<div class="${cls}" onclick="dtSelectDay(${d})">${d}</div>`;
  }
  const trailing = (first + daysInMon) % 7;
  for (let d = 1; d <= (trailing ? 7 - trailing : 0); d++)
    html += `<div class="dt-day other-month">${d}</div>`;

  document.getElementById('dt-cal-grid').innerHTML = html;
}

function dtSelectDay(day) {
  _dtp.selYear = _dtp.year; _dtp.selMonth = _dtp.month; _dtp.selDay = day;
  dtRenderCal();
}
function dtPrevMonth() {
  if (--_dtp.month < 0) { _dtp.month = 11; _dtp.year--; }
  dtRenderCal();
}
function dtNextMonth() {
  if (++_dtp.month > 11) { _dtp.month = 0; _dtp.year++; }
  dtRenderCal();
}
function dtAdjTime(part, delta) {
  if (part === 'h') {
    _dtp.selHour = ((_dtp.selHour + delta) + 24) % 24;
    document.getElementById('dt-hour').value = String(_dtp.selHour).padStart(2,'0');
  } else {
    _dtp.selMin = ((_dtp.selMin + delta) + 60) % 60;
    document.getElementById('dt-min').value  = String(_dtp.selMin).padStart(2,'0');
  }
}
function dtClampTime(part, el) {
  const max = part === 'h' ? 23 : 59;
  let v = parseInt(el.value);
  if (isNaN(v) || v < 0) v = 0;
  if (v > max) v = max;
  el.value = String(v).padStart(2,'0');
  if (part === 'h') _dtp.selHour = v; else _dtp.selMin = v;
}
function dtConfirm() {
  if (_dtp.selDay === null) { alert('날짜를 선택하세요.'); return; }
  _dtp.selHour = parseInt(document.getElementById('dt-hour').value) || 0;
  _dtp.selMin  = parseInt(document.getElementById('dt-min').value)  || 0;
  const p = n => String(n).padStart(2,'0');
  const val = `${_dtp.selYear}-${p(_dtp.selMonth+1)}-${p(_dtp.selDay)}T${p(_dtp.selHour)}:${p(_dtp.selMin)}`;
  document.getElementById(`hf-${_dtp.target}`).value = val;
  document.getElementById(`dtp-${_dtp.target}-display`).textContent =
    `${_dtp.selYear}-${p(_dtp.selMonth+1)}-${p(_dtp.selDay)} ${p(_dtp.selHour)}:${p(_dtp.selMin)}`;
  dtClose();
}
function dtClear()  { dtClearField(_dtp.target); dtClose(); }
function dtClearField(target) {
  if (!target) return;
  document.getElementById(`hf-${target}`).value = '';
  document.getElementById(`dtp-${target}-display`).textContent = '— 선택 안 함 —';
}
function dtCancel() { dtClose(); }
function dtClose()  {
  document.getElementById('dt-popup').classList.add('hidden');
  document.getElementById('dt-overlay').classList.add('hidden');
  _dtp.target = null;
}

/* ── DOMContentLoaded: bind tab nav, lazy-load on click ──────── */
document.addEventListener('DOMContentLoaded', async () => {
  document.querySelectorAll('.nav-item').forEach(item => {
    item.addEventListener('click', () => activateTab(item.dataset.tab));
  });

  await activateTab('setup');
});
