// 漏洞管理相关功能

function vulnT(key, opts) {
    if (typeof window.t === 'function') {
        return window.t(key, opts);
    }
    return key;
}

function vulnDateLocale() {
    try {
        const lang = (window.__locale || '').toLowerCase();
        if (lang.indexOf('zh') === 0) {
            return 'zh-CN';
        }
    } catch (e) { /* ignore */ }
    return 'en-US';
}

function vulnSeverityLabel(code) {
    const m = {
        critical: 'dashboard.severityCritical',
        high: 'dashboard.severityHigh',
        medium: 'dashboard.severityMedium',
        low: 'dashboard.severityLow',
        info: 'dashboard.severityInfo'
    };
    return m[code] ? vulnT(m[code]) : code;
}

function vulnStatusLabel(code) {
    const m = {
        open: 'vulnerabilityPage.statusOpen',
        confirmed: 'vulnerabilityPage.statusConfirmed',
        fixed: 'vulnerabilityPage.statusFixed',
        false_positive: 'vulnerabilityPage.statusFalsePositive',
        ignored: 'vulnerabilityPage.statusIgnored'
    };
    return m[code] ? vulnT(m[code]) : code;
}

const VULN_STATUS_CODES = ['open', 'confirmed', 'fixed', 'ignored', 'false_positive'];
const VULNERABILITY_REMOVE_ANIM_MS = 200;

function getVulnerabilityScrollContainer() {
    const page = document.getElementById('page-vulnerabilities');
    return page ? page.querySelector('.page-content') : null;
}

function getExpandedVulnerabilityIds() {
    const ids = [];
    document.querySelectorAll('#vulnerabilities-list .vulnerability-content').forEach(function (el) {
        if (el.style.display !== 'none') {
            const id = (el.id || '').replace(/^content-/, '');
            if (id) ids.push(id);
        }
    });
    return ids;
}

function restoreExpandedVulnerabilityDetails(expandedIds) {
    if (!expandedIds || !expandedIds.length) return;
    expandedIds.forEach(function (id) {
        const content = document.getElementById('content-' + id);
        const icon = document.getElementById('expand-icon-' + id);
        if (!content || content.style.display !== 'none') return;
        content.style.display = 'block';
        if (icon) icon.style.transform = 'rotate(90deg)';
        loadVulnerabilityRelatedFacts(id).catch(function (e) { console.warn(e); });
    });
}

function buildVulnerabilityStatusPicker(vuln) {
    const current = vuln.status || 'open';
    const id = escapeHtml(vuln.id);
    const label = escapeHtml(vulnT('vulnerabilityPage.statusChangeLabel'));
    const caretSvg = '<svg class="vuln-status-picker-caret" width="12" height="12" viewBox="0 0 24 24" fill="none" aria-hidden="true"><path d="M6 9l6 6 6-6" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg>';
    const options = VULN_STATUS_CODES.map(function (code) {
        const selected = code === current;
        const selCls = selected ? ' is-selected' : '';
        const ariaSel = selected ? ' aria-selected="true"' : ' aria-selected="false"';
        return '<button type="button" class="vuln-status-picker-option' + selCls + '" role="option" data-value="' + code + '"' + ariaSel + '>' +
            '<span class="vuln-status-picker-check" aria-hidden="true">✓</span>' +
            '<span class="vuln-status-picker-label">' + escapeHtml(vulnStatusLabel(code)) + '</span>' +
            '</button>';
    }).join('');
    return '<div class="vuln-status-picker status-' + escapeHtml(current) + '" data-vuln-id="' + id + '" data-prev-status="' + escapeHtml(current) + '">' +
        '<button type="button" class="vuln-status-picker-trigger" aria-label="' + label + '" aria-haspopup="listbox" aria-expanded="false">' +
        '<span class="vuln-status-picker-value">' + escapeHtml(vulnStatusLabel(current)) + '</span>' +
        caretSvg +
        '</button>' +
        '<div class="vuln-status-picker-menu" role="listbox" hidden>' + options + '</div>' +
        '</div>';
}

const VULN_STATUS_PICKER_STATUS_CLASSES = VULN_STATUS_CODES.map(function (code) {
    return 'status-' + code;
});

function setVulnerabilityStatusPickerDisabled(pickerEl, disabled) {
    if (!pickerEl) return;
    pickerEl.classList.toggle('is-disabled', !!disabled);
    const trigger = pickerEl.querySelector('.vuln-status-picker-trigger');
    if (trigger) trigger.disabled = !!disabled;
}

function updateVulnerabilityStatusPicker(pickerEl, status) {
    if (!pickerEl) return;
    const code = status || 'open';
    VULN_STATUS_PICKER_STATUS_CLASSES.forEach(function (cls) {
        pickerEl.classList.remove(cls);
    });
    pickerEl.classList.add('status-' + code);
    pickerEl.dataset.prevStatus = code;
    const valueEl = pickerEl.querySelector('.vuln-status-picker-value');
    if (valueEl) valueEl.textContent = vulnStatusLabel(code);
    pickerEl.querySelectorAll('.vuln-status-picker-option').forEach(function (opt) {
        const isSel = opt.getAttribute('data-value') === code;
        opt.classList.toggle('is-selected', isSel);
        opt.setAttribute('aria-selected', isSel ? 'true' : 'false');
    });
}

let vulnerabilityStatusPickerDocBound = false;

function closeAllVulnerabilityStatusPickers() {
    document.querySelectorAll('.vuln-status-picker.open').forEach(function (picker) {
        picker.classList.remove('open');
        const menu = picker.querySelector('.vuln-status-picker-menu');
        const trigger = picker.querySelector('.vuln-status-picker-trigger');
        if (menu) menu.hidden = true;
        if (trigger) trigger.setAttribute('aria-expanded', 'false');
    });
}

function initVulnerabilityStatusPickers(root) {
    if (!vulnerabilityStatusPickerDocBound) {
        document.addEventListener('click', closeAllVulnerabilityStatusPickers);
        document.addEventListener('keydown', function (e) {
            if (e.key === 'Escape') closeAllVulnerabilityStatusPickers();
        });
        vulnerabilityStatusPickerDocBound = true;
    }

    const scope = root || document.getElementById('vulnerabilities-list');
    if (!scope) return;

    scope.querySelectorAll('.vuln-status-picker').forEach(function (picker) {
        if (picker.dataset.bound === '1') return;
        picker.dataset.bound = '1';

        picker.addEventListener('click', function (e) { e.stopPropagation(); });
        picker.addEventListener('keydown', function (e) { e.stopPropagation(); });

        const trigger = picker.querySelector('.vuln-status-picker-trigger');
        const menu = picker.querySelector('.vuln-status-picker-menu');
        if (!trigger || !menu) return;

        trigger.addEventListener('click', function (e) {
            e.stopPropagation();
            if (picker.classList.contains('is-disabled')) return;
            const wasOpen = picker.classList.contains('open');
            closeAllVulnerabilityStatusPickers();
            closeAllVulnDetailProjectSelects();
            closeAllVulnFilterCustomSelects();
            if (!wasOpen) {
                picker.classList.add('open');
                menu.hidden = false;
                trigger.setAttribute('aria-expanded', 'true');
            }
        });

        menu.addEventListener('click', function (e) {
            e.stopPropagation();
            const opt = e.target.closest('.vuln-status-picker-option');
            if (!opt || picker.classList.contains('is-disabled')) return;
            const newStatus = opt.getAttribute('data-value');
            const vulnId = picker.dataset.vulnId;
            closeAllVulnerabilityStatusPickers();
            changeVulnerabilityStatus(vulnId, newStatus, picker);
        });
    });
}

function vulnerabilityStatusMatchesFilter(status) {
    const filterStatus = (vulnerabilityFilters.status || '').trim();
    return !filterStatus || filterStatus === status;
}

function removeVulnerabilityCard(vulnId, options) {
    const opts = options || {};
    const card = document.getElementById('vulnerability-card-' + vulnId) ||
        document.querySelector('.vulnerability-card[data-vuln-id="' + vulnId + '"]');
    if (!card) return;

    const nextCard = card.nextElementSibling;
    card.classList.add('vulnerability-card--removing');

    setTimeout(function () {
        card.remove();
        if (opts.decrementTotal !== false) {
            vulnerabilityPagination.total = Math.max(0, (vulnerabilityPagination.total || 0) - 1);
            vulnerabilityPagination.totalPages = Math.max(
                1,
                Math.ceil(vulnerabilityPagination.total / vulnerabilityPagination.pageSize)
            );
            renderVulnerabilityPagination();
        }

        const list = document.getElementById('vulnerabilities-list');
        const remaining = list ? list.querySelectorAll('.vulnerability-card').length : 0;
        if (remaining === 0) {
            if (vulnerabilityPagination.currentPage > 1) {
                vulnerabilityPagination.currentPage--;
            }
            loadVulnerabilities();
            return;
        }

        if (opts.focusNext !== false && nextCard && nextCard.classList.contains('vulnerability-card')) {
            nextCard.scrollIntoView({ block: 'nearest', behavior: 'smooth' });
        }
    }, VULNERABILITY_REMOVE_ANIM_MS);
}

async function changeVulnerabilityStatus(vulnId, newStatus, pickerEl) {
    if (!vulnId || !pickerEl) return;
    const prevStatus = pickerEl.dataset.prevStatus || newStatus;
    if (newStatus === prevStatus) return;

    setVulnerabilityStatusPickerDisabled(pickerEl, true);
    try {
        const response = await apiFetch('/api/vulnerabilities/' + encodeURIComponent(vulnId), {
            method: 'PUT',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify({ status: newStatus }),
        });
        if (!response.ok) {
            const err = await response.json().catch(function () { return {}; });
            throw new Error(err.error || vulnT('vulnerabilityPage.statusUpdateFailed'));
        }

        updateVulnerabilityStatusPicker(pickerEl, newStatus);
        loadVulnerabilityStats();

        if (!vulnerabilityStatusMatchesFilter(newStatus)) {
            removeVulnerabilityCard(vulnId, { decrementTotal: true, focusNext: true });
        }
    } catch (error) {
        console.error('更新漏洞状态失败:', error);
        updateVulnerabilityStatusPicker(pickerEl, prevStatus);
        alert(vulnT('vulnerabilityPage.statusUpdateFailed') + ': ' + error.message);
    } finally {
        setVulnerabilityStatusPickerDisabled(pickerEl, false);
    }
}

// 从localStorage读取每页显示数量，默认为20
const getVulnerabilityPageSize = () => {
    const saved = localStorage.getItem('vulnerabilityPageSize');
    return saved ? parseInt(saved, 10) : 20;
};

let currentVulnerabilityId = null;
let vulnerabilityFilters = {
    q: '',
    id: '',
    project_id: '',
    conversation_id: '',
    task_id: '',
    conversation_tag: '',
    task_tag: '',
    severity: '',
    status: ''
};
let vulnerabilityPagination = {
    currentPage: 1,
    pageSize: getVulnerabilityPageSize(),
    total: 0,
    totalPages: 1
};

const VULN_STAT_SEVERITIES = ['critical', 'high', 'medium', 'low', 'info'];
let vulnerabilityStatCardsBound = false;
let vulnerabilityFilterPanelBound = false;
let vulnerabilityFilterOptionsCache = null;
let vulnerabilityMoreFiltersPopoverOpen = false;
let vulnerabilityFilterDebounceTimer = null;
const VULNERABILITY_FILTER_DEBOUNCE_MS = 400;
const VULNERABILITY_DATALIST_MAX = 8;
const VULNERABILITY_DATALIST_MIN_QUERY = 2;

const VULN_FILTER_CHIP_FIELDS = [
    { key: 'q', labelKey: 'vulnerabilityPage.searchKeywordShort' },
    { key: 'id', labelKey: 'vulnerabilityPage.vulnId' },
    { key: 'status', labelKey: null, format: 'status' },
    { key: 'severity', labelKey: null, format: 'severity' },
    { key: 'project_id', labelKey: null },
    { key: 'conversation_id', labelKey: 'vulnerabilityPage.conversationId' },
    { key: 'task_id', labelKey: 'vulnerabilityPage.taskOrQueueId' },
    { key: 'conversation_tag', labelKey: 'vulnerabilityPage.conversationTag' },
    { key: 'task_tag', labelKey: 'vulnerabilityPage.taskTag' }
];

// 从地址栏 #vulnerabilities?conversation_id= / ?task_id= / ?id= 同步筛选（通知/对话菜单/任务管理联动）
function syncVulnerabilityFiltersFromLocationHash() {
    const hash = window.location.hash.slice(1);
    const hashParts = hash.split('?');
    if (hashParts[0] !== 'vulnerabilities' || hashParts.length < 2) {
        return;
    }
    const params = new URLSearchParams(hashParts.slice(1).join('?'));
    const vid = (params.get('id') || '').trim();
    const cid = (params.get('conversation_id') || '').trim();
    const tid = (params.get('task_id') || '').trim();
    const sev = (params.get('severity') || '').trim();
    const st = (params.get('status') || '').trim();
    const convTag = (params.get('conversation_tag') || '').trim();
    const taskTag = (params.get('task_tag') || '').trim();
    const pid = (params.get('project_id') || '').trim();
    const q = (params.get('q') || params.get('search') || '').trim();
    if (!vid && !cid && !tid && !sev && !st && !convTag && !taskTag && !q && !pid) {
        return;
    }

    vulnerabilityFilters.q = '';
    vulnerabilityFilters.id = '';
    vulnerabilityFilters.project_id = '';
    vulnerabilityFilters.conversation_id = '';
    vulnerabilityFilters.task_id = '';
    vulnerabilityFilters.conversation_tag = '';
    vulnerabilityFilters.task_tag = '';
    vulnerabilityFilters.severity = '';
    vulnerabilityFilters.status = '';
    const searchEl = document.getElementById('vulnerability-search-filter');
    const exactIdEl = document.getElementById('vulnerability-exact-id-filter');
    const convEl = document.getElementById('vulnerability-conversation-filter');
    const taskEl = document.getElementById('vulnerability-task-filter');
    const convTagEl = document.getElementById('vulnerability-conversation-tag-filter');
    const taskTagEl = document.getElementById('vulnerability-task-tag-filter');
    const projEl = document.getElementById('vulnerability-project-filter');
    const sevEl = document.getElementById('vulnerability-severity-filter');
    const stEl = document.getElementById('vulnerability-status-filter');
    if (searchEl) searchEl.value = '';
    if (exactIdEl) exactIdEl.value = '';
    if (convEl) convEl.value = '';
    if (taskEl) taskEl.value = '';
    if (convTagEl) convTagEl.value = '';
    if (taskTagEl) taskTagEl.value = '';
    if (projEl) projEl.value = '';
    if (sevEl) sevEl.value = '';
    if (stEl) stEl.value = '';

    if (q) {
        vulnerabilityFilters.q = q;
        if (searchEl) searchEl.value = q;
    }
    if (pid) {
        vulnerabilityFilters.project_id = pid;
        if (projEl) projEl.value = pid;
    }
    if (vid) {
        vulnerabilityFilters.id = vid;
        if (exactIdEl) exactIdEl.value = vid;
    }
    if (cid) {
        vulnerabilityFilters.conversation_id = cid;
        if (convEl) convEl.value = cid;
    }
    if (tid) {
        vulnerabilityFilters.task_id = tid;
        if (taskEl) taskEl.value = tid;
    }
    if (convTag) {
        vulnerabilityFilters.conversation_tag = convTag;
        if (convTagEl) convTagEl.value = convTag;
    }
    if (taskTag) {
        vulnerabilityFilters.task_tag = taskTag;
        if (taskTagEl) taskTagEl.value = taskTag;
    }
    if (sev) {
        vulnerabilityFilters.severity = sev;
        if (sevEl) sevEl.value = sev;
    }
    if (st) {
        vulnerabilityFilters.status = st;
        if (stEl) stEl.value = st;
    }
    vulnerabilityPagination.currentPage = 1;
    syncVulnerabilityStatCardActiveState();
    updateVulnerabilityFilterPanelState();
    renderVulnerabilityFilterChips();
    syncAllVulnFilterCustomSelects();
}

// 初始化漏洞管理页面
async function initVulnerabilityPage() {
    // 从localStorage加载每页条数设置
    vulnerabilityPagination.pageSize = getVulnerabilityPageSize();
    initVulnerabilityStatCards();
    initVulnerabilityFilterPanel();
    syncVulnerabilityFiltersFromLocationHash();
    await refreshVulnerabilityProjectFilter();
    updateVulnerabilityFilterPanelState();
    renderVulnerabilityFilterChips();
    loadVulnerabilityFilterOptions();
    loadVulnerabilityStats();
    loadVulnerabilities();
}

function initVulnerabilityStatCards() {
    if (vulnerabilityStatCardsBound) {
        syncVulnerabilityStatCardActiveState();
        return;
    }
    const root = document.getElementById('vulnerability-stat-cards');
    if (!root) return;
    vulnerabilityStatCardsBound = true;
    root.addEventListener('click', onVulnerabilityStatCardClick);
    root.addEventListener('keydown', onVulnerabilityStatCardKeydown);
}

function onVulnerabilityStatCardClick(ev) {
    const totalCard = ev.target.closest('.stat-card.stat-card-total');
    if (totalCard) {
        applyVulnerabilitySeverityFilter('');
        return;
    }
    const card = ev.target.closest('.stat-card.is-clickable[data-severity]');
    if (!card) return;
    const sev = card.getAttribute('data-severity');
    if (!sev) return;
    const sevEl = document.getElementById('vulnerability-severity-filter');
    const current = sevEl ? sevEl.value : vulnerabilityFilters.severity;
    applyVulnerabilitySeverityFilter(current === sev ? '' : sev);
}

function onVulnerabilityStatCardKeydown(ev) {
    if (ev.key !== 'Enter' && ev.key !== ' ') return;
    const card = ev.target.closest('.stat-card.is-clickable');
    if (!card || !card.contains(ev.target)) return;
    ev.preventDefault();
    card.click();
}

function applyVulnerabilitySeverityFilter(severity) {
    const sevEl = document.getElementById('vulnerability-severity-filter');
    if (sevEl) sevEl.value = severity || '';
    applyVulnerabilityFilters();
}

function readVulnerabilityFiltersFromForm() {
    vulnerabilityFilters.q = (document.getElementById('vulnerability-search-filter')?.value || '').trim();
    vulnerabilityFilters.id = (document.getElementById('vulnerability-exact-id-filter')?.value || '').trim();
    vulnerabilityFilters.project_id = (document.getElementById('vulnerability-project-filter')?.value || '').trim();
    vulnerabilityFilters.conversation_id = (document.getElementById('vulnerability-conversation-filter')?.value || '').trim();
    vulnerabilityFilters.task_id = (document.getElementById('vulnerability-task-filter')?.value || '').trim();
    vulnerabilityFilters.conversation_tag = (document.getElementById('vulnerability-conversation-tag-filter')?.value || '').trim();
    vulnerabilityFilters.task_tag = (document.getElementById('vulnerability-task-tag-filter')?.value || '').trim();
    vulnerabilityFilters.severity = document.getElementById('vulnerability-severity-filter')?.value || '';
    vulnerabilityFilters.status = document.getElementById('vulnerability-status-filter')?.value || '';
    return vulnerabilityFilters;
}

function hasVulnerabilityAdvancedFiltersActive() {
    const f = vulnerabilityFilters;
    return Boolean(f.id || f.conversation_id || f.task_id || f.conversation_tag || f.task_tag);
}

function hasAnyVulnerabilityFilterActive() {
    const f = vulnerabilityFilters;
    return Boolean(
        f.q || f.id || f.project_id || f.conversation_id || f.task_id || f.conversation_tag || f.task_tag || f.severity || f.status
    );
}

function applyVulnerabilityFilters() {
    readVulnerabilityFiltersFromForm();
    vulnerabilityPagination.currentPage = 1;
    syncVulnerabilityStatCardActiveState();
    updateVulnerabilityLocationHashFromFilters();
    updateVulnerabilityFilterPanelState();
    renderVulnerabilityFilterChips();
    loadVulnerabilityStats();
    loadVulnerabilities();
}

function updateVulnerabilityLocationHashFromFilters() {
    const hash = window.location.hash.slice(1);
    const hashParts = hash.split('?');
    if (hashParts[0] !== 'vulnerabilities') return;
    const params = new URLSearchParams(hashParts.length >= 2 ? hashParts.slice(1).join('?') : '');
    const f = vulnerabilityFilters;
    const pairs = [
        ['q', f.q],
        ['id', f.id],
        ['project_id', f.project_id],
        ['conversation_id', f.conversation_id],
        ['task_id', f.task_id],
        ['conversation_tag', f.conversation_tag],
        ['task_tag', f.task_tag],
        ['severity', f.severity],
        ['status', f.status]
    ];
    pairs.forEach(function (pair) {
        if (pair[1]) {
            params.set(pair[0], pair[1]);
        } else {
            params.delete(pair[0]);
        }
    });
    const qs = params.toString();
    const newHash = qs ? 'vulnerabilities?' + qs : 'vulnerabilities';
    if (window.location.hash.slice(1) === newHash) return;
    const newFull = '#' + newHash;
    if (typeof history.replaceState === 'function') {
        history.replaceState(null, '', window.location.pathname + window.location.search + newFull);
    } else {
        window.location.hash = newHash;
    }
}

function scheduleVulnerabilityFilterApply(immediate) {
    if (vulnerabilityFilterDebounceTimer) {
        clearTimeout(vulnerabilityFilterDebounceTimer);
        vulnerabilityFilterDebounceTimer = null;
    }
    if (immediate) {
        applyVulnerabilityFilters();
        return;
    }
    vulnerabilityFilterDebounceTimer = setTimeout(function () {
        vulnerabilityFilterDebounceTimer = null;
        applyVulnerabilityFilters();
    }, VULNERABILITY_FILTER_DEBOUNCE_MS);
}

function syncVulnerabilityAdvancedFieldsFromFilters() {
    const map = {
        id: 'vulnerability-exact-id-filter',
        conversation_id: 'vulnerability-conversation-filter',
        task_id: 'vulnerability-task-filter',
        conversation_tag: 'vulnerability-conversation-tag-filter',
        task_tag: 'vulnerability-task-tag-filter'
    };
    Object.keys(map).forEach(function (key) {
        const el = document.getElementById(map[key]);
        if (el) el.value = vulnerabilityFilters[key] || '';
    });
}

function setVulnerabilityMoreFiltersPopoverOpen(open) {
    const btn = document.getElementById('vulnerability-more-filters-btn');
    const popover = document.getElementById('vulnerability-more-filters-popover');
    if (!btn || !popover) return;
    vulnerabilityMoreFiltersPopoverOpen = open;
    btn.setAttribute('aria-expanded', open ? 'true' : 'false');
    btn.classList.toggle('is-active', open);
    popover.hidden = !open;
}

function toggleVulnerabilityMoreFiltersPopover(ev) {
    if (ev) {
        ev.preventDefault();
        ev.stopPropagation();
    }
    const opening = !vulnerabilityMoreFiltersPopoverOpen;
    if (opening) {
        readVulnerabilityFiltersFromForm();
        syncVulnerabilityAdvancedFieldsFromFilters();
    }
    setVulnerabilityMoreFiltersPopoverOpen(opening);
}

function closeVulnerabilityMoreFiltersPopover(revertDraft) {
    if (revertDraft) syncVulnerabilityAdvancedFieldsFromFilters();
    setVulnerabilityMoreFiltersPopoverOpen(false);
}

function clearVulnerabilityAdvancedFilterFields() {
    ['vulnerability-exact-id-filter', 'vulnerability-conversation-filter', 'vulnerability-task-filter',
        'vulnerability-conversation-tag-filter', 'vulnerability-task-tag-filter'].forEach(function (id) {
        const el = document.getElementById(id);
        if (el) el.value = '';
    });
}

function applyVulnerabilityMoreFiltersFromPopover() {
    closeVulnerabilityMoreFiltersPopover(false);
    scheduleVulnerabilityFilterApply(true);
}

function onVulnerabilityFilterDocumentPointerDown(ev) {
    if (!vulnerabilityMoreFiltersPopoverOpen) return;
    const anchor = document.querySelector('.vulnerability-more-filters-anchor');
    if (anchor && anchor.contains(ev.target)) return;
    closeVulnerabilityMoreFiltersPopover(true);
}

function initVulnerabilityFilterPanel() {
    const panel = document.getElementById('vulnerability-filter-panel');
    if (!panel) return;

    if (vulnerabilityFilterPanelBound) {
        updateVulnerabilityFilterPanelState();
        syncAllVulnFilterCustomSelects();
        return;
    }
    vulnerabilityFilterPanelBound = true;

    const stEl = document.getElementById('vulnerability-status-filter');
    if (stEl) stEl.addEventListener('change', function () { scheduleVulnerabilityFilterApply(true); });

    const searchEl = document.getElementById('vulnerability-search-filter');
    if (searchEl) {
        searchEl.addEventListener('input', function () { scheduleVulnerabilityFilterApply(false); });
        searchEl.addEventListener('keydown', function (ev) {
            if (ev.key === 'Enter') {
                ev.preventDefault();
                scheduleVulnerabilityFilterApply(true);
            }
        });
        searchEl.addEventListener('search', function () {
            if (searchEl.value === '') scheduleVulnerabilityFilterApply(true);
        });
    }

    const moreBtn = document.getElementById('vulnerability-more-filters-btn');
    if (moreBtn) moreBtn.addEventListener('click', toggleVulnerabilityMoreFiltersPopover);

    const applyBtn = document.getElementById('vulnerability-more-filters-apply');
    if (applyBtn) applyBtn.addEventListener('click', applyVulnerabilityMoreFiltersFromPopover);

    const resetBtn = document.getElementById('vulnerability-more-filters-reset');
    if (resetBtn) {
        resetBtn.addEventListener('click', function () {
            clearVulnerabilityAdvancedFilterFields();
            applyVulnerabilityMoreFiltersFromPopover();
        });
    }

    const advancedTextIds = [
        'vulnerability-exact-id-filter',
        'vulnerability-conversation-filter',
        'vulnerability-task-filter',
        'vulnerability-conversation-tag-filter',
        'vulnerability-task-tag-filter'
    ];
    advancedTextIds.forEach(function (id) {
        const el = document.getElementById(id);
        if (!el) return;
        el.addEventListener('keydown', function (ev) {
            if (ev.key === 'Enter') {
                ev.preventDefault();
                applyVulnerabilityMoreFiltersFromPopover();
            }
        });
    });

    document.addEventListener('mousedown', onVulnerabilityFilterDocumentPointerDown);
    document.addEventListener('keydown', function (ev) {
        if (ev.key === 'Escape' && vulnerabilityMoreFiltersPopoverOpen) {
            closeVulnerabilityMoreFiltersPopover(true);
        }
    });

    bindVulnerabilityFilterTypeaheads();
    initVulnerabilityFilterSelects();
}

const VULN_FILTER_CUSTOM_SELECT_IDS = ['vulnerability-project-filter', 'vulnerability-status-filter'];
const vulnFilterCustomSelectMap = {};
let vulnFilterCustomSelectDocBound = false;

const VULN_FILTER_SELECT_CARET = '<svg class="vuln-filter-select-caret" width="14" height="14" viewBox="0 0 24 24" fill="none" aria-hidden="true"><path d="M6 9l6 6 6-6" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg>';

function closeAllVulnFilterCustomSelects() {
    Object.keys(vulnFilterCustomSelectMap).forEach(function (id) {
        const reg = vulnFilterCustomSelectMap[id];
        if (!reg || !reg.wrapper) return;
        reg.wrapper.classList.remove('open');
        if (reg.trigger) reg.trigger.setAttribute('aria-expanded', 'false');
    });
}

function syncVulnFilterCustomSelect(selectId) {
    const reg = vulnFilterCustomSelectMap[selectId];
    if (!reg) return;
    const select = reg.select;
    const dropdown = reg.dropdown;
    const trigger = reg.trigger;
    const valueSpan = trigger.querySelector('.vuln-filter-select-value');

    dropdown.innerHTML = '';
    Array.prototype.forEach.call(select.options, function (opt) {
        const item = document.createElement('button');
        item.type = 'button';
        item.className = 'vuln-filter-select-option';
        item.setAttribute('role', 'option');
        item.setAttribute('data-value', opt.value);
        if (opt.value === select.value) {
            item.classList.add('is-selected');
            item.setAttribute('aria-selected', 'true');
        } else {
            item.setAttribute('aria-selected', 'false');
        }
        const check = document.createElement('span');
        check.className = 'vuln-filter-select-check';
        check.setAttribute('aria-hidden', 'true');
        check.textContent = '✓';
        const label = document.createElement('span');
        label.className = 'vuln-filter-select-label';
        label.textContent = opt.textContent;
        item.appendChild(check);
        item.appendChild(label);
        dropdown.appendChild(item);
    });

    const selectedOpt = select.options[select.selectedIndex];
    if (valueSpan) {
        valueSpan.textContent = selectedOpt ? selectedOpt.textContent : '';
    }
    trigger.disabled = !!select.disabled;
    reg.wrapper.classList.toggle('is-disabled', !!select.disabled);
}

function syncAllVulnFilterCustomSelects() {
    VULN_FILTER_CUSTOM_SELECT_IDS.forEach(syncVulnFilterCustomSelect);
}

function enhanceVulnFilterCustomSelect(selectId) {
    const select = document.getElementById(selectId);
    if (!select) return;
    if (select.dataset.vulnCustomSelect === '1') {
        syncVulnFilterCustomSelect(selectId);
        return;
    }
    select.dataset.vulnCustomSelect = '1';
    select.classList.add('vuln-filter-native-select');
    select.tabIndex = -1;
    select.setAttribute('aria-hidden', 'true');

    const wrapper = document.createElement('div');
    wrapper.className = 'vuln-filter-select';

    const trigger = document.createElement('button');
    trigger.type = 'button';
    trigger.className = 'vuln-filter-select-trigger';
    trigger.setAttribute('aria-haspopup', 'listbox');
    trigger.setAttribute('aria-expanded', 'false');
    const valueSpan = document.createElement('span');
    valueSpan.className = 'vuln-filter-select-value';
    trigger.appendChild(valueSpan);
    trigger.insertAdjacentHTML('beforeend', VULN_FILTER_SELECT_CARET);

    const dropdown = document.createElement('div');
    dropdown.className = 'vuln-filter-select-dropdown';
    dropdown.setAttribute('role', 'listbox');

    const parent = select.parentNode;
    parent.insertBefore(wrapper, select);
    wrapper.appendChild(trigger);
    wrapper.appendChild(dropdown);
    wrapper.appendChild(select);

    vulnFilterCustomSelectMap[selectId] = { wrapper: wrapper, trigger: trigger, dropdown: dropdown, select: select };

    trigger.addEventListener('click', function (e) {
        e.stopPropagation();
        if (select.disabled) return;
        if (typeof closeAllVulnerabilityStatusPickers === 'function') {
            closeAllVulnerabilityStatusPickers();
        }
        closeAllVulnDetailProjectSelects();
        const open = wrapper.classList.contains('open');
        closeAllVulnFilterCustomSelects();
        if (!open) {
            wrapper.classList.add('open');
            trigger.setAttribute('aria-expanded', 'true');
        }
    });

    dropdown.addEventListener('click', function (e) {
        const opt = e.target.closest('.vuln-filter-select-option');
        if (!opt) return;
        e.stopPropagation();
        const val = opt.getAttribute('data-value');
        if (val === null) return;
        if (select.value !== val) {
            select.value = val;
            select.dispatchEvent(new Event('change', { bubbles: true }));
        }
        wrapper.classList.remove('open');
        trigger.setAttribute('aria-expanded', 'false');
        syncVulnFilterCustomSelect(selectId);
    });
}

function initVulnerabilityFilterSelects() {
    if (!vulnFilterCustomSelectDocBound) {
        document.addEventListener('click', closeAllVulnFilterCustomSelects);
        document.addEventListener('keydown', function (e) {
            if (e.key === 'Escape') closeAllVulnFilterCustomSelects();
        });
        vulnFilterCustomSelectDocBound = true;
    }
    VULN_FILTER_CUSTOM_SELECT_IDS.forEach(enhanceVulnFilterCustomSelect);
    syncAllVulnFilterCustomSelects();
}

const VULN_DETAIL_SELECT_CARET = '<svg class="vuln-detail-select-caret" width="14" height="14" viewBox="0 0 24 24" fill="none" aria-hidden="true"><path d="M6 9l6 6 6-6" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg>';
const vulnDetailProjectSelectMap = new WeakMap();
let vulnDetailProjectSelectDocBound = false;

function closeAllVulnDetailProjectSelects() {
    document.querySelectorAll('.vuln-detail-select-ui.open').forEach(function (wrapper) {
        wrapper.classList.remove('open');
        const trigger = wrapper.querySelector('.vuln-detail-select-trigger');
        if (trigger) trigger.setAttribute('aria-expanded', 'false');
    });
}

function syncVulnDetailProjectSelect(select) {
    const reg = vulnDetailProjectSelectMap.get(select);
    if (!reg) return;
    const dropdown = reg.dropdown;
    const trigger = reg.trigger;
    const valueSpan = trigger.querySelector('.vuln-detail-select-value');

    dropdown.innerHTML = '';
    Array.prototype.forEach.call(select.options, function (opt) {
        const item = document.createElement('button');
        item.type = 'button';
        item.className = 'vuln-detail-select-option';
        item.setAttribute('role', 'option');
        item.setAttribute('data-value', opt.value);
        if (opt.value === select.value) {
            item.classList.add('is-selected');
            item.setAttribute('aria-selected', 'true');
        } else {
            item.setAttribute('aria-selected', 'false');
        }
        const check = document.createElement('span');
        check.className = 'vuln-detail-select-check';
        check.setAttribute('aria-hidden', 'true');
        check.textContent = '✓';
        const label = document.createElement('span');
        label.className = 'vuln-detail-select-label';
        label.textContent = opt.textContent;
        item.appendChild(check);
        item.appendChild(label);
        dropdown.appendChild(item);
    });

    const selectedOpt = select.options[select.selectedIndex];
    if (valueSpan) {
        valueSpan.textContent = selectedOpt ? selectedOpt.textContent : '';
    }
    trigger.disabled = !!select.disabled;
    reg.wrapper.classList.toggle('is-disabled', !!select.disabled);
}

function enhanceVulnDetailProjectSelect(select) {
    if (!select || !select.classList.contains('vulnerability-project-bind-select')) return;
    if (select.dataset.vulnDetailCustomSelect === '1') {
        syncVulnDetailProjectSelect(select);
        return;
    }
    select.dataset.vulnDetailCustomSelect = '1';
    select.classList.add('vuln-detail-native-select');
    select.tabIndex = -1;
    select.setAttribute('aria-hidden', 'true');

    const wrapper = document.createElement('div');
    wrapper.className = 'vuln-detail-select-ui';

    const trigger = document.createElement('button');
    trigger.type = 'button';
    trigger.className = 'vuln-detail-select-trigger';
    trigger.setAttribute('aria-haspopup', 'listbox');
    trigger.setAttribute('aria-expanded', 'false');
    const label = select.getAttribute('aria-label');
    if (label) trigger.setAttribute('aria-label', label);
    const valueSpan = document.createElement('span');
    valueSpan.className = 'vuln-detail-select-value';
    trigger.appendChild(valueSpan);
    trigger.insertAdjacentHTML('beforeend', VULN_DETAIL_SELECT_CARET);

    const dropdown = document.createElement('div');
    dropdown.className = 'vuln-detail-select-dropdown';
    dropdown.setAttribute('role', 'listbox');

    const parent = select.parentNode;
    parent.insertBefore(wrapper, select);
    wrapper.appendChild(trigger);
    wrapper.appendChild(dropdown);
    wrapper.appendChild(select);

    vulnDetailProjectSelectMap.set(select, { wrapper: wrapper, trigger: trigger, dropdown: dropdown, select: select });

    trigger.addEventListener('click', function (e) {
        e.stopPropagation();
        if (select.disabled) return;
        if (typeof closeAllVulnerabilityStatusPickers === 'function') {
            closeAllVulnerabilityStatusPickers();
        }
        closeAllVulnFilterCustomSelects();
        const open = wrapper.classList.contains('open');
        closeAllVulnDetailProjectSelects();
        if (!open) {
            wrapper.classList.add('open');
            trigger.setAttribute('aria-expanded', 'true');
        }
    });

    dropdown.addEventListener('click', function (e) {
        const opt = e.target.closest('.vuln-detail-select-option');
        if (!opt) return;
        e.stopPropagation();
        const val = opt.getAttribute('data-value');
        if (val === null) return;
        if (select.value !== val) {
            select.value = val;
            select.dispatchEvent(new Event('change', { bubbles: true }));
        }
        wrapper.classList.remove('open');
        trigger.setAttribute('aria-expanded', 'false');
        syncVulnDetailProjectSelect(select);
    });

    syncVulnDetailProjectSelect(select);
}

function initVulnerabilityProjectBindSelects(root) {
    if (!vulnDetailProjectSelectDocBound) {
        document.addEventListener('click', closeAllVulnDetailProjectSelects);
        document.addEventListener('keydown', function (e) {
            if (e.key === 'Escape') closeAllVulnDetailProjectSelects();
        });
        vulnDetailProjectSelectDocBound = true;
    }
    const scope = root || document.getElementById('vulnerabilities-list');
    if (!scope) return;
    scope.querySelectorAll('select.vulnerability-project-bind-select').forEach(enhanceVulnDetailProjectSelect);
}

function countVulnerabilityAdvancedFiltersActive() {
    const f = vulnerabilityFilters;
    let n = 0;
    if (f.id) n++;
    if (f.conversation_id) n++;
    if (f.task_id) n++;
    if (f.conversation_tag) n++;
    if (f.task_tag) n++;
    return n;
}

function updateVulnerabilityAdvancedBadge() {
    const badge = document.getElementById('vulnerability-advanced-badge');
    if (!badge) return;
    readVulnerabilityFiltersFromForm();
    const n = countVulnerabilityAdvancedFiltersActive();
    if (n > 0) {
        badge.hidden = false;
        badge.textContent = '(' + n + ')';
        badge.setAttribute('aria-label', String(n));
    } else {
        badge.hidden = true;
        badge.textContent = '';
        badge.removeAttribute('aria-label');
    }
}

function updateVulnerabilityFilterPanelState() {
    const panel = document.getElementById('vulnerability-filter-panel');
    if (!panel) return;
    readVulnerabilityFiltersFromForm();
    panel.classList.toggle('is-filtered', hasAnyVulnerabilityFilterActive());
    updateVulnerabilityAdvancedBadge();
    const clearBtn = document.getElementById('vulnerability-filter-clear-btn');
    if (clearBtn) clearBtn.hidden = !hasAnyVulnerabilityFilterActive();
}

function formatVulnerabilityFilterChipValue(key, value) {
    if (key === 'severity') return vulnSeverityLabel(value);
    if (key === 'status') return vulnStatusLabel(value);
    if (key === 'project_id') {
        const name = typeof getProjectName === 'function' ? getProjectName(value) : '';
        return name && name !== value ? name : value;
    }
    return value;
}

function renderVulnerabilityFilterChips() {
    const wrap = document.getElementById('vulnerability-filter-chips');
    const list = document.getElementById('vulnerability-filter-chips-list');
    if (!wrap || !list) return;

    readVulnerabilityFiltersFromForm();
    const chips = [];
    VULN_FILTER_CHIP_FIELDS.forEach(function (field) {
        const val = vulnerabilityFilters[field.key];
        if (!val) return;
        const label = field.labelKey ? vulnT(field.labelKey) : (field.key === 'project_id' ? '项目' : '');
        const displayVal = formatVulnerabilityFilterChipValue(field.key, val);
        const text = label ? label + ': ' + displayVal : displayVal;
        chips.push({ key: field.key, text: text });
    });

    if (!chips.length) {
        wrap.hidden = true;
        list.innerHTML = '';
        return;
    }

    wrap.hidden = false;
    const removeLabel = vulnT('vulnerabilityPage.chipRemove');
    list.innerHTML = chips.map(function (chip) {
        return (
            '<button type="button" class="vulnerability-filter-chip" role="listitem" data-filter-key="' +
            escapeHtml(chip.key) + '" title="' + escapeHtml(removeLabel) + '">' +
            '<span>' + escapeHtml(chip.text) + '</span>' +
            '<span class="vulnerability-filter-chip-remove" aria-hidden="true">×</span>' +
            '</button>'
        );
    }).join('');

    list.querySelectorAll('.vulnerability-filter-chip').forEach(function (btn) {
        btn.addEventListener('click', function () {
            const key = btn.getAttribute('data-filter-key');
            if (key) removeVulnerabilityFilterByKey(key);
        });
    });
}

function removeVulnerabilityFilterByKey(key) {
    const map = {
        q: 'vulnerability-search-filter',
        id: 'vulnerability-exact-id-filter',
        conversation_id: 'vulnerability-conversation-filter',
        task_id: 'vulnerability-task-filter',
        conversation_tag: 'vulnerability-conversation-tag-filter',
        task_tag: 'vulnerability-task-tag-filter',
        project_id: 'vulnerability-project-filter',
        severity: 'vulnerability-severity-filter',
        status: 'vulnerability-status-filter'
    };
    const elId = map[key];
    if (elId) {
        const el = document.getElementById(elId);
        if (el) el.value = '';
    }
    if (Object.prototype.hasOwnProperty.call(vulnerabilityFilters, key)) {
        vulnerabilityFilters[key] = '';
    }
    if (key === 'project_id' || key === 'status') {
        syncAllVulnFilterCustomSelects();
    }
    applyVulnerabilityFilters();
}

async function loadVulnerabilityFilterOptions() {
    if (typeof apiFetch === 'undefined') return;
    try {
        const response = await apiFetch('/api/vulnerabilities/filter-options');
        if (!response.ok) return;
        vulnerabilityFilterOptionsCache = await response.json();
        populateVulnerabilityDatalist(
            'vulnerability-conversation-tag-suggestions',
            vulnerabilityFilterOptionsCache.conversation_tags,
            { max: 20 }
        );
        populateVulnerabilityDatalist(
            'vulnerability-task-tag-suggestions',
            vulnerabilityFilterOptionsCache.task_tags,
            { max: 20 }
        );
        clearVulnerabilityDatalist('vulnerability-conversation-suggestions');
        clearVulnerabilityDatalist('vulnerability-task-suggestions');
    } catch (e) {
        console.warn('加载漏洞筛选建议失败', e);
    }
}

function clearVulnerabilityDatalist(listId) {
    const list = document.getElementById(listId);
    if (list) list.innerHTML = '';
}

function populateVulnerabilityDatalist(listId, values, opts) {
    const list = document.getElementById(listId);
    if (!list || !Array.isArray(values)) return;
    const max = (opts && opts.max) || VULNERABILITY_DATALIST_MAX;
    const seen = new Set();
    const unique = [];
    values.forEach(function (v) {
        const s = String(v || '').trim();
        if (!s || seen.has(s)) return;
        seen.add(s);
        unique.push(s);
        if (unique.length >= max) return;
    });
    list.innerHTML = unique.slice(0, max).map(function (v) {
        return '<option value="' + escapeHtml(v) + '"></option>';
    }).join('');
}

function filterVulnerabilitySuggestionPool(pool, query) {
    if (!Array.isArray(pool) || !query) return [];
    const q = query.toLowerCase();
    const out = [];
    for (let i = 0; i < pool.length && out.length < VULNERABILITY_DATALIST_MAX; i++) {
        const s = String(pool[i] || '').trim();
        if (s && s.toLowerCase().indexOf(q) !== -1) out.push(s);
    }
    return out;
}

function updateVulnerabilityTypeaheadDatalist(inputId, listId, poolKey) {
    const el = document.getElementById(inputId);
    if (!el || !vulnerabilityFilterOptionsCache) return;
    const q = el.value.trim();
    if (q.length < VULNERABILITY_DATALIST_MIN_QUERY) {
        clearVulnerabilityDatalist(listId);
        return;
    }
    let pool = vulnerabilityFilterOptionsCache[poolKey] || [];
    if (poolKey === 'task_ids') {
        pool = (vulnerabilityFilterOptionsCache.task_ids || []).concat(vulnerabilityFilterOptionsCache.queue_ids || []);
    }
    populateVulnerabilityDatalist(listId, filterVulnerabilitySuggestionPool(pool, q));
}

function bindVulnerabilityFilterTypeaheads() {
    const pairs = [
        { inputId: 'vulnerability-conversation-filter', listId: 'vulnerability-conversation-suggestions', poolKey: 'conversation_ids' },
        { inputId: 'vulnerability-task-filter', listId: 'vulnerability-task-suggestions', poolKey: 'task_ids' }
    ];
    pairs.forEach(function (pair) {
        const el = document.getElementById(pair.inputId);
        if (!el) return;
        el.addEventListener('input', function () {
            updateVulnerabilityTypeaheadDatalist(pair.inputId, pair.listId, pair.poolKey);
        });
        el.addEventListener('blur', function () {
            setTimeout(function () { clearVulnerabilityDatalist(pair.listId); }, 150);
        });
    });

    ['vulnerability-conversation-tag-filter', 'vulnerability-task-tag-filter'].forEach(function (inputId) {
        const el = document.getElementById(inputId);
        if (!el) return;
        el.addEventListener('focus', function () {
            if (!vulnerabilityFilterOptionsCache) return;
            const listId = inputId === 'vulnerability-conversation-tag-filter'
                ? 'vulnerability-conversation-tag-suggestions'
                : 'vulnerability-task-tag-suggestions';
            const key = inputId === 'vulnerability-conversation-tag-filter' ? 'conversation_tags' : 'task_tags';
            const q = el.value.trim();
            if (q.length >= VULNERABILITY_DATALIST_MIN_QUERY) {
                populateVulnerabilityDatalist(listId, filterVulnerabilitySuggestionPool(vulnerabilityFilterOptionsCache[key], q), { max: 20 });
            }
        });
    });
}

function syncVulnerabilityStatCardActiveState() {
    const sevEl = document.getElementById('vulnerability-severity-filter');
    const sev = (sevEl && sevEl.value) || vulnerabilityFilters.severity || '';
    const root = document.getElementById('vulnerability-stat-cards');
    if (!root) return;
    root.querySelectorAll('.stat-card.is-clickable').forEach(function (card) {
        if (card.classList.contains('stat-card-total')) {
            card.classList.toggle('is-active', !sev);
            card.setAttribute('aria-pressed', sev ? 'false' : 'true');
        } else {
            const cardSev = card.getAttribute('data-severity');
            const active = Boolean(sev && cardSev === sev);
            card.classList.toggle('is-active', active);
            card.setAttribute('aria-pressed', active ? 'true' : 'false');
        }
    });
}

function updateVulnerabilityStatStackedBar(bySeverity, total) {
    const bar = document.getElementById('stat-stacked-bar');
    if (!bar) return;
    const segs = bar.querySelectorAll('.stat-stacked-seg');
    if (!total) {
        bar.classList.add('is-empty');
        segs.forEach(function (seg) {
            seg.style.flex = '0 0 0';
            seg.style.display = 'none';
        });
        return;
    }
    bar.classList.remove('is-empty');
    segs.forEach(function (seg) {
        const sev = seg.getAttribute('data-sev');
        const count = bySeverity[sev] || 0;
        if (count <= 0) {
            seg.style.display = 'none';
            seg.style.flex = '0 0 0';
            return;
        }
        seg.style.display = '';
        const pct = Math.max((count / total) * 100, 0);
        seg.style.flex = '1 1 ' + pct + '%';
    });
}

// 加载漏洞统计
async function loadVulnerabilityStats() {
    try {
        // 检查apiFetch是否可用
        if (typeof apiFetch === 'undefined') {
            console.error('apiFetch未定义，请确保auth.js已加载');
            throw new Error('apiFetch未定义');
        }

        const params = buildVulnerabilityDashboardStatsParams();

        const response = await apiFetch(`/api/vulnerabilities/stats?${params.toString()}`);
        if (!response.ok) {
            const errorText = await response.text();
            console.error('获取统计失败:', response.status, errorText);
            throw new Error(`获取统计失败: ${response.status}`);
        }

        const stats = await response.json();
        updateVulnerabilityStats(stats);
    } catch (error) {
        console.error('加载漏洞统计失败:', error);
        // 统计失败不影响列表显示，只重置统计为0
        updateVulnerabilityStats(null);
    }
}

// 更新漏洞统计显示
function updateVulnerabilityStats(stats) {
    // 处理空值情况
    if (!stats) {
        stats = {
            total: 0,
            by_severity: {},
            by_status: {}
        };
    }

    const total = stats.total || 0;
    const bySeverity = stats.by_severity || {};

    const totalEl = document.getElementById('stat-total');
    if (totalEl) {
        totalEl.textContent = String(total);
        totalEl.classList.toggle('is-zero', total === 0);
    }

    VULN_STAT_SEVERITIES.forEach(function (sev) {
        const count = bySeverity[sev] || 0;
        const valEl = document.getElementById('stat-' + sev);
        const pctEl = document.getElementById('stat-' + sev + '-pct');
        if (valEl) {
            valEl.textContent = String(count);
            valEl.classList.toggle('is-zero', count === 0);
        }
        if (pctEl) {
            const pct = total > 0 ? Math.round((count / total) * 100) : 0;
            pctEl.textContent = pct + '%';
            pctEl.setAttribute('aria-hidden', total === 0 ? 'true' : 'false');
        }
    });

    updateVulnerabilityStatStackedBar(bySeverity, total);
    syncVulnerabilityStatCardActiveState();
}

// 加载漏洞列表
async function loadVulnerabilities(page = null, options = {}) {
    const opts = options && typeof options === 'object' ? options : {};
    const preserveScroll = !!opts.preserveScroll;
    const silent = !!opts.silent;
    let expandedIds = opts.expandedIds;

    const scrollEl = preserveScroll ? getVulnerabilityScrollContainer() : null;
    const scrollTop = scrollEl ? scrollEl.scrollTop : 0;
    if (expandedIds === undefined && preserveScroll) {
        expandedIds = getExpandedVulnerabilityIds();
    }

    const listContainer = document.getElementById('vulnerabilities-list');
    if (!silent) {
        listContainer.innerHTML = `<div class="loading-spinner">${escapeHtml(vulnT('vulnerabilityPage.loading'))}</div>`;
    }

    try {
        // 检查apiFetch是否可用
        if (typeof apiFetch === 'undefined') {
            console.error('apiFetch未定义，请确保auth.js已加载');
            throw new Error('apiFetch未定义');
        }

        // 如果指定了页码，使用页码；否则使用当前页码
        if (page !== null) {
            vulnerabilityPagination.currentPage = page;
        }

        const params = buildVulnerabilityFilterParams();
        params.append('page', vulnerabilityPagination.currentPage.toString());
        params.append('limit', vulnerabilityPagination.pageSize.toString());

        const response = await apiFetch(`/api/vulnerabilities?${params.toString()}`);
        if (!response.ok) {
            const errorText = await response.text();
            console.error('获取漏洞列表失败:', response.status, errorText);
            throw new Error(`获取漏洞列表失败: ${response.status}`);
        }

        const data = await response.json();
        
        // 判断响应格式：新格式（有total字段）还是旧格式（直接是数组）
        let vulnerabilities;
        if (Array.isArray(data)) {
            // 旧格式：直接是数组
            vulnerabilities = data;
            // 使用数组长度作为总数（可能不准确，但至少能显示分页控件）
            vulnerabilityPagination.total = data.length;
            vulnerabilityPagination.totalPages = Math.max(1, Math.ceil(data.length / vulnerabilityPagination.pageSize));
            console.warn('后端返回的是旧格式（数组），建议更新后端API以支持分页');
        } else if ('vulnerabilities' in data) {
            // 新格式：包含分页信息的对象（vulnerabilities可能为null或数组）
            vulnerabilities = Array.isArray(data.vulnerabilities) ? data.vulnerabilities : [];
            vulnerabilityPagination.total = data.total || 0;
            vulnerabilityPagination.currentPage = data.page || vulnerabilityPagination.currentPage;
            vulnerabilityPagination.pageSize = data.page_size || vulnerabilityPagination.pageSize;
            vulnerabilityPagination.totalPages = data.total_pages || 1;
        } else {
            // 未知格式，尝试作为数组处理
            vulnerabilities = [];
            console.error('未知的响应格式:', data);
        }

        renderVulnerabilities(vulnerabilities, { expandedIds: expandedIds || [] });
        renderVulnerabilityPagination();

        if (preserveScroll && scrollEl) {
            requestAnimationFrame(function () {
                scrollEl.scrollTop = scrollTop;
            });
        }
    } catch (error) {
        console.error('加载漏洞列表失败:', error);
        listContainer.innerHTML = `<div class="error-message">${escapeHtml(vulnT('vulnerabilityPage.loadListFailed'))}: ${escapeHtml(error.message)}</div>`;
    }
}

// 渲染漏洞列表
function renderVulnerabilities(vulnerabilities, renderOptions) {
    const opts = renderOptions && typeof renderOptions === 'object' ? renderOptions : {};
    const listContainer = document.getElementById('vulnerabilities-list');
    
    // 处理空值情况（使用 data-i18n 以便语言切换时自动更新）
    if (!vulnerabilities || !Array.isArray(vulnerabilities)) {
        listContainer.innerHTML = '<div class="empty-state" data-i18n="vulnerabilityPage.noRecords">暂无漏洞记录</div>';
        if (typeof window.applyTranslations === 'function') {
            window.applyTranslations(listContainer);
        }
        return;
    }
    
    if (vulnerabilities.length === 0) {
        listContainer.innerHTML = '<div class="empty-state" data-i18n="vulnerabilityPage.noRecords">暂无漏洞记录</div>';
        if (typeof window.applyTranslations === 'function') {
            window.applyTranslations(listContainer);
        }
        return;
    }

    const html = vulnerabilities.map(vuln => {
        const severityClass = `severity-${vuln.severity}`;
        const severityText = vulnSeverityLabel(vuln.severity);
        const createdDate = new Date(vuln.created_at).toLocaleString(vulnDateLocale());
        const projectLabel = vuln.project_id
            ? escapeHtml(typeof getProjectName === 'function' ? getProjectName(vuln.project_id) : vuln.project_id)
            : escapeHtml(vulnT('vulnerabilityPage.projectUnbound'));
        const projectBadge = vuln.project_id
            ? `<span class="vulnerability-project-badge" title="${escapeAttr(vuln.project_id)}">${escapeHtml(vulnT('vulnerabilityPage.detailProject'))}: ${projectLabel}</span>`
            : `<span class="vulnerability-project-badge vulnerability-project-badge--unbound">${escapeHtml(vulnT('vulnerabilityPage.projectUnbound'))}</span>`;
        const dlTitle = escapeHtml(vulnT('vulnerabilityPage.downloadMarkdownTitle'));
        const editTitle = escapeHtml(vulnT('common.edit'));
        const deleteTitle = escapeHtml(vulnT('common.delete'));
        const vulnIdJs = escapeJsStringAttr(vuln.id);

        return `
            <div class="vulnerability-card ${severityClass}" id="vulnerability-card-${escapeAttr(vuln.id)}" data-vuln-id="${escapeAttr(vuln.id)}">
                <div class="vulnerability-header" onclick="toggleVulnerabilityDetails(${vulnIdJs})" style="cursor: pointer;">
                    <div class="vulnerability-title-section">
                        <div style="display: flex; align-items: center; gap: 8px;">
                            <svg class="vulnerability-expand-icon" id="expand-icon-${escapeAttr(vuln.id)}" width="16" height="16" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" style="transition: transform 0.2s ease; flex-shrink: 0;">
                                <path d="M9 18l6-6-6-6" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
                            </svg>
                            <h3 class="vulnerability-title">${escapeHtml(vuln.title)}</h3>
                        </div>
                        <div class="vulnerability-meta">
                            <span class="severity-badge ${severityClass}">${severityText}</span>
                            ${buildVulnerabilityStatusPicker(vuln)}
                            ${projectBadge}
                            <span class="vulnerability-date">${createdDate}</span>
                        </div>
                    </div>
                    <div class="vulnerability-actions" onclick="event.stopPropagation();">
                        <button class="btn-ghost" onclick="downloadVulnerabilityAsMarkdown(${vulnIdJs}, event)" title="${dlTitle}">
                            <svg width="16" height="16" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
                                <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
                                <polyline points="7 10 12 15 17 10" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
                                <line x1="12" y1="15" x2="12" y2="3" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
                            </svg>
                        </button>
                        <button class="btn-ghost" onclick="editVulnerability(${vulnIdJs})" title="${editTitle}">
                            <svg width="16" height="16" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
                                <path d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
                                <path d="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
                            </svg>
                        </button>
                        <button class="btn-ghost" data-require-permission="vulnerability:delete" onclick="deleteVulnerability(${vulnIdJs})" title="${deleteTitle}">
                            <svg width="16" height="16" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
                                <path d="M3 6h18M8 6V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2m3 0v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6h14z" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
                            </svg>
                        </button>
                    </div>
                </div>
                <div class="vulnerability-content" id="content-${vuln.id}" style="display: none;">
                    ${vuln.description ? `<div class="vulnerability-description">${escapeHtml(vuln.description)}</div>` : ''}
                    <div class="vulnerability-details">
                        ${vulnDetailField(vulnT('vulnerabilityPage.detailVulnId'), vuln.id, true)}
                        ${vulnDetailProjectField(vuln)}
                        ${vuln.type ? vulnDetailField(vulnT('vulnerabilityPage.detailType'), vuln.type, false) : ''}
                        ${vuln.target ? vulnDetailField(vulnT('vulnerabilityPage.detailTarget'), vuln.target, false) : ''}
                        ${vulnDetailConversationField(vuln.conversation_id)}
                        ${vuln.task_id ? vulnDetailField(vulnT('vulnerabilityPage.detailTaskId'), vuln.task_id, true) : ''}
                        ${vuln.task_queue_id ? vulnDetailField(vulnT('vulnerabilityPage.detailTaskQueueId'), vuln.task_queue_id, true) : ''}
                        ${vuln.conversation_tag ? vulnDetailField(vulnT('vulnerabilityPage.detailConversationTag'), vuln.conversation_tag, false) : ''}
                        ${vuln.task_tag ? vulnDetailField(vulnT('vulnerabilityPage.detailTaskTag'), vuln.task_tag, false) : ''}
                    </div>
                    <div class="vulnerability-repro">
                        ${vulnNarrativeSection(vulnT('vulnerabilityPage.detailPreconditions'), vuln.preconditions)}
                        ${vulnNarrativeSection(vulnT('vulnerabilityPage.detailReproductionSteps'), vuln.reproduction_steps)}
                        ${vulnNarrativeSection(vulnT('vulnerabilityPage.detailEvidence'), vuln.evidence, { code: true })}
                        ${vulnNarrativeSection(vulnT('vulnerabilityPage.detailImpact'), vuln.impact)}
                        ${vulnNarrativeSection(vulnT('vulnerabilityPage.detailRecommendation'), vuln.recommendation)}
                        ${vulnNarrativeSection(vulnT('vulnerabilityPage.detailRetestNotes'), vuln.retest_notes)}
                    </div>
                    <div class="vulnerability-related-facts" id="vuln-related-facts-${escapeAttr(vuln.id)}" data-project-id="${escapeAttr(vuln.project_id || '')}" data-vuln-id="${escapeAttr(vuln.id)}" hidden></div>
                </div>
            </div>
        `;
    }).join('');

    listContainer.innerHTML = html;
    initVulnerabilityStatusPickers(listContainer);
    initVulnerabilityProjectBindSelects(listContainer);
    if (typeof window.applyTranslations === 'function') {
        window.applyTranslations(listContainer);
    }
    if (typeof rbacAfterDynamicRender === 'function') rbacAfterDynamicRender(listContainer);

    restoreExpandedVulnerabilityDetails(opts.expandedIds);

    // 如果通过漏洞ID筛选且只返回一条记录，自动展开详情（提升“点击查看”的用户体验）
    if (vulnerabilities.length === 1 && vulnerabilityFilters.id && vulnerabilityFilters.id === vulnerabilities[0].id) {
        setTimeout(() => {
            toggleVulnerabilityDetails(vulnerabilities[0].id);
        }, 300);
    }
}

// 渲染分页控件
function renderVulnerabilityPagination() {
    const paginationContainer = document.getElementById('vulnerability-pagination');
    if (!paginationContainer) {
        return;
    }

    const { currentPage, totalPages, total, pageSize } = vulnerabilityPagination;

    // 计算显示范围
    const start = total === 0 ? 0 : (currentPage - 1) * pageSize + 1;
    const end = total === 0 ? 0 : Math.min(currentPage * pageSize, total);
    
    let paginationHTML = '<div class="pagination">';
    
    // 左侧：显示范围信息和每页数量选择器（参考Skills样式）
    paginationHTML += `
        <div class="pagination-info">
            <span>${escapeHtml(vulnT('skillsPage.paginationShow', { start, end, total }))}</span>
            <label class="pagination-page-size">
                ${escapeHtml(vulnT('skillsPage.perPageLabel'))}
                <select id="vulnerability-page-size-pagination" onchange="changeVulnerabilityPageSize()">
                    <option value="10" ${pageSize === 10 ? 'selected' : ''}>10</option>
                    <option value="20" ${pageSize === 20 ? 'selected' : ''}>20</option>
                    <option value="50" ${pageSize === 50 ? 'selected' : ''}>50</option>
                    <option value="100" ${pageSize === 100 ? 'selected' : ''}>100</option>
                </select>
            </label>
        </div>
    `;
    
    // 右侧：分页按钮（参考Skills样式：首页、上一页、第X/Y页、下一页、末页）
    paginationHTML += `
        <div class="pagination-controls">
            <button class="btn-secondary" onclick="loadVulnerabilities(1)" ${currentPage === 1 || total === 0 ? 'disabled' : ''}>${escapeHtml(vulnT('skillsPage.firstPage'))}</button>
            <button class="btn-secondary" onclick="loadVulnerabilities(${currentPage - 1})" ${currentPage === 1 || total === 0 ? 'disabled' : ''}>${escapeHtml(vulnT('skillsPage.prevPage'))}</button>
            <span class="pagination-page">${escapeHtml(vulnT('skillsPage.pageOf', { current: currentPage, total: totalPages || 1 }))}</span>
            <button class="btn-secondary" onclick="loadVulnerabilities(${currentPage + 1})" ${currentPage >= totalPages || total === 0 ? 'disabled' : ''}>${escapeHtml(vulnT('skillsPage.nextPage'))}</button>
            <button class="btn-secondary" onclick="loadVulnerabilities(${totalPages || 1})" ${currentPage >= totalPages || total === 0 ? 'disabled' : ''}>${escapeHtml(vulnT('skillsPage.lastPage'))}</button>
        </div>
    `;
    
    paginationHTML += '</div>';
    
    paginationContainer.innerHTML = paginationHTML;
    if (typeof window.applyTranslations === 'function') {
        window.applyTranslations(paginationContainer);
    }
}

// 改变每页显示数量
async function changeVulnerabilityPageSize() {
    const pageSizeSelect = document.getElementById('vulnerability-page-size-pagination');
    if (!pageSizeSelect) return;
    
    const newPageSize = parseInt(pageSizeSelect.value, 10);
    if (isNaN(newPageSize) || newPageSize < 1) {
        return;
    }
    
    // 保存到localStorage
    localStorage.setItem('vulnerabilityPageSize', newPageSize.toString());
    
    // 更新分页配置
    vulnerabilityPagination.pageSize = newPageSize;
    
    // 重新计算当前页（保持显示的数据范围尽可能接近）
    const currentStartItem = (vulnerabilityPagination.currentPage - 1) * vulnerabilityPagination.pageSize + 1;
    const newPage = Math.max(1, Math.floor((currentStartItem - 1) / newPageSize) + 1);
    vulnerabilityPagination.currentPage = newPage;
    
    // 重新加载数据
    await loadVulnerabilities();
}

function buildVulnerabilityProjectOptionsHtml(selectedId) {
    const sel = (selectedId || '').trim();
    let html = `<option value="">${escapeHtml(vulnT('vulnerabilityModal.projectNone'))}</option>`;
    const entries = typeof projectNameById !== 'undefined' ? Object.entries(projectNameById) : [];
    entries.sort((a, b) => (a[1] || '').localeCompare(b[1] || '', undefined, { sensitivity: 'base' }));
    entries.forEach(([id, name]) => {
        if (!id) return;
        const selected = id === sel ? ' selected' : '';
        html += `<option value="${escapeAttr(id)}"${selected}>${escapeHtml(name || id)}</option>`;
    });
    if (sel && !entries.some(([id]) => id === sel)) {
        html += `<option value="${escapeAttr(sel)}" selected>${escapeHtml(sel)}</option>`;
    }
    return html;
}

async function populateVulnerabilityModalProjectSelect(selectedId) {
    const sel = document.getElementById('vulnerability-project-id');
    if (!sel) return;
    try {
        let list = [];
        if (typeof fetchAllProjects === 'function') {
            list = await fetchAllProjects();
        } else {
            const res = await apiFetch('/api/projects?limit=500');
            if (res.ok) {
                const data = await res.json();
                list = typeof parseProjectsListResponse === 'function'
                    ? parseProjectsListResponse(data).items
                    : (Array.isArray(data) ? data : (data.projects || []));
            }
        }
        if (list.length) {
            if (typeof rebuildProjectNameMap === 'function') {
                rebuildProjectNameMap(list);
            } else if (typeof projectNameById !== 'undefined') {
                list.forEach((p) => { if (p.id) projectNameById[p.id] = p.name || p.id; });
            }
        }
    } catch (e) {
        console.warn('加载项目列表失败', e);
    }
    sel.innerHTML = buildVulnerabilityProjectOptionsHtml(selectedId || '');
    sel.value = selectedId || '';
}

// 显示添加漏洞模态框
async function showAddVulnerabilityModal() {
    if (typeof requirePermission === 'function' && !requirePermission('vulnerability:write')) return;
    currentVulnerabilityId = null;
    document.getElementById('vulnerability-modal-title').textContent = vulnT('vulnerability.addVuln');
    
    const defaultProject = vulnerabilityFilters.project_id || '';
    await populateVulnerabilityModalProjectSelect(defaultProject);

    // 清空表单
    document.getElementById('vulnerability-conversation-id').value = '';
    document.getElementById('vulnerability-conversation-tag').value = '';
    document.getElementById('vulnerability-task-tag').value = '';
    document.getElementById('vulnerability-title').value = '';
    document.getElementById('vulnerability-description').value = '';
    document.getElementById('vulnerability-severity').value = '';
    document.getElementById('vulnerability-status').value = 'open';
    document.getElementById('vulnerability-type').value = '';
    document.getElementById('vulnerability-target').value = '';
    document.getElementById('vulnerability-preconditions').value = '';
    document.getElementById('vulnerability-reproduction-steps').value = '';
    document.getElementById('vulnerability-evidence').value = '';
    document.getElementById('vulnerability-impact').value = '';
    document.getElementById('vulnerability-recommendation').value = '';
    document.getElementById('vulnerability-retest-notes').value = '';

    openAppModal('vulnerability-modal');
}

// 编辑漏洞
async function editVulnerability(id) {
    try {
        currentVulnerabilityId = id;
        document.getElementById('vulnerability-modal-title').textContent = vulnT('vulnerability.editVuln');
        openAppModal('vulnerability-modal', { focus: false });
        const response = await apiFetch(`/api/vulnerabilities/${id}`);
        if (!response.ok) throw new Error(vulnT('vulnerabilityPage.fetchFailed'));
        const vuln = await response.json();
        deferModalContent(async () => {
            document.getElementById('vulnerability-conversation-id').value = vuln.conversation_id || '';
            document.getElementById('vulnerability-conversation-tag').value = vuln.conversation_tag || '';
            document.getElementById('vulnerability-task-tag').value = vuln.task_tag || '';
            document.getElementById('vulnerability-title').value = vuln.title || '';
            document.getElementById('vulnerability-description').value = vuln.description || '';
            document.getElementById('vulnerability-severity').value = vuln.severity || '';
            document.getElementById('vulnerability-status').value = vuln.status || 'open';
            document.getElementById('vulnerability-type').value = vuln.type || '';
            document.getElementById('vulnerability-target').value = vuln.target || '';
            document.getElementById('vulnerability-preconditions').value = vuln.preconditions || '';
            document.getElementById('vulnerability-reproduction-steps').value = vuln.reproduction_steps || '';
            document.getElementById('vulnerability-evidence').value = vuln.evidence || '';
            document.getElementById('vulnerability-impact').value = vuln.impact || '';
            document.getElementById('vulnerability-recommendation').value = vuln.recommendation || '';
            document.getElementById('vulnerability-retest-notes').value = vuln.retest_notes || '';
            await populateVulnerabilityModalProjectSelect(vuln.project_id || '');
            document.getElementById('vulnerability-title')?.focus();
        });
    } catch (error) {
        closeVulnerabilityModal();
        console.error('加载漏洞失败:', error);
        alert(vulnT('vulnerability.loadFailed') + ': ' + error.message);
    }
}

// 保存漏洞
async function saveVulnerability() {
    if (typeof requirePermission === 'function' && !requirePermission('vulnerability:write')) return;
    const conversationId = document.getElementById('vulnerability-conversation-id').value.trim();
    const title = document.getElementById('vulnerability-title').value.trim();
    const description = document.getElementById('vulnerability-description').value.trim();
    const severity = document.getElementById('vulnerability-severity').value;
    const type = document.getElementById('vulnerability-type').value.trim();
    const target = document.getElementById('vulnerability-target').value.trim();
    const reproductionSteps = document.getElementById('vulnerability-reproduction-steps').value.trim();
    const evidence = document.getElementById('vulnerability-evidence').value.trim();
    const impact = document.getElementById('vulnerability-impact').value.trim();
    const recommendation = document.getElementById('vulnerability-recommendation').value.trim();

    if (!conversationId || !title || !description || !severity || !type || !target || !reproductionSteps || !evidence || !impact || !recommendation) {
        alert(vulnT('vulnerabilityPage.saveRequiredFields'));
        return;
    }

    const projectId = (document.getElementById('vulnerability-project-id')?.value || '').trim();

    const data = {
        conversation_id: conversationId,
        project_id: projectId,
        conversation_tag: document.getElementById('vulnerability-conversation-tag').value.trim(),
        task_tag: document.getElementById('vulnerability-task-tag').value.trim(),
        title: title,
        description: description,
        severity: severity,
        status: document.getElementById('vulnerability-status').value,
        type: type,
        target: target,
        preconditions: document.getElementById('vulnerability-preconditions').value.trim(),
        reproduction_steps: reproductionSteps,
        evidence: evidence,
        impact: impact,
        recommendation: recommendation,
        retest_notes: document.getElementById('vulnerability-retest-notes').value.trim()
    };

    try {
        const url = currentVulnerabilityId 
            ? `/api/vulnerabilities/${currentVulnerabilityId}`
            : '/api/vulnerabilities';
        const method = currentVulnerabilityId ? 'PUT' : 'POST';

        let body = data;
        if (currentVulnerabilityId) {
            body = {
                project_id: projectId,
                conversation_tag: data.conversation_tag,
                task_tag: data.task_tag,
                title: data.title,
                description: data.description,
                severity: data.severity,
                status: data.status,
                type: data.type,
                target: data.target,
                preconditions: data.preconditions,
                reproduction_steps: data.reproduction_steps,
                evidence: data.evidence,
                impact: data.impact,
                recommendation: data.recommendation,
                retest_notes: data.retest_notes,
            };
        }

        const response = await apiFetch(url, {
            method: method,
            headers: {
                'Content-Type': 'application/json'
            },
            body: JSON.stringify(body)
        });

        if (!response.ok) {
            const error = await response.json();
            throw new Error(error.error || vulnT('vulnerabilityPage.saveFailed'));
        }

        const updated = await response.json();
        const editedId = currentVulnerabilityId;
        const isEdit = !!editedId;
        const expandedIds = isEdit ? getExpandedVulnerabilityIds() : [];

        closeVulnerabilityModal();
        loadVulnerabilityStats();

        if (!isEdit) {
            vulnerabilityPagination.currentPage = 1;
            loadVulnerabilities();
            return;
        }

        const newStatus = (updated && updated.status) || data.status;
        if (!vulnerabilityStatusMatchesFilter(newStatus)) {
            removeVulnerabilityCard(editedId, { decrementTotal: true, focusNext: true });
            return;
        }

        await loadVulnerabilities(null, { preserveScroll: true, silent: true, expandedIds: expandedIds });
    } catch (error) {
        console.error('保存漏洞失败:', error);
        alert(vulnT('vulnerabilityPage.saveFailed') + ': ' + error.message);
    }
}

// 删除漏洞
async function deleteVulnerability(id) {
    if (!confirm(vulnT('vulnerability.deleteConfirm'))) {
        return;
    }

    try {
        const response = await apiFetch(`/api/vulnerabilities/${id}`, {
            method: 'DELETE'
        });

        if (!response.ok) throw new Error(vulnT('vulnerabilityPage.deleteFailed'));

        loadVulnerabilityStats();
        const card = document.getElementById('vulnerability-card-' + id) ||
            document.querySelector('.vulnerability-card[data-vuln-id="' + id + '"]');
        if (card) {
            removeVulnerabilityCard(id, { decrementTotal: true, focusNext: true });
            return;
        }

        if (vulnerabilityPagination.currentPage > 1 && vulnerabilityPagination.total > 0) {
            const itemsOnCurrentPage = vulnerabilityPagination.total - (vulnerabilityPagination.currentPage - 1) * vulnerabilityPagination.pageSize;
            if (itemsOnCurrentPage <= 1) {
                vulnerabilityPagination.currentPage--;
            }
        }
        await loadVulnerabilities(null, { preserveScroll: true });
    } catch (error) {
        console.error('删除漏洞失败:', error);
        alert(vulnT('vulnerabilityPage.deleteFailed') + ': ' + error.message);
    }
}

// 关闭漏洞模态框
function closeVulnerabilityModal() {
    closeAppModal('vulnerability-modal');
    currentVulnerabilityId = null;
}

// 筛选漏洞（应用当前表单条件）
function filterVulnerabilities() {
    applyVulnerabilityFilters();
}

// 清除筛选
function clearVulnerabilityFilters() {
    closeVulnerabilityMoreFiltersPopover(false);
    if (vulnerabilityFilterDebounceTimer) {
        clearTimeout(vulnerabilityFilterDebounceTimer);
        vulnerabilityFilterDebounceTimer = null;
    }
    const fields = [
        'vulnerability-search-filter',
        'vulnerability-exact-id-filter',
        'vulnerability-conversation-filter',
        'vulnerability-task-filter',
        'vulnerability-conversation-tag-filter',
        'vulnerability-task-tag-filter',
        'vulnerability-project-filter',
        'vulnerability-severity-filter',
        'vulnerability-status-filter'
    ];
    fields.forEach(function (id) {
        const el = document.getElementById(id);
        if (el) el.value = '';
    });
    syncAllVulnFilterCustomSelects();

    vulnerabilityFilters = {
        q: '',
        id: '',
        project_id: '',
        conversation_id: '',
        task_id: '',
        conversation_tag: '',
        task_tag: '',
        severity: '',
        status: ''
    };

    applyVulnerabilityFilters();
}

// 刷新漏洞
function refreshVulnerabilities() {
    loadVulnerabilityStats();
    loadVulnerabilities();
}

async function loadVulnerabilityAlertSubscription() {
	const enabledEl = document.getElementById('vulnerability-alert-enabled');
	const severityEl = document.getElementById('vulnerability-alert-min-severity');
	if (!enabledEl || !severityEl || typeof apiFetch === 'undefined') return;
	try {
		const response = await apiFetch('/api/vulnerability-alerts/subscription');
		if (!response.ok) throw new Error(await response.text());
		const data = await response.json();
		const sub = data.subscription || {};
		enabledEl.checked = !!sub.enabled;
		severityEl.value = sub.min_severity || 'high';
		severityEl.disabled = false;
		if (typeof window.refreshSettingsCustomSelects === 'function') {
			window.refreshSettingsCustomSelects();
		}
		const hint = document.getElementById('vulnerability-alert-binding-hint');
		const bindButton = document.getElementById('vulnerability-alert-bind-button');
		if (hint && !data.delivery_ready) {
			const supportedTypes = [
				{ type: 'wecom', label: vulnT('settings.robots.wecom.title') },
				{ type: 'lark', label: vulnT('settings.robots.lark.title') },
				{ type: 'telegram', label: 'Telegram' },
				{ type: 'slack', label: 'Slack' },
				{ type: 'discord', label: 'Discord' }
			];
			const enabledPlatforms = supportedTypes.filter(function (item) {
				if (typeof getRobotStatus !== 'function') return false;
				const status = getRobotStatus(item.type);
				return status && status.state === 'enabled';
			}).map(function (item) { return item.label; });
			hint.textContent = enabledPlatforms.length
				? vulnT('vulnerabilityPage.alertConfiguredNotBound', { platforms: enabledPlatforms.join('、') })
				: vulnT('vulnerabilityPage.alertNoBinding');
			hint.classList.add('is-warning');
			if (bindButton) bindButton.hidden = false;
		} else if (hint) {
			hint.textContent = vulnT('vulnerabilityPage.alertHint');
			hint.classList.remove('is-warning');
			if (bindButton) bindButton.hidden = true;
		}
	} catch (error) {
		console.warn('加载漏洞提醒设置失败', error);
	}
}

async function saveVulnerabilityAlertSubscription() {
	const enabledEl = document.getElementById('vulnerability-alert-enabled');
	const severityEl = document.getElementById('vulnerability-alert-min-severity');
	if (!enabledEl || !severityEl) return;
	enabledEl.disabled = true;
	severityEl.disabled = true;
	if (typeof window.refreshSettingsCustomSelects === 'function') {
		window.refreshSettingsCustomSelects();
	}
	try {
		const response = await apiFetch('/api/vulnerability-alerts/subscription', {
			method: 'PUT', headers: { 'Content-Type': 'application/json' },
			body: JSON.stringify({ enabled: enabledEl.checked, min_severity: severityEl.value })
		});
		if (!response.ok) throw new Error(await response.text());
		if (typeof showNotification === 'function') showNotification(vulnT('vulnerabilityPage.alertSaved'), 'success');
	} catch (error) {
		if (typeof showNotification === 'function') showNotification(vulnT('vulnerabilityPage.alertSaveFailed'), 'error');
		await loadVulnerabilityAlertSubscription();
	} finally {
		enabledEl.disabled = false;
		severityEl.disabled = false;
		if (typeof window.refreshSettingsCustomSelects === 'function') {
			window.refreshSettingsCustomSelects();
		}
	}
}

// 切换漏洞详情展开/折叠
function toggleVulnerabilityDetails(id) {
    const content = document.getElementById(`content-${id}`);
    const icon = document.getElementById(`expand-icon-${id}`);
    
    if (!content || !icon) return;
    
    if (content.style.display === 'none') {
        content.style.display = 'block';
        icon.style.transform = 'rotate(90deg)';
        loadVulnerabilityRelatedFacts(id).catch((e) => console.warn(e));
    } else {
        content.style.display = 'none';
        icon.style.transform = 'rotate(0deg)';
    }
}

/** 展开漏洞详情时加载关联项目事实 */
async function loadVulnerabilityRelatedFacts(vulnId) {
    const el = document.getElementById(`vuln-related-facts-${vulnId}`);
    if (!el || el.dataset.loaded === '1') return;
    const projectId = (el.dataset.projectId || '').trim();
    if (!projectId) {
        el.hidden = true;
        el.dataset.loaded = '1';
        return;
    }
    el.hidden = false;
    el.innerHTML = '<span>加载关联事实…</span>';
    try {
        const res = await apiFetch(
            `/api/projects/${encodeURIComponent(projectId)}/facts?related_vulnerability_id=${encodeURIComponent(vulnId)}&limit=20&exclude_deprecated=true`,
        );
        if (!res.ok) throw new Error('fetch failed');
        const facts = await res.json();
        if (!Array.isArray(facts) || !facts.length) {
            el.innerHTML =
                '<strong>关联事实</strong><p style="margin:6px 0 0;color:#64748b">暂无；可在「项目管理」事实详情中关联或生成漏洞草稿。</p>';
            el.dataset.loaded = '1';
            return;
        }
        const items = facts
            .map((f) => {
                const key = escapeHtml(f.fact_key);
                const sum = escapeHtml((f.summary || '').slice(0, 120));
                const pid = escapeAttr(projectId);
                const rawKey = escapeAttr(f.fact_key);
                return `<li><a role="button" href="#" data-project-id="${pid}" data-fact-key="${rawKey}" onclick="event.preventDefault();openProjectFactFromVulnerability(this.dataset.projectId,this.dataset.factKey)"><code>${key}</code></a> — ${sum}</li>`;
            })
            .join('');
        el.innerHTML = `<strong>关联事实（${facts.length}）</strong><ul>${items}</ul>`;
        el.dataset.loaded = '1';
    } catch (e) {
        el.innerHTML = '<strong>关联事实</strong><p style="margin:6px 0 0;color:#b91c1c">加载失败</p>';
    }
}

function openProjectFactFromVulnerability(projectId, factKey) {
    if (!projectId || !factKey) return;
    if (typeof switchPage === 'function') {
        switchPage('projects');
    }
    setTimeout(async () => {
        if (typeof window.initProjectsPage === 'function') {
            await window.initProjectsPage();
        }
        if (typeof window.selectProject === 'function') {
            await window.selectProject(projectId);
        }
        if (typeof window.switchProjectTab === 'function') {
            window.switchProjectTab('facts');
        }
        if (typeof window.viewProjectFactBody === 'function') {
            window.viewProjectFactBody(factKey);
        }
    }, 350);
}

window.openProjectFactFromVulnerability = openProjectFactFromVulnerability;

// HTML转义
function escapeHtml(text) {
    const div = document.createElement('div');
    div.textContent = text;
    return div.innerHTML;
}

function escapeJsString(text) {
    return JSON.stringify(String(text == null ? '' : text));
}

function escapeAttr(text) {
    return escapeHtml(text).replace(/"/g, '&quot;').replace(/'/g, '&#39;');
}

function escapeJsStringAttr(text) {
    return escapeAttr(escapeJsString(text));
}

/** 复制详情字段（编码由 encodeURIComponent 传入，避免引号截断） */
function vulnerabilityCopyEncoded(evt, encoded) {
    if (evt && evt.stopPropagation) {
        evt.stopPropagation();
    }
    let text = '';
    try {
        text = decodeURIComponent(encoded);
    } catch (e) {
        return;
    }
    const done = () => {
        if (evt && evt.target && evt.target.closest) {
            const btn = evt.target.closest('.vuln-detail-field__copy');
            if (btn) {
                const t0 = btn.getAttribute('title') || '';
                btn.setAttribute('title', vulnT('common.copied'));
                setTimeout(() => btn.setAttribute('title', t0), 1600);
            }
        }
    };
    if (navigator.clipboard && typeof navigator.clipboard.writeText === 'function') {
        navigator.clipboard.writeText(text).then(done).catch(() => {
            try {
                const ta = document.createElement('textarea');
                ta.value = text;
                ta.style.position = 'fixed';
                ta.style.left = '-9999px';
                document.body.appendChild(ta);
                ta.select();
                document.execCommand('copy');
                document.body.removeChild(ta);
                done();
            } catch (err) {
                console.error('copy failed', err);
            }
        });
    } else {
        try {
            const ta = document.createElement('textarea');
            ta.value = text;
            ta.style.position = 'fixed';
            ta.style.left = '-9999px';
            document.body.appendChild(ta);
            ta.select();
            document.execCommand('copy');
            document.body.removeChild(ta);
            done();
        } catch (err) {
            console.error('copy failed', err);
        }
    }
}

function vulnDetailProjectField(vuln) {
    const label = vulnT('vulnerabilityPage.detailProject');
    const hint = escapeHtml(vulnT('vulnerabilityPage.projectBindHint'));
    return `<div class="vuln-detail-field">
<div class="vuln-detail-field__label">${escapeHtml(label)}</div>
<div class="vuln-detail-field__row">
<select class="vulnerability-project-bind-select" data-vuln-id="${escapeAttr(vuln.id)}"
    onchange="bindVulnerabilityProject(this.dataset.vulnId, this.value, true)"
    title="${escapeAttr(hint)}" aria-label="${escapeAttr(label)}">
${buildVulnerabilityProjectOptionsHtml(vuln.project_id || '')}
</select>
<span class="vuln-detail-field__copy-spacer" aria-hidden="true"></span>
</div>
</div>`;
}

function vulnDetailField(label, value, asCode) {
    if (value === undefined || value === null || String(value) === '') {
        return '';
    }
    const s = String(value);
    const enc = encodeURIComponent(s);
    const copyTitle = escapeHtml(vulnT('common.copy'));
    const valueEl = asCode
        ? `<code class="vuln-detail-field-value">${escapeHtml(s)}</code>`
        : `<span class="vuln-detail-field-value">${escapeHtml(s)}</span>`;
    const copyBtn = `<button type="button" class="vuln-detail-field__copy" onclick="vulnerabilityCopyEncoded(event, '${enc}')" title="${copyTitle}" aria-label="${copyTitle}">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" aria-hidden="true" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="9" y="9" width="13" height="13" rx="2"/><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"/></svg>
</button>`;
    return `<div class="vuln-detail-field">
<div class="vuln-detail-field__label">${escapeHtml(label)}</div>
<div class="vuln-detail-field__row">${valueEl}<div class="vuln-detail-field__actions">${copyBtn}</div></div>
</div>`;
}

function vulnDetailConversationField(conversationId) {
    if (conversationId === undefined || conversationId === null || String(conversationId).trim() === '') {
        return '';
    }
    const s = String(conversationId).trim();
    const enc = encodeURIComponent(s);
    const copyTitle = escapeHtml(vulnT('common.copy'));
    const openTitle = escapeHtml(vulnT('vulnerabilityPage.openConversation'));
    const qId = JSON.stringify(s).replace(/"/g, '&quot;');
    const openBtn = `<button type="button" class="vuln-detail-field__action" onclick="openVulnerabilityConversation(${qId})" title="${openTitle}" aria-label="${openTitle}">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" aria-hidden="true" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"/></svg>
</button>`;
    const copyBtn = `<button type="button" class="vuln-detail-field__copy" onclick="vulnerabilityCopyEncoded(event, '${enc}')" title="${copyTitle}" aria-label="${copyTitle}">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" aria-hidden="true" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="9" y="9" width="13" height="13" rx="2"/><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"/></svg>
</button>`;
    return `<div class="vuln-detail-field">
<div class="vuln-detail-field__label">${escapeHtml(vulnT('vulnerabilityPage.detailConversationId'))}</div>
<div class="vuln-detail-field__row"><code class="vuln-detail-field-value">${escapeHtml(s)}</code><div class="vuln-detail-field__actions">${openBtn}${copyBtn}</div></div>
</div>`;
}

function openVulnerabilityConversation(conversationId) {
    if (typeof navigateToConversation === 'function') {
        navigateToConversation(conversationId);
    } else if (typeof window.navigateToConversation === 'function') {
        window.navigateToConversation(conversationId);
    }
}

function vulnNarrativeSection(label, value, options) {
    if (value === undefined || value === null || String(value).trim() === '') return '';
    const opts = options || {};
    const tag = opts.code ? 'pre' : 'div';
    const cls = opts.code ? 'vulnerability-section-body vulnerability-section-body--code' : 'vulnerability-section-body';
    return `<section class="vulnerability-section">
<strong>${escapeHtml(label)}</strong>
<${tag} class="${cls}">${escapeHtml(String(value))}</${tag}>
</section>`;
}

// 将漏洞格式化为Markdown（章节标题随界面语言）
function formatVulnerabilityAsMarkdown(vuln) {
    const severityText = vulnSeverityLabel(vuln.severity);
    const statusText = vulnStatusLabel(vuln.status);
    const loc = vulnDateLocale();
    const createdDate = new Date(vuln.created_at).toLocaleString(loc);
    const updatedDate = new Date(vuln.updated_at).toLocaleString(loc);
    const L = (k) => vulnT('vulnerabilityMd.' + k);

    let markdown = `# ${vuln.title}\n\n`;

    markdown += `## ${L('headingBasic')}\n\n`;
    markdown += `- **${L('labelId')}**: \`${vuln.id}\`\n`;
    markdown += `- **${L('labelSeverity')}**: ${severityText}\n`;
    markdown += `- **${L('labelStatus')}**: ${statusText}\n`;
    if (vuln.type) {
        markdown += `- **${L('labelType')}**: ${vuln.type}\n`;
    }
    if (vuln.target) {
        markdown += `- **${L('labelTarget')}**: ${vuln.target}\n`;
    }
    markdown += `- **${L('labelConversationId')}**: \`${vuln.conversation_id}\`\n`;
    if (vuln.task_id) {
        markdown += `- **${L('labelTaskId')}**: \`${vuln.task_id}\`\n`;
    }
    if (vuln.task_queue_id) {
        markdown += `- **${L('labelTaskQueueId')}**: \`${vuln.task_queue_id}\`\n`;
    }
    if (vuln.conversation_tag) {
        markdown += `- **${L('labelConversationTag')}**: ${vuln.conversation_tag}\n`;
    }
    if (vuln.task_tag) {
        markdown += `- **${L('labelTaskTag')}**: ${vuln.task_tag}\n`;
    }
    markdown += `- **${L('labelCreated')}**: ${createdDate}\n`;
    markdown += `- **${L('labelUpdated')}**: ${updatedDate}\n\n`;

    if (vuln.description) {
        markdown += `## ${L('headingDescription')}\n\n${vuln.description}\n\n`;
    }

    if (vuln.preconditions) {
        markdown += `## ${L('headingPreconditions')}\n\n${vuln.preconditions}\n\n`;
    }

    if (vuln.reproduction_steps) {
        markdown += `## ${L('headingReproductionSteps')}\n\n${vuln.reproduction_steps}\n\n`;
    }

    if (vuln.evidence) {
        markdown += `## ${L('headingEvidence')}\n\n\`\`\`\n${vuln.evidence}\n\`\`\`\n\n`;
    }

    if (vuln.impact) {
        markdown += `## ${L('headingImpact')}\n\n${vuln.impact}\n\n`;
    }

    if (vuln.recommendation) {
        markdown += `## ${L('headingRecommendation')}\n\n${vuln.recommendation}\n\n`;
    }

    if (vuln.retest_notes) {
        markdown += `## ${L('headingRetestNotes')}\n\n${vuln.retest_notes}\n\n`;
    }

    return markdown;
}

function buildVulnerabilityFilterParams() {
    const params = new URLSearchParams();
    if (vulnerabilityFilters.q) {
        params.append('q', vulnerabilityFilters.q);
    }
    const keys = ['id', 'project_id', 'conversation_id', 'task_id', 'conversation_tag', 'task_tag', 'severity', 'status'];
    keys.forEach(function (k) {
        if (vulnerabilityFilters[k]) {
            params.append(k, vulnerabilityFilters[k]);
        }
    });
    return params;
}

/** 看板统计：保留项目/关键词等筛选，但不带严重度（卡片本身用于切换严重度筛选） */
function buildVulnerabilityDashboardStatsParams() {
    const params = buildVulnerabilityFilterParams();
    params.delete('severity');
    return params;
}

function triggerTextDownload(fileName, content) {
    const blob = new Blob([content], { type: 'text/markdown;charset=utf-8' });
    const url = URL.createObjectURL(blob);
    const link = document.createElement('a');
    link.href = url;
    link.download = fileName;
    document.body.appendChild(link);
    link.click();
    document.body.removeChild(link);
    URL.revokeObjectURL(url);
}

function hasActiveVulnerabilityFilters() {
    const keys = ['q', 'id', 'project_id', 'conversation_id', 'task_id', 'conversation_tag', 'task_tag', 'severity', 'status'];
    return keys.some(function (k) {
        return Boolean(vulnerabilityFilters[k]);
    });
}

async function batchDeleteVulnerabilityReports() {
    try {
        const params = buildVulnerabilityFilterParams();
        const statsResponse = await apiFetch(`/api/vulnerabilities/stats?${params.toString()}`);
        if (!statsResponse.ok) {
            throw new Error(vulnT('vulnerabilityPage.deleteFailed'));
        }
        const stats = await statsResponse.json();
        const count = stats.total || 0;
        if (count <= 0) {
            alert(vulnT('vulnerabilityPage.batchDeleteNoResults'));
            return;
        }

        const confirmKey = hasActiveVulnerabilityFilters()
            ? 'vulnerabilityPage.batchDeleteConfirm'
            : 'vulnerabilityPage.batchDeleteConfirmAll';
        if (!confirm(vulnT(confirmKey, { count: count }))) {
            return;
        }

        const response = await apiFetch(`/api/vulnerabilities/batch?${params.toString()}`, {
            method: 'DELETE'
        });
        if (!response.ok) {
            const error = await response.json().catch(() => ({ error: vulnT('vulnerabilityPage.deleteFailed') }));
            throw new Error(error.error || vulnT('vulnerabilityPage.deleteFailed'));
        }
        const data = await response.json();
        const deleted = data.deleted || 0;
        alert(vulnT('vulnerabilityPage.batchDeleteSuccess', { count: deleted }));
        vulnerabilityPagination.currentPage = 1;
        loadVulnerabilityStats();
        loadVulnerabilities();
    } catch (error) {
        console.error('批量删除漏洞失败:', error);
        alert(vulnT('vulnerabilityPage.batchDeleteFailed') + ': ' + error.message);
    }
}

async function exportVulnerabilityReports() {
    try {
        const params = buildVulnerabilityFilterParams();
        params.set('mode', 'summary');
        params.set('group_by', 'conversation');
        const response = await apiFetch(`/api/vulnerabilities/export?${params.toString()}`);
        if (!response.ok) {
            const error = await response.json().catch(() => ({ error: vulnT('vulnerabilityPage.exportFailedMessage') }));
            throw new Error(error.error || vulnT('vulnerabilityPage.exportFailedMessage'));
        }
        const data = await response.json();
        const files = Array.isArray(data.files) ? data.files : [];
        if (!files.length) {
            alert(vulnT('vulnerabilityPage.exportNoResults'));
            return;
        }
        files.forEach((file, idx) => {
            setTimeout(() => triggerTextDownload(file.filename || `vulnerability-export-${idx + 1}.md`, file.content || ''), idx * 120);
        });
        if (files.length > 1) {
            alert(vulnT('vulnerabilityPage.exportStarted', { count: files.length }));
        }
    } catch (error) {
        console.error('导出漏洞报告失败:', error);
        alert(vulnT('vulnerabilityPage.exportFailed') + ': ' + error.message);
    }
}


// 下载漏洞为Markdown格式
async function downloadVulnerabilityAsMarkdown(id, event) {
    try {
        const response = await apiFetch(`/api/vulnerabilities/${id}`);
        if (!response.ok) {
            throw new Error(vulnT('vulnerabilityPage.fetchFailed'));
        }

        const vuln = await response.json();
        const markdown = formatVulnerabilityAsMarkdown(vuln);

        // 创建Blob对象
        const blob = new Blob([markdown], { type: 'text/markdown;charset=utf-8' });
        
        // 创建下载链接
        const url = URL.createObjectURL(blob);
        const link = document.createElement('a');
        link.href = url;
        
        // 生成文件名（使用漏洞标题，清理特殊字符，保留中文）
        const cleanTitle = vuln.title
            .replace(/[<>:"/\\|?*]/g, '') // 移除Windows不允许的字符
            .replace(/\s+/g, '_') // 空格替换为下划线
            .substring(0, 50); // 限制长度
        const fileName = `${cleanTitle}_${vuln.id.substring(0, 8)}.md`;
        link.download = fileName;
        
        // 触发下载
        document.body.appendChild(link);
        link.click();
        
        // 清理
        document.body.removeChild(link);
        URL.revokeObjectURL(url);
        
        // 显示成功提示
        if (event && event.target) {
            const button = event.target.closest('button');
            if (button) {
                const originalTitle = button.title || vulnT('vulnerabilityPage.downloadMarkdownTitle');
                button.title = vulnT('vulnerabilityPage.downloadOkTitle');
                setTimeout(() => {
                    button.title = originalTitle;
                }, 2000);
            }
        }
    } catch (error) {
        console.error('下载失败:', error);
        alert(vulnT('vulnerabilityPage.downloadFailed') + ': ' + error.message);
    }
}

// 点击模态框外部关闭
window.onclick = function(event) {
    const modal = document.getElementById('vulnerability-modal');
    if (event.target === modal) {
        closeVulnerabilityModal();
    }
};

document.addEventListener('languagechange', async function () {
    const page = document.getElementById('page-vulnerabilities');
    if (page && page.classList.contains('active')) {
        const panel = document.getElementById('vulnerability-filter-panel');
        if (panel && typeof window.applyTranslations === 'function') {
            window.applyTranslations(panel);
        }
        renderVulnerabilityFilterChips();
        await refreshVulnerabilityProjectFilter();
        syncAllVulnFilterCustomSelects();
        loadVulnerabilities();
    }
});

async function bindVulnerabilityProject(vulnId, projectId, silent) {
    if (!vulnId) return;
    try {
        const response = await apiFetch(`/api/vulnerabilities/${encodeURIComponent(vulnId)}`, {
            method: 'PUT',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify({ project_id: projectId || '' }),
        });
        if (!response.ok) {
            const err = await response.json().catch(() => ({}));
            throw new Error(err.error || vulnT('vulnerabilityPage.projectBindFailed'));
        }
        if (!silent) {
            alert(vulnT('vulnerabilityPage.projectBindOk'));
        }
        loadVulnerabilityStats();
        const expandedIds = getExpandedVulnerabilityIds();
        if (!expandedIds.includes(vulnId)) {
            expandedIds.push(vulnId);
        }
        await loadVulnerabilities(null, { preserveScroll: true, silent: true, expandedIds: expandedIds });
    } catch (error) {
        console.error('绑定项目失败:', error);
        alert(vulnT('vulnerabilityPage.projectBindFailed') + ': ' + error.message);
        await loadVulnerabilities(null, { preserveScroll: true });
    }
}

async function refreshVulnerabilityProjectFilter() {
    const sel = document.getElementById('vulnerability-project-filter');
    if (!sel) return;
    try {
        let list = [];
        if (typeof fetchAllProjects === 'function') {
            list = await fetchAllProjects(true);
        } else {
            const res = await apiFetch('/api/projects?limit=500');
            if (!res.ok) return;
            const data = await res.json();
            list = typeof parseProjectsListResponse === 'function'
                ? parseProjectsListResponse(data).items
                : (Array.isArray(data) ? data : (data.projects || []));
        }
        if (typeof rebuildProjectNameMap === 'function') {
            rebuildProjectNameMap(list);
        } else if (typeof projectNameById !== 'undefined') {
            list.forEach((p) => { if (p.id) projectNameById[p.id] = p.name || p.id; });
        }
        const cur = vulnerabilityFilters.project_id || sel.value || '';
        let html = '<option value="">' + escapeHtml(vulnT('vulnerabilityPage.allProjects')) + '</option>';
        (list || []).forEach((p) => {
            if (!p.id) return;
            const selected = p.id === cur ? ' selected' : '';
            const arch = p.status === 'archived' ? ' [' + vulnT('projects.archived') + ']' : '';
            html += `<option value="${escapeAttr(p.id)}"${selected}>${escapeHtml(p.name || p.id)}${arch}</option>`;
        });
        sel.innerHTML = html;
        if (cur) sel.value = cur;
        syncVulnFilterCustomSelect('vulnerability-project-filter');
        const modalSel = document.getElementById('vulnerability-project-id');
        if (modalSel && isAppModalOpen('vulnerability-modal')) {
            const modalCur = modalSel.value || '';
            modalSel.innerHTML = buildVulnerabilityProjectOptionsHtml(modalCur);
            modalSel.value = modalCur;
        }
    } catch (e) {
        console.warn('加载项目筛选列表失败', e);
    }
}

function setVulnerabilityProjectFilter(projectId) {
    vulnerabilityFilters.project_id = projectId || '';
    const sel = document.getElementById('vulnerability-project-filter');
    if (sel) sel.value = projectId || '';
    syncVulnFilterCustomSelect('vulnerability-project-filter');
    applyVulnerabilityFilters();
}

function setVulnerabilityIdFilter(vulnId) {
    vulnerabilityFilters.id = vulnId || '';
    const el = document.getElementById('vulnerability-exact-id-filter');
    if (el) el.value = vulnId || '';
    applyVulnerabilityFilters();
}

window.refreshVulnerabilityProjectFilter = refreshVulnerabilityProjectFilter;
window.setVulnerabilityProjectFilter = setVulnerabilityProjectFilter;
window.setVulnerabilityIdFilter = setVulnerabilityIdFilter;
window.bindVulnerabilityProject = bindVulnerabilityProject;
window.buildVulnerabilityProjectOptionsHtml = buildVulnerabilityProjectOptionsHtml;
window.changeVulnerabilityStatus = changeVulnerabilityStatus;
window.openVulnerabilityConversation = openVulnerabilityConversation;
window.loadVulnerabilityAlertSubscription = loadVulnerabilityAlertSubscription;
window.saveVulnerabilityAlertSubscription = saveVulnerabilityAlertSubscription;
