import { buildCardTransform } from './utils.js';
import { showConfirmDialog } from './modals.js';
import { state } from './state.js';
import { updateSettingsApi } from './api.js';
import { refreshImagePasteToggleButton } from './eventListeners.js';

export function initCanvas() {
    const container = document.getElementById('binder-container');
    const content = document.getElementById('binder-content');
    const bg = document.querySelector('.binder-bg');

    if (!container || !content) return;

    let scale = 1;
    let translateX = 0;
    let translateY = 0;

    let maxZoneZIndex = 10;
    let maxCardZIndex = 1000;

    let isPanning = false;
    let startX = 0;
    let startY = 0;

    let viewportSaveTimer = null;
    const debouncedSaveViewport = () => {
        clearTimeout(viewportSaveTimer);
        viewportSaveTimer = setTimeout(() => {
            if (window.App && window.App.saveViewport) {
                window.App.saveViewport(scale, translateX, translateY);
            }
        }, 500);
    };

    let rotationSaveTimer = null;
    const debouncedSaveRotation = () => {
        clearTimeout(rotationSaveTimer);
        rotationSaveTimer = setTimeout(() => {
            if (window.App && window.App.savePlacements) {
                window.App.savePlacements();
            }
        }, 500);
    };

    let isSelecting = false;
    let selStartX = 0;
    let selStartY = 0;
    let initialSelectedCardsForSelection = new Set();
    let isSpacePressed = false;

    const selectionBox = document.createElement('div');
    selectionBox.className = 'selection-box';
    selectionBox.style.position = 'absolute';
    selectionBox.style.border = '1px solid rgba(123, 176, 255, 0.8)';
    selectionBox.style.backgroundColor = 'rgba(123, 176, 255, 0.2)';
    selectionBox.style.pointerEvents = 'none';
    selectionBox.style.zIndex = '9999';
    selectionBox.style.display = 'none';
    container.appendChild(selectionBox);

    window.addEventListener('keydown', (e) => {
        const tagName = e.target.tagName.toLowerCase();
        if (tagName === 'input' || tagName === 'textarea' || tagName === 'select') return;

        if (e.code === 'Space') {
            isSpacePressed = true;
            if (!isPanning && !isSelecting) {
                container.style.cursor = 'grab';
            }
            e.preventDefault();
        }
    });

    window.addEventListener('keyup', (e) => {
        if (e.code === 'Space') {
            isSpacePressed = false;
            if (!isPanning && !isSelecting) {
                container.style.cursor = 'default';
            }
        }
    });

    const updateTransform = () => {
        content.style.transform = `translate(${translateX}px, ${translateY}px) scale(${scale})`;
        if (bg) {
            bg.style.backgroundPosition = `${translateX}px ${translateY}px`;
            bg.style.backgroundSize = `${40 * scale}px ${40 * scale}px`;
        }
    };

    updateTransform();

    container.addEventListener('mousedown', (e) => {
        if (state.gravityLocked) return;
        if (e.target !== container && e.target !== content && e.target !== bg && !e.target.classList.contains('binder-zone')) {
            return;
        }

        if (e.target.classList.contains('binder-zone')) {
            const zoneRect = e.target.getBoundingClientRect();
            const RESIZE_GRIP_SIZE = 16;
            const isOnResizeGrip =
                (zoneRect.right - e.clientX) <= RESIZE_GRIP_SIZE &&
                (zoneRect.bottom - e.clientY) <= RESIZE_GRIP_SIZE;
            if (isOnResizeGrip) {
                return;
            }
        }

        if (isSpacePressed || e.button === 1) {
            isPanning = true;
            document.body.classList.add('is-panning');
            startX = e.clientX - translateX;
            startY = e.clientY - translateY;
            container.style.cursor = 'grabbing';
        } else if (e.button === 0) {
            isSelecting = true;
            document.body.classList.add('is-selecting');
            const rect = container.getBoundingClientRect();
            selStartX = e.clientX - rect.left;
            selStartY = e.clientY - rect.top;

            selectionBox.style.left = `${selStartX}px`;
            selectionBox.style.top = `${selStartY}px`;
            selectionBox.style.width = '0px';
            selectionBox.style.height = '0px';
            selectionBox.style.display = 'block';

            if (!e.shiftKey && !e.ctrlKey && !e.metaKey) {
                clearSelection();
            }
            initialSelectedCardsForSelection = new Set(selectedCards);
        }
    });

    window.addEventListener('mousemove', (e) => {
        if (resizingCards.length > 0) {
            const dxScreen = (e.clientX - resizeStartMouseX) / scale;
            const dyScreen = (e.clientY - resizeStartMouseY) / scale;
            resizingCards.forEach(rc => {
                const rad = rc.startRotation * Math.PI / 180;
                const cos = Math.cos(rad);
                const sin = Math.sin(rad);
                const dx = dxScreen * cos + dyScreen * sin;
                const dy = -dxScreen * sin + dyScreen * cos;
                const newWidth = Math.max(CARD_MIN_WIDTH, rc.startWidth + dx);
                const newHeight = Math.max(CARD_MIN_HEIGHT, rc.startHeight + dy);
                rc.card.style.width = `${newWidth}px`;
                rc.card.style.height = `${newHeight}px`;
            });
            return;
        }

        if (rotatingCard) {
            const currentAngle = Math.atan2(e.clientY - rotateCenterY, e.clientX - rotateCenterX) * 180 / Math.PI;
            let newRotation = Math.round(rotateStartRotation + (currentAngle - rotateStartAngle));
            newRotation = ((newRotation % 360) + 360) % 360; // 0〜359度に正規化
            rotatingCard.dataset.rotation = newRotation;
            rotatingCard.style.setProperty('--card-rotation', `${newRotation}deg`);
            rotatingCard.style.transform = buildCardTransform(rotatingCard.dataset.x, rotatingCard.dataset.y, newRotation);
            return;
        }

        if (isPanning) {
            translateX = e.clientX - startX;
            translateY = e.clientY - startY;
            updateTransform();
            debouncedSaveViewport();
            return;
        }

        if (isSelecting) {
            const rect = container.getBoundingClientRect();
            let currentX = e.clientX - rect.left;
            let currentY = e.clientY - rect.top;

            currentX = Math.max(0, Math.min(currentX, rect.width));
            currentY = Math.max(0, Math.min(currentY, rect.height));

            const left = Math.min(selStartX, currentX);
            const top = Math.min(selStartY, currentY);
            const width = Math.abs(currentX - selStartX);
            const height = Math.abs(currentY - selStartY);

            selectionBox.style.left = `${left}px`;
            selectionBox.style.top = `${top}px`;
            selectionBox.style.width = `${width}px`;
            selectionBox.style.height = `${height}px`;

            const selRect = {
                left: rect.left + left,
                top: rect.top + top,
                right: rect.left + left + width,
                bottom: rect.top + top + height
            };

            const cards = getExistingCards();
            cards.forEach(card => {
                const cardRect = card.getBoundingClientRect();
                const overlap = isOverlapping(selRect, cardRect);

                if (overlap) {
                    if (!selectedCards.has(card)) {
                        card.classList.add('selected');
                        selectedCards.add(card);
                    }
                } else {
                    if (!initialSelectedCardsForSelection.has(card)) {
                        card.classList.remove('selected');
                        selectedCards.delete(card);
                    }
                }
            });
            return;
        }

        if (draggingCards.length > 0) {
            const dx = (e.clientX - dragStartX) / scale;
            const dy = (e.clientY - dragStartY) / scale;

            draggingCards.forEach(c => {
                const initialPos = initialCardPositions.get(c);
                if (initialPos) {
                    const newX = initialPos.x + dx;
                    const newY = initialPos.y + dy;
                    c.dataset.x = newX;
                    c.dataset.y = newY;
                    c.style.transform = buildCardTransform(newX, newY, c.dataset.rotation);
                }
            });

            if (!draggingCards.some(c => c.classList.contains('binder-zone'))) {
                const zones = Array.from(content.querySelectorAll('.binder-zone'));
                const portals = Array.from(content.querySelectorAll('.portal-card'));
                zones.forEach(zone => zone.classList.remove('zone-highlight'));
                portals.forEach(portal => {
                    portal.classList.remove('portal-highlight');
                    portal.style.transform = buildCardTransform(portal.dataset.x, portal.dataset.y, portal.dataset.rotation, 1);
                });

                let highlightedPortal = false;
                let highlightedZone = null;

                for (const c of draggingCards) {
                    const cX = parseFloat(c.dataset.x || 0) + (c.offsetWidth / 2);
                    const cY = parseFloat(c.dataset.y || 0) + (c.offsetHeight / 2);

                    if (!highlightedPortal) {
                        for (const portal of portals) {
                            if (draggingCards.includes(portal)) continue;
                            const pX = parseFloat(portal.dataset.x || 0);
                            const pY = parseFloat(portal.dataset.y || 0);
                            const pW = portal.offsetWidth || 200;
                            const pH = portal.offsetHeight || 150;
                            if (cX >= pX && cX <= pX + pW &&
                                cY >= pY && cY <= pY + pH) {
                                portal.classList.add('portal-highlight');
                                portal.style.transform = buildCardTransform(pX, pY, portal.dataset.rotation, 3);
                                highlightedPortal = true;
                                break;
                            }
                        }
                    }

                    if (!highlightedPortal && !highlightedZone) {
                        for (const zone of zones) {
                            const zX = parseFloat(zone.dataset.x || 0);
                            const zY = parseFloat(zone.dataset.y || 0);
                            if (cX >= zX && cX <= zX + zone.offsetWidth &&
                                cY >= zY && cY <= zY + zone.offsetHeight) {
                                zone.classList.add('zone-highlight');
                                highlightedZone = zone;
                                break;
                            }
                        }
                    }
                }
            }
        }
    });

    window.addEventListener('mouseup', (e) => {
        if (resizingCards.length > 0) {
            resizingCards.forEach(rc => { rc.card.style.transition = ''; });
            resizingCards = [];
            document.body.classList.remove('is-resizing');
            if (window.App && window.App.savePlacements) {
                window.App.savePlacements();
            }
            return;
        }

        if (rotatingCard) {
            rotatingCard.style.transition = '';
            rotatingCard = null;
            if (window.App && window.App.savePlacements) {
                window.App.savePlacements();
            }
            return;
        }

        if (isPanning) {
            isPanning = false;
            document.body.classList.remove('is-panning');
            container.style.cursor = isSpacePressed ? 'grab' : 'default';
        }
        if (isSelecting) {
            isSelecting = false;
            document.body.classList.remove('is-selecting');
            selectionBox.style.display = 'none';
            dispatchSelectionChange();
        }

        if (draggingCards.length > 0) {
            document.querySelectorAll('.binder-zone.zone-highlight').forEach(z => z.classList.remove('zone-highlight'));

            let positionChanged = false;
            const zones = Array.from(content.querySelectorAll('.binder-zone'));
            const portals = Array.from(content.querySelectorAll('.portal-card'));

            let droppedPortal = null;

            if (!draggingCards.some(c => c.classList.contains('binder-zone'))) {
                for (const c of draggingCards) {
                    const cRect = c.getBoundingClientRect();
                    const cCenterX = cRect.left + cRect.width / 2;
                    const cCenterY = cRect.top + cRect.height / 2;

                    for (const portal of portals) {
                        if (draggingCards.includes(portal)) continue;
                        const portalRect = portal.getBoundingClientRect();
                        if (cCenterX >= portalRect.left && cCenterX <= portalRect.right &&
                            cCenterY >= portalRect.top && cCenterY <= portalRect.bottom) {
                            droppedPortal = portal;
                            break;
                        }
                    }
                    if (droppedPortal) break; // 1つでもポータルに重なっていればOK
                }
            }

            if (droppedPortal) {
                portals.forEach(p => {
                    if (p !== droppedPortal) {
                        p.classList.remove('portal-highlight');
                        p.style.transform = buildCardTransform(p.dataset.x, p.dataset.y, p.dataset.rotation, 1);
                    }
                });

                const cardsData = draggingCards.map(c => {
                    return {
                        element: c,
                        initialPos: initialCardPositions.get(c)
                    };
                });

                if (window.App && window.App.handlePortalDrop) {
                    window.App.handlePortalDrop(cardsData, droppedPortal);
                }

                draggingCards.forEach(c => {
                    endDragging(c);
                });

            } else {
                portals.forEach(p => {
                    p.classList.remove('portal-highlight');
                    p.style.transform = buildCardTransform(p.dataset.x, p.dataset.y, p.dataset.rotation, 1);
                });

                draggingCards.forEach(c => {
                    endDragging(c);
                    c.style.transition = ''; // restore css transition

                    const initialPos = initialCardPositions.get(c);
                    if (initialPos && (initialPos.x !== parseFloat(c.dataset.x) || initialPos.y !== parseFloat(c.dataset.y))) {
                        positionChanged = true;

                        if (c.classList.contains('binder-zone') && c.magnetCards && c.magnetCards.length > 0) {
                            const dx = parseFloat(c.dataset.x) - initialPos.x;
                            const dy = parseFloat(c.dataset.y) - initialPos.y;

                            c.magnetCards.forEach(mc => {
                                if (!draggingCards.includes(mc)) {
                                    const mx = parseFloat(mc.dataset.x || 0);
                                    const my = parseFloat(mc.dataset.y || 0);
                                    let newX = mx + dx;
                                    let newY = my + dy;

                                    const zoneX = parseFloat(c.dataset.x);
                                    const zoneY = parseFloat(c.dataset.y);
                                    const zoneW = c.offsetWidth || 300;
                                    const zoneH = c.offsetHeight || 500;
                                    const cardW = mc.offsetWidth || 250;
                                    const cardH = mc.offsetHeight || 150;

                                    const paddingX = 10;
                                    const paddingTop = 60; // ヘッダーの高さ考慮
                                    const paddingBottom = 10;

                                    const minX = zoneX + paddingX;
                                    const maxX = Math.max(minX, zoneX + zoneW - cardW - paddingX);
                                    const minY = zoneY + paddingTop;
                                    const maxY = Math.max(minY, zoneY + zoneH - cardH - paddingBottom);

                                    newX = Math.max(minX, Math.min(newX, maxX));
                                    newY = Math.max(minY, Math.min(newY, maxY));

                                    mc.dataset.x = newX;
                                    mc.dataset.y = newY;

                                    maxCardZIndex++;
                                    mc.style.zIndex = maxCardZIndex;

                                    mc.style.transition = 'transform 0.5s cubic-bezier(0.175, 0.885, 0.32, 1.275)';
                                    mc.style.transform = buildCardTransform(newX, newY, mc.dataset.rotation);

                                    setTimeout(() => {
                                        mc.style.transition = '';
                                    }, 500);
                                }
                            });
                        }
                    }

                    if (c.classList.contains('binder-zone')) {
                        c.magnetCards = null;
                    }

                    if (!c.classList.contains('binder-zone')) {
                        const cardRect = c.getBoundingClientRect();
                        let droppedZone = null;

                        const cardCenterX = cardRect.left + cardRect.width / 2;
                        const cardCenterY = cardRect.top + cardRect.height / 2;

                        for (const zone of zones) {
                            const zoneRect = zone.getBoundingClientRect();
                            if (cardCenterX >= zoneRect.left && cardCenterX <= zoneRect.right &&
                                cardCenterY >= zoneRect.top && cardCenterY <= zoneRect.bottom) {
                                droppedZone = zone;
                                break;
                            }
                        }

                        if (window.App && window.App.handleCardDrop) {
                            window.App.handleCardDrop(c, droppedZone, initialPos);
                        }
                    }
                });
            }

            if (positionChanged && window.App && window.App.savePlacements) {
                window.App.savePlacements();
            }

            draggingCards = [];
        }

        if (pendingDeselectCard) {
            const movedDistance = Math.hypot(e.clientX - dragStartX, e.clientY - dragStartY);
            if (movedDistance < CLICK_DRAG_THRESHOLD) {
                pendingDeselectCard.classList.remove('selected');
                selectedCards.delete(pendingDeselectCard);
                dispatchSelectionChange();
            }
            pendingDeselectCard = null;
        }
    });

    container.addEventListener('wheel', (e) => {
        e.preventDefault();
        if (state.gravityLocked) return;

        if (e.shiftKey && selectedCards.size === 1) {
            const card = selectedCards.values().next().value;
            const rotationStep = 1; // 1度単位の自由回転
            const current = Number(card.dataset.rotation) || 0;
            let next = current + (e.deltaY > 0 ? rotationStep : -rotationStep);
            next = ((next % 360) + 360) % 360; // 0〜359度に正規化
            card.dataset.rotation = next;
            card.style.setProperty('--card-rotation', `${next}deg`);
            card.style.transform = buildCardTransform(card.dataset.x, card.dataset.y, next);
            debouncedSaveRotation();
            return;
        }

        const zoomSensitivity = 0.001;
        const delta = -e.deltaY * zoomSensitivity;

        let newScale = scale * Math.exp(delta);

        newScale = Math.max(0.1, Math.min(newScale, 5));

        const rect = container.getBoundingClientRect();
        const mouseX = e.clientX - rect.left;
        const mouseY = e.clientY - rect.top;

        const ratio = 1 - newScale / scale;

        translateX += (mouseX - translateX) * ratio;
        translateY += (mouseY - translateY) * ratio;

        scale = newScale;

        updateTransform();
        debouncedSaveViewport();
    }, { passive: false });


    const getExistingCards = () => {
        return Array.from(content.querySelectorAll('.binder-card'));
    };

    const isOverlapping = (rect1, rect2) => {
        return !(rect1.right < rect2.left ||
            rect1.left > rect2.right ||
            rect1.bottom < rect2.top ||
            rect1.top > rect2.bottom);
    };

    const findSafePosition = (targetX, targetY, width = 250, height = 150) => {
        const cards = getExistingCards().map(c => {
            return {
                left: parseFloat(c.dataset.x || 0),
                top: parseFloat(c.dataset.y || 0),
                right: parseFloat(c.dataset.x || 0) + (c.offsetWidth || 250),
                bottom: parseFloat(c.dataset.y || 0) + (c.offsetHeight || 150)
            };
        });

        let currentX = targetX;
        let currentY = targetY;
        let angle = 0;
        let radius = 0;
        const step = 20; // 少しずつ広げていく

        for (let i = 0; i < 1000; i++) {
            const rect = {
                left: currentX,
                top: currentY,
                right: currentX + width,
                bottom: currentY + height
            };

            let overlap = false;
            for (const card of cards) {
                if (isOverlapping(rect, card)) {
                    overlap = true;
                    break;
                }
            }

            if (!overlap) {
                return { x: currentX, y: currentY };
            }

            angle += 0.5;
            radius += step * 0.1;
            currentX = targetX + Math.cos(angle) * radius;
            currentY = targetY + Math.sin(angle) * radius;
        }

        return { x: targetX + 20, y: targetY + 20 };
    };

    let selectedCards = new Set();
    let draggingCards = [];
    let dragStartX = 0;
    let dragStartY = 0;
    let initialCardPositions = new Map();

    let pendingDeselectCard = null;
    const CLICK_DRAG_THRESHOLD = 4; // px（スクリーン座標・scale非依存）

    let rotatingCard = null;
    let rotateCenterX = 0;
    let rotateCenterY = 0;
    let rotateStartAngle = 0;
    let rotateStartRotation = 0;

    let resizingCards = [];
    let resizeStartMouseX = 0;
    let resizeStartMouseY = 0;
    const CARD_MIN_WIDTH = 150;
    const CARD_MIN_HEIGHT = 100;

    const endDragging = (card) => {
        card.classList.remove('dragging');
        const twineTag = card.querySelector('.sp-portal-twine-tag');
        if (twineTag) {
            card.classList.add('drag-settling');
            twineTag.addEventListener('animationend', () => {
                card.classList.remove('drag-settling');
            }, { once: true });
        }
    };

    const dispatchSelectionChange = () => {
        window.dispatchEvent(new CustomEvent('binder-selection-changed', {
            detail: { selectedCards: Array.from(selectedCards) }
        }));
    };

    const clearSelection = () => {
        const wasEmpty = selectedCards.size === 0;
        selectedCards.forEach(c => c.classList.remove('selected'));
        selectedCards.clear();
        if (!wasEmpty) dispatchSelectionChange();
    };

    const toggleSelection = (card, multiSelect) => {
        if (!multiSelect && !selectedCards.has(card)) {
            clearSelection();
        }
        if (selectedCards.has(card) && multiSelect) {
            card.classList.remove('selected');
            selectedCards.delete(card);
        } else {
            card.classList.add('selected');
            selectedCards.add(card);
        }
        dispatchSelectionChange();
    };

    const BinderManager = {
        addCard: (html, x, y, options = {}) => {
            const safePos = options.skipCollision ? { x, y } : findSafePosition(x, y, options.width, options.height);

            const card = document.createElement('div');
            card.className = 'binder-card glass';
            if (options.isPortal) {
                card.classList.add('portal-card');
                card.dataset.targetBinderId = options.targetBinderId;
            }
            if (options.customClass) {
                card.classList.add(options.customClass);
            }
            card.innerHTML = html;

            const resizeHandle = document.createElement('div');
            resizeHandle.className = 'card-resize-handle';
            resizeHandle.addEventListener('mousedown', (e) => {
                if (e.button !== 0) return;
                const targets = selectedCards.has(card)
                    ? Array.from(selectedCards).filter(c => !c.classList.contains('binder-zone'))
                    : [card];
                resizingCards = targets.map(c => ({
                    card: c,
                    startWidth: c.offsetWidth,
                    startHeight: c.offsetHeight,
                    startRotation: Number(c.dataset.rotation) || 0
                }));
                resizeStartMouseX = e.clientX;
                resizeStartMouseY = e.clientY;
                resizingCards.forEach(rc => { rc.card.style.transition = 'none'; });
                document.body.classList.add('is-resizing');
                e.stopPropagation();
                e.preventDefault();
            });
            card.appendChild(resizeHandle);

            if (options.width) card.style.width = `${options.width}px`;
            if (options.height) card.style.height = `${options.height}px`;

            card.dataset.x = safePos.x;
            card.dataset.y = safePos.y;
            card.dataset.rotation = options.rotation || 0;
            card.style.setProperty('--card-rotation', `${card.dataset.rotation}deg`);
            card.style.transform = buildCardTransform(safePos.x, safePos.y, card.dataset.rotation);

            maxCardZIndex++;
            card.style.zIndex = maxCardZIndex;

            card.addEventListener('mousedown', (e) => {
                if (e.button === 0) {
                    if (e.shiftKey && selectedCards.size === 1 && selectedCards.has(card)) {
                        const cardRect = card.getBoundingClientRect();
                        rotatingCard = card;
                        rotateCenterX = cardRect.left + cardRect.width / 2;
                        rotateCenterY = cardRect.top + cardRect.height / 2;
                        rotateStartAngle = Math.atan2(e.clientY - rotateCenterY, e.clientX - rotateCenterX) * 180 / Math.PI;
                        rotateStartRotation = Number(card.dataset.rotation) || 0;
                        card.style.transition = 'none';
                        e.stopPropagation();
                        return;
                    }

                    const multiSelect = e.ctrlKey || e.metaKey || e.shiftKey;

                    if (!multiSelect && selectedCards.has(card)) {
                        pendingDeselectCard = card;
                    } else {
                        pendingDeselectCard = null;
                        toggleSelection(card, multiSelect);
                    }

                    draggingCards = Array.from(selectedCards);
                    if (!draggingCards.includes(card)) draggingCards.push(card);

                    dragStartX = e.clientX;
                    dragStartY = e.clientY;
                    initialCardPositions.clear();

                    draggingCards.forEach(c => {
                        initialCardPositions.set(c, {
                            x: parseFloat(c.dataset.x || 0),
                            y: parseFloat(c.dataset.y || 0)
                        });
                        c.classList.add('dragging');
                        c.style.transition = 'none';

                        if (c.classList.contains('binder-zone')) {
                            maxZoneZIndex++;
                            c.style.zIndex = maxZoneZIndex;
                        } else {
                            maxCardZIndex++;
                            c.style.zIndex = maxCardZIndex;
                        }
                    });

                    e.stopPropagation();
                }
            });

            if (options.isPortal) {
                card.addEventListener('dblclick', () => {
                    const targetId = card.dataset.targetBinderId;
                    console.log(`Jump to binder: ${targetId}`);
                    if (window.App && window.App.loadBinder) {
                        window.App.loadBinder(targetId);
                    }
                });
            }

            let hasObservedCardSizeOnce = false;
            let cardResizeSaveTimer = null;
            const cardResizeObserver = new ResizeObserver(() => {
                if (!hasObservedCardSizeOnce) {
                    hasObservedCardSizeOnce = true;
                    return;
                }
                clearTimeout(cardResizeSaveTimer);
                cardResizeSaveTimer = setTimeout(() => {
                    const imgBg = card.querySelector('.sp-image-bg[data-file-path]');
                    if (imgBg && imgBg.dataset.filePath) {
                        const targetW = Math.ceil(card.offsetWidth * 1.4);
                        const targetH = Math.ceil(card.offsetHeight * 1.4);
                        imgBg.style.backgroundImage = `url('/api/file/preview?path=${encodeURIComponent(imgBg.dataset.filePath)}&w=${targetW}&h=${targetH}')`;
                    }
                    if (window.App && window.App.savePlacements) {
                        window.App.savePlacements();
                    }
                }, 400);
            });
            cardResizeObserver.observe(card);

            content.appendChild(card);
            return card;
        },
        addZone: (id, x, y, width, height, status, title) => {
            const zone = document.createElement('div');
            zone.id = 'zone-' + id;
            zone.dataset.id = id;
            zone.dataset.status = status;
            zone.className = `binder-zone zone-${status.toLowerCase()}`;
            zone.style.width = `${width}px`;
            zone.style.height = `${height}px`;

            zone.dataset.x = x;
            zone.dataset.y = y;
            zone.style.transform = `translate(${x}px, ${y}px)`;

            maxZoneZIndex++;
            zone.style.zIndex = maxZoneZIndex;

            const header = document.createElement('div');
            header.className = 'binder-zone-header';
            header.style.display = 'flex';
            header.style.justifyContent = 'space-between';
            header.style.alignItems = 'center';

            const titleSpan = document.createElement('span');
            titleSpan.textContent = title;
            header.appendChild(titleSpan);

            if (status && status.toLowerCase() === 'done') {
                const btnArchive = document.createElement('button');
                btnArchive.textContent = '📦 引っ越し';
                btnArchive.title = '完了済みのカードを別バインダーへ移動します';
                btnArchive.style.fontSize = '0.7rem';
                btnArchive.style.padding = '2px 8px';
                btnArchive.style.background = 'rgba(255,255,255,0.2)';
                btnArchive.style.border = '1px solid rgba(255,255,255,0.3)';
                btnArchive.style.color = '#fff';
                btnArchive.style.borderRadius = '4px';
                btnArchive.style.cursor = 'pointer';

                btnArchive.addEventListener('mouseover', () => btnArchive.style.background = 'rgba(255,255,255,0.3)');
                btnArchive.addEventListener('mouseout', () => btnArchive.style.background = 'rgba(255,255,255,0.2)');

                btnArchive.addEventListener('mousedown', (e) => e.stopPropagation()); // prevent drag start
                btnArchive.addEventListener('click', (e) => {
                    e.stopPropagation();
                    if (window.App && window.App.archiveDoneCards) {
                        window.App.archiveDoneCards();
                    }
                });
                header.appendChild(btnArchive);
            }

            zone.appendChild(header);

            const gearSvg = document.createElement('div');
            gearSvg.className = 'zone-bg-gear';
            gearSvg.innerHTML = `<svg viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg"><g fill="none" stroke="currentColor" stroke-linejoin="round"><path stroke-width="1.5" d="M40.94,16.19 L43.88,6.43 L56.12,6.43 L59.06,16.19 A35,35 0 0 1 62.54,17.32 L70.66,11.15 L80.56,18.35 L77.2,27.97 A35,35 0 0 1 79.35,30.94 L89.55,30.71 L93.33,42.36 L84.95,48.17 A35,35 0 0 1 84.95,51.83 L93.33,57.64 L89.55,69.29 L79.35,69.06 A35,35 0 0 1 77.2,72.03 L80.56,81.65 L70.66,88.85 L62.54,82.68 A35,35 0 0 1 59.06,83.81 L56.12,93.57 L43.88,93.57 L40.94,83.81 A35,35 0 0 1 37.46,82.68 L29.34,88.85 L19.44,81.65 L22.8,72.03 A35,35 0 0 1 20.65,69.06 L10.45,69.29 L6.67,57.64 L15.05,51.83 A35,35 0 0 1 15.05,48.17 L6.67,42.36 L10.45,30.71 L20.65,30.94 A35,35 0 0 1 22.8,27.97 L19.44,18.35 L29.34,11.15 L37.46,17.32 A35,35 0 0 1 40.94,16.19 Z" /><circle cx="50" cy="50" r="18" stroke-width="1.2" /><circle cx="50" cy="50" r="5" stroke-width="1.2" /><circle cx="65.56" cy="34.44" r="2" stroke-width="1" /><circle cx="65.56" cy="65.56" r="2" stroke-width="1" /><circle cx="34.44" cy="65.56" r="2" stroke-width="1" /><circle cx="34.44" cy="34.44" r="2" stroke-width="1" /></g></svg>`;
            zone.appendChild(gearSvg);

            let hasObservedZoneSizeOnce = false;
            let zoneResizeSaveTimer = null;
            const ro = new ResizeObserver(() => {
                if (!hasObservedZoneSizeOnce) {
                    hasObservedZoneSizeOnce = true;
                    return;
                }
                clearTimeout(zoneResizeSaveTimer);
                zoneResizeSaveTimer = setTimeout(() => {
                    if (window.App && window.App.savePlacements) {
                        window.App.savePlacements();
                    }
                }, 400);
            });
            ro.observe(zone);

            zone.addEventListener('mousedown', (e) => {
                if (!e.target.closest('.binder-zone-header')) {
                    return;
                }

                if (e.button === 0) {
                    toggleSelection(zone, e.ctrlKey || e.metaKey || e.shiftKey);

                    draggingCards = Array.from(selectedCards);
                    if (!draggingCards.includes(zone)) draggingCards.push(zone);

                    dragStartX = e.clientX;
                    dragStartY = e.clientY;
                    initialCardPositions.clear();

                    draggingCards.forEach(c => {
                        initialCardPositions.set(c, {
                            x: parseFloat(c.dataset.x || 0),
                            y: parseFloat(c.dataset.y || 0)
                        });
                        c.classList.add('dragging');
                        c.style.transition = 'none';

                        if (c.classList.contains('binder-zone')) {
                            maxZoneZIndex++;
                            c.style.zIndex = maxZoneZIndex;
                        } else {
                            maxCardZIndex++;
                            c.style.zIndex = maxCardZIndex;
                        }
                    });

                    const zoneRect = zone.getBoundingClientRect();
                    const allCards = Array.from(content.querySelectorAll('.binder-card:not(.portal-card)'));
                    const zoneStatus = zone.dataset.status;

                    zone.magnetCards = allCards.filter(c => {
                        const expectedStatusClass = 'status-' + (zoneStatus || 'todo').toLowerCase();

                        const hasMatchingStatus = c.querySelector('.' + expectedStatusClass) !== null;
                        if (!hasMatchingStatus) {
                            return false;
                        }

                        if (c.classList.contains('card-type-task')) {
                            return true;
                        }

                        const tags = Array.from(c.querySelectorAll('.tag, .sp-tag'));
                        const hasTask = tags.some(tagEl => tagEl.textContent.trim().toLowerCase() === 'task');
                        if (!hasTask) {
                            return false;
                        }

                        return true;
                    });

                    e.stopPropagation();
                }
            });

            content.appendChild(zone);
            return zone;
        },
        getScale: () => scale,
        getTranslate: () => ({ x: translateX, y: translateY }),
        getSelectedCards: () => Array.from(selectedCards),
        clearSelection: clearSelection,
        selectCard: (card) => toggleSelection(card, false),
        setViewport: (newScale, newTranslateX, newTranslateY) => {
            if (typeof newScale === 'number' && !isNaN(newScale)) scale = Math.max(0.1, Math.min(newScale, 5));
            if (typeof newTranslateX === 'number' && !isNaN(newTranslateX)) translateX = newTranslateX;
            if (typeof newTranslateY === 'number' && !isNaN(newTranslateY)) translateY = newTranslateY;
            updateTransform();
        }
    };


    document.addEventListener('paste', async (e) => {
        if (document.querySelector('.modal-overlay.active') || document.querySelector('.modal.active') || document.querySelector('.sp-modal.active')) return;
        if (state.gravityLocked) return;

        const tagName = e.target?.tagName?.toLowerCase() || '';
        if (tagName === 'input' || tagName === 'textarea' || tagName === 'select' || e.target?.isContentEditable) return;

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

        try {
            const clipboardData = e.clipboardData || window.clipboardData;

            const rect = container.getBoundingClientRect();
            const centerX = (rect.width / 2 - translateX) / scale;
            const centerY = (rect.height / 2 - translateY) / scale;

            const imageItem = clipboardData?.items
                && Array.from(clipboardData.items).find(item => item.type && item.type.startsWith('image/'));
            if (imageItem) {
                const imageFile = imageItem.getAsFile();
                if (imageFile) {
                    e.preventDefault();

                    if (!state.settings.allow_image_paste) {
                        const proceed = await showConfirmDialog(
                            'クリップボード画像の貼り付け',
                            '貼り付けた画像はローカルの storage/scraps フォルダに実体ファイルとして保存されます。\nこの機能を有効にしてもよろしいですか？',
                            '保存して貼り付ける'
                        );
                        if (!proceed) return;

                        try {
                            await updateSettingsApi({ allow_image_paste: true });
                        } catch (err) {
                            console.error('Failed to update settings', err);
                        }
                        refreshImagePasteToggleButton();
                    }

                    if (window.App && window.App.handlePastedImage) {
                        await window.App.handlePastedImage(imageFile, centerX, centerY);
                    }
                    return;
                }
            }

            const pastedText = clipboardData.getData('text');
            if (!pastedText) return;

            if (pastedText.length > 1000000) {
                const proceed = await showConfirmDialog(
                    '警告',
                    `貼り付けられたテキストが非常に大きいため（${pastedText.length.toLocaleString()}文字）、保存時にエラーが発生する可能性があります。\n本当にこのままメモカードを作成しますか？`,
                    '作成する',
                    true
                );
                if (!proceed) return;
            }

            const lines = pastedText.split(/\r?\n/).map(line => line.trim()).filter(line => line.length > 0);
            if (lines.length === 0) return;

            const isPath = (line) => /^"?([A-Za-z]:\\|\/)[^\0]+"?$/.test(line);
            const isUrl = (line) => /^https?:\/\//i.test(line);

            const hasNormalText = lines.some(line => !isPath(line) && !isUrl(line));

            if (hasNormalText) {
                if (window.App && window.App.handlePastedText) {
                    await window.App.handlePastedText(pastedText, centerX, centerY);
                }
            } else {
                const validPaths = lines.filter(isPath).map(line => line.replace(/^"|"$/g, ''));
                const validUrls = lines.filter(isUrl);

                const tasks = [];
                if (validPaths.length > 0) {
                    console.log("File paths detected:", validPaths);
                    if (window.App && window.App.handlePastedPaths) {
                        tasks.push(window.App.handlePastedPaths(validPaths, centerX, centerY));
                    }
                }

                if (validUrls.length > 0) {
                    console.log("URLs detected:", validUrls);
                    if (window.App && window.App.handlePastedUrls) {
                        tasks.push(window.App.handlePastedUrls(validUrls, centerX, centerY));
                    }
                }

                if (tasks.length > 0) await Promise.all(tasks);
            }
        } finally {
            state.isPasteProcessing = false;
        }
    });

    document.addEventListener('keydown', (e) => {
        const tagName = e.target.tagName.toLowerCase();
        if (tagName === 'input' || tagName === 'textarea' || tagName === 'select') {
            return; // フォーム入力中は無視
        }
        if (state.gravityLocked) return;

        if (e.key === 'Delete' || e.key === 'Backspace') {
            const selected = BinderManager.getSelectedCards();
            if (selected.length > 0) {
                e.preventDefault(); // ブラウザバック等を防止
                if (window.App && window.App.showDeleteConfirm) {
                    window.App.showDeleteConfirm(selected);
                }
            }
        }
    });

    const contextMenu = document.getElementById('contextMenu');
    const ctxMenuCanvas = document.getElementById('ctx-menu-canvas');
    const ctxMenuCard = document.getElementById('ctx-menu-card');
    const ctxMenuBulk = document.getElementById('ctx-menu-bulk');

    document.addEventListener('contextmenu', (e) => {
        if (!container.contains(e.target) || !contextMenu) return;

        const tagName = e.target.tagName.toLowerCase();
        if (tagName === 'input' || tagName === 'textarea' || tagName === 'select') return;

        e.preventDefault();

        const cardTarget = state.gravityLocked ? null : e.target.closest('.binder-card');
        state.ctxTargetId = cardTarget ? cardTarget.dataset.id : null;
        state.ctxTargetCard = cardTarget;

        if (state.gravityLocked) {
            ctxMenuCanvas.style.display = 'flex';
            ctxMenuCard.style.display = 'none';
            if (ctxMenuBulk) ctxMenuBulk.style.display = 'none';
            const undoArrangeItem = document.getElementById('ctx-undo-arrange');
            if (undoArrangeItem) undoArrangeItem.style.display = 'flex';
        } else if (cardTarget) {
            const currentSelected = BinderManager.getSelectedCards();
            const isMultiSelect = currentSelected.length > 1 && currentSelected.includes(cardTarget);

            if (isMultiSelect) {
                ctxMenuCanvas.style.display = 'none';
                ctxMenuCard.style.display = 'none';
                if (ctxMenuBulk) ctxMenuBulk.style.display = 'flex';
            } else {
                ctxMenuCanvas.style.display = 'none';
                ctxMenuCard.style.display = 'flex';
                if (ctxMenuBulk) ctxMenuBulk.style.display = 'none';

                const isPortal = cardTarget.classList.contains('portal-card');
                document.getElementById('ctx-edit-card').style.display = isPortal ? 'none' : 'flex';
                const isTaskCard = cardTarget.classList.contains('card-type-task');
                document.getElementById('ctx-status-card').style.display = isTaskCard ? 'flex' : 'none';
            }
        } else {
            ctxMenuCanvas.style.display = 'flex';
            ctxMenuCard.style.display = 'none';
            if (ctxMenuBulk) ctxMenuBulk.style.display = 'none';

            const undoArrangeItem = document.getElementById('ctx-undo-arrange');
            if (undoArrangeItem) {
                const canUndo = !!(window.App && window.App.canUndoArrange && window.App.canUndoArrange());
                undoArrangeItem.style.display = canUndo ? 'flex' : 'none';
            }

            const hasKanbanZone = !!(state.currentBinder?.placements?.some(p => p.type === 'zone'));
            const addKanbanItem = document.getElementById('ctx-add-kanban');
            const removeKanbanItem = document.getElementById('ctx-remove-kanban');
            if (addKanbanItem) addKanbanItem.style.display = hasKanbanZone ? 'none' : 'flex';
            if (removeKanbanItem) removeKanbanItem.style.display = hasKanbanZone ? 'flex' : 'none';
        }

        const rect = container.getBoundingClientRect();
        state.ctxMouseX = (e.clientX - rect.left - translateX) / scale;
        state.ctxMouseY = (e.clientY - rect.top - translateY) / scale;

        contextMenu.style.display = 'block';
        let left = e.clientX;
        let top = e.clientY;

        if (left + contextMenu.offsetWidth > window.innerWidth) {
            left = window.innerWidth - contextMenu.offsetWidth - 10;
        }
        if (top + contextMenu.offsetHeight > window.innerHeight) {
            top = window.innerHeight - contextMenu.offsetHeight - 10;
        }

        contextMenu.style.left = `${left}px`;
        contextMenu.style.top = `${top}px`;
    });

    document.addEventListener('mousedown', (e) => {
        if (contextMenu && contextMenu.style.display === 'block') {
            if (!contextMenu.contains(e.target)) {
                contextMenu.style.display = 'none';
            }
        }
    });

    return BinderManager;
}
