/* global React */
// Data + helpers for feature modal content.
// describeItem(name, group, cat) returns: { desc, protocols, screens }
// We use a keyword map to generate sensible technical blurbs from the item name + context.

const DICT = [
  // Networking / protocols
  { kw: ['TCP'], proto: ['TCP', 'JSON Lines'], desc: '透過 TCP 長連線傳輸結構化 JSON 訊息，支援指令下達、狀態回報、ACK 與 ping/pong 心跳；連線中斷時自動重連並同步本機事件佇列。' },
  { kw: ['UDP', 'PTT', '電台', '語音廣播', '音訊'], proto: ['UDP', 'LGAP', 'WAV / PCM'], desc: '低延遲 UDP 多播音訊串流（LGAP 自定協定），封包帶有序號與時間戳供去重與抖動緩衝，後端再合成 WAV 餵入 Whisper 轉文字。' },
  { kw: ['MQTT', '節點'], proto: ['MQTT', 'JSON'], desc: '以 MQTT broker 訂閱 LoRa 節點上行訊息，topic 規範為 `node/<id>/<metric>`，後端聚合 RSSI、SNR、電量、PDR 與 GPS 後寫入 SQLite。' },
  { kw: ['HTTP', 'API', 'Dashboard', 'FastAPI', 'PDF', '報告'], proto: ['HTTP', 'FastAPI', 'JSON'], desc: '透過 FastAPI HTTP API 提供查詢與上傳介面，支援分頁、過濾、長輪詢與檔案串流；Web Dashboard 同源呼叫，跨網段裝置以 token 驗證。' },
  { kw: ['BLE'], proto: ['BLE 5.0', 'GATT'], desc: '透過 BLE GATT 介面與隨身 LoRa 節點配對，自定 service UUID 提供雙向資料管道；連線後上拋 LoRa 上下行封包至 App，並接收 App 的下行命令。' },
  { kw: ['Bonjour', 'mDNS', 'NSD', '自動發現', '自動探索'], proto: ['Bonjour', 'mDNS', 'NSD'], desc: '裝置開機後在區網以 mDNS 廣播 `_linkguard._tcp.` 服務描述，外勤 App 可在沒有 DNS 的災區現場零設定發現 HQ；Android 對應 NSD、iOS 用 NetService。' },
  { kw: ['Peer', 'HQ Peer', 'HQ 同步'], proto: ['TCP', 'CRDT-lite'], desc: 'HQ 之間以 Peer-to-Peer 同步指揮決策、命令與狀態快照，採輕量 CRDT 解衝突，斷線後自動補資料；任一台 HQ 失聯，其他節點仍可獨立指揮。' },
  { kw: ['NSD'], proto: ['NSD'], desc: 'Android NSD 在區網廣播 / 探索 HQ 服務，App 自動完成 IP 與 port 設定，免使用者輸入。' },

  // AI
  { kw: ['雙模型', '小模型', '大模型', '升級', '信心度'], proto: ['Ollama', 'Gemma', 'Qwen'], desc: '雙模型架構：小模型先做快速初判（< 1s），輸出帶 confidence；當信心度低於閾值或情境涉及高風險時，自動升級到大模型做深度推理，兼顧速度與準度。' },
  { kw: ['Ollama', 'AI 決策', 'AI 指揮', 'AI 建議', 'AI 聊天', 'AI'], proto: ['Ollama', 'Prompt 模板'], desc: '本機 Ollama 推論服務，所有 prompt 與決策結果不出本機，符合災區資安要求；Prompt 中綁定即時氣象、傷患摘要、節點狀態，模型輸出結構化 JSON 直接驅動 UI。' },
  { kw: ['Whisper', '語音轉文字', '語音辨識', '語音轉錄', '音訊轉錄'], proto: ['Whisper', 'WAV / PCM'], desc: 'Whisper.cpp 在本機執行的多語語音轉錄，支援噪音背景下的關鍵詞辨識；UDP 收到的 PTT 音訊封包合成 WAV 後送入，輸出文字附帶時間軸與信心分數。' },
  { kw: ['START', '檢傷', '傷患'], proto: ['START 演算法'], desc: '依 START（Simple Triage and Rapid Treatment）三步驟流程引導使用者判斷呼吸、循環、意識，App 自動套用六維度加權評分後給出 IMMEDIATE / DELAYED / MINOR / DECEASED 分級，並依此排序傷患佇列。' },
  { kw: ['翻譯'], proto: ['HTTP', 'On-device'], desc: '醫療場景翻譯 API，常用語句以片段對映即時回傳；當後端不可達時退回 iOS / Android on-device 翻譯模型，確保斷網仍可使用。' },

  // Data / storage
  { kw: ['SQLite', 'WAL', '資料庫'], proto: ['SQLite', 'WAL'], desc: '所有事件、命令、決策、傷患、軌跡寫入 SQLite（WAL 模式），支援高併發讀寫與 crash-safe；單一檔案易於 USB 備份與離線回放。' },
  { kw: ['USB 備份', 'USB'], proto: ['檔案系統'], desc: '偵測 USB 隨身碟插入後自動定時完整備份資料庫與音訊／照片資料夾，採增量同步減少寫入量；用於跨指揮中心轉移資料。' },
  { kw: ['離線回放', '離線備份', '回放', '快照'], proto: ['SQLite 快照'], desc: '從備份資料還原任一時間點的系統狀態，可在事後檢討時逐步回放決策、命令、傷患變化與通訊紀錄；支援指定區間與裝置篩選。' },
  { kw: ['事件日誌', '歷史', '紀錄', '記錄', '歷史紀錄', '事件記錄'], proto: ['SQLite'], desc: '所有 inbound / outbound 訊息寫入事件日誌，附時間戳、來源、收件人、payload 大小；支援後台搜尋、過濾、匯出，作為事後檢討與資安稽核依據。' },

  // GPS / 災情
  { kw: ['GPS'], proto: ['GPS', 'TCP'], desc: '前線裝置每 5–15 秒上報一次 GPS 座標（依電量與移動速度動態調整），HQ 端 Dashboard 與分區地圖即時更新軌跡與最後出現點。' },
  { kw: ['SOS', '求救', '警報'], proto: ['TCP 高優先', 'UDP 廣播'], desc: 'SOS 透過獨立高優先 channel 廣播，繞過一般佇列；HQ 以全螢幕警報攔截、播放警示音並自動鎖定地圖位置，不需確認。' },
  { kw: ['增援', '增援請求'], proto: ['TCP'], desc: '前線送出增援請求帶座標、需求類型（醫療／救援／補給）、人數；HQ 端以 overlay 提醒並可一鍵指派人員前往。' },
  { kw: ['危險標記', '災情', '災害狀態'], proto: ['TCP', 'GeoJSON'], desc: '前線以 GeoJSON 標記危險點（坍方、瓦斯、火源），HQ 與其他外勤端即時同步；標記具時效，到期後自動降亮。' },
  { kw: ['氣象', 'PWS'], proto: ['HTTP', 'CWA Open API'], desc: '從中央氣象署 Open API 抓取觀測站與雷達回波資料，格式化後給 AI 與 Dashboard；本機快取 24h 歷史，斷網時亦可顯示。' },

  // UI
  { kw: ['夜視儀', '深色', '暗色'], proto: ['色彩系統'], desc: '夜視儀風格暗色主題：背景近黑、文字偏綠白、警示用單色強調，避免救援人員夜間眼睛適應被破壞。' },
  { kw: ['Dashboard', '總覽', '主控台', '儀表板'], proto: ['SwiftUI / Compose'], desc: '依角色客製的總覽 Dashboard：HQ 看戰情全貌、外勤看當前任務與隊員位置；以 Live grid 即時更新，斷網時切換為 last-known 視圖。' },
  { kw: ['響應式', '平板', '手機'], proto: ['Compose / SwiftUI'], desc: '響應式版型：手機採單欄堆疊、平板兩欄分割、HQ 桌面三欄並列；橫直向皆可，避免在搶救過程中切換造成資訊遺失。' },

  // Photo
  { kw: ['照片', '照片牆', '縮圖', '原圖'], proto: ['HTTP multipart', 'JPEG'], desc: '前線拍照後以 multipart 上傳，後端產生縮圖與 EXIF 資訊；HQ 以照片牆瀑布流展示，可依災情、隊員、時間過濾。' },

  // Stats / resource
  { kw: ['統計', '即時統計', '歷史統計'], proto: ['HTTP', 'WebSocket'], desc: '即時統計透過 WebSocket 推送增量更新，避免大量輪詢；歷史統計以時間軸聚合，可下鑽至原始事件。' },
  { kw: ['資源'], proto: ['HTTP'], desc: '資源（人力、車輛、器材、藥品）以 CRUD API 管理，支援部署 / 回收狀態追蹤；HQ 端可拖拉指派至特定災情或隊伍。' },

  // Misc
  { kw: ['倒數', '計時器'], proto: ['Local'], desc: '本地倒數計時器供搜救任務（黃金救援時間、輪換休息）使用，到時於鎖定畫面與震動提醒。' },
  { kw: ['交班', '會報', '摘要'], proto: ['AI 摘要'], desc: '由 AI 將時段內事件、決策、傷患變化彙整為一段交班摘要，便於下一輪指揮快速 onboarding。' },
  { kw: ['命令', '廣播', '指派', '任務'], proto: ['TCP', 'ACK'], desc: '指揮命令以 TCP 廣播或單播下達，每筆帶 ACK 與已讀狀態；前線端依 priority 顯示為 toast、overlay 或全螢幕。' },
  { kw: ['通知'], proto: ['Local Notification'], desc: '本地通知中心彙整訊息、命令、警報；支援等級過濾與已讀標記。' },
  { kw: ['Critical 命令', '全螢幕', 'overlay'], proto: ['UI overlay'], desc: 'Critical 等級命令以全螢幕 overlay 顯示，需手動確認後才解除，避免在混亂環境中漏看關鍵指令。' },
  { kw: ['模擬', '模擬模式', '模擬引擎'], proto: ['本機'], desc: '無需後端的模擬模式：產生擬真的傷患、節點、命令資料流供訓練、Demo 與單元測試使用。' },
  { kw: ['語音', 'Apple Speech'], proto: ['SFSpeechRecognizer'], desc: 'macOS / iOS 使用 Apple Speech 框架做本機語音辨識；無 Whisper 後端時的 fallback。' },
  { kw: ['橋接', 'Bridge'], proto: ['TCP'], desc: 'HQ 端橋接 Windows 後端：將 HQ 收到的訊息中繼至後端微服務群，讓兩個指揮平台共享同一份資料源。' },
  { kw: ['Mac-only', '獨立模式'], proto: ['本機'], desc: 'macOS HQ 可獨立執行，所有後端職責（語音、照片、統計、AI、資源）由本機服務替代，適用小規模事件或 Windows 主機故障。' },
];

function describeItem(name, group, cat) {
  // Find first dictionary entry whose any keyword appears in name
  const lower = name.toLowerCase();
  for (const e of DICT) {
    if (e.kw.some((k) => name.includes(k) || lower.includes(k.toLowerCase()))) {
      return e;
    }
  }
  // Fallback by category
  return {
    proto: cat ? [cat.en] : ['—'],
    desc: `${name} 屬於「${group}」子模組，依 LinkGuard 三層架構在前線、HQ 與後端之間流轉。詳細實作隨情境調校，可在事件發生時透過事件日誌與離線回放回溯完整資料流。`,
  };
}

// Generate a small SVG flow diagram based on protocols / category
function FlowDiagram({ item, cat }) {
  // pick nodes based on what's mentioned
  const lower = (item + ' ' + (cat?.en || '')).toLowerCase();
  let nodes;
  if (lower.includes('lora') || item.includes('LoRa') || item.includes('節點') || item.includes('MQTT')) {
    nodes = ['LoRa 節點', 'BLE Bridge', 'Field App', 'TCP', 'HQ'];
  } else if (item.includes('語音') || item.includes('PTT') || item.includes('音訊') || item.includes('Whisper')) {
    nodes = ['麥克風', 'UDP', 'udp_server', 'Whisper', 'SQLite'];
  } else if (item.includes('AI') || item.includes('決策') || item.includes('模型')) {
    nodes = ['事件流', 'tcp_server', 'AI Engine', '決策', 'Broadcast'];
  } else if (item.includes('照片')) {
    nodes = ['Camera', 'multipart', 'photo_server', '縮圖', '照片牆'];
  } else if (item.includes('GPS') || item.includes('位置') || item.includes('軌跡')) {
    nodes = ['GPS', 'Field App', 'TCP', 'SQLite', 'HQ Map'];
  } else if (item.includes('SOS') || item.includes('求救') || item.includes('增援') || item.includes('警報')) {
    nodes = ['Field App', '高優先 ch', 'tcp_server', 'Broadcast', 'HQ Overlay'];
  } else if (item.includes('備份') || item.includes('回放') || item.includes('USB') || item.includes('SQLite')) {
    nodes = ['事件流', 'SQLite WAL', 'USB Backup', '離線回放', '事後檢討'];
  } else if (item.includes('翻譯')) {
    nodes = ['輸入', 'API', '線上模型', 'On-device fallback', '輸出'];
  } else if (item.includes('命令') || item.includes('廣播') || item.includes('任務') || item.includes('指派')) {
    nodes = ['HQ', 'tcp_server', 'Broadcast', 'Field App', 'ACK'];
  } else if (item.includes('Bonjour') || item.includes('mDNS') || item.includes('NSD') || item.includes('發現') || item.includes('探索')) {
    nodes = ['HQ', 'mDNS Advertise', '區網', 'Field App Browse', '自動連線'];
  } else if (item.includes('統計') || item.includes('Dashboard') || item.includes('儀表板')) {
    nodes = ['事件流', 'SQLite', 'stats_server', 'WebSocket', 'Dashboard'];
  } else if (item.includes('Peer') || item.includes('同步') || item.includes('橋接')) {
    nodes = ['HQ A', 'Peer Sync', 'HQ B', 'Backend', 'Field'];
  } else {
    nodes = ['Field', '網路層', '後端', 'SQLite', 'HQ'];
  }

  // layout
  const W = 720, H = 110;
  const PAD = 30;
  const stepX = (W - PAD * 2) / (nodes.length - 1);
  return (
    <svg viewBox={`0 0 ${W} ${H}`} className="flow-svg" preserveAspectRatio="xMidYMid meet">
      <defs>
        <marker id="flowarr" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="8" markerHeight="8" orient="auto">
          <path d="M0 0 L10 5 L0 10 z" fill="#1d6fe0" />
        </marker>
      </defs>
      {nodes.map((n, i) => {
        const cx = PAD + stepX * i;
        return (
          <g key={i}>
            {i < nodes.length - 1 && (
              <line x1={cx + 50} y1={50} x2={cx + stepX - 6} y2={50}
                stroke="#1d6fe0" strokeWidth="1.2" markerEnd="url(#flowarr)" strokeDasharray="3 3" />
            )}
            <rect x={cx - 50} y={28} width={100} height={44} rx={2}
              fill="#fff" stroke="#0a0a0a" strokeWidth="1" />
            <text x={cx} y={54} textAnchor="middle"
              fontFamily="'Noto Sans TC', sans-serif" fontSize="11" fill="#0a0a0a">
              {n}
            </text>
          </g>
        );
      })}
      <text x={PAD} y={H - 10} fontFamily="'JetBrains Mono', monospace" fontSize="9" fill="#666" letterSpacing="0.1em">
        DATA FLOW · {item}
      </text>
    </svg>
  );
}

window.LGFeatureData = { describeItem, FlowDiagram };
