AtCoder Standings Filtering by Group

お気に入りのみ表示がオンの時に、事前定義したユーザーグループで順位表をフィルタリングする機能を追加します。当該コンテストに参加していないお気に入りユーザーを非表示にする機能も含んでいます。

このスクリプトの質問や評価の投稿はこちら通報はこちらへお寄せください
// ==UserScript==
// @name            AtCoder Standings Filtering by Group
// @name:en         AtCoder Standings Filtering by Group
// @namespace       http://tampermonkey.net/
// @version         1.1.0
// @description     お気に入りのみ表示がオンの時に、事前定義したユーザーグループで順位表をフィルタリングする機能を追加します。当該コンテストに参加していないお気に入りユーザーを非表示にする機能も含んでいます。
// @description:en  Filter AtCoder standings by predefined user groups when "Favorites Only" is enabled. Supports unregistered favorites hiding.
// @author          sorachandu
// @match           https://atcoder.jp/contests/*/standings*
// @match           https://atcoder.jp/settings/fav
// @exclude         https://atcoder.jp/contests/*/standings/json
// @grant           none
// @license         MIT
// ==/UserScript==

// 注: 当Scriptはほぼ全て GitHub Copilot Agent mode Claude Sonnet 4 に書いてもらっています.

/*
 * This script includes code derived from "AtCoder Standings Excluding Unrated User" by HalsSC
 * Original source: https://greasyfork.org/ja/scripts/472242-atcoder-standings-excluding-unrated-user
 * Licensed under MIT License
 * Copyright (c) HalsSC
 * 
 * Modifications made to work with filtered standings by checking the number of
 * td.standings-result elements instead of checking td.standings-rank content.
 */

(function() {
    'use strict';

    // グループデータを管理するためのローカルストレージキー
    const GROUPS_STORAGE_KEY = 'atcoder_user_groups';
    const GROUP_ORDER_STORAGE_KEY = 'atcoder_group_order';
    const FILTER_STATE_STORAGE_KEY = 'atcoder_filter_state';
    
    // 未登録ユーザー非表示の遅延時間(ミリ秒)
    const HIDE_UNREGISTERED_DELAY = 100;
    
    // 現在編集中のグループデータ
    let currentEditingGroup = null;
    let originalEditingGroup = null; // 元のグループデータを保持
    let isEditMode = false;
    let saveCompleted = false; // 保存完了フラグ
    
    // 元の統計情報を保持
    let originalFastestSolvers = null;
    let originalSolverCounts = null;
    
    // グループデータの初期化
    function initializeGroups() {
        const groups = localStorage.getItem(GROUPS_STORAGE_KEY);
        if (!groups) {
            localStorage.setItem(GROUPS_STORAGE_KEY, JSON.stringify({}));
            return {};
        }
        return JSON.parse(groups);
    }

    // グループデータの保存
    function saveGroups(groups) {
        localStorage.setItem(GROUPS_STORAGE_KEY, JSON.stringify(groups));
    }

    // グループデータの取得
    function getGroups() {
        return JSON.parse(localStorage.getItem(GROUPS_STORAGE_KEY) || '{}');
    }

    // グループ順序の保存
    function saveGroupOrder(order) {
        localStorage.setItem(GROUP_ORDER_STORAGE_KEY, JSON.stringify(order));
    }

    // グループ順序の取得
    function getGroupOrder() {
        const order = localStorage.getItem(GROUP_ORDER_STORAGE_KEY);
        if (!order) {
            const groups = getGroups();
            const defaultOrder = Object.keys(groups);
            saveGroupOrder(defaultOrder);
            return defaultOrder;
        }
        return JSON.parse(order);
    }

    // フィルター状態の保存
    function saveFilterState(state) {
        localStorage.setItem(FILTER_STATE_STORAGE_KEY, JSON.stringify(state));
    }

    // フィルター状態の取得
    function getFilterState() {
        const state = localStorage.getItem(FILTER_STATE_STORAGE_KEY);
        if (!state) {
            return {
                noneChecked: true,
                selectedGroups: [],
                operation: 'OR',
                hideUnregistered: true
            };
        }
        
        const filterState = JSON.parse(state);
        
        // 現在存在するグループのみに絞り込む
        const currentGroups = getGroups();
        const existingGroupNames = Object.keys(currentGroups);
        const originalSelectedGroups = filterState.selectedGroups || [];
        const validSelectedGroups = originalSelectedGroups.filter(groupName => 
            existingGroupNames.includes(groupName)
        );
        
        // 削除されたグループがあった場合の処理
        if (originalSelectedGroups.length !== validSelectedGroups.length) {
            // 有効なグループがなくなった場合はNoneに戻す
            if (validSelectedGroups.length === 0 && originalSelectedGroups.length > 0) {
                filterState.noneChecked = true;
                filterState.selectedGroups = [];
            } else {
                filterState.selectedGroups = validSelectedGroups;
            }
            
            // 更新された状態を保存
            saveFilterState(filterState);
        }
        
        // hideUnregisteredのデフォルト値を追加
        if (typeof filterState.hideUnregistered === 'undefined') {
            filterState.hideUnregistered = true;
        }
        
        return filterState;
    }

    // お気に入りユーザー一覧を取得
    function getFavoriteUsers() {
        // AtCoderのページから var favs を取得
        if (typeof window.favs !== 'undefined') {
            return window.favs;
        }
        
        // スクリプトタグから取得を試みる
        const scripts = document.querySelectorAll('script');
        for (let script of scripts) {
            const content = script.textContent;
            const match = content.match(/var\s+favs\s*=\s*(\[.*?\]);/s);
            if (match) {
                try {
                    return JSON.parse(match[1]);
                } catch (e) {
                    console.error('お気に入りユーザーのパースに失敗:', e);
                }
            }
        }
        
        console.warn('お気に入りユーザー一覧が見つかりません');
        return [];
    }

    /*
     * The following function hideUnregisteredUsers is based on code from
     * "AtCoder Standings Excluding Unrated User" by HalsSC
     * Original source: https://greasyfork.org/ja/scripts/472242-atcoder-standings-excluding-unrated-user
     * Licensed under MIT License
     * Copyright (c) HalsSC
     * 
     * Uses the original logic of checking td.standings-rank content for "-"
     * to identify unregistered users.
     */
    
    // 順位表の中で参加登録していないユーザの行を非表示にする関数
    function hideUnregisteredUsers() {
        setTimeout(function() {
            const userRows = getUserRows();
            
            userRows.forEach(function(row) {
                // standings-rank要素をチェック
                const rankCell = row.querySelector('td.standings-rank');
                if (!rankCell) return;
                
                const rankText = rankCell.textContent.trim();
                
                // 順位が"-"の場合は未登録ユーザー
                if (rankText === "-") {
                    // FAに垢消しが含まれるとFA欄まで消えちゃう対策
                    if (row.className !== "standings-fa") {
                        row.hidden = true;
                    }
                }
            });
        }, HIDE_UNREGISTERED_DELAY);
    }

    // 未登録ユーザーの非表示を解除する関数
    function showUnregisteredUsers() {
        const hiddenRows = document.querySelectorAll("tr[hidden]");
        hiddenRows.forEach(function(row) {
            row.hidden = false;
        });
    }

    // FA行をスキップしてユーザー行のみを取得する共通関数
    function getUserRows(includeHidden = true) {
        const standingsTable = document.querySelector('#standings-tbody, .table tbody');
        if (!standingsTable) return [];
        
        const rows = standingsTable.querySelectorAll('tr');
        const userRows = [];
        
        rows.forEach(row => {
            // FA行(最速正解者行)をスキップ
            if (row.classList.contains('standings-fa')) return;
            
            // ユーザー行かどうかをチェック(リンクがあるかで判定)
            const userCell = row.querySelector('td a[href*="/users/"]');
            if (userCell && (includeHidden || !row.hidden)) {
                userRows.push(row);
            }
        });
        
        return userRows;
    }

    // お気に入り順位表を表示する共通関数
    function showFavoriteStandings() {
        const userRows = getUserRows();
        const visibleRows = [];
        
        userRows.forEach(row => {
            row.style.display = '';
            visibleRows.push(row);
        });
        
        // お気に入り順位表の統計を再計算
        updateFilteredStandings(visibleRows);
        
        return visibleRows;
    }

    // モーダルのスタイルを追加
    function addModalStyles() {
        const style = document.createElement('style');
        style.textContent = `
            .group-filter-modal {
                position: fixed;
                top: 0;
                left: 0;
                width: 100vw;
                height: 100vh;
                background-color: rgba(0, 0, 0, 0.5);
                display: none;
                z-index: 10000;
                animation: fadeIn 0.3s;
            }
            
            .group-filter-modal.show {
                display: flex !important;
                align-items: center !important;
                justify-content: center !important;
            }
            
            .group-filter-modal-content {
                background-color: white;
                padding: 20px;
                border-radius: 8px;
                width: 400px;
                max-width: 90vw;
                max-height: 80vh;
                overflow-y: auto;
                box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
                position: relative;
                margin: auto;
            }
            
            .group-filter-header {
                font-size: 18px;
                font-weight: bold;
                margin-bottom: 20px;
                text-align: center;
                color: #333;
            }
            
            .group-filter-option {
                margin: 10px 0;
                display: flex;
                align-items: center;
            }
            
            .group-filter-option input[type="checkbox"] {
                margin-right: 8px;
            }
            
            .group-filter-operation {
                margin: 20px 0;
                text-align: center;
            }
            
            .group-filter-operation input[type="radio"] {
                margin: 0 5px;
            }
            
            .group-filter-buttons {
                text-align: center;
                margin-top: 20px;
            }
            
            .group-filter-button {
                padding: 8px 16px;
                margin: 5px;
                border: 1px solid #ccc;
                border-radius: 4px;
                background-color: #f8f9fa;
                cursor: pointer;
                text-decoration: none;
                display: inline-block;
            }
            
            .group-filter-button:hover {
                background-color: #e9ecef;
            }
            
            .filtering-by-group-btn {
                margin-left: 10px;
                padding: 5px 10px;
                background-color: #007bff;
                color: white;
                border: none;
                border-radius: 4px;
                cursor: pointer;
                font-size: 14px;
            }
            
            .filtering-by-group-btn:hover {
                background-color: #0056b3;
            }
            
            .filtering-by-group-btn:disabled {
                background-color: #6c757d;
                cursor: not-allowed;
            }
            
            .clear-filtering-btn {
                margin-left: 10px;
                padding: 5px 10px;
                background-color: #dc3545;
                color: white;
                border: none;
                border-radius: 4px;
                cursor: pointer;
                font-size: 14px;
            }
            
            .clear-filtering-btn:hover {
                background-color: #c82333;
            }
            
            .clear-filtering-btn:disabled {
                background-color: #6c757d;
                cursor: not-allowed;
            }
            
            @keyframes fadeIn {
                from { opacity: 0; }
                to { opacity: 1; }
            }
            
            /* Group Management Modal Styles */
            .group-manage-modal {
                position: fixed;
                top: 0;
                left: 0;
                width: 100vw;
                height: 100vh;
                background-color: rgba(0, 0, 0, 0.5);
                display: none;
                z-index: 10000;
                animation: fadeIn 0.3s;
            }
            
            .group-manage-modal.show {
                display: flex !important;
                align-items: center !important;
                justify-content: center !important;
            }
            
            .group-manage-modal-content {
                background-color: white;
                padding: 20px;
                border-radius: 8px;
                width: 600px;
                max-width: 95vw;
                max-height: 90vh;
                overflow-y: auto;
                box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
                position: relative;
            }
            
            .group-manage-header {
                font-size: 18px;
                font-weight: bold;
                margin-bottom: 20px;
                text-align: center;
                color: #333;
            }
            
            .group-list {
                margin: 20px 0;
            }
            
            .group-item {
                display: flex;
                align-items: center;
                padding: 10px;
                border: 1px solid #e0e0e0;
                margin-bottom: 5px;
                border-radius: 4px;
                background-color: #f9f9f9;
                transition: background-color 0.2s;
            }
            
            .group-item.dragging {
                opacity: 0.5;
                background-color: #e9ecef;
            }
            
            .group-item.drag-over {
                background-color: #d1ecf1;
                border-color: #bee5eb;
            }
            
            .group-name {
                flex: 1;
                font-weight: bold;
            }
            
            .group-actions {
                display: flex;
                gap: 5px;
            }
            
            .group-action-btn {
                padding: 5px 10px;
                border: 1px solid #ccc;
                border-radius: 3px;
                cursor: pointer;
                font-size: 12px;
            }
            
            .group-edit-btn {
                background-color: #007bff;
                color: white;
            }
            
            .group-delete-btn {
                background-color: #dc3545;
                color: white;
            }
            
            .group-drag-btn {
                background-color: #6c757d;
                color: white;
                cursor: move;
            }
            
            .create-group-btn {
                width: 100%;
                padding: 10px;
                background-color: #28a745;
                color: white;
                border: none;
                border-radius: 4px;
                cursor: pointer;
                font-size: 16px;
                margin-bottom: 20px;
            }
            
            .manage-group-btn {
                margin-left: 10px;
                padding: 8px 16px;
                background-color: #17a2b8;
                color: white;
                border: none;
                border-radius: 4px;
                cursor: pointer;
                font-size: 14px;
            }
            
            /* Edit Group Modal Styles */
            .edit-group-modal {
                position: fixed;
                top: 0;
                left: 0;
                width: 100vw;
                height: 100vh;
                background-color: rgba(0, 0, 0, 0.7);
                display: none;
                z-index: 11000;
                animation: fadeIn 0.3s;
            }
            
            .edit-group-modal.show {
                display: flex !important;
                align-items: center !important;
                justify-content: center !important;
            }
            
            .edit-group-modal-content {
                background-color: white;
                padding: 20px;
                border-radius: 8px;
                width: 800px;
                max-width: 95vw;
                max-height: 90vh;
                overflow-y: auto;
                box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
                position: relative;
            }
            
            .edit-group-header {
                font-size: 18px;
                font-weight: bold;
                margin-bottom: 20px;
                text-align: center;
                color: #333;
            }
            
            .edit-group-cols {
                display: flex;
                gap: 20px;
                margin: 20px 0;
            }
            
            .edit-group-col {
                flex: 1;
                border: 1px solid #e0e0e0;
                border-radius: 4px;
                padding: 10px;
                max-height: 400px;
                overflow-y: auto;
            }
            
            .edit-group-col-header {
                font-weight: bold;
                margin-bottom: 10px;
                text-align: center;
                padding-bottom: 5px;
                border-bottom: 1px solid #e0e0e0;
            }
            
            .user-search-input {
                width: 100%;
                padding: 8px;
                border: 1px solid #ccc;
                border-radius: 4px;
                margin-bottom: 10px;
                font-size: 12px;
                box-sizing: border-box;
            }
            
            .user-search-input:focus {
                outline: none;
                border-color: #007bff;
                box-shadow: 0 0 3px rgba(0, 123, 255, 0.25);
            }
            
            .user-item {
                display: flex;
                align-items: center;
                padding: 5px;
                margin-bottom: 3px;
                border-radius: 3px;
            }
            
            .user-item.included {
                background-color: #d4edda;
            }
            
            .user-name {
                flex: 1;
                margin-left: 5px;
            }
            
            .user-action-btn {
                padding: 3px 8px;
                border: 1px solid #ccc;
                border-radius: 3px;
                cursor: pointer;
                font-size: 11px;
            }
            
            .user-add-btn {
                background-color: #007bff;
                color: white;
            }
            
            .user-delete-btn {
                background-color: #dc3545;
                color: white;
            }
            
            .edit-group-buttons {
                display: flex;
                justify-content: center;
                gap: 10px;
                margin-top: 20px;
            }
            
            .edit-group-save-btn {
                padding: 10px 20px;
                background-color: #28a745;
                color: white;
                border: none;
                border-radius: 4px;
                cursor: pointer;
            }
            
            .edit-group-cancel-btn {
                padding: 10px 20px;
                background-color: #6c757d;
                color: white;
                border: none;
                border-radius: 4px;
                cursor: pointer;
            }
            
            /* Group Name Modal Styles */
            .group-name-modal {
                position: fixed;
                top: 0;
                left: 0;
                width: 100vw;
                height: 100vh;
                background-color: rgba(0, 0, 0, 0.8);
                display: none;
                z-index: 12000;
                animation: fadeIn 0.3s;
            }
            
            .group-name-modal.show {
                display: flex !important;
                align-items: center !important;
                justify-content: center !important;
            }
            
            .group-name-modal-content {
                background-color: white;
                padding: 20px;
                border-radius: 8px;
                width: 400px;
                max-width: 90vw;
                box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
                position: relative;
            }
            
            .group-name-header {
                font-size: 16px;
                font-weight: bold;
                margin-bottom: 15px;
                text-align: center;
                color: #333;
            }
            
            .group-name-input {
                width: 100%;
                padding: 10px;
                border: 1px solid #ccc;
                border-radius: 4px;
                margin-bottom: 15px;
                font-size: 14px;
            }
            
            .group-name-ok-btn {
                width: 100%;
                padding: 10px;
                background-color: #007bff;
                color: white;
                border: none;
                border-radius: 4px;
                cursor: pointer;
                font-size: 14px;
            }
        `;
        document.head.appendChild(style);
    }

    // モーダルHTMLを作成
    function createModal() {
        const modal = document.createElement('div');
        modal.className = 'group-filter-modal';
        modal.id = 'groupFilterModal';
        
        modal.innerHTML = `
            <div class="group-filter-modal-content">
                <div class="group-filter-header">Filtering by Group</div>
                <div id="groupFilterOptions">
                    <div class="group-filter-option">
                        <input type="checkbox" id="noneFilter" checked>
                        <label for="noneFilter">None</label>
                    </div>
                </div>
                <div class="group-filter-operation">
                    <label><input type="radio" name="operation" value="OR" checked> OR</label>
                    <label><input type="radio" name="operation" value="AND"> AND</label>
                </div>
                <div class="group-filter-option">
                    <input type="checkbox" id="hideUnregistered">
                    <label for="hideUnregistered">Hide Unregistered User</label>
                </div>
                <div class="group-filter-buttons">
                    <button class="group-filter-button" onclick="closeGroupFilterModal()">Close</button>
                    <a href="https://atcoder.jp/settings/fav" target="_blank" class="group-filter-button">Manage Group</a>
                </div>
            </div>
        `;
        
        document.body.appendChild(modal);
        
        // モーダル外をクリックで閉じる
        modal.addEventListener('click', function(e) {
            if (e.target === modal) {
                closeGroupFilterModal();
            }
        });
        
        return modal;
    }

    // Group管理モーダルを作成
    function createGroupManageModal() {
        const modal = document.createElement('div');
        modal.className = 'group-manage-modal';
        modal.id = 'groupManageModal';
        
        modal.innerHTML = `
            <div class="group-manage-modal-content">
                <div class="group-manage-header">Manage Groups</div>
                <button class="create-group-btn" onclick="openEditGroupModal(false)">Create Group</button>
                <div class="group-list" id="groupList"></div>
                <div class="group-filter-buttons">
                    <button class="group-filter-button" onclick="closeGroupManageModal()">Close</button>
                </div>
            </div>
        `;
        
        document.body.appendChild(modal);
        
        // モーダル外をクリックで閉じる
        modal.addEventListener('click', function(e) {
            if (e.target === modal) {
                closeGroupManageModal();
            }
        });
        
        return modal;
    }

    // Edit Group モーダルを作成
    function createEditGroupModal() {
        const modal = document.createElement('div');
        modal.className = 'edit-group-modal';
        modal.id = 'editGroupModal';
        
        modal.innerHTML = `
            <div class="edit-group-modal-content">
                <div class="edit-group-header" id="editGroupHeader">Create New Group</div>
                <div class="edit-group-cols">
                    <div class="edit-group-col">
                        <div class="edit-group-col-header">Favorite Users</div>
                        <input type="text" class="user-search-input" id="userSearchInput" placeholder="Search users..." autocomplete="off">
                        <div id="favoriteUsersList"></div>
                    </div>
                    <div class="edit-group-col">
                        <div class="edit-group-col-header">Group Members</div>
                        <div id="groupMembersList"></div>
                    </div>
                </div>
                <div class="edit-group-buttons">
                    <button class="edit-group-save-btn" onclick="saveEditGroup()">Save</button>
                    <button class="edit-group-cancel-btn" onclick="closeEditGroupModal()">Cancel</button>
                </div>
            </div>
        `;
        
        document.body.appendChild(modal);
        return modal;
    }

    // Group名入力モーダルを作成
    function createGroupNameModal() {
        const modal = document.createElement('div');
        modal.className = 'group-name-modal';
        modal.id = 'groupNameModal';
        
        modal.innerHTML = `
            <div class="group-name-modal-content">
                <div class="group-name-header">Enter Group Name</div>
                <input type="text" class="group-name-input" id="groupNameInput" placeholder="">
                <button class="group-name-ok-btn" onclick="confirmGroupName()">OK</button>
            </div>
        `;
        
        document.body.appendChild(modal);
        return modal;
    }

    // Clear Filtering機能
    window.clearFiltering = function() {
        // フィルター状態をNoneに設定
        const filterState = {
            noneChecked: true,
            selectedGroups: [],
            operation: 'OR',
            hideUnregistered: true
        };
        saveFilterState(filterState);
        
        // お気に入りのみ表示がオンの場合のみフィルタリングを適用
        const favCheckbox = document.getElementById("checkbox-fav-only");
        if (favCheckbox && favCheckbox.checked) {
            // 元の統計情報を保存
            saveOriginalStatistics();
            
            // お気に入り順位表を表示(統計は再計算)
            showFavoriteStandings();
        } else {
            // お気に入りのみ表示がオフの場合は元の統計情報を復元
            restoreOriginalStatistics();
        }
    };

    // モーダルを開く
    function openGroupFilterModal() {
        const modal = document.getElementById('groupFilterModal');
        if (modal) {
            updateModalContent();
            modal.classList.add('show');
        }
    }

    // モーダルを閉じる(グローバルに定義)
    window.closeGroupFilterModal = function() {
        const modal = document.getElementById('groupFilterModal');
        if (modal) {
            // フィルター状態を保存
            const noneChecked = document.getElementById('noneFilter').checked;
            const selectedGroups = [];
            const groupCheckboxes = modal.querySelectorAll('input[data-group]:checked');
            groupCheckboxes.forEach(cb => {
                selectedGroups.push(cb.dataset.group);
            });
            const operation = modal.querySelector('input[name="operation"]:checked').value;
            const hideUnregistered = document.getElementById('hideUnregistered').checked;
            
            saveFilterState({
                noneChecked: noneChecked,
                selectedGroups: selectedGroups,
                operation: operation,
                hideUnregistered: hideUnregistered
            });
            
            modal.classList.remove('show');
            applyFiltering();
            
            // Clear ボタンの状態を更新
            setTimeout(() => {
                const clearBtn = document.querySelector('.clear-filtering-btn');
                if (clearBtn) {
                    const filterState = getFilterState();
                    const favCheckbox = document.getElementById("checkbox-fav-only");
                    const isFavChecked = favCheckbox && favCheckbox.checked;
                    const isNoneSelected = filterState.noneChecked || filterState.selectedGroups.length === 0;
                    clearBtn.disabled = !isFavChecked || isNoneSelected;
                }
            }, 100);
        }
    };

    // Group管理モーダルを開く
    window.openGroupManageModal = function() {
        const modal = document.getElementById('groupManageModal');
        if (modal) {
            updateGroupList();
            modal.classList.add('show');
        }
    };

    // Group管理モーダルを閉じる
    window.closeGroupManageModal = function() {
        const modal = document.getElementById('groupManageModal');
        if (modal) {
            modal.classList.remove('show');
        }
    };

    // Edit Groupモーダルを開く
    window.openEditGroupModal = function(editMode, groupName = null) {
        isEditMode = editMode;
        const groups = getGroups();
        currentEditingGroup = editMode ? [...groups[groupName]] : [];
        originalEditingGroup = editMode ? [...groups[groupName]] : []; // 元のデータを保持
        saveCompleted = false; // フラグをリセット
        
        const modal = document.getElementById('editGroupModal');
        if (modal) {
            const header = document.getElementById('editGroupHeader');
            header.textContent = editMode ? `Edit Group: ${groupName}` : 'Create New Group';
            
            updateEditGroupContent();
            modal.classList.add('show');
        }
    };

    // Edit Groupモーダルを閉じる
    window.closeEditGroupModal = function() {
        // 保存が完了している場合は確認ダイアログを表示しない
        if (!saveCompleted) {
            // 変更があるかチェック
            const hasChanges = !arraysEqual(currentEditingGroup, originalEditingGroup);
            
            if (hasChanges) {
                if (!confirm('変更が保存されていません。変更を破棄してもよろしいですか?')) {
                    return; // キャンセルされた場合は閉じない
                }
            }
        }
        
        const modal = document.getElementById('editGroupModal');
        if (modal) {
            modal.classList.remove('show');
            currentEditingGroup = null;
            originalEditingGroup = null;
            isEditMode = false;
            saveCompleted = false; // フラグをリセット
        }
    };

    // 配列が等しいかチェックするヘルパー関数
    function arraysEqual(a, b) {
        if (a.length !== b.length) return false;
        const sortedA = [...a].sort();
        const sortedB = [...b].sort();
        return sortedA.every((val, index) => val === sortedB[index]);
    }

    // Edit Groupを保存
    window.saveEditGroup = function() {
        openGroupNameModal();
    };

    // Group名入力モーダルを開く
    function openGroupNameModal() {
        const modal = document.getElementById('groupNameModal');
        const input = document.getElementById('groupNameInput');
        
        if (isEditMode) {
            const header = document.getElementById('editGroupHeader').textContent;
            const currentName = header.replace('Edit Group: ', '');
            input.placeholder = currentName;
            input.value = '';
        } else {
            input.placeholder = '';
            input.value = '';
        }
        
        modal.classList.add('show');
        input.focus();
    }

    // Group名を確認
    window.confirmGroupName = function() {
        const input = document.getElementById('groupNameInput');
        let groupName = input.value.trim();
        
        // 空白の場合はplaceholderを使用
        if (!groupName && isEditMode) {
            groupName = input.placeholder;
        }
        
        // バリデーション
        if (!groupName) {
            alert('Group name cannot be empty. Please enter a valid name.');
            return;
        }
        
        const groups = getGroups();
        const existingNames = Object.keys(groups);
        
        // 編集モードでない場合、または名前が変更された場合の重複チェック
        if (!isEditMode || (isEditMode && input.placeholder !== groupName)) {
            if (existingNames.includes(groupName)) {
                alert(`Group name "${groupName}" already exists. Please choose a different name.`);
                return;
            }
        }
        
        // グループを保存
        const newGroups = {...groups};
        
        if (isEditMode) {
            const oldName = input.placeholder;
            delete newGroups[oldName];
            
            // 順序も更新
            const order = getGroupOrder();
            const index = order.indexOf(oldName);
            if (index !== -1) {
                order[index] = groupName;
                saveGroupOrder(order);
            }
            
            // 保存されたフィルター状態のグループ名も更新
            const filterState = getFilterState();
            const selectedIndex = filterState.selectedGroups.indexOf(oldName);
            if (selectedIndex !== -1) {
                filterState.selectedGroups[selectedIndex] = groupName;
                saveFilterState(filterState);
            }
        } else {
            // 新規作成の場合は順序の最後に追加
            const order = getGroupOrder();
            order.push(groupName);
            saveGroupOrder(order);
        }
        
        newGroups[groupName] = currentEditingGroup || [];
        saveGroups(newGroups);
        
        // 保存完了フラグを設定
        saveCompleted = true;
        
        // モーダルを閉じる
        document.getElementById('groupNameModal').classList.remove('show');
        closeEditGroupModal();
        
        // Group管理モーダルを更新
        updateGroupList();
    };

    // Group一覧を更新
    function updateGroupList() {
        const groups = getGroups();
        const order = getGroupOrder();
        const container = document.getElementById('groupList');
        
        if (!container) return;
        
        container.innerHTML = '';
        
        // 順序に従ってグループを表示
        order.forEach((groupName, index) => {
            if (groups[groupName]) {
                const groupItem = document.createElement('div');
                groupItem.className = 'group-item';
                groupItem.draggable = true;
                groupItem.dataset.groupName = groupName;
                groupItem.dataset.index = index;
                groupItem.innerHTML = `
                    <div class="group-name">${groupName} (${groups[groupName].length} users)</div>
                    <div class="group-actions">
                        <button class="group-action-btn group-edit-btn" onclick="openEditGroupModal(true, '${groupName}')">Edit</button>
                        <button class="group-action-btn group-delete-btn" onclick="deleteGroup('${groupName}')">Delete</button>
                        <button class="group-action-btn group-drag-btn" title="Drag to reorder">⋮⋮</button>
                    </div>
                `;
                
                // ドラッグイベントを追加
                addDragEvents(groupItem);
                
                container.appendChild(groupItem);
            }
        });
    }

    // ドラッグイベントを追加
    function addDragEvents(groupItem) {
        const dragBtn = groupItem.querySelector('.group-drag-btn');
        
        // ドラッグボタンのマウスダウンで親要素をドラッグ可能にする
        dragBtn.addEventListener('mousedown', function(e) {
            groupItem.draggable = true;
        });
        
        // ドラッグボタン以外の場所ではドラッグを無効にする
        groupItem.addEventListener('mousedown', function(e) {
            if (!e.target.classList.contains('group-drag-btn')) {
                groupItem.draggable = false;
            }
        });
        
        groupItem.addEventListener('dragstart', function(e) {
            if (!e.target.classList.contains('group-drag-btn') && !groupItem.draggable) {
                e.preventDefault();
                return;
            }
            
            this.classList.add('dragging');
            e.dataTransfer.effectAllowed = 'move';
            e.dataTransfer.setData('text/html', this.outerHTML);
            e.dataTransfer.setData('text/plain', this.dataset.groupName);
        });
        
        groupItem.addEventListener('dragend', function(e) {
            this.classList.remove('dragging');
        });
        
        groupItem.addEventListener('dragover', function(e) {
            e.preventDefault();
            e.dataTransfer.dropEffect = 'move';
            this.classList.add('drag-over');
        });
        
        groupItem.addEventListener('dragleave', function(e) {
            this.classList.remove('drag-over');
        });
        
        groupItem.addEventListener('drop', function(e) {
            e.preventDefault();
            this.classList.remove('drag-over');
            
            const draggedGroupName = e.dataTransfer.getData('text/plain');
            const targetGroupName = this.dataset.groupName;
            
            if (draggedGroupName !== targetGroupName) {
                reorderGroups(draggedGroupName, targetGroupName);
            }
        });
    }

    // グループの順序を変更
    function reorderGroups(draggedGroupName, targetGroupName) {
        const order = getGroupOrder();
        const draggedIndex = order.indexOf(draggedGroupName);
        const targetIndex = order.indexOf(targetGroupName);
        
        if (draggedIndex === -1 || targetIndex === -1) return;
        
        // 配列から削除して新しい位置に挿入
        order.splice(draggedIndex, 1);
        order.splice(targetIndex, 0, draggedGroupName);
        
        // 順序を保存
        saveGroupOrder(order);
        
        // 表示を更新
        updateGroupList();
    }

    // Edit Group内容を更新
    function updateEditGroupContent() {
        const favoriteUsers = getFavoriteUsers();
        const groupMembers = currentEditingGroup || [];
        
        // 検索ボックスのイベントリスナーを設定
        setupUserSearch();
        
        // お気に入りユーザーリストを更新
        updateFavoriteUsersList(favoriteUsers, groupMembers);
        
        // グループメンバーリストを更新
        updateGroupMembersList(groupMembers);
    }

    // 検索機能のセットアップ
    function setupUserSearch() {
        const searchInput = document.getElementById('userSearchInput');
        if (!searchInput) return;
        
        // 既存のイベントリスナーを削除(重複を防ぐため)
        searchInput.removeEventListener('input', handleUserSearch);
        
        // 新しいイベントリスナーを追加
        searchInput.addEventListener('input', handleUserSearch);
        
        // 初期状態では検索文字列をクリア
        searchInput.value = '';
    }

    // 検索処理
    function handleUserSearch() {
        const searchInput = document.getElementById('userSearchInput');
        const searchTerm = searchInput.value.toLowerCase().trim();
        const favoriteUsers = getFavoriteUsers();
        const groupMembers = currentEditingGroup || [];
        
        // 検索結果をフィルタリング
        const filteredUsers = favoriteUsers.filter(user => 
            user.toLowerCase().startsWith(searchTerm)
        );
        
        // フィルタされたリストを表示
        updateFavoriteUsersList(filteredUsers, groupMembers);
    }

    // お気に入りユーザーリストを更新
    function updateFavoriteUsersList(userList, groupMembers) {
        const favContainer = document.getElementById('favoriteUsersList');
        if (!favContainer) return;
        
        favContainer.innerHTML = '';
        userList.forEach(user => {
            const isIncluded = groupMembers.includes(user);
            const userItem = document.createElement('div');
            userItem.className = `user-item ${isIncluded ? 'included' : ''}`;
            userItem.innerHTML = `
                <div class="user-name">${user}</div>
                <button class="user-action-btn user-add-btn" onclick="addUserToGroup('${user}')" ${isIncluded ? 'disabled' : ''}>Add</button>
            `;
            favContainer.appendChild(userItem);
        });
    }

    // グループメンバーリストを更新
    function updateGroupMembersList(groupMembers) {
        const membersContainer = document.getElementById('groupMembersList');
        if (!membersContainer) return;
        
        membersContainer.innerHTML = '';
        groupMembers.forEach(user => {
            const userItem = document.createElement('div');
            userItem.className = 'user-item';
            userItem.innerHTML = `
                <div class="user-name">${user}</div>
                <button class="user-action-btn user-delete-btn" onclick="removeUserFromGroup('${user}')">Delete</button>
            `;
            membersContainer.appendChild(userItem);
        });
    }

    // ユーザーをグループに追加
    window.addUserToGroup = function(username) {
        if (!currentEditingGroup.includes(username)) {
            currentEditingGroup.push(username);
            
            // 現在の検索状態を保持して更新
            const searchInput = document.getElementById('userSearchInput');
            if (searchInput && searchInput.value.trim()) {
                handleUserSearch(); // 検索結果を更新
            } else {
                updateEditGroupContent(); // 全体を更新
            }
            
            // グループメンバーリストのみ更新
            updateGroupMembersList(currentEditingGroup);
        }
    };

    // ユーザーをグループから削除
    window.removeUserFromGroup = function(username) {
        const index = currentEditingGroup.indexOf(username);
        if (index !== -1) {
            currentEditingGroup.splice(index, 1);
            
            // 現在の検索状態を保持して更新
            const searchInput = document.getElementById('userSearchInput');
            if (searchInput && searchInput.value.trim()) {
                handleUserSearch(); // 検索結果を更新
            } else {
                updateEditGroupContent(); // 全体を更新
            }
            
            // グループメンバーリストのみ更新
            updateGroupMembersList(currentEditingGroup);
        }
    };

    // グループを削除
    window.deleteGroup = function(groupName) {
        if (confirm(`Are you sure you want to delete group "${groupName}"?`)) {
            const groups = getGroups();
            delete groups[groupName];
            saveGroups(groups);
            
            // 順序からも削除
            const order = getGroupOrder();
            const index = order.indexOf(groupName);
            if (index !== -1) {
                order.splice(index, 1);
                saveGroupOrder(order);
            }
            
            // 保存されたフィルター状態からも削除されたグループを除去
            const filterState = getFilterState();
            if (filterState.selectedGroups.includes(groupName)) {
                filterState.selectedGroups = filterState.selectedGroups.filter(name => name !== groupName);
                
                // 削除後にselectedGroupsが空になった場合はNoneに戻す
                if (filterState.selectedGroups.length === 0) {
                    filterState.noneChecked = true;
                }
                
                saveFilterState(filterState);
            }
            
            updateGroupList();
        }
    };

    // モーダルの内容を更新
    function updateModalContent() {
        const groups = getGroups();
        const order = getGroupOrder();
        const filterState = getFilterState();
        const optionsContainer = document.getElementById('groupFilterOptions');
        
        // Noneオプション以外をクリア
        const noneOption = optionsContainer.querySelector('.group-filter-option');
        optionsContainer.innerHTML = '';
        optionsContainer.appendChild(noneOption);
        
        // グループ順序に従ってオプションを追加
        order.forEach(groupName => {
            if (groups[groupName]) {
                const option = document.createElement('div');
                option.className = 'group-filter-option';
                option.innerHTML = `
                    <input type="checkbox" id="group_${groupName}" data-group="${groupName}">
                    <label for="group_${groupName}">${groupName}</label>
                `;
                optionsContainer.appendChild(option);
            }
        });
        
        // 保存されたフィルター状態を復元
        const noneCheckbox = document.getElementById('noneFilter');
        noneCheckbox.checked = filterState.noneChecked;
        
        // Hide Unregistered Userチェックボックスの状態を復元
        const hideUnregisteredCheckbox = document.getElementById('hideUnregistered');
        if (hideUnregisteredCheckbox) {
            hideUnregisteredCheckbox.checked = filterState.hideUnregistered || false;
        }
        
        // 選択されたグループを復元
        filterState.selectedGroups.forEach(groupName => {
            const checkbox = document.getElementById(`group_${groupName}`);
            if (checkbox) {
                checkbox.checked = true;
            }
        });
        
        // 演算子を復元
        const operationRadio = document.querySelector(`input[name="operation"][value="${filterState.operation}"]`);
        if (operationRadio) {
            operationRadio.checked = true;
        }
        
        // Noneチェックボックスのイベントリスナー
        noneCheckbox.addEventListener('change', function() {
            if (this.checked) {
                // 他のすべてのチェックボックスを外す
                const groupCheckboxes = optionsContainer.querySelectorAll('input[data-group]');
                groupCheckboxes.forEach(cb => cb.checked = false);
            }
        });
        
        // グループチェックボックスのイベントリスナー
        const groupCheckboxes = optionsContainer.querySelectorAll('input[data-group]');
        groupCheckboxes.forEach(checkbox => {
            checkbox.addEventListener('change', function() {
                if (this.checked) {
                    // Noneチェックボックスを外す
                    noneCheckbox.checked = false;
                }
            });
        });
    }

    // フィルタリングを適用
    function applyFiltering() {
        const modal = document.getElementById('groupFilterModal');
        if (!modal) return;
        
        // 最初にフィルタリングを適用する際に元の統計情報を保存
        saveOriginalStatistics();
        
        // まず未登録ユーザーの表示状態をリセット
        showUnregisteredUsers();
        
        const filterState = getFilterState();
        const noneChecked = filterState.noneChecked;
        
        if (noneChecked) {
            // フィルタリングなし - お気に入り順位表を表示(統計は再計算)
            showFavoriteStandings();
            
            // 未登録ユーザー非表示の処理
            if (filterState.hideUnregistered) {
                hideUnregisteredUsers();
            }
            
            return;
        }
        
        // 選択されたグループを取得
        const selectedGroups = [];
        const groupCheckboxes = modal.querySelectorAll('input[data-group]:checked');
        groupCheckboxes.forEach(cb => {
            selectedGroups.push(cb.dataset.group);
        });
        
        if (selectedGroups.length === 0) {
            // グループが選択されていない場合 - お気に入り順位表を表示(統計は再計算)
            showFavoriteStandings();
            
            // 未登録ユーザー非表示の処理
            if (filterState.hideUnregistered) {
                hideUnregisteredUsers();
            }
            
            return;
        }
        
        // 演算子を取得
        const operation = modal.querySelector('input[name="operation"]:checked').value;
        
        // フィルタリング対象のユーザーセットを計算
        const targetUsers = calculateTargetUsers(selectedGroups, operation);
        
        // 順位表にフィルタリングを適用
        filterStandingsTable(targetUsers);
    }

    // 対象ユーザーセットを計算
    function calculateTargetUsers(selectedGroups, operation) {
        const groups = getGroups();
        let targetUsers = new Set();
        
        if (operation === 'OR') {
            selectedGroups.forEach(groupName => {
                if (groups[groupName]) {
                    groups[groupName].forEach(user => targetUsers.add(user));
                }
            });
        } else { // AND
            if (selectedGroups.length > 0) {
                targetUsers = new Set(groups[selectedGroups[0]] || []);
                for (let i = 1; i < selectedGroups.length; i++) {
                    const groupUsers = new Set(groups[selectedGroups[i]] || []);
                    targetUsers = new Set([...targetUsers].filter(user => groupUsers.has(user)));
                }
            }
        }
        
        return targetUsers;
    }

    // 順位表テーブルにフィルタリングを適用
    function filterStandingsTable(targetUsers) {
        const userRows = getUserRows();
        const visibleRows = [];
        
        userRows.forEach(row => {
            const userCell = row.querySelector('td a[href*="/users/"]');
            if (userCell) {
                const username = userCell.textContent.trim();
                if (targetUsers.has(username)) {
                    row.style.display = '';
                    visibleRows.push(row);
                } else {
                    row.style.display = 'none';
                }
            }
        });
        
        // フィルタされた結果に基づいて順位表を更新
        updateFilteredStandings(visibleRows);
        
        // 未登録ユーザー非表示の処理
        const filterState = getFilterState();
        if (filterState.hideUnregistered) {
            hideUnregisteredUsers();
        }
    }

    // フィルタされた順位表の情報を更新
    function updateFilteredStandings(visibleRows) {
        if (visibleRows.length === 0) return;
        
        // 1. 順位を更新
        updateRankings(visibleRows);
        
        // 2. 問題統計を更新
        updateProblemStatistics(visibleRows);
    }

    // 順位を更新
    function updateRankings(visibleRows) {
        // 元の順位を取得
        const rankedRows = visibleRows.map(row => {
            const rankElement = row.querySelector('.standings-rank');
            if (!rankElement) return null;
            
            // gray属性のspan要素から元の順位を取得(全体順位)
            const graySpan = rankElement.querySelector('span.gray');
            let originalRank = 0;
            
            if (graySpan) {
                // (数字)形式から数字を抽出
                const match = graySpan.textContent.match(/\((\d+)\)/);
                if (match) {
                    originalRank = parseInt(match[1]);
                }
            } else {
                // gray要素がない場合は通常のspan要素から取得
                const span = rankElement.querySelector('span');
                if (span && !span.classList.contains('gray')) {
                    originalRank = parseInt(span.textContent) || 0;
                }
            }
            
            return { row, originalRank };
        }).filter(item => item !== null && item.originalRank > 0); // 全体順位が0の人は除外
        
        // フィルターされたユーザーの元の順位の一覧を取得してソート
        const originalRanks = rankedRows.map(item => item.originalRank).sort((a, b) => a - b);
        
        // ユニークな元の順位を取得してソート
        const uniqueOriginalRanks = [...new Set(originalRanks)].sort((a, b) => a - b);
        
        // 元の順位から新しい相対順位へのマッピングを作成
        const rankMapping = {};
        let newRankCounter = 1;
        
        uniqueOriginalRanks.forEach(originalRank => {
            rankMapping[originalRank] = newRankCounter;
            // 同じ順位のユーザー数を数えて次の順位を決定
            const sameRankCount = originalRanks.filter(rank => rank === originalRank).length;
            newRankCounter += sameRankCount;
        });
        
        // 各行に新しい相対順位を割り当て
        rankedRows.forEach(({ row, originalRank }) => {
            const rankElement = row.querySelector('.standings-rank span:not(.gray)');
            
            if (rankElement) {
                const newRank = rankMapping[originalRank];
                rankElement.textContent = newRank.toString();
            }
        });
    }

    // 問題統計を更新
    function updateProblemStatistics(visibleRows) {
        // 問題数を取得
        const problemCount = getProblemCount();
        if (problemCount === 0) return;
        
        // 各問題の統計を計算
        const problemStats = calculateProblemStatistics(visibleRows, problemCount);
        
        // 最速正解者を更新
        updateFastestSolvers(problemStats);
        
        // 正解者数/提出者数を更新
        updateSolverCounts(problemStats);
    }

    // 問題数を取得
    function getProblemCount() {
        const faRow = document.querySelector('tr.standings-fa');
        if (!faRow) return 0;
        
        const tdElements = faRow.querySelectorAll('td');
        return Math.max(0, tdElements.length - 1); // 1つ目のtdは問題列に対応しないため-1
    }

    // 問題統計を計算
    function calculateProblemStatistics(visibleRows, problemCount) {
        const stats = [];
        
        // 各問題の統計を初期化
        for (let i = 0; i < problemCount; i++) {
            stats.push({
                fastestSolver: null,
                fastestTime: Infinity,
                solverCount: 0,
                submitterCount: 0
            });
        }
        
        // 各ユーザーの結果を解析
        visibleRows.forEach((row, userIndex) => {
            const username = getUsernameFromRow(row);
            const userColor = getUserColorFromRow(row);
            const results = row.querySelectorAll('td.standings-result');
            
            // 最初のstandings-resultは総合得点なのでスキップ
            for (let i = 1; i <= problemCount && i < results.length; i++) {
                const result = results[i];
                const problemIndex = i - 1;
                
                const acSpan = result.querySelector('span.standings-ac');
                const resultText = result.textContent.trim();
                
                if (acSpan) {
                    // 正解の場合 - 時刻部分を抽出
                    const timeMatch = resultText.match(/(\d+:\d+)/);
                    if (timeMatch) {
                        const timeText = timeMatch[1];
                        
                        stats[problemIndex].solverCount++;
                        stats[problemIndex].submitterCount++;
                        
                        // 時間を秒に変換
                        const timeInSeconds = parseTimeToSeconds(timeText);
                        
                        // 最速かチェック
                        if (timeInSeconds < stats[problemIndex].fastestTime) {
                            stats[problemIndex].fastestTime = timeInSeconds;
                            stats[problemIndex].fastestSolver = {
                                username: username,
                                time: timeText,
                                color: userColor
                            };
                        }
                    }
                } else if (resultText.match(/^\(\d+\)$/)) {
                    // 不正解の場合(提出はしている)
                    stats[problemIndex].submitterCount++;
                }
            }
        });
        
        return stats;
    }

    // 行からユーザー名を取得
    function getUsernameFromRow(row) {
        const userLink = row.querySelector('td a[href*="/users/"]');
        return userLink ? userLink.textContent.trim() : '';
    }

    // 行からユーザーの色を取得
    function getUserColorFromRow(row) {
        const userLink = row.querySelector('td a[href*="/users/"]');
        if (!userLink) return '';
        
        // ユーザーリンク内のspan要素から色情報を取得
        const userSpan = userLink.querySelector('span');
        if (userSpan) {
            // spanのclass属性をそのまま取得
            const className = userSpan.getAttribute('class');
            if (className) {
                return className;
            }
            
            // class属性がない場合はstyle属性を確認
            const style = userSpan.getAttribute('style');
            if (style && style.includes('color')) {
                return `style="${style}"`;
            }
        }
        
        // フォールバック: リンク自体のclass属性から色情報を取得
        const classList = userLink.classList;
        for (let className of classList) {
            if (className.startsWith('user-') || className.includes('color')) {
                return className;
            }
        }
        
        return '';
    }

    // 元の統計情報を保存(全体順位表の統計情報)
    function saveOriginalStatistics() {
        if (originalFastestSolvers && originalSolverCounts) {
            return; // 既に保存済み
        }
        
        // 最速正解者の情報を保存(全体順位表から)
        const faRow = document.querySelector('tr.standings-fa');
        if (faRow) {
            const tdElements = faRow.querySelectorAll('td');
            originalFastestSolvers = [];
            for (let i = 1; i < tdElements.length; i++) {
                originalFastestSolvers.push(tdElements[i].innerHTML);
            }
        }
        
        // 正解者数/提出者数の情報を保存(全体順位表から)
        const statsRow = document.querySelector('tr.standings-statistics');
        if (statsRow) {
            const tdElements = statsRow.querySelectorAll('td');
            originalSolverCounts = [];
            for (let i = 1; i < tdElements.length; i++) {
                originalSolverCounts.push(tdElements[i].innerHTML);
            }
        }
    }

    // 元の統計情報を復元
    function restoreOriginalStatistics() {
        if (!originalFastestSolvers || !originalSolverCounts) {
            return;
        }
        
        // 最速正解者を復元
        const faRow = document.querySelector('tr.standings-fa');
        if (faRow && originalFastestSolvers) {
            const tdElements = faRow.querySelectorAll('td');
            for (let i = 1; i < tdElements.length && i - 1 < originalFastestSolvers.length; i++) {
                tdElements[i].innerHTML = originalFastestSolvers[i - 1];
            }
        }
        
        // 正解者数/提出者数を復元
        const statsRow = document.querySelector('tr.standings-statistics');
        if (statsRow && originalSolverCounts) {
            const tdElements = statsRow.querySelectorAll('td');
            for (let i = 1; i < tdElements.length && i - 1 < originalSolverCounts.length; i++) {
                tdElements[i].innerHTML = originalSolverCounts[i - 1];
            }
        }
        
        // フォントサイズを再調整
        setTimeout(() => {
            recalculateStatisticsFontSizes();
        }, 100);
    }
    function parseTimeToSeconds(timeStr) {
        const parts = timeStr.split(':');
        if (parts.length !== 2) return Infinity;
        
        const minutes = parseInt(parts[0]) || 0;
        const seconds = parseInt(parts[1]) || 0;
        return minutes * 60 + seconds;
    }

    // 最速正解者を更新
    function updateFastestSolvers(problemStats) {
        const faRow = document.querySelector('tr.standings-fa');
        if (!faRow) return;
        
        const tdElements = faRow.querySelectorAll('td');
        
        problemStats.forEach((stat, index) => {
            const tdIndex = index + 1; // 最初のtdはスキップ
            if (tdIndex < tdElements.length) {
                const td = tdElements[tdIndex];
                
                if (stat.fastestSolver) {
                    const { username, time, color } = stat.fastestSolver;
                    
                    // 元のスタイルを取得
                    const existingP = td.querySelector('p.fit-font-size');
                    const existingStyle = existingP ? existingP.getAttribute('style') : 'font-size: 10px; width: 50px;';
                    const noBreakClass = existingP && existingP.classList.contains('no-break') ? ' no-break' : '';
                    
                    // HTML構造を元の形式で作成
                    let html = `<p class="fit-font-size${noBreakClass}" style="${existingStyle}">`;
                    html += `<a href="https://atcoder.jp/users/${username}" class="username">`;
                    
                    // 色の処理を改善
                    if (color.startsWith('style=')) {
                        // style属性の場合
                        html += `<span ${color}>${username}</span>`;
                    } else {
                        // class属性の場合
                        html += `<span class="${color}">${username}</span>`;
                    }
                    
                    html += `</a></p>`;
                    html += `<p>${time}</p>`;
                    
                    td.innerHTML = html;
                } else {
                    // 最速正解者がいない場合は"-"を表示
                    const existingP = td.querySelector('p.fit-font-size');
                    const existingStyle = existingP ? existingP.getAttribute('style') : 'font-size: 10px; width: 50px;';
                    const noBreakClass = existingP && existingP.classList.contains('no-break') ? ' no-break' : '';
                    
                    td.innerHTML = `<p class="fit-font-size${noBreakClass}" style="${existingStyle}">-</p>`;
                }
            }
        });
        
        // フォントサイズを再調整
        setTimeout(() => {
            recalculateStatisticsFontSizes();
        }, 100);
    }

    // 正解者数/提出者数を更新
    function updateSolverCounts(problemStats) {
        const statsRow = document.querySelector('tr.standings-statistics');
        if (!statsRow) return;
        
        const tdElements = statsRow.querySelectorAll('td');
        
        problemStats.forEach((stat, index) => {
            const tdIndex = index + 1; // 最初のtdはスキップ
            if (tdIndex < tdElements.length) {
                const td = tdElements[tdIndex];
                
                // tdにclass属性がある場合は"-"のみ表示
                const tdClass = td.getAttribute('class');
                if (tdClass && tdClass.trim() !== '') {
                    // 元のスタイルを取得
                    const existingP = td.querySelector('p.fit-font-size');
                    const existingStyle = existingP ? existingP.getAttribute('style') : 'font-size: 10px; width: 50px;';
                    
                    td.innerHTML = `<p class="fit-font-size" style="${existingStyle}">-</p>`;
                } else {
                    // 通常の統計表示
                    // 元のスタイルを取得
                    const existingP = td.querySelector('p.fit-font-size');
                    const existingStyle = existingP ? existingP.getAttribute('style') : 'font-size: 10px; width: 50px;';
                    
                    // HTML構造を元の形式で作成
                    const html = `<p class="fit-font-size" style="${existingStyle}">` +
                        `<span class="standings-ac">${stat.solverCount}</span> / <span>${stat.submitterCount}</span>` +
                        `</p>`;
                    
                    td.innerHTML = html;
                }
            }
        });
        
        // フォントサイズを再調整
        setTimeout(() => {
            recalculateStatisticsFontSizes();
        }, 100);
    }

    // すべての行を表示(統計情報復元は別途呼び出し)
    function showAllRows() {
        const standingsTable = document.querySelector('#standings-tbody, .table tbody');
        if (!standingsTable) return;
        
        const rows = standingsTable.querySelectorAll('tr');
        
        rows.forEach(row => {
            const userCell = row.querySelector('td a[href*="/users/"]');
            if (userCell) {
                row.style.display = '';
            }
        });
    }

    // フォントサイズを調整する関数
    function adjustFontSize(element) {
        if (!element) return 10;
        
        // jQuery fitFontSizeプラグインが利用可能かチェック (基本的には使えるはず)
        if (typeof $ !== 'undefined' && $.fn && $.fn.fitFontSize) {
            return adjustFontSizeWithPlugin(element);
        } else {
            return adjustFontSizeDynamic(element);
        }
    }
    
    // jQuery fitFontSizeプラグインを使用した調整
    function adjustFontSizeWithPlugin(element) {
        const $element = $(element);
        const textContent = element.textContent.trim();
        const textLength = textContent.length;
        
        const problemCount = getProblemCount();
        const availableWidth = (Math.max(60, 420/(problemCount + 1)))-10;
        
        $element.fitFontSize(availableWidth, textLength / 1.5, 10);
        
        const resultFontSize = parseInt($element.css('font-size'));
        return resultFontSize;
    }
    
    // 動的フォントサイズ調整(プラグイン未使用)
    function adjustFontSizeDynamic(element) {
        const parentWidth = element.parentElement.offsetWidth;
        const textContent = element.textContent.trim();
        const textLength = textContent.length;
        
        // 利用可能な幅を計算(余白を考慮)
        const availableWidth = Math.max(60, parentWidth - 20);
        
        let fontSize;
        let minFontSize;
        
        // テキストの長さに応じた基本フォントサイズと最小サイズを計算
        if (textLength === 0) {
            fontSize = 10;
            minFontSize = 10;
        } else if (textLength <= 3) {
            // 非常に短いテキスト: 大きめのフォント
            fontSize = Math.min(14, Math.floor(availableWidth / (textLength * 10)));
            minFontSize = 10;
        } else if (textLength <= 8) {
            // 短いテキスト: 中程度のフォント
            fontSize = Math.min(12, Math.floor(availableWidth / (textLength * 8)));
            minFontSize = 9;
        } else if (textLength <= 15) {
            // 中程度のテキスト: やや小さめのフォント
            fontSize = Math.min(11, Math.floor(availableWidth / (textLength * 6)));
            minFontSize = 8;
        } else {
            // 長いテキスト: 小さめのフォント
            fontSize = Math.min(10, Math.floor(availableWidth / (textLength * 5)));
            minFontSize = 7;
        }
        
        // 最小フォントサイズを保証
        fontSize = Math.max(minFontSize, fontSize);
        
        // フォントサイズと改行設定を適用
        element.style.fontSize = fontSize + 'px';
        element.style.whiteSpace = 'normal';
        element.style.wordBreak = 'break-word';
        
        // デバッグ用ログ(必要に応じてコメントアウト)
        // console.log(`dynamicSize: text="${textContent}" length=${textLength} width=${availableWidth} minSize=${minFontSize} result=${fontSize}px`);
        
        return fontSize;
    }

    // 統計情報のフォントサイズを再調整
    function recalculateStatisticsFontSizes() {
        // DOM更新後の計算のため少し待つ
        setTimeout(() => {
            // 最速正解者のフォントサイズを調整
            const faRow = document.querySelector('tr.standings-fa');
            if (faRow) {
                const tdElements = faRow.querySelectorAll('td');
                
                for (let i = 1; i < tdElements.length; i++) {
                    const td = tdElements[i];
                    if (td.offsetWidth > 0) { // 表示されている要素のみ処理
                        const pElements = td.querySelectorAll('p.fit-font-size');
                        
                        pElements.forEach((p) => {
                            adjustFontSize(p);
                        });
                    }
                }
            }
            
            // 正解者数/提出者数のフォントサイズを調整
            const statsRow = document.querySelector('tr.standings-statistics');
            if (statsRow) {
                const tdElements = statsRow.querySelectorAll('td');
                
                for (let i = 1; i < tdElements.length; i++) {
                    const td = tdElements[i];
                    if (td.offsetWidth > 0) { // 表示されている要素のみ処理
                        const pElements = td.querySelectorAll('p.fit-font-size');
                        
                        pElements.forEach((p) => {
                            adjustFontSize(p);
                        });
                    }
                }
            }
        }, 50);
    }

    // "Filtering by Group"ボタンを追加
    function addFilteringButton() {
        const delay = 1000; // 1秒待機
        
        setTimeout(() => {
            // お気に入りのみ表示のチェックボックスを探す(IDで検索)
            const favCheckbox = document.getElementById("checkbox-fav-only");
            if (!favCheckbox) {
                console.log('お気に入りチェックボックスが見つかりません');
                return;
            }
            
            const favContainer = favCheckbox.closest('label') || favCheckbox.parentElement;
            if (!favContainer) {
                console.log('お気に入りコンテナが見つかりません');
                return;
            }
            
            // 既にボタンが追加されている場合はスキップ
            if (document.querySelector('.filtering-by-group-btn')) {
                return;
            }
            
            // 初期状態を保存(チェックボックスがONの場合に備えて)
            const initialCheckedState = favCheckbox.checked;
            
            // チェックボックスがONの場合、一瞬OFFにして全体統計を保存
            if (initialCheckedState) {
                console.log('初期状態がONのため、一瞬OFFにして全体統計を保存します');
                favCheckbox.checked = false;
                // チェックボックスの変更イベントを発火させて全体表示にする
                favCheckbox.dispatchEvent(new Event('change', { bubbles: true }));
                
                // 少し待ってから統計を保存
                setTimeout(() => {
                    saveOriginalStatistics();
                    
                    // 元の状態に戻す
                    favCheckbox.checked = true;
                    favCheckbox.dispatchEvent(new Event('change', { bubbles: true }));
                    
                    // さらに少し待ってからフィルター状態を適用
                    setTimeout(() => {
                        applyStoredFiltering();
                    }, 200);
                }, 300);
            } else {
                // 初期状態がOFFの場合は、そのまま統計を保存
                saveOriginalStatistics();
            }
            
            // ボタンを作成
            const filteringBtn = document.createElement('button');
            filteringBtn.textContent = 'Filtering by Group';
            filteringBtn.className = 'filtering-by-group-btn';
            filteringBtn.type = 'button'; // フォーム送信を防ぐ
            filteringBtn.addEventListener('click', function(e) {
                e.preventDefault(); // デフォルト動作を防ぐ
                e.stopPropagation(); // イベントの伝播を防ぐ
                openGroupFilterModal();
            });
            
            // Clear Filteringボタンを作成
            const clearBtn = document.createElement('button');
            clearBtn.textContent = 'Clear Filtering';
            clearBtn.className = 'clear-filtering-btn';
            clearBtn.type = 'button';
            clearBtn.addEventListener('click', function(e) {
                e.preventDefault();
                e.stopPropagation();
                clearFiltering();
                updateClearButtonState(); // 状態を更新
            });
            
            // ボタンの有効/無効を判定する関数
            function updateClearButtonState() {
                const filterState = getFilterState();
                const isFavChecked = favCheckbox.checked;
                const isNoneSelected = filterState.noneChecked || filterState.selectedGroups.length === 0;
                
                // お気に入りOFF または フィルタリングがNone/未選択の場合は無効
                clearBtn.disabled = !isFavChecked || isNoneSelected;
            }
            
            // お気に入りのみ表示の状態に応じてボタンの有効/無効を切り替え
            function updateButtonState() {
                filteringBtn.disabled = !favCheckbox.checked;
                updateClearButtonState(); // Clear ボタンの状態も更新
            }
            
            // 初期状態を設定
            updateButtonState();
            
            // チェックボックスの変更を監視
            favCheckbox.addEventListener('change', function() {
                updateButtonState();
                
                // チェックボックスがオフになった場合は元の統計情報を復元
                if (!this.checked) {
                    // 未登録ユーザーの非表示も解除
                    showUnregisteredUsers();
                    restoreOriginalStatistics();
                } else {
                    // チェックボックスがオンになった場合は保存されたフィルター状態を適用
                    setTimeout(() => {
                        applyStoredFiltering();
                        // お気に入りのみ表示がオンになったときに自動でHide未登録ユーザーを実行
                        const filterState = getFilterState();
                        if (filterState.hideUnregistered) {
                            hideUnregisteredUsers();
                        }
                    }, 100); // 少し遅延させてDOM更新を待つ
                }
            });
            
            // ボタンを挿入
            favContainer.parentNode.insertBefore(filteringBtn, favContainer.nextSibling);
            favContainer.parentNode.insertBefore(clearBtn, filteringBtn.nextSibling);
            
            // 初期状態がOFFの場合のみ、すぐにフィルター状態を適用
            if (!initialCheckedState) {
                setTimeout(() => {
                    applyStoredFiltering();
                }, 500);
            } else {
                // 初期状態がONの場合は、Hide未登録ユーザーも実行
                setTimeout(() => {
                    const filterState = getFilterState();
                    if (filterState.hideUnregistered) {
                        hideUnregisteredUsers();
                    }
                }, 500);
            }
        }, delay);
    }

    // 保存されたフィルター状態を適用
    function applyStoredFiltering() {
        const filterState = getFilterState();
        
        // お気に入りのみ表示がオンの場合のみフィルタリングを適用
        const favCheckbox = document.getElementById("checkbox-fav-only");
        if (!favCheckbox || !favCheckbox.checked) {
            return;
        }
        
        // 最初にフィルタリングを適用する際に元の統計情報を保存
        saveOriginalStatistics();
        
        if (filterState.noneChecked || filterState.selectedGroups.length === 0) {
            // フィルタリングなし - お気に入り順位表を表示(統計は再計算)
            const standingsTable = document.querySelector('#standings-tbody, .table tbody');
            if (standingsTable) {
                const rows = standingsTable.querySelectorAll('tr');
                const visibleRows = [];
                
                rows.forEach(row => {
                    const userCell = row.querySelector('td a[href*="/users/"]');
                    if (userCell) {
                        row.style.display = '';
                        visibleRows.push(row);
                    }
                });
                
                // お気に入り順位表の統計を再計算
                console.log('お気に入り順位表の統計を再計算します');
                updateFilteredStandings(visibleRows);
            }
            return;
        }
        
        // フィルタリング対象のユーザーセットを計算
        const targetUsers = calculateTargetUsers(filterState.selectedGroups, filterState.operation);
        
        // 順位表にフィルタリングを適用
        filterStandingsTable(targetUsers);
    }

    // お気に入り設定ページに"Manage Group"ボタンを追加
    function addManageGroupButton() {
        
        setTimeout(() => {
            // 同期ボタンを探す(正確なセレクタ)
            let syncButton = document.querySelector('a.btn.btn-default');
            
            // 「同期」という文字を含むことを確認
            if (syncButton && !syncButton.textContent.includes('同期')) {
                syncButton = null;
            }
            
            // フォールバック: glyphicon-transferを含む要素を探す
            if (!syncButton) {
                const transferIcon = document.querySelector('.glyphicon-transfer');
                if (transferIcon) {
                    syncButton = transferIcon.closest('a.btn');
                }
            }
            
            if (!syncButton) {
                console.log('同期ボタンが見つかりません');
                console.log('利用可能な .btn 要素:', document.querySelectorAll('.btn').length);
                console.log('利用可能な a.btn-default 要素:', document.querySelectorAll('a.btn-default').length);
                return;
            }
            
            console.log('見つかった同期ボタン:', syncButton.textContent.trim());
            
            // 既にボタンが追加されている場合はスキップ
            if (document.querySelector('.manage-group-btn')) {
                return;
            }
            
            // ボタンを作成
            const manageBtn = document.createElement('button');
            manageBtn.textContent = 'Manage Group';
            manageBtn.className = 'manage-group-btn';
            manageBtn.type = 'button';
            manageBtn.addEventListener('click', function(e) {
                e.preventDefault();
                e.stopPropagation();
                openGroupManageModal();
            });
            
            // 同期ボタンの親要素(<p>タグ)に追加
            const parentP = syncButton.parentElement;
            if (parentP && parentP.tagName === 'P') {
                parentP.appendChild(document.createTextNode(' '));
                parentP.appendChild(manageBtn);
            } else {
                // フォールバック: 同期ボタンの隣に挿入
                syncButton.parentNode.insertBefore(manageBtn, syncButton.nextSibling);
            }
            
            console.log('Manage Groupボタンを追加しました');
        });
    }

    // 初期化
    function initialize() {
        // グループデータの初期化
        initializeGroups();
        
        // スタイルを追加
        addModalStyles();
        
        // 現在のページに応じて初期化
        const currentUrl = window.location.href;
        
        if (currentUrl.includes('/contests/') && currentUrl.includes('/standings')) {
            // 順位表ページ
            createModal();
            addFilteringButton();
        } else if (currentUrl.includes('/settings/fav')) {
            // お気に入り設定ページ
            createGroupManageModal();
            createEditGroupModal();
            createGroupNameModal();
            addManageGroupButton();
        }
    }

    // スクリプト開始
    initialize();

})();