/**
 * 관심종목 관리 + WebSocket 실시간 시세 + 10초 폴링 분석
 * - 서버 API (/api/watchlist)에 종목 저장
 * - WS 구독으로 1초마다 현재가/등락률/체결강도 + 미니 차트 갱신
 * - 10초마다 /api/watchlist-signal 폴링으로 복합 분석 뱃지 갱신
 */
(function () {
    const MAX_HISTORY  = 60; // 체결강도 히스토리 최대 포인트
    const SIGNAL_INTERVAL = 10 * 1000; // 10초 폴링

    // 종목별 체결강도 히스토리 (카드 재렌더링 시에도 유지)
    const cntrHistory = new Map(); // code → number[]

    // --- 서버 API 헬퍼 ---
    // 메모리 캐시 (서버 동기화용)
    let cachedList = [];

    async function loadFromServer() {
        try {
            const resp = await fetch('/api/watchlist');
            if (!resp.ok) return [];
            const list = await resp.json();
            cachedList = Array.isArray(list) ? list : [];
            return cachedList;
        } catch {
            return cachedList;
        }
    }

    function loadList() {
        return cachedList;
    }

    async function addToServer(code, name) {
        const resp = await fetch('/api/watchlist', {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify({ code, name }),
        });
        if (!resp.ok) {
            const err = await resp.json().catch(() => ({}));
            throw new Error(err.error || '추가 실패');
        }
        cachedList.push({ code, name });
    }

    async function removeFromServer(code) {
        await fetch(`/api/watchlist/${code}`, { method: 'DELETE' });
        cachedList = cachedList.filter(s => s.code !== code);
    }

    // --- DOM 요소 ---
    const input      = document.getElementById('watchlistInput');
    const addBtn     = document.getElementById('watchlistAddBtn');
    const msgEl      = document.getElementById('watchlistMsg');
    const sidebarEl  = document.getElementById('watchlistSidebar');
    const emptyEl    = document.getElementById('watchlistEmpty');
    const panelEl    = document.getElementById('watchlistPanel');
    const panelEmpty = document.getElementById('watchlistPanelEmpty');
    const wsStatusEl = document.getElementById('wsStatus');

    // --- WS 상태 표시 ---
    function setWsStatus(text, color) {
        if (!wsStatusEl) return;
        wsStatusEl.textContent = text;
        wsStatusEl.className = `text-xs font-normal ml-1 ${color}`;
    }

    // ─────────────────────────────────────────────
    // 포맷 유틸
    // ─────────────────────────────────────────────
    function fmtNum(n) {
        if (n == null) return '-';
        return Math.abs(n).toLocaleString('ko-KR');
    }
    function fmtRate(f) {
        if (f == null) return '-';
        return (f >= 0 ? '+' : '') + f.toFixed(2) + '%';
    }
    function fmtCntr(f) {
        return f ? f.toFixed(2) : '-';
    }
    function rateClass(f) {
        if (f > 0) return 'text-red-500';
        if (f < 0) return 'text-blue-500';
        return 'text-gray-500';
    }
    function rateBadgeClass(f) {
        if (f > 0) return 'bg-red-50 text-red-500';
        if (f < 0) return 'bg-blue-50 text-blue-500';
        return 'bg-gray-100 text-gray-500';
    }

    // ─────────────────────────────────────────────
    // 뱃지 HTML 생성 함수 (signal.js와 동일)
    // ─────────────────────────────────────────────
    function signalTypeBadge(s) {
        if (!s.signalType) return '';
        const map = {
            '강한매수': 'bg-red-600 text-white border-red-700',
            '매수우세': 'bg-orange-400 text-white border-orange-500',
            '물량소화': 'bg-yellow-100 text-yellow-700 border-yellow-300',
            '추격위험': 'bg-gray-800 text-white border-gray-900',
            '약한상승': 'bg-gray-100 text-gray-500 border-gray-300',
        };
        const cls = map[s.signalType] || 'bg-gray-100 text-gray-500';
        return `<span class="border ${cls} text-xs px-1.5 py-0.5 rounded font-bold">${s.signalType}</span>`;
    }

    function riseProbBadge(s) {
        if (!s.riseLabel) return '';
        const isVeryHigh = s.riseLabel === '매우 높음';
        const cls = isVeryHigh
            ? 'bg-emerald-500 text-white border border-emerald-600'
            : 'bg-teal-100 text-teal-700 border border-teal-200';
        const icon = isVeryHigh ? '🚀' : '📈';
        return `<span class="${cls} text-xs px-1.5 py-0.5 rounded font-bold" title="상승확률점수: ${s.riseScore}점">${icon} ${s.riseLabel}</span>`;
    }

    function risingBadge(n) {
        if (!n) return '';
        if (n >= 4) return `<span class="bg-red-500 text-white text-xs px-1.5 py-0.5 rounded font-bold">🔥${n}연속</span>`;
        if (n >= 2) return `<span class="bg-orange-400 text-white text-xs px-1.5 py-0.5 rounded font-bold">▲${n}연속</span>`;
        return `<span class="bg-yellow-100 text-yellow-700 text-xs px-1.5 py-0.5 rounded font-semibold">↑상승</span>`;
    }

    function sentimentBadge(s) {
        const map = {
            '호재': 'bg-green-100 text-green-700 border border-green-200',
            '악재': 'bg-red-100 text-red-600 border border-red-200',
            '중립': 'bg-gray-100 text-gray-500',
        };
        const cls = map[s.sentiment];
        if (!cls) return '';
        const title = s.sentimentReason ? ` title="${s.sentimentReason}"` : '';
        return `<span class="${cls} text-xs px-1.5 py-0.5 rounded font-semibold"${title}>${s.sentiment}</span>`;
    }

    function complexIndicators(s) {
        const rows = [];

        if (s.volRatio > 0) {
            let volCls = 'text-gray-500';
            let volLabel = `${s.volRatio.toFixed(1)}배`;
            if (s.volRatio >= 10)      { volCls = 'text-gray-400 line-through'; volLabel += ' ⚠과열'; }
            else if (s.volRatio >= 5)  volCls = 'text-orange-400';
            else if (s.volRatio >= 2)  volCls = 'text-green-600 font-semibold';
            else if (s.volRatio >= 1)  volCls = 'text-green-500';
            rows.push(`<div class="flex justify-between">
                <span class="text-gray-400">거래량 증가</span>
                <span class="${volCls}">${volLabel}</span>
            </div>`);
        }

        if (s.totalAskVol > 0 && s.totalBidVol > 0) {
            const ratio = s.askBidRatio.toFixed(2);
            let ratioCls = 'text-gray-500';
            let ratioLabel = `${ratio} (`;
            if (s.askBidRatio <= 0.7)      { ratioCls = 'text-green-600 font-semibold'; ratioLabel += '매수 강세)'; }
            else if (s.askBidRatio <= 1.0) { ratioCls = 'text-green-500'; ratioLabel += '매수 우세)'; }
            else if (s.askBidRatio <= 1.5) { ratioCls = 'text-gray-500'; ratioLabel += '균형)'; }
            else                           { ratioCls = 'text-blue-500'; ratioLabel += '매도 우세)'; }
            rows.push(`<div class="flex justify-between">
                <span class="text-gray-400">매도/매수 잔량</span>
                <span class="${ratioCls} text-xs">${ratioLabel}</span>
            </div>`);
        }

        if (s.pricePos !== undefined) {
            const pos = s.pricePos.toFixed(0);
            let posCls = 'text-gray-500';
            if (s.pricePos >= 80)      posCls = 'text-red-500 font-semibold';
            else if (s.pricePos >= 60) posCls = 'text-orange-400';
            else if (s.pricePos <= 30) posCls = 'text-blue-400';
            rows.push(`<div class="flex justify-between">
                <span class="text-gray-400">가격 위치</span>
                <span class="${posCls}">${pos}%</span>
            </div>`);
        }

        if (rows.length === 0) return '';
        return `<div class="mt-2 pt-2 border-t border-gray-100 space-y-1 text-sm">${rows.join('')}</div>`;
    }

    function targetPriceBadge(s) {
        if (!s.targetPrice || s.targetPrice === 0) return '';
        const diff = s.targetPrice - s.currentPrice;
        const pct  = ((diff / s.currentPrice) * 100).toFixed(1);
        const sign = diff >= 0 ? '+' : '';
        const cls  = diff >= 0 ? 'bg-purple-50 text-purple-600 border border-purple-200' : 'bg-gray-100 text-gray-500';
        const title = s.targetReason ? ` title="${s.targetReason}"` : '';
        return `<div class="flex justify-between items-center mt-2 pt-2 border-t border-purple-50">
            <span class="text-xs text-gray-400">AI 목표가</span>
            <span class="${cls} text-xs px-2 py-0.5 rounded font-semibold"${title}>
                ${fmtNum(s.targetPrice)}원 <span class="opacity-70">(${sign}${pct}%)</span>
            </span>
        </div>`;
    }

    function nextDayBadge(s) {
        const trendMap = {
            '상승': { bg: 'bg-red-50 border-red-200',   icon: '▲', cls: 'text-red-500'  },
            '하락': { bg: 'bg-blue-50 border-blue-200', icon: '▼', cls: 'text-blue-500' },
            '횡보': { bg: 'bg-gray-50 border-gray-200', icon: '─', cls: 'text-gray-500' },
        };

        if (!s.nextDayTrend) {
            return `<div class="mt-2 pt-2 border-t border-gray-100">
                <div class="flex items-center justify-between">
                    <span class="text-xs text-gray-400">익일 추세</span>
                    <span class="text-xs text-gray-400 animate-pulse">분석 중...</span>
                </div>
            </div>`;
        }

        const style = trendMap[s.nextDayTrend] || trendMap['횡보'];
        const confBadge = s.nextDayConf ? `<span class="text-xs text-gray-400 ml-1">(${s.nextDayConf})</span>` : '';
        const reasonTip = s.nextDayReason ? ` title="${s.nextDayReason}"` : '';
        return `<div class="mt-2 pt-2 border-t border-gray-100">
            <div class="flex items-center justify-between">
                <span class="text-xs text-gray-400">익일 추세</span>
                <span class="${style.bg} ${style.cls} border text-xs px-2 py-0.5 rounded font-bold"${reasonTip}>
                    ${style.icon} ${s.nextDayTrend}${confBadge}
                </span>
            </div>
            ${s.nextDayReason ? `<p class="text-xs text-gray-400 mt-1 truncate" title="${s.nextDayReason}">${s.nextDayReason}</p>` : ''}
        </div>`;
    }

    // ─────────────────────────────────────────────
    // 체결강도 히스토리 + 미니 차트
    // ─────────────────────────────────────────────
    function recordCntr(code, value) {
        if (value == null || value === 0) return;
        if (!cntrHistory.has(code)) cntrHistory.set(code, []);
        const arr = cntrHistory.get(code);
        arr.push(value);
        if (arr.length > MAX_HISTORY) arr.shift();
    }

    function drawChart(code) {
        const canvas = document.getElementById(`wc-chart-${code}`);
        if (!canvas) return;
        const data = cntrHistory.get(code) || [];
        if (data.length < 2) return;

        const rect = canvas.getBoundingClientRect();
        const dpr  = window.devicePixelRatio || 1;
        const w    = Math.floor(rect.width  * dpr);
        const h    = Math.floor(rect.height * dpr);
        if (w === 0 || h === 0) return;
        if (canvas.width !== w || canvas.height !== h) {
            canvas.width  = w;
            canvas.height = h;
        }

        const ctx   = canvas.getContext('2d');
        const pad   = 3 * dpr;
        const drawH = h - pad * 2;

        ctx.clearRect(0, 0, w, h);

        const min   = Math.min(...data);
        const max   = Math.max(...data);
        const range = max - min || 1;
        const step  = w / (data.length - 1);
        const getY  = val => h - pad - ((val - min) / range) * drawH;

        // 그라디언트 필
        const lastX = (data.length - 1) * step;
        const grad  = ctx.createLinearGradient(0, pad, 0, h);
        grad.addColorStop(0, 'rgba(249,115,22,0.35)');
        grad.addColorStop(1, 'rgba(249,115,22,0)');

        ctx.beginPath();
        data.forEach((val, i) => {
            const x = i * step;
            const y = getY(val);
            i === 0 ? ctx.moveTo(x, y) : ctx.lineTo(x, y);
        });
        ctx.lineTo(lastX, h);
        ctx.lineTo(0, h);
        ctx.closePath();
        ctx.fillStyle = grad;
        ctx.fill();

        // 라인
        ctx.beginPath();
        data.forEach((val, i) => {
            const x = i * step;
            const y = getY(val);
            i === 0 ? ctx.moveTo(x, y) : ctx.lineTo(x, y);
        });
        ctx.strokeStyle = '#f97316';
        ctx.lineWidth   = 1.5 * dpr;
        ctx.lineJoin    = 'round';
        ctx.stroke();

        // 마지막 포인트 강조
        const lastVal = data[data.length - 1];
        ctx.beginPath();
        ctx.arc(lastX, getY(lastVal), 2.5 * dpr, 0, Math.PI * 2);
        ctx.fillStyle = '#f97316';
        ctx.fill();
    }

    // ─────────────────────────────────────────────
    // 관심종목 추가 / 삭제
    // ─────────────────────────────────────────────
    async function addStock(code) {
        code = code.trim().toUpperCase();
        if (!/^\d{6}$/.test(code)) {
            showMsg('6자리 숫자 종목코드를 입력해주세요.');
            return;
        }
        if (loadList().find(s => s.code === code)) {
            showMsg('이미 추가된 종목입니다.');
            return;
        }

        showMsg('조회 중...', false);
        try {
            const resp = await fetch(`/api/stock/${code}`);
            if (!resp.ok) throw new Error('조회 실패');
            const data = await resp.json();
            if (!data.name) throw new Error('종목 없음');

            await addToServer(code, data.name);
            hideMsg();
            input.value = '';
            renderSidebar();
            addPanelCard(code, data.name);
            updateCard(code, data);
            subscribeCode(code);
        } catch (e) {
            showMsg(e.message || '종목을 찾을 수 없습니다.');
        }
    }

    async function removeStock(code) {
        await removeFromServer(code);
        stockWS.unsubscribe(code);
        document.getElementById(`si-${code}`)?.remove();
        document.getElementById(`wc-${code}`)?.remove();
        cntrHistory.delete(code);
        updateEmptyStates();
    }

    // ─────────────────────────────────────────────
    // 사이드바 렌더링
    // ─────────────────────────────────────────────
    function renderSidebar() {
        const list = loadList();
        Array.from(sidebarEl.children).forEach(el => {
            if (el.id !== 'watchlistEmpty') el.remove();
        });
        list.forEach(s => sidebarEl.appendChild(makeSidebarItem(s.code, s.name)));
        updateEmptyStates();
    }

    function makeSidebarItem(code, name) {
        const li = document.createElement('li');
        li.id = `si-${code}`;
        li.className = 'flex items-center justify-between px-3 py-2.5 hover:bg-gray-50 group';
        li.innerHTML = `
            <a href="/stock/${code}" class="flex-1 min-w-0">
                <p class="text-xs font-medium text-gray-800 truncate">${name}</p>
                <p class="text-xs text-gray-400 font-mono">${code}</p>
            </a>
            <button onclick="removeStock('${code}')" title="삭제"
                class="ml-2 text-gray-300 hover:text-red-400 shrink-0 opacity-0 group-hover:opacity-100 transition-opacity text-base leading-none">
                ×
            </button>`;
        return li;
    }

    // ─────────────────────────────────────────────
    // 패널 카드 추가 (signal.js makeCard 구조와 동일)
    // ─────────────────────────────────────────────
    function addPanelCard(code, name) {
        if (document.getElementById(`wc-${code}`)) return;

        const card = document.createElement('div');
        card.id = `wc-${code}`;
        card.className = 'bg-white rounded-xl shadow-sm hover:shadow-md transition-shadow p-4 border border-gray-100 relative';
        card.innerHTML = `
            <!-- 헤더: 종목코드 + 분석 뱃지 + 등락률 -->
            <div class="flex justify-between items-start mb-2">
                <a href="/stock/${code}" class="text-xs text-gray-400 font-mono hover:text-blue-500">${code}</a>
                <div id="wc-badges-${code}" class="flex items-center gap-1 flex-wrap justify-end">
                    <span id="wc-rate-${code}" class="text-xs px-2 py-0.5 rounded-full font-semibold bg-gray-100 text-gray-500">-</span>
                </div>
            </div>
            <!-- 종목명 + 현재가 -->
            <a href="/stock/${code}" class="block">
                <p class="font-semibold text-gray-800 text-sm truncate mb-1">${name}</p>
                <p id="wc-price-${code}" class="text-xl font-bold text-gray-900 mb-2">-</p>
            </a>
            <!-- info 섹션 -->
            <div class="pt-2 border-t border-gray-50 text-sm space-y-1">
                <div class="flex justify-between">
                    <span class="text-gray-400">체결강도</span>
                    <span id="wc-cntr-${code}" class="font-bold text-orange-500">-</span>
                </div>
                <div class="flex justify-between">
                    <span class="text-gray-400">직전 대비</span>
                    <span id="wc-prev-${code}" class="text-gray-500">-</span>
                </div>
                <div class="flex justify-between">
                    <span class="text-gray-400">거래량</span>
                    <span id="wc-vol-${code}" class="text-gray-600">-</span>
                </div>
                <!-- 복합 지표 (10초 폴링 갱신) -->
                <div id="wc-complex-${code}"></div>
                <!-- AI 목표가 (10초 폴링 갱신) -->
                <div id="wc-target-${code}"></div>
                <!-- 익일 추세 (10초 폴링 갱신) -->
                <div id="wc-nextday-${code}"></div>
            </div>
            <!-- 체결강도 미니 라인차트 -->
            <canvas id="wc-chart-${code}"
                style="width:100%;height:48px;display:block;margin-top:10px;"
                class="rounded-sm"></canvas>
            <!-- 삭제 버튼 -->
            <button onclick="removeStock('${code}')" title="삭제"
                class="absolute top-3 right-3 text-gray-200 hover:text-red-400 text-lg leading-none opacity-0 hover:opacity-100 transition-opacity" style="font-size:18px;">×</button>`;

        panelEl.appendChild(card);
        updateEmptyStates();
    }

    // ─────────────────────────────────────────────
    // WS 실시간 카드 업데이트 (1초)
    // ─────────────────────────────────────────────
    function updateCard(code, data) {
        const priceEl = document.getElementById(`wc-price-${code}`);
        const rateEl  = document.getElementById(`wc-rate-${code}`);
        const volEl   = document.getElementById(`wc-vol-${code}`);
        const cntrEl  = document.getElementById(`wc-cntr-${code}`);
        if (!priceEl) return;

        const rate = data.changeRate ?? 0;
        const colorCls = rateClass(rate);
        const bgCls    = rateBadgeClass(rate);
        const sign     = rate > 0 ? '+' : '';

        priceEl.textContent = fmtNum(data.currentPrice) + '원';
        priceEl.className   = `text-xl font-bold mb-2 ${colorCls}`;
        rateEl.textContent  = sign + rate.toFixed(2) + '%';
        rateEl.className    = `text-xs px-2 py-0.5 rounded-full font-semibold ${bgCls}`;
        if (volEl) volEl.textContent = fmtNum(data.volume);
        if (cntrEl && data.cntrStr !== undefined) {
            const cs = data.cntrStr;
            cntrEl.textContent = fmtCntr(cs);
            cntrEl.className   = `font-bold ${cs >= 100 ? 'text-orange-500' : 'text-blue-400'}`;
        }

        // 체결강도 히스토리 기록 + 미니 차트 갱신
        if (data.cntrStr != null && data.cntrStr !== 0) {
            recordCntr(code, data.cntrStr);
            drawChart(code);
        }
    }

    // ─────────────────────────────────────────────
    // 10초 폴링 분석 결과 DOM 갱신
    // ─────────────────────────────────────────────
    function updateAnalysis(code, sig) {
        // 뱃지 영역 갱신 (등락률 뱃지는 유지하면서 분석 뱃지 앞에 삽입)
        const badgesEl = document.getElementById(`wc-badges-${code}`);
        const rateEl   = document.getElementById(`wc-rate-${code}`);
        if (badgesEl && rateEl) {
            // 기존 분석 뱃지 제거
            badgesEl.querySelectorAll('.analysis-badge').forEach(el => el.remove());
            // 분석 뱃지 생성 후 등락률 앞에 삽입
            const frag = document.createRange().createContextualFragment(
                [
                    signalTypeBadge(sig),
                    riseProbBadge(sig),
                    risingBadge(sig.risingCount),
                    sentimentBadge(sig),
                ].filter(Boolean).join('')
            );
            // analysis-badge 클래스 추가 (제거 시 사용)
            frag.querySelectorAll('span').forEach(el => el.classList.add('analysis-badge'));
            badgesEl.insertBefore(frag, rateEl);
        }

        // 직전 대비 갱신
        const prevEl = document.getElementById(`wc-prev-${code}`);
        if (prevEl && sig.prevCntrStr != null && sig.cntrStr != null) {
            const diff = sig.cntrStr - sig.prevCntrStr;
            prevEl.innerHTML = `${fmtCntr(sig.prevCntrStr)} → <span class="${diff >= 0 ? 'text-green-500' : 'text-blue-400'} font-semibold">${diff >= 0 ? '+' : ''}${diff.toFixed(2)}</span>`;
        }

        // 복합 지표 갱신
        const complexEl = document.getElementById(`wc-complex-${code}`);
        if (complexEl) complexEl.innerHTML = complexIndicators(sig);

        // AI 목표가 갱신
        const targetEl = document.getElementById(`wc-target-${code}`);
        if (targetEl) targetEl.innerHTML = targetPriceBadge(sig);

        // 익일 추세 갱신
        const nextEl = document.getElementById(`wc-nextday-${code}`);
        if (nextEl) nextEl.innerHTML = nextDayBadge(sig);
    }

    // ─────────────────────────────────────────────
    // WS 구독
    // ─────────────────────────────────────────────
    function subscribeCode(code) {
        stockWS.subscribe(code);
        stockWS.onPrice(code, (data) => updateCard(code, data));
    }

    // ─────────────────────────────────────────────
    // 10초 폴링: /api/watchlist-signal
    // ─────────────────────────────────────────────
    async function fetchWatchlistSignal() {
        const codes = cachedList.map(s => s.code).join(',');
        if (!codes) return;
        try {
            const resp = await fetch(`/api/watchlist-signal?codes=${codes}`);
            if (!resp.ok) throw new Error('조회 실패');
            const signals = await resp.json();
            if (!Array.isArray(signals)) return;
            signals.forEach(sig => updateAnalysis(sig.code, sig));
        } catch (e) {
            console.error('관심종목 분석 조회 실패:', e);
        }
    }

    // ─────────────────────────────────────────────
    // 빈 상태 처리
    // ─────────────────────────────────────────────
    function updateEmptyStates() {
        const hasItems = cachedList.length > 0;
        emptyEl.classList.toggle('hidden', hasItems);
        panelEmpty?.classList.toggle('hidden', hasItems);
    }

    // ─────────────────────────────────────────────
    // 메시지
    // ─────────────────────────────────────────────
    function showMsg(text, isError = true) {
        msgEl.textContent = text;
        msgEl.className = `text-xs mt-1 ${isError ? 'text-red-500' : 'text-gray-400'}`;
        msgEl.classList.remove('hidden');
    }
    function hideMsg() { msgEl.classList.add('hidden'); }

    // ─────────────────────────────────────────────
    // WS 상태 모니터링
    // ─────────────────────────────────────────────
    function monitorWS() {
        setInterval(() => {
            if (stockWS.ws?.readyState === WebSocket.OPEN) {
                setWsStatus('● 실시간', 'text-green-500 font-normal ml-1');
            } else {
                setWsStatus('○ 연결 중...', 'text-gray-400 font-normal ml-1');
            }
        }, 1000);
    }

    // ─────────────────────────────────────────────
    // 이벤트 바인딩
    // ─────────────────────────────────────────────
    addBtn.addEventListener('click', () => addStock(input.value));
    input.addEventListener('keydown', e => { if (e.key === 'Enter') addStock(input.value); });

    // removeStock을 전역으로 노출 (onclick 속성에서 호출)
    window.removeStock = removeStock;

    // ─────────────────────────────────────────────
    // 초기화
    // ─────────────────────────────────────────────
    monitorWS();

    // 서버에서 관심종목 로드 후 카드 생성 + WS 구독
    (async function init() {
        await loadFromServer();
        renderSidebar();
        cachedList.forEach(s => {
            addPanelCard(s.code, s.name);
            subscribeCode(s.code);
            fetch(`/api/stock/${s.code}`)
                .then(r => r.ok ? r.json() : null)
                .then(data => { if (data) updateCard(s.code, data); })
                .catch(() => {});
        });
        updateEmptyStates();

        // 10초 폴링 시작 (즉시 1회 + 10초 주기)
        fetchWatchlistSignal();
        setInterval(fetchWatchlistSignal, SIGNAL_INTERVAL);
    })();
})();
