// ============================================================
// JaviD Future Bot V12.12 - Web UI
// ============================================================

// ### V18.2.7.5: asset fingerprint.  index.html compares this against the version
// it was built with and warns the user when the browser is still running a cached
// copy of this file - which silently disables every feature added since.
window.__JAVID_ASSET_VER__ = 'V18.2.10';

// ============================================================
// ### V18.2.8.1-SIGNUP  external Binance links - THE ONLY PLACE
// ------------------------------------------------------------
// Someone opening this program for the first time has no way to know a
// Binance ACCOUNT and an API key must exist before anything works.  Two
// places now say so: the first-run onboarding popup (STEP 0) and the
// Settings tab, under the API key inputs.
//
// Both read the URLs from THIS object and nowhere else - index.html carries
// no URL at all - so changing a link is a one-line edit here and can never
// leave a stale copy behind somewhere in the markup.
// ============================================================
const JAVID_LINKS = {
    signup:  'https://www.binance.com/register?ref=PKNNLUSL',
    apimgmt: 'https://www.binance.com/en/my/settings/api-management',
};

const STRATEGY_DISPLAY_NAMES = {
    'JaviDAgrt': 'Strategy-A',
    'MeanRevert': 'Strategy-B',
    'Breakout': 'Strategy-C',
    'EXTERNAL': 'EXTERNAL',
    'Unknown': 'Unknown',
};

function getStrategyDisplay(internalName) {
    return STRATEGY_DISPLAY_NAMES[internalName] || internalName;
}

const API = '/api';
let state = {
    config: null,
    symbols: [],
    selectedSymbols: new Set(),
    allTickers: {},
    positions: [],
    records: [],
    statistics: {},
    isRunning: false,
};

// ============================================================
// ============================================================
async function api(method, path, body) {
    const opts = { method, headers: { 'Content-Type': 'application/json' } };
    if (body) opts.body = JSON.stringify(body);
    try {
        const r = await fetch(API + path, opts);
        if (!r.ok) {
            const err = await r.text();
            console.error(`API ${method} ${path} failed:`, err);
            return null;
        }
        return await r.json();
    } catch (e) {
        console.error(e);
        return null;
    }
}

const $ = (id) => document.getElementById(id);
const $$ = (sel) => document.querySelectorAll(sel);

function fmtNumber(v, digits = 2) {
    if (v === null || v === undefined) return '-';
    const n = Number(v);
    return isNaN(n) ? '-' : n.toLocaleString('ko-KR', { maximumFractionDigits: digits, minimumFractionDigits: digits });
}
function fmtPct(v, digits = 2) {
    const n = Number(v);
    if (isNaN(n)) return '-';
    return (n >= 0 ? '+' : '') + n.toFixed(digits) + '%';
}
function fmtUsdt(v) { return fmtNumber(v, 4) + ' USDT'; }
function pctClass(v) { return v > 0 ? 'up' : v < 0 ? 'down' : ''; }

// ============================================================
// ============================================================
let _positionsAutoRefreshTimer = null;
function startPositionsAutoRefresh() {
    if (_positionsAutoRefreshTimer) return;
    _positionsAutoRefreshTimer = setInterval(() => {
        if ($('view-positions') && $('view-positions').classList.contains('active')) {
            loadPositions();
        } else {
            stopPositionsAutoRefresh();
        }
    }, 1000);
}
function stopPositionsAutoRefresh() {
    if (_positionsAutoRefreshTimer) {
        clearInterval(_positionsAutoRefreshTimer);
        _positionsAutoRefreshTimer = null;
    }
    // ★ V18.2.7.3: leaving the Positions tab also stops the chart's candle poll.
    if (typeof poschartStopFeed === 'function') poschartStopFeed();
}

function setView(viewName) {
    $$('.view').forEach(v => v.classList.remove('active'));
    $$('.nav-btn').forEach(b => b.classList.remove('active'));
    // ★ V18.2.7.3: Logs has no nav button any more, so the button lookup can
    //   legitimately return null. Guard both lookups instead of throwing.
    const _viewEl = $(`view-${viewName}`);
    if (!_viewEl) return;
    _viewEl.classList.add('active');
    const _navBtn = document.querySelector(`.nav-btn[data-view="${viewName}"]`);
    if (_navBtn) _navBtn.classList.add('active');

    if (viewName !== 'positions') {
        stopPositionsAutoRefresh();
    }

    if (viewName === 'dashboard') loadDashboard();
    else if (viewName === 'positions') {
        loadPositions();
        startPositionsAutoRefresh();
    }
    else if (viewName === 'symbols') loadSymbols();
    else if (viewName === 'records') loadRecords();
    else if (viewName === 'settings') {
        loadSettings();
        if (typeof loadSettingsInvestmentMode === 'function') {
            loadSettingsInvestmentMode();
        }
        if (typeof loadTelegramSettings === 'function') {
            loadTelegramSettings();
        }
        if (typeof loadTierSettings === 'function') {
            loadTierSettings();
        }
    }
    else if (viewName === 'logs') loadLogs();
}
$$('.nav-btn').forEach(btn => {
    btn.addEventListener('click', () => setView(btn.dataset.view));
});

// ★ V18.2.7.3: Logs has no menu button any more, but nothing about logging changed:
//   the backend still writes logs/ files and GET /api/logs still serves them, and the
//   view itself is still in the page.  Two quiet ways in for maintenance:
//     - append #logs to the URL   (http://127.0.0.1:PORT/#logs)
//     - press Ctrl + Shift + L
function openLogsView() { setView('logs'); }
window.openLogsView = openLogsView;
document.addEventListener('keydown', (e) => {
    if (e.ctrlKey && e.shiftKey && (e.key === 'L' || e.key === 'l')) {
        e.preventDefault();
        openLogsView();
    }
});
window.addEventListener('hashchange', () => {
    if (location.hash === '#logs') openLogsView();
});
if (location.hash === '#logs') setTimeout(openLogsView, 0);

// ============================================================
// Start / Stop
// ============================================================
// ============================================================
// ============================================================
let _startStopBusy = false;

$('btn-start-stop').addEventListener('click', async () => {
    if (_startStopBusy) return;
    _startStopBusy = true;
    
    const btn = $('btn-start-stop');
    const text = $('start-stop-text');
    const icon = $('start-stop-icon');
    const originalText = text.textContent;
    
    try {
        if (state.isRunning) {
            text.textContent = 'Stopping...';
            btn.disabled = true;
            const r = await api('POST', '/control/stop');
            if (r && r.is_running === false) {
                state.isRunning = false;
            } else {
                state.isRunning = false;
            }
        } else {
            const s = await api('GET', '/status');
            if (!s || !s.is_initialized) {
                alert('Initialization not complete. Please try again in a moment.');
                return;
            }

            const proceed = await v18ConfirmLiveTradingIfNeeded();
            if (!proceed) return;

            text.textContent = 'Starting...';
            btn.disabled = true;
            const r = await api('POST', '/control/start');
            if (!r) {
                alert('Failed to start trading: no response from server');
                return;
            }
            if (r.ok === false) {
                alert(`Failed to start trading: ${r.reason || 'Unknown reason'}`);
                return;
            }
            state.isRunning = (r.is_running === true);
            if (!state.isRunning) {
                alert('Failed to start trading: backend did not start. Check the server logs.');
                return;
            }
        }
    } catch (e) {
        alert('Error: ' + e);
        text.textContent = originalText;
    } finally {
        btn.disabled = false;
        if (state.isRunning) {
            btn.classList.remove('btn-start');
            btn.classList.add('btn-stop');
            icon.textContent = '■';
            text.textContent = 'Stop Trading';
        } else {
            btn.classList.remove('btn-stop');
            btn.classList.add('btn-start');
            icon.textContent = '▶';
            text.textContent = 'Start Trading';
        }
        setTimeout(() => { _startStopBusy = false; }, 3000);
    }
});

// ============================================================
// ============================================================
function setTextIfChanged(elementId, newText) {
    const el = document.getElementById(elementId);
    if (el && el.textContent !== newText) {
        el.textContent = newText;
    }
}

function setClassIfChanged(elementId, newClass) {
    const el = document.getElementById(elementId);
    if (el && el.className !== newClass) {
        el.className = newClass;
    }
}

async function refreshStatus() {
    if (_startStopBusy) return;
    
    const s = await api('GET', '/status');
    if (!s) return;
    if (typeof s.usdt_balance !== 'number') return;
    
    const wasRunning = state.isRunning;
    state.isRunning = s.is_running;
    
    if (wasRunning !== state.isRunning) {
        updateStartStopButton();
    }
    
    let statusClass, statusText;
    if (s.is_running) {
        statusClass = 'status-dot status-running';
        statusText = 'Trading in progress';
    } else if (s.is_initialized) {
        statusClass = 'status-dot status-online';
        statusText = 'Waiting (Select symbol → Load candles → Start trading)';
    } else {
        statusClass = 'status-dot status-offline';
        statusText = '⏳ Auto-initializing...';
    }
    setClassIfChanged('status-indicator', statusClass);
    setTextIfChanged('status-text', statusText);
    
    const balanceLabel = (s.is_shadow_mode || s.is_virtual_balance) 
        ? fmtNumber(s.usdt_balance) + ' USDT (virtual)' 
        : fmtNumber(s.usdt_balance) + ' USDT';
    setTextIfChanged('usdt-balance', balanceLabel);
    setTextIfChanged('account-equity', fmtNumber(s.account_equity) + ' USDT');
    setTextIfChanged('position-count-display', String(s.position_count || 0));
    
    state.lastStatus = s;
    state.lastStatusTime = Date.now();
}

function updateStartStopButton() {
    const btn = $('btn-start-stop');
    const icon = $('start-stop-icon');
    const text = $('start-stop-text');
    
    const wantStop = state.isRunning;
    const isCurrentlyStop = btn.classList.contains('btn-stop');
    
    if (wantStop === isCurrentlyStop) {
        return;
    }
    
    if (wantStop) {
        btn.classList.remove('btn-start');
        btn.classList.add('btn-stop');
        icon.textContent = '■';
        text.textContent = 'Stop Trading';
    } else {
        btn.classList.remove('btn-stop');
        btn.classList.add('btn-start');
        icon.textContent = '▶';
        text.textContent = 'Start Trading';
    }
}

// ============================================================
// ============================================================
async function loadDashboard() {
    const s = await api('GET', '/status');
    if (s) {
        $('order-count-display').textContent = s.order_count || 0;
        const sb = $('status-badge');
        if (sb) {
            if (s.is_running) {
                sb.textContent = 'Running';
                sb.className = 'status-badge';
            } else if (s.is_initialized) {
                sb.textContent = 'Stopped';
                sb.className = 'status-badge idle';
            } else {
                sb.textContent = 'Idle';
                sb.className = 'status-badge idle';
            }
        }
    }

    const rec = await api('GET', '/records');
    if (rec && rec.statistics) {
        const st = rec.statistics;
        const pnl = st.total_pnl || 0;
        const pnlText = (pnl >= 0 ? '+$' : '-$') + fmtNumber(Math.abs(pnl), 2);
        const pnlClass = 'kpi-value ' + (pnl >= 0 ? 'up' : 'down');
        setTextIfChanged('cumulative-pnl', pnlText);
        setClassIfChanged('cumulative-pnl', pnlClass);
        setTextIfChanged('total-trades', String(st.total_trades || 0));
        setTextIfChanged('win-rate', (st.win_rate || 0).toFixed(1) + '%');
        
        const currentEquity = (s && s.account_equity) ? s.account_equity : 0;
        const initEquity = currentEquity - pnl;
        let roi = 0;
        if (initEquity > 0) {
            roi = (pnl / initEquity) * 100;
        }
        const roiText = fmtPct(roi, 2);
        const roiClass = 'kpi-value ' + (roi > 0 ? 'up' : roi < 0 ? 'down' : '');
        setTextIfChanged('cumulative-roi', roiText);
        setClassIfChanged('cumulative-roi', roiClass);
    }

    // 3. Market Overview
    const d = await api('GET', '/market-overview');
    if (!d || !d.btc) { loadChart(); return; }

    const btc = d.btc, eth = d.eth;
    $('btc-price').textContent = '$' + fmtNumber(btc.last_price || btc.lastPrice, 0);
    $('btc-change').textContent = fmtPct(btc.change_24h || btc.priceChangePercent);
    $('btc-change').className = 'card-change-inline ' + pctClass(btc.change_24h || btc.priceChangePercent);
    $('btc-range').textContent = `H: ${fmtNumber(btc.high_24h || btc.highPrice, 0)} L: ${fmtNumber(btc.low_24h || btc.lowPrice, 0)}`;

    $('eth-price').textContent = '$' + fmtNumber(eth.last_price || eth.lastPrice, 0);
    $('eth-change').textContent = fmtPct(eth.change_24h || eth.priceChangePercent);
    $('eth-change').className = 'card-change-inline ' + pctClass(eth.change_24h || eth.priceChangePercent);
    $('eth-range').textContent = `H: ${fmtNumber(eth.high_24h || eth.highPrice, 0)} L: ${fmtNumber(eth.low_24h || eth.lowPrice, 0)}`;

    const total = d.total_count || (d.up_count + d.down_count);
    const upPct = total > 0 ? (d.up_count / total * 100) : 50;
    const downPct = total > 0 ? (d.down_count / total * 100) : 50;
    const supBar = $('sentiment-up-bar'); if (supBar) supBar.style.width = upPct + '%';
    const sdownBar = $('sentiment-down-bar'); if (sdownBar) sdownBar.style.width = downPct + '%';
    $('up-count').textContent = `${d.up_count} up`;
    $('down-count').textContent = `${d.down_count} down`;
    const sumEl = $('sentiment-summary');
    if (sumEl) sumEl.textContent = `Total ${total} (${upPct.toFixed(0)}% up)`;
    $('avg-change').textContent = 'Avg change: ' + fmtPct(d.avg_change);

    const avgVol = d.avg_volatility || 0;
    $('volatility-value').textContent = `|${fmtPct(avgVol)}|`;
    const volLevel = avgVol > 3 ? 'High' : avgVol > 1.5 ? 'Normal' : 'Low';
    $('volatility-level').textContent = volLevel;
    const volLevelEl = $('volatility-level');
    if (volLevelEl) volLevelEl.className = 'card-price ' +
        (avgVol > 3 ? 'up' : avgVol < 1 ? 'down' : '');
    const volPct = Math.min(avgVol * 20, 100);
    $('volatility-bar').style.width = volPct + '%';
    const volRangeEl = $('volatility-range');
    if (volRangeEl) volRangeEl.textContent = fmtPct(d.avg_range || 0);

    $('avg-funding').textContent = (d.avg_funding_rate >= 0 ? '+' : '') +
        fmtPct(d.avg_funding_rate, 4);
    if (d.top_funding && d.top_funding.length) {
        $('top-funding').textContent = d.top_funding.slice(0, 1).map(
            f => `${f.symbol} ${fmtPct(f.rate, 3)}`).join(', ');
    }
    if (d.bottom_funding && d.bottom_funding.length) {
        $('bottom-funding').textContent = d.bottom_funding.slice(0, 1).map(
            f => `${f.symbol} ${fmtPct(f.rate, 3)}`).join(', ');
    }

    const updEl = $('market-updated');
    if (updEl) {
        const now = new Date();
        updEl.textContent = `Updated: ${now.toLocaleTimeString('en-US')}`;
    }

    const renderTop = (rows, bodyId, valueKey) => {
        const tbody = $(bodyId);
        if (!tbody) return;
        tbody.innerHTML = '';
        (rows || []).forEach(r => {
            const tr = document.createElement('tr');
            const val = r[valueKey] !== undefined ? r[valueKey] : (r.change || r.change_24h || 0);
            const cls = typeof val === 'number' ? pctClass(val) : '';
            tr.innerHTML = `
                <td>${r.symbol.replace('USDT','')}</td>
                <td class="${cls}">${typeof val === 'number' ? fmtPct(val) : val}</td>
            `;
            tbody.appendChild(tr);
        });
    };
    renderTop(d.top_gainers, 'top-gainers-body', 'change');
    renderTop(d.top_losers, 'top-losers-body', 'change');

    const tbodyV = $('top-volume-body');
    if (tbodyV) {
        tbodyV.innerHTML = '';
        (d.top_volume || []).forEach(r => {
            const tr = document.createElement('tr');
            const vol = r.volume > 1e9 ? (r.volume/1e9).toFixed(1) + 'B'
                       : r.volume > 1e6 ? (r.volume/1e6).toFixed(1) + 'M'
                       : r.volume.toFixed(0);
            const change = r.change || r.change_24h || 0;
            tr.innerHTML = `
                <td>${r.symbol.replace('USDT','')}</td>
                <td>$${vol}</td>
            `;
            tbodyV.appendChild(tr);
        });
    }

    loadChart();
}

async function loadChart() {
    const d = await api('GET', '/dashboard/daily-pnl?days=30');
    if (!d) return;
    drawChart(d);
}

function drawChart(data, hoverIdx) {
    // ★ V18.0.6: "Daily Balance Change" line chart ★
    //  - smooth curve + gradient area under the line
    //  - auto color: green when up / red when down over the period
    //  - dashed baseline + highlighted last point with balance label
    //  - hover tooltip: date / balance / daily P&L
    //
    // ★★★ V18.2.7.2: the series is ANCHORED TO THE REAL ACCOUNT BALANCE ★★★
    // Before, each point was plotted as `start_balance + cumulative_realized_i`.
    // `start_balance` is the daily baseline, which is re-anchored at 00:00 KST and
    // therefore ALREADY contains every past day's profit — so adding the cumulative
    // PnL on top of it counted the same profit twice and the chart drifted
    // permanently above the Balance shown on the dashboard.
    // The server now returns a per-day `balance` back-computed from the live
    // balance (balance_i = current_balance - (cum_total - cum_i)), so the last
    // plotted point is the dashboard Balance by construction.
    const canvas = $('chart-canvas');
    if (!canvas) return;
    const ctx = canvas.getContext('2d');
    const W = canvas.width;
    const H = canvas.height;
    ctx.clearRect(0, 0, W, H);

    const currentBal = data.current_balance || 0;
    const items = data.items || [];
    // Baseline used by the chart = balance at the start of the plotted window.
    // Falls back to the legacy start balance only when the account balance is unknown.
    const startBal = (typeof data.series_start_balance === 'number' && data.balance_anchored)
        ? data.series_start_balance
        : (data.start_balance || 0);

    // Header info (chart baseline / total change over the plotted window)
    if (startBal > 0) {
        setTextIfChanged('chart-start-balance', '$' + fmtNumber(startBal, 2));
        const totalChange = ((currentBal - startBal) / startBal) * 100;
        const changeText = (totalChange >= 0 ? '+' : '') + totalChange.toFixed(2) + '%';
        const el = $('chart-balance-change');
        if (el) {
            el.textContent = changeText;
            el.style.color = totalChange >= 0 ? '#22c55e' : '#ef4444';
        }
    } else {
        setTextIfChanged('chart-start-balance', 'Not set');
        const el = $('chart-balance-change');
        if (el) {
            el.textContent = 'Recorded automatically when trading starts';
            el.style.color = '#8b92a6';
        }
    }

    if (!items.length) {
        ctx.fillStyle = '#8b92a6';
        ctx.font = '13px Segoe UI';
        ctx.textAlign = 'center';
        ctx.fillText(startBal > 0 ? 'No trade records (shown after trading starts)' : 'Recorded automatically when trading starts',
                     W / 2, H / 2);
        canvas._chartPts = null;
        return;
    }

    // ── Build the daily balance series ──
    // ★ V18.2.7.2: plot the server-supplied `balance` (anchored to the live
    //   account balance). The `startBal + cumulative` form is kept only as a
    //   fallback for an older server that does not send a per-day balance.
    const pts = [];
    if (startBal > 0) {
        pts.push({ date: 'Start', balance: startBal, daily: 0, isStart: true });
    }
    items.forEach(it => {
        const bal = (typeof it.balance === 'number')
            ? it.balance
            : (startBal > 0 ? startBal + (it.cumulative || 0) : (it.cumulative || 0));
        pts.push({ date: it.date || '', balance: bal, daily: it.realized || 0, isStart: false });
    });
    // Hard guarantee: the last plotted point IS the dashboard Balance.
    if (pts.length && currentBal > 0) pts[pts.length - 1].balance = currentBal;

    const padTop = 28, padBottom = 36, padLeft = 78, padRight = 34;
    const chartW = W - padLeft - padRight;
    const chartH = H - padTop - padBottom;

    const vals = pts.map(p => p.balance);
    let vMin = Math.min(...vals);
    let vMax = Math.max(...vals);
    if (startBal > 0) { vMin = Math.min(vMin, startBal); vMax = Math.max(vMax, startBal); }
    if (vMax - vMin < 1e-9) { vMin -= 1; vMax += 1; }
    const span = vMax - vMin;
    vMin -= span * 0.12;
    vMax += span * 0.12;

    const xAt = (i) => pts.length === 1 ? padLeft + chartW / 2
                                        : padLeft + chartW * (i / (pts.length - 1));
    const yAt = (v) => padTop + chartH * (1 - (v - vMin) / (vMax - vMin));

    const up = pts[pts.length - 1].balance >= pts[0].balance;
    const lineColor = up ? '#22c55e' : '#ef4444';
    const softColor = up ? 'rgba(34,197,94,' : 'rgba(239,68,68,';

    // ── Horizontal grid + $ axis labels ──
    ctx.font = '11px Segoe UI';
    ctx.textAlign = 'right';
    ctx.lineWidth = 1;
    const gridSteps = 4;
    for (let g = 0; g <= gridSteps; g++) {
        const v = vMin + (vMax - vMin) * (g / gridSteps);
        const y = yAt(v);
        ctx.strokeStyle = '#1f2533';
        ctx.setLineDash([2, 3]);
        ctx.beginPath();
        ctx.moveTo(padLeft, y);
        ctx.lineTo(W - padRight, y);
        ctx.stroke();
        ctx.fillStyle = '#5b6478';
        ctx.fillText('$' + fmtNumber(v, 2), padLeft - 8, y + 4);
    }
    ctx.setLineDash([]);

    // ── Baseline dashed line (amber) ──
    if (startBal > 0 && startBal >= vMin && startBal <= vMax) {
        const y0 = yAt(startBal);
        ctx.strokeStyle = 'rgba(251,191,36,0.55)';
        ctx.setLineDash([6, 4]);
        ctx.lineWidth = 1.2;
        ctx.beginPath();
        ctx.moveTo(padLeft, y0);
        ctx.lineTo(W - padRight, y0);
        ctx.stroke();
        ctx.setLineDash([]);
        ctx.fillStyle = '#fbbf24';
        ctx.font = 'bold 10px Segoe UI';
        ctx.textAlign = 'left';
        ctx.fillText('Baseline $' + fmtNumber(startBal, 2), padLeft + 4, y0 - 5);
    }

    // ── Smooth curve path (midpoint bezier) ──
    const buildPath = () => {
        ctx.beginPath();
        ctx.moveTo(xAt(0), yAt(pts[0].balance));
        if (pts.length === 1) return;
        for (let i = 1; i < pts.length; i++) {
            const x0 = xAt(i - 1), y0 = yAt(pts[i - 1].balance);
            const x1 = xAt(i),     y1 = yAt(pts[i].balance);
            const mx = (x0 + x1) / 2;
            ctx.bezierCurveTo(mx, y0, mx, y1, x1, y1);
        }
    };

    // ── Gradient area under the line ──
    buildPath();
    ctx.lineTo(xAt(pts.length - 1), padTop + chartH);
    ctx.lineTo(xAt(0), padTop + chartH);
    ctx.closePath();
    const areaGrad = ctx.createLinearGradient(0, padTop, 0, padTop + chartH);
    areaGrad.addColorStop(0, softColor + '0.28)');
    areaGrad.addColorStop(0.7, softColor + '0.07)');
    areaGrad.addColorStop(1, softColor + '0)');
    ctx.fillStyle = areaGrad;
    ctx.fill();

    // ── Line (soft glow + main stroke) ──
    buildPath();
    ctx.strokeStyle = softColor + '0.25)';
    ctx.lineWidth = 6;
    ctx.lineJoin = 'round';
    ctx.lineCap = 'round';
    ctx.stroke();

    buildPath();
    ctx.strokeStyle = lineColor;
    ctx.lineWidth = 2.2;
    ctx.stroke();

    // ── Data points (sampled when there are many) ──
    const dotStep = pts.length > 40 ? Math.ceil(pts.length / 40) : 1;
    pts.forEach((p, i) => {
        if (i % dotStep !== 0 && i !== pts.length - 1) return;
        const x = xAt(i), y = yAt(p.balance);
        ctx.beginPath();
        ctx.arc(x, y, 3, 0, Math.PI * 2);
        ctx.fillStyle = '#0f1522';
        ctx.fill();
        ctx.strokeStyle = lineColor;
        ctx.lineWidth = 1.6;
        ctx.stroke();
    });

    // ── Last point highlight + current balance label ──
    const li = pts.length - 1;
    const lx = xAt(li), ly = yAt(pts[li].balance);
    ctx.beginPath();
    ctx.arc(lx, ly, 7, 0, Math.PI * 2);
    ctx.fillStyle = softColor + '0.25)';
    ctx.fill();
    ctx.beginPath();
    ctx.arc(lx, ly, 4, 0, Math.PI * 2);
    ctx.fillStyle = lineColor;
    ctx.fill();
    ctx.strokeStyle = '#0f1522';
    ctx.lineWidth = 1.5;
    ctx.stroke();

    const lastLabel = '$' + fmtNumber(pts[li].balance, 2);
    ctx.font = 'bold 12px Segoe UI';
    const tw = ctx.measureText(lastLabel).width;
    let lblX = lx + 10;
    if (lblX + tw > W - padRight) lblX = lx - tw - 10;
    let lblY = ly - 10;
    if (lblY < padTop + 12) lblY = ly + 20;
    ctx.fillStyle = lineColor;
    ctx.textAlign = 'left';
    ctx.fillText(lastLabel, lblX, lblY);

    // ── X-axis date labels ──
    ctx.fillStyle = '#8b92a6';
    ctx.font = '10px Segoe UI';
    ctx.textAlign = 'center';
    const labelStep = Math.max(1, Math.floor(pts.length / 10));
    pts.forEach((p, i) => {
        if (i % labelStep !== 0 && i !== pts.length - 1) return;
        const dateStr = p.isStart ? p.date : (p.date ? p.date.slice(5) : '');
        ctx.fillText(dateStr, xAt(i), H - padBottom + 16);
    });

    // ── Axis border ──
    ctx.strokeStyle = '#223047';
    ctx.lineWidth = 1;
    ctx.beginPath();
    ctx.moveTo(padLeft, padTop);
    ctx.lineTo(padLeft, H - padBottom);
    ctx.lineTo(W - padRight, H - padBottom);
    ctx.stroke();

    // ── Hover tooltip ──
    canvas._chartPts = { pts, xAt, yAt, padTop, chartH, padLeft, padRight };
    canvas._chartData = data;
    if (typeof hoverIdx === 'number' && hoverIdx >= 0 && hoverIdx < pts.length) {
        const p = pts[hoverIdx];
        const hx = xAt(hoverIdx), hy = yAt(p.balance);
        // Vertical guide line
        ctx.strokeStyle = 'rgba(125,138,160,0.4)';
        ctx.setLineDash([3, 3]);
        ctx.beginPath();
        ctx.moveTo(hx, padTop);
        ctx.lineTo(hx, padTop + chartH);
        ctx.stroke();
        ctx.setLineDash([]);
        // Point highlight
        ctx.beginPath();
        ctx.arc(hx, hy, 5, 0, Math.PI * 2);
        ctx.fillStyle = '#fff';
        ctx.fill();
        ctx.strokeStyle = lineColor;
        ctx.lineWidth = 2;
        ctx.stroke();
        // Tooltip box
        const line1 = p.isStart ? 'Trading start' : p.date;
        const line2 = 'Balance  $' + fmtNumber(p.balance, 2);
        const dSign = p.daily >= 0 ? '+' : '';
        const line3 = p.isStart ? '' : ('Daily P&L  ' + dSign + fmtNumber(p.daily, 2) + ' USDT');
        ctx.font = '11px Segoe UI';
        const boxW = Math.max(ctx.measureText(line1).width,
                              ctx.measureText(line2).width,
                              line3 ? ctx.measureText(line3).width : 0) + 20;
        const boxH = line3 ? 52 : 38;
        let bx = hx + 12;
        if (bx + boxW > W - padRight) bx = hx - boxW - 12;
        let by = hy - boxH - 8;
        if (by < padTop) by = hy + 12;
        ctx.fillStyle = 'rgba(15,21,34,0.92)';
        ctx.strokeStyle = '#2a3a52';
        ctx.lineWidth = 1;
        if (ctx.roundRect) {
            ctx.beginPath();
            ctx.roundRect(bx, by, boxW, boxH, 6);
            ctx.fill();
            ctx.stroke();
        } else {
            ctx.fillRect(bx, by, boxW, boxH);
            ctx.strokeRect(bx, by, boxW, boxH);
        }
        ctx.textAlign = 'left';
        ctx.fillStyle = '#8b92a6';
        ctx.fillText(line1, bx + 10, by + 15);
        ctx.fillStyle = '#fff';
        ctx.font = 'bold 11px Segoe UI';
        ctx.fillText(line2, bx + 10, by + 30);
        if (line3) {
            ctx.fillStyle = p.daily >= 0 ? '#22c55e' : '#ef4444';
            ctx.font = '11px Segoe UI';
            ctx.fillText(line3, bx + 10, by + 45);
        }
    }

    // Bind mouse handlers once
    if (!canvas._hoverBound) {
        canvas._hoverBound = true;
        canvas.addEventListener('mousemove', (e) => {
            const meta = canvas._chartPts;
            if (!meta || !canvas._chartData) return;
            const rect = canvas.getBoundingClientRect();
            const mx = (e.clientX - rect.left) * (canvas.width / rect.width);
            let best = -1, bestD = 1e9;
            meta.pts.forEach((p, i) => {
                const d = Math.abs(meta.xAt(i) - mx);
                if (d < bestD) { bestD = d; best = i; }
            });
            if (best >= 0 && bestD < 40) drawChart(canvas._chartData, best);
            else drawChart(canvas._chartData);
        });
        canvas.addEventListener('mouseleave', () => {
            if (canvas._chartData) drawChart(canvas._chartData);
        });
    }
}

// ============================================================
// ============================================================
// ============================================================
// ★ V18.2.7.3 — Positions live chart (TradingView lightweight-charts)
// ============================================================
// PRESENTATION ONLY.  This module never places, modifies or cancels an order and
// never calls into the trading engine.  Its candle feed is GET /api/candles, which
// returns the candles the DataCollector ALREADY keeps in memory from the WebSocket
// stream — so the chart costs ZERO exchange REST weight.  That property is
// deliberate: this bot has been IP-banned (HTTP 418) by REST weight before, and a
// chart that polled klines would reintroduce exactly that risk.
//
// Required behaviour:
//   - while any position is held, the right pane always shows exactly one of them
//   - clicking a row pins that position; otherwise the top row is followed
//   - LONG / SHORT entry marker + entry price line (and TP / SL lines when set)
//   - when the shown position closes: draw a CLOSE marker, hold it briefly, then
//     move on to the next remaining position (or the empty placeholder if none)

const POSCHART_TF_DEFAULT = '1m';
const POSCHART_CLOSE_HOLD_MS = 4000;   // how long the CLOSE marker stays on screen
const POSCHART_FEED_MS = 2000;         // incremental candle poll while visible

const POSCHART = {
    chart: null,
    series: null,
    lines: [],          // price lines currently attached to the series
    key: null,          // "SYMBOL|SIDE" currently drawn
    pinned: null,       // row the user clicked (held until that position closes)
    tf: POSCHART_TF_DEFAULT,
    lastTime: 0,        // last candle time pushed into the series (chart units)
    feedTimer: null,
    resizeObs: null,
    closing: false,     // a CLOSE marker is on screen right now
    closeUntil: 0,
    seen: {},           // key -> last snapshot seen, used to draw the CLOSE marker
    entryMarker: null,
    emptyShown: false,
    unavailable: false, // library or DOM missing -> degrade quietly, never throw
};

function posKey(p) { return String(p.symbol) + '|' + String(p.side); }

function poschartCurrentKey() { return POSCHART.pinned || POSCHART.key; }

// lightweight-charts v4 paints a UTCTimestamp axis in UTC.  Shifting each stamp by
// the browser's own UTC offset makes the axis read local time (KST for this
// operator).  Cosmetic only — the offset is recomputed per timestamp so DST-aware
// zones stay correct too.
function poschartTime(ms) {
    return Math.floor(ms / 1000) - new Date(ms).getTimezoneOffset() * 60;
}

function poschartNote(msg) {
    const el = $('poschart-note');
    if (el) el.textContent = msg || '';
}

function poschartShowBox(show) {
    const box = $('poschart-box');
    const empty = $('poschart-empty');
    if (box) box.style.display = show ? '' : 'none';
    if (empty) empty.classList.toggle('show', !show);
    if (show && POSCHART.chart && box && box.clientWidth > 0) {
        try { POSCHART.chart.applyOptions({ width: box.clientWidth }); } catch (e) {}
    }
}

function poschartEnsure() {
    if (POSCHART.chart) return POSCHART.chart;
    if (POSCHART.unavailable) return null;
    const box = $('poschart-box');
    if (!box) { POSCHART.unavailable = true; return null; }
    if (typeof LightweightCharts === 'undefined' || !LightweightCharts.createChart) {
        POSCHART.unavailable = true;
        box.style.display = 'none';
        const empty = $('poschart-empty');
        if (empty) {
            empty.textContent = 'Chart library missing: static/vendor/lightweight-charts.standalone.production.js';
            empty.classList.add('show');
        }
        return null;
    }
    try {
        POSCHART.chart = LightweightCharts.createChart(box, {
            width: box.clientWidth || 520,
            height: box.clientHeight || 400,
            layout: { background: { type: 'solid', color: '#0d1420' }, textColor: '#7d8aa0', fontSize: 11 },
            grid: { vertLines: { color: 'rgba(28,37,48,.75)' }, horzLines: { color: 'rgba(28,37,48,.75)' } },
            rightPriceScale: { borderColor: '#27303f' },
            timeScale: { borderColor: '#27303f', timeVisible: true, secondsVisible: false },
            crosshair: { mode: LightweightCharts.CrosshairMode.Normal },
            localization: { locale: 'ko-KR' },
        });
        POSCHART.series = POSCHART.chart.addCandlestickSeries({
            upColor: '#34d399', downColor: '#fb7185',
            borderUpColor: '#34d399', borderDownColor: '#fb7185',
            wickUpColor: '#34d399', wickDownColor: '#fb7185',
        });
    } catch (e) {
        console.error('poschart init failed', e);
        POSCHART.unavailable = true;
        return null;
    }
    if (window.ResizeObserver) {
        POSCHART.resizeObs = new ResizeObserver(() => {
            if (!POSCHART.chart) return;
            const w = box.clientWidth;
            if (w > 0) { try { POSCHART.chart.applyOptions({ width: w }); } catch (e) {} }
        });
        POSCHART.resizeObs.observe(box);
    }
    return POSCHART.chart;
}

function poschartClearLines() {
    if (!POSCHART.series) return;
    POSCHART.lines.forEach(l => { try { POSCHART.series.removePriceLine(l); } catch (e) {} });
    POSCHART.lines = [];
}

function poschartAddLine(price, color, title, dashed) {
    if (!POSCHART.series || !(Number(price) > 0)) return;
    try {
        POSCHART.lines.push(POSCHART.series.createPriceLine({
            price: Number(price),
            color: color,
            lineWidth: 1,
            lineStyle: dashed ? LightweightCharts.LineStyle.Dashed : LightweightCharts.LineStyle.Solid,
            axisLabelVisible: true,
            title: title,
        }));
    } catch (e) {}
}

function poschartHeader(p, closedInfo) {
    const symEl = $('poschart-symbol');
    const sideEl = $('poschart-side');
    const set = (id, text, cls) => {
        const el = $(id);
        if (!el) return;
        el.textContent = text;
        el.className = cls || '';
    };
    const src = closedInfo || p;
    if (!src) {
        if (symEl) symEl.textContent = '-';
        if (sideEl) { sideEl.textContent = '-'; sideEl.className = 'poschart-side'; }
        ['poschart-entry', 'poschart-last', 'poschart-pnl', 'poschart-roe']
            .forEach(id => set(id, '-', ''));
        return;
    }
    const isLong = String(src.side).toUpperCase() === 'LONG';
    if (symEl) symEl.textContent = src.symbol;
    if (sideEl) {
        if (closedInfo) {
            sideEl.textContent = 'CLOSED';
            sideEl.className = 'poschart-side closed';
        } else {
            sideEl.textContent = src.side + ' ×' + src.leverage;
            sideEl.className = 'poschart-side ' + (isLong ? 'long' : 'short');
        }
    }
    set('poschart-entry', fmtNumber(src.entry_price, 4), '');
    set('poschart-last', fmtNumber(src.current_price, 4), '');
    set('poschart-pnl', fmtNumber(src.current_pnl, 4), src.current_pnl >= 0 ? 'up' : 'down');
    set('poschart-roe', fmtPct(src.current_roe), src.current_roe >= 0 ? 'up' : 'down');
}

function poschartDrawEntry(p, bars) {
    if (!POSCHART.series || !bars.length) return;
    const isLong = String(p.side).toUpperCase() === 'LONG';

    // Wallet.EntryTimestamp is epoch seconds; fall back to parsing TradingTime.
    let entryMs = Number(p.entry_timestamp) || 0;
    if (entryMs > 0 && entryMs < 1e11) entryMs *= 1000;     // seconds -> ms
    if (!(entryMs > 0) && p.trading_time) {
        const t = Date.parse(String(p.trading_time).replace(' ', 'T'));
        if (!isNaN(t)) entryMs = t;
    }

    let markerTime = bars[0].time;
    if (entryMs > 0) {
        const et = poschartTime(entryMs);
        if (et <= bars[0].time) {
            markerTime = bars[0].time;                      // entry older than the window
        } else {
            const hit = bars.find(b => b.time >= et);
            markerTime = hit ? hit.time : bars[bars.length - 1].time;
        }
    }
    POSCHART.entryMarker = {
        time: markerTime,
        position: isLong ? 'belowBar' : 'aboveBar',
        color: isLong ? '#34d399' : '#fb7185',
        shape: isLong ? 'arrowUp' : 'arrowDown',
        text: (isLong ? 'LONG' : 'SHORT') + ' ×' + p.leverage + ' @ ' + fmtNumber(p.entry_price, 4),
    };
    try { POSCHART.series.setMarkers([POSCHART.entryMarker]); } catch (e) {}

    poschartClearLines();
    poschartAddLine(p.entry_price, '#2dd4bf', 'ENTRY', false);
    if (Number(p.target_price) > 0) poschartAddLine(p.target_price, '#34d399', 'TP', true);
    if (Number(p.stop_loss) > 0) poschartAddLine(p.stop_loss, '#fb7185', 'SL', true);
}

async function poschartLoad(p, full) {
    if (!POSCHART.series) return;
    const limit = full ? 300 : 3;
    const d = await api('GET', '/candles?symbol=' + encodeURIComponent(p.symbol)
                        + '&tf=' + encodeURIComponent(POSCHART.tf) + '&limit=' + limit);
    if (!d || !Array.isArray(d.candles)) return;
    const bars = d.candles.map(c => ({
        time: poschartTime(c.t), open: c.o, high: c.h, low: c.l, close: c.c,
    })).filter(b => isFinite(b.time) && isFinite(b.close) && b.close > 0);

    if (!bars.length) {
        if (full) poschartNote('Waiting for ' + p.symbol + ' ' + POSCHART.tf
                              + ' candles from the live feed…');
        return;
    }
    if (full) {
        try { POSCHART.series.setData(bars); } catch (e) { return; }
        POSCHART.lastTime = bars[bars.length - 1].time;
        try { POSCHART.chart.timeScale().fitContent(); } catch (e) {}
        poschartDrawEntry(p, bars);
        poschartNote('');
    } else {
        bars.forEach(b => {
            if (b.time < POSCHART.lastTime) return;
            try { POSCHART.series.update(b); } catch (e) { return; }
            POSCHART.lastTime = b.time;
        });
    }
}

function poschartStartFeed() {
    if (POSCHART.feedTimer) return;
    POSCHART.feedTimer = setInterval(() => {
        const view = $('view-positions');
        if (!view || !view.classList.contains('active')) { poschartStopFeed(); return; }
        if (POSCHART.closing || !POSCHART.key) return;
        const p = (state.positions || []).find(x => posKey(x) === POSCHART.key);
        if (p) poschartLoad(p, false);
    }, POSCHART_FEED_MS);
}

function poschartStopFeed() {
    if (POSCHART.feedTimer) { clearInterval(POSCHART.feedTimer); POSCHART.feedTimer = null; }
}

async function poschartSelect(key, positions) {
    const p = (positions || []).find(x => posKey(x) === key);
    if (!p) return;
    poschartShowBox(true);            // unhide first so the chart can measure itself
    if (!poschartEnsure()) return;
    POSCHART.key = key;
    POSCHART.emptyShown = false;
    POSCHART.lastTime = 0;
    POSCHART.entryMarker = null;
    poschartClearLines();
    try { POSCHART.series.setMarkers([]); } catch (e) {}
    try { POSCHART.series.setData([]); } catch (e) {}
    poschartHeader(p, null);
    await poschartLoad(p, true);
    poschartStartFeed();
}

function poschartShowClose(key) {
    const last = POSCHART.seen[key];
    POSCHART.closing = true;
    POSCHART.closeUntil = Date.now() + POSCHART_CLOSE_HOLD_MS;
    poschartStopFeed();
    if (POSCHART.series && last) {
        const isLong = String(last.side).toUpperCase() === 'LONG';
        const markers = [];
        if (POSCHART.entryMarker) markers.push(POSCHART.entryMarker);
        markers.push({
            time: POSCHART.lastTime
                  || (POSCHART.entryMarker ? POSCHART.entryMarker.time : poschartTime(Date.now())),
            position: isLong ? 'aboveBar' : 'belowBar',
            color: '#fbbf24',
            shape: 'circle',
            text: 'CLOSE @ ' + fmtNumber(last.current_price, 4)
                  + '  (' + fmtNumber(last.current_pnl, 4) + ' USDT)',
        });
        markers.sort((a, b) => a.time - b.time);
        try { POSCHART.series.setMarkers(markers); } catch (e) {}
        poschartAddLine(last.current_price, '#fbbf24', 'CLOSE', true);
    }
    poschartHeader(null, last);
    poschartNote('Position closed — switching to the next held position…');
}

function poschartShowEmpty() {
    POSCHART.key = null;
    POSCHART.pinned = null;
    POSCHART.entryMarker = null;
    POSCHART.lastTime = 0;
    poschartStopFeed();
    poschartClearLines();
    if (POSCHART.series) {
        try { POSCHART.series.setMarkers([]); } catch (e) {}
        try { POSCHART.series.setData([]); } catch (e) {}
    }
    poschartHeader(null, null);
    poschartShowBox(false);
    poschartNote('');
    POSCHART.emptyShown = true;
}

// Called once per positions render (1 s cadence).  Decides which position the
// right pane should be showing and drives the close -> next-position handover.
function poschartSync(positions) {
    if (POSCHART.unavailable) return;
    if (!$('pos-split-right')) return;          // old single-pane layout: nothing to do

    const list = positions || [];
    const keys = list.map(posKey);

    // Remember every position we can still see, so that when one vanishes we still
    // know its final price / PnL for the CLOSE marker.
    list.forEach(p => { POSCHART.seen[posKey(p)] = p; });

    // A CLOSE marker is up: keep it, then fall through and re-select.
    if (POSCHART.closing) {
        if (Date.now() < POSCHART.closeUntil) return;
        POSCHART.closing = false;
        POSCHART.key = null;
    }

    // A pin only lasts as long as that position does.
    if (POSCHART.pinned && keys.indexOf(POSCHART.pinned) < 0) POSCHART.pinned = null;

    // The position being drawn just closed -> CLOSE marker before moving on.
    if (POSCHART.key && keys.indexOf(POSCHART.key) < 0) {
        poschartShowClose(POSCHART.key);
        return;
    }

    if (!keys.length) {
        if (!POSCHART.emptyShown) poschartShowEmpty();
        return;
    }

    const want = POSCHART.pinned || POSCHART.key || keys[0];
    if (want !== POSCHART.key) {
        poschartSelect(want, list);
    } else {
        const p = list.find(x => posKey(x) === want);
        if (p) poschartHeader(p, null);         // live numbers, no extra request
    }
}

function poschartPin(key) {
    if (!key || POSCHART.closing) return;
    POSCHART.pinned = key;
    if (key !== POSCHART.key) poschartSelect(key, state.positions || []);
    renderPositions();                          // move the row highlight at once
}

async function loadPositions() {
    const d = await api('GET', '/positions');
    if (!d) return;
    state.positions = d.items;
    $('pos-count-badge').textContent = `(${d.count})`;
    renderPositions();
}

let _lastRenderPositionsTime = 0;
let _pendingRenderPositions = null;
function renderPositions() {
    const now = Date.now();
    const elapsed = now - _lastRenderPositionsTime;
    if (elapsed < 100) {
        if (_pendingRenderPositions) clearTimeout(_pendingRenderPositions);
        _pendingRenderPositions = setTimeout(() => {
            _pendingRenderPositions = null;
            _renderPositionsImpl();
        }, 100 - elapsed);
        return;
    }
    _lastRenderPositionsTime = now;
    _renderPositionsImpl();
}

function _renderPositionsImpl() {
    const tbody = $('positions-body');
    if (!tbody) return;
    tbody.innerHTML = '';
    if (!state.positions || !state.positions.length) {
        tbody.innerHTML = '<tr><td colspan="12" style="text-align:center;color:#8b92a6">No positions</td></tr>';
        if (typeof poschartSync === 'function') poschartSync([]);
        return;
    }
    // ★ V18.2.7.3: selKey only decides which row is highlighted.
    const selKey = (typeof poschartCurrentKey === 'function') ? poschartCurrentKey() : null;
    state.positions.forEach(p => {
        const tr = document.createElement('tr');
        const _key = String(p.symbol) + '|' + String(p.side);
        tr.dataset.poskey = _key;
        tr.className = 'pos-row' + (_key === selKey ? ' pos-row-selected' : '');
        tr.innerHTML = `
            <td><strong>${p.symbol}</strong></td>
            <td><span class="side-${p.side.toLowerCase()}">${p.side}</span></td>
            <td>${fmtNumber(p.entry_count, 4)}</td>
            <td>${p.leverage}x</td>
            <td>${fmtNumber(p.entry_price, 4)}</td>
            <td>${fmtNumber(p.current_price, 4)}</td>
            <td class="pnl-${p.is_profit ? 'pos' : 'neg'}">${fmtNumber(p.current_pnl, 4)}</td>
            <td class="roe-${p.is_profit ? 'pos' : 'neg'}">${fmtPct(p.current_roe)}</td>
            <td>${p.split_count}</td>
            <td>${getStrategyDisplay(p.entry_type)}</td>
            <td class="pos-time" style="font-size:11px">${p.trading_time || '-'}</td>
            <td>
                <button class="btn btn-danger" onclick="closePosition('${p.symbol}','${p.side}')">Close</button>
            </td>
        `;
        tbody.appendChild(tr);
    });
    // ★ V18.2.7.3: drive the right-hand chart from the list we just drew.
    if (typeof poschartSync === 'function') poschartSync(state.positions);
}

// ★ V18.2.7.3: row click -> pin that position's chart.  Delegated on the tbody so
//   it survives the 1 s re-render, and it ignores clicks on the Close button.
(function wirePositionRowSelection() {
    const tbody = $('positions-body');
    if (!tbody) return;
    tbody.addEventListener('click', (e) => {
        if (e.target.closest('button') || e.target.closest('input')) return;
        const tr = e.target.closest('tr[data-poskey]');
        if (!tr) return;
        poschartPin(tr.dataset.poskey);
    });
})();

// ★ V18.2.7.3: chart timeframe buttons (chart display only — the strategies keep
//   using their own configured timeframes, this changes nothing about trading).
(function wirePosChartTimeframes() {
    $$('.tf-btn').forEach(b => {
        b.addEventListener('click', () => {
            const tf = b.dataset.tf;
            if (!tf || tf === POSCHART.tf) return;
            $$('.tf-btn').forEach(x => x.classList.remove('active'));
            b.classList.add('active');
            POSCHART.tf = tf;
            const key = POSCHART.key;
            if (key) {
                POSCHART.key = null;            // force a full reload of the new tf
                poschartSelect(key, state.positions || []);
            }
        });
    });
})();

window.closePosition = async (symbol, side) => {
    if (!confirm(`Close ${symbol} ${side} position?`)) return;
    const r = await api('POST', '/control/close-position', { symbol, side });
    // ### V18.2.7.5: a null response means the request itself failed (network
    // error, or the position was already gone).  Staying silent here is what let a
    // failed close look like a successful one.
    if (!r) {
        alert(`${symbol} ${side}: no usable response for the close request.\n` +
              `Verify on the exchange whether the position is still open, and check the logs.`);
        return;
    }
    // ### V18.2.7.5: ok === false means the exchange still holds it.
    if (r && r.ok === false) {
        alert(`${symbol} ${side} could NOT be closed - the exchange still holds it` +
              (r.residual ? ` (${r.residual})` : '') + `.\n` +
              `The position stays in the list and will be retried. Check the logs.`);
    }
    if (r) loadPositions();
};

$('btn-close-all').addEventListener('click', async () => {
    if (!confirm('Close all positions?')) return;
    const r = await api('POST', '/control/close-all');
    if (r) {
        // ### V18.2.7.5: r.failed are positions the exchange STILL holds.
        const failed = Number(r.failed || 0);
        if (failed > 0) {
            alert(`${r.closed} closed, ${failed} STILL OPEN on the exchange.\n` +
                  `They are kept in the list and will be retried - check the logs.`);
        } else {
            alert(`${r.closed} closed`);
        }
        loadPositions();
    }
});

$('btn-sync-pos').addEventListener('click', async () => {
    await api('POST', '/control/sync-positions');
    loadPositions();
});

// ============================================================
// ============================================================
// ============================================================
async function loadSymbols() {
    const useFilter = $('filter-toggle') && $('filter-toggle').checked;
    const excludeBottom = $('exclude-bottom-toggle') && $('exclude-bottom-toggle').checked;
    
    let url;
    if (useFilter) {
        url = '/symbols/filtered';
        if (excludeBottom) {
            url += '?exclude_low_volume=20';
        }
    } else {
        url = '/symbols';
        url += `?exclude_low_volume=${excludeBottom ? 20 : 0}`;
    }
    
    const d = await api('GET', url);
    if (!d) return;
    state.symbols = d.items;
    state.symbolsTotalCount = d.total_count;
    state.symbolsFilteredCount = d.filtered_count !== undefined 
        ? d.filtered_count : (d.items ? d.items.length : 0);
    state.selectedSymbols = new Set(d.selected);
    updateSymbolCountBadge();
    renderSymbols();
}

async function refreshSymbols() {
    const btn = $('btn-refresh-symbols');
    const originalText = btn ? btn.textContent : '';
    if (btn) {
        btn.disabled = true;
        btn.textContent = '🔄 Refreshing...';
    }
    
    try {
        const r = await api('POST', '/symbols/refresh');
        if (r && r.ok) {
            await loadSymbols();
            const msg = `✅ Refresh complete\n\n` +
                `Total symbols: ${r.total}\n` +
                `Passed filter: ${r.filtered}\n` +
                `Tickers updated: ${r.ticker_count}`;
            showToast(msg, 'success');
        } else {
            showToast(`Refresh failed: ${r ? r.error : 'no response'}`, 'error');
        }
    } catch (e) {
        showToast('Refresh error: ' + e, 'error');
    } finally {
        if (btn) {
            btn.disabled = false;
            btn.textContent = originalText || '🔄 Refresh';
        }
    }
}

function showToast(msg, type) {
    let toast = document.getElementById('toast-notification');
    if (!toast) {
        toast = document.createElement('div');
        toast.id = 'toast-notification';
        toast.style.cssText = `
            position: fixed; bottom: 20px; right: 20px; 
            padding: 14px 20px; border-radius: 8px;
            background: #14141f; color: #fff; 
            font-size: 13px; line-height: 1.5;
            white-space: pre-line; z-index: 9999;
            box-shadow: 0 4px 16px rgba(0,0,0,0.5);
            transition: opacity 0.3s;
        `;
        document.body.appendChild(toast);
    }
    toast.style.borderLeft = type === 'success' 
        ? '4px solid #4caf50' 
        : (type === 'error' ? '4px solid #f44' : '4px solid #4080c0');
    toast.textContent = msg;
    toast.style.opacity = '1';
    setTimeout(() => {
        toast.style.opacity = '0';
        setTimeout(() => { 
            if (toast.parentNode) toast.parentNode.removeChild(toast); 
        }, 400);
    }, 4000);
}

function updateSymbolCountBadge() {
    const total = state.symbolsTotalCount || state.symbols.length;
    const filtered = state.symbolsFilteredCount || state.symbols.length;
    const selected = state.selectedSymbols.size;
    const visible = getFilteredSymbols().length;
    const q = ($('symbol-search').value || '').trim();
    
    const useFilter = $('filter-toggle') && $('filter-toggle').checked;
    const excludeBottom = $('exclude-bottom-toggle') && $('exclude-bottom-toggle').checked;
    
    let msg = '';
    if (q) {
        msg = `(Total ${total} / Shown ${filtered} / Search results ${visible} / Selected ${selected})`;
    } else {
        msg = `(Total ${total} / Shown ${filtered} / Selected ${selected})`;
        const notes = [];
        if (useFilter) notes.push('Volume filter applied (~526)');
        if (excludeBottom) notes.push('Bottom 20 excluded');
        if (notes.length > 0) {
            msg += ` ※ ${notes.join(', ')}`;
        }
    }
    $('sym-count-badge').textContent = msg;
}

function getFilteredSymbols() {
    const q = ($('symbol-search').value || '').toUpperCase().trim();
    let result = state.symbols;
    if (q) {
        result = result.filter(s => s.symbol.toUpperCase().includes(q));
    }
    const sortBy = ($('symbol-sort') && $('symbol-sort').value) || 'volume';
    if (sortBy === 'alpha') {
        result = [...result].sort((a, b) => a.symbol.localeCompare(b.symbol));
    } else if (sortBy === 'change') {
        result = [...result].sort((a, b) => (b.change_24h || 0) - (a.change_24h || 0));
    } else {
        result = [...result].sort((a, b) => (b.volume_24h || 0) - (a.volume_24h || 0));
    }
    return result;
}

function renderSymbols() {
    const grid = $('symbols-grid');
    grid.innerHTML = '';
    const filtered = getFilteredSymbols();
    
    const frag = document.createDocumentFragment();
    
    filtered.forEach(s => {
        const isSelected = state.selectedSymbols.has(s.symbol);
        const div = document.createElement('div');
        div.className = 'symbol-item' + (isSelected ? ' selected' : '');
        div.innerHTML = `
            <div class="symbol-checkbox">
                <input type="checkbox" ${isSelected ? 'checked' : ''} 
                       data-symbol="${s.symbol}" tabindex="-1">
            </div>
            <div class="symbol-content">
                <div>
                    <div class="symbol-name">${s.symbol}</div>
                    <div class="symbol-info">$${fmtNumber(s.last_price, 4)}</div>
                </div>
                <div class="symbol-info">
                    <div class="${pctClass(s.change_24h)}">${fmtPct(s.change_24h)}</div>
                    <div>${(s.volume_24h / 1e6).toFixed(1)}M</div>
                </div>
            </div>
        `;
        div.addEventListener('click', (e) => {
            if (state.selectedSymbols.has(s.symbol)) {
                state.selectedSymbols.delete(s.symbol);
                div.classList.remove('selected');
                div.querySelector('input').checked = false;
            } else {
                state.selectedSymbols.add(s.symbol);
                div.classList.add('selected');
                div.querySelector('input').checked = true;
            }
            updateSymbolCountBadge();
        });
        frag.appendChild(div);
    });
    
    grid.appendChild(frag);
    updateSymbolCountBadge();
}

$('symbol-search').addEventListener('input', () => renderSymbols());
const sortSelect = $('symbol-sort');
if (sortSelect) sortSelect.addEventListener('change', () => renderSymbols());

const btnRefresh = $('btn-refresh-symbols');
if (btnRefresh) btnRefresh.addEventListener('click', refreshSymbols);

const filterToggle = $('filter-toggle');
if (filterToggle) filterToggle.addEventListener('change', () => loadSymbols());

const excludeBottomToggle = $('exclude-bottom-toggle');
if (excludeBottomToggle) excludeBottomToggle.addEventListener('change', () => loadSymbols());

$('btn-select-all').addEventListener('click', () => {
    state.symbols.forEach(s => state.selectedSymbols.add(s.symbol));
    renderSymbols();
});
$('btn-clear-all').addEventListener('click', () => {
    state.selectedSymbols.clear();
    renderSymbols();
});
$('btn-select-top20').addEventListener('click', async () => {
    const r = await api('POST', '/symbols/top20');
    if (r) {
        state.selectedSymbols = new Set(r.selected);
        renderSymbols();
    }
});
$('btn-confirm-symbols').addEventListener('click', async () => {
    const symbols = Array.from(state.selectedSymbols);
    if (symbols.length === 0) {
        if (!confirm('No symbols selected. Deselect all?')) return;
    }

    const progressBox = $('candle-load-progress');
    const label = $('progress-label');
    const counter = $('progress-counter');
    const fill = $('progress-bar-fill');
    const currentSym = $('progress-current-symbol');
    
    if (progressBox) {
        progressBox.style.display = 'block';
        label.textContent = 'Loading candle data (trading continues in the meantime)';
        counter.textContent = `0 / ${symbols.length}`;
        fill.style.width = '0%';
        currentSym.textContent = 'Preparing...';
    }

    const selectPromise = api('POST', '/symbols/select', { symbols });

    let pollTimer = null;
    if (progressBox && symbols.length > 0) {
        pollTimer = setInterval(async () => {
            try {
                const p = await api('GET', '/symbols/load-progress');
                if (!p) return;
                const total = p.total || symbols.length;
                const loaded = p.loaded || 0;
                counter.textContent = `${loaded} / ${total}`;
                fill.style.width = total > 0 ? `${(loaded / total * 100).toFixed(1)}%` : '0%';
                if (p.current_symbol) {
                    currentSym.textContent = `Recent: ${p.current_symbol}`;
                }
                if (!p.running) {
                    label.textContent = '✅ Candle load complete → new symbols now included in trade evaluation';
                    fill.style.width = '100%';
                    clearInterval(pollTimer);
                    setTimeout(() => { progressBox.style.display = 'none'; }, 3000);
                    if (typeof showToast === 'function') {
                        showToast(`✅ Loaded candles for ${total} symbols\nTrading continues in progress`, 'success');
                    }
                }
            } catch (e) { }
        }, 300);
    }

    const r = await selectPromise;
    if (r) {
        console.log(`${r.count} selections saved - trading continues automatically`);
    }
});

$('btn-clear-saved-list').addEventListener('click', async () => {
    if (!confirm('This will delete the saved coin list in config.json.\n(Other settings such as strategies remain unchanged.)\n\nContinue?')) return;
    const r = await api('POST', '/symbols/clear');
    if (r && r.ok) {
        state.selectedSymbols.clear();
        renderSymbols();
        const msg = `🗑 Saved coin list cleared (${r.cleared} removed)`;
        if (typeof showToast === 'function') showToast(msg, 'success');
        else alert(msg);
    }
});

// ============================================================
// ============================================================
async function loadRecords() {
    const f = $('record-filter').value;
    let qs = '';
    if (f) {
        const [k, v] = f.split(':');
        qs = `?${k}=${v}`;
    }
    const d = await api('GET', '/records' + qs);
    if (!d) return;
    state.records = d.items;
    state.statistics = d.statistics;
    state.statistics_real = d.statistics_real || {};
    state.statistics_shadow = d.statistics_shadow || {};
    state.records_meta = d.meta || {};
    renderRecordsStats();
    renderRecords();
    renderStatsContext();
}

function renderRecordsStats() {
    const s = state.statistics || {};
    $('stat-today-pnl').textContent = fmtUsdt(s.today_pnl || 0);
    $('stat-today-pnl').className = 'stat-value ' + pctClass(s.today_pnl || 0);
    $('stat-total-pnl').textContent = fmtUsdt(s.total_pnl || 0);
    $('stat-total-pnl').className = 'stat-value ' + pctClass(s.total_pnl || 0);
    $('stat-win-rate').textContent = fmtPct(s.win_rate || 0);
    $('stat-avg-roe').textContent = fmtPct(s.avg_roe || 0);
    $('stat-avg-roe').className = 'stat-value ' + pctClass(s.avg_roe || 0);
    $('stat-max-profit').textContent = fmtUsdt(s.max_profit || 0);
    $('stat-max-profit').className = 'stat-value up';
    $('stat-max-loss').textContent = fmtUsdt(s.max_loss || 0);
    $('stat-max-loss').className = 'stat-value down';
}

function renderStatsContext() {
    const meta = state.records_meta || {};
    const sReal = state.statistics_real || {};
    const sShadow = state.statistics_shadow || {};

    let banner = $('stats-context-banner');
    if (!banner) {
        banner = document.createElement('div');
        banner.id = 'stats-context-banner';
        banner.style.cssText = 'padding:10px 14px; margin-bottom:12px; border-radius:6px; font-size:13px; line-height:1.5;';
        const statsBox = document.querySelector('.stats-grid') || $('stat-today-pnl')?.closest('.records-stats');
        if (statsBox && statsBox.parentNode) {
            statsBox.parentNode.insertBefore(banner, statsBox);
        }
    }

    const realCount = meta.real_count || 0;
    const shadowCount = meta.shadow_count || 0;

    if (meta.shadow_mode_enabled) {
        banner.style.background = '#1a2a3a';
        banner.style.border = '1px solid #2a4a6a';
        banner.style.color = '#7cb7ff';
        banner.innerHTML = `
            🪞 <strong>Shadow Mode active</strong> — the stats below are
            <strong>${meta.include_shadow_used ? 'live trading + Shadow combined' : 'live trading only'}</strong>.
            <span style="color:#5f6b7d; margin-left:8px;">(Live ${realCount} / Shadow ${shadowCount})</span>
            ${shadowCount > 0 ? `<br><span style="color:#9eecb6; font-size:12px;">Shadow only: PNL ${fmtUsdt(sShadow.total_pnl || 0)}, ${sShadow.total_trades || 0} trades, win rate ${fmtPct(sShadow.win_rate || 0)}</span>` : ''}
            ${realCount > 0 ? `<br><span style="color:#fc9; font-size:12px;">Live trading only: PNL ${fmtUsdt(sReal.total_pnl || 0)}, ${sReal.total_trades || 0} trades, win rate ${fmtPct(sReal.win_rate || 0)}</span>` : ''}
        `;
    } else if (realCount === 0 && shadowCount > 0) {
        banner.style.background = '#2a2419';
        banner.style.border = '1px solid #5a4419';
        banner.style.color = '#ffd';
        banner.innerHTML = `
            ⚠️ No live trading records (${shadowCount} Shadow trades exist).
            Currently showing: <strong>Shadow combined</strong> stats.
            Will switch automatically once live trading starts.
        `;
    } else if (shadowCount > 0) {
        banner.style.background = '#1f2a1f';
        banner.style.border = '1px solid #3a5a3a';
        banner.style.color = '#cfc';
        banner.innerHTML = `
            ✅ Live trading mode — shown stats are <strong>live trading only</strong> (${realCount}).
            <span style="color:#5f6b7d;">Shadow trades (${shadowCount}) are excluded from stats.</span>
        `;
    } else {
        banner.style.display = 'none';
        return;
    }
    banner.style.display = '';
}

function renderRecords() {
    const tbody = $('records-body');
    tbody.innerHTML = '';
    if (!state.records.length) {
        tbody.innerHTML = '<tr><td colspan="12" style="text-align:center;color:#8b92a6">No records</td></tr>';
        return;
    }
    // ★ V18.2.7.3: newest record on top.  DISPLAY ORDER ONLY — data/records.json,
    //   the statistics and the CSV export are all untouched.
    //   data-idx now carries `index`, the row's position in the FULL server-side
    //   record list, because POST /api/records/delete deletes by that index.
    //   (Previously it carried the index within the FILTERED list, so deleting
    //   while a strategy/side/period filter was active removed the wrong rows.)
    const _rows = state.records.slice().reverse();
    _rows.forEach((r, i) => {
        const _delIdx = (Number.isInteger(r.index) && r.index >= 0)
            ? r.index : (state.records.length - 1 - i);
        const isWin = r.pnl > 0;
        const tr = document.createElement('tr');
        if (r.is_shadow) {
            tr.style.background = 'rgba(60,80,120,0.12)';
        }
        const symbolCell = r.is_shadow
            ? `<strong>${r.symbol}</strong> <span style="font-size:10px; padding:1px 5px; background:#2a4a6a; color:#7cb7ff; border-radius:3px; margin-left:4px;">SHADOW</span>`
            : `<strong>${r.symbol}</strong>`;
        tr.innerHTML = `
            <td><input type="checkbox" class="rec-check" data-idx="${_delIdx}"></td>
            <td>${symbolCell}</td>
            <td><span class="side-${r.position.toLowerCase()}">${r.position}</span></td>
            <td>${getStrategyDisplay(r.entry_type)}</td>
            <td>${r.time_frame || '-'}</td>
            <td>${fmtNumber(r.entry_price, 4)}</td>
            <td>${fmtNumber(r.close_price, 4)}</td>
            <td>${fmtNumber(r.close_count, 4)}</td>
            <td class="pnl-${isWin ? 'pos' : 'neg'}">${fmtUsdt(r.pnl)}</td>
            <td class="roe-${isWin ? 'pos' : 'neg'}">${fmtPct(r.roe)}</td>
            <td>${r.split_count}</td>
            <td style="font-size:11px">${r.trading_time || '-'}</td>
        `;
        tbody.appendChild(tr);
    });
}

$('record-filter').addEventListener('change', loadRecords);
$('rec-select-all').addEventListener('change', (e) => {
    $$('.rec-check').forEach(c => c.checked = e.target.checked);
});
$('btn-delete-selected').addEventListener('click', async () => {
    const checks = Array.from($$('.rec-check')).filter(c => c.checked);
    if (!checks.length) { alert('No items selected.'); return; }
    if (!confirm(`Delete ${checks.length} records?`)) return;
    // ★ V18.2.7.3: only well-formed indices are sent (see renderRecords).
    const indices = checks.map(c => parseInt(c.dataset.idx, 10))
                          .filter(n => Number.isInteger(n) && n >= 0);
    if (!indices.length) { alert('No items selected.'); return; }
    const r = await api('POST', '/records/delete', { indices });
    if (r) loadRecords();
});

// ============================================================
// ============================================================

const BREAKOUT_FIELDS = [
    { section: 'LONG' },
    { name: 'LongUSDT', label: 'Long USDT', type: 'number', step: 0.1 },
    { name: 'LongLeverage', label: 'Long leverage', type: 'number' },
    { name: 'LongPositionCnt', label: 'Long holdings', type: 'number' },
    { name: 'LongSplitCnt', label: 'Long split count', type: 'number' },
    { name: 'LongSplitAdd', label: 'Long SplitAdd%', type: 'number', step: 1 },
    { name: 'LongSplitGap', label: 'Long SplitGap%', type: 'number', step: 0.1 },
    { section: 'SHORT' },
    { name: 'ShortUSDT', label: 'Short USDT', type: 'number', step: 0.1 },
    { name: 'ShortLeverage', label: 'Short leverage', type: 'number' },
    { name: 'ShortPositionCnt', label: 'Short holdings', type: 'number' },
    { name: 'ShortSplitCnt', label: 'Short split count', type: 'number' },
    { name: 'ShortSplitAdd', label: 'Short SplitAdd%', type: 'number', step: 1 },
    { name: 'ShortSplitGap', label: 'Short SplitGap%', type: 'number', step: 0.1 },
    { section: 'Close' },
    { name: 'Profit', label: 'Take profit %', type: 'number', step: 0.1 },
    { name: 'StopLoss', label: 'Stop loss %', type: 'number', step: 0.1 },
    { name: 'TrailingStop', label: 'Trailing %', type: 'number', step: 0.1 },
    { name: 'MinProfit', label: 'Min take profit %', type: 'number', step: 0.1 },
    { section: 'Timeframe / Volatility' },
    { name: 'Use1h', label: 'Use 1h', type: 'checkbox' },
    { name: 'Volatility1h', label: '1h volatility %', type: 'number', step: 0.1 },
    { name: 'Use4h', label: 'Use 4h', type: 'checkbox' },
    { name: 'Volatility4h', label: '4h volatility %', type: 'number', step: 0.1 },
    { name: 'Use1d', label: 'Use 1d', type: 'checkbox' },
    { name: 'Volatility1d', label: '1d volatility %', type: 'number', step: 0.1 },
    { section: 'Filter' },
    { name: 'FundingFilterEnabled', label: 'Funding filter', type: 'checkbox' },
    { name: 'LongMaxFundingRate', label: 'Long MaxFunding', type: 'number', step: 0.01 },
    { name: 'ShortMaxFundingRate', label: 'Short MaxFunding', type: 'number', step: 0.01 },
    { name: 'DepthGapFilterEnabled', label: 'DepthGap filter', type: 'checkbox' },
    { name: 'MaxDepthGap', label: 'Max DepthGap %', type: 'number', step: 0.1 },
];

const MR_FIELDS_COMMON = [
    { section: 'LONG' },
    { name: 'LongUSDT', label: 'Long USDT', type: 'number', step: 0.1 },
    { name: 'LongLeverage', label: 'Long leverage', type: 'number' },
    { name: 'LongPositionCnt', label: 'Long holdings', type: 'number' },
    { name: 'LongSplitCnt', label: 'Long split count', type: 'number' },
    { name: 'LongSplitGap', label: 'Long SplitGap%', type: 'number', step: 0.1 },
    { section: 'SHORT' },
    { name: 'ShortUSDT', label: 'Short USDT', type: 'number', step: 0.1 },
    { name: 'ShortLeverage', label: 'Short leverage', type: 'number' },
    { name: 'ShortPositionCnt', label: 'Short holdings', type: 'number' },
    { name: 'ShortSplitCnt', label: 'Short split count', type: 'number' },
    { name: 'ShortSplitGap', label: 'Short SplitGap%', type: 'number', step: 0.1 },
    { section: 'Close' },
    { name: 'Profit', label: 'Take profit %', type: 'number', step: 0.1 },
    { name: 'StopLoss', label: 'Stop loss %', type: 'number', step: 0.1 },
    { name: 'TrailingStop', label: 'Trailing %', type: 'number', step: 0.1 },
    { name: 'MinProfit', label: 'Min take profit %', type: 'number', step: 0.1 },
];

const MR_FIELDS_EXTRA = [
    { section: 'Use timeframe (LONG)' },
    { name: 'LongUse1m', label: '1m', type: 'checkbox' },
    { name: 'LongUse15m', label: '15m', type: 'checkbox' },
    { name: 'LongUse1h', label: '1h', type: 'checkbox' },
    { name: 'LongUse4h', label: '4h', type: 'checkbox' },
    { section: 'BB std dev (LONG)' },
    { name: 'LongStandard1m', label: '1m std', type: 'number', step: 0.1 },
    { name: 'LongStandard15m', label: '15m std', type: 'number', step: 0.1 },
    { name: 'LongStandard1h', label: '1h std', type: 'number', step: 0.1 },
    { name: 'LongStandard4h', label: '4h std', type: 'number', step: 0.1 },
    { section: 'Use timeframe (SHORT)' },
    { name: 'ShortUse1m', label: '1m', type: 'checkbox' },
    { name: 'ShortUse15m', label: '15m', type: 'checkbox' },
    { name: 'ShortUse1h', label: '1h', type: 'checkbox' },
    { name: 'ShortUse4h', label: '4h', type: 'checkbox' },
    { section: 'BB std dev (SHORT)' },
    { name: 'ShortStandard1m', label: '1m std', type: 'number', step: 0.1 },
    { name: 'ShortStandard15m', label: '15m std', type: 'number', step: 0.1 },
    { name: 'ShortStandard1h', label: '1h std', type: 'number', step: 0.1 },
    { name: 'ShortStandard4h', label: '4h std', type: 'number', step: 0.1 },
    { section: 'Volatility threshold (shared)' },
    { name: 'Volatility1m', label: '1m', type: 'number', step: 0.1 },
    { name: 'Volatility15m', label: '15m', type: 'number', step: 0.1 },
    { name: 'Volatility1h', label: '1h', type: 'number', step: 0.1 },
    { name: 'Volatility4h', label: '4h', type: 'number', step: 0.1 },
    { section: 'Trailing entry' },
    { name: 'TrailingEntryEnabled', label: 'Enabled', type: 'checkbox' },
    { name: 'TrailingEntryPercent', label: 'Retracement %', type: 'number', step: 0.1 },
    { name: 'TrailingEntryTimeout', label: 'Timeout(sec)', type: 'number' },
    { section: 'Filter' },
    { name: 'FundingFilterEnabled', label: 'Funding filter', type: 'checkbox' },
    { name: 'LongMaxFundingRate', label: 'Long MaxFunding', type: 'number', step: 0.01 },
    { name: 'ShortMaxFundingRate', label: 'Short MaxFunding', type: 'number', step: 0.01 },
    { name: 'DepthGapFilterEnabled', label: 'DepthGap filter', type: 'checkbox' },
    { name: 'MaxDepthGap', label: 'Max DepthGap %', type: 'number', step: 0.1 },
    { section: 'Long cooldown' },
    { name: 'LongCloseCooldownEnabled', label: 'Enabled', type: 'checkbox' },
    { name: 'LongCloseCooldownHours', label: 'Time', type: 'number' },
];

const UA_FIELDS_EXTRA = [
    { section: 'Use timeframe (LONG)' },
    { name: 'LongUse1m', label: '1m', type: 'checkbox' },
    { name: 'LongUse15m', label: '15m', type: 'checkbox' },
    { name: 'LongUse1h', label: '1h', type: 'checkbox' },
    { name: 'LongUse4h', label: '4h', type: 'checkbox' },
    { section: 'BB std dev (LONG)' },
    { name: 'LongStandard1m', label: '1m std', type: 'number', step: 0.1 },
    { name: 'LongStandard15m', label: '15m std', type: 'number', step: 0.1 },
    { name: 'LongStandard1h', label: '1h std', type: 'number', step: 0.1 },
    { name: 'LongStandard4h', label: '4h std', type: 'number', step: 0.1 },
    { section: 'LONG volatility' },
    { name: 'LongVolatility1m', label: '1m', type: 'number', step: 0.1 },
    { name: 'LongVolatility15m', label: '15m', type: 'number', step: 0.1 },
    { name: 'LongVolatility1h', label: '1h', type: 'number', step: 0.1 },
    { name: 'LongVolatility4h', label: '4h', type: 'number', step: 0.1 },
    { section: 'Use timeframe (SHORT)' },
    { name: 'ShortUse1m', label: '1m', type: 'checkbox' },
    { name: 'ShortUse15m', label: '15m', type: 'checkbox' },
    { name: 'ShortUse1h', label: '1h', type: 'checkbox' },
    { name: 'ShortUse4h', label: '4h', type: 'checkbox' },
    { section: 'BB std dev (SHORT)' },
    { name: 'ShortStandard1m', label: '1m std', type: 'number', step: 0.1 },
    { name: 'ShortStandard15m', label: '15m std', type: 'number', step: 0.1 },
    { name: 'ShortStandard1h', label: '1h std', type: 'number', step: 0.1 },
    { name: 'ShortStandard4h', label: '4h std', type: 'number', step: 0.1 },
    { section: 'SHORT volatility' },
    { name: 'ShortVolatility1m', label: '1m', type: 'number', step: 0.1 },
    { name: 'ShortVolatility15m', label: '15m', type: 'number', step: 0.1 },
    { name: 'ShortVolatility1h', label: '1h', type: 'number', step: 0.1 },
    { name: 'ShortVolatility4h', label: '4h', type: 'number', step: 0.1 },
    { section: 'Trailing entry' },
    { name: 'TrailingEntryEnabled', label: 'Enabled', type: 'checkbox' },
    { name: 'TrailingEntryPercent', label: 'Retracement %', type: 'number', step: 0.1 },
    { name: 'TrailingEntryTimeout', label: 'Timeout(sec)', type: 'number' },
    { section: 'Filter' },
    { name: 'FundingFilterEnabled', label: 'Funding filter', type: 'checkbox' },
    { name: 'LongMaxFundingRate', label: 'Long MaxFunding', type: 'number', step: 0.01 },
    { name: 'ShortMaxFundingRate', label: 'Short MaxFunding', type: 'number', step: 0.01 },
    { name: 'DepthGapFilterEnabled', label: 'DepthGap filter', type: 'checkbox' },
    { name: 'MaxDepthGap', label: 'Max DepthGap %', type: 'number', step: 0.1 },
    { section: 'Long cooldown' },
    { name: 'LongCloseCooldownEnabled', label: 'Enabled', type: 'checkbox' },
    { name: 'LongCloseCooldownHours', label: 'Time', type: 'number' },
];

// ──────────────────────────────────────────────────────────────
// 
// ──────────────────────────────────────────────────────────────

const ADVANCED_FIELDS = [
    { section: 'Close action' },
    { name: 'TrailingStopUse', label: 'Use trailing stop', type: 'checkbox' },
    { name: 'ProfitUse', label: 'Use take profit (usually OFF)', type: 'checkbox' },
    { name: 'StopLossUse', label: 'Use stop loss (usually OFF)', type: 'checkbox' },
    { name: 'ReverseExit', label: 'Close on opposite signal', type: 'checkbox' },
    { name: 'LongStopLossSkip', label: 'Block Long stop loss', type: 'checkbox' },
    { name: 'ShortStopLossSkip', label: 'Block Short stop loss', type: 'checkbox' },
    
    { section: 'Additional entry conditions' },
    { name: 'AddConditionSkip', label: 'Skip split-entry additional condition (recommended for JaviDAgrt only)', type: 'checkbox' },
    { name: 'FundingFeeCheckEnabled', label: 'Avoid funding time', type: 'checkbox' },
    
    { section: 'Auto investment amount (balance-based)' },
    { name: 'LongAutoInputCtrl', label: 'Long auto-input divisor', type: 'number', step: 1 },
    { name: 'ShortAutoInputCtrl', label: 'Short auto-input divisor', type: 'number', step: 1 },
    
    { section: 'Split entry' },
    { name: 'SplitSquaredEnabled', label: 'Order-squared spacing', type: 'checkbox' },
    { name: 'SplitIntervalMultiplier', label: 'Spacing multiplier', type: 'number', step: 0.1 },
    { name: 'SplitMultiplierStartAt', label: 'Multiplier start order', type: 'number' },
    
    { section: '⚠️ Advanced (disables all strategies)', advanced: true },
    { name: 'LongOpenSkip', label: 'Block Long entry (recommended for Breakout only)', type: 'checkbox' },
    { name: 'ShortOpenSkip', label: 'Block Short entry', type: 'checkbox' },
    { name: 'LongCloseSkip', label: 'Block Long close (risky)', type: 'checkbox' },
    { name: 'ShortCloseSkip', label: 'Block Short close (risky)', type: 'checkbox' },
    
    { section: '⚠️ Advanced (experimental / usually OFF)', advanced: true },
    { name: 'UseFixedTrade', label: 'Fixed amount mode', type: 'checkbox' },
    { name: 'TodayLossFilterEnabled', label: 'Block entry after today\'s loss', type: 'checkbox' },
    { name: 'RebalanceEnabled', label: 'Rebalance', type: 'checkbox' },
    { name: 'RebalanceHours', label: 'Rebalance time', type: 'number' },
    { name: 'RebalanceLossPercent', label: 'Rebalance loss % threshold', type: 'number', step: 0.1 },
];

function buildForm(containerId, fields, values, prefix) {
    const c = $(containerId);
    c.innerHTML = '';
    
    let currentContainer = c;
    let inAdvanced = false;
    
    fields.forEach(f => {
        if (f.section) {
            if (f.advanced && !inAdvanced) {
                inAdvanced = true;
                const det = document.createElement('details');
                det.className = 'advanced-section';
                const sum = document.createElement('summary');
                sum.textContent = f.section + ' (click to expand)';
                sum.style.cssText = 'cursor:pointer; padding:8px; background:#2a1a1a; '
                                  + 'border:1px solid #f44; border-radius:4px; '
                                  + 'color:#ff8; font-weight:600; margin:12px 0 8px;';
                det.appendChild(sum);
                c.appendChild(det);
                currentContainer = det;
                return;
            }
            const h = document.createElement('div');
            h.className = 'section-divider';
            h.textContent = f.section;
            currentContainer.appendChild(h);
            return;
        }
        const grp = document.createElement('div');
        grp.className = 'field-group';
        const lbl = document.createElement('label');
        lbl.textContent = f.label;
        const input = document.createElement('input');
        input.type = f.type;
        input.id = `${prefix}-${f.name}`;
        input.dataset.field = f.name;
        input.dataset.type = f.type;
        if (f.step) input.step = f.step;
        const v = values?.[f.name];
        if (v !== undefined && v !== null) {
            if (f.type === 'checkbox') input.checked = v;
            else input.value = v;
        }
        grp.appendChild(lbl);
        grp.appendChild(input);
        currentContainer.appendChild(grp);
    });
}

function collectForm(containerId) {
    const c = $(containerId);
    const result = {};
    c.querySelectorAll('input').forEach(input => {
        const name = input.dataset.field;
        const type = input.dataset.type;
        if (!name) return;
        if (type === 'checkbox') result[name] = input.checked;
        else if (type === 'number') result[name] = parseFloat(input.value) || 0;
        else result[name] = input.value;
    });
    return result;
}

async function loadSettings() {
    const cfg = await api('GET', '/config');
    if (!cfg) return;
    state.config = cfg;
    $('cfg-access-key').value = cfg.AccessKey || '';
    $('cfg-two-way').checked = !!cfg.UseTwoWayTrading;
    $('cfg-fixed-trade').checked = !!cfg.UseFixedTrade;
    $('cfg-profit-close-filter').checked = !!cfg.ProfitCloseFilterEnabled;

    buildForm('form-breakout', BREAKOUT_FIELDS.concat(ADVANCED_FIELDS), cfg.BreakoutTarget, 'bo');
    buildForm('form-mean-revert', MR_FIELDS_COMMON.concat(MR_FIELDS_EXTRA).concat(ADVANCED_FIELDS), 
               cfg.MeanRevertTarget, 'mr');
    buildForm('form-javid-agrt', MR_FIELDS_COMMON.concat(UA_FIELDS_EXTRA).concat(ADVANCED_FIELDS), 
               cfg.JaviDAgrtTarget, 'ua');

    await loadActiveFeatures();
}

// ============================================================
// ============================================================
async function loadActiveFeatures() {
    const data = await api('GET', '/active-features');
    if (!data) return;
    renderActiveFeatures(data);
}

function renderActiveFeatures(data) {
    if (!document.getElementById('active-features-list')) return;
    const list = $('active-features-list');
    const counter = $('active-features-count');
    if (!list || !counter) return;

    counter.textContent = data.summary;
    counter.style.background = data.enabled_count >= data.total - 1 ? '#0e3b34' : '#132436';
    counter.style.color = data.enabled_count >= data.total - 1 ? '#2dd4bf' : '#7cb7ff';

    list.innerHTML = '';
    for (const f of data.features) {
        const card = document.createElement('div');
        card.className = 'feature-card ' + (f.enabled ? 'enabled' : 'disabled');
        card.dataset.key = f.key;

        const thresholdsStr = Object.entries(f.thresholds || {})
            .map(([k, v]) => `${k}=${v}`).join(', ');

        card.innerHTML = `
            <div class="feature-status-dot"></div>
            <div class="feature-body">
                <div class="feature-header">
                    <div>
                        <span class="feature-label">${escapeHtml(f.label)}</span>
                        <span class="applied-badge">${f.enabled ? 'Active' : 'Inactive'}</span>
                        <span class="feature-tag">${escapeHtml(f.applied_to)}</span>
                    </div>
                    <label class="feature-toggle">
                        <input type="checkbox" data-feature-key="${f.key}" ${f.enabled ? 'checked' : ''}>
                        <span class="feature-slider"></span>
                    </label>
                </div>
                <div class="feature-desc">${escapeHtml(f.description)}</div>
                ${thresholdsStr ? `<div class="feature-thresholds">⚙ ${escapeHtml(thresholdsStr)}</div>` : ''}
            </div>
        `;
        list.appendChild(card);
    }

    list.querySelectorAll('input[data-feature-key]').forEach(input => {
        input.addEventListener('change', async (e) => {
            const key = e.target.dataset.featureKey;
            const enabled = e.target.checked;

            const card = e.target.closest('.feature-card');
            card.style.opacity = '0.6';
            const badge = card.querySelector('.applied-badge');
            if (badge) badge.textContent = 'Saving…';

            const result = await api('PUT', '/config', { [key]: enabled });
            card.style.opacity = '';

            if (result) {
                await loadActiveFeatures();
            } else {
                e.target.checked = !enabled;
                if (badge) badge.textContent = enabled ? 'Inactive' : 'Active';
                alert(`Failed to save ${key} settings`);
            }
        });
    });
}

function escapeHtml(s) {
    if (s == null) return '';
    return String(s)
        .replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;')
        .replace(/"/g, '&quot;').replace(/'/g, '&#39;');
}

$('btn-save-api-keys').addEventListener('click', async () => {
    const access_key = $('cfg-access-key').value;
    const secret_key = $('cfg-secret-key').value;
    if (!access_key || !secret_key) {
        alert('Please enter both Access Key and Secret Key.');
        return;
    }
    
    // V11.7: save + verify immediately
    const r = await api('POST', '/control/api-keys', { access_key, secret_key });
    
    let msg = '';
    if (r.format_issues && r.format_issues.length) {
        msg += '⚠️ Format issues:\n  - ' + r.format_issues.join('\n  - ') + '\n\n';
    }
    
    if (r.verification && r.verification.verified) {
        msg += `✅ Connection verified!\nBalance: ${r.verification.balance} USDT`;
        alert(msg);
    } else if (r.verification) {
        msg += `❌ Connection verification failed:\n${r.verification.error}\n\n`;
        msg += 'Check the "🔍 Run Connection Diagnostics" button for details.';
        alert(msg);
    } else {
        alert('Save complete (verification not performed)');
    }
});

$('btn-test-connection').addEventListener('click', async () => {
    const s = $('connection-status');
    s.textContent = 'Testing (up to 30s)...';
    s.className = 'conn-status';
    
    const r = await api('POST', '/control/test-connection');
    
    if (r && r.ok) {
        s.textContent = `✅ Connected OK (Balance: ${fmtNumber(r.balance)} USDT)`;
        s.className = 'conn-status ok';
    } else {
        s.textContent = `❌ Failed: ${r ? r.reason : 'no response'}`;
        s.className = 'conn-status err';
        s.title = r && r.reason ? r.reason : '';  // full message as tooltip
    }
});

// Save common settings immediately on change
['cfg-two-way', 'cfg-fixed-trade', 'cfg-profit-close-filter'].forEach(id => {
    $(id).addEventListener('change', async () => {
        await api('PUT', '/config', {
            UseTwoWayTrading: $('cfg-two-way').checked,
            UseFixedTrade: $('cfg-fixed-trade').checked,
            ProfitCloseFilterEnabled: $('cfg-profit-close-filter').checked,
        });
    });
});

// Strategy save button (V11.41: apply immediately + clear feedback)
$$('[data-save]').forEach(btn => {
    btn.addEventListener('click', async () => {
        const strategy = btn.dataset.save;
        let containerId, route;
        if (strategy === 'Breakout') { containerId = 'form-breakout'; route = '/strategies/breakout'; }
        else if (strategy === 'MeanRevert') { containerId = 'form-mean-revert'; route = '/strategies/mean-revert'; }
        else if (strategy === 'JaviDAgrt') { containerId = 'form-javid-agrt'; route = '/strategies/javid-agrt'; }
        else return;
        
        const target = collectForm(containerId);
        const enabledCheckbox = document.querySelector(`[data-toggle="${strategy}"]`);
        const enabled = enabledCheckbox ? enabledCheckbox.checked : true;
        
        // Change button state (show saving)
        const origText = btn.textContent;
        btn.disabled = true;
        btn.textContent = 'Saving...';
        
        try {
            const r = await api('PUT', route, { target, enabled });
            if (r && r.ok) {
                // V11.41: toast + button text to confirm applied immediately
                btn.textContent = '✅ Saved (applied immediately)';
                if (typeof showToast === 'function') {
                    showToast(`✅ ${getStrategyDisplay(strategy)} settings saved\nNow applied to trading`, 'success');
                } else {
                    alert(`${getStrategyDisplay(strategy)} save complete - changes are now applied to trading immediately.`);
                }
                // Reload form after save (reflect server-normalized values)
                if (typeof loadSettings === 'function') {
                    setTimeout(() => loadSettings(), 500);
                }
                setTimeout(() => { btn.textContent = origText; btn.disabled = false; }, 2000);
            } else {
                btn.textContent = '❌ Save failed';
                if (typeof showToast === 'function') {
                    showToast(`❌ ${getStrategyDisplay(strategy)} save failed`, 'error');
                }
                setTimeout(() => { btn.textContent = origText; btn.disabled = false; }, 2000);
            }
        } catch (e) {
            btn.textContent = '❌ Save error';
            if (typeof showToast === 'function') {
                showToast(`❌ Save error: ${e}`, 'error');
            }
            setTimeout(() => { btn.textContent = origText; btn.disabled = false; }, 2000);
        }
    });
});

// Strategy toggle (V11.41: immediate notification)
$$('[data-toggle]').forEach(cb => {
    cb.addEventListener('change', async () => {
        const strategy = cb.dataset.toggle;
        const route = {
            'Breakout': '/strategies/breakout/toggle',
            'MeanRevert': '/strategies/mean-revert/toggle',
            'JaviDAgrt': '/strategies/javid-agrt/toggle',
        }[strategy];
        if (route) {
            const r = await api('POST', route, { enabled: cb.checked });
            if (r && typeof showToast === 'function') {
                const status = cb.checked ? 'enabled' : 'disabled';
                showToast(`${getStrategyDisplay(strategy)} strategy ${status}`, 'success');
            }
        }
    });
});

// Apply javid_r original settings (V13.1.1 ★)
$$('.btn-javid_r').forEach(btn => {
    btn.addEventListener('click', async () => {
        const strategy = btn.dataset.strategy;

        if (strategy === 'ALL') {
            const msg = '⚠️ Applies the original values from\n' +
                        'UPRICH FUTURE BOT javid_r config.json to all 3 strategies (MeanRevert/Breakout/JaviDAgrt).\n\n' +
                        'Continue?';
            if (!confirm(msg)) return;
            const r = await api('POST', '/presets/load-original-javid_r-all');
            if (!r) return;
            const lines = ['✅ javid_r original applied (3 strategies)'];
            for (const [s, info] of Object.entries(r.summary || {})) {
                lines.push(`  ${s}: ${info.fields_applied} fields applied` +
                           (info.fields_unmapped.length ? ` (${info.fields_unmapped.length} unmapped)` : ''));
            }
            lines.push('');
            lines.push('Preserved V13.1 new features:');
            lines.push('  ' + (r.preserved_v131_features || []).join(', '));
            alert(lines.join('\n'));
        } else {
            const display = getStrategyDisplay(strategy);
            const msg = `Applies the original values from javid_r config.json to the ${display} strategy.\n\nContinue?`;
            if (!confirm(msg)) return;
            const r = await api('POST', `/presets/load-original-javid_r?strategy=${strategy}`);
            if (!r) return;
            const lines = [
                `✅ javid_r original applied (${display})`,
                `   ${r.fields_applied} fields applied` +
                    (r.fields_unmapped.length ? ` (${r.fields_unmapped.length} unmapped)` : ''),
                '',
                'Preserved V13.1 new features:',
                '  ' + (r.preserved_v131_features || []).join(', '),
            ];
            alert(lines.join('\n'));
        }
        loadSettings();
    });
});

// ============================================================
// Logs
// ============================================================
async function loadLogs() {
    const cat = $('log-category').value;
    const qs = cat ? `?category=${cat}&limit=500` : '?limit=500';
    const d = await api('GET', '/logs' + qs);
    if (!d) return;
    renderLogs(d.items);
}
function renderLogs(items) {
    const c = $('log-container');
    c.innerHTML = '';
    $('log-count').textContent = `${items.length} entries`;
    items.forEach(e => {
        const div = document.createElement('div');
        div.className = `log-line ${e.category} ${e.level}`;
        div.textContent = `[${e.timestamp}] [${e.category}] ${e.message}`;
        c.appendChild(div);
    });
    c.scrollTop = c.scrollHeight;
}
$('log-category').addEventListener('change', loadLogs);
$('btn-refresh-logs').addEventListener('click', loadLogs);

// ============================================================
// WebSocket real-time updates
// ============================================================
let ws;
function connectWS() {
    const proto = location.protocol === 'https:' ? 'wss' : 'ws';
    ws = new WebSocket(`${proto}://${location.host}/api/ws/status`);
    ws.onopen = () => console.log('WS connected');
    ws.onclose = () => {
        console.log('WS disconnected, reconnecting in 3s...');
        setTimeout(connectWS, 3000);
    };
    ws.onmessage = (ev) => {
        try {
            const msg = JSON.parse(ev.data);
            if (msg.type === 'status') {
                // V11.21: guard against invalid data (prevent flicker)
                if (!msg.data || typeof msg.data.usdt_balance !== 'number') {
                    return;  // ignore - keep last valid value
                }
                // V11.21: only update buttons when state changes
                const wasRunning = state.isRunning;
                state.isRunning = msg.data.is_running;
                if (wasRunning !== state.isRunning) {
                    updateStartStopButton();
                }
                // V11.20: use setTextIfChanged to prevent flicker
                const balLabel = (msg.data.is_shadow_mode || msg.data.is_virtual_balance)
                    ? fmtNumber(msg.data.usdt_balance) + ' USDT (virtual)'
                    : fmtNumber(msg.data.usdt_balance) + ' USDT';
                setTextIfChanged('usdt-balance', balLabel);
                setTextIfChanged('account-equity', fmtNumber(msg.data.account_equity) + ' USDT');
                setTextIfChanged('position-count-display', String(msg.data.position_count || 0));
            } else if (msg.type === 'positions') {
                // ★ V11.43-f: always update state.positions (show immediately on tab switch)
                state.positions = msg.data || [];
                
                // Render immediately if the positions tab is active
                if ($('view-positions') && $('view-positions').classList.contains('active')) {
                    renderPositions();
                }
                
                // Always update the position count badge
                const badge = $('pos-count-badge');
                if (badge) {
                    badge.textContent = `(${state.positions.length})`;
                }
                
                // Also update position-count on the main screen
                const posCount = $('position-count-display');
                if (posCount) {
                    posCount.textContent = String(state.positions.length);
                }
            } else if (msg.type === 'log') {
                // V11.43-d: throttle log messages - prevent DOM overload
                // If the same message arrives 8 times every 10 seconds, DOM can accumulate infinitely -> UI freeze
                // 1) Only append when the logs screen is active
                // 2) Cap at 500 entries (oldest auto-removed)
                if ($('view-logs') && $('view-logs').classList.contains('active')) {
                    const c = $('log-container');
                    if (c) {
                        const div = document.createElement('div');
                        div.className = `log-line ${msg.data.category} ${msg.data.level}`;
                        div.textContent = `[${msg.data.timestamp}] [${msg.data.category}] ${msg.data.message}`;
                        c.appendChild(div);
                        
                        // ★ V11.43-d: prevent DOM buildup - keep max 500
                        const MAX_LOG_LINES = 500;
                        while (c.children.length > MAX_LOG_LINES) {
                            c.removeChild(c.firstChild);
                        }
                        
                        // Auto-scroll - skip if user is scrolling
                        const isAtBottom = (c.scrollHeight - c.scrollTop - c.clientHeight) < 50;
                        if (isAtBottom) {
                            c.scrollTop = c.scrollHeight;
                        }
                    }
                }
            } else if (msg.type === 'trade') {
                console.log('Trade:', msg.data);
            }
        } catch (e) { console.error(e); }
    };
}

// ============================================================
// V7: Shadow Mode toggle
// ============================================================
async function loadShadowStatus() {
    try {
        const r = await api('GET', '/shadow/status');
        if (r) {
            const enabled = !!r.enabled;
            const cb = $('shadow-toggle');
            if (cb) cb.checked = enabled;
            updateShadowUI(enabled);
        }
    } catch (e) {}
}

function updateShadowUI(enabled) {
    const banner = $('shadow-banner');
    const txt = $('shadow-status-text');
    const badge = $('status-badge');
    if (banner) banner.style.display = enabled ? 'block' : 'none';
    if (txt) txt.textContent = enabled ? 'Shadow active (virtual)' : 'Live trading mode';
    if (badge) {
        if (enabled) badge.classList.add('shadow');
        else badge.classList.remove('shadow');
    }
}

async function toggleShadow(enabled) {
    try {
        const r = await api('POST', '/shadow/toggle', { enabled });
        if (r && r.success) {
            updateShadowUI(r.enabled);
            // ★ V18.1: after switching, show the actual open position count for the mode\'s ledger
            let msg = r.enabled
                ? '🪞 Shadow Mode turned on\nLive order placement disabled.\nSignals are logged to logs/SHADOW/.'
                : 'Switched to live trading mode.\nLive Binance orders are now enabled.';
            if (typeof r.loaded_count === 'number') {
                msg += `\n\n📌 Positions loaded for current mode: ${r.loaded_count} (reflected in trading logic)`;
            }
            if (r.warning) {
                msg += `\n\n⚠️ ${r.warning}`;
            }
            alert(msg);
            // Refresh positions view immediately (show swapped ledger)
            if (typeof loadPositions === 'function') loadPositions();
            if (typeof refreshStatus === 'function') refreshStatus();
        } else {
            // ★ V18.1: e.g. switch rejected while trading is running - revert toggle
            alert('⚠️ Mode switch failed:\n' + ((r && r.error) || 'no response from server'));
            const cb = $('shadow-toggle');
            if (cb && r && typeof r.enabled === 'boolean') {
                cb.checked = r.enabled;
                updateShadowUI(r.enabled);
            } else {
                await loadShadowStatus();
            }
        }
    } catch (e) {
        alert('Shadow toggle failed: ' + e);
        await loadShadowStatus();
    }
}

// ============================================================
// V9: Investment mode (Aggressive/Normal/Stable)
// ============================================================
// V11.23: investment mode - always shown + dimmed when inactive
// ============================================================
async function loadInvestmentMode() {
    try {
        const r = await api('GET', '/investment-mode');
        if (!r) return;
        
        const container = document.querySelector('.investment-mode-container');
        const cb = document.getElementById('investment-mode-toggle');
        const status = document.getElementById('investment-mode-status');
        
        if (cb) cb.checked = !!r.enabled;
        
        // V11.23: use 'enabled' class to dim (not display:none)
        if (container) {
            container.classList.toggle('enabled', !!r.enabled);
        }
        
        if (status) {
            if (r.enabled) {
                const labels = {AGGRESSIVE: '🔥 Aggressive', NORMAL: '⚖️ Normal', STABLE: '🛡️ Stable', CUSTOM: '🎛️ Custom'};
                status.textContent = `${labels[r.mode] || r.mode} (balance÷${r.divisor})`;
            } else {
                status.textContent = 'OFF (using fixed amount)';
            }
        }
        
        // Show active mode button
        document.querySelectorAll('.mode-btn').forEach(btn => {
            btn.classList.toggle('active', btn.dataset.mode === r.mode);
        });
        
        // Show balance/margin
        const balEl = document.getElementById('mode-balance');
        const marginEl = document.getElementById('mode-margin');
        if (balEl) balEl.textContent = (r.balance || 0).toFixed(2);
        if (marginEl) marginEl.textContent = (r.calculated_margin || 0).toFixed(3);
    } catch (e) {
        console.error('loadInvestmentMode failed:', e);
    }
}

async function toggleInvestmentMode(enabled) {
    try {
        await api('POST', '/investment-mode', { enabled });
        await loadInvestmentMode();
        if (enabled) {
            alert('💰 Investment Mode ON\n\nEntry amounts will now be determined automatically based on your balance.\nChoose from Aggressive/Normal/Stable.');
        }
    } catch (e) {
        alert('Mode switch failed: ' + e);
    }
}

async function setInvestmentMode(mode) {
    try {
        await api('POST', '/investment-mode', { mode });
        await loadInvestmentMode();
    } catch (e) {
        alert('Mode change failed: ' + e);
    }
}

// ============================================================
// Initialization
// ============================================================
(async function init() {
    setView('dashboard');
    
    // V7: Shadow toggle event
    const shadowCb = document.getElementById('shadow-toggle');
    if (shadowCb) {
        shadowCb.addEventListener('change', e => toggleShadow(e.target.checked));
    }
    await loadShadowStatus();
    
    // V9: investment mode toggle + mode buttons
    const invCb = document.getElementById('investment-mode-toggle');
    if (invCb) {
        invCb.addEventListener('change', e => toggleInvestmentMode(e.target.checked));
    }
    document.querySelectorAll('.mode-btn').forEach(btn => {
        btn.addEventListener('click', () => setInvestmentMode(btn.dataset.mode));
    });
    await loadInvestmentMode();
    
    await refreshStatus();
    connectWS();
    setInterval(refreshStatus, 5000);
    setInterval(() => {
        if ($('view-dashboard').classList.contains('active')) loadDashboard();
    }, 10000);
    setInterval(loadShadowStatus, 30000);
    setInterval(loadInvestmentMode, 30000);  // V9: refresh mode + balance
})();

// ============================================================
// V11: import original config.json
// ============================================================
async function importOriginalConfig(file) {
    if (!file) return;
    
    try {
        const text = await file.text();
        const cfgJson = JSON.parse(text);
        
        const r = await api('POST', '/import-original-config', { config: cfgJson });
        
        if (r && r.success) {
            const msg = `✅ ${r.imported_count} fields applied\n\n` +
                        `First changes:\n${(r.changes || []).slice(0, 5).join('\n')}\n\n` +
                        `${r.note || ''}`;
            alert(msg);
            // Reload page to reflect changes
            location.reload();
        } else {
            alert('Apply failed: ' + (r ? r.error : 'Unknown'));
        }
    } catch (e) {
        alert('File parsing failed: ' + e.message);
    }
}

// register in init
document.addEventListener('DOMContentLoaded', () => {
    const btn = document.getElementById('config-import-btn');
    const file = document.getElementById('config-import-file');
    if (btn && file) {
        btn.addEventListener('click', () => file.click());
        file.addEventListener('change', e => {
            if (e.target.files && e.target.files[0]) {
                importOriginalConfig(e.target.files[0]);
            }
        });
    }
});

// ============================================================
// V11.5: connection diagnostics + virtual balance
// ============================================================
async function runDiagnose() {
    let modal = document.getElementById('diagnose-modal-instance');
    if (modal) modal.remove();
    
    modal = document.createElement('div');
    modal.id = 'diagnose-modal-instance';
    modal.className = 'diagnose-modal';
    modal.innerHTML = '<h3>🔍 Running connection diagnostics...</h3><p>Please wait (up to 30s)...</p>';
    document.body.appendChild(modal);
    
    try {
        const r = await api('GET', '/diagnose');
        let html = '<h3>🔍 Connection Diagnostics Result</h3>';
        
        for (const c of (r.checks || [])) {
            const ok = c.ok ? 'ok' : 'fail';
            const icon = c.ok ? '✓' : '✗';
            html += `<div class="check-row ${ok}">`;
            html += `<strong>${icon} ${c.name}</strong><br>`;
            html += `<span style="color:#aaa;">${c.detail}</span>`;
            html += `</div>`;
        }
        
        if (r.summary) {
            html += `<div style="margin-top:12px; padding:10px; background:#1a2a3a; border-radius:4px;">`;
            html += `<strong>Summary:</strong> ${r.summary}</div>`;
        }
        if (r.recommendation) {
            html += `<div style="margin-top:8px; padding:10px; background:#2a2a14; border-radius:4px; color:#ffe;">`;
            html += `<strong>💡 Recommendation:</strong> ${r.recommendation}</div>`;
        }
        
        html += '<button onclick="document.getElementById(\'diagnose-modal-instance\').remove()">Close</button>';
        modal.innerHTML = html;
    } catch (e) {
        modal.innerHTML = `<h3>❌ Diagnostics failed</h3><p>${e}</p>` +
            '<button onclick="document.getElementById(\'diagnose-modal-instance\').remove()">Close</button>';
    }
}

async function updateShadowVirtualBalance(value) {
    try {
        const cfg = await api('GET', '/config');
        cfg.ShadowVirtualBalance = parseFloat(value) || 1000;
        await api('PUT', '/config', cfg);
    } catch (e) {
        console.error('Failed to save virtual balance:', e);
    }
}

document.addEventListener('DOMContentLoaded', () => {
    const dbtn = document.getElementById('btn-diagnose');
    if (dbtn) dbtn.addEventListener('click', runDiagnose);
    
    const vbi = document.getElementById('shadow-virtual-balance-input');
    if (vbi) {
        vbi.addEventListener('change', e => updateShadowVirtualBalance(e.target.value));
        api('GET', '/config').then(c => {
            if (c && c.ShadowVirtualBalance) vbi.value = c.ShadowVirtualBalance;
        }).catch(() => {});
    }
});

// ============================================================
// ============================================================
async function updateWorkflowSteps() {
    try {
        const s = await api('GET', '/status');
        const cfg = await api('GET', '/config');
        if (!s || !cfg) return;
        
        const hasKey = cfg.AccessKey && cfg.AccessKey.length > 30;
        const selectedCount = (cfg.SelectedSymbols || []).length;
        
        let loadDone = false, loadActive = false;
        if (selectedCount > 0) {
            try {
                const p = await api('GET', '/symbols/load-progress');
                if (p && !p.running && p.loaded > 0) loadDone = true;
                else if (p && p.running) loadActive = true;
            } catch (e) {}
        }
        
        const shadowOn = !!cfg.ShadowModeEnabled;
        const targets = {
            'step-api': hasKey ? 'done' : 'active',
            'step-shadow': shadowOn ? 'done' : '',
            'step-strategy': hasKey ? 'done' : '',
            'step-symbols': selectedCount > 0 ? 'done' : (hasKey ? 'active' : ''),
            'step-load': loadDone ? 'done' : (loadActive ? 'active' : ''),
            'step-trade': s.is_running ? 'done' : (selectedCount > 0 && loadDone ? 'active' : ''),
        };
        
        for (const [id, want] of Object.entries(targets)) {
            const el = document.getElementById(id);
            if (!el) continue;
            const has_done = el.classList.contains('done');
            const has_active = el.classList.contains('active');
            const want_done = want === 'done';
            const want_active = want === 'active';
            if (has_done !== want_done || has_active !== want_active) {
                el.classList.remove('done', 'active');
                if (want_done) el.classList.add('done');
                else if (want_active) el.classList.add('active');
            }
        }
    } catch (e) { }
}

setInterval(updateWorkflowSteps, 5000);
setTimeout(updateWorkflowSteps, 1500);

// ============================================================
// ============================================================
async function loadSettingsInvestmentMode() {
    try {
        const r = await api('GET', '/investment-mode');
        if (!r) return;
        
        const cb = document.getElementById('settings-inv-mode-toggle');
        if (cb) cb.checked = !!r.enabled;
        
        document.querySelectorAll('.settings-mode-btn').forEach(btn => {
            btn.classList.toggle('active', btn.dataset.mode === r.mode);
        });
        
        const balEl = document.getElementById('settings-mode-balance');
        const marginEl = document.getElementById('settings-mode-margin');
        if (balEl) balEl.textContent = (r.balance || 0).toFixed(2);
        if (marginEl) {
            const margin = r.calculated_margin || 0;
            marginEl.textContent = margin > 0 ? margin.toFixed(4) : '-';
        }
        
        const panel = document.querySelector('.settings-panel:has(#settings-inv-mode-toggle)');
        if (panel) panel.classList.toggle('disabled', !r.enabled);
    } catch (e) { console.error(e); }
}

async function setSettingsInvestmentMode(mode) {
    const r = await api('POST', '/investment-mode', {
        enabled: true,
        mode: mode,
    });
    if (r) {
        await loadInvestmentMode();
        await loadSettingsInvestmentMode();
        showToast(`Investment mode changed: ${mode}`, 'success');
    }
}

async function toggleSettingsInvestmentMode(enabled) {
    const activeMode = document.querySelector('.settings-mode-btn.active');
    const mode = activeMode ? activeMode.dataset.mode : 'NORMAL';
    const r = await api('POST', '/investment-mode', {
        enabled: enabled,
        mode: mode,
    });
    if (r) {
        await loadInvestmentMode();
        await loadSettingsInvestmentMode();
    }
}

document.addEventListener('DOMContentLoaded', () => {
    setTimeout(() => {
        const tog = document.getElementById('settings-inv-mode-toggle');
        if (tog) {
            tog.addEventListener('change', e => toggleSettingsInvestmentMode(e.target.checked));
        }
        document.querySelectorAll('.settings-mode-btn').forEach(btn => {
            btn.addEventListener('click', () => setSettingsInvestmentMode(btn.dataset.mode));
        });
        loadSettingsInvestmentMode();
        
        document.querySelectorAll('.settings-entry-mode-btn').forEach(btn => {
            btn.addEventListener('click', () => setEntryMode(btn.dataset.entryMode));
        });
        loadEntryMode();

        document.querySelectorAll('.settings-leverage-btn').forEach(btn => {
            btn.addEventListener('click', () => setLeverage(parseInt(btn.dataset.leverage, 10)));
        });
        loadLeverage();


        document.querySelectorAll('.strategy-preset-header').forEach(header => {
            header.addEventListener('click', () => {
                const card = header.closest('.strategy-preset-card');
                if (!card) return;
                card.classList.toggle('expanded');
                try {
                    const strategy = header.dataset.strategyToggle;
                    if (strategy) {
                        const expanded = card.classList.contains('expanded');
                        const map = JSON.parse(sessionStorage.getItem('strategy_card_expanded') || '{}');
                        map[strategy] = expanded;
                        sessionStorage.setItem('strategy_card_expanded', JSON.stringify(map));
                    }
                } catch (e) { }
            });
        });
        try {
            const map = JSON.parse(sessionStorage.getItem('strategy_card_expanded') || '{}');
            for (const [strategy, expanded] of Object.entries(map)) {
                if (expanded) {
                    const card = document.getElementById(`card-strategy-${strategy}`);
                    if (card) card.classList.add('expanded');
                }
            }
        } catch (e) { }
        
        const resetBtn = document.getElementById('btn-reset-baseline');
        if (resetBtn) {
            resetBtn.addEventListener('click', resetChartBaseline);
        }
    }, 1000);
});

async function setEntryMode(mode) {
    const btn = document.querySelector(`.settings-entry-mode-btn[data-entry-mode="${mode}"]`);
    if (btn) {
        btn.style.opacity = '0.6';
        btn.style.pointerEvents = 'none';
    }
    const r = await api('POST', '/entry-mode', { mode: mode });
    if (btn) {
        btn.style.opacity = '';
        btn.style.pointerEvents = '';
    }
    if (r) {
        document.querySelectorAll('.settings-entry-mode-btn').forEach(b => {
            b.classList.toggle('active', b.dataset.entryMode === r.mode);
        });
        const badge = document.getElementById('entry-mode-current-badge');
        if (badge) {
            badge.textContent = `Current: ${r.label || r.mode}`;
            badge.style.background = '#0e3b34';
            badge.style.color = '#2dd4bf';
        }
        showToast(`✅ Entry mode applied: ${r.label || r.mode}`, 'success');
        console.log('[V13.1.4] Entry mode changed:', r);
    }
}

async function loadEntryMode() {
    try {
        const r = await api('GET', '/entry-mode');
        if (r) {
            document.querySelectorAll('.settings-entry-mode-btn').forEach(b => {
                b.classList.toggle('active', b.dataset.entryMode === r.mode);
            });
            const badge = document.getElementById('entry-mode-current-badge');
            if (badge) {
                badge.textContent = `Current: ${r.label || r.mode}`;
            }
        }
    } catch (e) { console.error('loadEntryMode failed:', e); }
}

async function setLeverage(leverage) {
    const btn = document.querySelector(`.settings-leverage-btn[data-leverage="${leverage}"]`);
    if (btn) {
        btn.style.opacity = '0.6';
        btn.style.pointerEvents = 'none';
    }
    const r = await api('POST', '/leverage', { leverage: leverage });
    if (btn) {
        btn.style.opacity = '';
        btn.style.pointerEvents = '';
    }
    if (r) {
        document.querySelectorAll('.settings-leverage-btn').forEach(b => {
            b.classList.toggle('active', parseInt(b.dataset.leverage, 10) === r.leverage);
        });
        const badge = document.getElementById('leverage-current-badge');
        if (badge) {
            badge.textContent = `Current: ${r.label}`;
            badge.style.background = '#0e3b34';
            badge.style.color = '#2dd4bf';
        }
        showToast(`✅ Leverage applied: ${r.label}`, 'success');
        console.log('[Leverage] Changed:', r);
    }
}

async function loadLeverage() {
    try {
        const r = await api('GET', '/leverage');
        if (r) {
            document.querySelectorAll('.settings-leverage-btn').forEach(b => {
                b.classList.toggle('active', parseInt(b.dataset.leverage, 10) === r.leverage);
            });
            const badge = document.getElementById('leverage-current-badge');
            if (badge) {
                badge.textContent = `Current: ${r.label}`;
            }
        }
    } catch (e) { console.error('loadLeverage failed:', e); }
}

async function setOrderTypeMode(mode) {
    const btn = document.querySelector(`.settings-order-type-btn[data-order-type="${mode}"]`);
    if (btn) {
        btn.style.opacity = '0.6';
        btn.style.pointerEvents = 'none';
    }
    const r = await api('POST', '/order-type-mode', { mode: mode });
    if (btn) {
        btn.style.opacity = '';
        btn.style.pointerEvents = '';
    }
    if (r) {
        document.querySelectorAll('.settings-order-type-btn').forEach(b => {
            b.classList.toggle('active', b.dataset.orderType === r.mode);
        });
        showToast(`✅ Order type applied: ${r.description || r.mode}`, 'success');
        await loadOrderTypeMode();
        console.log('[V13.1.4] Order type changed:', r);
    }
}

async function loadOrderTypeMode() {
    try {
        const r = await api('GET', '/order-type-mode');
        if (r) {
            document.querySelectorAll('.settings-order-type-btn').forEach(b => {
                b.classList.toggle('active', b.dataset.orderType === r.mode);
            });
            const badge = document.getElementById('order-type-current-badge');
            if (badge) {
                badge.textContent = `Current: ${r.label || r.mode}`;
            }
            const info = document.getElementById('order-type-effective-info');
            if (info) {
                const lines = [
                    `Selected mode:    ${r.mode} (${r.label || ''})`,
                    `Entry order type:  ${r.expected_open_order_type}` +
                        `  (Uses Post-Only: ${r.open_uses_post_only ? 'Yes ✓' : 'No ✗'})`,
                    `Close order type:  ${r.expected_close_order_type}` +
                        `  (Uses Post-Only: ${r.close_uses_post_only ? 'Yes ✓' : 'No ✗'})`,
                    `Expected fee rate: ${(r.expected_fee_pct || 0).toFixed(3)}%`,
                ];
                if (r.error) {
                    lines.push(`⚠️ Validation error: ${r.error}`);
                }
                info.innerHTML = lines.map(escapeHtml).join('<br>');
            }
        }
    } catch (e) { console.error('loadOrderTypeMode failed:', e); }
}

function showToast(message, type) {
    let toast = document.getElementById('app-toast');
    if (!toast) {
        toast = document.createElement('div');
        toast.id = 'app-toast';
        toast.style.cssText = 'position:fixed; bottom:20px; right:20px; padding:12px 20px; ' +
            'border-radius:6px; font-size:14px; z-index:9999; ' +
            'box-shadow:0 4px 12px rgba(0,0,0,0.4); transition:opacity 0.3s;';
        document.body.appendChild(toast);
    }
    const colors = {
        'success': { bg: '#1f3a26', border: '#14655c', color: '#9eecb6' },
        'error':   { bg: '#3a1f1f', border: '#7a2d2d', color: '#ec9e9e' },
        'info':    { bg: '#1a2a3a', border: '#2a4a6a', color: '#7cb7ff' },
    };
    const c = colors[type || 'info'];
    toast.style.background = c.bg;
    toast.style.border = `1px solid ${c.border}`;
    toast.style.color = c.color;
    toast.textContent = message;
    toast.style.opacity = '1';
    clearTimeout(toast._timer);
    toast._timer = setTimeout(() => { toast.style.opacity = '0'; }, 2500);
}

async function resetChartBaseline() {
    const startInfo = await api('GET', '/start-balance');
    const currentBal = startInfo ? (startInfo.current_balance || 0) : 0;
    const oldBal = startInfo ? (startInfo.balance || 0) : 0;
    
    if (currentBal <= 0) {
        alert('Unable to fetch balance. Please start trading first.');
        return;
    }
    
    const msg = oldBal > 0 
        ? `Reset the chart baseline to the current balance?\n\n` +
          `Previous baseline: $${oldBal.toFixed(2)}\n` +
          `New baseline: $${currentBal.toFixed(2)}\n\n` +
          `★ The Daily Balance Change chart will restart from the new baseline ★`
        : `Set the trading-start balance to the current balance?\n\n` +
          `Balance: $${currentBal.toFixed(2)}`;
    
    if (!confirm(msg)) return;
    
    const r = await api('POST', '/start-balance/reset', { balance: null });
    if (r && r.ok) {
        console.log('[V12.12] Baseline reset:', r);
        await loadChart();
    } else {
        alert('Baseline reset failed');
    }
}

const _origSwitchView = window.switchView;


// ============================================================
// ============================================================
async function v14Restorejavid_rDefaults() {
    if (!confirm('Restore all javid_r identical-trading defaults?\n\n' +
                 '- Entry mode: Conservative (single)\n' +
                 '- Order type: javid_r (market)\n' +
                 '- RegimeFilter / TimeStop / ATR / Wick / PartialExit all OFF\n\n' +
                 '※ All V13 safeguards will be turned OFF. Trading will match javid_r exactly.')) {
        return;
    }
    try {
        const r = await api('POST', '/v14/restore-javid_r-defaults');
        if (r.ok) {
            alert(`✅ javid_r identical-trading defaults restored\n\n` +
                  `Changes: ${r.changes.length} settings\n${r.changes.join('\n')}\n\n` +
                  `javid_r match rate: ${r.javid_r_match_rate}`);
            await loadActiveFeatures();
            await loadEntryMode();
            await loadOrderTypeMode();
        } else {
            alert('Restore failed');
        }
    } catch (e) {
        alert('Error: ' + e.message);
    }
}

async function v14EnableAllV13Protections() {
    if (!confirm('Turn ON all 6 V13 safeguards?\n\n' +
                 '- RegimeFilter: blocks SHORT in trending markets\n' +
                 '- TimeStop: force-closes if not recovered within N candles after entry\n' +
                 '- ATR dynamic loss: volatility-based LossTrailing per coin\n' +
                 '- Wick rejection candle: entry precision\n' +
                 '- TP1 partial take-profit: 50% + BE at first target\n' +
                 '- MAE/MFE logging: trade tracking\n\n' +
                 '※ Trading will be more conservative / safer than javid_r.')) {
        return;
    }
    try {
        const r = await api('POST', '/v14/enable-all-v13-protections');
        if (r.ok) {
            alert(`✅ All V13 safeguards turned ON\n\n` +
                  `Changes: ${r.changes.length} settings\n${r.changes.join('\n')}\n\n` +
                  `${r.note}`);
            await loadActiveFeatures();
        } else {
            alert('Failed');
        }
    } catch (e) {
        alert('Error: ' + e.message);
    }
}

async function loadjavid_rDefaultStatus() {
    try {
        const r = await api('GET', '/v14/javid_r-default-status');
        const panel = document.getElementById('javid_r-default-info-panel');
        if (panel && r) {
            if (r.is_javid_r_default) {
                panel.style.borderLeftColor = '#2dd4bf';
            } else {
                panel.style.borderLeftColor = '#fbbf24';
            }
        }
    } catch (e) {
        console.warn('[V14.0] Failed to load javid_r default state:', e);
    }
}

document.addEventListener('DOMContentLoaded', () => {
    const btnRestore = document.getElementById('btn-restore-javid_r-defaults');
    if (btnRestore) {
        btnRestore.addEventListener('click', v14Restorejavid_rDefaults);
    }
    const btnEnableV13 = document.getElementById('btn-enable-all-v13');
    if (btnEnableV13) {
        btnEnableV13.addEventListener('click', v14EnableAllV13Protections);
    }
    setTimeout(loadjavid_rDefaultStatus, 1500);
});


// ============================================================
// ============================================================


// ============================================================
// ============================================================
function setTgStatus(msg, ok) {
    const el = $('tg-status');
    if (!el) return;
    el.textContent = msg;
    el.style.color = ok ? '#2dd4bf' : '#fb7185';
}

async function loadTelegramSettings() {
    const r = await api('GET', '/telegram/config');
    if (!r) return;
    if ($('tg-enabled')) $('tg-enabled').checked = !!r.enabled;
    if ($('tg-chat-id')) $('tg-chat-id').value = r.chat_id || '';
    if ($('tg-auto-min')) $('tg-auto-min').value = r.auto_report_min || 0;
    const saved = $('tg-token-saved');
    if (saved) {
        saved.textContent = r.has_token
            ? `Saved token: ${r.bot_token_masked}  (enter a new token above only if you want to change it)`
            : 'No saved token — enter the token issued by BotFather';
    }
    const badge = $('tg-status-badge');
    if (badge) {
        const on = !!r.enabled;
        badge.textContent = on ? 'ON' : 'OFF';
        badge.style.background = on ? '#0e3b34' : '#1d2836';
        badge.style.color = on ? '#2dd4bf' : '#7d8aa0';
    }
}

async function saveTelegramSettings() {
    const payload = {
        enabled: $('tg-enabled') ? $('tg-enabled').checked : false,
        bot_token: $('tg-token') ? $('tg-token').value.trim() : '',
        chat_id: $('tg-chat-id') ? $('tg-chat-id').value.trim() : '',
        auto_report_min: $('tg-auto-min') ? (parseInt($('tg-auto-min').value) || 0) : 0,
    };
    const r = await api('POST', '/telegram/config', payload);
    if (r && r.ok) {
        if ($('tg-token')) $('tg-token').value = '';
        setTgStatus('✅ Saved successfully', true);
        await loadTelegramSettings();
    } else {
        setTgStatus('❌ Save failed — check server logs', false);
    }
}

async function testTelegram() {
    setTgStatus('Sending test message...', true);
    const payload = {};
    if ($('tg-token') && $('tg-token').value.trim()) payload.bot_token = $('tg-token').value.trim();
    if ($('tg-chat-id') && $('tg-chat-id').value.trim()) payload.chat_id = $('tg-chat-id').value.trim();
    const r = await api('POST', '/telegram/test', payload);
    if (r && r.ok) {
        setTgStatus('✅ Sent successfully! Check your Telegram chat.', true);
    } else {
        setTgStatus('❌ ' + ((r && r.error) || 'Send failed (no response from server)'), false);
    }
}

async function sendTelegramReport() {
    setTgStatus('Sending report...', true);
    const r = await api('POST', '/telegram/send-report');
    if (r && r.ok) {
        setTgStatus('✅ Report sent successfully!', true);
    } else {
        setTgStatus('❌ ' + ((r && r.error) || 'Send failed'), false);
    }
}

function showTelegramGuide() {
    let modal = document.getElementById('tg-guide-modal');
    if (modal) modal.remove();
    modal = document.createElement('div');
    modal.id = 'tg-guide-modal';
    modal.className = 'diagnose-modal';
    modal.style.maxWidth = '640px';
    modal.innerHTML = `
        <h3>📨 Telegram Notification Setup Guide</h3>
        <div style="font-size:13px; line-height:1.8; color:#aab8cc;">
            <p style="margin:6px 0;"><strong style="color:#2dd4bf;">Step 1. Create a bot (issue a bot token)</strong></p>
            <ol style="margin:4px 0 10px 18px; padding:0;">
                <li>In the Telegram app, search for <strong>@BotFather</strong> → start a chat</li>
                <li>Enter <code>/newbot</code> → enter a bot name (e.g. MyJaviDBot)</li>
                <li>Enter a bot username (must end in <code>bot</code>, e.g. my_javid_bot)</li>
                <li>Copy the entire <strong>token</strong> BotFather gives you (e.g. <code>123456789:AAH...</code>)
                    → paste it into the "Bot Token" field on the Settings tab (watch for leading/trailing spaces)</li>
            </ol>
            <p style="margin:6px 0;"><strong style="color:#2dd4bf;">Step 2. Get your Chat ID</strong></p>
            <ol style="margin:4px 0 10px 18px; padding:0;">
                <li>In Telegram, search for <strong>@userinfobot</strong> → start a chat (/start)</li>
                <li>Enter the <strong>Id number</strong> shown (e.g. 123456789) into the "Chat ID" field</li>
            </ol>
            <p style="margin:6px 0;"><strong style="color:#2dd4bf;">Step 3. Start a chat with your bot (important!)</strong></p>
            <ol style="margin:4px 0 10px 18px; padding:0;">
                <li>Search for the <strong>bot you created</strong> in Step 1 and send it <code>/start</code> first.<br>
                    <span style="color:#fbbf24;">※ Skipping this step means the bot cannot send you messages (the most common failure cause)</span></li>
            </ol>
            <p style="margin:6px 0;"><strong style="color:#2dd4bf;">Step 4. Save → Test</strong></p>
            <ol style="margin:4px 0 10px 18px; padding:0;">
                <li>Check "Enable Telegram notifications" → click <strong>Save</strong></li>
                <li>Click <strong>Send Test</strong> → success if you receive a message on Telegram</li>
                <li>Use "Send Report Now" to instantly receive dashboard metrics (balance, equity, cumulative P&L, cumulative ROI, win rate, open positions)</li>
                <li>Set an auto-report interval (minutes) to send automatically on that schedule (0 = disabled)</li>
            </ol>
            <p style="margin:10px 0 4px 0; padding:8px 10px; background:#111823; border-left:3px solid #fbbf24; border-radius:4px; color:#e2cf8f;">
                🔐 <strong>Security note</strong>: Your bot token is like a password. Do not share it with anyone.
                If it is ever exposed, reissue it via <code>/revoke</code> in BotFather.
            </p>
        </div>
        <button onclick="document.getElementById('tg-guide-modal').remove()"
                style="margin-top:12px;">Close</button>
    `;
    document.body.appendChild(modal);
}

// ============================================================
// ============================================================
async function v18ConfirmLiveTradingIfNeeded() {
    try {
        const sh = await api('GET', '/shadow/status');
        if (sh && sh.enabled) return true;

        const cfg = await api('GET', '/config') || {};
        const levs = [];
        for (const t of ['BreakoutTarget', 'MeanRevertTarget', 'JaviDAgrtTarget']) {
            const tt = cfg[t] || {};
            if (tt.LongLeverage) levs.push(Number(tt.LongLeverage));
            if (tt.ShortLeverage) levs.push(Number(tt.ShortLeverage));
        }
        const maxLev = levs.length ? Math.max(...levs) : 10;
        const symCount = (cfg.SelectedSymbols || []).length;
        const invMode = cfg.UseInvestmentMode
            ? `Investment Mode ON (${cfg.InvestmentMode || 'NORMAL'}, balance÷${cfg.CustomInvestmentDivisor && cfg.InvestmentMode === 'CUSTOM' ? cfg.CustomInvestmentDivisor : ({AGGRESSIVE:300, NORMAL:500, STABLE:700}[cfg.InvestmentMode] || 500)})`
            : 'Fixed amount mode (LongUSDT/ShortUSDT)';

        let msg = '⚠️ Starting trading in live mode — real orders will be placed!\n\n';
        msg += `· Leverage: up to ${maxLev}x (cross/CROSSED margin)\n`;
        msg += `· Fund management: ${invMode}\n`;
        msg += `· Selected symbols: ${symCount}\n\n`;
        if (maxLev >= 10) {
            msg += `🔥 At ${maxLev}x leverage, a ${(100 / maxLev).toFixed(1)}% move against you\n`;
            msg += '   can result in total loss of that position\'s margin.\n\n';
        }
        msg += '💡 Safety guidelines:\n';
        msg += '  - If this is your first time, verify with Shadow Mode first\n';
        msg += '  - Do not run another bot on the same account at the same time (use a separate Binance sub-account)\n';
        msg += '  - Only trade with funds you can afford to lose\n\n';
        msg += 'You have reviewed the above. Start live trading?';
        return confirm(msg);
    } catch (e) {
        return confirm('⚠️ This is live trading mode. Real orders will be placed.\nStart trading?');
    }
}

// ============================================================
// ============================================================
async function v18ShowOnboardingIfFirstRun() {
    try {
        if (localStorage.getItem('javid_v18_onboarding_done') === '1') return;
        const cfg = await api('GET', '/config');
        const hasKey = cfg && cfg.AccessKey && cfg.AccessKey.length > 30;
        if (hasKey) return;

        let ov = document.getElementById('v18-onboarding-overlay');
        if (ov) ov.remove();
        ov = document.createElement('div');
        ov.id = 'v18-onboarding-overlay';
        ov.style.cssText = 'position:fixed; inset:0; background:rgba(8,10,16,0.82); z-index:9998; display:flex; align-items:center; justify-content:center;';
        ov.innerHTML = `
        <div style="max-width:600px; width:92%; max-height:86vh; overflow-y:auto; background:#0e141d; border:1px solid #223047; border-radius:10px; padding:22px 26px; box-shadow:0 8px 40px rgba(0,0,0,0.6);">
            <h2 style="margin:0 0 4px 0; color:#fff; font-size:20px;">👋 Welcome to JaviD Future Bot V18.0</h2>
            <p style="color:#7d8aa0; font-size:13px; margin:4px 0 14px 0;">
                Follow the steps below when using it for the first time to get started safely.
            </p>
            <div style="display:flex; flex-direction:column; gap:10px; font-size:14px; color:#dde;">
                <div style="padding:10px 14px; background:#1b1608; border-left:3px solid #f0b90b; border-radius:6px;">
                    <strong style="color:#f0b90b;">STEP 0 — Create a Binance account first (if you don't have one)</strong><br>
                    <span style="font-size:13px; color:#c7b291;">This program trades a Binance <strong>Futures</strong> account,
                    so nothing here works without one. Futures trading also requires <strong>identity verification (KYC)</strong>.<br>
                    Already have an account? Skip to STEP 1.<br>
                    <a href="${JAVID_LINKS.signup}" target="_blank" rel="noopener noreferrer"
                       id="v18-ob-signup-link"
                       style="display:inline-block; margin-top:8px; padding:7px 14px; border:1px solid #f0b90b;
                              border-radius:4px; color:#f0b90b; font-weight:700; text-decoration:none;">
                       🔗 Sign up for Binance</a>
                    <a href="${JAVID_LINKS.apimgmt}" target="_blank" rel="noopener noreferrer"
                       id="v18-ob-apimgmt-link"
                       style="display:inline-block; margin-top:8px; margin-left:6px; padding:7px 14px;
                              border:1px solid #2f3f57; border-radius:4px; color:#93a1b7; text-decoration:none;">
                       🔑 Get API keys</a>
                    </span>
                </div>
                <div style="padding:10px 14px; background:#111823; border-left:3px solid #2dd4bf; border-radius:6px;">
                    <strong style="color:#2dd4bf;">STEP 1 — Connect API keys</strong><br>
                    <span style="font-size:13px; color:#93a1b7;">[Settings] tab → enter your Binance API Access/Secret Key → Save → <strong>Test Connection</strong>.<br>
                    Requires Futures permission + IP whitelist registered in Binance API management.</span>
                </div>
                <div style="padding:10px 14px; background:#111823; border-left:3px solid #2dd4bf; border-radius:6px;">
                    <strong style="color:#2dd4bf;">STEP 2 — Try Shadow Mode first (strongly recommended)</strong><br>
                    <span style="font-size:13px; color:#93a1b7;">Turn on 🪞 Shadow Mode in the sidebar → simulate live trading with real market prices, no real orders.<br>
                    Switch to live trading only after thoroughly verifying your strategy and settings.</span>
                </div>
                <div style="padding:10px 14px; background:#111823; border-left:3px solid #2dd4bf; border-radius:6px;">
                    <strong style="color:#2dd4bf;">STEP 3 — Select coins + load candles</strong><br>
                    <span style="font-size:13px; color:#93a1b7;">[Symbol Selection] tab → select coins (or "Top 20 by volume") → <strong>Save Selection</strong> → wait for candle loading to complete.</span>
                </div>
                <div style="padding:10px 14px; background:#111823; border-left:3px solid #2dd4bf; border-radius:6px;">
                    <strong style="color:#2dd4bf;">STEP 4 — Start trading</strong><br>
                    <span style="font-size:13px; color:#93a1b7;">Click ▶ Start Trading in the sidebar. The step markers in the "📌 Standard Trading Flow"
                    panel at the top of the dashboard show your current progress in real time.</span>
                </div>
                <div style="padding:10px 14px; background:#231018; border-left:3px solid #fbbf24; border-radius:6px;">
                    <strong style="color:#fbbf24;">⚠️ Before you go live, please check</strong><br>
                    <span style="font-size:13px; color:#c7b291;">
                    · Default leverage is 10x / cross (CROSSED) margin — losses can grow quickly.<br>
                    · A <strong>confirmation modal</strong> summarizing leverage and margin must be approved before live trading starts.<br>
                    · Do not run multiple bots on the same Binance account at once — a <strong>separate sub-account</strong> is recommended.</span>
                </div>
            </div>
            <div style="display:flex; gap:8px; margin-top:16px; flex-wrap:wrap;">
                <a id="v18-ob-signup-btn" href="${JAVID_LINKS.signup}" target="_blank" rel="noopener noreferrer"
                   class="btn" style="min-width:150px; border-color:#f0b90b; color:#f0b90b; font-weight:700;
                          text-decoration:none; display:inline-flex; align-items:center; justify-content:center;">🔗 Sign up for Binance</a>
                <button id="v18-ob-goto-settings" class="btn btn-primary" style="flex:1; min-width:150px;">⚙️ Go to Settings tab</button>
                <button id="v18-ob-close" class="btn btn-secondary">Later</button>
                <button id="v18-ob-never" class="btn btn-secondary" style="color:#5f6b7d;">Don\'t show again</button>
            </div>
        </div>`;
        document.body.appendChild(ov);
        document.getElementById('v18-ob-goto-settings').addEventListener('click', () => {
            ov.remove();
            if (typeof setView === 'function') setView('settings');
        });
        document.getElementById('v18-ob-close').addEventListener('click', () => ov.remove());
        document.getElementById('v18-ob-never').addEventListener('click', () => {
            localStorage.setItem('javid_v18_onboarding_done', '1');
            ov.remove();
        });
    } catch (e) { }
}

document.addEventListener('DOMContentLoaded', () => {
    const b1 = $('btn-tg-save');    if (b1) b1.addEventListener('click', saveTelegramSettings);
    const b2 = $('btn-tg-test');    if (b2) b2.addEventListener('click', testTelegram);
    const b3 = $('btn-tg-report');  if (b3) b3.addEventListener('click', sendTelegramReport);
    const b4 = $('btn-tg-guide');   if (b4) b4.addEventListener('click', showTelegramGuide);
    setTimeout(loadTelegramSettings, 1200);
    setTimeout(v18ShowOnboardingIfFirstRun, 2000);
});


// ============================================================
// ============================================================
function setTierStatus(msg, ok) {
    const el = $('tier-status');
    if (!el) return;
    el.textContent = msg;
    el.style.color = ok ? '#2dd4bf' : '#fb7185';
}

async function loadTierSettings() {
    const r = await api('GET', '/tiers/status');
    if (!r) return;
    if ($('tier-enabled')) $('tier-enabled').checked = !!r.enabled;
    if ($('tier1-topn')) $('tier1-topn').value = r.tier1_top_n;
    if ($('tier2-topn')) $('tier2-topn').value = r.tier2_top_n;
    if ($('tier1-scale')) $('tier1-scale').value = r.tier1_scale;
    if ($('tier2-scale')) $('tier2-scale').value = r.tier2_scale;
    if ($('tier-floor')) $('tier-floor').value = r.min_threshold_pct;
    if ($('tier1-mscale')) $('tier1-mscale').value = r.tier1_margin_scale;
    if ($('tier2-mscale')) $('tier2-mscale').value = r.tier2_margin_scale;
    if ($('tier3-mscale')) $('tier3-mscale').value = r.tier3_margin_scale;
    const badge = $('tier-status-badge');
    if (badge) {
        const on = !!r.enabled;
        badge.textContent = on ? 'ON' : 'OFF';
        badge.style.background = on ? '#2b2350' : '#1d2836';
        badge.style.color = on ? '#b79cff' : '#7d8aa0';
    }
}

async function saveTierSettings() {
    const payload = {
        enabled: $('tier-enabled') ? $('tier-enabled').checked : false,
        tier1_top_n: parseInt($('tier1-topn').value) || 5,
        tier2_top_n: parseInt($('tier2-topn').value) || 30,
        tier1_scale: parseFloat($('tier1-scale').value) || 0.15,
        tier2_scale: parseFloat($('tier2-scale').value) || 0.35,
        min_threshold_pct: parseFloat($('tier-floor').value) || 0.8,
        tier1_margin_scale: parseFloat($('tier1-mscale').value) || 1.0,
        tier2_margin_scale: parseFloat($('tier2-mscale').value) || 1.0,
        tier3_margin_scale: parseFloat($('tier3-mscale').value) || 1.0,
    };
    if (payload.enabled) {
        const prev = await api('GET', '/tiers/status');
        if (prev && !prev.enabled) {
            if (!confirm('⚠️ Enabling the tier preset lowers the entry threshold for top-volume coins,\n' +
                         'triggering entries on large-cap coins like BTC/ETH.\n\n' +
                         '· Entry frequency may increase\n' +
                         '· We recommend verifying with Shadow Mode for 1-2 weeks first\n\n' +
                         'Apply?')) {
                setTierStatus('Cancelled', false);
                return;
            }
        }
    }
    const r = await api('POST', '/tiers/config', payload);
    if (r && r.ok) {
        let msg = '✅ Saved successfully';
        if (r.warnings && r.warnings.length) msg += ' (adjusted: ' + r.warnings.join(', ') + ')';
        setTierStatus(msg, true);
        await loadTierSettings();
    } else {
        setTierStatus('❌ Save failed', false);
    }
}

async function tierPreview() {
    setTierStatus('Looking up classification...', true);
    const r = await api('GET', '/tiers/status');
    const box = $('tier-preview-box');
    if (!r || !box) { setTierStatus('❌ Lookup failed', false); return; }
    let html = '';
    html += `<strong style="color:#b79cff;">Tier1 (${(r.tier1_symbols||[]).length})</strong>: ${(r.tier1_symbols||[]).join(', ') || '(shown after initialization)'}<br>`;
    const t2 = r.tier2_symbols || [];
    html += `<strong style="color:#7cb7ff;">Tier2 (${t2.length})</strong>: ${t2.slice(0,25).join(', ')}${t2.length>25?' …':''}<br>`;
    html += `<strong style="color:#889;">Tier3</strong>: ${r.tier3_count} (current threshold retained)<br>`;
    if (r.examples && r.examples.length) {
        html += '<br><strong>Applied example (Strategy-A 1h SHORT, dev/vol threshold):</strong><br>';
        for (const ex of r.examples) {
            html += `&nbsp;&nbsp;${ex.symbol} → Tier${ex.tier} : dev ${ex.dev_thr_1h_short}% / vol ${ex.vol_thr_1h_short}%<br>`;
        }
    }
    box.innerHTML = html;
    box.style.display = 'block';
    setTierStatus('✅', true);
}

document.addEventListener('DOMContentLoaded', () => {
    const b1 = $('btn-tier-save');    if (b1) b1.addEventListener('click', saveTierSettings);
    const b2 = $('btn-tier-preview'); if (b2) b2.addEventListener('click', tierPreview);
    setTimeout(loadTierSettings, 1400);
});

// ============================================================
// ### V18.2.7.5  unmanaged-position warning
// ------------------------------------------------------------
// Positions that exist on the EXCHANGE but have no local wallet (normally because
// BlockExternalPositions=True) used to be silently skipped by sync, so a leftover
// could sit open on Binance/Bitget while the dashboard showed nothing at all.
// This polls GET /api/positions/unmanaged and shows a top banner whenever the
// exchange holds something this bot is not managing.
//
// Deliberately self-contained and appended at the end of the file: it does not
// touch the positions table or the chart.
// ============================================================
(function () {
    var POLL_MS = 20000;
    function render(items) {
        var el = document.getElementById('unmanaged-banner');
        if (!el) return;
        if (!items || !items.length) {
            el.style.display = 'none';
            el.innerHTML = '';
            return;
        }
        var parts = items.slice(0, 12).map(function (it) {
            return (it.symbol || '?') + ' ' + (it.side || '?') + ' x' + (it.amt || 0);
        });
        el.innerHTML =
            '\u26A0 ' + items.length + ' position(s) are OPEN on the exchange but NOT ' +
            'managed by this bot - they are not shown in the Positions list and the exit ' +
            'logic will not close them.' +
            '<span class="unmanaged-list">' + parts.join(' &nbsp;|&nbsp; ') +
            (items.length > parts.length ? ' &nbsp;…' : '') + '</span>';
        el.style.display = 'block';
    }
    async function poll() {
        try {
            var r = await api('GET', '/positions/unmanaged');
            if (r) render(r.items || []);
        } catch (e) { /* never break the UI over a warning banner */ }
    }
    function start() { poll(); setInterval(poll, POLL_MS); }
    if (document.readyState === 'complete' || document.readyState === 'interactive') {
        setTimeout(start, 2500);
    } else {
        window.addEventListener('DOMContentLoaded', function () { setTimeout(start, 2500); });
    }
})();

// ============================================================
// ### V18.2.8.1-SIGNUP  wire the Settings-tab links
// ------------------------------------------------------------
// index.html declares the two <a> elements with no href; the URLs come from
// JAVID_LINKS (declared at the top of this file) so there is exactly one
// place to edit.  Appended at the end and fully self-contained: it registers
// nothing global and touches no trading control.
// ============================================================
(function () {
    function wire() {
        try {
            var a = document.getElementById('btn-binance-signup');
            if (a) a.href = JAVID_LINKS.signup;
            var b = document.getElementById('btn-binance-apimgmt');
            if (b) b.href = JAVID_LINKS.apimgmt;
        } catch (e) { /* a help link must never break the app */ }
    }
    if (document.readyState === 'complete' || document.readyState === 'interactive') {
        wire();
    } else {
        window.addEventListener('DOMContentLoaded', wire);
    }
})();
