CSES Solved Filter by Date

Hide solved check marks (list & task pages) for problems whose last submission is before a selected date.

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

You will need to install an extension such as Tampermonkey to install this script.

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

您需要先安裝使用者腳本管理器擴充功能後才能安裝該腳本。

(我已經安裝了使用者腳本管理器,讓我安裝!)

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

"use strict";
// ==UserScript==
// @name         CSES Solved Filter by Date
// @namespace    https://cses.fi/
// @version      0.1.8
// @description  Hide solved check marks (list & task pages) for problems whose last submission is before a selected date.
// @author       Gaurish Gangwar
// @match        https://cses.fi/problemset/list
// @match        https://cses.fi/problemset/list/
// @match        https://cses.fi/problemset/list/*
// @match        https://cses.fi/problemset/task/*
// @match        https://cses.fi/problemset/*
// @icon         https://cses.fi/favicon.ico
// @run-at       document-end
// @grant        none
// @license      MIT
// ==/UserScript==
(function () {
    'use strict';
    const CACHE_PREFIX = 'cses:lastSubmission:';
    const CACHE_VERSION_KEY = 'cses:lastSubmission:__version';
    const CACHE_VERSION = 'v1';
    const THRESHOLD_DATE_KEY = 'cses:thresholdDate';
    const EXCLUDED_SECTIONS_KEY = 'cses:excludedSections';
    function loadExcludedSections() {
        try {
            const raw = localStorage.getItem(EXCLUDED_SECTIONS_KEY);
            if (!raw)
                return new Set();
            const arr = JSON.parse(raw);
            return new Set(Array.isArray(arr) ? arr : []);
        }
        catch {
            return new Set();
        }
    }
    function saveExcludedSections(set) {
        try {
            localStorage.setItem(EXCLUDED_SECTIONS_KEY, JSON.stringify(Array.from(set)));
        }
        catch { }
    }
    function isDarkTheme() {
        const dm = document.getElementById('darkmode-enabled');
        if (dm && dm.textContent && dm.textContent.trim() === 'true')
            return true;
        const themeMeta = document.getElementById('theme-color');
        const c = themeMeta && themeMeta.getAttribute('content');
        return !!(c && c.toLowerCase() === '#292929');
    }
    // Ensure cache version
    if (localStorage.getItem(CACHE_VERSION_KEY) !== CACHE_VERSION) {
        Object.keys(localStorage).forEach(k => { if (k.startsWith(CACHE_PREFIX))
            localStorage.removeItem(k); });
        localStorage.setItem(CACHE_VERSION_KEY, CACHE_VERSION);
    }
    function extractLatestSubmissionDate(htmlText) {
        const re = /(\d{4}-\d{2}-\d{2})\s+(\d{2}:\d{2}:\d{2})/;
        const match = re.exec(htmlText);
        if (!match)
            return null;
        const dateTimeStr = match[1] + 'T' + match[2];
        const date = new Date(dateTimeStr);
        return isNaN(date.getTime()) ? null : date;
    }
    function toISODate(d) {
        const iso = d.toISOString();
        const idx = iso.indexOf('T');
        return idx === -1 ? iso : iso.slice(0, idx);
    }
    function createUI() {
        let panel = document.getElementById('cses-filter-panel');
        if (panel)
            return panel;
        panel = document.createElement('div');
        panel.id = 'cses-filter-panel';
        panel.innerHTML = `
      <label style="display:flex;align-items:center;gap:4px;font:12px system-ui;"> 
        <span style="font-weight:600;">Solved after:</span>
        <input type="date" id="cses-threshold-date" style="padding:2px;font-size:12px;" />
        <button type="button" id="cses-clear-cache" title="Clear cached submission timestamps" style="font-size:11px;padding:2px 6px;">Clear Cache</button>
      </label>
      <div id="cses-filter-status" style="margin-top:4px;font:11px system-ui;color:#444;max-width:260px;line-height:1.3;"></div>
    `;
        function applyTheme() {
            const dark = isDarkTheme();
            if (dark) {
                Object.assign(panel.style, {
                    position: 'fixed', top: '6px', left: '6px', background: 'rgba(32,32,32,0.92)',
                    border: '1px solid #444', padding: '6px 8px', borderRadius: '6px', zIndex: '10000',
                    boxShadow: '0 2px 6px rgba(0,0,0,0.5)', color: '#eee'
                });
                const status = panel.querySelector('#cses-filter-status');
                if (status)
                    status.style.color = '#bbb';
                const input = panel.querySelector('#cses-threshold-date');
                if (input)
                    Object.assign(input.style, { background: '#1e1e1e', color: '#eee', border: '1px solid #555' });
                const btn = panel.querySelector('#cses-clear-cache');
                if (btn)
                    Object.assign(btn.style, { background: '#2a2a2a', color: '#ddd', border: '1px solid #555', cursor: 'pointer' });
            }
            else {
                Object.assign(panel.style, {
                    position: 'fixed', top: '6px', left: '6px', background: 'rgba(255,255,255,0.9)',
                    border: '1px solid #ccc', padding: '6px 8px', borderRadius: '6px', zIndex: '10000',
                    boxShadow: '0 2px 5px rgba(0,0,0,0.15)', color: '#222'
                });
                const status = panel.querySelector('#cses-filter-status');
                if (status)
                    status.style.color = '#444';
                const input = panel.querySelector('#cses-threshold-date');
                if (input)
                    Object.assign(input.style, { background: '#fff', color: '#111', border: '1px solid #bbb' });
                const btn = panel.querySelector('#cses-clear-cache');
                if (btn)
                    Object.assign(btn.style, { background: '#f5f5f5', color: '#222', border: '1px solid #bbb', cursor: 'pointer' });
            }
        }
        applyTheme();
        const themeMeta = document.getElementById('theme-color');
        if (themeMeta) {
            const observer = new MutationObserver(() => applyTheme());
            observer.observe(themeMeta, { attributes: true, attributeFilter: ['content'] });
        }
        document.body.appendChild(panel);
        return panel;
    }
    async function fetchViewContent(url) {
        const resp = await fetch(url, { credentials: 'same-origin' });
        if (!resp.ok)
            throw new Error('HTTP ' + resp.status);
        const ct = (resp.headers.get('content-type') || '').toLowerCase();
        if (ct.startsWith('text/') || ct.includes('json') || ct.includes('xml') || ct.includes('csv') || ct.includes('charset=')) {
            return await resp.text();
        }
        try {
            const blob = await resp.blob();
            if (blob.text) {
                return await blob.text();
            }
        }
        catch { }
        try {
            const buf = await resp.arrayBuffer();
            return new TextDecoder('utf-8').decode(buf);
        }
        catch {
            return await resp.text();
        }
    }
    function getThresholdDate() {
        const input = document.getElementById('cses-threshold-date');
        if (!input || !input.value)
            return null;
        const d = new Date(input.value + 'T00:00:00');
        return isNaN(d.getTime()) ? null : d;
    }
    function setStatus(msg) {
        const el = document.getElementById('cses-filter-status');
        if (el)
            el.textContent = msg;
    }
    async function getSubmissionMeta(problemId) {
        const cacheKey = CACHE_PREFIX + problemId;
        const cached = localStorage.getItem(cacheKey);
        if (cached) {
            if (cached === 'NONE')
                return { date: null, attempted: false };
            const d = new Date(cached);
            if (!isNaN(d.getTime()))
                return { date: d, attempted: true };
        }
        try {
            const url = `https://cses.fi/problemset/submit/${problemId}/`;
            const resp = await fetch(url, { credentials: 'same-origin' });
            if (!resp.ok)
                throw new Error('HTTP ' + resp.status);
            const text = await resp.text();
            const date = extractLatestSubmissionDate(text);
            if (date) {
                localStorage.setItem(cacheKey, date.toISOString());
                return { date, attempted: true };
            }
            else {
                localStorage.setItem(cacheKey, 'NONE');
                return { date: null, attempted: false };
            }
        }
        catch (e) {
            console.error('Failed to fetch submissions for', problemId, e);
            return { date: null, attempted: false };
        }
    }
    async function getLastSubmissionDate(problemId) {
        const meta = await getSubmissionMeta(problemId);
        return meta.date;
    }
    async function refreshProblem(problemId, icon, forceRefetch = false) {
        if (forceRefetch)
            localStorage.removeItem(CACHE_PREFIX + problemId);
        const date = await getLastSubmissionDate(problemId);
        const threshold = getThresholdDate();
        if (icon) {
            if (icon.classList.contains('full'))
                icon.dataset.originalSolved = '1';
            if (date && threshold && date < threshold) {
                if (icon.classList.contains('full'))
                    icon.classList.remove('full');
                icon.title = `Hidden (old solve): last submission ${date.toLocaleString()}`;
            }
            else if (date) {
                if (!icon.classList.contains('full'))
                    icon.classList.add('full');
                icon.title = `Last submission ${date.toLocaleString()}`;
            }
        }
        if (/\/problemset\/list\/?/.test(location.pathname))
            updateFilteredSectionStats();
    }
    class TaskQueue {
        constructor(concurrency = 3) {
            this.running = 0;
            this.q = [];
            this.c = concurrency;
        }
        push(task) { this.q.push(task); this.run(); }
        run() {
            while (this.running < this.c && this.q.length) {
                const t = this.q.shift();
                this.running++;
                Promise.resolve().then(t).catch(() => { }).finally(() => { this.running--; this.run(); });
            }
        }
    }
    async function init() {
        const path = location.pathname;
        if (!/\/problemset\//.test(path))
            return;
        const panel = createUI();
        const dateInput = panel.querySelector('#cses-threshold-date');
        const saved = localStorage.getItem(THRESHOLD_DATE_KEY);
        const today = toISODate(new Date());
        dateInput.value = saved || today;
        dateInput.addEventListener('change', () => {
            localStorage.setItem(THRESHOLD_DATE_KEY, dateInput.value);
            applyFilter();
            buildSectionStats();
        });
        panel.querySelector('#cses-clear-cache').addEventListener('click', () => {
            if (!confirm('Clear cached submission timestamps?'))
                return;
            let cleared = 0;
            Object.keys(localStorage).forEach(k => { if (k.startsWith(CACHE_PREFIX)) {
                localStorage.removeItem(k);
                cleared++;
            } });
            setStatus(`Cleared ${cleared} cached entries.`);
            applyFilter(true);
            buildSectionStats();
        });
        document.addEventListener('click', (e) => {
            if (!(e instanceof MouseEvent) || !e.altKey)
                return;
            const target = e.target;
            if (!target)
                return;
            if (target.classList.contains('task-score')) {
                const problemId = target.getAttribute('data-problem-id') || extractProblemId(target);
                if (problemId) {
                    setStatus(`Refreshing problem ${problemId}...`);
                    refreshProblem(problemId, target, true).then(() => setStatus(`Refreshed problem ${problemId}.`));
                }
                e.preventDefault();
                e.stopPropagation();
            }
        });
        const mo = new MutationObserver(muts => {
            muts.forEach(m => {
                if (m.type === 'attributes' && m.attributeName === 'class') {
                    const el = m.target;
                    if (el && el.classList.contains('task-score') && el.classList.contains('full')) {
                        const pid = el.getAttribute('data-problem-id') || extractProblemId(el);
                        if (pid) {
                            el.dataset.originalSolved = '1';
                            refreshProblem(pid, el, true);
                        }
                    }
                }
                else if (m.addedNodes && m.addedNodes.length) {
                    m.addedNodes.forEach(n => {
                        if (n instanceof HTMLElement) {
                            const icons = (n.matches && n.matches('span.task-score.icon.full')) ? [n] : Array.from(n.querySelectorAll('span.task-score.icon.full'));
                            icons.forEach(ic => {
                                const pid = ic.getAttribute('data-problem-id') || extractProblemId(ic);
                                if (pid) {
                                    ic.dataset.originalSolved = '1';
                                    refreshProblem(pid, ic, true);
                                }
                            });
                        }
                    });
                }
            });
        });
        mo.observe(document.documentElement, { subtree: true, childList: true, attributes: true, attributeFilter: ['class'] });
        applyFilter();
        buildSectionStats();
        if (/\/problemset\/result\//.test(path) || /\/problemset\/view\//.test(path)) {
            enhanceResultCopyButtons();
            const mo2 = new MutationObserver(() => enhanceResultCopyButtons());
            mo2.observe(document.body, { childList: true, subtree: true });
        }
    }
    function collectSolved() {
        const nodes = Array.from(document.querySelectorAll('span.task-score.icon'));
        nodes.forEach(n => { if (n.classList.contains('full'))
            n.dataset.originalSolved = '1'; });
        return nodes.filter(n => n.dataset.originalSolved === '1');
    }
    function extractProblemId(el) {
        let link = null;
        const li = el.closest('li.task');
        if (li)
            link = li.querySelector('a[href*="/problemset/task/"]');
        if (!link)
            link = el.closest('a[href*="/problemset/task/"]');
        if (!link)
            return null;
        const href = link.getAttribute('href') || '';
        const m = /\/problemset\/task\/(\d+)/.exec(href);
        return m && m[1] ? m[1] : null;
    }
    function extractProblemTitle(icon) {
        let link = null;
        const li = icon.closest('li.task');
        if (li)
            link = li.querySelector('a[href*="/problemset/task/"]');
        if (!link)
            link = icon.closest('a[href*="/problemset/task/"]');
        const text = (link && link.childNodes && Array.from(link.childNodes).filter((n) => n.nodeType === 3).map((n) => n.textContent).join('').trim()) || 'Unknown';
        return text;
    }
    const sectionQueue = new TaskQueue(2);
    function getHeadingTitle(heading) {
        try {
            const clone = heading.cloneNode(true);
            const injected = clone.querySelectorAll('.cses-section-stats, .cses-section-stats-filter, .cses-section-toggle');
            injected.forEach(n => n.remove());
            return (clone.textContent || '').trim().toLowerCase();
        }
        catch {
            return (heading.textContent || '').trim().toLowerCase();
        }
    }
    function findSections() {
        return Array.from(document.querySelectorAll('h2')).map(h => ({
            heading: h,
            list: (h.nextElementSibling && h.nextElementSibling.matches('ul.task-list')) ? h.nextElementSibling : null
        }));
    }
    function classifyTask(li) {
        var _a;
        const icon = li.querySelector('span.task-score.icon');
        const solved = !!(icon && icon.classList.contains('full'));
        const wrongImmediate = !!(icon && icon.classList.contains('zero'));
        const link = li.querySelector('a[href*="/problemset/task/"]');
        const problemId = link ? (_a = (link.getAttribute('href') || '').match(/(\d+)/)) === null || _a === void 0 ? void 0 : _a[1] : null;
        if (!problemId)
            return { solved: false, attempted: false, unattended: true, pending: false };
        if (solved)
            return { solved: true, attempted: true, unattended: false, pending: false, problemId };
        if (wrongImmediate)
            return { solved: false, attempted: true, unattended: false, pending: false, problemId };
        const cacheVal = localStorage.getItem(CACHE_PREFIX + problemId);
        if (cacheVal) {
            if (cacheVal === 'NONE')
                return { solved: false, attempted: false, unattended: true, pending: false, problemId };
            const d = new Date(cacheVal);
            if (!isNaN(d.getTime()))
                return { solved: false, attempted: true, unattended: false, pending: false, problemId };
        }
        return { solved: false, attempted: false, unattended: true, pending: true, problemId };
    }
    function updateSectionHeading(section) {
        const { heading, list } = section;
        if (!list)
            return;
        const tasks = Array.from(list.querySelectorAll('li.task'));
        let total = tasks.length, correct = 0, wrong = 0, unattended = 0;
        tasks.forEach(li => {
            const c = classifyTask(li);
            if (c.solved)
                correct++;
            else if (c.attempted)
                wrong++;
            else
                unattended++;
        });
        const base = heading.getAttribute('data-base-text') || (heading.textContent || '').trim();
        heading.setAttribute('data-base-text', base);
        let badge = heading.querySelector(':scope > .cses-section-stats');
        if (!badge) {
            badge = document.createElement('span');
            badge.className = 'cses-section-stats';
            badge.style.cssText = 'margin-right:6px;font-weight:normal;font-size:0.75em;color:#888;';
            heading.prepend(badge);
        }
        badge.innerHTML = `[<span style="color:#ccc;">${total}</span> / <span style="color:#3c9b3c;">${correct}</span> / <span style="color:#d28b26;">${wrong}</span> / <span style="color:#777;">${unattended}</span>] `;
        badge.title = 'Overall: total / solved / wrong (attempted unsolved) / unattended';
        heading.dataset.sectionOverall = JSON.stringify({ total, correct, wrong, unattended });
    }
    function buildSectionStats() {
        if (!/\/problemset\/list\/?/.test(location.pathname))
            return;
        const sections = findSections();
        sections.forEach(section => {
            updateSectionHeading(section);
            if (section.list)
                addSectionToggle(section);
            if (!section.list)
                return;
            const tasks = Array.from(section.list.querySelectorAll('li.task'));
            tasks.forEach(li => {
                const c = classifyTask(li);
                if (c.pending && c.problemId) {
                    sectionQueue.push(async () => {
                        await getSubmissionMeta(c.problemId);
                        updateSectionHeading(section);
                        updateFilteredSectionStats();
                    });
                }
            });
        });
        updateFilteredSectionStats();
    }
    function updateFilteredSectionStats() {
        if (!/\/problemset\/list\/?/.test(location.pathname))
            return;
        const sections = findSections();
        sections.forEach(section => {
            const { heading, list } = section;
            if (!list)
                return;
            const tasks = Array.from(list.querySelectorAll('li.task'));
            let filteredSolved = 0, filteredWrong = 0, filteredUnatt = 0;
            tasks.forEach(li => {
                var _a;
                const icon = li.querySelector('span.task-score.icon');
                if (!icon)
                    return;
                const originallySolved = icon.dataset.originalSolved === '1' || icon.classList.contains('full');
                const isSolvedNow = icon.classList.contains('full');
                const wrongImmediate = icon.classList.contains('zero');
                const link = li.querySelector('a[href*="/problemset/task/"]');
                const problemId = link ? (_a = (link.getAttribute('href') || '').match(/(\d+)/)) === null || _a === void 0 ? void 0 : _a[1] : null;
                let attempted = false;
                if (wrongImmediate)
                    attempted = true;
                else if (problemId) {
                    const cacheVal = localStorage.getItem(CACHE_PREFIX + problemId);
                    if (cacheVal && cacheVal !== 'NONE')
                        attempted = true;
                }
                if (isSolvedNow && originallySolved)
                    filteredSolved++;
                else if (attempted && !originallySolved)
                    filteredWrong++;
                else
                    filteredUnatt++;
            });
            if (!tasks.length)
                return;
            let filteredBadge = heading.querySelector(':scope > .cses-section-stats-filter');
            if (!filteredBadge) {
                filteredBadge = document.createElement('span');
                filteredBadge.className = 'cses-section-stats-filter';
                filteredBadge.style.cssText = 'margin-left:8px;font-weight:normal;font-size:0.7em;color:#5aa;';
                heading.appendChild(filteredBadge);
            }
            filteredBadge.innerHTML = `[filtered <span style="color:#3c9b3c;">${filteredSolved}</span> / <span style="color:#d28b26;">${filteredWrong}</span> / <span style="color:#777;">${filteredUnatt}</span>]`;
            filteredBadge.title = 'Filtered (date threshold): solved-after-threshold / wrong (attempted unsolved) / old-or-unattended';
            heading.dataset.sectionFiltered = JSON.stringify({ solved: filteredSolved, wrong: filteredWrong, unattended: filteredUnatt });
        });
        let aggTotal = 0, aggSolved = 0, aggWrong = 0, aggUnatt = 0, aggFilteredSolved = 0, aggFilteredWrong = 0, aggFilteredUnatt = 0;
        sections.forEach(section => {
            const { heading, list } = section;
            if (!list)
                return;
            const included = heading.dataset.excluded !== '1';
            if (!included)
                return;
            let overall = null;
            try {
                overall = JSON.parse(heading.dataset.sectionOverall || 'null');
            }
            catch { }
            if (!overall) {
                const tasks = Array.from(list.querySelectorAll('li.task'));
                let total = tasks.length, correct = 0, wrong = 0, unattended = 0;
                tasks.forEach(li => {
                    const c = classifyTask(li);
                    if (c.solved)
                        correct++;
                    else if (c.attempted)
                        wrong++;
                    else
                        unattended++;
                });
                overall = { total, correct, wrong, unattended };
            }
            aggTotal += (overall.total || 0);
            aggSolved += (overall.correct || 0);
            aggWrong += (overall.wrong || 0);
            aggUnatt += (overall.unattended || 0);
            let filt = null;
            try {
                filt = JSON.parse(heading.dataset.sectionFiltered || 'null');
            }
            catch { }
            if (!filt) {
                const tasks = Array.from(list.querySelectorAll('li.task'));
                let fs = 0, fw = 0, fu = 0;
                tasks.forEach(li => {
                    var _a;
                    const icon = li.querySelector('span.task-score.icon');
                    if (!icon)
                        return;
                    const originallySolved = icon.dataset.originalSolved === '1' || icon.classList.contains('full');
                    const isSolvedNow = icon.classList.contains('full');
                    const wrongImmediate = icon.classList.contains('zero');
                    const link = li.querySelector('a[href*="/problemset/task/"]');
                    const problemId = link ? (_a = (link.getAttribute('href') || '').match(/(\d+)/)) === null || _a === void 0 ? void 0 : _a[1] : null;
                    let attempted = false;
                    if (wrongImmediate)
                        attempted = true;
                    else if (problemId) {
                        const cacheVal = localStorage.getItem(CACHE_PREFIX + problemId);
                        if (cacheVal && cacheVal !== 'NONE')
                            attempted = true;
                    }
                    if (isSolvedNow && originallySolved)
                        fs++;
                    else if (attempted && !originallySolved)
                        fw++;
                    else
                        fu++;
                });
                filt = { solved: fs, wrong: fw, unattended: fu };
            }
            aggFilteredSolved += (filt.solved || 0);
            aggFilteredWrong += (filt.wrong || 0);
            aggFilteredUnatt += (filt.unattended || 0);
        });
        const general = sections.find(s => getHeadingTitle(s.heading).startsWith('general'));
        if (general) {
            const h = general.heading;
            let overallBadge = h.querySelector(':scope > .cses-section-stats');
            if (!overallBadge) {
                overallBadge = document.createElement('span');
                overallBadge.className = 'cses-section-stats';
                overallBadge.style.cssText = 'margin-right:6px;font-weight:normal;font-size:0.75em;color:#888;';
                h.prepend(overallBadge);
            }
            overallBadge.innerHTML = `[<span style="color:#ccc;">${aggTotal}</span> / <span style="color:#3c9b3c;">${aggSolved}</span> / <span style=\"color:#d28b26;\">${aggWrong}</span> / <span style=\"color:#777;\">${aggUnatt}</span>] `;
            overallBadge.title = 'Overall totals across all included sections: total / solved / wrong / unattended';
            let filteredBadge = h.querySelector(':scope > .cses-section-stats-filter');
            if (!filteredBadge) {
                filteredBadge = document.createElement('span');
                filteredBadge.className = 'cses-section-stats-filter';
                filteredBadge.style.cssText = 'margin-left:8px;font-weight:normal;font-size:0.7em;color:#5aa;';
                h.appendChild(filteredBadge);
            }
            filteredBadge.innerHTML = `[filtered <span style=\"color:#3c9b3c;\">${aggFilteredSolved}</span> / <span style=\"color:#d28b26;\">${aggFilteredWrong}</span> / <span style=\"color:#777;\">${aggFilteredUnatt}</span>]`;
            filteredBadge.title = 'Aggregate filtered counts (date threshold) across included sections';
        }
    }
    function addSectionToggle(section) {
        const { heading, list } = section;
        if (!list)
            return;
        const title = getHeadingTitle(heading);
        if (title.startsWith('general'))
            return;
        const excludedSet = loadExcludedSections();
        let btn = heading.querySelector(':scope > .cses-section-toggle');
        if (!btn) {
            btn = document.createElement('button');
            btn.className = 'cses-section-toggle';
            btn.type = 'button';
            btn.style.marginLeft = '8px';
            btn.style.fontSize = '11px';
            btn.style.padding = '1px 6px';
            btn.style.borderRadius = '4px';
            btn.style.cursor = 'pointer';
            const dark = isDarkTheme();
            btn.style.border = '1px solid ' + (dark ? '#555' : '#bbb');
            btn.style.background = dark ? '#2a2a2a' : '#f5f5f5';
            btn.style.color = dark ? '#ddd' : '#222';
            heading.appendChild(btn);
        }
        const key = title;
        if (excludedSet.has(key)) {
            heading.dataset.excluded = '1';
            list.style.display = 'none';
        }
        const updateBtnUI = () => {
            const excluded = heading.dataset.excluded === '1';
            btn.textContent = excluded ? '🙈' : '👁';
            btn.title = excluded ? 'Include Section' : 'Exclude Section';
            btn.setAttribute('aria-label', btn.title);
        };
        updateBtnUI();
        btn.onclick = () => {
            const excluded = heading.dataset.excluded === '1';
            if (excluded) {
                delete heading.dataset.excluded;
                list.style.display = '';
                excludedSet.delete(key);
            }
            else {
                heading.dataset.excluded = '1';
                list.style.display = 'none';
                excludedSet.add(key);
            }
            saveExcludedSections(excludedSet);
            updateBtnUI();
            updateFilteredSectionStats();
        };
    }
    function applyFilter(forceRefetch = false) {
        const threshold = getThresholdDate();
        if (!threshold)
            return;
        const todayMidnight = new Date();
        todayMidnight.setHours(0, 0, 0, 0);
        const solvedIcons = collectSolved();
        if (!solvedIcons.length) {
            setStatus('No solved problems detected.');
            return;
        }
        solvedIcons.forEach(icon => { if (!icon.classList.contains('full'))
            icon.classList.add('full'); });
        const queue = new TaskQueue(3);
        let processed = 0, total = solvedIcons.length, fetched = 0;
        const thresholdIso = threshold.toISOString();
        console.log('[CSES Filter] Threshold:', thresholdIso, 'Solved icons:', total);
        solvedIcons.forEach((icon, i) => {
            console.log(`[CSES Filter][DISCOVER] #${i} classes=`, icon.className, 'outerHTML=', icon.outerHTML);
        });
        solvedIcons.forEach(icon => {
            const problemId = extractProblemId(icon);
            if (!problemId) {
                console.warn('[CSES Filter][NO-ID] Could not find problem id for icon', icon);
                return;
            }
            const title = extractProblemTitle(icon);
            icon.setAttribute('data-problem-id', problemId);
            icon.setAttribute('data-problem-title', title);
            if (forceRefetch)
                localStorage.removeItem(CACHE_PREFIX + problemId);
            queue.push(async () => {
                const fetchStart = performance.now();
                console.log(`[CSES Filter][FETCH START] ${title} id=${problemId}`);
                const date = await getLastSubmissionDate(problemId);
                const fetchDur = (performance.now() - fetchStart).toFixed(0);
                if (date) {
                    fetched++;
                    if (threshold.getTime() === todayMidnight.getTime()) {
                        if (!icon.classList.contains('full'))
                            icon.classList.add('full');
                        icon.title = `Last submission ${date.toLocaleString()}`;
                        console.log(`[CSES Filter][DECISION] ${title} (id ${problemId}) last=${date.toISOString()} -> keep (threshold is today) fetch=${fetchDur}ms`);
                    }
                    else if (date < threshold) {
                        if (icon.classList.contains('full'))
                            icon.classList.remove('full');
                        icon.title = `Hidden: last submission ${date.toLocaleString()}`;
                        console.log(`[CSES Filter][DECISION] ${title} (id ${problemId}) last=${date.toISOString()} threshold=${thresholdIso} -> HIDE fetch=${fetchDur}ms`);
                    }
                    else {
                        if (!icon.classList.contains('full'))
                            icon.classList.add('full');
                        icon.title = `Last submission ${date.toLocaleString()}`;
                        console.log(`[CSES Filter][DECISION] ${title} (id ${problemId}) last=${date.toISOString()} threshold=${thresholdIso} -> keep fetch=${fetchDur}ms`);
                    }
                }
                else {
                    console.warn(`[CSES Filter][ERROR] ${title} (id ${problemId}) failed to fetch/parse date.`);
                }
                processed++;
                if (processed % 5 === 0 || processed === total) {
                    setStatus(`Processed ${processed}/${total} solved problems (fetched ${fetched}). Threshold ${toISODate(threshold)}.`);
                    updateFilteredSectionStats();
                }
            });
        });
        setStatus(`Queued ${solvedIcons.length} solved problems...`);
        console.log('[CSES Filter] Queue filled. Beginning async fetches.');
        updateFilteredSectionStats();
    }
    function enhanceResultCopyButtons() {
        const actions = Array.from(document.querySelectorAll('div.samp-actions'));
        if (!actions.length)
            return;
        actions.forEach(act => {
            if (act.getAttribute('data-copy-enhanced') === '1')
                return;
            const view = act.querySelector('a.view');
            const save = act.querySelector('a.save');
            const href = view ? view.getAttribute('href') : (save ? save.getAttribute('href') : null);
            if (!href)
                return;
            const btn = document.createElement('button');
            btn.type = 'button';
            btn.className = 'cses-copy-btn';
            btn.textContent = 'Copy';
            btn.title = 'Copy full data to clipboard';
            const dark = isDarkTheme();
            Object.assign(btn.style, {
                display: 'inline-flex', alignItems: 'center', gap: '4px',
                border: '1px solid ' + (dark ? '#555' : '#bbb'),
                background: dark ? '#2a2a2a' : '#f5f5f5',
                color: dark ? '#ddd' : '#222',
                padding: '1px 6px', fontSize: '11px', borderRadius: '4px', cursor: 'pointer'
            });
            const url = new URL(href, location.origin).toString();
            let resetTimer = null;
            btn.addEventListener('click', async () => {
                if (btn.disabled)
                    return;
                const prev = btn.innerHTML;
                btn.disabled = true;
                btn.innerHTML = '<i class="fas fa-spinner fa-spin"></i> Copying';
                try {
                    const content = await fetchViewContent(url);
                    await navigator.clipboard.writeText(content);
                    btn.innerHTML = '<i class="fas fa-check"></i> Copied';
                    btn.style.borderColor = '#3c9b3c';
                    btn.style.color = isDarkTheme() ? '#bde5bd' : '#2a6f2a';
                }
                catch (e) {
                    console.error('[CSES Copy] Failed to copy', e);
                    btn.innerHTML = '<i class="fas fa-exclamation-triangle"></i> Failed';
                    btn.style.borderColor = '#b55';
                    btn.style.color = isDarkTheme() ? '#ffbdbd' : '#7a1f1f';
                }
                finally {
                    if (resetTimer)
                        window.clearTimeout(resetTimer);
                    resetTimer = window.setTimeout(() => { btn.disabled = false; btn.innerHTML = prev; }, 1400);
                }
            });
            act.appendChild(document.createTextNode(' '));
            act.appendChild(btn);
            act.setAttribute('data-copy-enhanced', '1');
        });
    }
    if (document.readyState === 'loading') {
        document.addEventListener('DOMContentLoaded', init);
    }
    else {
        init();
    }
})();