"""
╔══════════════════════════════════════════════════════════════════════════════╗
║  ALASKA v2.0 - Task Monitor HTML Template                                    ║
║  Web Dashboard HTML/CSS/JS Template                                          ║
╠══════════════════════════════════════════════════════════════════════════════╣
║  Version : 2.0.1                                                             ║
║  Date    : 2026-02-07                                                        ║
╠══════════════════════════════════════════════════════════════════════════════╣
║  Summary:                                                                    ║
║    Task Monitor 웹 대시보드 HTML 템플릿                                      ║
║    - task_monitor.py에서 분리된 HTML/CSS/JS 코드                             ║
╠══════════════════════════════════════════════════════════════════════════════╣
║  Functions:                                                                  ║
║    render_monitor_page - 모니터링 페이지 HTML 생성                           ║
╚══════════════════════════════════════════════════════════════════════════════╝
"""

from .task_monitor_css import MONITOR_CSS


def render_monitor_page(status: dict, app_info: dict) -> str:
    """모니터링 페이지 HTML 생성.

    Args:
        status: 상태 정보 dict (uptime, running, total, timestamp)
        app_info: 앱 정보 dict (name, version, id)

    Returns:
        완성된 HTML 문자열
    """
    d = status
    app_name = app_info.get("name", "")
    app_ver = app_info.get("version", "")
    app_id = app_info.get("id", "")
    app_info_text = f"{app_name}:{app_ver} id={app_id}" if app_name else ""
    app_info_html = f'<span class="app-info">{app_info_text}</span>' if app_name else ""
    title_text = f"ALASKA Task Monitor {app_info_text}".strip()
    title_html = f"ALASKA Task Monitor {app_info_html}".strip()

    return f'''<!DOCTYPE html>
<html><head><meta charset="utf-8"><title>{title_text}</title>
<style>{MONITOR_CSS}</style></head>
<body>
<div class="header">
<h1>{title_html}</h1>
<span class="header-info" id="info-text">Uptime: {d['uptime']} | Tasks: {d['running']}/{d['total']} | {d['timestamp']}</span>
<div class="refresh-ctrl">
<input type="checkbox" id="autoRefresh" checked>
<label for="autoRefresh">Auto</label>
<select id="refreshInterval">
<option value="1">1s</option>
<option value="2">2s</option>
<option value="3" selected>3s</option>
<option value="5">5s</option>
<option value="10">10s</option>
</select>
<button class="btn-refresh" onclick="manualRefresh()">Refresh</button>
<button class="btn-clear" onclick="clearStats()">Clear</button>
</div>
</div>
<div class="main-container">
<div class="sidebar" id="sidebar">
<div class="sidebar-nav">
<div class="nav-category" onclick="toggleCategory(this)"><span>RMI</span><span class="nav-category-icon">▼</span></div>
<div class="nav-group">
<div class="nav-item active" onclick="showPanel('cpu')" data-panel="cpu">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="4" y="4" width="16" height="16" rx="2"/><rect x="9" y="9" width="6" height="6"/><path d="M9 1v3M15 1v3M9 20v3M15 20v3M20 9h3M20 14h3M1 9h3M1 14h3"/></svg>
<span>Task/Mem</span>
</div>
<div class="nav-item" onclick="showPanel('rmi')" data-panel="rmi">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M22 12h-4l-3 9L9 3l-3 9H2"/></svg>
<span>Speed</span>
</div>
<div class="nav-item" onclick="showPanel('rmicall')" data-panel="rmicall">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M18 20V10M12 20V4M6 20v-6"/></svg>
<span>Count</span>
</div>
<div class="nav-item" onclick="showPanel('signals')" data-panel="signals">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M8.59 13.51L15.42 17.49M15.41 6.51L8.59 10.49M21 5a3 3 0 11-6 0 3 3 0 016 0zM21 19a3 3 0 11-6 0 3 3 0 016 0zM9 12a3 3 0 11-6 0 3 3 0 016 0z"/></svg>
<span>Signals</span>
</div>
</div>
<div class="nav-category" onclick="toggleCategory(this)"><span>FILE</span><span class="nav-category-icon">▼</span></div>
<div class="nav-group">
<div class="nav-item" onclick="showPanel('gconfig')" data-panel="gconfig">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M14 2H6a2 2 0 00-2 2v16a2 2 0 002 2h12a2 2 0 002-2V8z"/><path d="M14 2v6h6M16 13H8M16 17H8M10 9H8"/></svg>
<span>Config</span>
</div>
<div class="nav-item" onclick="showPanel('logs')" data-panel="logs">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M14 2H6a2 2 0 00-2 2v16a2 2 0 002 2h12a2 2 0 002-2V8z"/><path d="M14 2v6h6"/><path d="M16 13H8"/><path d="M16 17H8"/><path d="M10 9H8"/></svg>
<span>Logs</span>
</div>
</div>
<div class="nav-category" onclick="toggleCategory(this)"><span>EXTERNAL</span><span class="nav-category-icon">▼</span></div>
<div class="nav-group">
<div class="nav-item" onclick="showPanel('extlink')" data-panel="extlink">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M4 4h16c1.1 0 2 .9 2 2v12c0 1.1-.9 2-2 2H4c-1.1 0-2-.9-2-2V6c0-1.1.9-2 2-2z"/><polyline points="22,6 12,13 2,6"/></svg>
<span>Integration</span>
</div>
<div class="nav-item" onclick="showPanel('sysinfo')" data-panel="sysinfo">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="2" y="3" width="20" height="14" rx="2"/><path d="M8 21h8M12 17v4"/></svg>
<span>SysInfo</span>
</div>
<div class="nav-item" onclick="showPanel('settings')" data-panel="settings">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="3"/><path d="M19.4 15a1.65 1.65 0 00.33 1.82l.06.06a2 2 0 010 2.83 2 2 0 01-2.83 0l-.06-.06a1.65 1.65 0 00-1.82-.33 1.65 1.65 0 00-1 1.51V21a2 2 0 01-2 2 2 2 0 01-2-2v-.09A1.65 1.65 0 009 19.4a1.65 1.65 0 00-1.82.33l-.06.06a2 2 0 01-2.83 0 2 2 0 010-2.83l.06-.06a1.65 1.65 0 00.33-1.82 1.65 1.65 0 00-1.51-1H3a2 2 0 01-2-2 2 2 0 012-2h.09A1.65 1.65 0 004.6 9a1.65 1.65 0 00-.33-1.82l-.06-.06a2 2 0 010-2.83 2 2 0 012.83 0l.06.06a1.65 1.65 0 001.82.33H9a1.65 1.65 0 001-1.51V3a2 2 0 012-2 2 2 0 012 2v.09a1.65 1.65 0 001 1.51 1.65 1.65 0 001.82-.33l.06-.06a2 2 0 012.83 0 2 2 0 010 2.83l-.06.06a1.65 1.65 0 00-.33 1.82V9a1.65 1.65 0 001.51 1H21a2 2 0 012 2 2 2 0 01-2 2h-.09a1.65 1.65 0 00-1.51 1z"/></svg>
<span>Settings</span>
</div>
</div>
</div>
<div class="sidebar-debug" title="Developer Debug Mode - For internal testing only">
<span class="debug-icon">🔧</span>
<span class="debug-text">DEV MODE</span>
</div>
<button class="sidebar-toggle" onclick="toggleSidebar()" title="Toggle Sidebar">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M15 18l-6-6 6-6"/></svg>
</button>
</div>
<div class="content-area">
<div id="panel-rmi" class="panel">
<div id="rmi-loading">Loading Latency data...</div>
<div id="rmi-content" style="display:none">
<div style="display:flex;padding:6px 12px;margin-bottom:4px;color:#9e9e9e;font-size:12px;border-bottom:1px solid #333">
<span style="width:28px"></span>
<span style="flex:1">Task ID</span>
<span style="width:100px">Class</span>
<span style="width:70px">Mode</span>
<span style="width:70px">Status</span>
<span style="width:140px;text-align:center">Latency (min/avg/max)</span>
</div>
<div id="rmi-tree" class="tree-container" style="font-size:13px"></div>
</div>
</div>
<div id="panel-rmicall" class="panel active">
<div id="rmicall-loading">Loading RMI method stats...</div>
<div id="rmicall-content" style="display:none">
<div style="margin-bottom:12px;display:flex;align-items:center;gap:16px">
<span style="color:#9e9e9e;font-size:12px" id="rmicall-interval">Interval: -</span>
<button id="measurement-toggle" class="btn-refresh" onclick="toggleMeasurement()" style="padding:4px 12px">
<span id="measurement-status">OFF</span>
</button>
<span style="color:#666;font-size:11px">measurement: config._monitor.measurement</span>
</div>
<div style="display:flex;padding:6px 12px;margin-bottom:4px;color:#9e9e9e;font-size:12px;border-bottom:1px solid #333">
<span style="width:28px"></span>
<span style="flex:1">Task ID</span>
<span style="width:120px">Class</span>
<span style="width:70px">Mode</span>
<span style="width:70px">Status</span>
<span style="width:80px;text-align:right">Total</span>
<span style="width:80px;text-align:right">Rate</span>
</div>
<div id="rmicall-tree" class="tree-container" style="font-size:13px"></div>
</div>
</div>
<div id="panel-cpu" class="panel">
<div id="cpu-loading">Loading CPU data...</div>
<div id="cpu-content" style="display:none">
<div id="application-section" style="margin-bottom:16px">
<div style="margin-bottom:8px;color:#9e9e9e;font-size:13px;font-weight:bold">Application</div>
<div id="appinfo-content" class="tree-container" style="font-size:13px;padding:12px"></div>
</div>
<div id="smblock-section" style="margin-bottom:16px">
<div style="margin-bottom:8px;color:#9e9e9e;font-size:13px;font-weight:bold">SmBlock Pools</div>
<div id="smblock-tree" class="tree-container" style="font-size:13px"></div>
</div>
<div style="display:flex;gap:16px;margin-bottom:16px">
<div style="flex:2;min-width:0">
<div style="background:#1e3a5f;border-radius:4px;overflow:hidden">
<table id="cpu-table" style="width:100%;border-collapse:collapse;font-size:13px">
<thead>
<tr style="background:#0d2137;color:#9e9e9e;font-size:12px">
<th style="padding:8px 6px;text-align:center;width:40px;color:#ff9800">Status</th>
<th style="padding:8px 10px;text-align:left">Task ID</th>
<th style="padding:8px 10px;text-align:left">Class</th>
<th style="padding:8px 10px;text-align:center;color:#ff9800">Fail/Rst</th>
<th style="padding:8px 10px;text-align:center;width:130px;color:#ff9800">CPU</th>
<th style="padding:8px 10px;text-align:right">Memory</th>
<th style="padding:8px 10px;text-align:right">PID</th>
</tr>
</thead>
<tbody id="cpu-tbody"></tbody>
</table>
</div>
</div>
<div id="task-vars-panel" style="flex:1;min-width:280px;background:#1e3a5f;border-radius:4px;padding:12px">
<div style="color:#9e9e9e;font-size:12px;margin-bottom:12px;font-weight:bold">Task Variables</div>
<div id="task-vars-content" style="color:#666;font-style:italic">Select a task to view variables</div>
</div>
</div>
</div>
</div>
<div id="panel-gconfig" class="panel">
<div id="gconfig-loading">Loading GConfig...</div>
<div id="gconfig-content" style="display:none">
<div class="gconfig-header">
<span id="gconfig-filepath"></span>
<button class="btn-refresh" onclick="loadGConfigData()">Refresh</button>
<button class="btn-expand" onclick="expandAllTree()">Expand All</button>
<button class="btn-collapse" onclick="collapseAllTree()">Collapse All</button>
</div>
<div class="gconfig-body">
<div class="gconfig-tree-wrap">
<div id="gconfig-tree" class="tree-container"></div>
</div>
<div class="value-setter">
<h3>Value Setter</h3>
<div id="setter-content">
<div class="value-setter-empty">트리에서 값을 선택하세요</div>
</div>
</div>
</div>
</div>
</div>
<div id="panel-signals" class="panel">
<div id="signals-loading">Loading Signal subscriptions...</div>
<div id="signals-content" style="display:none">
<div style="margin-bottom:16px;display:flex;align-items:center;gap:16px;flex-wrap:wrap">
<span style="color:#9e9e9e;font-size:12px">Signal Broker Mode: </span>
<span id="signals-mode" style="color:#81d4fa;font-weight:bold">-</span>
<button class="btn-refresh" onclick="loadSignalsData()">Refresh</button>
<span style="border-left:1px solid #444;height:20px"></span>
<span style="color:#9e9e9e;font-size:12px">Stats:</span>
<button id="signals-stats-toggle" class="btn-refresh" onclick="toggleSignalStats()">
<span id="signals-stats-status">ON</span>
</button>
<button class="btn-clear" onclick="clearSignalStats()">Clear Stats</button>
<span id="signals-cleared-at" style="color:#666;font-size:11px"></span>
</div>
<div style="display:flex;gap:16px;flex-wrap:wrap">
<div style="flex:1;min-width:280px">
<div style="margin-bottom:8px;color:#9e9e9e;font-size:13px;font-weight:bold">Signal Subscriptions</div>
<div id="signals-subs" class="tree-container" style="font-size:13px;min-height:200px"></div>
</div>
<div style="flex:1;min-width:280px">
<div style="margin-bottom:8px;color:#9e9e9e;font-size:13px;font-weight:bold">Registered Queues</div>
<div id="signals-queues" class="tree-container" style="font-size:13px;min-height:150px"></div>
</div>
<div style="flex:1;min-width:280px">
<div style="margin-bottom:8px;color:#9e9e9e;font-size:13px;font-weight:bold">Signal Statistics</div>
<div id="signals-stats" class="tree-container" style="font-size:13px;min-height:150px"></div>
</div>
</div>
</div>
</div>
<div id="panel-sysinfo" class="panel">
<div id="sysinfo-loading">Loading System Info...</div>
<div id="sysinfo-content" style="display:none">
<div style="margin-bottom:12px;display:flex;align-items:center;gap:16px">
<button class="btn-refresh" onclick="loadSysInfo()">Refresh</button>
</div>
<div style="display:flex;gap:16px;flex-wrap:wrap">
<div style="flex:1;min-width:300px">
<div style="margin-bottom:8px;color:#9e9e9e;font-size:13px;font-weight:bold">System</div>
<div id="sysinfo-system" class="tree-container" style="font-size:13px;padding:12px"></div>
</div>
<div style="flex:1;min-width:200px">
<div style="margin-bottom:8px;color:#9e9e9e;font-size:13px;font-weight:bold">CPU</div>
<div id="sysinfo-cpu" class="tree-container" style="font-size:13px;padding:12px"></div>
</div>
<div style="flex:1;min-width:200px">
<div style="margin-bottom:8px;color:#9e9e9e;font-size:13px;font-weight:bold">Memory</div>
<div id="sysinfo-memory" class="tree-container" style="font-size:13px;padding:12px"></div>
</div>
</div>
<div style="display:flex;gap:16px;flex-wrap:wrap;margin-top:16px">
<div style="flex:1;min-width:280px">
<div style="margin-bottom:8px;color:#9e9e9e;font-size:13px;font-weight:bold">GPU</div>
<div id="sysinfo-gpu" class="tree-container" style="font-size:13px;padding:12px"></div>
</div>
<div style="flex:2;min-width:400px">
<div style="margin-bottom:8px;color:#9e9e9e;font-size:13px;font-weight:bold">Storage</div>
<div id="sysinfo-storage" class="tree-container" style="font-size:13px;padding:12px"></div>
</div>
</div>
<div style="display:flex;gap:16px;flex-wrap:wrap;margin-top:16px">
<div style="flex:1;min-width:300px">
<div style="margin-bottom:8px;color:#9e9e9e;font-size:13px;font-weight:bold">Network Interfaces</div>
<div id="sysinfo-network" class="tree-container" style="font-size:13px;padding:12px;max-height:200px;overflow-y:auto"></div>
</div>
<div style="flex:1;min-width:250px">
<div style="margin-bottom:8px;color:#9e9e9e;font-size:13px;font-weight:bold">ARP Table</div>
<div id="sysinfo-arp" class="tree-container" style="font-size:13px;padding:12px;max-height:200px;overflow-y:auto"></div>
</div>
<div style="flex:1;min-width:300px">
<div style="margin-bottom:8px;color:#9e9e9e;font-size:13px;font-weight:bold">Routing Table</div>
<div id="sysinfo-routing" class="tree-container" style="font-size:13px;padding:12px;max-height:200px;overflow-y:auto"></div>
</div>
</div>
</div>
</div>
<div id="panel-extlink" class="panel">
<div id="extlink-loading">Loading External Link settings...</div>
<div id="extlink-content" style="display:none">
<div style="margin-bottom:12px;display:flex;align-items:center;gap:16px">
<button class="btn-refresh" onclick="loadExtLink()">Refresh</button>
<button class="btn-refresh" onclick="saveExtLink()">Save</button>
</div>
<div style="margin-bottom:16px">
<div style="margin-bottom:8px;color:#9e9e9e;font-size:13px;font-weight:bold">Health Report</div>
<div class="tree-container" style="padding:12px">
<div style="display:flex;gap:4px;margin-bottom:12px;border-bottom:1px solid #444;padding-bottom:8px">
<button class="extlink-tab active" onclick="showExtTab('slack')" data-tab="slack" style="padding:6px 16px;background:#0288d1;border:none;color:#fff;border-radius:4px 4px 0 0;cursor:pointer">Slack</button>
<button class="extlink-tab" onclick="showExtTab('email')" data-tab="email" style="padding:6px 16px;background:#444;border:none;color:#9e9e9e;border-radius:4px 4px 0 0;cursor:pointer">Email</button>
<button class="extlink-tab" onclick="showExtTab('webhook')" data-tab="webhook" style="padding:6px 16px;background:#444;border:none;color:#9e9e9e;border-radius:4px 4px 0 0;cursor:pointer">Webhook</button>
</div>
<div id="extlink-tab-slack" class="extlink-tab-content" style="display:block">
<div id="extlink-slack"></div>
</div>
<div id="extlink-tab-email" class="extlink-tab-content" style="display:none">
<div id="extlink-email"></div>
</div>
<div id="extlink-tab-webhook" class="extlink-tab-content" style="display:none">
<div id="extlink-webhook"></div>
</div>
<div style="margin-top:16px;padding-top:12px;border-top:1px solid #444">
<div style="margin-bottom:8px;color:#ffcc80;font-weight:bold">Schedule Settings</div>
<div id="extlink-report"></div>
</div>
</div>
</div>
<div style="margin-bottom:16px">
<div style="margin-bottom:8px;color:#9e9e9e;font-size:13px;font-weight:bold">Alert Settings</div>
<div id="extlink-alert" class="tree-container" style="font-size:13px;padding:12px"></div>
</div>
</div>
</div>
</div>
<div id="panel-logs" class="panel">
<div class="panel-tabs">
<div class="panel-tab active" onclick="switchLogsTab('logs')">Logs</div>
<div class="panel-tab" onclick="switchLogsTab('memory')">Memory</div>
</div>
<div id="logs-tab-logs" class="tab-content active">
<div style="margin-bottom:12px;display:flex;align-items:center;gap:16px;flex-wrap:wrap">
<button class="btn-refresh" onclick="loadLogs()">Refresh</button>
<select id="log-level" style="background:#2d2d2d;color:#e0e0e0;border:1px solid #444;padding:6px 10px;border-radius:4px">
<option value="">All Levels</option>
<option value="DEBUG">DEBUG</option>
<option value="INFO">INFO</option>
<option value="WARNING">WARNING</option>
<option value="ERROR">ERROR</option>
<option value="CRITICAL">CRITICAL</option>
</select>
<select id="log-task" style="background:#2d2d2d;color:#e0e0e0;border:1px solid #444;padding:6px 10px;border-radius:4px">
<option value="">All Tasks</option>
</select>
<select id="log-count" style="background:#2d2d2d;color:#e0e0e0;border:1px solid #444;padding:6px 10px;border-radius:4px">
<option value="50">50 logs</option>
<option value="100" selected>100 logs</option>
<option value="200">200 logs</option>
<option value="500">500 logs</option>
</select>
<label style="display:flex;align-items:center;gap:6px;color:#9e9e9e;font-size:13px">
<input type="checkbox" id="log-auto" checked style="width:14px;height:14px"> Auto Refresh
</label>
</div>
<div id="logs-loading">Loading logs...</div>
<div id="logs-content" style="display:none">
<div id="logs-table-wrap" style="max-height:calc(100vh - 250px);overflow-y:auto;overflow-x:auto">
<table id="logs-table" style="width:100%;table-layout:fixed">
<thead style="position:sticky;top:0">
<tr>
<th style="width:180px">Time</th>
<th style="width:80px">Level</th>
<th style="width:120px">Task</th>
<th style="width:auto">Message</th>
</tr>
</thead>
<tbody id="logs-body"></tbody>
</table>
</div>
</div>
</div>
<div id="logs-tab-memory" class="tab-content">
<div style="margin-bottom:8px;color:var(--text-accent2);font-size:14px;font-weight:bold">Memory Usage History (Leak Detection)</div>
<div class="tree-container" style="padding:12px">
<div style="display:flex;align-items:center;gap:16px;margin-bottom:8px;flex-wrap:wrap">
<span id="memory-current" style="color:#81d4fa;font-size:13px">Current: --</span>
<span id="memory-trend" style="font-size:13px">Trend: --</span>
<span id="memory-stats" style="color:#666;font-size:12px">Points: --</span>
<span id="memory-warning" style="color:#f44336;font-size:13px;display:none"></span>
</div>
<canvas id="memory-graph" width="800" height="150" style="width:100%;height:150px;background:var(--bg-main);border-radius:4px"></canvas>
<div style="display:flex;justify-content:space-between;margin-top:4px;font-size:11px;color:#666">
<span id="memory-time-start">--</span>
<span>Memory Trend (72h max, 1 min interval)</span>
<span id="memory-time-end">Now</span>
</div>
</div>
</div>
</div>
<div id="panel-settings" class="panel">
<div style="max-width:500px">
<h2 style="margin:0 0 20px 0;color:var(--text-accent);font-size:18px;border-bottom:1px solid var(--border-color);padding-bottom:10px">Settings</h2>
<div style="background:var(--bg-content);padding:16px;border-radius:8px;margin-bottom:16px">
<div style="margin-bottom:12px;color:var(--text-primary);font-size:14px;font-weight:500">Theme</div>
<div style="display:flex;align-items:center;gap:10px">
<span style="font-size:16px">🌙</span>
<label class="theme-toggle">
<input type="checkbox" id="themeToggle" onchange="toggleThemeSwitch()">
<span class="theme-slider"></span>
</label>
<span style="font-size:16px">☀️</span>
<span style="color:var(--text-secondary);font-size:12px;margin-left:8px">Dark / Light</span>
</div>
</div>
</div>
</div>
</div>
</div>
''' + _get_javascript()


def _get_javascript() -> str:
    """JavaScript 코드 반환."""
    return '''<script>
// Global variable declarations (must be at top to avoid TDZ errors)
let refreshTimer = null;
let rmiCallTime = 0;        // RMI call timestamp
let selectedPath = null;    // GConfig selected path
let selectedVtype = null;   // GConfig selected value type
let logsTimer = null;       // Logs auto-refresh timer
let logsTaskList = [];      // Logs task list

// Theme initialization
const savedTheme = localStorage.getItem('theme') || 'dark';
document.documentElement.setAttribute('data-theme', savedTheme);
// Set toggle state (checked = light mode)
setTimeout(() => {
    const toggle = document.getElementById('themeToggle');
    if (toggle) toggle.checked = savedTheme === 'light';
}, 0);

function toggleThemeSwitch() {
    const toggle = document.getElementById('themeToggle');
    const newTheme = toggle.checked ? 'light' : 'dark';
    document.documentElement.setAttribute('data-theme', newTheme);
    localStorage.setItem('theme', newTheme);
}

function switchLogsTab(tab) {
    // Update tab buttons
    document.querySelectorAll('#panel-logs .panel-tab').forEach(t => t.classList.remove('active'));
    document.querySelector(`#panel-logs .panel-tab[onclick*="${tab}"]`).classList.add('active');
    // Update tab content
    document.querySelectorAll('#panel-logs .tab-content').forEach(c => c.classList.remove('active'));
    document.getElementById('logs-tab-' + tab).classList.add('active');
}

const checkbox = document.getElementById('autoRefresh');
const intervalSelect = document.getElementById('refreshInterval');
const savedEnabled = localStorage.getItem('refreshEnabled');
const savedInterval = localStorage.getItem('refreshInterval');
if (savedEnabled !== null) checkbox.checked = savedEnabled === 'true';
if (savedInterval) intervalSelect.value = savedInterval;

function updateStats() {
    fetch('/api/status').then(r => r.json()).then(data => {
        document.getElementById('rmi-loading').style.display = 'none';
        document.getElementById('rmi-content').style.display = 'block';

        let html = '';
        data.tasks.forEach((t, idx) => {
            const treeId = `rmi-tree-${idx}`;
            const isExpanded = localStorage.getItem(`rmi-${t.id}`) !== 'false';
            const statusColor = t.status === 'running' ? '#4caf50' : t.status === 'stopped' ? '#f44336' : '#ff9800';

            html += `<div class="tree-node">
                <div class="tree-parent" onclick="toggleRmiTree('${treeId}','${t.id}')" style="display:flex;align-items:center;padding:10px 12px;background:#1e3a5f;border-radius:4px;margin-bottom:2px;cursor:pointer">
                    <span class="tree-arrow" id="arrow-${treeId}" style="margin-right:10px;transition:transform 0.2s">${isExpanded ? '▼' : '▶'}</span>
                    <span style="flex:1;font-weight:bold;color:#81d4fa">${t.id}</span>
                    <span style="width:100px;color:#9e9e9e">${t.class}</span>
                    <span style="width:70px;color:#9e9e9e">${t.mode}</span>
                    <span style="width:70px;color:${statusColor}">${t.status}</span>
                    <span style="width:140px;text-align:center;color:#e0e0e0">${t.rmi_min.toFixed(1)} / ${t.rmi_avg.toFixed(1)} / ${t.rmi_max.toFixed(1)}</span>
                </div>
                <div class="tree-children" id="${treeId}" style="margin-left:28px;display:${isExpanded ? 'block' : 'none'}">
                    <div style="display:flex;padding:6px 12px;border-bottom:1px solid #333">
                        <span style="width:120px;color:#ffcc80">RMI Count</span>
                        <span style="color:#e0e0e0">Rx: ${t.rmi_rx.toLocaleString()} / Tx: ${t.rmi_tx.toLocaleString()}</span>
                    </div>
                    <div style="display:flex;padding:6px 12px;border-bottom:1px solid #333">
                        <span style="width:120px;color:#ffcc80">Failures</span>
                        <span><span style="color:#f44336">${t.rmi_fail.toLocaleString()}</span> RMI / <span style="color:#ff9800">${t.restart}</span> Restart</span>
                    </div>
                    <div style="display:flex;padding:6px 12px;border-bottom:1px solid #333">
                        <span style="width:120px;color:#ffcc80">Queue Size</span>
                        <span style="color:#e0e0e0">RxQ: ${t.rxq_size} / TxQ: ${t.txq_size}</span>
                    </div>
                </div>
            </div>`;
        });
        document.getElementById('rmi-tree').innerHTML = html;
        document.getElementById('info-text').innerHTML =
            `Uptime: ${data.uptime} | Tasks: ${data.running}/${data.total} running | ${data.timestamp}`;
    }).catch(err => {
        document.getElementById('rmi-loading').textContent = 'Error: ' + err.message;
        document.getElementById('rmi-loading').style.color = '#f44336';
    });
}

function toggleRmiTree(treeId, taskId) {
    const el = document.getElementById(treeId);
    const arrow = document.getElementById('arrow-' + treeId);
    const isHidden = el.style.display === 'none';
    el.style.display = isHidden ? 'block' : 'none';
    arrow.textContent = isHidden ? '▼' : '▶';
    localStorage.setItem(`rmi-${taskId}`, isHidden ? 'true' : 'false');
}

function refreshActivePanel() {
    const activePanel = localStorage.getItem('activePanel') || 'cpu';
    if (activePanel === 'cpu') loadCpuData();
    else if (activePanel === 'rmi') updateStats();
    else if (activePanel === 'rmicall') loadRmiCallData();
    // cpu, gconfig: no auto-refresh (static data)
}

function startRefresh() {
    stopRefresh();
    if (checkbox.checked) {
        refreshTimer = setInterval(refreshActivePanel, parseInt(intervalSelect.value) * 1000);
    }
}

function stopRefresh() {
    if (refreshTimer) { clearInterval(refreshTimer); refreshTimer = null; }
}

checkbox.addEventListener('change', () => {
    localStorage.setItem('refreshEnabled', checkbox.checked);
    startRefresh();
});

intervalSelect.addEventListener('change', () => {
    localStorage.setItem('refreshInterval', intervalSelect.value);
    startRefresh();
});

startRefresh();

function clearStats() { fetch('/api/clear').then(() => updateStats()); }

function manualRefresh() {
    const activePanel = localStorage.getItem('activePanel') || 'cpu';
    if (activePanel === 'cpu') loadCpuData();
    else if (activePanel === 'rmi') updateStats();
    else if (activePanel === 'rmicall') loadRmiCallData();
    else if (activePanel === 'gconfig') loadGConfigData();
}

function toggleSidebar() {
    const sidebar = document.getElementById('sidebar');
    sidebar.classList.toggle('collapsed');
    localStorage.setItem('sidebarCollapsed', sidebar.classList.contains('collapsed'));
}

function toggleCategory(el) {
    el.classList.toggle('collapsed');
}

function showPanel(name) {
    const panel = document.querySelector(`.panel#panel-${name}`);
    const navItem = document.querySelector(`.nav-item[data-panel="${name}"]`);
    if (!panel || !navItem) {
        name = 'cpu';  // Fallback to default panel (Task/Mem)
        localStorage.setItem('activePanel', name);
        return showPanel(name);
    }
    document.querySelectorAll('.nav-item').forEach(n => n.classList.remove('active'));
    document.querySelectorAll('.panel').forEach(p => p.classList.remove('active'));
    panel.classList.add('active');
    navItem.classList.add('active');
    localStorage.setItem('activePanel', name);
    // Load panel data on switch
    if (name === 'rmi') updateStats();
    else if (name === 'rmicall') loadRmiCallData();
    else if (name === 'cpu') loadCpuData();
    else if (name === 'gconfig') loadGConfigData();
    else if (name === 'signals') loadSignalsData();
    else if (name === 'sysinfo') loadSysInfo();
    else if (name === 'extlink') loadExtLink();
    else if (name === 'logs') { loadLogs(); startLogsTimer(); }
    // Restart refresh timer for new panel
    startRefresh();
}

// Restore sidebar state
if (localStorage.getItem('sidebarCollapsed') === 'true') {
    document.getElementById('sidebar').classList.add('collapsed');
}

const savedPanel = localStorage.getItem('activePanel');
if (savedPanel) showPanel(savedPanel);

let cpuTasksData = [];  // Store tasks data for selection
let selectedTaskId = null;
let systemCpuData = {cpu: 0, mem: 0};  // Store system CPU/Memory data

function loadCpuData() {
    fetch('/api/cpu').then(r => r.json()).then(data => {
        document.getElementById('cpu-loading').style.display = 'none';
        document.getElementById('cpu-content').style.display = 'block';
        if (data.error) {
            document.getElementById('cpu-tbody').innerHTML = '<tr><td colspan="7" style="color:#f44336;padding:12px">psutil not installed. Run: pip install psutil</td></tr>';
            return;
        }
        systemCpuData = {cpu: data.system_cpu, mem: data.system_memory};

        cpuTasksData = data.tasks;  // Store for selection
        let html = '';
        data.tasks.forEach((t, idx) => {
            const cpu = t.cpu || 0;
            const cpuClass = cpu < 30 ? 'cpu-low' : cpu < 70 ? 'cpu-mid' : 'cpu-high';
            const statusColor = t.alive ? '#4caf50' : '#f44336';
            const failColor = (t.rmi_fail || 0) > 0 ? '#f44336' : '#666';
            const restartColor = (t.restart || 0) > 0 ? '#ff9800' : '#666';
            const isSelected = selectedTaskId === t.id;
            const rowBg = isSelected ? '#2a4a6f' : (idx % 2 === 0 ? '#1e3a5f' : '#1a3050');
            // Mode icon: Process=CPU chip, Thread=branch
            const modeColor = t.mode === 'process' ? '#64b5f6' : '#ce93d8';
            const modeIcon = t.mode === 'process'
                ? '<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="' + modeColor + '" stroke-width="2" title="Process"><rect x="4" y="4" width="16" height="16" rx="2"/><rect x="9" y="9" width="6" height="6"/><path d="M9 1v3M15 1v3M9 20v3M15 20v3M20 9h3M20 14h3M1 9h3M1 14h3"/></svg>'
                : '<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="' + modeColor + '" stroke-width="2" title="Thread"><circle cx="18" cy="18" r="3"/><circle cx="6" cy="6" r="3"/><path d="M6 21V9a9 9 0 009 9"/></svg>';

            html += `<tr onclick="selectTask('${t.id}')" style="cursor:pointer;background:${rowBg}"
                onmouseover="this.style.background='#2a4a6f'"
                onmouseout="this.style.background='${isSelected ? '#2a4a6f' : rowBg}'">
                <td style="padding:8px 6px;text-align:center"><span style="display:inline-block;width:10px;height:10px;border-radius:50%;background:${statusColor}"></span></td>
                <td style="padding:8px 10px;color:#81d4fa;font-weight:bold"><span style="display:inline-flex;align-items:center;gap:6px">${t.id}${modeIcon}</span></td>
                <td style="padding:8px 10px;color:#9e9e9e">${t.class || '-'}</td>
                <td style="padding:8px 10px;text-align:center;font-size:11px"><span style="color:${failColor}">${t.rmi_fail || 0}</span>/<span style="color:${restartColor}">${t.restart || 0}</span></td>
                <td style="padding:8px 10px">
                    <div style="display:flex;align-items:center;gap:4px">
                        <div class="cpu-bar" style="height:12px;width:70px">
                            <div class="cpu-bar-fill ${cpuClass}" style="width:${Math.min(cpu, 100)}%"></div>
                        </div>
                        <span style="color:#e0e0e0;font-size:11px">${cpu.toFixed(1)}%</span>
                    </div>
                </td>
                <td style="padding:8px 10px;text-align:right;color:#9e9e9e">${(t.memory || 0).toFixed(1)} MB</td>
                <td style="padding:8px 10px;text-align:right;color:#9e9e9e">${t.pid || '-'}</td>
            </tr>`;
        });

        document.getElementById('cpu-tbody').innerHTML = html;
        loadSmBlockData();
        loadAppInfoData();

        // Refresh selected task vars if any
        if (selectedTaskId) {
            const task = cpuTasksData.find(t => t.id === selectedTaskId);
            if (task) showTaskVars(task);
        }
    }).catch(err => {
        document.getElementById('cpu-loading').textContent = 'Error: ' + err.message;
        document.getElementById('cpu-loading').style.color = '#f44336';
    });
}

function selectTask(taskId) {
    selectedTaskId = taskId;
    const task = cpuTasksData.find(t => t.id === taskId);
    if (task) {
        showTaskVars(task);
        loadCpuData();  // Refresh to show selection highlight
    }
}

function showTaskVars(task) {
    const container = document.getElementById('task-vars-content');
    const vars = task.vars || {};
    const varKeys = Object.keys(vars);

    if (varKeys.length === 0) {
        container.innerHTML = `<div style="color:#81d4fa;margin-bottom:8px;font-weight:bold">${task.id}</div>
            <div style="color:#666;font-style:italic">No config variables</div>`;
        return;
    }

    let html = `<div style="color:#81d4fa;margin-bottom:12px;font-weight:bold">${task.id}</div>
        <table style="width:100%;border-collapse:collapse;font-size:12px">
        <thead><tr style="background:#0d2137;color:#9e9e9e">
            <th style="padding:6px 8px;text-align:left">Variable</th>
            <th style="padding:6px 8px;text-align:left">Value</th>
            <th style="padding:6px 8px;text-align:center;width:50px"></th>
        </tr></thead><tbody>`;

    varKeys.forEach((k, idx) => {
        const val = vars[k];
        const inputId = `cpu-cfg-${task.id}-${k}`;
        const isBool = typeof val === 'boolean';
        const rowBg = idx % 2 === 0 ? '#1a3050' : '#1e3a5f';

        if (isBool) {
            html += `<tr style="background:${rowBg}">
                <td style="padding:6px 8px;color:#ffcc80">${k}</td>
                <td style="padding:6px 8px">
                    <label style="cursor:pointer;margin-right:8px">
                        <input type="radio" name="${inputId}" value="true" ${val ? 'checked' : ''}> <span style="color:#4caf50">T</span>
                    </label>
                    <label style="cursor:pointer">
                        <input type="radio" name="${inputId}" value="false" ${!val ? 'checked' : ''}> <span style="color:#f44336">F</span>
                    </label>
                </td>
                <td style="padding:6px 8px;text-align:center">
                    <button class="btn-save" style="padding:2px 6px;font-size:10px" onclick="updateCpuConfigBool('${task.id}','${k}','${inputId}')">Save</button>
                    <span id="msg-${inputId}" class="msg" style="font-size:10px"></span>
                </td>
            </tr>`;
        } else {
            html += `<tr style="background:${rowBg}">
                <td style="padding:6px 8px;color:#ffcc80">${k}</td>
                <td style="padding:6px 8px">
                    <input type="text" id="${inputId}" class="config-input" style="width:100%;padding:3px 6px;font-size:11px" value="${val}">
                </td>
                <td style="padding:6px 8px;text-align:center">
                    <button class="btn-save" style="padding:2px 6px;font-size:10px" onclick="updateCpuConfig('${task.id}','${k}','${inputId}')">Save</button>
                    <span id="msg-${inputId}" class="msg" style="font-size:10px"></span>
                </td>
            </tr>`;
        }
    });
    html += `</tbody></table>`;
    container.innerHTML = html;
}

function updateCpuConfig(taskId, key, inputId) {
    const input = document.getElementById(inputId);
    const msgEl = document.getElementById('msg-' + inputId);
    fetch('/api/config', {
        method: 'POST',
        headers: {'Content-Type': 'application/json'},
        body: JSON.stringify({task_id: taskId, key: key, value: input.value})
    }).then(r => r.json()).then(res => {
        msgEl.className = res.status === 'ok' ? 'msg msg-ok' : 'msg msg-err';
        msgEl.textContent = res.status === 'ok' ? 'OK' : (res.message || 'Error');
        setTimeout(() => { msgEl.textContent = ''; }, 2000);
    });
}

function updateCpuConfigBool(taskId, key, inputId) {
    const radios = document.getElementsByName(inputId);
    const msgEl = document.getElementById('msg-' + inputId);
    let newVal = false;
    radios.forEach(r => { if (r.checked) newVal = (r.value === 'true'); });
    fetch('/api/config', {
        method: 'POST',
        headers: {'Content-Type': 'application/json'},
        body: JSON.stringify({task_id: taskId, key: key, value: newVal ? 'true' : 'false'})
    }).then(r => r.json()).then(res => {
        msgEl.className = res.status === 'ok' ? 'msg msg-ok' : 'msg msg-err';
        msgEl.textContent = res.status === 'ok' ? 'OK' : (res.message || 'Error');
        setTimeout(() => { msgEl.textContent = ''; }, 2000);
    });
}

function loadSmBlockData() {
    fetch('/api/smblock').then(r => r.json()).then(data => {
        const treeEl = document.getElementById('smblock-tree');
        if (!treeEl) return;
        if (data.length === 0) {
            treeEl.innerHTML = '<div style="color:#666;padding:8px 0">No SmBlock pools configured</div>';
            return;
        }

        let html = '';
        data.forEach((s, idx) => {
            if (s.error) {
                html += `<div style="padding:10px 12px;background:#3e2723;border-radius:4px;margin-bottom:2px">
                    <span style="color:#f44336">${s.name}: ${s.error}</span>
                </div>`;
                return;
            }

            const treeId = `smblock-tree-${idx}`;
            const isExpanded = localStorage.getItem(`smblock-${s.name}`) !== 'false';
            const usageClass = s.usage_pct > 80 ? 'fail' : s.usage_pct > 50 ? 'warn' : '';
            const barColor = s.usage_pct > 80 ? '#f44336' : s.usage_pct > 50 ? '#ff9800' : '#4caf50';
            const users = s.users || [];

            // Parent row: SmBlock summary
            html += `<div class="tree-node">
                <div class="tree-parent" onclick="toggleSmBlockTree('${treeId}','${s.name}')" style="display:flex;align-items:center;padding:10px 12px;background:#1e3a5f;border-radius:4px;margin-bottom:2px;cursor:pointer">
                    <span class="tree-arrow" id="arrow-${treeId}" style="margin-right:10px">${isExpanded ? '▼' : '▶'}</span>
                    <span style="width:120px;font-weight:bold;color:#81d4fa">${s.name}</span>
                    <span style="width:130px;color:#9e9e9e">${s.shape.join(' x ')}</span>
                    <span style="width:80px;color:#9e9e9e">max: ${s.maxsize}</span>
                    <span style="width:150px">
                        <div style="display:flex;align-items:center;gap:8px">
                            <div class="cpu-bar" style="flex:1;height:14px">
                                <div class="cpu-bar-fill" style="width:${s.usage_pct}%;background:${barColor}"></div>
                            </div>
                            <span class="num ${usageClass}" style="width:45px">${s.usage_pct}%</span>
                        </div>
                    </span>
                    <span style="width:80px;text-align:right;color:#9e9e9e">${s.total_mb} MB</span>
                </div>
                <div class="tree-children" id="${treeId}" style="margin-left:28px;display:${isExpanded ? 'block' : 'none'}">
                    <div style="display:flex;padding:6px 12px;border-bottom:1px solid #333">
                        <span style="width:100px;color:#ffcc80">Shape</span>
                        <span style="color:#e0e0e0">${s.shape.join(' x ')} (H x W x C)</span>
                    </div>
                    <div style="display:flex;padding:6px 12px;border-bottom:1px solid #333">
                        <span style="width:100px;color:#ffcc80">MaxSize</span>
                        <span style="color:#e0e0e0">${s.maxsize} blocks</span>
                    </div>
                    <div style="display:flex;padding:6px 12px;border-bottom:1px solid #333">
                        <span style="width:100px;color:#ffcc80">Item Size</span>
                        <span style="color:#e0e0e0">${s.item_size.toLocaleString()} bytes (${(s.item_size / 1024 / 1024).toFixed(2)} MB)</span>
                    </div>
                    <div style="display:flex;padding:6px 12px;border-bottom:1px solid #333">
                        <span style="width:100px;color:#ffcc80">Total Size</span>
                        <span style="color:#e0e0e0">${s.total_mb} MB</span>
                    </div>
                    <div style="display:flex;padding:6px 12px;border-bottom:1px solid #333">
                        <span style="width:100px;color:#ffcc80">Used/Free</span>
                        <span><span style="color:#f44336">${s.used}</span> / <span style="color:#4caf50">${s.free}</span></span>
                    </div>
                    <div style="display:flex;padding:6px 12px;border-bottom:1px solid #333">
                        <span style="width:100px;color:#ffcc80">Users</span>
                        <span style="color:#64b5f6">${users.length > 0 ? users.join(', ') : '(none)'}</span>
                    </div>
                </div>
            </div>`;
        });

        treeEl.innerHTML = html;
    }).catch(err => {
        const treeEl = document.getElementById('smblock-tree');
        if (treeEl) treeEl.innerHTML = `<div style="color:#f44336">Error: ${err.message}</div>`;
    });
}

function toggleSmBlockTree(treeId, name) {
    const el = document.getElementById(treeId);
    const arrow = document.getElementById('arrow-' + treeId);
    const isHidden = el.style.display === 'none';
    el.style.display = isHidden ? 'block' : 'none';
    arrow.textContent = isHidden ? '▼' : '▶';
    localStorage.setItem(`smblock-${name}`, isHidden ? 'true' : 'false');
}

function loadAppInfoData() {
    fetch('/api/gconfig').then(r => r.json()).then(data => {
        const el = document.getElementById('appinfo-content');
        if (!el) return;
        const info = data.data?.app_info || {};
        const name = info.name || '-';
        const version = info.version || '-';
        const id = info.id || '-';
        const filepath = data.filepath || '-';
        const homeDir = data.home_dir || '-';
        const logDir = data.log_dir || '-';

        const cpuVal = systemCpuData.cpu ? systemCpuData.cpu.toFixed(1) : '-';
        const memVal = systemCpuData.mem ? systemCpuData.mem.toFixed(1) : '-';
        el.innerHTML = `
            <div style="display:flex;flex-wrap:wrap;gap:20px;font-size:13px;margin-bottom:8px">
                <span><span style="color:#9e9e9e">Name:</span> <strong style="color:#81d4fa">${name}</strong></span>
                <span><span style="color:#9e9e9e">Ver:</span> <span style="color:#e0e0e0">${version}</span></span>
                <span><span style="color:#9e9e9e">ID:</span> <span style="color:#e0e0e0">${id}</span></span>
                <span><span style="color:#9e9e9e">CPU:</span> <strong style="color:#4fc3f7">${cpuVal}</strong>%</span>
                <span><span style="color:#9e9e9e">Memory:</span> <strong style="color:#4fc3f7">${memVal}</strong>%</span>
                <button class="btn-refresh" style="padding:2px 8px;font-size:11px" onclick="loadCpuData()">Refresh</button>
            </div>
            <div style="font-size:12px;color:#9e9e9e;line-height:1.6">
                <div><span style="color:#ffcc80">Config:</span> <span style="color:#666">${filepath}</span></div>
                <div><span style="color:#ffcc80">Home_Dir:</span> <span style="color:#666">${homeDir}</span></div>
                <div><span style="color:#ffcc80">Log_Dir:</span> <span style="color:#666">${logDir}</span></div>
            </div>`;
    }).catch(err => {
        const el = document.getElementById('appinfo-content');
        if (el) el.innerHTML = `<div style="color:#f44336">Error: ${err.message}</div>`;
    });
}

let rmiCallPrev = {};  // taskId:method -> count
let rmiCallCache = null; // cached data for diff check

function loadRmiCallData() {
    fetch('/api/rmi_methods').then(r => r.json()).then(data => {
        document.getElementById('rmicall-loading').style.display = 'none';
        document.getElementById('rmicall-content').style.display = 'block';

        const now = Date.now();
        const elapsed = rmiCallTime > 0 ? (now - rmiCallTime) / 1000 : 0;
        rmiCallTime = now;

        document.getElementById('rmicall-interval').textContent = elapsed > 0 ? `Interval: ${elapsed.toFixed(1)}s` : 'Interval: -';

        let html = '';
        let newPrev = {};

        data.forEach((task, idx) => {
            const methods = Object.entries(task.methods).sort((a, b) => b[1] - a[1]);
            let taskPrevTotal = 0, taskCurrTotal = 0;

            // Calculate totals
            methods.forEach(m => {
                const key = `${task.id}:${m[0]}`;
                const prev = rmiCallPrev[key] || 0;
                taskPrevTotal += prev;
                taskCurrTotal += m[1];
                newPrev[key] = m[1];
            });

            const totalDiff = taskCurrTotal - taskPrevTotal;
            const totalPerSec = elapsed > 0 ? (totalDiff / elapsed) : 0;
            const treeId = `tree-${idx}`;
            const isExpanded = localStorage.getItem(`rmicall-${task.id}`) !== 'false';
            const statusClass = task.alive ? 'running' : 'stopped';
            const statusText = task.alive ? 'running' : 'stopped';

            // Parent node: Task/Class/Mode/Status/Sum
            const hasChildren = methods.length > 0;
            const arrowHtml = hasChildren ? `<span class="tree-arrow" id="arrow-${treeId}" style="margin-right:8px;transition:transform 0.2s">${isExpanded ? '▼' : '▶'}</span>` : `<span style="width:20px"></span>`;
            const clickAttr = hasChildren ? `onclick="toggleTree('${treeId}','${task.id}')" style="display:flex;align-items:center;padding:8px 12px;background:#1e3a5f;border-radius:4px;margin-bottom:2px;cursor:pointer"` : `style="display:flex;align-items:center;padding:8px 12px;background:#1e3a5f;border-radius:4px;margin-bottom:2px"`;

            html += `<div class="tree-node">
                <div class="tree-parent" ${clickAttr}>
                    ${arrowHtml}
                    <span style="flex:1;font-weight:bold">${task.id}</span>
                    <span style="width:120px;color:#9e9e9e">${task.class}</span>
                    <span style="width:70px;color:#9e9e9e">${task.mode || '-'}</span>
                    <span style="width:70px" class="${statusClass}">${statusText}</span>
                    <span style="width:80px;text-align:right;color:#4fc3f7">${taskCurrTotal.toLocaleString()}</span>
                    <span style="width:80px;text-align:right;color:${totalPerSec > 0 ? '#4caf50' : '#666'}">${totalPerSec.toFixed(1)}/s</span>
                </div>`;

            if (hasChildren) {
                const timing = task.methods_timing || {};
                html += `<div class="tree-children" id="${treeId}" style="margin-left:20px;display:${isExpanded ? 'block' : 'none'}">`;
                // Header for method details
                html += `<div style="display:flex;align-items:center;padding:4px 12px;border-bottom:1px solid #444;color:#9e9e9e;font-size:11px">
                    <span style="width:28px"></span>
                    <span style="flex:1">Method</span>
                    <span style="width:60px;text-align:right">Prev</span>
                    <span style="width:60px;text-align:right">Curr</span>
                    <span style="width:60px;text-align:right">Rate</span>
                    <span style="width:140px;text-align:center;color:#ff9800">IPC (min/avg/max)</span>
                    <span style="width:140px;text-align:center;color:#ff9800">FUNC (min/avg/max)</span>
                </div>`;
                methods.forEach(m => {
                    const key = `${task.id}:${m[0]}`;
                    const methodName = m[0];
                    const curr = m[1];
                    const prev = rmiCallPrev[key] || 0;
                    const diff = curr - prev;
                    const perSec = elapsed > 0 ? (diff / elapsed) : 0;

                    const isOneWay = methodName.startsWith('on_');
                    const wayBadge = isOneWay
                        ? '<span style="background:#ff9800;color:#000;padding:1px 5px;border-radius:3px;font-size:10px;margin-right:6px">1W</span>'
                        : '<span style="background:#2196f3;color:#fff;padding:1px 5px;border-radius:3px;font-size:10px;margin-right:6px">2W</span>';

                    // Get timing data for this method
                    const t = timing[methodName];
                    let ipcStr = '-';
                    let funcStr = '-';
                    if (t && t.ipc && t.func) {
                        ipcStr = `${t.ipc.min}/${t.ipc.avg}/${t.ipc.max}`;
                        funcStr = `${t.func.min}/${t.func.avg}/${t.func.max}`;
                    }

                    html += `<div style="display:flex;align-items:center;padding:4px 12px;border-bottom:1px solid #333">
                        ${wayBadge}
                        <span style="flex:1">${methodName}</span>
                        <span style="width:60px;text-align:right;color:#9e9e9e">${prev.toLocaleString()}</span>
                        <span style="width:60px;text-align:right">${curr.toLocaleString()}</span>
                        <span style="width:60px;text-align:right;color:${perSec > 0 ? '#4caf50' : '#666'}">${perSec.toFixed(1)}/s</span>
                        <span style="width:140px;text-align:center;color:#81d4fa">${ipcStr}</span>
                        <span style="width:140px;text-align:center;color:#a5d6a7">${funcStr}</span>
                    </div>`;
                });
                html += `</div>`;
            }
            html += `</div>`;
        });

        rmiCallPrev = newPrev;
        document.getElementById('rmicall-tree').innerHTML = html;
        updateMeasurementStatus();
    });
}

function toggleTree(treeId, taskId) {
    const el = document.getElementById(treeId);
    const arrow = document.getElementById('arrow-' + treeId);
    const isHidden = el.style.display === 'none';
    el.style.display = isHidden ? 'block' : 'none';
    arrow.textContent = isHidden ? '▼' : '▶';
    localStorage.setItem(`rmicall-${taskId}`, isHidden ? 'true' : 'false');
}

function updateMeasurementStatus() {
    fetch('/api/measurement').then(r => r.json()).then(data => {
        const btn = document.getElementById('measurement-toggle');
        const status = document.getElementById('measurement-status');
        if (data.enabled) {
            btn.style.background = '#4caf50';
            status.textContent = 'ON';
        } else {
            btn.style.background = '#f44336';
            status.textContent = 'OFF';
        }
    });
}

function toggleMeasurement() {
    fetch('/api/measurement', {method: 'POST', headers: {'Content-Type': 'application/json'}, body: '{}'})
        .then(r => r.json())
        .then(data => {
            if (data.status === 'ok') {
                updateMeasurementStatus();
            }
        });
}

function loadGConfigData() {
    fetch('/api/gconfig').then(r => r.json()).then(data => {
        document.getElementById('gconfig-loading').style.display = 'none';
        document.getElementById('gconfig-content').style.display = 'block';
        if (data.error) {
            document.getElementById('gconfig-tree').innerHTML = `<p style="color:#f44336">${data.error}</p>`;
            return;
        }
        document.getElementById('gconfig-filepath').textContent = data.filepath || 'No file';
        document.getElementById('gconfig-tree').innerHTML = renderTree(data.data, '');
        // Reset value setter
        selectedPath = null;
        selectedVtype = null;
        document.getElementById('setter-content').innerHTML = '<div class="value-setter-empty">트리에서 값을 선택하세요</div>';
    });
}

function loadSignalsData() {
    fetch('/api/subscriptions').then(r => r.json()).then(data => {
        document.getElementById('signals-loading').style.display = 'none';
        document.getElementById('signals-content').style.display = 'block';
        document.getElementById('signals-mode').textContent = data.mode || 'unknown';
        if (data.error) {
            document.getElementById('signals-subs').innerHTML = `<p style="color:#f44336">${data.error}</p>`;
            return;
        }
        // Render subscriptions
        let subsHtml = '';
        if (data.subscriptions && data.subscriptions.length > 0) {
            data.subscriptions.forEach(sub => {
                const subscriberList = sub.subscribers.map(s => `<span style="color:#81d4fa;padding:2px 6px;background:#1e3a5f;border-radius:3px;margin:2px;display:inline-block">${s}</span>`).join('');
                subsHtml += `<div style="margin-bottom:12px;padding:10px;background:#383838;border-radius:4px;border-left:3px solid #ff9800">
                    <div style="color:#ffcc80;font-weight:bold;margin-bottom:6px">${sub.signal}</div>
                    <div>${subscriberList}</div>
                </div>`;
            });
        } else {
            subsHtml = '<div style="color:#666;font-style:italic">No active subscriptions</div>';
        }
        document.getElementById('signals-subs').innerHTML = subsHtml;
        // Render queues
        let queuesHtml = '';
        if (data.queues && data.queues.length > 0) {
            queuesHtml = data.queues.map(q => `<div style="padding:6px 10px;background:#1e3a5f;border-radius:4px;margin-bottom:4px;color:#81d4fa">${q}</div>`).join('');
        } else {
            queuesHtml = '<div style="color:#666;font-style:italic">No registered queues</div>';
        }
        document.getElementById('signals-queues').innerHTML = queuesHtml;
        // Render stats
        const stats = data.stats || {};
        const statsStatus = document.getElementById('signals-stats-status');
        const clearedAt = document.getElementById('signals-cleared-at');
        statsStatus.textContent = stats.enabled ? 'ON' : 'OFF';
        statsStatus.parentElement.style.background = stats.enabled ? '#4caf50' : '#666';
        if (stats.cleared_at && stats.cleared_at > 0) {
            const clearDate = new Date(stats.cleared_at * 1000);
            clearedAt.textContent = `Cleared: ${clearDate.toLocaleString()}`;
        } else {
            clearedAt.textContent = '';
        }
        let statsHtml = '';
        const signals = stats.signals || {};
        const signalNames = Object.keys(signals);
        if (signalNames.length > 0) {
            signalNames.forEach(name => {
                const s = signals[name];
                const lastTime = s.last_time > 0 ? new Date(s.last_time * 1000).toLocaleString() : '-';
                statsHtml += `<div style="padding:8px 10px;background:#383838;border-radius:4px;margin-bottom:4px;display:flex;justify-content:space-between;align-items:center">
                    <span style="color:#ffcc80;font-weight:bold">${name}</span>
                    <span style="display:flex;gap:16px">
                        <span style="color:#81d4fa">Count: <span style="color:#4caf50;font-weight:bold">${s.count.toLocaleString()}</span></span>
                        <span style="color:#9e9e9e;font-size:11px">Last: ${lastTime}</span>
                    </span>
                </div>`;
            });
        } else {
            statsHtml = '<div style="color:#666;font-style:italic">No signal stats recorded</div>';
        }
        document.getElementById('signals-stats').innerHTML = statsHtml;
    }).catch(err => {
        document.getElementById('signals-loading').textContent = 'Error: ' + err.message;
        document.getElementById('signals-loading').style.color = '#f44336';
    });
}

function toggleSignalStats() {
    fetch('/api/subscriptions/toggle', {method: 'POST'}).then(r => r.json()).then(data => {
        if (data.status === 'ok') {
            loadSignalsData();
        }
    });
}

function clearSignalStats() {
    if (confirm('Clear all signal statistics?')) {
        fetch('/api/subscriptions/clear', {method: 'POST'}).then(r => r.json()).then(data => {
            if (data.status === 'ok') {
                loadSignalsData();
            }
        });
    }
}

function loadSysInfo() {
    fetch('/api/sysinfo').then(r => r.json()).then(data => {
        document.getElementById('sysinfo-loading').style.display = 'none';
        document.getElementById('sysinfo-content').style.display = 'block';
        if (data.error) {
            document.getElementById('sysinfo-system').innerHTML = `<p style="color:#f44336">${data.error}</p>`;
            return;
        }
        // System
        const sys = data.system || {};
        document.getElementById('sysinfo-system').innerHTML = `
            <div style="margin-bottom:6px"><span style="color:#9e9e9e;width:100px;display:inline-block">OS:</span><span style="color:#81d4fa">${sys.os || 'N/A'}</span></div>
            <div style="margin-bottom:6px"><span style="color:#9e9e9e;width:100px;display:inline-block">Hostname:</span><span style="color:#e0e0e0">${sys.hostname || 'N/A'}</span></div>
            <div style="margin-bottom:6px"><span style="color:#9e9e9e;width:100px;display:inline-block">Windows ID:</span><span style="color:#e0e0e0">${sys.windows_id || 'N/A'}</span></div>
            <div><span style="color:#9e9e9e;width:100px;display:inline-block">Uptime:</span><span style="color:#4caf50">${sys.uptime_str || 'N/A'}</span></div>
        `;
        // CPU
        const cpu = data.cpu || {};
        document.getElementById('sysinfo-cpu').innerHTML = `
            <div style="margin-bottom:6px"><span style="color:#9e9e9e">Name:</span> <span style="color:#e0e0e0">${cpu.name || 'N/A'}</span></div>
            <div style="margin-bottom:6px"><span style="color:#9e9e9e">Cores:</span> <span style="color:#81d4fa">${cpu.cores_physical || 0}C / ${cpu.cores_logical || 0}T</span></div>
            <div style="margin-bottom:6px"><span style="color:#9e9e9e">Freq:</span> <span style="color:#e0e0e0">${cpu.frequency_mhz || 0} MHz</span></div>
            <div><span style="color:#9e9e9e">Usage:</span> <span style="color:#ff9800">${(cpu.usage_percent || 0).toFixed(1)}%</span></div>
        `;
        // Memory
        const mem = data.memory || {};
        const memPct = mem.usage_percent || 0;
        const memColor = memPct > 90 ? '#f44336' : memPct > 70 ? '#ff9800' : '#4caf50';
        document.getElementById('sysinfo-memory').innerHTML = `
            <div style="margin-bottom:6px"><span style="color:#9e9e9e">Total:</span> <span style="color:#e0e0e0">${mem.total_gb || 0} GB</span></div>
            <div style="margin-bottom:6px"><span style="color:#9e9e9e">Used:</span> <span style="color:#81d4fa">${mem.used_gb || 0} GB</span></div>
            <div style="margin-bottom:6px"><span style="color:#9e9e9e">Available:</span> <span style="color:#4caf50">${mem.available_gb || 0} GB</span></div>
            <div><span style="color:#9e9e9e">Usage:</span> <span style="color:${memColor}">${memPct.toFixed(1)}%</span></div>
        `;
        // GPU
        const gpus = data.gpu || [];
        if (gpus.length > 0) {
            document.getElementById('sysinfo-gpu').innerHTML = gpus.map(g => `
                <div style="margin-bottom:8px;padding:8px;background:#383838;border-radius:4px">
                    <div style="color:#81d4fa;font-weight:bold">${g.name || 'Unknown'}</div>
                    <div style="color:#9e9e9e;font-size:12px">VRAM: ${g.vram_mb || 0} MB | Driver: ${g.driver_version || 'N/A'}</div>
                </div>
            `).join('');
        } else {
            document.getElementById('sysinfo-gpu').innerHTML = '<div style="color:#666;font-style:italic">No GPU info</div>';
        }
        // Storage
        const storage = data.storage || [];
        if (storage.length > 0) {
            document.getElementById('sysinfo-storage').innerHTML = storage.map(s => {
                const pct = s.usage_percent || 0;
                const color = pct > 95 ? '#f44336' : pct > 80 ? '#ff9800' : '#4caf50';
                return `<div style="display:flex;align-items:center;gap:12px;margin-bottom:6px">
                    <span style="color:#81d4fa;width:50px">${s.device || s.mountpoint}</span>
                    <div style="flex:1;background:#333;height:16px;border-radius:3px;overflow:hidden">
                        <div style="width:${pct}%;height:100%;background:${color}"></div>
                    </div>
                    <span style="color:#e0e0e0;width:160px;text-align:right">${s.used_gb}/${s.total_gb} GB (${pct.toFixed(1)}%)</span>
                </div>`;
            }).join('');
        } else {
            document.getElementById('sysinfo-storage').innerHTML = '<div style="color:#666;font-style:italic">No storage info</div>';
        }
        // Network
        const net = data.network || {};
        const ifaces = net.interfaces || [];
        if (ifaces.length > 0) {
            document.getElementById('sysinfo-network').innerHTML = `<table style="width:100%;font-size:12px">
                <tr style="color:#9e9e9e"><th style="text-align:left">Name</th><th style="text-align:left">IP</th><th style="text-align:left">MAC</th></tr>
                ${ifaces.map(i => `<tr><td style="color:#81d4fa">${i.name}</td><td style="color:#e0e0e0">${i.ip}</td><td style="color:#666">${i.mac}</td></tr>`).join('')}
            </table>`;
        } else {
            document.getElementById('sysinfo-network').innerHTML = '<div style="color:#666;font-style:italic">No network info</div>';
        }
        // ARP
        const arp = net.arp_table || [];
        if (arp.length > 0) {
            document.getElementById('sysinfo-arp').innerHTML = `<table style="width:100%;font-size:12px">
                <tr style="color:#9e9e9e"><th style="text-align:left">IP</th><th style="text-align:left">MAC</th></tr>
                ${arp.slice(0, 20).map(a => `<tr><td style="color:#e0e0e0">${a.ip}</td><td style="color:#666">${a.mac}</td></tr>`).join('')}
            </table>${arp.length > 20 ? `<div style="color:#666;font-size:11px">... and ${arp.length - 20} more</div>` : ''}`;
        } else {
            document.getElementById('sysinfo-arp').innerHTML = '<div style="color:#666;font-style:italic">No ARP entries</div>';
        }
        // Routing
        const routes = net.routing_table || [];
        if (routes.length > 0) {
            document.getElementById('sysinfo-routing').innerHTML = `<table style="width:100%;font-size:12px">
                <tr style="color:#9e9e9e"><th style="text-align:left">Dest</th><th style="text-align:left">Gateway</th><th style="text-align:left">Metric</th></tr>
                ${routes.slice(0, 15).map(r => `<tr><td style="color:#e0e0e0">${r.destination}</td><td style="color:#81d4fa">${r.gateway}</td><td style="color:#666">${r.metric || '-'}</td></tr>`).join('')}
            </table>${routes.length > 15 ? `<div style="color:#666;font-size:11px">... and ${routes.length - 15} more</div>` : ''}`;
        } else {
            document.getElementById('sysinfo-routing').innerHTML = '<div style="color:#666;font-style:italic">No routing entries</div>';
        }
    }).catch(err => {
        document.getElementById('sysinfo-loading').style.display = 'none';
        document.getElementById('sysinfo-content').style.display = 'block';
        document.getElementById('sysinfo-system').innerHTML = `<p style="color:#f44336">Error: ${err.message}</p>`;
    });
    // Load memory graph after sysinfo
    loadMemoryGraph();
}

function loadMemoryGraph() {
    fetch('/api/sysinfo/memory_history').then(r => r.json()).then(data => {
        const history = data.history || [];
        if (history.length < 2) {
            document.getElementById('memory-current').textContent = 'Current: Collecting data...';
            document.getElementById('memory-trend').textContent = 'Trend: Need more data';
            return;
        }

        const canvas = document.getElementById('memory-graph');
        const ctx = canvas.getContext('2d');
        const width = canvas.width;
        const height = canvas.height;

        // Clear canvas
        ctx.fillStyle = '#1a1a1a';
        ctx.fillRect(0, 0, width, height);

        // Calculate min/max for scaling
        const values = history.map(h => h.used_gb);
        const minVal = Math.min(...values);
        const maxVal = Math.max(...values);
        const range = maxVal - minVal || 1;
        const padding = range * 0.1;
        const yMin = Math.max(0, minVal - padding);
        const yMax = maxVal + padding;
        const yRange = yMax - yMin;

        // Draw grid lines
        ctx.strokeStyle = '#333';
        ctx.lineWidth = 1;
        for (let i = 0; i <= 4; i++) {
            const y = height - (i / 4) * height;
            ctx.beginPath();
            ctx.moveTo(0, y);
            ctx.lineTo(width, y);
            ctx.stroke();
        }

        // Draw memory usage line
        ctx.strokeStyle = '#2196f3';
        ctx.lineWidth = 2;
        ctx.beginPath();
        history.forEach((point, i) => {
            const x = (i / (history.length - 1)) * width;
            const y = height - ((point.used_gb - yMin) / yRange) * height;
            if (i === 0) ctx.moveTo(x, y);
            else ctx.lineTo(x, y);
        });
        ctx.stroke();

        // Draw trend line (linear regression)
        const n = history.length;
        let sumX = 0, sumY = 0, sumXY = 0, sumX2 = 0;
        history.forEach((p, i) => {
            sumX += i;
            sumY += p.used_gb;
            sumXY += i * p.used_gb;
            sumX2 += i * i;
        });
        const slope = (n * sumXY - sumX * sumY) / (n * sumX2 - sumX * sumX);
        const intercept = (sumY - slope * sumX) / n;

        // Draw trend line
        ctx.strokeStyle = slope > 0.001 ? '#ff5722' : slope < -0.001 ? '#4caf50' : '#9e9e9e';
        ctx.lineWidth = 1;
        ctx.setLineDash([5, 5]);
        ctx.beginPath();
        const y0 = height - ((intercept - yMin) / yRange) * height;
        const y1 = height - ((slope * (n - 1) + intercept - yMin) / yRange) * height;
        ctx.moveTo(0, y0);
        ctx.lineTo(width, y1);
        ctx.stroke();
        ctx.setLineDash([]);

        // Update status text
        const latest = history[history.length - 1];
        const first = history[0];
        document.getElementById('memory-current').textContent = 'Current: ' + latest.used_gb.toFixed(2) + ' GB (' + latest.percent.toFixed(1) + '%)';

        // slope is GB per sample (1 sample = 1 minute), convert to MB/hour
        const slopePerHour = slope * 60 * 1024;  // MB per hour
        let trendText = 'Trend: ';
        let trendColor = '#9e9e9e';
        if (slopePerHour > 10) {  // More than 10 MB/hour increase
            trendText += '+' + slopePerHour.toFixed(1) + ' MB/h';
            trendColor = '#ff5722';
        } else if (slopePerHour < -10) {
            trendText += slopePerHour.toFixed(1) + ' MB/h';
            trendColor = '#4caf50';
        } else {
            trendText += 'Stable (' + (slopePerHour >= 0 ? '+' : '') + slopePerHour.toFixed(1) + ' MB/h)';
            trendColor = '#4caf50';
        }
        document.getElementById('memory-trend').innerHTML = '<span style="color:' + trendColor + '">' + trendText + '</span>';

        // Warning for potential leak (more than 100 MB/hour sustained increase)
        const warningEl = document.getElementById('memory-warning');
        if (slopePerHour > 100) {
            warningEl.textContent = '⚠️ Potential memory leak detected! (+' + slopePerHour.toFixed(0) + ' MB/h)';
            warningEl.style.display = 'inline';
        } else {
            warningEl.style.display = 'none';
        }

        // Time labels (show date and time for 72-hour window)
        const startTime = new Date(first.ts * 1000);
        const endTime = new Date(latest.ts * 1000);
        const formatDateTime = (d) => {
            const mm = String(d.getMonth() + 1).padStart(2, '0');
            const dd = String(d.getDate()).padStart(2, '0');
            const hh = String(d.getHours()).padStart(2, '0');
            const mi = String(d.getMinutes()).padStart(2, '0');
            return mm + '/' + dd + ' ' + hh + ':' + mi;
        };
        document.getElementById('memory-time-start').textContent = formatDateTime(startTime);
        document.getElementById('memory-time-end').textContent = formatDateTime(endTime);

        // Draw Y-axis labels
        ctx.fillStyle = '#666';
        ctx.font = '10px monospace';
        ctx.textAlign = 'left';
        for (let i = 0; i <= 4; i++) {
            const val = yMin + (i / 4) * yRange;
            const y = height - (i / 4) * height;
            ctx.fillText(val.toFixed(2) + ' GB', 4, y - 2);
        }

        // Show stats (duration and data points)
        const durationMin = Math.round((latest.ts - first.ts) / 60);
        const durationHours = (durationMin / 60).toFixed(1);
        const maxPoints = data.max_points || 4320;
        document.getElementById('memory-stats').textContent =
            'Duration: ' + durationHours + 'h | Points: ' + history.length + '/' + maxPoints;
    }).catch(err => {
        console.error('Memory graph error:', err);
    });
}

let extLinkConfig = {};
function loadExtLink() {
    fetch('/api/external').then(r => r.json()).then(data => {
        document.getElementById('extlink-loading').style.display = 'none';
        document.getElementById('extlink-content').style.display = 'block';
        extLinkConfig = data;
        if (data.error) {
            document.getElementById('extlink-slack').innerHTML = `<p style="color:#f44336">${data.error}</p>`;
            return;
        }
        // Slack
        const slack = data.slack || {};
        document.getElementById('extlink-slack').innerHTML = `
            <div style="margin-bottom:8px"><label><input type="checkbox" id="slack-enabled" ${slack.enabled ? 'checked' : ''}> Enabled</label>
            <button class="btn-refresh" style="margin-left:12px;padding:2px 8px;font-size:11px" onclick="testExtLink('slack')">Test</button></div>
            <div style="margin-bottom:6px"><span style="color:#9e9e9e;display:block;margin-bottom:2px">Webhook URL:</span><input type="text" id="slack-url" value="${slack.webhook_url || ''}" style="width:100%;padding:4px;background:#333;border:1px solid #555;color:#e0e0e0;border-radius:3px"></div>
            <div style="margin-bottom:6px"><span style="color:#9e9e9e;display:block;margin-bottom:2px">Channel:</span><input type="text" id="slack-channel" value="${slack.channel || ''}" style="width:100%;padding:4px;background:#333;border:1px solid #555;color:#e0e0e0;border-radius:3px"></div>
            <div><span style="color:#9e9e9e;display:block;margin-bottom:2px">Username:</span><input type="text" id="slack-username" value="${slack.username || 'Alaska Monitor'}" style="width:100%;padding:4px;background:#333;border:1px solid #555;color:#e0e0e0;border-radius:3px"></div>
        `;
        // Email
        const email = data.email || {};
        document.getElementById('extlink-email').innerHTML = `
            <div style="margin-bottom:8px"><label><input type="checkbox" id="email-enabled" ${email.enabled ? 'checked' : ''}> Enabled</label>
            <button class="btn-refresh" style="margin-left:12px;padding:2px 8px;font-size:11px" onclick="testExtLink('email')">Test</button></div>
            <div style="display:flex;gap:8px;margin-bottom:6px">
                <div style="flex:2"><span style="color:#9e9e9e;display:block;margin-bottom:2px">SMTP Host:</span><input type="text" id="email-host" value="${email.smtp_host || ''}" style="width:100%;padding:4px;background:#333;border:1px solid #555;color:#e0e0e0;border-radius:3px"></div>
                <div style="flex:1"><span style="color:#9e9e9e;display:block;margin-bottom:2px">Port:</span><input type="number" id="email-port" value="${email.smtp_port || 587}" style="width:100%;padding:4px;background:#333;border:1px solid #555;color:#e0e0e0;border-radius:3px"></div>
            </div>
            <div style="margin-bottom:6px"><span style="color:#9e9e9e;display:block;margin-bottom:2px">User:</span><input type="text" id="email-user" value="${email.smtp_user || ''}" style="width:100%;padding:4px;background:#333;border:1px solid #555;color:#e0e0e0;border-radius:3px"></div>
            <div style="margin-bottom:6px"><span style="color:#9e9e9e;display:block;margin-bottom:2px">To Addrs:</span><input type="text" id="email-to" value="${(email.to_addrs || []).join(', ')}" style="width:100%;padding:4px;background:#333;border:1px solid #555;color:#e0e0e0;border-radius:3px"></div>
        `;
        // Webhook
        const wh = data.webhook || {};
        document.getElementById('extlink-webhook').innerHTML = `
            <div style="margin-bottom:8px"><label><input type="checkbox" id="wh-enabled" ${wh.enabled ? 'checked' : ''}> Enabled</label>
            <button class="btn-refresh" style="margin-left:12px;padding:2px 8px;font-size:11px" onclick="testExtLink('webhook')">Test</button></div>
            <div style="margin-bottom:6px"><span style="color:#9e9e9e;display:block;margin-bottom:2px">URL:</span><input type="text" id="wh-url" value="${wh.url || ''}" style="width:100%;padding:4px;background:#333;border:1px solid #555;color:#e0e0e0;border-radius:3px"></div>
            <div style="display:flex;gap:8px;margin-bottom:6px">
                <div style="flex:1"><span style="color:#9e9e9e;display:block;margin-bottom:2px">Method:</span><select id="wh-method" style="width:100%;padding:4px;background:#333;border:1px solid #555;color:#e0e0e0;border-radius:3px"><option ${wh.method === 'POST' ? 'selected' : ''}>POST</option><option ${wh.method === 'PUT' ? 'selected' : ''}>PUT</option></select></div>
                <div style="flex:1"><span style="color:#9e9e9e;display:block;margin-bottom:2px">Timeout:</span><input type="number" id="wh-timeout" value="${wh.timeout || 10}" style="width:100%;padding:4px;background:#333;border:1px solid #555;color:#e0e0e0;border-radius:3px"></div>
            </div>
        `;
        // Health Report
        const hr = data.health_report || {};
        document.getElementById('extlink-report').innerHTML = `
            <div style="margin-bottom:8px"><label><input type="checkbox" id="hr-enabled" ${hr.enabled ? 'checked' : ''}> Enabled</label>
            <button class="btn-refresh" style="margin-left:12px;padding:2px 8px;font-size:11px" onclick="sendReport()">Send Now</button></div>
            <div style="margin-bottom:6px"><span style="color:#9e9e9e;display:block;margin-bottom:2px">Schedule (HH:MM, comma separated):</span><input type="text" id="hr-schedule" value="${(hr.schedule || []).join(', ')}" style="width:100%;padding:4px;background:#333;border:1px solid #555;color:#e0e0e0;border-radius:3px"></div>
            <div><span style="color:#9e9e9e;display:block;margin-bottom:2px">Targets:</span>
                <label style="margin-right:12px"><input type="checkbox" id="hr-slack" ${(hr.targets || []).includes('slack') ? 'checked' : ''}> Slack</label>
                <label style="margin-right:12px"><input type="checkbox" id="hr-email" ${(hr.targets || []).includes('email') ? 'checked' : ''}> Email</label>
                <label><input type="checkbox" id="hr-webhook" ${(hr.targets || []).includes('webhook') ? 'checked' : ''}> Webhook</label>
            </div>
        `;
        // Alert Settings
        const alert = data.alert || {};
        document.getElementById('extlink-alert').innerHTML = `
            <div style="display:flex;flex-wrap:wrap;gap:16px">
                <label style="display:flex;align-items:center;gap:6px"><input type="checkbox" id="alert-task-restart" ${alert.on_task_restart ? 'checked' : ''}> On Task Restart</label>
                <label style="display:flex;align-items:center;gap:6px"><input type="checkbox" id="alert-signal-error" ${alert.on_signal_error ? 'checked' : ''}> On Signal Error</label>
                <div style="display:flex;align-items:center;gap:6px">
                    <label>On RMI Fail &gt;</label>
                    <input type="number" id="alert-rmi-threshold" value="${alert.on_rmi_fail_threshold || 0}" style="width:60px;padding:4px;background:#333;border:1px solid #555;color:#e0e0e0;border-radius:3px">
                    <span style="color:#9e9e9e">times</span>
                </div>
            </div>
        `;
    }).catch(err => {
        document.getElementById('extlink-loading').style.display = 'none';
        document.getElementById('extlink-content').style.display = 'block';
        document.getElementById('extlink-slack').innerHTML = `<p style="color:#f44336">Error: ${err.message}</p>`;
    });
}

function saveExtLink() {
    const config = {
        slack: {
            enabled: document.getElementById('slack-enabled').checked,
            webhook_url: document.getElementById('slack-url').value,
            channel: document.getElementById('slack-channel').value,
            username: document.getElementById('slack-username').value
        },
        email: {
            enabled: document.getElementById('email-enabled').checked,
            smtp_host: document.getElementById('email-host').value,
            smtp_port: parseInt(document.getElementById('email-port').value) || 587,
            smtp_user: document.getElementById('email-user').value,
            to_addrs: document.getElementById('email-to').value.split(',').map(s => s.trim()).filter(s => s)
        },
        webhook: {
            enabled: document.getElementById('wh-enabled').checked,
            url: document.getElementById('wh-url').value,
            method: document.getElementById('wh-method').value,
            timeout: parseInt(document.getElementById('wh-timeout').value) || 10
        },
        health_report: {
            enabled: document.getElementById('hr-enabled').checked,
            schedule: document.getElementById('hr-schedule').value.split(',').map(s => s.trim()).filter(s => s),
            targets: ['slack', 'email', 'webhook'].filter(t => document.getElementById('hr-' + t).checked)
        },
        alert: {
            on_task_restart: document.getElementById('alert-task-restart').checked,
            on_signal_error: document.getElementById('alert-signal-error').checked,
            on_rmi_fail_threshold: parseInt(document.getElementById('alert-rmi-threshold').value) || 0
        }
    };
    fetch('/api/external', {
        method: 'POST',
        headers: {'Content-Type': 'application/json'},
        body: JSON.stringify(config)
    }).then(r => r.json()).then(data => {
        alert(data.status === 'ok' ? 'Saved!' : 'Save failed: ' + (data.message || 'Unknown error'));
        if (data.status === 'ok') loadExtLink();
    });
}

function testExtLink(target) {
    fetch('/api/external/test/' + target, {method: 'POST'}).then(r => r.json()).then(data => {
        alert(data.success ? 'Test successful!' : 'Test failed: ' + (data.error || 'Unknown error'));
    });
}

function sendReport() {
    const targets = ['slack', 'email', 'webhook'].filter(t => document.getElementById('hr-' + t).checked);
    fetch('/api/external/report', {
        method: 'POST',
        headers: {'Content-Type': 'application/json'},
        body: JSON.stringify({targets: targets})
    }).then(r => r.json()).then(data => {
        if (data.status === 'ok') {
            const results = data.results || {};
            const msg = Object.entries(results).map(([k, v]) => `${k}: ${v ? 'OK' : 'Failed'}`).join(', ');
            alert('Report sent! ' + msg);
        } else {
            alert('Send failed: ' + (data.message || 'Unknown error'));
        }
    });
}

function showExtTab(tab) {
    document.querySelectorAll('.extlink-tab').forEach(t => {
        t.style.background = '#444';
        t.style.color = '#9e9e9e';
        t.classList.remove('active');
    });
    document.querySelectorAll('.extlink-tab-content').forEach(c => c.style.display = 'none');
    const btn = document.querySelector(`.extlink-tab[data-tab="${tab}"]`);
    if (btn) {
        btn.style.background = '#0288d1';
        btn.style.color = '#fff';
        btn.classList.add('active');
    }
    const content = document.getElementById('extlink-tab-' + tab);
    if (content) content.style.display = 'block';
}

function renderTree(obj, path, depth=0) {
    const keyClass = `tree-key tree-key-${Math.min(depth, 4)}`;
    if (obj === null) return `<span class="tree-val-null tree-val-edit" onclick="editGConfig('${path}', null, 'null')">null</span>`;
    if (typeof obj === 'boolean') return `<span class="tree-val-bool tree-val-edit" onclick="editGConfig('${path}', ${obj}, 'bool')">${obj}</span>`;
    if (typeof obj === 'number') return `<span class="tree-val-num tree-val-edit" onclick="editGConfig('${path}', ${obj}, 'num')">${obj}</span>`;
    if (typeof obj === 'string') return `<span class="tree-val-str tree-val-edit" onclick="editGConfig('${path}', '${escapeHtml(obj).replace(/'/g, "\\'")}', 'str')">"${escapeHtml(obj)}"</span>`;
    if (Array.isArray(obj)) {
        if (obj.length === 0) return '<span class="tree-bracket">[]</span>';
        const id = 'tree-' + Math.random().toString(36).substr(2, 9);
        let html = `<span class="tree-expanded" id="${id}"><span class="tree-toggle" onclick="toggleTree('${id}')" title="Click to collapse"></span><span class="tree-bracket">[</span>`;
        obj.forEach((v, i) => {
            const childPath = path ? `${path}[${i}]` : `[${i}]`;
            html += `<div class="tree-node"><span class="${keyClass}">${i}</span>: ${renderTree(v, childPath, depth+1)}${i < obj.length - 1 ? ',' : ''}</div>`;
        });
        html += '<span class="tree-bracket">]</span></span>';
        return html;
    }
    if (typeof obj === 'object') {
        const keys = Object.keys(obj);
        if (keys.length === 0) return '<span class="tree-bracket">{}</span>';
        const id = 'tree-' + Math.random().toString(36).substr(2, 9);
        let html = `<span class="tree-expanded" id="${id}"><span class="tree-toggle" onclick="toggleTree('${id}')" title="Click to collapse"></span><span class="tree-bracket">{</span>`;
        keys.forEach((k, i) => {
            const childPath = path ? `${path}.${k}` : k;
            html += `<div class="tree-node"><span class="${keyClass}">"${escapeHtml(k)}"</span>: ${renderTree(obj[k], childPath, depth+1)}${i < keys.length - 1 ? ',' : ''}</div>`;
        });
        html += '<span class="tree-bracket">}</span></span>';
        return html;
    }
    return String(obj);
}

function formatPath(path) {
    // Convert "a.b.c" to "(a).(b).(c)"
    return path.split('.').map(p => `(${p})`).join('.');
}

function editGConfig(path, value, vtype) {
    // Remove previous selection highlight
    document.querySelectorAll('.tree-val-edit.selected').forEach(el => el.classList.remove('selected'));
    // Highlight current selection
    event.target.classList.add('selected');

    selectedPath = path;
    selectedVtype = vtype;
    const displayVal = vtype === 'str' ? value : (value === null ? '' : value);
    const typeLabel = {'str': 'String', 'num': 'Number', 'bool': 'Boolean', 'null': 'Null'}[vtype] || vtype;
    const displayPath = formatPath(path);

    // Check if this is a task_config path (RMI will be triggered)
    const parts = path.split('.');
    const isTaskConfig = path.startsWith('task_config.') && parts.length >= 3 && parts[1] !== '_monitor';
    const rmiNotice = isTaskConfig ? '<div class="setter-rmi-notice">📡 task_config 변경 시 실행중인 프로세스 변수도 RMI로 함께 변경됩니다</div>' : '';
    const btnText = isTaskConfig ? '파일 + RMI 변경' : '변경';

    document.getElementById('setter-content').innerHTML = `
        ${rmiNotice}
        <div class="setter-row">
            <div class="setter-label">Path (data_id)</div>
            <div class="setter-path">${escapeHtml(displayPath)}</div>
        </div>
        <div class="setter-row">
            <div class="setter-label">Value</div>
            <input type="text" class="setter-input" id="setter-value" value="${displayVal}" onkeydown="if(event.key==='Enter')applySetterValue();">
            <div class="setter-type">Type: ${typeLabel}</div>
        </div>
        <button class="btn-change" onclick="applySetterValue()">${btnText}</button>
        <div id="setter-msg"></div>
    `;
    document.getElementById('setter-value').focus();
    document.getElementById('setter-value').select();
}

function applySetterValue() {
    if (!selectedPath) return;
    const inputEl = document.getElementById('setter-value');
    let value = inputEl.value;
    if (selectedVtype === 'null' && value === '') value = 'null';
    const msgEl = document.getElementById('setter-msg');

    // 1. Save to gconfig file
    fetch('/api/gconfig', {
        method: 'POST',
        headers: {'Content-Type': 'application/json'},
        body: JSON.stringify({path: selectedPath, value: value, vtype: selectedVtype})
    }).then(r => r.json()).then(res => {
        if (res.status === 'ok') {
            // 2. If task_config path, also update running process via RMI
            if (selectedPath.startsWith('task_config.')) {
                const parts = selectedPath.split('.');
                // task_config.process1/aaa.counter -> taskId=process1, key=counter
                // Note: TaskManager uses "process1/aaa".split('/')[0] as task_id
                if (parts.length >= 3) {
                    const taskId = parts[1].split('/')[0];
                    const key = parts.slice(2).join('.');
                    // Skip _monitor section
                    if (taskId !== '_monitor') {
                        fetch('/api/config', {
                            method: 'POST',
                            headers: {'Content-Type': 'application/json'},
                            body: JSON.stringify({task_id: taskId, key: key, value: value})
                        }).then(r => r.json()).then(rmiRes => {
                            if (rmiRes.status === 'ok') {
                                msgEl.className = 'setter-msg setter-msg-ok';
                                msgEl.textContent = '파일+프로세스 변경 완료!';
                            } else {
                                msgEl.className = 'setter-msg setter-msg-ok';
                                msgEl.textContent = '파일 변경 완료 (RMI: ' + (rmiRes.message || 'error') + ')';
                            }
                            setTimeout(() => loadGConfigData(), 500);
                        });
                        return;
                    }
                }
            }
            msgEl.className = 'setter-msg setter-msg-ok';
            msgEl.textContent = '변경 완료!';
            setTimeout(() => loadGConfigData(), 500);
        } else {
            msgEl.className = 'setter-msg setter-msg-err';
            msgEl.textContent = res.message || 'Error';
        }
    });
}

function saveGConfig(path, vtype, inputEl) {
    let value = inputEl.value;
    if (vtype === 'null' && value === '') value = 'null';
    fetch('/api/gconfig', {
        method: 'POST',
        headers: {'Content-Type': 'application/json'},
        body: JSON.stringify({path: path, value: value})
    }).then(r => r.json()).then(res => {
        if (res.status === 'ok') {
            loadGConfigData();
        } else {
            const msgEl = document.getElementById('msg-' + path.replace(/\\./g, '-'));
            if (msgEl) { msgEl.textContent = res.message || 'Error'; msgEl.style.color = '#f44336'; }
        }
    });
}

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

function toggleTree(id) {
    const el = document.getElementById(id);
    if (el.classList.contains('tree-expanded')) {
        el.classList.remove('tree-expanded');
        el.classList.add('tree-collapsed');
    } else {
        el.classList.remove('tree-collapsed');
        el.classList.add('tree-expanded');
    }
}

function expandAllTree() {
    document.querySelectorAll('.tree-collapsed').forEach(el => {
        el.classList.remove('tree-collapsed');
        el.classList.add('tree-expanded');
    });
}

function collapseAllTree() {
    document.querySelectorAll('.tree-expanded').forEach(el => {
        el.classList.remove('tree-expanded');
        el.classList.add('tree-collapsed');
    });
}

// Logs functions
function loadLogs() {
    const level = document.getElementById('log-level').value;
    const task = document.getElementById('log-task').value;
    const count = document.getElementById('log-count').value;
    let url = `/api/logs?count=${count}`;
    if (level) url += `&level=${level}`;
    if (task) url += `&task=${task}`;

    fetch(url).then(r => r.json()).then(data => {
        document.getElementById('logs-loading').style.display = 'none';
        document.getElementById('logs-content').style.display = 'block';

        if (data.error) {
            document.getElementById('logs-body').innerHTML = `<tr><td colspan="4" style="color:#f44336">${data.error}</td></tr>`;
            return;
        }

        const logs = data.logs || [];
        if (logs.length === 0) {
            document.getElementById('logs-body').innerHTML = `<tr><td colspan="4" style="color:#9e9e9e;text-align:center">No logs found</td></tr>`;
            return;
        }

        // Update task dropdown
        const tasks = new Set();
        logs.forEach(log => tasks.add(log.task));
        const taskSelect = document.getElementById('log-task');
        const currentTask = taskSelect.value;
        const newTasks = Array.from(tasks).sort();
        if (JSON.stringify(newTasks) !== JSON.stringify(logsTaskList)) {
            logsTaskList = newTasks;
            const escapeHtml = s => (s || '').replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;');
            taskSelect.innerHTML = '<option value="">All Tasks</option>' + newTasks.map(t => '<option value="' + escapeHtml(t) + '"' + (t === currentTask ? ' selected' : '') + '>' + escapeHtml(t) + '</option>').join('');
        }

        // Level colors
        const levelColors = {
            'DEBUG': '#9e9e9e',
            'INFO': '#4caf50',
            'WARNING': '#ff9800',
            'ERROR': '#f44336',
            'CRITICAL': '#9c27b0'
        };

        let html = '';
        logs.forEach(log => {
            const ts = new Date(log.ts * 1000);
            const timeStr = ts.toLocaleString('sv-SE', {year:'numeric',month:'2-digit',day:'2-digit',hour:'2-digit',minute:'2-digit',second:'2-digit'}).replace(' ', ' ') + '.' + String(ts.getMilliseconds()).padStart(3, '0');
            const levelColor = levelColors[log.lv] || '#e0e0e0';
            const msg = log.msg || '';
            const msgEscaped = msg.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;');
            const msgHtml = msgEscaped.replace(/\\\\n/g, '<br>').replace(/\\n/g, '<br>');
            html += '<tr>' +
                '<td style="font-family:monospace;white-space:nowrap;overflow:hidden;text-overflow:ellipsis">' + timeStr + '</td>' +
                '<td style="color:' + levelColor + ';font-weight:bold;text-align:center">' + log.lv + '</td>' +
                '<td style="color:#81d4fa;overflow:hidden;text-overflow:ellipsis">' + log.task + '</td>' +
                '<td style="font-family:monospace;white-space:pre-wrap;word-break:break-word;overflow-wrap:break-word">' + msgHtml + '</td>' +
            '</tr>';
        });
        document.getElementById('logs-body').innerHTML = html;
    }).catch(err => {
        document.getElementById('logs-loading').textContent = 'Error: ' + err.message;
        document.getElementById('logs-loading').style.color = '#f44336';
    });
}

function startLogsTimer() {
    if (logsTimer) clearInterval(logsTimer);
    if (document.getElementById('log-auto').checked) {
        logsTimer = setInterval(loadLogs, 3000);
    }
}

document.getElementById('log-auto').addEventListener('change', startLogsTimer);
document.getElementById('log-level').addEventListener('change', loadLogs);
document.getElementById('log-task').addEventListener('change', loadLogs);
document.getElementById('log-count').addEventListener('change', loadLogs);
</script>
</body></html>'''
