<!DOCTYPE html>
<html lang="ko">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>HUTAMS — LTE-R 기반 지능형 관제 대시보드</title>
  <!-- [Chapter 9.0] PWA Manifest 연동 -->
  <link rel="manifest" href="/static/manifest.json">
  <!-- Bootstrap 5 CSS -->
  <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet">
  <!-- Bootstrap Icons -->
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.css">
  <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">

  <style>
    :root {
      --bg-base: #0f172a;
      --bg-panel: #1e293b;
      --border-color: #334155;
      --accent: #38bdf8;
      --text-main: #f8fafc;
      --text-muted: #94a3b8;
    }

    /* Live Mode Radar Pulse Effect */
    @keyframes radar-pulse {
      0% {
        box-shadow: 0 0 0 0 rgba(239, 68, 68, 0.4);
      }

      70% {
        box-shadow: 0 0 0 20px rgba(239, 68, 68, 0);
      }

      100% {
        box-shadow: 0 0 0 0 rgba(239, 68, 68, 0);
      }
    }

    .live-pulse-container {
      display: flex;
      flex-direction: column;
      align-items: center;
      justify-content: center;
      height: 200px;
      background-color: #1e293b;
      border: 1px dashed #ef4444;
      border-radius: 12px;
    }

    .live-pulse-icon {
      font-size: 2.5rem;
      color: #ef4444;
      border-radius: 50%;
      animation: radar-pulse 2s infinite;
      margin-bottom: 1rem;
    }

    body {
      background-color: var(--bg-base);
      color: var(--text-main);
      font-family: 'Inter', sans-serif;
      height: 100vh;
      overflow: hidden;
    }

    /* Top Navigation */
    .topbar {
      background: linear-gradient(90deg, #0d1b2e 0%, #1e293b 100%);
      height: 60px;
      border-bottom: 1px solid var(--border-color);
      display: flex;
      align-items: center;
      padding: 0 1.5rem;
    }

    .brand-logo {
      color: var(--accent);
      font-weight: 700;
      font-size: 1.2rem;
      letter-spacing: 1px;
    }

    .brand-sub {
      color: var(--text-muted);
      font-weight: 400;
      font-size: 0.85rem;
      margin-left: 0.5rem;
    }

    /* Layout */
    .app-container {
      display: flex;
      height: calc(100vh - 60px);
      flex-direction: row;
    }

    .history-panel {
      flex: 0 0 32%;
      /* Initial flex-basis */
      min-width: 20%;
      max-width: 60%;
      background-color: var(--bg-panel);
      display: flex;
      flex-direction: column;
    }

    #resizer {
      width: 6px;
      background-color: var(--border-color);
      cursor: col-resize;
      transition: background-color 0.2s;
      flex-shrink: 0;
      z-index: 10;
    }

    #resizer:hover,
    #resizer.active {
      background-color: var(--accent);
    }

    .realtime-panel {
      flex: 1 1 0;
      /* Takes remaining space */
      display: flex;
      flex-direction: column;
      background-color: var(--bg-base);
    }

    /* Disable text selection during drag */
    .no-select {
      user-select: none;
      -webkit-user-select: none;
    }



    .panel-header {
      padding: 1rem 1.5rem;
      border-bottom: 1px solid var(--border-color);
      background-color: rgba(0, 0, 0, 0.1);
      font-weight: 600;
      flex-shrink: 0;
    }

    .scroll-area {
      flex: 1;
      overflow-y: auto;
      padding: 1.5rem;
    }

    /* History List Cards */
    .h-card {
      background-color: #0f172a;
      border: 1px solid var(--border-color);
      border-radius: 8px;
      padding: 1rem;
      margin-bottom: 0.75rem;
      cursor: pointer;
      transition: all 0.2s;
    }

    .h-card:hover {
      border-color: var(--accent);
      background-color: #1a2235;
      transform: translateY(-1px);
    }

    .h-card.urgent {
      border-left: 4px solid #ef4444;
    }

    .h-title-row {
      display: flex;
      justify-content: space-between;
      align-items: flex-start;
      margin-bottom: 0.4rem;
    }

    .h-title {
      font-weight: 600;
      font-size: 0.95rem;
      line-height: 1.3;
    }

    .h-time {
      font-size: 0.75rem;
      color: var(--text-muted);
      white-space: nowrap;
      margin-left: 0.5rem;
    }

    .h-summary {
      font-size: 0.85rem;
      color: #cbd5e1;
      display: -webkit-box;
      -webkit-line-clamp: 2;
      line-clamp: 2;
      /* 표준 속성 (lint 경고 해결) */
      -webkit-box-orient: vertical;
      overflow: hidden;
    }

    /* Badges */
    .badge-긴급 {
      background-color: #ef4444;
      color: white;
    }

    .badge-일반 {
      background-color: #334155;
      color: var(--accent);
    }

    .badge-관제 {
      background-color: #0ea5e9;
      color: white;
    }

    .badge-열차 {
      background-color: #10b981;
      color: white;
    }

    .badge-미상 {
      background-color: #64748b;
      color: white;
    }

    .badge {
      font-weight: 500;
      font-size: 0.75rem;
      margin-right: 0.4rem;
      padding: 0.35em 0.65em;
    }

    /* Forms & Inputs */
    .form-control-dark,
    .form-select-dark {
      background-color: #0f172a;
      border: 1px solid var(--border-color);
      color: var(--text-main);
    }

    .form-control-dark:focus,
    .form-select-dark:focus {
      background-color: #0f172a;
      color: var(--text-main);
      border-color: var(--accent);
      box-shadow: 0 0 0 0.2rem rgba(56, 189, 248, 0.25);
    }

    .upload-zone {
      border: 2px dashed var(--border-color);
      border-radius: 12px;
      padding: 2.5rem;
      text-align: center;
      cursor: pointer;
      background-color: #1e293b;
      transition: all 0.2s ease;
    }

    .upload-zone:hover {
      border-color: var(--accent);
      background-color: #24344d;
    }

    /* Segment Items */
    .segment-item {
      background-color: rgba(255, 255, 255, 0.03);
      border: 1px solid var(--border-color);
      border-radius: 8px;
      padding: 0.8rem 1rem;
      margin-bottom: 0.5rem;
      cursor: pointer;
      transition: background-color 0.2s;
    }

    .segment-item:hover {
      background-color: rgba(56, 189, 248, 0.08);
      border-color: rgba(56, 189, 248, 0.4);
    }

    .seg-time {
      font-family: monospace;
      color: var(--accent);
      font-size: 0.8rem;
      min-width: 95px;
      display: inline-block;
    }

    /* Audio Player Custom */
    audio {
      width: 100%;
      height: 40px;
      border-radius: 8px;
      filter: invert(1) hue-rotate(180deg) contrast(1.2);
    }

    /* Modal Overrides */
    .modal-content {
      background-color: var(--bg-panel);
      border: 1px solid var(--border-color);
    }

    .modal-header,
    .modal-footer {
      border-color: var(--border-color);
    }

    .btn-close {
      filter: invert(1) grayscale(100%) brightness(200%);
    }

    /* Spinner */
    .custom-spinner {
      width: 3rem;
      height: 3rem;
      border: 4px solid var(--border-color);
      border-top-color: var(--accent);
      border-radius: 50%;
      animation: s-spin 1s linear infinite;
    }

    @keyframes s-spin {
      to {
        transform: rotate(360deg);
      }
    }

    /* Scrollbar */
    ::-webkit-scrollbar {
      width: 6px;
    }

    ::-webkit-scrollbar-track {
      background: transparent;
    }

    ::-webkit-scrollbar-thumb {
      background: var(--border-color);
      border-radius: 10px;
    }

    ::-webkit-scrollbar-thumb:hover {
      background: #475569;
    }

    /* ── 실시간 스트림 누적 블록 ── */
    .rt-block {
      padding: 0.75rem 1rem;
      border-left: 3px solid var(--accent);
      background: rgba(30, 41, 59, 0.6);
      border-radius: 0 8px 8px 0;
      margin-bottom: 0.5rem;
    }

    .rt-block-header {
      font-size: 0.9rem;
    }

    /* ── Chapter 7.0: 긴급 카드 블링킹 및 확인(Acknowledge) 처리 ── */
    @keyframes urgency-blink {
      0% {
        border-color: #dc3545;
        box-shadow: 0 0 10px rgba(220, 53, 69, 0.8);
      }

      50% {
        border-color: transparent;
        box-shadow: none;
      }

      100% {
        border-color: #dc3545;
        box-shadow: 0 0 10px rgba(220, 53, 69, 0.8);
      }
    }

    @keyframes icon-blink {
      0% {
        opacity: 1;
        transform: scale(1);
      }

      50% {
        opacity: 0.3;
        transform: scale(1.2);
      }

      100% {
        opacity: 1;
        transform: scale(1);
      }
    }

    .card-urgent-blinking {
      animation: urgency-blink 1s infinite;
      border: 2px solid #dc3545 !important;
      cursor: pointer;
    }

    .card-urgent-blinking .urgency-icon {
      animation: icon-blink 1s infinite;
      display: inline-block;
    }

    .card-urgent-acknowledged {
      border: 2px solid #dc3545 !important;
    }

    /* ── Daily 채팅 버블 (카카오톡 스타일) ── */
    .chat-bubble {
      padding: 0.5rem 0.8rem;
      border-radius: 16px;
      font-size: 0.88rem;
      line-height: 1.5;
      max-width: 100%;
      word-break: break-word;
    }

    .chat-left {
      background: #334155;
      color: #f1f5f9;
      border-top-left-radius: 4px;
    }

    .chat-right {
      background: #0ea5e9;
      color: #fff;
      border-top-right-radius: 4px;
    }
  </style>
</head>

<body>

  <!-- Topbar -->
  <header class="topbar">
    <div class="brand-logo">HUTAMS<span class="brand-sub">LTE-R 무전 관제 플랫폼</span></div>
    <div class="ms-auto text-muted small d-flex align-items-center gap-3">
      <span><i class="bi bi-cpu"></i> LLM 0.8B Active</span>
      <span><i class="bi bi-database"></i> SQLite Sync</span>
    </div>
  </header>

  <div class="app-container">
    <!-- Left: History Panel (Dual Tab) -->
    <aside class="history-panel">
      <div class="panel-header d-flex justify-content-between align-items-center">
        <span><i class="bi bi-archive-fill me-2 text-info"></i>무전 이력</span>
        <span class="badge bg-secondary rounded-pill" id="history-count">0</span>
      </div>
      <!-- Dual Tab -->
      <div class="px-3 pt-2" style="border-bottom:1px solid var(--border-color);">
        <ul class="nav nav-pills nav-fill gap-1 pb-2" id="historyTab" role="tablist">
          <li class="nav-item" role="presentation">
            <button class="nav-link active py-1 small" id="tab-list" data-bs-toggle="pill" data-bs-target="#pane-list"
              type="button" role="tab">
              <i class="bi bi-list-ul me-1"></i>요약 리스트
            </button>
          </li>
          <li class="nav-item" role="presentation">
            <button class="nav-link py-1 small" id="tab-daily" data-bs-toggle="pill" data-bs-target="#pane-daily"
              type="button" role="tab">
              <i class="bi bi-chat-dots me-1"></i>일자별 기록
            </button>
          </li>
        </ul>
      </div>

      <div class="tab-content d-flex flex-column flex-grow-1 overflow-hidden">
        <!-- Tab 1: 요약 리스트 -->
        <div id="pane-list" class="tab-pane fade show active d-flex flex-column flex-grow-1 overflow-hidden"
          role="tabpanel">
          <div class="p-2 border-bottom" style="border-color:var(--border-color)!important;">
            <div class="input-group input-group-sm">
              <span class="input-group-text bg-transparent border-end-0 border-secondary text-muted">
                <i class="bi bi-search"></i></span>
              <input type="text" id="search-input" class="form-control form-control-dark border-start-0"
                placeholder="키워드 검색...">
            </div>
          </div>
          <div class="scroll-area flex-grow-1" id="history-list">
            <div class="text-center text-muted mt-5"><i class="bi bi-arrow-clockwise"></i></div>
          </div>
        </div>

        <!-- Tab 2: 일자별 기록 (채팅뷰) -->
        <div id="pane-daily" class="tab-pane fade d-flex flex-column flex-grow-1 overflow-hidden" role="tabpanel">
          <div class="p-2 border-bottom" style="border-color:var(--border-color)!important;">
            <input type="date" id="daily-date" class="form-control form-control-sm form-control-dark" value=""
              title="조회 날짜">
          </div>
          <div class="scroll-area flex-grow-1" id="daily-chat-area">
            <div class="text-center text-muted mt-5 small">날짜를 선택하면 전체 통신 기록이 나타납니다.</div>
          </div>
        </div>
      </div>
    </aside>

    <!-- Resizer (Splitter) -->
    <div id="resizer"></div>

    <!-- Right: Realtime & Actions Panel -->
    <main class="realtime-panel">
      <div class="panel-header d-flex justify-content-between align-items-center">
        <div><i class="bi bi-broadcast me-2 text-info"></i>실시간 무전 STT 변환</div>
        <div class="form-check form-switch fw-normal small mb-0 d-flex align-items-center gap-2">
          <label class="form-check-label text-muted" for="modeSwitch">Test Mode</label>
          <input class="form-check-input mt-0" type="checkbox" role="switch" id="modeSwitch">
          <label class="form-check-label text-danger" for="modeSwitch" id="liveLabel" style="opacity: 0.5;">Live
            Mode</label>
        </div>
      </div>
      <div class="scroll-area">

        <!-- Live Mode Waiting Panel -->
        <div id="live-waiting-panel" class="mb-4" style="display:none;">
          <div class="live-pulse-container">
            <i class="bi bi-mic-fill live-pulse-icon"></i>
            <h5 class="text-danger fw-bold">🔴 실시간 무전 수신 대기 중...</h5>
            <p class="text-muted small mb-0">웹소켓(WebSocket)이 연결되어 오디오 스트림을 감청합니다.</p>
          </div>
        </div>

        <!-- Upload Form -->
        <form id="stt-form" class="mb-4">
          <div class="upload-zone mb-3" id="upload-zone" onclick="document.getElementById('audio-file').click()">
            <i class="bi bi-cloud-upload display-5 text-info mb-3"></i>
            <h5>오디오 파일 업로드</h5>
            <p class="text-muted small mb-0">클릭하거나 파일을 이 영역으로 드래그 (m4a, wav 등)</p>
            <input type="file" id="audio-file" style="display:none" accept="audio/*,video/*">
          </div>
          <div id="file-indicator" class="text-info small mb-3 text-center" style="display:none;"></div>

          <div class="row g-2 mb-3">
            <div class="col-md-4">
              <label class="form-label small text-muted">언어</label>
              <select id="lang-select" class="form-select form-select-dark">
                <option value="ko" selected>한국어</option>
                <option value="en">English</option>
              </select>
            </div>
            <div class="col-md-8">
              <label class="form-label small text-muted">녹음 시작 시간 (옵션)</label>
              <input type="datetime-local" step="1" id="base-dt" class="form-control form-control-dark">
            </div>
          </div>
          <button type="submit" id="submit-btn" class="btn btn-info w-100 fw-bold text-white shadow-sm">
            <i class="bi bi-play-fill"></i> STT 변환 시작
          </button>
        </form>

        <!-- Loading State -->
        <div id="loading-area" class="text-center py-5" style="display:none;">
          <div class="custom-spinner mx-auto mb-3"></div>
          <h6 class="text-info">STT & LLM 연산 중입니다...</h6>
          <p class="text-muted small">AI가 화자 분리 및 요약을 수행하고 있습니다.</p>
        </div>

        <!-- Realtime Stream Area & Context Panel -->
        <div id="rt-container" class="row g-3 flex-grow-1 h-100" style="display:none;">
          <div class="col-lg-8 d-flex flex-column h-100">
            <!-- 메신저형 누적 뷰 -->
            <div id="rt-stream" class="flex-grow-1 overflow-auto pe-2"></div>
          </div>
          <div class="col-lg-4 d-flex flex-column h-100 border-start border-secondary ps-3">
            <h6 class="text-info fw-bold mb-3"><i class="bi bi-lightbulb-fill text-warning me-2"></i>관련 지식 (Context)
            </h6>
            <div id="context-panel" class="flex-grow-1 overflow-auto pb-3">
              <div class="text-muted small text-center mt-5"><i class="bi bi-inbox mb-2 d-block fs-3"></i>지식 대기 중...
              </div>
            </div>
          </div>
        </div>

      </div>
    </main>
  </div>

  <!-- Detail Modal -->
  <div class="modal fade" id="detailModal" tabindex="-1" aria-hidden="true">
    <div class="modal-dialog modal-lg modal-dialog-centered modal-dialog-scrollable">
      <div class="modal-content">
        <div class="modal-header">
          <div>
            <span id="modal-urgency" class="badge me-2"></span>
            <h5 class="modal-title d-inline-block fw-bold" id="modal-title">무전 상세 정보</h5>
          </div>
          <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
        </div>
        <div class="modal-body">
          <div class="text-muted small mb-3 align-items-center d-flex gap-3">
            <span id="modal-time"><i class="bi bi-clock me-1"></i> -</span>
            <span id="modal-filename"><i class="bi bi-file-earmark-music me-1"></i> -</span>
          </div>

          <div class="p-3 rounded mb-4"
            style="background-color: rgba(0,0,0,0.2); border: 1px solid var(--border-color);">
            <p class="mb-2 text-light" id="modal-summary"></p>
            <div id="modal-keywords"></div>
          </div>

          <audio id="modal-audio" controls class="mb-4"></audio>

          <h6 class="text-muted mb-3"><i class="bi bi-chat-text me-2"></i>상세 통신 기록</h6>
          <div id="modal-segments"></div>
        </div>
        <div class="modal-footer justify-content-between">
          <div class="small text-muted" id="modal-bench"></div>
          <div>
            <button type="button" class="btn btn-outline-info me-2" id="export-btn"><i
                class="bi bi-download me-1"></i>TXT 내보내기</button>
            <button type="button" class="btn btn-secondary" data-bs-dismiss="modal">닫기</button>
          </div>
        </div>
      </div>
    </div>
  </div>

  <!-- Bootstrap JS -->
  <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script>

  <!-- Application Logic -->
  <script>
    // 전역 상태
    let allRecords = [];
    let currentDetailRecord = null;
    let localFileUrl = null;
    let rtAudioUrl = null;
    const detailModal = new bootstrap.Modal(document.getElementById('detailModal'));

    // 유틸: 초 포맷팅
    const fmt = sec => {
      if (sec == null) return "00:00";
      const m = String(Math.floor(sec / 60)).padStart(2, '0');
      const s = String(Math.floor(sec % 60)).padStart(2, '0');
      return `${m}:${s}`;
    };

    // ─── 1. 좌측 이력 패널 로드 ───
    async function loadHistory(keyword = '') {
      const url = `/api/v1/records?limit=50${keyword ? '&keyword=' + encodeURIComponent(keyword) : ''}`;
      const listEl = document.getElementById('history-list');
      try {
        const res = await fetch(url);
        const data = await res.json();
        allRecords = data.records || [];

        document.getElementById('history-count').textContent = data.total;

        if (allRecords.length === 0) {
          listEl.innerHTML = '<div class="text-center text-muted mt-5"><i class="bi bi-inbox display-4 d-block mb-3"></i>기록이 없습니다.</div>';
          return;
        }

        listEl.innerHTML = '';
        allRecords.forEach(r => {
          const isUrgent = r.urgency === '긴급';
          const urgencyClass = `badge-${r.urgency || '일반'}`;
          const dt = r.created_at ? new Date(r.created_at).toLocaleString() : '-';

          // 카드 마크업
          const card = document.createElement('div');
          card.className = `h-card ${isUrgent ? 'urgent' : ''}`;
          card.onclick = () => openModal(r.id);
          card.innerHTML = `
            <div class="h-title-row">
              <div class="h-title text-truncate me-2">${r.title || r.filename}</div>
              <div class="d-flex align-items-center">
                <span class="badge ${urgencyClass} rounded-pill">${r.urgency || '일반'}</span>
              </div>
            </div>
            <div class="h-summary mb-2" title="${r.summary}">${r.summary || r.full_text || '요약 정보 없음'}</div>
            <div class="h-time"><i class="bi bi-clock me-1"></i>${dt}</div>
          `;
          listEl.appendChild(card);
        });

      } catch (err) {
        listEl.innerHTML = `<div class="text-danger mt-4 text-center">불러오기 실패: ${err.message}</div>`;
      }
    }

    // 검색 디바운스 처리
    let searchTimer;
    document.getElementById('search-input').addEventListener('input', e => {
      clearTimeout(searchTimer);
      searchTimer = setTimeout(() => loadHistory(e.target.value.trim()), 400);
    });

    // ─── 2. 모달 열기 및 데이터 주입 ───
    function openModal(id) {
      const record = allRecords.find(r => r.id === id);
      if (!record) return;
      currentDetailRecord = record;

      // 헤더
      const uBadge = document.getElementById('modal-urgency');
      uBadge.className = `badge badge-${record.urgency || '일반'} rounded-pill`;
      uBadge.textContent = record.urgency || '일반';
      document.getElementById('modal-title').textContent = record.title || '상세 정보';

      // 메타 정보
      document.getElementById('modal-time').innerHTML = `<i class="bi bi-clock me-1"></i> ${new Date(record.created_at).toLocaleString()}`;
      document.getElementById('modal-filename').innerHTML = `<i class="bi bi-file-earmark-music me-1"></i> ${record.filename}`;
      document.getElementById('modal-summary').textContent = record.summary || record.full_text;

      const kwArea = document.getElementById('modal-keywords');
      kwArea.innerHTML = '';
      if (record.keywords) {
        record.keywords.split(',').forEach(k => {
          kwArea.innerHTML += `<span class="badge bg-secondary me-1 fw-normal">${k.trim()}</span>`;
        });
      }

      // 오디오 소스 정적 서빙 경로 연동
      const audio = document.getElementById('modal-audio');
      if (record.filename) {
        audio.src = `/static/audio/${record.filename}`;
        audio.style.display = 'block';
      } else {
        audio.src = '';
        audio.style.display = 'none';
      }

      // 벤치마크
      document.getElementById('modal-bench').innerHTML = `처리시간: <b>${record.processing_time_sec}s</b> | RAM: <b>${record.peak_memory_mb}MB</b>`;

      // 세그먼트 (화자 뱃지 포함)
      const segArea = document.getElementById('modal-segments');
      segArea.innerHTML = '';
      if (record.segments && record.segments.length > 0) {
        record.segments.forEach(seg => {
          const spkrBadge = seg.speaker ? `<span class="badge badge-${seg.speaker}">${seg.speaker}</span>` : '';
          const div = document.createElement('div');
          div.className = 'segment-item d-flex align-items-start';
          div.innerHTML = `
            <div class="seg-time">${fmt(seg.start_sec)}</div>
            <div class="flex-grow-1">
              ${spkrBadge} <span class="text-light">${seg.text}</span>
            </div>
          `;
          // 재생 기능 (로컬 임시 URL이 살아있을때만 의미있음)
          div.onclick = () => { if (audio.src) { audio.currentTime = seg.start_sec; audio.play(); } };
          segArea.appendChild(div);
        });
      } else {
        segArea.innerHTML = `<div class="p-3 text-muted">${record.full_text}</div>`;
      }

      detailModal.show();
    }

    // TXT 내보내기 
    document.getElementById('export-btn').onclick = () => {
      if (!currentDetailRecord) return;
      const r = currentDetailRecord;
      let content = `제목: ${r.title}\n일시: ${r.created_at}\n요약: ${r.summary}\n긴급도: ${r.urgency}\n\n[통신 기록]\n`;
      if (r.segments && r.segments.length > 0) {
        r.segments.forEach(s => { content += `[${fmt(s.start_sec)}] ${s.speaker ? '<' + s.speaker + '> ' : ''}${s.text}\n`; });
      } else {
        content += r.full_text;
      }
      const blob = new Blob([content], { type: 'text/plain;charset=utf-8' });
      const a = document.createElement('a');
      a.href = URL.createObjectURL(blob);
      a.download = `HUTAMS_Record_${r.id}.txt`;
      a.click();
    };

    // ─── 3. 실시간 업로드 및 타자기 효과 ───
    const fileInput = document.getElementById('audio-file');
    const uploadZone = document.getElementById('upload-zone');
    const fileInd = document.getElementById('file-indicator');

    uploadZone.ondragover = e => { e.preventDefault(); uploadZone.style.borderColor = "var(--accent)"; };
    uploadZone.ondragleave = e => { uploadZone.style.borderColor = "var(--border-color)"; };
    uploadZone.ondrop = e => {
      e.preventDefault();
      uploadZone.style.borderColor = "var(--border-color)";
      if (e.dataTransfer.files.length) { fileInput.files = e.dataTransfer.files; handleFileSelect(); }
    };
    fileInput.onchange = handleFileSelect;

    function handleFileSelect() {
      if (fileInput.files[0]) {
        fileInd.innerHTML = `<i class="bi bi-file-earmark-music me-1"></i> 선택됨: ${fileInput.files[0].name}`;
        fileInd.style.display = 'block';
        if (localFileUrl) URL.revokeObjectURL(localFileUrl);
        localFileUrl = URL.createObjectURL(fileInput.files[0]);
      }
    }

    document.getElementById('stt-form').onsubmit = async (e) => {
      e.preventDefault();
      if (!fileInput.files[0]) return alert('파일을 먼저 선택해주세요.');

      // Toggle UI
      document.getElementById('submit-btn').disabled = true;
      document.getElementById('realtime-result').style.display = 'none';
      document.getElementById('loading-area').style.display = 'block';

      const fd = new FormData();
      fd.append('audio', fileInput.files[0]);
      fd.append('language', document.getElementById('lang-select').value);
      const bdt = document.getElementById('base-dt').value;
      if (bdt) fd.append('base_datetime', bdt);

      try {
        const res = await fetch('/api/v1/transcribe', { method: 'POST', body: fd });
        if (!res.ok) throw new Error((await res.json()).detail || `HTTP ${res.status}`);
        const data = await res.json();

        renderRealtimeStream(data, true);  // true = typewriter
        loadHistory();
      } catch (err) {
        alert(`변환 실패: ${err.message}`);
      } finally {
        document.getElementById('loading-area').style.display = 'none';
        document.getElementById('submit-btn').disabled = false;
        fileInput.value = '';
        fileInd.style.display = 'none';
      }
    };

    // ─── 3. 누적형 실시간 스트림 렌더러 (Jitter Debounce 적용) ────────────────
    const RT_MAX_BLOCKS = 100;

    // UI 깜빡임 방지용 대기열 (Debounce Queue)
    let _sttResultQueue = {};

    let currentSharedAudio = null;
    let currentSharedPlayBtn = null;

    function playSegmentAudio(id, btn) {
      if (currentSharedAudio) {
        currentSharedAudio.pause();
        currentSharedAudio.currentTime = 0;
        if (currentSharedPlayBtn) {
          currentSharedPlayBtn.innerHTML = '<i class="bi bi-play-fill"></i> 재생';
        }
      }
      currentSharedAudio = new Audio(`/api/v1/segments/${id}/audio`);
      currentSharedPlayBtn = btn;
      btn.innerHTML = '<i class="bi bi-stop-fill"></i> 정지';
      currentSharedAudio.play().catch(e => {
        console.error("Audio play failed:", e);
        alert("오디오 다운로드 또는 재생에 실패했습니다.");
        btn.innerHTML = '<i class="bi bi-play-fill"></i> 재생';
      });
      currentSharedAudio.onended = () => {
        btn.innerHTML = '<i class="bi bi-play-fill"></i> 재생';
        currentSharedAudio = null;
        currentSharedPlayBtn = null;
      };
    }

    function scheduleRealtimeStream(data, typewriter = true) {
      if (!typewriter) {
        // late_join 처럼 즉각 표시해야 하는 것은 딜레이 없이 렌더링
        renderRealtimeStream(data, false);
        return;
      }
      // 새 레코드를 바로 그리지 않고 1500ms 대기 (이 안에 thread_updated가 오면 취소/병합됨)
      const keyId = data.segments && data.segments.length > 0 ? data.segments[0].id : data.id;
      _sttResultQueue[keyId] = {
        data: data,
        typewriter: typewriter,
        timer: setTimeout(() => {
          renderRealtimeStream(data, true);
          delete _sttResultQueue[keyId];
        }, 1500)
      };
    }

    function renderRealtimeStream(data, typewriter = true) {
      const container = document.getElementById('rt-container');
      const stream = document.getElementById('rt-stream');
      container.style.display = 'flex';
      container.closest('.scroll-area').style.display = 'flex';
      container.closest('.scroll-area').style.flexDirection = 'column';

      // ── 큐 초과 시 오래된 블록 제거 ──
      while (stream.children.length >= RT_MAX_BLOCKS) {
        stream.removeChild(stream.firstChild);
      }

      // ── 새 Record 블록 생성 ──
      const urgency = data.urgency || '일반';
      const isUrgent = urgency === '긴급';
      const block = document.createElement('div');
      block.className = `rt-block ${isUrgent ? 'card-urgent-blinking' : ''}`;

      if (isUrgent) {
        block.onclick = function () {
          this.classList.remove('card-urgent-blinking');
          this.classList.add('card-urgent-acknowledged');
        };
      }

      block.style.cssText = typewriter
        ? 'opacity:0; transform:translateY(8px); transition:all 0.35s ease;'
        : '';

      // 키워드 뱃지 HTML
      const kwHtml = data.keywords
        ? data.keywords.split(',').map(k =>
          `<span class="badge bg-secondary fw-normal me-1">${k.trim()}</span>`).join('')
        : '';

      // 열번 추출 뱃지
      const trainNoHtml = data.train_number
        ? `<span class="badge bg-warning text-dark ms-2"><i class="bi bi-train-front-fill me-1"></i>${data.train_number}</span>`
        : '';

      const urgencyIconHtml = isUrgent ? '<i class="bi bi-exclamation-triangle-fill text-danger me-1 urgency-icon"></i>' : '';

      // 헤더
      const ts = new Date().toLocaleTimeString('ko-KR', { hour: '2-digit', minute: '2-digit', second: '2-digit' });
      block.innerHTML = `
        <div class="rt-block-header d-flex justify-content-between align-items-center mb-1">
          <span class="fw-bold text-white">${data.title || '실시간 무전'}${trainNoHtml}</span>
          <span class="d-flex align-items-center gap-2">
            ${urgencyIconHtml}
            <span class="badge badge-${urgency}">${urgency}</span>
            <span class="text-muted small">${ts}</span>
          </span>
        </div>
        <p class="text-secondary small mb-1">${data.summary || ''}</p>
        <div class="mb-2">${kwHtml}</div>
        <div class="rt-block-segs"></div>
        <hr class="border-secondary my-2">
      `;
      stream.appendChild(block);

      // ── 세그먼트 렌더링 ──
      const segArea = block.querySelector('.rt-block-segs');
      const segs = data.segments || [];

      if (segs.length > 0) {
        segs.forEach((seg, i) => {
          const div = document.createElement('div');
          div.className = 'segment-item d-flex align-items-start mb-1';
          const spkr = seg.speaker
            ? `<span class="badge badge-${seg.speaker} me-1">${seg.speaker}</span>` : '';
          const playBtn = seg.id
            ? `<button class="btn btn-sm btn-outline-info py-0 px-1 ms-2" style="font-size:0.75rem;" onclick="playSegmentAudio(${seg.id}, this)"><i class="bi bi-play-fill"></i> 재생</button>`
            : '';
          div.innerHTML = `
            <div class="seg-time align-self-center">${fmt(seg.start_sec)}</div>
            <div class="flex-grow-1 align-self-center lh-sm">${spkr}<span class="type-target text-light"></span>${playBtn}</div>
          `;
          segArea.appendChild(div);

          if (typewriter) {
            setTimeout(() => {
              const target = div.querySelector('.type-target');
              let idx = 0;
              const txt = seg.text || '';
              (function typeChar() {
                if (idx < txt.length) {
                  target.textContent += txt.charAt(idx++);
                  setTimeout(typeChar, 22);
                }
              })();
            }, 200 + i * 500);
          } else {
            div.querySelector('.type-target').textContent = seg.text || '';
          }
        });
      } else {
        segArea.innerHTML = `<div class="p-2 text-muted small">${data.text || data.summary || ''}</div>`;
      }

      // ── 블록 Fade-in ──
      if (typewriter) {
        requestAnimationFrame(() => {
          block.style.opacity = '1';
          block.style.transform = 'translateY(0)';
        });
      }

      // ── Auto-scroll: 렌더 완료 후 맨 아래로 ──
      const scrollEl = stream.closest('.scroll-area');
      setTimeout(() => {
        scrollEl.scrollTop = scrollEl.scrollHeight;
      }, typewriter ? 300 : 50);
    }

    // ─── 4. Daily 채팅뷰 탭 로직 ─────────────────────────────────────────────
    const dailyDateInput = document.getElementById('daily-date');
    // 오늘 날짜를 기본값으로
    dailyDateInput.value = new Date().toISOString().slice(0, 10);

    // ── Daily 채팅뷰 상태 변수 ──
    let _dailyCursor = null;
    let _dailyLoading = false;
    let _dailyHasMore = false;
    let _dailyCurrentDate = '';

    function _renderSegBubble(seg) {
      const spkr = seg.speaker || '불명';
      const timeStr = seg.absolute_start_time
        ? new Date(seg.absolute_start_time).toLocaleTimeString('ko-KR', { hour: '2-digit', minute: '2-digit', second: '2-digit' })
        : fmt(seg.start_sec);
      const txt = seg.text || '';
      const reviewedMark = seg.is_reviewed ? ' <i class="bi bi-check2 text-success" title="검토완료"></i>' : '';

      const bubble = document.createElement('div');
      if (spkr === '불명') {
        bubble.className = 'd-flex justify-content-center mb-2 px-2';
        bubble.innerHTML = `
          <div class="text-center" style="max-width:90%;">
            <span class="badge bg-secondary fw-normal small">${spkr}${reviewedMark}</span>
            <div class="text-muted small mt-1">${txt}</div>
            <div style="font-size:0.68rem; color:#64748b;">${timeStr}</div>
          </div>`;
      } else {
        const isCtrl = spkr === '관제';
        bubble.className = `d-flex mb-2 ${isCtrl ? 'flex-row-reverse' : 'flex-row'} align-items-end gap-2 px-2`;
        bubble.innerHTML = `
          <div style="max-width:75%;">
            <div class="small text-muted mb-1 ${isCtrl ? 'text-end' : ''}">${spkr}${reviewedMark}</div>
            <div class="chat-bubble chat-${isCtrl ? 'right' : 'left'}">${txt}</div>
            <div class="small text-muted mt-1 ${isCtrl ? 'text-end' : ''}" style="font-size:0.7rem;">${timeStr}</div>
          </div>`;
      }
      return bubble;
    }

    async function _fetchDailyPage(dateStr, cursor) {
      const cursorParam = cursor != null ? `&cursor=${cursor}` : '';
      const res = await fetch(`/api/v1/segments/daily?date=${dateStr}&limit=100${cursorParam}`);
      if (!res.ok) throw new Error(`HTTP ${res.status}`);
      return res.json(); // { items, next_cursor, has_more }
    }

    async function loadDailyChat(dateStr) {
      const chatArea = document.getElementById('daily-chat-area');
      chatArea.innerHTML = '<div class="text-center text-muted mt-5 small"><i class="bi bi-arrow-clockwise"></i> 불러오는 중...</div>';
      _dailyCursor = null;
      _dailyHasMore = false;
      _dailyCurrentDate = dateStr;

      try {
        const page = await _fetchDailyPage(dateStr, null);
        console.debug(`[Daily] ${dateStr}: items=${page.items.length}, has_more=${page.has_more}, next_cursor=${page.next_cursor}`);

        if (!page.items.length) {
          chatArea.innerHTML = '<div class="text-center text-muted mt-5 small"><i class="bi bi-inbox"></i><br>해당 날짜에 기록이 없습니다.</div>';
          return;
        }

        chatArea.innerHTML = '';
        page.items.forEach(seg => chatArea.appendChild(_renderSegBubble(seg)));
        chatArea.scrollTop = chatArea.scrollHeight;

        _dailyCursor = page.next_cursor;
        _dailyHasMore = page.has_more;

        // 더 불러오기 버튼 표시
        if (_dailyHasMore) _appendLoadMoreBtn(chatArea, dateStr);

      } catch (err) {
        chatArea.innerHTML = `<div class="text-center text-danger mt-5 small">불러오기 실패: ${err.message}</div>`;
        console.error('[Daily] API 오류:', err);
      }
    }

    function _appendLoadMoreBtn(chatArea, dateStr) {
      const btn = document.createElement('div');
      btn.className = 'text-center my-3'; btn.id = 'daily-load-more';
      btn.innerHTML = '<button class="btn btn-sm btn-outline-secondary">다음 기록 더 보기</button>';
      btn.querySelector('button').onclick = async () => {
        if (_dailyLoading || !_dailyHasMore) return;
        _dailyLoading = true;
        btn.querySelector('button').textContent = '불러오는 중...';
        try {
          const page = await _fetchDailyPage(dateStr, _dailyCursor);
          btn.remove();
          page.items.forEach(seg => chatArea.appendChild(_renderSegBubble(seg)));
          _dailyCursor = page.next_cursor;
          _dailyHasMore = page.has_more;
          if (_dailyHasMore) _appendLoadMoreBtn(chatArea, dateStr);
        } catch (e) { console.error('[Daily] 더보기 실패:', e); }
        finally { _dailyLoading = false; }
      };
      chatArea.appendChild(btn);
    }

    // 날짜 변경 시 자동 로드
    dailyDateInput.addEventListener('change', () => loadDailyChat(dailyDateInput.value));

    // Daily 탭 클릭 시 자동 로드
    document.getElementById('tab-daily').addEventListener('shown.bs.tab', () => {
      loadDailyChat(dailyDateInput.value);
    });

    // ── Init ──
    loadHistory();

    // ─── 5. 드래그 스플리터 (Resizer) 로직 ───
    const resizer = document.getElementById('resizer');
    const leftPanel = document.querySelector('.history-panel');
    let isResizing = false;


    resizer.addEventListener('mousedown', (e) => {
      isResizing = true;
      resizer.classList.add('active');
      document.body.classList.add('no-select'); // 드래그 중 텍스트 선택 방지
    });

    document.addEventListener('mousemove', (e) => {
      if (!isResizing) return;
      // 좌측 패널의 새로운 너비 계산 (전체 창 대비)
      const newWidth = (e.clientX / window.innerWidth) * 100;
      // 최소 20%, 최대 60% 제한
      if (newWidth >= 20 && newWidth <= 60) {
        leftPanel.style.flexBasis = `${newWidth}%`;
      }
    });

    document.addEventListener('mouseup', () => {
      if (isResizing) {
        isResizing = false;
        resizer.classList.remove('active');
        document.body.classList.remove('no-select');
      }
    });

    // ─── 5. UI 토글 모드 및 WebSocket 로직 ───
    const modeSwitch = document.getElementById('modeSwitch');
    const sttForm = document.getElementById('stt-form');
    const liveWaitingPanel = document.getElementById('live-waiting-panel');
    const liveLabel = document.getElementById('liveLabel');
    let ws = null;

    modeSwitch.addEventListener('change', (e) => {
      const isLiveMode = e.target.checked;

      if (isLiveMode) {
        // Live Mode 활성화
        sttForm.style.display = 'none';
        liveWaitingPanel.style.display = 'block';
        liveLabel.style.opacity = '1';

        // WebSocket 연결 시작
        connectWebSocket();
      } else {
        // Test Mode 복귀
        sttForm.style.display = 'block';
        liveWaitingPanel.style.display = 'none';
        liveLabel.style.opacity = '0.5';

        // WebSocket 연결 종료
        if (ws) {
          ws.close();
          ws = null;
        }
      }
    });

    function connectWebSocket() {
      const protocol = window.location.protocol === "https:" ? "wss:" : "ws:";
      const wsUrl = `${protocol}//${window.location.host}/api/v1/ws/live`;
      ws = new WebSocket(wsUrl);

      ws.onopen = () => { console.log("[WS] Connected"); };

      ws.onmessage = (event) => {
        try {
          const data = JSON.parse(event.data);
          console.debug("[WS] received:", data.type, data.title || '');

          if (data.type === 'info') return;

          if (data.type === 'stt_result') {
            const renderData = {
              id: data.record_id,
              title: data.title || '실시간 무전',
              summary: data.summary || data.full_text || '',
              keywords: data.keywords || '',
              urgency: data.urgency || '일반',
              train_number: data.train_number || null,
              segments: data.segments || [],
              text: data.text || data.full_text || '',
            };
            // late_join=true → 즉시 표시( typewriter=false), 신규 → 타자기 효과
            scheduleRealtimeStream(renderData, !data.late_join);
            // history ajax reloading with delay
            setTimeout(loadHistory, 100);
          }

          if (data.type === 'context_discovered') {
            renderContextCard(data.data.contexts);
          }

          if (data.type === 'thread_updated') {
            const upd = data.data;
            console.debug(`[Thread] Action=${upd.action}, RecordID=${upd.record_id}, Speaker=${upd.speaker}`);

            // [Chapter 8.0] State Loss 완전 해결 및 UI Jitter 방어
            if (upd.action === 'append' && _sttResultQueue[upd.segment_id]) {
              const pendingReq = _sttResultQueue[upd.segment_id];
              const pendingData = pendingReq.data;
              clearTimeout(pendingReq.timer);
              delete _sttResultQueue[upd.segment_id];
              console.debug(`[Jitter 방지] segment=${upd.segment_id} 의 렌더링을 취소하고 기존 DOM 카드에 병합을 시작합니다.`);

              const stream = document.getElementById('rt-stream');
              if (stream.lastChild) {
                const block = stream.lastChild;
                const segArea = block.querySelector('.rt-block-segs');
                if (segArea) {
                  const segs = pendingData.segments || [];
                  segs.forEach((seg, i) => {
                    const div = document.createElement('div');
                    div.className = 'segment-item d-flex align-items-start mb-1';
                    const spkr = upd.speaker ? `<span class="badge badge-${upd.speaker} me-1">${upd.speaker}</span>` : '';
                    const playBtn = seg.id ? `<button class="btn btn-sm btn-outline-info py-0 px-1 ms-2" style="font-size:0.75rem;" onclick="playSegmentAudio(${seg.id}, this)"><i class="bi bi-play-fill"></i> 재생</button>` : '';
                    div.innerHTML = `
                            <div class="seg-time align-self-center">${fmt(seg.start_sec)}</div>
                            <div class="flex-grow-1 align-self-center lh-sm">${spkr}<span class="type-target text-light"></span>${playBtn}</div>
                          `;
                    segArea.appendChild(div);
                    const target = div.querySelector('.type-target');
                    let idx = 0;
                    const txt = seg.text || '';
                    (function typeChar() {
                      if (idx < txt.length) {
                        target.textContent += txt.charAt(idx++);
                        setTimeout(typeChar, 22);
                      }
                    })();
                  });
                }

                // 클래스 및 상태 보존 (업데이트 시 기존 Acknowledged 상태면 변경하지 않음)
                if (pendingData.urgency === '긴급' && !block.classList.contains('card-urgent-acknowledged')) {
                  block.classList.add('card-urgent-blinking');
                  block.onclick = function () {
                    this.classList.remove('card-urgent-blinking');
                    this.classList.add('card-urgent-acknowledged');
                  };
                }

                // 열차 번호 뱃지 보존/추가
                if (pendingData.train_number) {
                  const headerTitle = block.querySelector('.rt-block-header span.fw-bold');
                  if (headerTitle && !headerTitle.innerHTML.includes(pendingData.train_number)) {
                    headerTitle.innerHTML += `<span class="badge bg-warning text-dark ms-2"><i class="bi bi-train-front-fill me-1"></i>${pendingData.train_number}</span>`;
                  }
                }

                // Summary나 키워드도 여기서 텍스트 통합할 수 있지만, 요구사항인 뱃지와 클래스 병합에 집중
              }
            } else if (upd.action === 'new' && _sttResultQueue[upd.segment_id]) {
              const pendingReq = _sttResultQueue[upd.segment_id];
              clearTimeout(pendingReq.timer);
              const pendingData = pendingReq.data;
              const typewriter = pendingReq.typewriter;
              delete _sttResultQueue[upd.segment_id];
              renderRealtimeStream(pendingData, typewriter);
              console.debug(`[Jitter 해결] segment=${upd.segment_id} 의 렌더링 지연 대기를 풀고 새 카드로 즉시 표출합니다.`);
            }

            // 1) 좌측 히스토리 패널 AJAX 갱신 (전체 새로고침 없이 목록 UI만 갱신)
            loadHistory();

            // 2) Daily 뷰가 활성화되어 있으면 갱신
            if (document.getElementById('tab-daily').classList.contains('active')) {
              loadDailyChat(document.getElementById('daily-date').value);
            }
          }
        } catch (err) {
          console.error("[WS] Parse Error", err);
        }
      };

      ws.onclose = () => { console.log("[WS] Disconnected"); };
      ws.onerror = (e) => { console.warn("[WS] Error", e); };
    }

    // ─── 6. Context 렌더러 (최대 5개 유지) ───
    function renderContextCard(contexts) {
      const panel = document.getElementById('context-panel');
      // "대기 중..." 메시지 제거
      if (panel.querySelector('.bi-inbox')) {
        panel.innerHTML = '';
      }

      contexts.forEach(ctx => {
        const cdiv = document.createElement('div');
        cdiv.className = 'card bg-dark border-secondary mb-2 context-card fade-in';
        cdiv.innerHTML = `
          <div class="card-body p-2">
            <h6 class="card-title fw-bold text-info small mb-1"><i class="bi bi-tag-fill me-1"></i>${ctx.keyword}</h6>
            <div class="card-subtitle small fw-bold mb-1">${ctx.title}</div>
            <p class="card-text small text-muted lh-sm mb-0">${ctx.desc}</p>
          </div>
        `;
        panel.prepend(cdiv);
      });

      // 최대 5개 유지, 오래된 것 제거
      while (panel.children.length > 5) {
        panel.removeChild(panel.lastChild);
      }
    }
  </script>
</body>

</html>