{% extends "equity/base.html" %}
{% load static %}

{% block title %}{{ stock_code }} - 个股详情 - AgomTradePro{% endblock %}

{% block extra_css %}
<link rel="stylesheet" href="{% static 'css/equity.css' %}">
<style>
.valuation-chart-shell {
    position: relative;
    height: 220px;
    width: 100%;
}

.valuation-chart-shell canvas {
    width: 100% !important;
    height: 100% !important;
    display: block;
}

.metric-subvalue {
    margin-top: 6px;
    font-size: 12px;
    color: var(--color-text-muted);
    line-height: 1.4;
}

.technical-toolbar {
    display: flex;
    flex-wrap: wrap;
    justify-content: space-between;
    align-items: center;
    gap: 12px;
    margin-bottom: 16px;
}

.technical-timeframe-group {
    display: inline-flex;
    gap: 8px;
}

.technical-timeframe-btn.active {
    background: var(--color-primary);
    color: #fff;
    border-color: var(--color-primary);
}

.technical-summary {
    font-size: 13px;
    color: var(--color-text-secondary);
}

.technical-chart-shell {
    width: 100%;
    height: 520px;
}

.technical-empty {
    display: none;
    padding: 40px 16px;
    text-align: center;
    color: var(--color-text-muted);
    border: 1px dashed var(--color-border);
    border-radius: var(--radius-md, 8px);
}

.pulse-card-header {
    display: flex;
    justify-content: space-between;
    align-items: flex-start;
    gap: 12px;
    margin-bottom: 16px;
}

.pulse-card-subtitle {
    margin-top: 6px;
    font-size: 13px;
    color: var(--color-text-secondary);
}

.pulse-dimension-grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
    gap: 12px;
    margin-top: 16px;
}

.pulse-dimension-card {
    padding: 14px;
    border: 1px solid var(--color-border);
    border-radius: var(--radius-md, 8px);
    background: var(--color-surface, #fff);
}

.pulse-dimension-title {
    font-size: 12px;
    color: var(--color-text-muted);
    margin-bottom: 8px;
}

.pulse-dimension-score {
    font-size: 22px;
    font-weight: 600;
    color: var(--color-text-primary);
}

.pulse-dimension-signal {
    margin-top: 6px;
    font-size: 12px;
    color: var(--color-text-secondary);
}

.pulse-reasons {
    margin-top: 16px;
    padding: 12px 14px;
    border-radius: var(--radius-md, 8px);
    background: var(--color-bg-soft, #f8fafc);
    color: var(--color-text-secondary);
    font-size: 13px;
    line-height: 1.6;
}
</style>
{% endblock %}

{% block extra_js %}
<script src="{% static 'js/echarts.min.js' %}"></script>
<script src="{% static 'vendor/chartjs/chart.umd.min.js' %}"></script>
{% endblock %}

{% block content %}
<div class="equity-container">
    <div class="equity-header">
        <div style="display: flex; justify-content: space-between; align-items: flex-start;">
            <div>
                <h1 style="display: flex; align-items: center; gap: 12px;">
                    <span id="stockCode">{{ stock_code }}</span>
                    <span id="stockName" style="font-size: 22px; color: #6b7280;">加载中...</span>
                </h1>
                <p style="margin-top: 8px;">个股估值分析与基本面指标</p>
            </div>
            <div>
                <button class="btn btn-secondary" onclick="window.location.href='/equity/screen/'">
                    <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
                        <line x1="19" y1="12" x2="5" y2="12"/>
                        <polyline points="12 19 5 12 12 5"/>
                    </svg>
                    返回筛选
                </button>
            </div>
        </div>
    </div>

    <!-- 基本信息卡片 -->
    <div class="card">
        <h2 class="card-title">基本信息</h2>
        <div class="metric-grid" style="grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));">
            <div class="metric-card">
                <div class="metric-label">股票代码</div>
                <div class="metric-value" style="font-size: 16px;" id="detailCode">{{ stock_code }}</div>
            </div>
            <div class="metric-card">
                <div class="metric-label">所属行业</div>
                <div class="metric-value" style="font-size: 16px;" id="detailSector">-</div>
            </div>
            <div class="metric-card">
                <div class="metric-label">交易市场</div>
                <div class="metric-value" style="font-size: 16px;" id="detailMarket">-</div>
            </div>
            <div class="metric-card">
                <div class="metric-label">上市日期</div>
                <div class="metric-value" style="font-size: 16px;" id="detailListDate">-</div>
            </div>
        </div>
    </div>

    <!-- 最新价格与估值 -->
    <div class="card">
        <div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 16px;">
            <h2 class="card-title" style="margin: 0;">最新价格与估值</h2>
            <div style="display: flex; align-items: center; gap: 12px;">
                <span id="dataUpdateTime" style="font-size: 12px; color: var(--color-text-muted);">
                    <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" style="vertical-align: middle; margin-right: 4px;">
                        <circle cx="12" cy="12" r="10"/>
                        <polyline points="12 6 12 12 16 14"/>
                    </svg>
                    数据更新于: --
                </span>
                <button id="refreshDataBtn" onclick="refreshStockData()" class="btn btn-secondary" style="font-size: 12px; padding: 4px 10px;" title="手动刷新数据">
                    <svg id="refreshIcon" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" style="vertical-align: middle;">
                        <path d="M21 2v6h-6"/>
                        <path d="M3 12a9 9 0 0 1 15-6.7L21 8"/>
                        <path d="M3 22v-6h6"/>
                        <path d="M21 12a9 9 0 0 1-15 6.7L3 16"/>
                    </svg>
                    <span id="refreshBtnText">刷新</span>
                </button>
                <a id="eastmoneyLink" href="#" target="_blank" rel="noopener noreferrer" class="btn btn-secondary" style="font-size: 12px; padding: 4px 10px;">
                    <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" style="vertical-align: middle;">
                        <path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6"/>
                        <polyline points="15 3 21 3 21 9"/>
                        <line x1="10" y1="14" x2="21" y2="3"/>
                    </svg>
                    东方财富
                </a>
            </div>
        </div>
        <div class="metric-grid">
            <div class="metric-card">
                <div class="metric-label">最新市价</div>
                <div class="metric-value" id="detailPrice">-</div>
                <div class="metric-subvalue" id="detailPriceTimestamp">实时价格时间: --</div>
            </div>
            <div class="metric-card">
                <div class="metric-label">市盈率 (PE)</div>
                <div class="metric-value" id="detailPe">-</div>
            </div>
            <div class="metric-card">
                <div class="metric-label">市净率 (PB)</div>
                <div class="metric-value" id="detailPb">-</div>
            </div>
            <div class="metric-card">
                <div class="metric-label">市销率 (PS)</div>
                <div class="metric-value" id="detailPs">-</div>
            </div>
            <div class="metric-card">
                <div class="metric-label">总市值</div>
                <div class="metric-value" style="font-size: 18px;" id="detailTotalMv">-</div>
            </div>
            <div class="metric-card">
                <div class="metric-label">流通市值</div>
                <div class="metric-value" style="font-size: 18px;" id="detailCircMv">-</div>
            </div>
            <div class="metric-card">
                <div class="metric-label">股息率</div>
                <div class="metric-value" id="detailDivYield">-</div>
            </div>
            <div class="metric-card">
                <div class="metric-label">换手率</div>
                <div class="metric-value" id="detailTurnover">-</div>
            </div>
        </div>
    </div>

    <div class="card">
        <div class="pulse-card-header">
            <div>
                <h2 class="card-title" style="margin: 0;">Pulse 战术环境</h2>
                <div class="pulse-card-subtitle">展示当前市场脉搏与转折预警，作为该股票的战术背景。</div>
            </div>
            <div id="pulseObservedAt" class="pulse-card-subtitle">更新时间: --</div>
        </div>
        <div class="metric-grid" style="grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));">
            <div class="metric-card">
                <div class="metric-label">Pulse 综合分数</div>
                <div class="metric-value" id="pulseCompositeScore">-</div>
            </div>
            <div class="metric-card">
                <div class="metric-label">Regime 背景</div>
                <div class="metric-value" id="pulseRegimeContext">-</div>
            </div>
            <div class="metric-card">
                <div class="metric-label">强度</div>
                <div class="metric-value" id="pulseStrength">-</div>
            </div>
            <div class="metric-card">
                <div class="metric-label">转折预警</div>
                <div class="metric-value" id="pulseTransitionWarning">-</div>
            </div>
        </div>
        <div id="pulseDimensionGrid" class="pulse-dimension-grid"></div>
        <div id="pulseReasons" class="pulse-reasons" style="display: none;"></div>
    </div>

    <div class="card">
        <h2 class="card-title">技术图谱</h2>
        <div class="technical-toolbar">
            <div class="technical-timeframe-group">
                <button type="button" class="btn btn-secondary technical-timeframe-btn" data-technical-timeframe="intraday">分时</button>
                <button type="button" class="btn btn-secondary technical-timeframe-btn" data-technical-timeframe="day">日线</button>
                <button type="button" class="btn btn-secondary technical-timeframe-btn" data-technical-timeframe="week">周线</button>
                <button type="button" class="btn btn-secondary technical-timeframe-btn" data-technical-timeframe="month">月线</button>
            </div>
            <div class="technical-summary" id="technicalSummary">加载技术图表中...</div>
        </div>
        <div id="technicalChartEmpty" class="technical-empty">暂无技术图表数据</div>
        <div id="technicalChart" class="technical-chart-shell" aria-label="技术图表"></div>
    </div>

    <!-- 财务指标 -->
    <div class="card">
        <h2 class="card-title">财务指标</h2>
        <div class="metric-subvalue" id="financialPeriodMeta" style="margin-bottom: 14px;">报告期: -- · 披露日: -- · 来源: --</div>
        <div class="metric-grid">
            <div class="metric-card">
                <div class="metric-label">净资产收益率 (ROE)</div>
                <div class="metric-value" id="detailRoe">-</div>
            </div>
            <div class="metric-card">
                <div class="metric-label">总资产收益率 (ROA)</div>
                <div class="metric-value" id="detailRoa">-</div>
            </div>
            <div class="metric-card">
                <div class="metric-label">营业收入</div>
                <div class="metric-value" style="font-size: 18px;" id="detailRevenue">-</div>
            </div>
            <div class="metric-card">
                <div class="metric-label">净利润</div>
                <div class="metric-value" style="font-size: 18px;" id="detailNetProfit">-</div>
            </div>
            <div class="metric-card">
                <div class="metric-label">营收增长率</div>
                <div class="metric-value" id="detailRevenueGrowth">-</div>
            </div>
            <div class="metric-card">
                <div class="metric-label">净利润增长率</div>
                <div class="metric-value" id="detailProfitGrowth">-</div>
            </div>
            <div class="metric-card">
                <div class="metric-label">资产负债率</div>
                <div class="metric-value" id="detailDebtRatio">-</div>
            </div>
            <div class="metric-card">
                <div class="metric-label">毛利率</div>
                <div class="metric-value" id="detailGrossMargin">-</div>
            </div>
        </div>
    </div>

    <!-- 估值分析 -->
    <div class="card">
        <h2 class="card-title">估值分析</h2>
        <div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); gap: 20px;">
            <div>
                <h3 style="font-size: 16px; margin: 0 0 12px 0;">PE 历史分位数</h3>
                <div class="valuation-chart-shell">
                    <canvas id="peChart"></canvas>
                </div>
                <div style="text-align: center; margin-top: 12px;">
                    <span style="font-size: 32px; font-weight: 600; color: #2563eb;" id="pePercentile">-</span>
                    <span style="color: #6b7280; margin-left: 8px;">%</span>
                </div>
                <p style="text-align: center; color: #6b7280; font-size: 14px; margin-top: 8px;" id="peHint">当前 PE 在历史中的位置</p>
            </div>
            <div>
                <h3 style="font-size: 16px; margin: 0 0 12px 0;">PB 历史分位数</h3>
                <div class="valuation-chart-shell">
                    <canvas id="pbChart"></canvas>
                </div>
                <div style="text-align: center; margin-top: 12px;">
                    <span style="font-size: 32px; font-weight: 600; color: #2563eb;" id="pbPercentile">-</span>
                    <span style="color: #6b7280; margin-left: 8px;">%</span>
                </div>
                <p style="text-align: center; color: #6b7280; font-size: 14px; margin-top: 8px;" id="pbHint">当前 PB 在历史中的位置</p>
            </div>
        </div>
    </div>

    <!-- DCF 估值 -->
    <div class="card" id="dcfCard" style="display: none;">
        <h2 class="card-title">DCF 绝对估值</h2>
        <div class="metric-grid" style="grid-template-columns: repeat(3, 1fr);">
            <div class="metric-card">
                <div class="metric-label">内在价值（每股）</div>
                <div class="metric-value" id="dcfIntrinsicValue">-</div>
            </div>
            <div class="metric-card">
                <div class="metric-label">当前价格</div>
                <div class="metric-value" id="dcfCurrentPrice">-</div>
            </div>
            <div class="metric-card">
                <div class="metric-label">上涨空间</div>
                <div class="metric-value" id="dcfUpside">-</div>
            </div>
        </div>
    </div>

    <!-- 资金流向（来自 data_center 统一数据源） -->
    <div class="card" id="capitalFlowCard">
        <h2 class="card-title">资金流向 <span style="font-size: 12px; color: var(--color-text-muted); font-weight: normal;">（东方财富）</span></h2>
        <div id="capitalFlowLoading" class="app-loading-state app-loading-state--compact">
            <span class="app-spinner app-spinner--sm" aria-hidden="true"></span>
            <p class="app-loading-text">加载中...</p>
        </div>
        <div id="capitalFlowEmpty" style="display: none; text-align: center; padding: 20px; color: var(--color-text-muted);">
            暂无资金流向数据
        </div>
        <div class="table-container" id="capitalFlowTable" style="display: none;">
            <table>
                <thead>
                    <tr>
                        <th>日期</th>
                        <th>主力净流入</th>
                        <th>主力占比</th>
                        <th>超大单</th>
                        <th>大单</th>
                        <th>中单</th>
                        <th>小单</th>
                    </tr>
                </thead>
                <tbody id="capitalFlowBody"></tbody>
            </table>
        </div>
    </div>

    <!-- 个股新闻（来自 data_center 统一数据源） -->
    <div class="card" id="stockNewsCard">
        <h2 class="card-title">最新新闻 <span style="font-size: 12px; color: var(--color-text-muted); font-weight: normal;">（东方财富）</span></h2>
        <div id="stockNewsLoading" class="app-loading-state app-loading-state--compact">
            <span class="app-spinner app-spinner--sm" aria-hidden="true"></span>
            <p class="app-loading-text">加载中...</p>
        </div>
        <div id="stockNewsEmpty" style="display: none; text-align: center; padding: 20px; color: var(--color-text-muted);">
            暂无新闻数据
        </div>
        <div id="stockNewsList" style="display: none;"></div>
    </div>

    <!-- Regime 相关性 -->
    <div class="card">
        <h2 class="card-title">Regime 相关性分析</h2>
        <div class="table-container">
            <table>
                <thead>
                    <tr>
                        <th>Regime</th>
                        <th>平均收益率</th>
                        <th>Beta</th>
                        <th>样本天数</th>
                    </tr>
                </thead>
                <tbody id="regimeTableBody">
                    <tr>
                        <td>复苏 (Recovery)</td>
                        <td class="text-rise">-</td>
                        <td>-</td>
                        <td>-</td>
                    </tr>
                    <tr>
                        <td>过热 (Overheat)</td>
                        <td class="text-rise">-</td>
                        <td>-</td>
                        <td>-</td>
                    </tr>
                    <tr>
                        <td>滞胀 (Stagflation)</td>
                        <td class="text-fall">-</td>
                        <td>-</td>
                        <td>-</td>
                    </tr>
                    <tr>
                        <td>通缩 (Deflation)</td>
                        <td class="text-fall">-</td>
                        <td>-</td>
                        <td>-</td>
                    </tr>
                </tbody>
            </table>
        </div>
    </div>
</div>

{{ market_session_profile|json_script:"equity-detail-market-session" }}
<script>
const stockCode = '{{ stock_code }}';
const marketSessionProfile = JSON.parse(
    document.getElementById('equity-detail-market-session').textContent
);
let peChartInstance = null;
let pbChartInstance = null;
let technicalChartInstance = null;
let activeTechnicalTimeframe = 'day';
let technicalRequestToken = 0;
let technicalRequestController = null;
const technicalChartCache = new Map();
const technicalLookbackDays = {
    day: 540,
    week: 1260,
    month: 2000,
};
const valuationLoadTimeoutMs = 8000;

function getMarketClockParts(timezoneName, now = new Date()) {
    if (!timezoneName) {
        return {};
    }
    const formatter = new Intl.DateTimeFormat('en-US', {
        timeZone: timezoneName,
        weekday: 'short',
        hour: '2-digit',
        minute: '2-digit',
        hour12: false,
    });
    const entries = formatter
        .formatToParts(now)
        .filter(part => part.type !== 'literal')
        .map(part => [part.type, part.value]);
    return Object.fromEntries(entries);
}

function isMarketTradingSession(profile, now = new Date()) {
    if (!profile || !profile.timezone || !Array.isArray(profile.sessions) || !profile.sessions.length) {
        return false;
    }

    const parts = getMarketClockParts(profile.timezone, now);
    const weekday = parts.weekday || '';
    if (weekday === 'Sat' || weekday === 'Sun') {
        return false;
    }

    const hour = Number(parts.hour || 0);
    const minute = Number(parts.minute || 0);
    const minutesSinceMidnight = hour * 60 + minute;

    return profile.sessions.some(session => {
        if (!session || !session.start || !session.end) {
            return false;
        }
        const [startHour, startMinute] = session.start.split(':').map(Number);
        const [endHour, endMinute] = session.end.split(':').map(Number);
        if (
            !Number.isFinite(startHour) ||
            !Number.isFinite(startMinute) ||
            !Number.isFinite(endHour) ||
            !Number.isFinite(endMinute)
        ) {
            return false;
        }

        const startMinutes = startHour * 60 + startMinute;
        const endMinutes = endHour * 60 + endMinute;
        return minutesSinceMidnight >= startMinutes && minutesSinceMidnight <= endMinutes;
    });
}

function resolveInitialTechnicalTimeframe(profile, now = new Date()) {
    if (isMarketTradingSession(profile, now)) {
        return profile.default_timeframe_in_session || 'intraday';
    }
    return profile.default_timeframe_out_of_session || 'intraday';
}

function getTechnicalCacheKey(timeframe) {
    return `${stockCode}:${timeframe}`;
}

function getCachedTechnicalPayload(timeframe) {
    return technicalChartCache.get(getTechnicalCacheKey(timeframe)) || null;
}

function setCachedTechnicalPayload(timeframe, payload) {
    technicalChartCache.set(getTechnicalCacheKey(timeframe), payload);
}

function buildTechnicalRequest(timeframe) {
    if (timeframe === 'intraday') {
        return {
            url: `/api/equity/intraday/${stockCode}/`,
            render(payload) {
                renderIntradayChart({ ...payload, timeframe });
            },
        };
    }

    return {
        url: `/api/equity/technical/${stockCode}/?timeframe=${timeframe}&lookback_days=${technicalLookbackDays[timeframe] || technicalLookbackDays.day}`,
        render(payload) {
            renderTechnicalChart(payload);
        },
    };
}

async function fetchJsonOrThrowCompat(resource, init = {}) {
    if (typeof window.fetchJsonOrThrow === 'function') {
        return window.fetchJsonOrThrow(resource, init);
    }

    const { timeoutMs, ...fetchInit } = init;
    const method = (fetchInit.method || 'GET').toUpperCase();
    const headers = new Headers(fetchInit.headers || {});
    if (!['GET', 'HEAD', 'OPTIONS', 'TRACE'].includes(method) && window.csrfToken && !headers.has('X-CSRFToken')) {
        headers.set('X-CSRFToken', window.csrfToken);
    }

    const shouldCreateTimeoutSignal =
        Number.isFinite(timeoutMs) &&
        timeoutMs > 0 &&
        !fetchInit.signal &&
        typeof AbortController !== 'undefined';
    const controller = shouldCreateTimeoutSignal ? new AbortController() : null;
    const timeoutHandle = controller ? window.setTimeout(() => controller.abort(), timeoutMs) : null;

    let response;
    try {
        response = await fetch(resource, {
            credentials: fetchInit.credentials || 'same-origin',
            ...fetchInit,
            method,
            headers,
            signal: controller ? controller.signal : fetchInit.signal,
        });
    } catch (error) {
        if (error && error.name === 'AbortError') {
            throw new Error('请求超时');
        }
        throw error;
    } finally {
        if (timeoutHandle !== null) {
            window.clearTimeout(timeoutHandle);
        }
    }

    if (!response.ok) {
        let message = `HTTP ${response.status}`;
        try {
            const contentType = response.headers.get('content-type') || '';
            if (contentType.includes('application/json')) {
                const payload = await response.clone().json();
                message = payload.detail || payload.error || payload.message || message;
            } else {
                const text = (await response.clone().text()).trim();
                if (text) {
                    message = text;
                }
            }
        } catch (_) {
        }
        throw new Error(message);
    }

    const contentType = response.headers.get('content-type') || '';
    if (!contentType.includes('application/json')) {
        throw new Error(`Expected JSON but received ${contentType || 'unknown content type'}`);
    }

    return response.json();
}

function setDetailHeaderFallback() {
    const stockNameEl = document.getElementById('stockName');
    if (!stockNameEl || (stockNameEl.textContent && stockNameEl.textContent.trim() !== '加载中...')) {
        return;
    }
    stockNameEl.textContent = stockCode;
}

// 生成东方财富链接
function getEastmoneyUrl(code) {
    if (!code) return '#';
    const market = code.endsWith('.SH') ? 'sh' : 'sz';
    const pureCode = code.replace(/\.(SH|SZ)$/, '');
    return `https://quote.eastmoney.com/${market}${pureCode}.html`;
}

// 设置外部链接
function setExternalLinks() {
    document.getElementById('eastmoneyLink').href = getEastmoneyUrl(stockCode);
}

// 更新数据时间显示
function updateDataTime(timestamp) {
    const el = document.getElementById('dataUpdateTime');
    if (timestamp) {
        const preciseTimestamp = formatPreciseTimestamp(timestamp);
        el.innerHTML = `
            <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" style="vertical-align: middle; margin-right: 4px;">
                <circle cx="12" cy="12" r="10"/>
                <polyline points="12 6 12 12 16 14"/>
            </svg>
            数据更新于: ${preciseTimestamp}
            <span style="color: #f59e0b; margin-left: 4px;" title="非实时数据，仅供参考">⚠️</span>
        `;
    } else {
        el.innerHTML = `
            <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" style="vertical-align: middle; margin-right: 4px;">
                <circle cx="12" cy="12" r="10"/>
                <polyline points="12 6 12 12 16 14"/>
            </svg>
            数据更新于: -- 
            <span style="color: #f59e0b; margin-left: 4px;" title="非实时数据，仅供参考">⚠️</span>
        `;
    }
}

function formatPreciseTimestamp(timestamp) {
    if (!timestamp) {
        return '--';
    }
    const date = new Date(timestamp);
    if (Number.isNaN(date.getTime())) {
        return '--';
    }
    const now = new Date();
    const isToday = date.toDateString() === now.toDateString();
    const timeStr = date.toLocaleTimeString('zh-CN', {
        hour: '2-digit',
        minute: '2-digit',
        second: '2-digit',
    });
    if (isToday) {
        return timeStr;
    }
    const dateStr = date.toLocaleDateString('zh-CN', {
        year: 'numeric',
        month: '2-digit',
        day: '2-digit',
    });
    return `${dateStr} ${timeStr}`;
}

function updatePriceTimestamp(timestamp, sourceLabel = '价格时间') {
    const el = document.getElementById('detailPriceTimestamp');
    el.textContent = `${sourceLabel}: ${formatPreciseTimestamp(timestamp)}`;
}

function setFallbackClosePrice(price, tradeDate) {
    const priceEl = document.getElementById('detailPrice');
    if (price !== undefined && price !== null && Number(price) > 0) {
        priceEl.textContent = Number(price).toFixed(2);
        priceEl.className = 'metric-value';
        updatePriceTimestamp(tradeDate, '上一交易日收盘');
        return;
    }

    priceEl.textContent = '-';
    priceEl.className = 'metric-value';
    document.getElementById('detailPriceTimestamp').textContent = '上一交易日收盘不可用';
}

function clampPercentile(value) {
    const numeric = Number(value);
    if (!Number.isFinite(numeric)) {
        return null;
    }
    return Math.max(0, Math.min(100, numeric * 100));
}

function getValuationSnapshot(data) {
    if (data.latest_valuation) {
        return data.latest_valuation;
    }
    return {
        pe: data.current_pe,
        pb: data.current_pb,
        pe_percentile: data.pe_percentile,
        pb_percentile: data.pb_percentile,
        price: data.current_price,
    };
}

function getPercentileHint(percentile) {
    if (percentile === null) {
        return '暂无足够历史数据';
    }
    if (percentile < 30) {
        return '历史低位，偏低估';
    }
    if (percentile > 70) {
        return '历史高位，偏高估';
    }
    return '历史中位附近';
}

function getCssVariable(name, fallback) {
    const value = getComputedStyle(document.documentElement).getPropertyValue(name).trim();
    return value || fallback;
}

function getTimeframeLabel(timeframe) {
    if (timeframe === 'intraday') return '分时';
    if (timeframe === 'week') return '周线';
    if (timeframe === 'month') return '月线';
    return '日线';
}

function setTechnicalButtonState(timeframe) {
    document.querySelectorAll('[data-technical-timeframe]').forEach(button => {
        button.classList.toggle('active', button.dataset.technicalTimeframe === timeframe);
    });
}

function renderTechnicalSummary(payload) {
    const summaryEl = document.getElementById('technicalSummary');
    if (payload.timeframe === 'intraday') {
        const pointCount = payload.points?.length || 0;
        if (!pointCount) {
            summaryEl.textContent = '分时暂无数据';
            return;
        }
        const latestPoint = payload.latest_point;
        const sessionDate = payload.session_date || '--';
        const latestPrice = latestPoint?.price != null ? Number(latestPoint.price).toFixed(2) : '--';
        summaryEl.textContent = `分时 · ${pointCount} 个分钟点 · ${sessionDate} · 最新 ${latestPrice}`;
        return;
    }

    const candleCount = payload.candles?.length || 0;
    const latestSignal = payload.latest_signal;

    if (!candleCount) {
        summaryEl.textContent = `${getTimeframeLabel(payload.timeframe)}暂无数据`;
        return;
    }

    if (latestSignal) {
        const signalLabel = latestSignal.signal_type === 'golden_cross' ? '金叉' : '死叉';
        summaryEl.textContent = `${getTimeframeLabel(payload.timeframe)} · ${candleCount} 根K线 · 最近信号 ${signalLabel} · ${latestSignal.trade_date} · ${latestSignal.label}`;
        return;
    }

    summaryEl.textContent = `${getTimeframeLabel(payload.timeframe)} · ${candleCount} 根K线 · 最近未检测到 MA5/MA20 金叉死叉`;
}

function renderIntradayChart(payload) {
    const chartContainer = document.getElementById('technicalChart');
    const emptyState = document.getElementById('technicalChartEmpty');
    const points = Array.isArray(payload.points) ? payload.points : [];

    if (!points.length) {
        emptyState.style.display = 'block';
        chartContainer.style.display = 'none';
        if (technicalChartInstance) {
            technicalChartInstance.dispose();
            technicalChartInstance = null;
        }
        renderTechnicalSummary(payload);
        return;
    }

    emptyState.style.display = 'none';
    chartContainer.style.display = 'block';

    if (!technicalChartInstance) {
        technicalChartInstance = echarts.init(chartContainer);
    }

    const mutedColor = getCssVariable('--color-text-muted', '#6b7280');
    const borderColor = getCssVariable('--color-border', '#d1d5db');
    const priceColor = '#2563eb';
    const avgColor = '#f59e0b';
    const volumeColor = '#94a3b8';

    const times = points.map(item => formatPreciseTimestamp(item.timestamp));
    const priceData = points.map(item => item.price);
    const avgData = points.map(item => item.avg_price);
    const volumeData = points.map(item => item.volume);

    technicalChartInstance.setOption({
        animation: false,
        legend: {
            top: 0,
            data: ['分时价格', '均价', '成交量'],
        },
        tooltip: {
            trigger: 'axis',
            axisPointer: {
                type: 'cross',
            },
        },
        axisPointer: {
            link: [{ xAxisIndex: [0, 1] }],
        },
        grid: [
            { left: '7%', right: '4%', top: 42, height: '56%' },
            { left: '7%', right: '4%', top: '74%', height: '14%' },
        ],
        xAxis: [
            {
                type: 'category',
                data: times,
                boundaryGap: false,
                axisLine: { lineStyle: { color: borderColor } },
                splitLine: { show: false },
                axisLabel: { color: mutedColor, hideOverlap: true },
            },
            {
                type: 'category',
                gridIndex: 1,
                data: times,
                boundaryGap: false,
                axisLine: { lineStyle: { color: borderColor } },
                splitLine: { show: false },
                axisLabel: { color: mutedColor, hideOverlap: true },
            },
        ],
        yAxis: [
            {
                scale: true,
                axisLabel: { color: mutedColor },
            },
            {
                scale: true,
                gridIndex: 1,
                splitNumber: 2,
                axisLabel: { color: mutedColor },
            },
        ],
        dataZoom: [
            {
                type: 'inside',
                xAxisIndex: [0, 1],
                start: 0,
                end: 100,
            },
            {
                show: true,
                type: 'slider',
                xAxisIndex: [0, 1],
                bottom: 12,
                height: 18,
                start: 0,
                end: 100,
            },
        ],
        series: [
            {
                name: '分时价格',
                type: 'line',
                data: priceData,
                showSymbol: false,
                smooth: true,
                lineStyle: { width: 1.6, color: priceColor },
                areaStyle: { color: 'rgba(37, 99, 235, 0.08)' },
            },
            {
                name: '均价',
                type: 'line',
                data: avgData,
                showSymbol: false,
                smooth: true,
                lineStyle: { width: 1.2, color: avgColor },
            },
            {
                name: '成交量',
                type: 'bar',
                xAxisIndex: 1,
                yAxisIndex: 1,
                data: volumeData,
                itemStyle: { color: volumeColor },
            },
        ],
    }, true);

    renderTechnicalSummary(payload);
}

function renderTechnicalChart(payload) {
    const chartContainer = document.getElementById('technicalChart');
    const emptyState = document.getElementById('technicalChartEmpty');
    const candles = Array.isArray(payload.candles) ? payload.candles : [];

    if (!candles.length) {
        emptyState.style.display = 'block';
        chartContainer.style.display = 'none';
        if (technicalChartInstance) {
            technicalChartInstance.dispose();
            technicalChartInstance = null;
        }
        renderTechnicalSummary(payload);
        return;
    }

    emptyState.style.display = 'none';
    chartContainer.style.display = 'block';

    if (!technicalChartInstance) {
        technicalChartInstance = echarts.init(chartContainer);
    }

    const riseColor = getCssVariable('--color-rise', '#d84b45');
    const fallColor = getCssVariable('--color-fall', '#1f9d55');
    const mutedColor = getCssVariable('--color-text-muted', '#6b7280');
    const borderColor = getCssVariable('--color-border', '#d1d5db');

    const dates = candles.map(item => item.trade_date);
    const candleData = candles.map(item => [item.open, item.close, item.low, item.high]);
    const ma5Data = candles.map(item => item.ma5);
    const ma20Data = candles.map(item => item.ma20);
    const ma60Data = candles.map(item => item.ma60);
    const macdData = candles.map(item => item.macd);
    const signalData = candles.map(item => item.macd_signal);
    const histData = candles.map(item => item.macd_hist);
    const markPointData = (payload.signals || []).map(signal => ({
        name: signal.signal_type === 'golden_cross' ? '金叉' : '死叉',
        coord: [signal.trade_date, signal.price],
        value: signal.signal_type === 'golden_cross' ? '金叉' : '死叉',
        itemStyle: {
            color: signal.signal_type === 'golden_cross' ? riseColor : fallColor,
        },
        label: {
            color: '#fff',
            fontSize: 10,
        },
    }));

    technicalChartInstance.setOption({
        animation: false,
        legend: {
            top: 0,
            data: ['K线', 'MA5', 'MA20', 'MA60', 'MACD', 'DIF', 'DEA'],
        },
        tooltip: {
            trigger: 'axis',
            axisPointer: {
                type: 'cross',
            },
        },
        axisPointer: {
            link: [{ xAxisIndex: [0, 1] }],
        },
        grid: [
            { left: '7%', right: '4%', top: 42, height: '56%' },
            { left: '7%', right: '4%', top: '74%', height: '16%' },
        ],
        xAxis: [
            {
                type: 'category',
                data: dates,
                scale: true,
                boundaryGap: false,
                axisLine: { onZero: false, lineStyle: { color: borderColor } },
                splitLine: { show: false },
                axisLabel: { color: mutedColor, hideOverlap: true },
                min: 'dataMin',
                max: 'dataMax',
            },
            {
                type: 'category',
                gridIndex: 1,
                data: dates,
                scale: true,
                boundaryGap: false,
                axisLine: { onZero: false, lineStyle: { color: borderColor } },
                splitLine: { show: false },
                axisLabel: { color: mutedColor, hideOverlap: true },
                min: 'dataMin',
                max: 'dataMax',
            },
        ],
        yAxis: [
            {
                scale: true,
                axisLabel: { color: mutedColor },
            },
            {
                scale: true,
                gridIndex: 1,
                splitNumber: 2,
                axisLabel: { color: mutedColor },
            },
        ],
        dataZoom: [
            {
                type: 'inside',
                xAxisIndex: [0, 1],
                start: 55,
                end: 100,
            },
            {
                show: true,
                type: 'slider',
                xAxisIndex: [0, 1],
                bottom: 12,
                height: 18,
                start: 55,
                end: 100,
            },
        ],
        series: [
            {
                name: 'K线',
                type: 'candlestick',
                data: candleData,
                itemStyle: {
                    color: riseColor,
                    color0: fallColor,
                    borderColor: riseColor,
                    borderColor0: fallColor,
                },
                markPoint: markPointData.length ? { data: markPointData } : undefined,
            },
            {
                name: 'MA5',
                type: 'line',
                data: ma5Data,
                showSymbol: false,
                smooth: true,
                lineStyle: { width: 1.4, color: '#2563eb' },
            },
            {
                name: 'MA20',
                type: 'line',
                data: ma20Data,
                showSymbol: false,
                smooth: true,
                lineStyle: { width: 1.4, color: '#0f766e' },
            },
            {
                name: 'MA60',
                type: 'line',
                data: ma60Data,
                showSymbol: false,
                smooth: true,
                lineStyle: { width: 1.2, color: '#7c3aed' },
            },
            {
                name: 'MACD',
                type: 'bar',
                xAxisIndex: 1,
                yAxisIndex: 1,
                data: histData,
                itemStyle: {
                    color: params => (params.value >= 0 ? riseColor : fallColor),
                },
            },
            {
                name: 'DIF',
                type: 'line',
                xAxisIndex: 1,
                yAxisIndex: 1,
                data: macdData,
                showSymbol: false,
                smooth: true,
                lineStyle: { width: 1.2, color: '#f59e0b' },
            },
            {
                name: 'DEA',
                type: 'line',
                xAxisIndex: 1,
                yAxisIndex: 1,
                data: signalData,
                showSymbol: false,
                smooth: true,
                lineStyle: { width: 1.2, color: '#475569' },
            },
        ],
    }, true);

    renderTechnicalSummary(payload);
}

async function loadTechnicalChart(timeframe = activeTechnicalTimeframe) {
    activeTechnicalTimeframe = timeframe;
    const requestToken = ++technicalRequestToken;
    setTechnicalButtonState(timeframe);

    const cachedPayload = getCachedTechnicalPayload(timeframe);
    if (cachedPayload) {
        if (timeframe === 'intraday') {
            renderIntradayChart(cachedPayload);
        } else {
            renderTechnicalChart(cachedPayload);
        }
        return;
    }

    const summaryEl = document.getElementById('technicalSummary');
    summaryEl.textContent = `${getTimeframeLabel(timeframe)}加载中...`;
    if (technicalRequestController) {
        technicalRequestController.abort();
    }
    technicalRequestController = typeof AbortController !== 'undefined' ? new AbortController() : null;
    const request = buildTechnicalRequest(timeframe);

    try {
        const payload = await fetchJsonOrThrowCompat(request.url, {
            signal: technicalRequestController ? technicalRequestController.signal : undefined,
        });
        if (requestToken !== technicalRequestToken || timeframe !== activeTechnicalTimeframe) {
            return;
        }
        const normalizedPayload = timeframe === 'intraday' ? { ...payload, timeframe } : payload;
        setCachedTechnicalPayload(timeframe, normalizedPayload);
        request.render(normalizedPayload);
    } catch (error) {
        if (requestToken !== technicalRequestToken || timeframe !== activeTechnicalTimeframe) {
            return;
        }
        console.error('加载技术图表失败:', error);
        const emptyPayload = timeframe === 'intraday'
            ? { timeframe, points: [], latest_point: null, session_date: null }
            : { timeframe, candles: [], signals: [], latest_signal: null };
        if (timeframe === 'intraday') {
            renderIntradayChart(emptyPayload);
        } else {
            renderTechnicalChart(emptyPayload);
        }
        summaryEl.textContent = `${getTimeframeLabel(timeframe)}加载失败`;
    } finally {
        if (requestToken === technicalRequestToken) {
            technicalRequestController = null;
        }
    }
}

function prefetchTechnicalChart(timeframe) {
    if (timeframe === activeTechnicalTimeframe || getCachedTechnicalPayload(timeframe)) {
        return;
    }

    const request = buildTechnicalRequest(timeframe);
    fetchJsonOrThrowCompat(request.url)
        .then(payload => {
            const normalizedPayload = timeframe === 'intraday' ? { ...payload, timeframe } : payload;
            setCachedTechnicalPayload(timeframe, normalizedPayload);
        })
        .catch(error => {
            console.debug(`预取${getTimeframeLabel(timeframe)}图表失败:`, error);
        });
}

function warmTechnicalChartCache() {
    ['intraday', 'day', 'week', 'month'].forEach((timeframe, index) => {
        window.setTimeout(() => prefetchTechnicalChart(timeframe), 300 * (index + 1));
    });
}

function initTechnicalControls() {
    document.querySelectorAll('[data-technical-timeframe]').forEach(button => {
        button.addEventListener('click', () => {
            loadTechnicalChart(button.dataset.technicalTimeframe);
        });
    });

    window.addEventListener('resize', () => {
        if (technicalChartInstance) {
            technicalChartInstance.resize();
        }
    });
}

function createPercentileChart(canvasId, percentileValue, label, color) {
    const ChartConstructor = window.Chart;
    const canvas = document.getElementById(canvasId);
    if (!ChartConstructor || !canvas) {
        console.warn('Chart.js 未就绪，跳过估值分位图渲染:', canvasId);
        return;
    }

    const chartPercentile = clampPercentile(percentileValue);
    const ctx = canvas.getContext('2d');

    if (canvasId === 'peChart' && peChartInstance) {
        peChartInstance.destroy();
    }
    if (canvasId === 'pbChart' && pbChartInstance) {
        pbChartInstance.destroy();
    }

    const instance = new ChartConstructor(ctx, {
        type: 'doughnut',
        data: {
            labels: [`${label} 当前分位`, '其余区间'],
            datasets: [{
                data: [chartPercentile ?? 0, Math.max(0, 100 - (chartPercentile ?? 0))],
                backgroundColor: [color, '#e5e7eb'],
                borderWidth: 0,
                hoverOffset: 0,
            }]
        },
        options: {
            responsive: true,
            maintainAspectRatio: false,
            animation: false,
            cutout: '76%',
            rotation: -90,
            circumference: 360,
            plugins: {
                legend: { display: false },
                tooltip: {
                    callbacks: {
                        label: (context) => `${context.label}: ${Number(context.raw).toFixed(1)}%`,
                    }
                }
            }
        }
    });

    if (canvasId === 'peChart') {
        peChartInstance = instance;
    } else if (canvasId === 'pbChart') {
        pbChartInstance = instance;
    }
}

// 加载股票详情
async function loadStockDetail() {
    try {
        const result = await fetchJsonOrThrowCompat(`/api/equity/valuation/${stockCode}/`, {
            timeoutMs: valuationLoadTimeoutMs,
        });

        if (result.success) {
            updateBasicInfo(result);
            updateValuation(result);
            updateFinancials(result);
            createCharts(result);
        } else {
            setDetailHeaderFallback();
            updateDataTime(null);
            console.warn('估值详情返回失败状态:', result.error || result);
        }
    } catch (error) {
        console.error('加载股票详情失败:', error);
        setDetailHeaderFallback();
        updateDataTime(null);
    }
}

// 获取实时价格
async function loadRealtimePrice() {
    const priceEl = document.getElementById('detailPrice');
    try {
        const quote = await fetchJsonOrThrowCompat(
            `/api/data-center/prices/quotes/?asset_code=${encodeURIComponent(stockCode)}`
        );
        const latestPrice = typeof quote.current_price === 'number'
            ? quote.current_price
            : quote.price;
        if (latestPrice && latestPrice > 0) {
            priceEl.textContent = latestPrice.toFixed(2);
            updatePriceTimestamp(quote.snapshot_at || quote.timestamp, '行情快照时间');
            const changePct = quote.change_pct !== undefined && quote.change_pct !== null
                ? quote.change_pct
                : (
                    quote.prev_close && quote.prev_close > 0
                        ? ((latestPrice - quote.prev_close) / quote.prev_close) * 100
                        : null
                );
            if (changePct !== undefined && changePct !== null) {
                const changeClass = changePct >= 0 ? 'text-rise' : 'text-fall';
                priceEl.className = 'metric-value ' + changeClass;
            }
            return;
        }
    } catch (e) {
        console.warn('获取实时价格失败:', e);
    }
    if (priceEl.textContent === '-' || !priceEl.textContent.trim()) {
        document.getElementById('detailPriceTimestamp').textContent = '实时价格不可用';
    }
}

function updateBasicInfo(data) {
    document.getElementById('stockName').textContent = data.name || data.stock_name || '未知';
    document.getElementById('detailSector').textContent = data.sector || '-';
    document.getElementById('detailMarket').textContent = data.market || '-';
    document.getElementById('detailListDate').textContent = data.list_date || '-';
}

function updateValuation(data) {
    const latest = getValuationSnapshot(data);
    if (latest) {
        setFallbackClosePrice(latest.price, latest.trade_date);
        document.getElementById('detailPe').textContent = formatMetricNumber(latest.pe);
        document.getElementById('detailPb').textContent = formatMetricNumber(latest.pb);
        document.getElementById('detailPs').textContent = formatMetricNumber(latest.ps);
        document.getElementById('detailTotalMv').textContent = formatMetricMoney(latest.total_mv);
        document.getElementById('detailCircMv').textContent = formatMetricMoney(latest.circ_mv);
        document.getElementById('detailDivYield').textContent = formatMetricPercent(latest.dividend_yield);
        document.getElementById('detailTurnover').textContent = formatMetricPercent(latest.turnover_rate);

        // 更新数据时间
        updateDataTime(latest.updated_at || latest.trade_date);

        // 更新估值分位数
        if (latest.pe_percentile !== undefined) {
            const pePercentile = clampPercentile(latest.pe_percentile);
            document.getElementById('pePercentile').textContent = pePercentile !== null ? pePercentile.toFixed(1) : '-';
            document.getElementById('peHint').textContent = getPercentileHint(pePercentile);
        }
        if (latest.pb_percentile !== undefined) {
            const pbPercentile = clampPercentile(latest.pb_percentile);
            document.getElementById('pbPercentile').textContent = pbPercentile !== null ? pbPercentile.toFixed(1) : '-';
            document.getElementById('pbHint').textContent = getPercentileHint(pbPercentile);
        }
    } else {
        updateDataTime(null);
    }

    // DCF 估值
    if (data.dcf_value) {
        document.getElementById('dcfCard').style.display = 'block';
        document.getElementById('dcfIntrinsicValue').textContent = '¥' + data.dcf_value.intrinsic_value_per_share.toFixed(2);
        document.getElementById('dcfCurrentPrice').textContent = '¥' + data.dcf_value.current_price.toFixed(2);
        const upside = (data.dcf_value.upside * 100).toFixed(1);
        document.getElementById('dcfUpside').textContent = upside + '%';
        document.getElementById('dcfUpside').className = 'metric-value ' + (data.dcf_value.upside > 0 ? 'text-rise' : 'text-fall');
    }
}

function updateFinancials(data) {
    const financial = data.financial_data;
    if (financial) {
        document.getElementById('financialPeriodMeta').textContent = [
            `报告期: ${financial.period_end || '--'}`,
            `披露日: ${financial.report_date || '--'}`,
            `区间类型: ${formatFinancialPeriodType(financial.period_type)}`,
            `来源: ${financial.source || '--'}`
        ].join(' · ');
        document.getElementById('detailRoe').textContent = formatMetricPercent(financial.roe);
        document.getElementById('detailRoa').textContent = formatMetricPercent(financial.roa);
        document.getElementById('detailRevenue').textContent = formatMetricMoney(financial.revenue);
        document.getElementById('detailNetProfit').textContent = formatMetricMoney(financial.net_profit);
        document.getElementById('detailRevenueGrowth').textContent = formatMetricPercent(financial.revenue_growth);
        document.getElementById('detailProfitGrowth').textContent = formatMetricPercent(financial.net_profit_growth);
        document.getElementById('detailDebtRatio').textContent = formatMetricPercent(financial.debt_ratio);
        document.getElementById('detailGrossMargin').textContent = formatMetricPercent(financial.gross_margin);
    }
}

function createCharts(data) {
    const latest = getValuationSnapshot(data);
    createPercentileChart('peChart', latest?.pe_percentile, 'PE', '#2563eb');
    createPercentileChart('pbChart', latest?.pb_percentile, 'PB', '#0f766e');
}

function formatMoney(value) {
    if (value >= 1e12) return (value / 1e12).toFixed(2) + '万亿';
    if (value >= 1e8) return (value / 1e8).toFixed(2) + '亿';
    if (value >= 1e4) return (value / 1e4).toFixed(2) + '万';
    return value.toFixed(2);
}

function hasMetricValue(value) {
    return value !== null && value !== undefined && !Number.isNaN(Number(value));
}

function formatMetricNumber(value, digits = 2) {
    return hasMetricValue(value) ? Number(value).toFixed(digits) : '-';
}

function formatMetricPercent(value, digits = 2) {
    return hasMetricValue(value) ? Number(value).toFixed(digits) + '%' : '-';
}

function formatMetricMoney(value) {
    return hasMetricValue(value) ? formatMoney(Number(value)) : '-';
}

function formatFinancialPeriodType(value) {
    const labels = {
        annual: '年度',
        semi_annual: '半年度',
        quarterly: '季度',
        ttm: 'TTM',
    };
    return labels[value] || value || '--';
}

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

function safeExternalUrl(url) {
    if (!url) return '';
    try {
        const parsed = new URL(url, window.location.origin);
        if (parsed.protocol === 'http:' || parsed.protocol === 'https:') {
            return parsed.toString();
        }
    } catch (_) {
    }
    return '';
}

function formatPercentFromRatio(value) {
    if (value === null || value === undefined || Number.isNaN(Number(value))) {
        return '-';
    }
    return (Number(value) * 100).toFixed(2) + '%';
}

function formatNumber(value, digits = 2) {
    if (value === null || value === undefined || Number.isNaN(Number(value))) {
        return '-';
    }
    return Number(value).toFixed(digits);
}

function getRegimeLabel(regime) {
    const labels = {
        Recovery: '复苏 (Recovery)',
        Overheat: '过热 (Overheat)',
        Stagflation: '滞胀 (Stagflation)',
        Deflation: '通缩 (Deflation)',
    };
    return labels[regime] || escapeHtml(regime || '-');
}

function getPulseStrengthLabel(value) {
    const labels = {
        weak: '弱',
        moderate: '中等',
        strong: '强',
    };
    return labels[value] || escapeHtml(value || '-');
}

function getPulseSignalLabel(value) {
    const labels = {
        bullish: '偏多',
        bearish: '偏空',
        neutral: '中性',
        improving: '改善',
        weakening: '走弱',
    };
    return labels[value] || escapeHtml(value || '-');
}

function renderRegimeCorrelationMessage(message) {
    const tbody = document.getElementById('regimeTableBody');
    tbody.innerHTML = `<tr><td colspan="4" style="text-align: center; color: var(--color-text-muted); padding: 16px;">${escapeHtml(message)}</td></tr>`;
}

async function loadRegimeCorrelation() {
    const tbody = document.getElementById('regimeTableBody');
    try {
        const result = await fetchJsonOrThrowCompat(`/api/equity/regime-correlation/${stockCode}/`);
        const rows = Array.isArray(result.regime_performance) ? result.regime_performance : [];

        if (!result.success || rows.length === 0) {
            renderRegimeCorrelationMessage(result.error || '暂无 Regime 相关性数据');
            return;
        }

        const order = ['Recovery', 'Overheat', 'Stagflation', 'Deflation'];
        const rowMap = new Map(rows.map(item => [item.regime, item]));
        tbody.innerHTML = order.map(regime => {
            const item = rowMap.get(regime);
            if (!item) {
                return `<tr>
                    <td>${getRegimeLabel(regime)}</td>
                    <td>-</td>
                    <td>-</td>
                    <td>-</td>
                </tr>`;
            }

            const avgReturn = Number(item.avg_return);
            const returnClass = Number.isNaN(avgReturn) ? '' : (avgReturn >= 0 ? 'text-rise' : 'text-fall');
            return `<tr>
                <td>${getRegimeLabel(regime)}</td>
                <td class="${returnClass}">${formatPercentFromRatio(item.avg_return)}</td>
                <td>${formatNumber(item.beta, 2)}</td>
                <td>${item.sample_days ?? '-'}</td>
            </tr>`;
        }).join('');
    } catch (error) {
        console.warn('加载 Regime 相关性失败:', error);
        renderRegimeCorrelationMessage(error.message || '加载 Regime 相关性失败');
    }
}

function renderPulseMessage(message) {
    document.getElementById('pulseCompositeScore').textContent = '-';
    document.getElementById('pulseRegimeContext').textContent = '-';
    document.getElementById('pulseStrength').textContent = '-';
    document.getElementById('pulseTransitionWarning').textContent = escapeHtml(message);
    document.getElementById('pulseDimensionGrid').innerHTML = '';
    const reasonsEl = document.getElementById('pulseReasons');
    reasonsEl.style.display = 'block';
    reasonsEl.textContent = message;
}

function updatePulseCard(data) {
    document.getElementById('pulseCompositeScore').textContent = formatNumber(data.composite_score, 2);
    document.getElementById('pulseRegimeContext').textContent = getRegimeLabel(data.regime_context);
    document.getElementById('pulseStrength').textContent = getPulseStrengthLabel(data.regime_strength);
    document.getElementById('pulseTransitionWarning').textContent = data.transition_warning
        ? `预警${data.transition_direction ? ' · ' + getPulseSignalLabel(data.transition_direction) : ''}`
        : '无';
    document.getElementById('pulseTransitionWarning').className = 'metric-value ' + (data.transition_warning ? 'text-fall' : '');
    document.getElementById('pulseObservedAt').textContent = `更新时间: ${data.observed_at ? new Date(data.observed_at).toLocaleString('zh-CN') : '--'}`;

    const dimensions = data.dimensions || {};
    const dimensionOrder = ['growth', 'inflation', 'liquidity', 'sentiment'];
    document.getElementById('pulseDimensionGrid').innerHTML = dimensionOrder
        .filter(key => dimensions[key])
        .map(key => {
            const item = dimensions[key];
            return `<div class="pulse-dimension-card">
                <div class="pulse-dimension-title">${escapeHtml(key)}</div>
                <div class="pulse-dimension-score">${formatNumber(item.score, 2)}</div>
                <div class="pulse-dimension-signal">${getPulseSignalLabel(item.signal)} · 指标 ${item.indicator_count ?? 0} 个</div>
            </div>`;
        })
        .join('');

    const reasonsEl = document.getElementById('pulseReasons');
    const reasons = Array.isArray(data.transition_reasons) ? data.transition_reasons.filter(Boolean) : [];
    if (reasons.length > 0) {
        reasonsEl.style.display = 'block';
        reasonsEl.innerHTML = `<strong style="display: block; margin-bottom: 6px; color: var(--color-text-primary);">转折说明</strong>${reasons.map(reason => escapeHtml(reason)).join('<br>')}`;
    } else {
        reasonsEl.style.display = 'none';
        reasonsEl.innerHTML = '';
    }
}

async function loadPulseContext() {
    try {
        const result = await fetchJsonOrThrowCompat('/api/pulse/current/');
        if (!result.success || !result.data) {
            renderPulseMessage(result.error || '暂无 Pulse 数据');
            return;
        }
        updatePulseCard(result.data);
    } catch (error) {
        console.warn('加载 Pulse 数据失败:', error);
        renderPulseMessage(error.message || '加载 Pulse 数据失败');
    }
}

// ========== 资金流向 ==========
async function loadCapitalFlow() {
    const loading = document.getElementById('capitalFlowLoading');
    const empty = document.getElementById('capitalFlowEmpty');
    const table = document.getElementById('capitalFlowTable');
    try {
        const start = new Date();
        start.setDate(start.getDate() - 10);
        const result = await fetchJsonOrThrowCompat(
            `/api/data-center/capital-flows/?asset_code=${stockCode}&start=${start.toISOString().slice(0, 10)}`
        );
        loading.style.display = 'none';
        if (!result.data || result.data.length === 0) {
            empty.style.display = 'block';
            return;
        }
        table.style.display = 'block';
        const tbody = document.getElementById('capitalFlowBody');
        const sortedRows = [...result.data].sort((a, b) => String(b.trade_date || '').localeCompare(String(a.trade_date || '')));
        tbody.innerHTML = sortedRows.map(row => {
            const cls = v => v >= 0 ? 'text-inflow' : 'text-outflow';
            const fmt = v => {
                const n = parseFloat(v);
                if (isNaN(n)) return '-';
                return (n >= 0 ? '+' : '') + formatMoney(Math.abs(n));
            };
            return `<tr>
                <td>${row.trade_date}</td>
                <td class="${cls(row.main_net_inflow)}">${fmt(row.main_net_inflow)}</td>
                <td class="${cls(row.main_net_ratio)}">${parseFloat(row.main_net_ratio).toFixed(2)}%</td>
                <td class="${cls(row.super_large_net_inflow)}">${fmt(row.super_large_net_inflow)}</td>
                <td class="${cls(row.large_net_inflow)}">${fmt(row.large_net_inflow)}</td>
                <td class="${cls(row.medium_net_inflow)}">${fmt(row.medium_net_inflow)}</td>
                <td class="${cls(row.small_net_inflow)}">${fmt(row.small_net_inflow)}</td>
            </tr>`;
        }).join('');
    } catch (e) {
        loading.style.display = 'none';
        empty.style.display = 'block';
        console.warn('加载资金流向失败:', e);
    }
}

// ========== 个股新闻 ==========
async function loadStockNews() {
    const loading = document.getElementById('stockNewsLoading');
    const empty = document.getElementById('stockNewsEmpty');
    const list = document.getElementById('stockNewsList');
    try {
        const result = await fetchJsonOrThrowCompat(`/api/data-center/news/?asset_code=${stockCode}&limit=10`);
        loading.style.display = 'none';
        if (!result.data || result.data.length === 0) {
            empty.style.display = 'block';
            return;
        }
        list.style.display = 'block';
        const sortedNews = [...result.data].sort((a, b) => {
            const left = a.published_at ? new Date(a.published_at).getTime() : 0;
            const right = b.published_at ? new Date(b.published_at).getTime() : 0;
            return right - left;
        });
        list.innerHTML = sortedNews.map(item => {
            const time = item.published_at ? new Date(item.published_at).toLocaleString('zh-CN') : '';
            const safeTitle = escapeHtml(item.title);
            const safeContent = escapeHtml(item.content || '');
            const safeUrl = safeExternalUrl(item.url);
            const link = safeUrl ? `<a href="${safeUrl}" target="_blank" rel="noopener noreferrer" style="color: var(--color-primary); margin-left: 8px; font-size: 12px;">原文</a>` : '';
            return `<div style="padding: 12px 0; border-bottom: 1px solid var(--color-border);">
                <div style="font-weight: 500; color: var(--color-text-primary);">${safeTitle}${link}</div>
                <div style="font-size: 12px; color: var(--color-text-muted); margin-top: 4px;">${escapeHtml(time)}</div>
                ${safeContent ? `<div style="font-size: 13px; color: var(--color-text-secondary); margin-top: 6px; line-height: 1.5;">${safeContent.substring(0, 200)}${safeContent.length > 200 ? '...' : ''}</div>` : ''}
            </div>`;
        }).join('');
    } catch (e) {
        loading.style.display = 'none';
        empty.style.display = 'block';
        console.warn('加载新闻失败:', e);
    }
}

// 手动刷新数据
let isRefreshing = false;

async function refreshStockData() {
    if (isRefreshing) return;
    
    const btn = document.getElementById('refreshDataBtn');
    const icon = document.getElementById('refreshIcon');
    const text = document.getElementById('refreshBtnText');
    
    // 设置加载状态
    isRefreshing = true;
    btn.disabled = true;
    icon.style.animation = 'spin 1s linear infinite';
    text.textContent = '刷新中...';
    
    try {
        // 1. 同步估值数据
        const syncResult = await fetchJsonOrThrowCompat('/api/equity/valuation-data/sync/', {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify({
                stock_codes: [stockCode],
                days_back: 7,
                primary_source: 'akshare'
            })
        });
        
        if (!syncResult.success && !syncResult.synced_count) {
            throw new Error(syncResult.error || '同步失败');
        }
        
        // 2. 同步财务数据
        await fetchJsonOrThrowCompat('/api/equity/financial-data/sync/', {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify({
                stock_codes: [stockCode],
                periods: 8,
                source: 'akshare'
            })
        });
        
        // 3. 重新加载数据
        await Promise.allSettled([
            loadStockDetail(),
            loadCapitalFlow(),
            loadTechnicalChart(activeTechnicalTimeframe),
            loadRealtimePrice(),
        ]);
        
        // 4. 更新时间显示
        updateDataTime(new Date().toISOString());
        
        // 显示成功提示
        text.textContent = '已刷新';
        setTimeout(() => {
            text.textContent = '刷新';
        }, 2000);
        
    } catch (e) {
        console.error('刷新数据失败:', e);
        text.textContent = '失败';
        setTimeout(() => {
            text.textContent = '刷新';
        }, 2000);
        alert('刷新数据失败: ' + e.message);
    } finally {
        isRefreshing = false;
        btn.disabled = false;
        icon.style.animation = '';
    }
}

function initializeDetailPage() {
    activeTechnicalTimeframe = resolveInitialTechnicalTimeframe(marketSessionProfile);
    setDetailHeaderFallback();
    initTechnicalControls();
    setExternalLinks();
    loadStockDetail();
    loadTechnicalChart(activeTechnicalTimeframe);
    warmTechnicalChartCache();
    loadRealtimePrice();
    loadPulseContext();
    loadRegimeCorrelation();
    loadCapitalFlow();
    loadStockNews();
}

if (document.readyState === 'loading') {
    document.addEventListener('DOMContentLoaded', initializeDetailPage, { once: true });
} else {
    initializeDetailPage();
}
</script>

<style>
@keyframes spin {
    from { transform: rotate(0deg); }
    to { transform: rotate(360deg); }
}
</style>

<!-- Floating Widget Config -->
<script>
    // Load floating widget config from JSON file
    fetch('{% static "widgets/configs/equity_widget.json" %}')
        .then(response => response.json())
        .then(config => {
            window.floatingWidgetConfig = config;
            // Trigger initialization manually since DOM is already loaded
            if (window.initFloatingWidget) {
                window.initFloatingWidget(config);
            }
        })
        .catch(err => console.error('Failed to load widget config:', err));
</script>

{% endblock %}
