import { API_BASE, state, setBinderSort } from './state.js';
import { showToast, getIntersectingZone, escapeHtml, bindGuardedFormSubmit } from './utils.js';
import {
    fetchTags,
    fetchBinder,
    fetchBinderList,
    syncAndRender,
    savePlacements,
    updateBinderInfo,
    saveResourceApi,
    toggleBinderPinApi,
    updateSettingsApi
} from './api.js';
import { renderBinder, updateBinderSelectors, renderBinderDashboard, checkHazardWarning } from './renderer.js';
import { openResourceModal, closeModal, showConfirmDialog } from './modals.js';
import {
    deleteCards,
    handlePastedPaths,
    arrangeCards,
    arrangeSelectedCards,
    alignSelectedCards,
    undoLastArrange
} from './cardActions.js';

async function inlineCardBackgroundImages(container) {
    const targets = Array.from(container.querySelectorAll('.sp-image-bg'));

    await Promise.all(targets.map(async (el) => {
        const originalValue = el.style.backgroundImage;
        const match = /^url\((['"]?)(.*)\1\)$/.exec(originalValue || '');
        if (!match || match[2].startsWith('data:')) return;

        try {
            const response = await fetch(match[2]);
            if (!response.ok) throw new Error(`status ${response.status}`);
            const blob = await response.blob();
            const dataUrl = await new Promise((resolve, reject) => {
                const reader = new FileReader();
                reader.onload = () => resolve(reader.result);
                reader.onerror = reject;
                reader.readAsDataURL(blob);
            });
            el.style.backgroundImage = `url("${dataUrl}")`;
        } catch (err) {
            console.error('Failed to inline background image for HTML export:', match[2], err);
        }
    }));
}

export function refreshImagePasteToggleButton() {
    const label = document.getElementById('btnToggleImagePasteLabel');
    if (!label) return;
    label.textContent = `画像貼り付け: ${state.settings.allow_image_paste ? 'ON' : 'OFF'}`;
}

export function setupEventListeners() {
    let selectedSearchTags = [];
    let tagMatchMode = 'or';
    let currentSearchResults = [];

    let bulkTagContext = { ids: [], source: 'main' };

    function renderSearchTagChips() {
        const container = document.getElementById('searchTagChipContainer');
        const input = document.getElementById('searchResourceTagInput');
        if (!container || !input) return;

        container.querySelectorAll('.tag-chip').forEach(el => el.remove());

        selectedSearchTags.forEach(tag => {
            const chip = document.createElement('span');
            chip.className = 'tag-chip';
            chip.textContent = tag;

            const removeBtn = document.createElement('span');
            removeBtn.className = 'tag-chip-remove';
            removeBtn.textContent = '×';
            removeBtn.addEventListener('click', () => {
                selectedSearchTags = selectedSearchTags.filter(t => t !== tag);
                renderSearchTagChips();
            });

            chip.appendChild(removeBtn);
            container.insertBefore(chip, input);
        });
    }

    function addSearchTag(rawTag) {
        const tag = rawTag.trim();
        if (!tag) return;
        const alreadyExists = selectedSearchTags.some(t => t.toLowerCase() === tag.toLowerCase());
        if (!alreadyExists) {
            selectedSearchTags.push(tag);
            renderSearchTagChips();
        }
        const input = document.getElementById('searchResourceTagInput');
        if (input) input.value = '';
        hideTagSuggestions();
    }

    function updateSelectAllButtonLabel() {
        const btn = document.getElementById('btnSelectAllResults');
        if (!btn) return;
        const checkboxes = Array.from(document.querySelectorAll('.search-result-checkbox:not([disabled])'));
        if (checkboxes.length === 0) {
            btn.disabled = true;
            btn.textContent = 'すべて選択';
            return;
        }
        btn.disabled = false;
        const allChecked = checkboxes.every(cb => cb.checked);
        btn.textContent = allChecked ? '選択解除' : 'すべて選択';
    }

    function hideTagSuggestions() {
        const dropdown = document.getElementById('searchTagSuggestions');
        if (dropdown) {
            dropdown.style.display = 'none';
            dropdown.innerHTML = '';
        }
    }

    function renderTagSuggestions(query) {
        const dropdown = document.getElementById('searchTagSuggestions');
        if (!dropdown) return;

        const q = query.trim().toLowerCase();
        if (!q) {
            hideTagSuggestions();
            return;
        }

        const candidates = (state.allTags || []).filter(tag => {
            if (selectedSearchTags.some(t => t.toLowerCase() === tag.toLowerCase())) return false;
            return tag.toLowerCase().includes(q);
        });

        if (candidates.length === 0) {
            hideTagSuggestions();
            return;
        }

        dropdown.innerHTML = '';
        candidates.forEach(tag => {
            const li = document.createElement('li');
            li.textContent = tag;
            li.addEventListener('mousedown', (e) => {
                e.preventDefault();
                addSearchTag(tag);
            });
            dropdown.appendChild(li);
        });
        dropdown.style.display = 'block';
    }

    async function showDeleteConfirmModal(cards) {
        const count = cards.length;
        const confirmed = await showConfirmDialog('削除の確認', `${count}枚のカードを削除しますか？`, '削除する', true);
        if (confirmed) {
            deleteCards(cards);
        }
    }

    window.App = window.App || {};
    window.App.showDeleteConfirm = showDeleteConfirmModal;

    function resetViewport() {
        if (window.BinderManager) {
            window.BinderManager.setViewport(1, 0, 0);
            if (window.App && window.App.saveViewport) {
                window.App.saveViewport(1, 0, 0);
            }
            const contextMenu = document.getElementById('contextMenu');
            if (contextMenu) contextMenu.style.display = 'none';
        }
    }

    document.querySelectorAll('.modal-overlay').forEach(overlay => {
        overlay.addEventListener('mousedown', (e) => {
            if (e.target === overlay) {
                overlay.classList.remove('active');
            }
        });
    });

    document.addEventListener('keydown', (e) => {
        if (e.key === 'Escape') {
            const activeModal = document.querySelector('.modal-overlay.active');
            if (activeModal) {
                activeModal.classList.remove('active');
            }
        }

        if (e.key === 'Home') {
            const tag = document.activeElement?.tagName;
            if (tag !== 'INPUT' && tag !== 'TEXTAREA' && tag !== 'SELECT') {
                resetViewport();
            }
        }

        if (e.ctrlKey && e.shiftKey && e.key.toLowerCase() === 'v') {
            e.preventDefault();
            fetch(`${API_BASE}/version`)
                .then(res => res.json())
                .then(data => showToast(`LoreBinder v${data.version}`, 'info'))
                .catch(() => showToast('バージョン情報の取得に失敗しました', 'error'));
        }
    });

    const searchInput = document.getElementById('searchInput');
    const btnClearSearch = document.getElementById('btnClearSearch');

    searchInput.addEventListener('input', (e) => {
        const query = e.target.value.toLowerCase().trim();
        
        if (btnClearSearch) {
            btnClearSearch.style.display = query.length > 0 ? 'block' : 'none';
        }

        const cards = document.querySelectorAll('.binder-card');
        cards.forEach(card => {
            if (query === '') {
                card.style.display = '';
                return;
            }
            const textContent = card.textContent.toLowerCase();
            if (textContent.includes(query)) {
                card.style.display = '';
            } else {
                card.style.display = 'none';
            }
        });
    });

    if (btnClearSearch) {
        btnClearSearch.addEventListener('click', () => {
            searchInput.value = '';
            searchInput.dispatchEvent(new Event('input'));
            searchInput.focus();
        });
    }

    document.getElementById('btnNewResource')?.addEventListener('click', () => openResourceModal());

    const ctxAddKanban = document.getElementById('ctx-add-kanban');
    if (ctxAddKanban) {
        ctxAddKanban.addEventListener('click', async () => {
            document.getElementById('contextMenu').style.display = 'none';
            if (!state.currentBinder) return;
            
            state.currentBinder.placements = state.currentBinder.placements || [];
            const existingZones = state.currentBinder.placements.filter(p => p.type === 'zone');
            if (existingZones.length > 0) {
                showToast('タスクボードは既に配置されています', 'info');
                return;
            }

            const rect = document.getElementById('binder-container').getBoundingClientRect();
            const scale = window.BinderManager ? window.BinderManager.getScale() : 1;
            const translate = window.BinderManager ? window.BinderManager.getTranslate() : {x:0, y:0};

            const gap = 20;

            const topWidth = 280;
            const topHeight = 540;

            const bottomWidth = topWidth * 2 + gap;
            const bottomHeight = 220;

            const viewRightX = (rect.width - translate.x) / scale;
            const viewTopY = (0 - translate.y) / scale;

            const margin = 40 / scale;
            const startX = viewRightX - bottomWidth - margin;
            const startY = viewTopY + margin;

            const zonesToAdd = [
                { id: 'zone-todo', status: 'ToDo', width: topWidth, height: topHeight, x: startX, y: startY },
                { id: 'zone-doing', status: 'Doing', width: topWidth, height: topHeight, x: startX + topWidth + gap, y: startY },
                { id: 'zone-done', status: 'Done', width: bottomWidth, height: bottomHeight, x: startX, y: startY + topHeight + gap }
            ];

            zonesToAdd.forEach(z => {
                state.currentBinder.placements.push({
                    id: z.id,
                    type: 'zone',
                    status: z.status,
                    x: z.x,
                    y: z.y,
                    width: z.width,
                    height: z.height
                });
            });

            renderBinder();
            await savePlacements();
            
            showToast('タスクボードを配置しました', 'success');
        });
    }

    const ctxRemoveKanban = document.getElementById('ctx-remove-kanban');
    if (ctxRemoveKanban) {
        ctxRemoveKanban.addEventListener('click', async () => {
            document.getElementById('contextMenu').style.display = 'none';
            if (!state.currentBinder || !state.currentBinder.placements) return;
            
            try {
                await fetch(`${API_BASE}/cards/reset-by-binder`, {
                    method: 'POST',
                    headers: { 'Content-Type': 'application/json' },
                    body: JSON.stringify({ binder_id: state.currentBinder.id })
                });
            } catch (e) {
                console.error('Failed to reset statuses', e);
            }

            const domZones = document.querySelectorAll('.binder-zone');
            domZones.forEach(z => z.remove());

            state.currentBinder.placements = state.currentBinder.placements.filter(p => p.type !== 'zone');

            await syncAndRender();
            await savePlacements();
            
            showToast('タスクボードを取り除きました', 'success');
        });
    }

    const btnDeleteBinder = document.getElementById('btnDeleteBinder');
    if (btnDeleteBinder) {
        btnDeleteBinder.addEventListener('click', async () => {
            if (!state.currentBinder) return;
            
            const confirmed = await showConfirmDialog(
                'バインダーの削除',
                `本当にバインダー「${state.currentBinder.title}」を削除しますか？\n(配置されているカード自体は削除されません)`,
                '削除する',
                true
            );
            if (!confirmed) return;
            
            try {
                await fetch(`${API_BASE}/cards/reset-by-binder`, {
                    method: 'POST',
                    headers: { 'Content-Type': 'application/json' },
                    body: JSON.stringify({ binder_id: state.currentBinder.id })
                });

                const res = await fetch(`${API_BASE}/binderes/${state.currentBinder.id}`, { method: 'DELETE' });
                if (!res.ok) throw new Error('バインダーの削除に失敗しました');
                
                showToast('バインダーを削除しました', 'success');
                
                fetch(`${API_BASE}/cards/gc`, {
                    method: 'POST'
                }).catch(e => console.error('GC error after binder deletion:', e));
                
                await fetchBinderList();
                
                if (state.binderList.length === 0) {
                    const createRes = await fetch(`${API_BASE}/binderes`, {
                        method: 'POST',
                        headers: { 'Content-Type': 'application/json' },
                        body: JSON.stringify({ title: 'New Workspace', description: 'Main workspace' })
                    });
                    const newBinder = await createRes.json();
                    state.binderList.push(newBinder);
                    await fetchBinder(newBinder.id);
                } else {
                    await fetchBinder(state.binderList[0].id);
                }
                updateBinderSelectors();
            } catch (err) {
                showToast(err.message, 'error');
            }
        });
    }

    document.querySelectorAll('[data-arrange-mode]').forEach(item => {
        item.addEventListener('click', async (e) => {
            e.stopPropagation();
            document.getElementById('contextMenu').style.display = 'none';
            await arrangeCards(e.currentTarget.dataset.arrangeMode);
        });
    });

    document.querySelectorAll('[data-local-arrange-mode]').forEach(item => {
        item.addEventListener('click', async (e) => {
            e.stopPropagation();
            document.getElementById('contextMenu').style.display = 'none';
            await arrangeSelectedCards(e.currentTarget.dataset.localArrangeMode);
        });
    });

    document.querySelectorAll('[data-align-direction]').forEach(item => {
        item.addEventListener('click', async (e) => {
            e.stopPropagation();
            document.getElementById('contextMenu').style.display = 'none';
            await alignSelectedCards(e.currentTarget.dataset.alignDirection);
        });
    });

    document.getElementById('ctx-undo-arrange')?.addEventListener('click', async (e) => {
        e.stopPropagation();
        document.getElementById('contextMenu').style.display = 'none';
        if (window.App && window.App.cancelGravity) window.App.cancelGravity();
        await undoLastArrange();
    });

    document.getElementById('ctx-search')?.addEventListener('click', () => {
        document.getElementById('contextMenu').style.display = 'none';
        const resultsContainer = document.getElementById('searchResourceResults');
        resultsContainer.innerHTML = '<div class="search-empty-msg">検索条件を入力して検索してください。</div>';
        document.getElementById('searchResourceInput').value = '';
        document.getElementById('searchResourceTagInput').value = '';
        selectedSearchTags = [];
        renderSearchTagChips();
        hideTagSuggestions();
        tagMatchMode = 'or';
        document.querySelectorAll('#tagMatchModeToggle .toggle-option').forEach(btn => {
            btn.classList.toggle('active', btn.dataset.mode === 'or');
        });
        currentSearchResults = [];
        document.getElementById('btnImportSelectedCards').disabled = true;
        document.getElementById('btnSearchBulkTag').disabled = true;
        document.getElementById('searchResultsCount').textContent = '';
        updateSelectAllButtonLabel();
        state.modals.searchResource.classList.add('active');
        setTimeout(() => document.getElementById('searchResourceInput').focus(), 50);
    });

    const searchTagInput = document.getElementById('searchResourceTagInput');
    searchTagInput?.addEventListener('input', (e) => {
        renderTagSuggestions(e.target.value);
    });
    searchTagInput?.addEventListener('keydown', (e) => {
        if (e.key === 'Enter') {
            e.preventDefault();
            const activeSuggestion = document.querySelector('#searchTagSuggestions li.active');
            addSearchTag(activeSuggestion ? activeSuggestion.textContent : searchTagInput.value);
        } else if (e.key === 'Escape') {
            hideTagSuggestions();
        }
    });
    searchTagInput?.addEventListener('blur', () => {
        setTimeout(hideTagSuggestions, 150);
    });

    document.getElementById('tagMatchModeToggle')?.addEventListener('click', (e) => {
        const btn = e.target.closest('.toggle-option');
        if (!btn) return;
        tagMatchMode = btn.dataset.mode;
        document.querySelectorAll('#tagMatchModeToggle .toggle-option').forEach(b => {
            b.classList.toggle('active', b === btn);
        });
    });

    async function performResourceSearch() {
        const q = document.getElementById('searchResourceInput').value.trim();
        const resultsContainer = document.getElementById('searchResourceResults');
        const btnImport = document.getElementById('btnImportSelectedCards');
        const btnSearchBulkTag = document.getElementById('btnSearchBulkTag');

        resultsContainer.innerHTML = '<div style="color: #aaa; text-align: center; padding: 20px;">検索中...</div>';
        btnImport.disabled = true;
        btnSearchBulkTag.disabled = true;

        try {
            let url = `${API_BASE}/cards?`;
            const params = new URLSearchParams();
            if (q) params.append('q', q);
            if (selectedSearchTags.length > 0) {
                params.append('tags', selectedSearchTags.join(','));
                params.append('tag_match_mode', tagMatchMode);
            }
            url += params.toString();

            const res = await fetch(url);
            if (!res.ok) throw new Error('検索に失敗しました');
            const data = await res.json();
            currentSearchResults = data;

            resultsContainer.innerHTML = '';
            document.getElementById('searchResultsCount').textContent = `${data.length}件ヒットしました`;

            if (data.length === 0) {
                resultsContainer.innerHTML = '<div style="color: #aaa; text-align: center; padding: 20px;">該当するカードがありません。</div>';
                updateSelectAllButtonLabel();
                return;
            }

            const placedIds = new Set((state.currentBinder?.placements || []).map(p => p.id));

            const updateBulkButtons = () => {
                const checked = document.querySelectorAll('.search-result-checkbox:checked');
                btnImport.disabled = checked.length === 0;
                btnSearchBulkTag.disabled = checked.length === 0;
                updateSelectAllButtonLabel();
            };

            data.forEach(item => {
                const isPlaced = placedIds.has(item.id);

                const div = document.createElement('div');
                div.style.display = 'flex';
                div.style.alignItems = 'center';
                div.style.padding = '8px';
                div.style.background = 'rgba(0,0,0,0.2)';
                div.style.borderRadius = '4px';
                div.style.gap = '10px';

                const checkbox = document.createElement('input');
                checkbox.type = 'checkbox';
                checkbox.value = item.id;
                checkbox.className = 'search-result-checkbox';
                checkbox.style.cursor = 'pointer';
                if (isPlaced) {
                    checkbox.disabled = true;
                } else {
                    checkbox.addEventListener('change', updateBulkButtons);
                }

                const info = document.createElement('div');
                info.style.flex = '1';

                const title = document.createElement('div');
                title.textContent = item.title;
                title.style.fontWeight = 'bold';

                const tagsEl = document.createElement('div');
                tagsEl.textContent = (item.tags || []).join(', ');
                tagsEl.style.fontSize = '0.8rem';
                tagsEl.style.color = '#aaa';

                info.appendChild(title);
                info.appendChild(tagsEl);

                if (isPlaced) {
                    const badge = document.createElement('span');
                    badge.textContent = '配置済';
                    badge.style.fontSize = '0.7rem';
                    badge.style.background = 'rgba(255,255,255,0.2)';
                    badge.style.padding = '2px 6px';
                    badge.style.borderRadius = '10px';
                    info.appendChild(badge);
                    div.style.opacity = '0.5';
                }

                div.appendChild(checkbox);
                div.appendChild(info);

                div.addEventListener('click', (ev) => {
                    if (isPlaced) return;
                    if (ev.target !== checkbox) {
                        checkbox.checked = !checkbox.checked;
                        checkbox.dispatchEvent(new Event('change'));
                    }
                });

                resultsContainer.appendChild(div);
            });

            updateSelectAllButtonLabel();

        } catch (err) {
            resultsContainer.innerHTML = `<div style="color: #ff6b6b; text-align: center; padding: 20px;">エラー: ${err.message}</div>`;
            document.getElementById('searchResultsCount').textContent = '';
            updateSelectAllButtonLabel();
        }
    }

    bindGuardedFormSubmit(document.getElementById('searchResourceForm'), async () => {
        await performResourceSearch();
    });

    document.getElementById('btnSelectAllResults')?.addEventListener('click', () => {
        const checkboxes = Array.from(document.querySelectorAll('.search-result-checkbox:not([disabled])'));
        if (checkboxes.length === 0) return;

        const shouldSelectAll = checkboxes.some(cb => !cb.checked);
        checkboxes.forEach(cb => { cb.checked = shouldSelectAll; });

        document.getElementById('btnImportSelectedCards').disabled = !shouldSelectAll;
        document.getElementById('btnSearchBulkTag').disabled = !shouldSelectAll;
        updateSelectAllButtonLabel();
    });

    document.getElementById('btnImportSelectedCards')?.addEventListener('click', async () => {
        if (!state.currentBinder) return;
        const checked = document.querySelectorAll('.search-result-checkbox:checked');
        if (checked.length === 0) return;

        const idsToAdd = Array.from(checked).map(cb => cb.value);
        
        const rect = document.getElementById('binder-container').getBoundingClientRect();
        let scale = window.BinderManager ? window.BinderManager.getScale() : 1;
        let trans = window.BinderManager ? window.BinderManager.getTranslate() : {x:0, y:0};
        
        let centerX = (state.ctxMouseX !== undefined) ? state.ctxMouseX : (rect.width / 2 - trans.x) / scale;
        let centerY = (state.ctxMouseY !== undefined) ? state.ctxMouseY : (rect.height / 2 - trans.y) / scale;

        state.ctxMouseX = undefined;
        state.ctxMouseY = undefined;

        const padding = 20;
        const cardWidth = 250;
        const cardHeight = 150;
        const minX = -trans.x / scale + padding;
        const maxX = (rect.width - trans.x) / scale - cardWidth - padding;
        const minY = -trans.y / scale + 40; // ヘッダー分少し多め
        const maxY = (rect.height - trans.y) / scale - cardHeight - padding;
        
        const maxRadius = Math.min(rect.width, rect.height) / 2 / scale * 0.8;

        idsToAdd.forEach((id, idx) => {
            let targetX, targetY;
            if (idx === 0) {
                targetX = centerX;
                targetY = centerY;
            } else {
                const r = Math.sqrt(Math.random()) * maxRadius; 
                const theta = Math.random() * 2 * Math.PI;
                targetX = centerX + r * Math.cos(theta);
                targetY = centerY + r * Math.sin(theta);
            }
            
            targetX = Math.max(minX, Math.min(targetX, maxX));
            targetY = Math.max(minY, Math.min(targetY, maxY));

            state.currentBinder.placements.push({
                id: id,
                type: 'card',
                x: targetX,
                y: targetY,
                width: cardWidth,
                height: cardHeight
            });
        });

        await syncAndRender();
        await savePlacements();

        showToast(`${idsToAdd.length}件のカードをバインダーに追加しました`, 'success');
        closeModal('searchResourceModal');
    });

    function openBulkTagModalForIds(ids, source = 'main') {
        if (ids.length === 0) return;

        document.getElementById('bulkTagForm').reset();

        let commonTags = new Set();
        ids.forEach(id => {
            const res = state.currentCards.find(r => r.id === id)
                || currentSearchResults.find(r => r.id === id);
            if (res && res.tags) {
                res.tags.forEach(t => commonTags.add(t));
            }
        });

        const container = document.getElementById('bulkTagsToRemoveContainer');
        container.innerHTML = '';

        if (commonTags.size === 0) {
            container.innerHTML = '<span style="color: #aaa; font-size: 0.9rem;">外せるタグがありません</span>';
        } else {
            Array.from(commonTags).sort().forEach(tag => {
                const label = document.createElement('label');
                label.style.display = 'flex';
                label.style.alignItems = 'center';
                label.style.gap = '5px';
                label.style.cursor = 'pointer';
                label.style.background = 'rgba(255,255,255,0.1)';
                label.style.padding = '4px 8px';
                label.style.borderRadius = '4px';
                label.style.fontSize = '0.9rem';

                const checkbox = document.createElement('input');
                checkbox.type = 'checkbox';
                checkbox.value = tag;
                checkbox.className = 'bulk-remove-tag-cb';

                label.appendChild(checkbox);
                label.appendChild(document.createTextNode(tag));
                container.appendChild(label);
            });
        }

        bulkTagContext = { ids, source };
        state.modals.bulkTag.classList.add('active');
        setTimeout(() => document.getElementById('bulkTagsToAdd').focus(), 50);
    }

    document.getElementById('ctx-bulk-tag-menu')?.addEventListener('click', () => {
        document.getElementById('contextMenu').style.display = 'none';
        const selected = window.BinderManager ? window.BinderManager.getSelectedCards() : [];
        if (selected.length === 0) return;

        const ids = selected
            .filter(card => (!card.dataset.type || card.dataset.type === 'card'))
            .map(card => card.dataset.id);

        openBulkTagModalForIds(ids, 'main');
    });

    document.getElementById('btnSearchBulkTag')?.addEventListener('click', () => {
        const checked = document.querySelectorAll('.search-result-checkbox:checked');
        if (checked.length === 0) return;

        const ids = Array.from(checked).map(cb => cb.value);
        openBulkTagModalForIds(ids, 'search');
    });

    bindGuardedFormSubmit(document.getElementById('bulkTagForm'), async () => {
        const tagsToAddStr = document.getElementById('bulkTagsToAdd').value;
        const tagsToAdd = tagsToAddStr.split(',').map(t => t.trim()).filter(t => t !== '');

        const checkedRemoveCbs = document.querySelectorAll('.bulk-remove-tag-cb:checked');
        const tagsToRemove = Array.from(checkedRemoveCbs).map(cb => cb.value);

        if (tagsToAdd.length === 0 && tagsToRemove.length === 0) {
            showToast('追加または外すタグを入力・選択してください', 'error');
            return;
        }

        const ids = bulkTagContext.ids;

        if (ids.length === 0) {
            showToast('一括更新できるカードが選択されていません', 'error');
            return;
        }

        try {
            const response = await fetch(`${API_BASE}/cards/bulk-tags`, {
                method: 'PATCH',
                headers: { 'Content-Type': 'application/json' },
                body: JSON.stringify({
                    ids: ids,
                    tags_to_add: tagsToAdd,
                    tags_to_remove: tagsToRemove
                })
            });

            if (!response.ok) throw new Error('一括タグ更新に失敗しました');

            showToast('タグを一括更新しました', 'success');
            closeModal('bulkTagModal');

            if (bulkTagContext.source === 'search') {
                await performResourceSearch();
            } else {
                await syncAndRender();

                if (window.BinderManager && window.BinderManager.clearSelection) {
                    window.BinderManager.clearSelection();
                }
            }

            await fetchTags();

        } catch (error) {
            showToast(error.message, 'error');
        }
    });

    bindGuardedFormSubmit(document.getElementById('resourceForm'), async () => {
        const id = document.getElementById('cardId').value;
        const title = document.getElementById('resourceTitle').value;
        const content = document.getElementById('resourceContent').value;
        const tagsStr = document.getElementById('resourceTags').value;
        const tags = tagsStr.split(',').map(t => t.trim()).filter(t => t !== '');

        try {
            const { savedData, isNew } = await saveResourceApi({ id, title, content, tags });

            if (isNew && state.currentBinder && savedData.id) {
                state.currentBinder.placements = state.currentBinder.placements || [];
                const rect = document.getElementById('binder-container').getBoundingClientRect();
                let scale = window.BinderManager ? window.BinderManager.getScale() : 1;
                let trans = window.BinderManager ? window.BinderManager.getTranslate() : {x:0, y:0};
                
                let x, y;
                if (state.ctxMouseX !== undefined && state.ctxMouseY !== undefined) {
                    x = state.ctxMouseX;
                    y = state.ctxMouseY;
                    state.ctxMouseX = undefined; // リセット
                    state.ctxMouseY = undefined;
                } else {
                    x = (rect.width / 2 - trans.x) / scale;
                    y = (rect.height / 2 - trans.y) / scale;
                }

                const CARD_W = 250;
                const CARD_H = 150;
                state.currentBinder.placements.push({
                    id: savedData.id,
                    type: 'card',
                    x: x,
                    y: y,
                    width: CARD_W,
                    height: CARD_H
                });

                const isTaskCard = tags.some(t => t.toLowerCase() === 'task');
                if (isTaskCard) {
                    const zone = getIntersectingZone(x, y, CARD_W, CARD_H, state.currentBinder.placements);
                    if (zone) {
                        try {
                            const statusRes = await fetch(`${API_BASE}/cards/${savedData.id}/status`, {
                                method: 'PATCH',
                                headers: { 'Content-Type': 'application/json' },
                                body: JSON.stringify({ status: zone.status, status_binder_id: state.currentBinder.id })
                            });
                            if (statusRes.ok) {
                                savedData.status = zone.status;
                                savedData.status_binder_id = state.currentBinder.id;
                            }
                        } catch (e) {
                            console.error('ステータス自動付与に失敗', e);
                        }
                    }
                }
            }

            showToast(isNew ? '作成しました' : '更新しました', 'success');
            closeModal('resourceModal');

            await syncAndRender();

            if (isNew && state.currentBinder && savedData.id) {
                await savePlacements();
            }
        } catch (error) {
            showToast(error.message, 'error');
        }
    });

    document.getElementById('btnOpenBinderDashboard')?.addEventListener('click', () => {
        state.modals.binderDashboard.classList.add('active');
        const sortSelect = document.getElementById('binderDashboardSort');
        if (sortSelect) sortSelect.value = state.binderSort;
        renderBinderDashboard(document.getElementById('binderDashboardSearch')?.value || '');
        setTimeout(() => document.getElementById('binderDashboardSearch')?.focus(), 50);
    });

    document.getElementById('binderDashboardSearch')?.addEventListener('input', (e) => {
        renderBinderDashboard(e.target.value);
    });

    document.getElementById('binderDashboardSort')?.addEventListener('change', (e) => {
        setBinderSort(e.target.value);
        renderBinderDashboard(document.getElementById('binderDashboardSearch')?.value || '');
    });

    document.getElementById('binderDashboardGrid')?.addEventListener('click', async (e) => {
        const pinBtn = e.target.closest('.dashboard-pin-btn');
        if (pinBtn && pinBtn.dataset.binderId) {
            e.stopPropagation();
            const binderId = pinBtn.dataset.binderId;
            const binder = state.binderList.find(b => b.id === binderId);
            const nextPinned = !(binder && binder.pinned);
            try {
                await toggleBinderPinApi(binderId, nextPinned);
                renderBinderDashboard(document.getElementById('binderDashboardSearch')?.value || '');
            } catch (error) {
                showToast(error.message, 'error');
            }
            return;
        }

        const card = e.target.closest('.dashboard-binder-card');
        if (!card || !card.dataset.binderId) return;
        await fetchBinder(card.dataset.binderId);
        updateBinderSelectors();
        state.modals.binderDashboard.classList.remove('active');
    });

    const openBinderModal = () => {
        document.getElementById('binderForm').reset();
        state.modals.binder.classList.add('active');
        setTimeout(() => document.getElementById('binderTitle').focus(), 50);
    };
    document.getElementById('btnNewBinder')?.addEventListener('click', openBinderModal);

    document.getElementById('btnToggleImagePaste')?.addEventListener('click', async () => {
        const nextValue = !state.settings.allow_image_paste;
        try {
            await updateSettingsApi({ allow_image_paste: nextValue });
            refreshImagePasteToggleButton();
            showToast(`クリップボード画像の貼り付けを${nextValue ? '有効' : '無効'}にしました`, 'info');
        } catch (e) {
            showToast('設定の更新に失敗しました', 'error');
        }
    });
    document.getElementById('btnNewBinderDashboard')?.addEventListener('click', () => {
        state.modals.binderDashboard.classList.remove('active');
        openBinderModal();
    });

    document.getElementById('ctx-new-task-card')?.addEventListener('click', () => {
        document.getElementById('contextMenu').style.display = 'none';
        openResourceModal(null);
        document.getElementById('resourceTags').value = 'Task';
    });

    document.getElementById('ctx-new-memo-card')?.addEventListener('click', () => {
        document.getElementById('contextMenu').style.display = 'none';
        openResourceModal(null);
        document.getElementById('resourceTags').value = 'メモ';
    });

    document.getElementById('ctx-paste')?.addEventListener('click', async () => {
        document.getElementById('contextMenu').style.display = 'none';

        if (state.isPasteProcessing) return;
        state.isPasteProcessing = true;

        try {
            const text = await navigator.clipboard.readText();
            if (text) {
                const lines = text.split(/\r?\n/).map(line => line.trim()).filter(line => line.length > 0);
                const validPaths = [];
                const pathRegex = /^"?([A-Za-z]:\\|\/)[^\0]+"?$/;
                for (const line of lines) {
                    if (pathRegex.test(line)) {
                        validPaths.push(line.replace(/^"|"$/g, ''));
                    }
                }
                if (validPaths.length > 0) {
                    await handlePastedPaths(validPaths, state.ctxMouseX || 100, state.ctxMouseY || 100);
                } else {
                    showToast('クリップボードに有効なパスが見つかりません', 'info');
                }
            }
        } catch (e) {
            console.error('Failed to read clipboard', e);
            showToast('クリップボードの読み取りに失敗しました。Ctrl+Vを使用してください。', 'error');
        } finally {
            state.isPasteProcessing = false;
        }
    });

    document.getElementById('ctx-reset-viewport')?.addEventListener('click', resetViewport);

    document.getElementById('btnExportHtml')?.addEventListener('click', async () => {
        showToast('💾 HTMLスナップショットを生成中...', 'info');

        const binderContainer = document.getElementById('binder-container');
        if (!binderContainer) return;

        try {
            let cssText = '';
            for (const sheet of document.styleSheets) {
                try {
                    const rules = sheet.cssRules || sheet.rules;
                    if (rules) {
                        for (const rule of rules) {
                            cssText += rule.cssText + '\n';
                        }
                    }
                } catch (e) {
                    console.warn('Cross-origin stylesheet ignored:', sheet.href);
                }
            }

            const clone = binderContainer.cloneNode(true);
            await inlineCardBackgroundImages(clone);

            const binderName = state.currentBinder?.title || 'Untitled';
            const htmlContent = `<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>LoreBinder Snapshot - ${escapeHtml(binderName)}</title>
    <style>
        body { background-color: #1a1d21; margin: 0; padding: 0; overflow: auto; }
        ${cssText}
    </style>
</head>
<body>
    ${clone.outerHTML}
</body>
</html>`;

            const blob = new Blob([htmlContent], { type: 'text/html;charset=utf-8' });
            const url = window.URL.createObjectURL(blob);
            const a = document.createElement('a');
            a.style.display = 'none';
            a.href = url;

            const safeName = binderName.replace(/[\\/:*?"<>|]/g, '_');
            const timestamp = new Date().toISOString().replace(/[:.]/g, '-').slice(0, 19);
            a.download = `LoreBinder_${safeName}_${timestamp}.html`;

            document.body.appendChild(a);
            a.click();

            setTimeout(() => {
                document.body.removeChild(a);
                window.URL.revokeObjectURL(url);
            }, 100);

            showToast('✅ HTMLスナップショットを保存しました！', 'success');
        } catch (error) {
            console.error('Export HTML failed:', error);
            showToast('エクスポートに失敗しました', 'error');
        }
    });

    document.getElementById('ctx-edit-binder')?.addEventListener('click', () => {
        document.getElementById('contextMenu').style.display = 'none';
        const titleEl = document.getElementById('binder-display-title');
        if (titleEl) {
            titleEl.dispatchEvent(new MouseEvent('dblclick', { bubbles: true }));
        }
    });

    document.getElementById('ctx-edit-card')?.addEventListener('click', () => {
        document.getElementById('contextMenu').style.display = 'none';
        if (state.ctxTargetId) {
            const res = state.currentCards.find(r => r.id === state.ctxTargetId);
            if (res) openResourceModal(res);
        }
    });

    document.getElementById('ctx-delete-card')?.addEventListener('click', () => {
        document.getElementById('contextMenu').style.display = 'none';
        if (state.ctxTargetCard) {
            showDeleteConfirmModal([state.ctxTargetCard]);
        }
    });

    document.getElementById('ctx-bulk-delete')?.addEventListener('click', () => {
        document.getElementById('contextMenu').style.display = 'none';
        const selected = window.BinderManager ? window.BinderManager.getSelectedCards() : [];
        if (selected.length > 0) {
            showDeleteConfirmModal(selected);
        }
    });

    document.getElementById('ctx-bulk-resize')?.addEventListener('click', () => {
        document.getElementById('contextMenu').style.display = 'none';

        const targetCard = state.ctxTargetCard;
        if (!targetCard) return;

        document.getElementById('bulkResizeWidth').value = targetCard.offsetWidth;
        document.getElementById('bulkResizeHeight').value = targetCard.offsetHeight;

        state.modals.bulkResize.classList.add('active');
        setTimeout(() => document.getElementById('bulkResizeWidth').focus(), 50);
    });

    bindGuardedFormSubmit(document.getElementById('bulkResizeForm'), async () => {
        const width = parseFloat(document.getElementById('bulkResizeWidth').value);
        const height = parseFloat(document.getElementById('bulkResizeHeight').value);

        if (!Number.isFinite(width) || !Number.isFinite(height) || width <= 0 || height <= 0) {
            showToast('幅と高さには正しい数値を入力してください', 'error');
            return;
        }

        const selected = window.BinderManager ? window.BinderManager.getSelectedCards() : [];
        const targets = selected.filter(c => !c.classList.contains('binder-zone'));

        if (targets.length === 0) {
            showToast('サイズを統一できるカードが選択されていません', 'error');
            return;
        }

        targets.forEach(c => {
            c.style.width = `${width}px`;
            c.style.height = `${height}px`;
        });

        closeModal('bulkResizeModal');
        await savePlacements();

        showToast(`${targets.length}件のカードのサイズを統一しました`, 'success');
    });

    document.getElementById('ctx-bulk-tag')?.addEventListener('click', () => {
        document.getElementById('contextMenu').style.display = 'none';
        if (state.ctxTargetCard && !state.ctxTargetCard.classList.contains('selected')) {
            if (window.BinderManager && window.BinderManager.selectCard) {
                window.BinderManager.selectCard(state.ctxTargetCard);
            }
        }
        const selected = window.BinderManager ? window.BinderManager.getSelectedCards() : [];
        if (selected.length === 0) return;

        const ids = selected
            .filter(card => (!card.dataset.type || card.dataset.type === 'card'))
            .map(card => card.dataset.id);

        openBulkTagModalForIds(ids, 'main');
    });

    document.querySelectorAll('#ctx-status-card .context-menu-item').forEach(item => {
        item.addEventListener('click', async (e) => {
            document.getElementById('contextMenu').style.display = 'none';
            e.stopPropagation();
            const status = e.target.dataset.status;
            if (state.ctxTargetId && status !== undefined) {
                const res = state.currentCards.find(r => r.id === state.ctxTargetId);
                if (res) {
                    try {
                        await fetch(`${API_BASE}/cards/${state.ctxTargetId}/status`, {
                            method: 'PATCH',
                            headers: { 'Content-Type': 'application/json' },
                            body: JSON.stringify({ status: status, status_binder_id: state.currentBinder.id })
                        });
                        res.status = status;
                        res.status_binder_id = state.currentBinder.id;
                        
                        let badge = state.ctxTargetCard.querySelector('.card-status-badge');
                        if (!status || status === 'none') {
                            if (badge) badge.remove();
                        } else {
                            if (!badge) {
                                badge = document.createElement('div');
                                badge.className = `card-status-badge status-${status.toLowerCase()}`;
                                const header = state.ctxTargetCard.querySelector('.card-header');
                                if (header) header.appendChild(badge);
                            }
                            badge.className = `card-status-badge status-${status.toLowerCase()}`;
                            badge.textContent = status;
                        }

                        const spStamp = state.ctxTargetCard.querySelector('.sp-task-stamp');
                        if (spStamp) {
                            if (!status || status === 'none') {
                                spStamp.textContent = '';
                                spStamp.className = 'sp-task-stamp status-none';
                            } else {
                                spStamp.textContent = status.toUpperCase();
                                spStamp.className = `sp-task-stamp status-${status.toLowerCase()}`;
                            }
                        }
                        showToast(`ステータスを ${status} に変更しました`, 'success');
                        
                        await savePlacements();
                        checkHazardWarning();
                    } catch (err) {
                        showToast('ステータスの更新に失敗しました', 'error');
                    }
                }
            }
        });
    });

    document.getElementById('ctx-toggle-pin')?.addEventListener('click', async () => {
        document.getElementById('contextMenu').style.display = 'none';
        const card = state.ctxTargetCard;
        if (!card) return;

        const pinned = !card.classList.contains('is-pinned');
        card.classList.toggle('is-pinned', pinned);
        card.dataset.pinned = pinned ? 'true' : 'false';

        showToast(pinned ? '⚓ カードをピン留めしました' : 'ピン留めを解除しました', 'success');
        await savePlacements();
    });

    document.getElementById('ctx-binder-link')?.addEventListener('click', () => {
        document.getElementById('contextMenu').style.display = 'none';
        const container = document.getElementById('binderLinkListContainer');
        const searchInput = document.getElementById('binderLinkSearch');
        const btnSubmit = document.getElementById('btnSubmitBinderLink');

        if (container && searchInput && btnSubmit) {
            const targetBinders = state.binderList.filter(b => b.id !== (state.currentBinder ? state.currentBinder.id : null));
            
            container.innerHTML = '';
            searchInput.value = '';
            btnSubmit.disabled = true;
            btnSubmit.textContent = `配置する (0)`;

            if (targetBinders.length === 0) {
                container.innerHTML = '<div style="padding:10px; color:#aaa; text-align:center;">配置可能なバインダーがありません</div>';
            } else {
                targetBinders.forEach(b => {
                    const item = document.createElement('label');
                    item.className = 'binder-link-list-item';
                    
                    const cb = document.createElement('input');
                    cb.type = 'checkbox';
                    cb.value = b.id;
                    cb.className = 'binder-link-cb';
                    
                    cb.addEventListener('change', () => {
                        const checked = container.querySelectorAll('.binder-link-cb:checked').length;
                        btnSubmit.disabled = checked === 0;
                        btnSubmit.textContent = `配置する (${checked})`;
                    });

                    const title = document.createElement('span');
                    title.className = 'binder-link-list-item-title';
                    title.textContent = b.title || 'Untitled';

                    const desc = document.createElement('span');
                    desc.className = 'binder-link-list-item-desc';
                    desc.textContent = b.description || '';

                    item.appendChild(cb);
                    item.appendChild(title);
                    item.appendChild(desc);
                    container.appendChild(item);
                });
            }
        }
        
        updateBinderSelectors();
        state.modals.binderLink.classList.add('active');
        setTimeout(() => document.getElementById('binderLinkSearch')?.focus(), 50);
    });

    document.getElementById('binderLinkSearch')?.addEventListener('input', (e) => {
        const term = e.target.value.toLowerCase();
        const items = document.querySelectorAll('.binder-link-list-item');
        items.forEach(item => {
            const title = item.querySelector('.binder-link-list-item-title')?.textContent.toLowerCase() || '';
            const desc = item.querySelector('.binder-link-list-item-desc')?.textContent.toLowerCase() || '';
            if (title.includes(term) || desc.includes(term)) {
                item.style.display = 'grid';
            } else {
                item.style.display = 'none';
            }
        });
    });

    bindGuardedFormSubmit(document.getElementById('binderForm'), async () => {
        const title = document.getElementById('binderTitle').value;
        const description = document.getElementById('binderDescription').value;
        try {
            const res = await fetch(`${API_BASE}/binderes`, {
                method: 'POST',
                headers: { 'Content-Type': 'application/json' },
                body: JSON.stringify({ title, description })
            });
            if (res.ok) {
                const newBinder = await res.json();
                showToast('バインダーを作成しました', 'success');
                closeModal('binderModal');
                await fetchBinderList();
                await fetchBinder(newBinder.id);
                updateBinderSelectors();
            }
        } catch (error) {
            showToast('作成に失敗しました', 'error');
        }
    });

    bindGuardedFormSubmit(document.getElementById('binderLinkForm'), async () => {
        const checkedCbs = document.querySelectorAll('.binder-link-cb:checked');
        if (checkedCbs.length === 0 || !state.currentBinder) return;
        
        const rect = document.getElementById('binder-container').getBoundingClientRect();
        let scale = window.BinderManager ? window.BinderManager.getScale() : 1;
        let trans = window.BinderManager ? window.BinderManager.getTranslate() : {x:0, y:0};
        const centerX = (rect.width / 2 - trans.x) / scale;
        const centerY = (rect.height / 2 - trans.y) / scale;
        const maxRadius = Math.min(rect.width, rect.height) / 2 / scale * 0.8;

        const CARD_W = 200; // binder link card default width
        const CARD_H = 150;
        
        state.currentBinder.placements = state.currentBinder.placements || [];

        Array.from(checkedCbs).forEach((cb, idx) => {
            const targetId = cb.value;
            let targetX, targetY;
            
            if (idx === 0 && checkedCbs.length === 1) {
                targetX = centerX;
                targetY = centerY;
            } else {
                const r = Math.sqrt(Math.random()) * maxRadius; 
                const theta = Math.random() * 2 * Math.PI;
                targetX = centerX + r * Math.cos(theta);
                targetY = centerY + r * Math.sin(theta);
            }

            state.currentBinder.placements.push({
                id: targetId,
                type: 'binder',
                x: targetX,
                y: targetY,
                width: CARD_W,
                height: CARD_H
            });
        });

        renderBinder();
        await savePlacements();

        showToast(`${checkedCbs.length}件のバインダーリンクを配置しました`, 'success');
        closeModal('binderLinkModal');
    });

    const titleEl = document.getElementById('binder-display-title');
    const descEl = document.getElementById('binder-display-desc');
    
    if (titleEl) {
        titleEl.addEventListener('dblclick', () => {
            if (!state.currentBinder) return;
            const input = document.createElement('input');
            input.type = 'text';
            input.value = state.currentBinder.title || '';
            input.className = 'form-control';
            input.style.fontSize = '1.5rem';
            input.style.fontWeight = 'bold';
            input.style.width = '100%';
            input.style.background = 'rgba(0,0,0,0.5)';
            input.style.color = 'white';
            
            const save = async () => {
                const newTitle = input.value.trim();
                if (newTitle && newTitle !== state.currentBinder.title) {
                    await updateBinderInfo(newTitle, state.currentBinder.description);
                }
                titleEl.textContent = state.currentBinder.title;
                titleEl.style.display = 'block';
                input.remove();
            };

            input.addEventListener('blur', save);
            input.addEventListener('keypress', (e) => {
                if (e.key === 'Enter') {
                    input.blur();
                }
            });

            titleEl.style.display = 'none';
            titleEl.parentNode.insertBefore(input, titleEl.nextSibling);
            input.focus();
        });
    }

    if (descEl) {
        descEl.addEventListener('dblclick', () => {
            if (!state.currentBinder) return;
            const textarea = document.createElement('textarea');
            textarea.value = state.currentBinder.description || '';
            textarea.className = 'form-control';
            textarea.rows = 15;
            textarea.style.width = '40ch';
            textarea.style.maxWidth = '100%';
            textarea.style.resize = 'vertical';
            textarea.style.fontSize = '0.9rem';
            textarea.style.background = 'rgba(0,0,0,0.5)';
            textarea.style.color = 'white';
            
            const save = async () => {
                const newDesc = textarea.value.trim();
                if (newDesc !== (state.currentBinder.description || '')) {
                    await updateBinderInfo(state.currentBinder.title, newDesc);
                }
                descEl.textContent = state.currentBinder.description || '説明はありません';
                descEl.style.display = 'block';
                textarea.remove();
            };

            textarea.addEventListener('blur', save);

            descEl.style.display = 'none';
            descEl.parentNode.insertBefore(textarea, descEl.nextSibling);
            textarea.focus();
        });
    }

    document.querySelectorAll('.has-submenu').forEach(item => {
        item.addEventListener('mouseenter', () => {
            const submenu = item.querySelector('.context-submenu');
            if (!submenu) return;
            
            submenu.style.left = '100%';
            submenu.style.right = 'auto';
            submenu.style.top = '0';
            submenu.style.bottom = 'auto';
            
            const rect = submenu.getBoundingClientRect();
            
            if (rect.right > window.innerWidth) {
                submenu.style.left = 'auto';
                submenu.style.right = '100%';
            }
            
            if (rect.bottom > window.innerHeight) {
                submenu.style.top = 'auto';
                submenu.style.bottom = '0';
            }
        });
    });

}
