class MangaViewer {
    lr_opening = 0; // 0:左開き 1:右開き
    total_pages = 0; // ページ数(画像数)
    current_index = 0; // 現在の表示ページ
    resize_timeout_id; //リサイズイベント用タイムアウトID
    top_control_show = false; //上部コントロール表示フラグ
    left_control_show = false; //左コントロール表示フラグ
    right_control_show = false; //右コントロール表示フラグ
    original_size; // 現在表示中ページの初期リサイズ時のサイズ
    image_cache = []; // ページキャッシュ
    zoom_mode = false; // 拡大モードフラグ
    zoom_scale = 1; // 現在表示中ページの拡大率
    move_mode = false; //画像移動モードフラグ
    move_start_cursor_pos; // 画像移動開始時のマウスカーソル位置
    view_position; // 画像移動
    page_class = "[data-thumbnail]"; // ページ要素のセレクタ（変更可能）
    animations = {}; // アニメーション管理用

    constructor(lr_opening = 0) {
        this.lr_opening = lr_opening;
        this.total_pages = document.querySelectorAll(this.page_class).length;
        this.image_cache = new Array(this.total_pages);
        for (var i = 0; i < this.total_pages; i++) {
            this.image_cache[i] = new Image();
        }
    }

    // アニメーションヘルパー（jQueryのanimate相当）
    animate(element, properties, duration = 300, callback = null) {
        const startValues = {};
        const endValues = {};

        // 開始値を取得
        for (let prop in properties) {
            const currentStyle = window.getComputedStyle(element);
            startValues[prop] = parseFloat(currentStyle[prop]) || 0;
            endValues[prop] = parseFloat(properties[prop]);
        }

        const startTime = performance.now();

        const step = (currentTime) => {
            const elapsed = currentTime - startTime;
            const progress = Math.min(elapsed / duration, 1);

            for (let prop in properties) {
                const value = startValues[prop] + (endValues[prop] - startValues[prop]) * progress;
                element.style[prop] = value + (prop === 'top' || prop === 'left' ? 'px' : '');
            }

            if (progress < 1) {
                requestAnimationFrame(step);
            } else if (callback) {
                callback();
            }
        };

        requestAnimationFrame(step);
    }

    // アニメーションチェーン用ヘルパー
    animateChain(element, animations) {
        const execute = (index) => {
            if (index >= animations.length) return;
            const anim = animations[index];
            this.animate(element, anim.props, anim.duration, () => execute(index + 1));
        };
        execute(0);
    }

    init_display(start_index) {
        //関連要素構築
        document.body.insertAdjacentHTML('beforeend',
            "<div class=\"manga-view\">" +
            "<div class=\"manga-view-control\"><span class=\"manga-view-return\">&lt; 戻る</a></div>" +
            "<div class=\"manga-view-control-left manga-view-control-lr\">&lt;</div>" +
            "<div class=\"manga-view-control-right manga-view-control-lr\">&gt;</div>" +
            "<div class=\"manga-view-congtrol-bottom\"></div>" +
            "<div class=\"manga-view-base\"><div class=\"manga-view-inner\"></div><img class=\"manga-view-loader\"></div></div>");

        const base = document.querySelector('.manga-view-base');
        base.addEventListener('click', this.page_click.bind(this));
        base.addEventListener('dblclick', (e) => {
            e.preventDefault();
            if (this.zoom_mode) {
                this.show_page(this.current_index);
            }
        });

        base.addEventListener('mousedown', this.mouse_start.bind(this));
        base.addEventListener('mousemove', this.mouse_move.bind(this));
        base.addEventListener('mouseup', this.mouse_end.bind(this));

        base.addEventListener('touchstart', this.touch_start.bind(this));
        base.addEventListener('touchmove', this.touch_move.bind(this));
        base.addEventListener('touchend', this.touch_end.bind(this));

        document.querySelector('.manga-view-return').addEventListener('click', this.close_manga_view.bind(this));

        //Windowリサイズ時
        window.addEventListener("resize", () => {
            if (this.resize_timeout_id) return;
            this.resize_timeout_id = setTimeout(() => {
                this.resize_timeout_id = 0;
                this.show_page(this.current_index);
            }, 500);
        });

        const pinchSwipeHandler = new PinchSwipeHandler('.manga-view-base');
        //ホイールスクロール操作
        base.addEventListener("wheel", (event) => {
            const delta = event.deltaY;
            const innerRect = document.querySelector(".manga-view-inner").getBoundingClientRect();
            const mouseX = event.clientX - innerRect.left;
            const mouseY = event.clientY - innerRect.top;
            this.zoom(delta, mouseX, mouseY);
        });
        //ピンチ操作
        base.addEventListener('pinch', (e) => {
            const delta = e.detail.value;
            const innerRect = document.querySelector(".manga-view-inner").getBoundingClientRect();
            const mouseX = e.detail.centerX - innerRect.left;
            const mouseY = e.detail.centerY - innerRect.top;
            this.zoom(delta * -1, mouseX, mouseY);
        });

        //スワイプ時動作
        base.addEventListener('swipeX', (e) => {
            if (e.detail.value > 0) {
                this.page_right();
            } else {
                this.page_left();
            }
        });
        base.addEventListener('swipeY', (e) => {
            const control = document.querySelector('.manga-view-control');
            control.style.top = '0em';
            this.top_control_show = true;
        });

        let screen_width = base.offsetWidth;
        const controlRight = document.querySelector('.manga-view-control-right');
        controlRight.style.left = (screen_width - controlRight.clientWidth) + "px";

        //トップコントローラを表示
        const control = document.querySelector('.manga-view-control');
        this.animateChain(control, [
            { props: { top: 0 }, duration: 1000 },
            { props: { top: -80 }, duration: 300 }
        ]);

        const controlLeft = document.querySelector('.manga-view-control-left');
        //ページ移動コントローラ表示制御
        if (this.total_pages - 1 > start_index) {
            //最終ページ以外
            if (this.lr_opening == 0) {
                this.animateChain(controlLeft, [
                    { props: { left: 0 }, duration: 1000 },
                    { props: { left: -controlLeft.clientWidth }, duration: 300 }
                ]);
                if (start_index == 0) {
                    controlRight.style.left = screen_width + "px";
                } else {
                    this.animateChain(controlRight, [
                        { props: { left: screen_width - controlRight.clientWidth }, duration: 1000 },
                        { props: { left: screen_width }, duration: 300 }
                    ]);
                }
            } else {
                this.animateChain(controlRight, [
                    { props: { left: screen_width - controlRight.clientWidth }, duration: 1000 },
                    { props: { left: screen_width }, duration: 300 }
                ]);
                if (start_index == 0) {
                    controlLeft.style.left = "-" + controlLeft.clientWidth + "px";
                } else {
                    this.animateChain(controlLeft, [
                        { props: { left: 0 }, duration: 1000 },
                        { props: { left: -controlLeft.clientWidth }, duration: 300 }
                    ]);
                }
            }
        } else {
            //最終ページ
            if (this.lr_opening == 0) {
                //右だけ表示
                controlLeft.style.left = "-" + controlLeft.clientWidth + "px";
                this.animateChain(controlRight, [
                    { props: { left: screen_width - controlRight.clientWidth }, duration: 1000 },
                    { props: { left: screen_width }, duration: 300 }
                ]);
            } else {
                //左だけ表示
                controlRight.style.left = screen_width + "px";
                this.animateChain(controlLeft, [
                    { props: { left: 0 }, duration: 1000 },
                    { props: { left: -controlLeft.clientWidth }, duration: 300 }
                ]);
            }
        }

        // body要素にスクロールバー非表示のスタイルを適用
        document.body.style.overflow = 'hidden';
        document.querySelector('.manga-view').style.top = window.pageYOffset + 'px';
    }

    zoom(delta, mouseX, mouseY) {
        if (delta < 0) {
            this.zoom_scale = this.zoom_scale + 0.1;
            if (this.zoom_scale > 5) {
                this.zoom_scale = 5;
            }
        } else {
            this.zoom_scale = this.zoom_scale - 0.1;
            if (this.zoom_scale < 1) {
                this.zoom_scale = 1;
            }
        }
        const newWidth = this.original_size.width * this.zoom_scale;
        const newHeight = this.original_size.height * this.zoom_scale;
        const inner = document.querySelector(".manga-view-inner");
        inner.style.width = newWidth + "px";
        inner.style.height = newHeight + "px";

        const base = document.querySelector(".manga-view-base");
        let screen_width = base.offsetWidth;
        let screen_height = base.offsetHeight;

        if (delta > 0 && screen_width > newWidth) {
            //縮小時、画面サイズより小さくなる場合は中央に拘束する
            const center_left = screen_width / 2 - newWidth / 2;
            inner.style.left = center_left + "px";
        } else {
            let center_left = screen_width / 2 - mouseX;
            if (center_left > 0) { center_left = 0 };
            if (center_left + newWidth < screen_width) { center_left = screen_width - newWidth };
            inner.style.left = center_left + "px";
        }

        if (delta > 0 && screen_height > newHeight) {
            //縮小時、画面サイズより小さくなる場合は中央に拘束する
            const center_top = screen_height / 2 - newHeight / 2;
            inner.style.top = center_top + "px";
        } else {
            let center_top = screen_height / 2 - mouseY;
            if (center_top > 0) { center_top = 0 };
            if (center_top + newHeight < screen_height) { center_top = screen_height - newHeight };
            inner.style.top = center_top + "px";
        }

        if (this.zoom_scale > 1) {
            this.zoom_mode = true;
        } else {
            this.zoom_mode = false;
        }
    }

    preload_image(index, update = true) {
        var img = this.image_cache[index];
        var page = document.querySelectorAll(this.page_class)[index];
        if (!page) return;
        if (page.dataset.loaded) {
            return;
        }
        img.onload = () => {
            page.dataset.loaded = 'true';
            console.log("loaded:" + index + " / " + page.dataset.loaded);
            if (index === this.current_index && update) {
                this.show_page(index);
            }
        };
        img.src = page.dataset.src;
    }

    close_manga_view() {
        document.body.style.overflow = 'scroll';
        const containers = document.querySelectorAll('.view-image-container');
        if (containers[this.current_index]) {
            const scroll_target = containers[this.current_index];
            const rect = scroll_target.getBoundingClientRect();
            const top = rect.top + window.pageYOffset + rect.height - 30;
            document.querySelector('.manga-view').remove();
            window.scrollTo(0, top);
        } else {
            document.querySelector('.manga-view').remove();
        }
    }

    page_click(e) {
        if (this.zoom_mode) {
            //拡大モード中はページ移動しない
            return;
        }
        const x = e.clientX;
        const center = document.querySelector('.manga-view-base').offsetWidth / 2;
        if (center > x) {
            this.page_left();
        } else {
            this.page_right();
        }
    }

    page_left() {
        if (this.lr_opening == 0) {
            this.current_index++;
        } else {
            this.current_index--;
        }
        if (this.current_index == this.total_pages) {
            this.current_index = this.total_pages - 1;
            const control = document.querySelector('.manga-view-control');
            control.style.top = '0em';
            this.top_control_show = true;
        } else if (this.current_index < 0) {
            this.current_index = 0;
        }
        this.show_page(this.current_index);
    }

    page_right() {
        if (this.lr_opening == 0) {
            this.current_index--;
        } else {
            this.current_index++;
        }
        if (this.current_index == this.total_pages) {
            this.current_index = this.total_pages - 1;
            const control = document.querySelector('.manga-view-control');
            control.style.top = '0em';
            this.top_control_show = true;
        } else if (this.current_index < 0) {
            this.current_index = 0;
        }
        this.show_page(this.current_index);
    }

    touch_start(e) {
        var x = e.touches[0].clientX;
        var y = e.touches[0].clientY;

        //上部メニュー展開
        const base = document.querySelector('.manga-view-base');
        const top_border = base.offsetHeight / 4;
        const control = document.querySelector('.manga-view-control');
        if (y < top_border) {
            control.style.top = '0';
            this.top_control_show = true;
        } else if (y > top_border && this.top_control_show) {
            this.top_control_show = false;
            control.style.top = '-5em';
        }

        if (this.zoom_mode) {
            this.move_start_cursor_pos = { "x": x, "y": y };
            const inner = document.querySelector('.manga-view-inner');
            const rect = inner.getBoundingClientRect();
            this.view_position = { left: rect.left, top: rect.top };
            this.move_mode = true;
        }
    }

    mouse_start(e) {
        var x = e.clientX;
        var y = e.clientY;
        if (this.zoom_mode) {
            this.move_start_cursor_pos = { "x": x, "y": y };
            const inner = document.querySelector(".manga-view-inner");
            const rect = inner.getBoundingClientRect();
            this.view_position = { left: rect.left, top: rect.top };
            this.move_mode = true;
        }
    }

    touch_move(e) {
        if (this.move_mode) {
            var x = e.touches[0].clientX;
            var y = e.touches[0].clientY;
            const move_X = this.view_position.left + (x - this.move_start_cursor_pos.x);
            const move_Y = this.view_position.top + (y - this.move_start_cursor_pos.y);
            const inner = document.querySelector(".manga-view-inner");
            inner.style.left = move_X + "px";
            inner.style.top = move_Y + "px";
        }
    }

    mouse_move(e) {
        const x = e.clientX;
        const y = e.clientY;
        const base = document.querySelector('.manga-view-base');
        const h = base.offsetHeight;
        const w = base.offsetWidth;
        const top_border = h * 0.2;
        const left_border = w * 0.2;
        const right_border = w * 0.8;

        const control = document.querySelector('.manga-view-control');
        const controlLeft = document.querySelector('.manga-view-control-left');
        const controlRight = document.querySelector('.manga-view-control-right');

        //上部コントローラ
        if (y < top_border && !this.top_control_show) {
            control.style.top = '0';
            this.top_control_show = true;
        } else if (y > top_border && this.top_control_show) {
            this.top_control_show = false;
            control.style.top = '-5em';
        }

        //ページ移動コントローラ
        if (!this.zoom_mode) {
            //左
            if (x < left_border && !this.left_control_show && this.current_index < this.total_pages - 1) {
                controlLeft.style.left = '0';
                this.left_control_show = true;
            } else if (x > left_border && this.left_control_show) {
                this.left_control_show = false;
                controlLeft.style.left = "-" + controlLeft.clientWidth + "px";
            }

            //右
            if (x > right_border && !this.right_control_show && this.current_index > 0) {
                controlRight.style.left = (w - controlRight.clientWidth) + "px";
                this.right_control_show = true;
            } else if (x < right_border && this.right_control_show) {
                this.right_control_show = false;
                controlRight.style.left = w + "px";
            }
        }

        //移動モード
        if (this.move_mode) {
            const move_X = this.view_position.left + (x - this.move_start_cursor_pos.x);
            const move_Y = this.view_position.top + (y - this.move_start_cursor_pos.y);
            const inner = document.querySelector(".manga-view-inner");
            inner.style.left = move_X + "px";
            inner.style.top = move_Y + "px";
        }
    }

    touch_end(e) {
        this.move_mode = false;
    }

    mouse_end(e) {
        this.move_mode = false;
    }

    get_screenfit_size(pageElement) {
        let width = pageElement.dataset.width || pageElement.naturalWidth;
        let height = pageElement.dataset.height || pageElement.naturalHeight;

        const base = document.querySelector(".manga-view-base");
        let screen_width = base.offsetWidth;
        let screen_height = base.offsetHeight;

        let scale_w = screen_width / width;
        let scale_h = screen_height / height;
        let scale = scale_w < scale_h ? scale_w : scale_h;

        let resize_width = width * scale;
        let resize_height = height * scale;
        let resize_left = (screen_width / 2) - (resize_width / 2);
        let resize_top = (screen_height / 2) - (resize_height / 2);
        return { "width": resize_width, "height": resize_height, "top": resize_top, "left": resize_left };
    }

    show_page(index) {
        this.current_index = index;
        this.zoom_scale = 1;
        this.zoom_mode = false;
        var page = document.querySelectorAll(this.page_class)[index];
        if (!page) return;

        const base = document.querySelector(".manga-view-base");
        let screen_width = base.offsetWidth;
        let screen_height = base.offsetHeight;
        const loader = document.querySelector('.manga-view-loader');
        const inner = document.querySelector('.manga-view-inner');

        if (page.dataset.loaded) {
            loader.style.display = "none";
            inner.style.backgroundImage = "url(" + page.dataset.src + ")";
            let size = this.get_screenfit_size(page);
            inner.style.width = size.width + "px";
            inner.style.height = size.height + "px";
            inner.style.top = size.top + "px";
            inner.style.left = size.left + "px";
            this.original_size = size;
        } else {
            loader.style.top = (screen_height / 2) - 42 + "px";
            loader.style.left = (screen_width / 2) - 42 + "px";
            loader.style.display = "block";
        }

        this.preload_image(index < this.total_pages - 1 ? index + 1 : 0);
        this.preload_image(index < this.total_pages - 2 ? index + 2 : 0);
        this.preload_image(index < this.total_pages - 3 ? index + 3 : 0);
        this.preload_image(index > 0 ? (index - 1) : 0);

        const controlLeft = document.querySelector('.manga-view-control-left');
        const controlRight = document.querySelector('.manga-view-control-right');

        //左
        if (this.current_index >= this.total_pages - 1) {
            this.left_control_show = false;
            controlLeft.style.left = "-" + controlLeft.clientWidth + "px";
        }

        //右
        if (this.current_index == 0) {
            this.right_control_show = false;
            controlRight.style.left = screen_width + "px";
        }
    }
}



/*
*   ピンチ操作・スワイプ動作判定用クラス
*/
class PinchSwipeHandler {
    constructor(element_selector) {
        this.element = document.querySelector(element_selector);
        this.element.style.touchAction = "none";
        this.initialDistance = 0;
        this.pinchThreshold = 10;
        this.startX = 0;
        this.startY = 0;
        this.lastReportDistance = 0;
        this.setupEvents();
    }

    setupEvents() {
        this.element.addEventListener('touchstart', this.handleTouchStart.bind(this));
        this.element.addEventListener('touchmove', this.handleTouchMove.bind(this));
        this.element.addEventListener('touchend', this.handleTouchEnd.bind(this));
    }

    handleTouchStart(event) {
        if (event.touches.length === 2) {
            this.initialDistance = this.calculateDistance(event.touches[0], event.touches[1]);
            this.startX = -1;
            this.startY = -1;
        } else {
            this.startX = event.touches[0].clientX;
            this.startY = event.touches[0].clientY;
        }
    }

    handleTouchMove(event) {
        if (event.touches.length === 2) {
            const currentDistance = this.calculateDistance(event.touches[0], event.touches[1]);
            const swipeValue = (currentDistance - this.initialDistance);
            const centerX = (event.touches[0].clientX + event.touches[1].clientX) / 2;
            const centerY = (event.touches[0].clientY + event.touches[1].clientY) / 2;
            if (Math.abs(swipeValue) > this.pinchThreshold) {
                const reportDistance = swipeValue - this.lastReportDistance;
                this.lastReportDistance = swipeValue;
                this.element.dispatchEvent(new CustomEvent('pinch', {
                    detail: { "value": reportDistance, "centerX": centerX, "centerY": centerY }
                }));
            }
        }
    }

    handleTouchEnd(event) {
        if (event.changedTouches.length > 1) {
            return;
        }
        const endY = event.changedTouches[0].clientY;
        const diffY = this.startY - endY;
        const endX = event.changedTouches[0].clientX;
        const diffX = this.startX - endX;
        if (this.startX != -1 && this.startY != -1) {
            if (Math.abs(diffX) > 50 && Math.abs(diffX) < 150 && Math.abs(diffY) < 50) {
                this.element.dispatchEvent(new CustomEvent('swipeX', {
                    detail: { "value": diffX }
                }));
            }
            if (Math.abs(diffY) > 50 && Math.abs(diffY) < 150 && Math.abs(diffX) < 50) {
                this.element.dispatchEvent(new CustomEvent('swipeY', {
                    detail: { "value": diffY }
                }));
            }
        }
    }

    calculateDistance(touch1, touch2) {
        const dx = touch1.clientX - touch2.clientX;
        const dy = touch1.clientY - touch2.clientY;
        return Math.sqrt(dx * dx + dy * dy);
    }
}
