/* ─────────────────────────────────────────────────────────────
    06  포인트빌더
───────────────────────────────────────────────────────────── */
const PB_GROUPS = ['controller1', 'analogmon1', 'digital1', 'digital2', 'custom'];
let pbPreviewData = [];

function pbCollectGroupData(groupKey) {
  const patternInput = document.querySelector(`input[data-group="${groupKey}"][data-field="tagPatterns"]`);
  const tagPatterns = patternInput?.value
    ? patternInput.value.split(',').map(s => s.trim()).filter(Boolean)
    : [];

  const checkedAttrs = Array.from(
    document.querySelectorAll(`input[data-group="${groupKey}"][data-field="attributes"]:checked`)
  ).map(cb => cb.value);

  const customInputs = document.querySelectorAll(`input[data-group="${groupKey}"][data-field="customAttrs"]`);
  customInputs.forEach(inp => {
    const v = inp.value.trim();
    if (v) checkedAttrs.push(v);
  });

  const dataTypeEl = document.querySelector(`select[data-group="${groupKey}"][data-field="dataType"]`);
  const dataType = dataTypeEl?.value || null;

  return { tagPatterns, attributes: checkedAttrs, dataType };
}

async function pbBuild() {
  const groups = {};
  for (const gk of PB_GROUPS) {
    const gd = pbCollectGroupData(gk);
    if (gd.tagPatterns.length > 0) {
      groups[gk] = gd;
    }
  }

  const activeKeys = Object.keys(groups);
  if (activeKeys.length === 0) {
    const logEl = document.getElementById('pb-build-log');
    logEl.classList.remove('hidden');
    logEl.innerHTML = '<div class="ll err">⚠️ 태그명 패턴을 최소 1개 입력하세요.</div>';
    return;
  }

  setGlobal('busy', '포인트 빌드 중');
  try {
    const d = await api('POST', '/api/pointbuilder/build', groups);
    const logEl = document.getElementById('pb-build-log');
    logEl.classList.remove('hidden');
    logEl.innerHTML = `<div class="ll ${d.success ? 'ok' : 'err'}">${d.success ? '✅' : '❌'} ${esc(d.message)}</div>`;
    setGlobal(d.success ? 'ok' : 'err', d.success ? `${d.count}개 포인트` : '빌드 실패');
    if (d.success) await pbRefresh();
  } catch (e) {
    const logEl = document.getElementById('pb-build-log');
    logEl.classList.remove('hidden');
    logEl.innerHTML = `<div class="ll err">❌ ${esc(e.message)}</div>`;
    setGlobal('err', '오류');
  }
}

async function pbPreview() {
  const groups = {};
  for (const gk of PB_GROUPS) {
    const gd = pbCollectGroupData(gk);
    if (gd.tagPatterns.length > 0) {
      groups[gk] = gd;
    }
  }

  const activeKeys = Object.keys(groups);
  if (activeKeys.length === 0) {
    const logEl = document.getElementById('pb-build-log');
    logEl.classList.remove('hidden');
    logEl.innerHTML = '<div class="ll err">⚠️ 태그명 패턴을 최소 1개 입력하세요.</div>';
    return;
  }

  setGlobal('busy', '미리보기 조회 중');
  try {
    const d = await api('POST', '/api/pointbuilder/preview', groups);
    pbPreviewData = (d.items || []).map((item, idx) => ({ ...item, selected: true, idx }));
    document.getElementById('pb-preview-count').textContent = `(${d.count}개)`;
    document.getElementById('pb-preview').classList.remove('hidden');
    pbRenderPreview(pbPreviewData);
    setGlobal('ok', `미리보기: ${d.count}개 포인트`);
  } catch (e) {
    setGlobal('err', '미리보기 실패');
  }
}

function pbRenderPreview(data) {
  const el = document.getElementById('pb-preview-table');
  const filtered = pbGetFilteredPreview();
  const pts = filtered.length > 0 ? filtered : data;

  if (pts.length === 0) {
    el.innerHTML = '<div style="padding:20px;color:var(--t2)">조건에 맞는 포인트가 없습니다.</div>';
    pbUpdatePreviewCount();
    return;
  }

  el.innerHTML = `
    <table>
      <thead>
        <tr>
          <th><input type="checkbox" onchange="pbPreviewToggleAll(this.checked)" title="전체 선택/해제"/></th>
          <th>ID</th>
          <th>TagName</th>
          <th>NodeType</th>
          <th>DataType</th>
          <th>Group</th>
        </tr>
      </thead>
      <tbody>
        ${pts.map((p, i) => `
          <tr style="${!p.selected ? 'opacity:0.5' : ''}">
            <td><input type="checkbox" ${p.selected ? 'checked' : ''} onchange="pbPreviewToggleItem(${p.idx})"/></td>
            <td class="mut">${i + 1}</td>
            <td style="font-weight:600">${esc((p?.tagName)?.toUpperCase() || '')}</td>
            <td>${esc(p?.name || '')}</td>
            <td class="mut">${esc(p?.dataType || '')}</td>
            <td><span class="group-badge">${esc(p?.group || '')}</span></td>
          </tr>
        `).join('')}
      </tbody>
    </table>
  `;
  pbUpdatePreviewCount();
}

function pbPreviewToggleItem(idx) {
  pbPreviewData[idx].selected = !pbPreviewData[idx].selected;
  pbRenderPreview(pbPreviewData);
}

function pbPreviewToggleAll(checked) {
  pbPreviewData.forEach((item) => {
    const searchVal = document.getElementById('pb-preview-search')?.value?.toLowerCase();
    if (searchVal) {
      const filtered = pbGetFilteredPreview();
      if (filtered.includes(item)) {
        item.selected = checked;
      }
    } else {
      item.selected = checked;
    }
  });
  pbRenderPreview(pbPreviewData);
}

function pbPreviewSelectAll() {
  pbPreviewData.forEach(p => p.selected = true);
  pbRenderPreview(pbPreviewData);
}

function pbPreviewDeselectAll() {
  pbPreviewData.forEach(p => p.selected = false);
  pbRenderPreview(pbPreviewData);
}

function pbPreviewInvert() {
  pbPreviewData.forEach(p => p.selected = !p.selected);
  pbRenderPreview(pbPreviewData);
}

function pbGetFilteredPreview() {
  const searchVal = document.getElementById('pb-preview-search')?.value?.toLowerCase();
  if (!searchVal) return [];
  return pbPreviewData.filter(p =>
    (p.tagName || '').toLowerCase().includes(searchVal) ||
    (p.nodeId || '').toLowerCase().includes(searchVal) ||
    (p.name || '').toLowerCase().includes(searchVal)
  );
}

function pbPreviewFilter() {
  pbRenderPreview(pbPreviewData);
}

function pbUpdatePreviewCount() {
  const selected = pbPreviewData.filter(p => p.selected).length;
  const total = pbPreviewData.length;
  document.getElementById('pb-preview-selected').textContent = `선택: ${selected}/${total}`;
}

function pbCancelPreview() {
  document.getElementById('pb-preview').classList.add('hidden');
  pbPreviewData = [];
}

async function pbApplySelected() {
  const selected = pbPreviewData.filter(p => p.selected).map(p => p.nodeId);
  if (selected.length === 0) {
    setGlobal('err', '적용할 포인트를 선택하세요.');
    return;
  }

  setGlobal('busy', `${selected.length}개 포인트 적용 중`);
  try {
    const d = await api('POST', '/api/pointbuilder/apply', { selectedNodeIds: selected });
    setGlobal(d.success ? 'ok' : 'err', d.success ? `${d.count}개 포인트 적용 완료` : '적용 실패');
    if (d.success) {
      pbCancelPreview();
      await pbRefresh();
    }
  } catch (e) {
    setGlobal('err', '적용 오류');
  }
}

async function pbAppendSelected() {
  const selected = pbPreviewData.filter(p => p.selected).map(p => p.nodeId);
  if (selected.length === 0) {
    setGlobal('err', '추가할 포인트를 선택하세요.');
    return;
  }

  setGlobal('busy', `${selected.length}개 포인트 추가 중`);
  try {
    const d = await api('POST', '/api/pointbuilder/append', { selectedNodeIds: selected });
    setGlobal(d.success ? 'ok' : 'err', d.success ? `${d.count}개 포인트 추가 완료` : '추가 실패');
    if (d.success) {
      pbCancelPreview();
      await pbRefresh();
    }
  } catch (e) {
    setGlobal('err', '추가 오류');
  }
}

async function pbRefresh() {
  try {
    const d = await api('GET', '/api/pointbuilder/points');
    document.getElementById('pb-count').textContent = `(${d.total}개)`;
    pbRender(d.items || []);
    rtStatus();
  } catch (e) { console.error('pbRefresh:', e); }
}

function pbRender(points) {
  const tbl = document.getElementById('pb-table');
  const pts = Array.isArray(points) ? points : [];
  if (pts.length === 0) {
    tbl.innerHTML = '<div style="padding:20px;color:var(--t2)">포인트가 없습니다. 위에서 테이블을 작성하세요.</div>';
    return;
  }
  tbl.innerHTML = `
    <table>
      <thead>
        <tr>
          <th>ID</th><th>TagName</th>
          <th>LiveValue</th><th>Timestamp</th><th style="text-align:center">이력 / 삭제</th>
        </tr>
      </thead>
      <tbody>
        ${pts.map(p => `
          <tr>
            <td class="mut">${esc(p?.id || '')}</td>
            <td style="font-weight:600">${esc((p?.tagName)?.toUpperCase() || '')}</td>
            <td class="val">${p?.liveValue != null ? esc(String(fmtVal(parseEnumPv(p.liveValue)))) : '<span style="color:var(--t3)">—</span>'}</td>
            <td class="mut" style="font-size:11px">${p?.liveValue != null ? fmtTs(p.timestamp) : '—'}</td>
            <td style="white-space:nowrap;text-align:center">
              <label style="font-size:11px;color:var(--t2);margin-right:8px;cursor:pointer" title="체크 시 이 태그의 이력(history_table)도 함께 영구 삭제 (복구 불가)">
                <input type="checkbox" id="pb-hist-${p.id}" style="vertical-align:middle"> 이력
              </label>
              <button class="btn-sm btn-b" style="color:var(--red,#e55)" onclick="pbDelete(${p.id}, '${esc((p?.tagName)||'')}')">✕</button>
            </td>
          </tr>
        `).join('')}
      </tbody>
    </table>
  `;
}

async function pbAddManual() {
  const nodeId = document.getElementById('pb-manual-nid').value.trim();
  if (!nodeId) { alert('Node ID를 입력하세요.'); return; }
  try {
    const d = await api('POST', '/api/pointbuilder/add', { nodeId });
    log('pb-manual-log', [{ c: d.success ? 'ok' : 'err', t: d.success
      ? `✅ 추가됨: ${d.point?.tagName} (${d.point?.nodeId})`
      : '❌ 추가 실패' }]);
    if (d.success) {
      document.getElementById('pb-manual-nid').value = '';
      await pbRefresh();
    }
  } catch (e) {
    log('pb-manual-log', [{ c: 'err', t: '❌ ' + e.message }]);
  }
}

async function pbDelete(id, tagName) {
  const label = tagName ? `${tagName.toUpperCase()} (#${id})` : `#${id}`;
  // 행의 "이력" 체크박스 = 이력 함께 삭제 opt-in (기본 미체크 = 이력 보존)
  const purgeHistory = !!document.getElementById('pb-hist-' + id)?.checked;

  const msg = purgeHistory
    ? `포인트 ${label} 삭제 + 이력 영구 삭제\n\n· realtime 포인트 삭제\n· 잔여 포인트 없으면 메타데이터(area/sub_area) 정리\n· ⚠️ 이력(history_table)까지 영구 삭제 — 복구 불가\n\n계속하시겠습니까?`
    : `포인트 ${label}를 삭제하시겠습니까?\n\n· realtime 포인트 삭제\n· 잔여 포인트 없으면 메타데이터(area/sub_area) 정리\n· 이력(history)은 보존 ('이력' 체크 시 함께 삭제)`;
  if (!confirm(msg)) return;

  try {
    const d = await api('DELETE', `/api/pointbuilder/${id}?purgeHistory=${purgeHistory}`);
    if (d.success) {
      const parts = [];
      if (d.metadataPurged) parts.push('메타데이터 정리됨');
      if (d.historyRowsDeleted > 0) parts.push(`이력 ${d.historyRowsDeleted.toLocaleString()}행 삭제`);
      else if (purgeHistory) parts.push('이력 없음');
      else parts.push('이력 보존');
      alert(`삭제 완료: ${label}\n(${parts.join(' · ')})`);
      await pbRefresh();
    } else {
      alert('삭제 실패: ' + d.message);
    }
  } catch (e) { alert('삭제 오류: ' + e.message); }
}

/* ── 실시간 구독 제어 ────────────────────────────────────────── */
async function rtStart() {
  const body = {
    serverHostName: document.getElementById('pb-rt-ip').value.trim(),
    port:           parseInt(document.getElementById('pb-rt-port').value) || 4840,
    clientHostName: document.getElementById('pb-rt-client').value.trim(),
    userName:       document.getElementById('pb-rt-user').value.trim(),
    password:       document.getElementById('pb-rt-pw').value
  };
  setGlobal('busy', '구독 시작 중');
  try {
    const d = await api('POST', '/api/realtime/start', body);
    log('pb-rt-status', [{ c: 'ok', t: '▶ ' + d.message }]);
    setGlobal('ok', '구독 중');
  } catch (e) {
    log('pb-rt-status', [{ c: 'err', t: '❌ ' + e.message }]);
    setGlobal('err', '오류');
  }
}

async function rtStop() {
  setGlobal('busy', '구독 중지 중');
  try {
    const d = await api('POST', '/api/realtime/stop');
    log('pb-rt-status', [{ c: 'inf', t: '■ ' + d.message }]);
    setGlobal('ok', '중지됨');
  } catch (e) { log('pb-rt-status', [{ c: 'err', t: '❌ ' + e.message }]); }
}

async function rtStatus() {
  try {
    const d = await api('GET', '/api/realtime/status');
    const logEl = document.getElementById('pb-rt-status');
    logEl.classList.remove('hidden');
    logEl.innerHTML = `<div class="ll ${d.running ? 'ok' : 'inf'}">
      ${d.running ? '▶' : '■'} ${esc(d.message)} (구독 ${d.subscribedCount}개)
    </div>`;
  } catch (e) { /* 무시 */ }
}

/* ── 메타데이터 관리 ─────────────────────────────────────────── */

async function metaReload() {
  const body = {
    serverHostName: document.getElementById('pb-rt-ip').value.trim(),
    port: parseInt(document.getElementById('pb-rt-port').value) || 4840,
    clientHostName: document.getElementById('pb-rt-client').value.trim(),
    userName: document.getElementById('pb-rt-user').value.trim(),
    password: document.getElementById('pb-rt-pw').value
  };
  const logEl = document.getElementById('meta-log');
  logEl.classList.remove('hidden');
  logEl.innerHTML = '<div class="ll inf">⏳ 메타데이터 갱신 중...</div>';
  try {
    const d = await api('POST', '/api/tags/metadata/reload', body);
    logEl.innerHTML = `<div class="ll ${d.success ? 'ok' : 'err'}">${d.success ? '✅' : '❌'} ${esc(d.message)}</div>`;
  } catch (e) {
    logEl.innerHTML = `<div class="ll err">❌ ${esc(e.message)}</div>`;
  }
}

async function metaView() {
  const viewEl = document.getElementById('meta-view');
  viewEl.classList.remove('hidden');
  viewEl.innerHTML = '<div class="ll inf">⏳ 조회 중...</div>';
  try {
    const d = await api('GET', '/api/tags/metadata');
    const items = d.items || [];
    if (items.length === 0) {
      viewEl.innerHTML = '<div class="ll inf">메타데이터가 없습니다.</div>';
      return;
    }
    viewEl.innerHTML = `
      <table style="width:100%;font-size:12px">
        <thead><tr><th>BaseTag</th><th>Attribute</th><th>Value</th><th>LoadedAt</th></tr></thead>
        <tbody>
          ${items.map(m => `
            <tr>
              <td style="font-weight:600">${esc(m.baseTag)}</td>
              <td>${esc(m.attribute)}</td>
              <td>${esc(m.value || '-')}</td>
              <td class="mut">${m.loadedAt ? new Date(m.loadedAt).toLocaleString('ko-KR') : '-'}</td>
            </tr>
          `).join('')}
        </tbody>
      </table>
    `;
  } catch (e) {
    viewEl.innerHTML = `<div class="ll err">❌ ${esc(e.message)}</div>`;
  }
}

/* ── Sub-Area (세부 Area) 관리 ───────────────────────────────── */
// area별 선택 가능한 sub_area 옵션 (마지막은 공용 = 두 코드)
const SUBAREA_OPTIONS = {
  P6:  ['P6-1', 'P6-2', 'P6-1,P6-2'],
  P9:  ['P9-1', 'P9-2', 'P9-1,P9-2'],
  P10: ['P10-1', 'P10-2', 'P10-1,P10-2'],
  P1:  ['P1-1', 'P1-2', 'P1-1,P1-2'],
  P2:  ['P2-1', 'P2-2', 'P2-1,P2-2'],
};

function subAreaLabel(code) {
  if (!code) return '(미분류)';
  return code.includes(',') ? `${code} (공용)` : code;
}

async function subAreaLoad() {
  const area = document.getElementById('subarea-area-select').value;
  const viewEl = document.getElementById('subarea-view');
  viewEl.classList.remove('hidden');
  viewEl.innerHTML = '<div class="ll inf">⏳ 조회 중...</div>';
  try {
    const d = await api('GET', `/api/tags/sub-area?area=${encodeURIComponent(area)}&page=1&pageSize=500`);
    const tags = d.tags || [];
    if (tags.length === 0) {
      viewEl.innerHTML = '<div class="ll inf">태그가 없습니다.</div>';
      return;
    }
    const opts = SUBAREA_OPTIONS[area] || [];
    viewEl.innerHTML = `
      <div class="mut" style="margin-bottom:6px">총 ${d.total}개 · 미분류는 (미분류)로 두면 NULL 유지</div>
      <table style="width:100%;font-size:12px">
        <thead><tr><th>BaseTag</th><th>Description</th><th>Sub-Area</th></tr></thead>
        <tbody>
          ${tags.map(t => {
            const cur = t.subArea || '';
            const optionHtml = ['', ...opts].map(o =>
              `<option value="${esc(o)}" ${o === cur ? 'selected' : ''}>${esc(subAreaLabel(o))}</option>`
            ).join('');
            // 현재값이 옵션에 없으면(수동 지정 등) 추가
            const extra = (cur && !opts.includes(cur))
              ? `<option value="${esc(cur)}" selected>${esc(subAreaLabel(cur))}</option>` : '';
            return `
              <tr>
                <td style="font-weight:600">${esc(t.baseTag)}</td>
                <td class="mut">${esc(t.description || '-')}</td>
                <td>
                  <select class="inp" style="font-size:12px;padding:2px 6px"
                          onchange="subAreaUpdate('${esc(t.baseTag)}', this.value)">
                    ${optionHtml}${extra}
                  </select>
                </td>
              </tr>`;
          }).join('')}
        </tbody>
      </table>`;
  } catch (e) {
    viewEl.innerHTML = `<div class="ll err">❌ ${esc(e.message)}</div>`;
  }
}

async function subAreaUpdate(baseTag, subArea) {
  const logEl = document.getElementById('subarea-log');
  logEl.classList.remove('hidden');
  try {
    const d = await api('PUT', '/api/tags/sub-area', { baseTag, subArea: subArea || null });
    logEl.innerHTML = `<div class="ll ${d.success ? 'ok' : 'err'}">${d.success ? '✅' : '❌'} ${esc(baseTag)} → ${esc(subAreaLabel(subArea))}</div>`;
  } catch (e) {
    logEl.innerHTML = `<div class="ll err">❌ ${esc(e.message)}</div>`;
  }
}

async function subAreaSeed(dryRun) {
  const msg = dryRun
    ? 'DryRun으로 분류 결과만 미리 봅니다(저장 안 함). 계속할까요?'
    : '실제 seed를 실행합니다(기존 sub_area는 보존, 신규만 추가). 계속할까요?';
  if (!confirm(msg)) return;
  const logEl = document.getElementById('subarea-log');
  logEl.classList.remove('hidden');
  logEl.innerHTML = `<div class="ll inf">⏳ Seed ${dryRun ? 'DryRun' : '실행'} 중...</div>`;
  try {
    const d = await api('POST', '/api/tags/sub-area/seed', { dryRun });
    const by = d.bySubArea || {};
    const byStr = Object.keys(by).sort().map(k => `${k}=${by[k]}`).join(', ');
    logEl.innerHTML = `
      <div class="ll ${d.success ? 'ok' : 'err'}">
        ${d.success ? '✅' : '❌'} ${dryRun ? '[DryRun] ' : ''}단일 ${d.assigned} · 공용 ${d.shared} · 미분류 ${d.unmatched}
      </div>
      <div class="ll inf">분류별: ${esc(byStr || '-')}</div>`;
    subAreaLoad();
  } catch (e) {
    logEl.innerHTML = `<div class="ll err">❌ ${esc(e.message)}</div>`;
  }
}
