import QtQuick 2.15

Item { id: _root
    property alias source: _image.source

    property real minScale  : 1
    property real maxScale  : 4
    property real startScale: 1
    property bool pinching  : false

    onSourceChanged: reset()

    function snapBack() {
        _flickable.contentX = Math.max(0, Math.min(_flickable.contentWidth  - _flickable.width,  _flickable.contentX))
        _flickable.contentY = Math.max(0, Math.min(_flickable.contentHeight - _flickable.height, _flickable.contentY))
    }

    function rubberClamp() {
        let minX = 0
        let maxX = Math.max(0, _flickable.contentWidth - _flickable.width)
        let minY = 0
        let maxY = Math.max(0, _flickable.contentHeight - _flickable.height)

        let resistance = 0.35

        _flickable.contentX = (_flickable.contentX < minX) ? minX + (_flickable.contentX - minX) * resistance
                            : (_flickable.contentX > maxX) ? maxX + (_flickable.contentX - maxX) * resistance
                            : _flickable.contentX

        _flickable.contentY = (_flickable.contentY < minY) ? minY + (_flickable.contentY - minY) * resistance
                            : (_flickable.contentY > maxY) ? maxY + (_flickable.contentY - maxY) * resistance
                            : _flickable.contentY
    }

    function reset() {
        _scaleTransform.xScale = 1
        _scaleTransform.yScale = 1
        _scaleTransform.origin.x = 0
        _scaleTransform.origin.y = 0
        _flickable.contentX = 0
        _flickable.contentY = 0
    }

    Flickable { id: _flickable
        anchors.fill    : parent
        contentWidth    : _image.width  * _scaleTransform.xScale
        contentHeight   : _image.height * _scaleTransform.yScale
        clip            : true
        interactive     : !pinching

        Behavior on contentX { NumberAnimation { duration: 150; easing.type: Easing.OutCubic } }
        Behavior on contentY { NumberAnimation { duration: 150; easing.type: Easing.OutCubic } }

        PinchArea {
            anchors.fill: parent

            pinch.minimumScale: minScale
            pinch.maximumScale: maxScale
            pinch.dragAxis: Pinch.XAndYAxis

            Image { id: _image
                fillMode: Image.PreserveAspectFit

                transform: Scale { id: _scaleTransform
                    origin.x: 0
                    origin.y: 0
                    xScale: 1
                    yScale: 1

                    Behavior on xScale { NumberAnimation { duration: 150; easing.type: Easing.OutQuad } }
                    Behavior on yScale { NumberAnimation { duration: 150; easing.type: Easing.OutQuad } }
                }

                x: (_flickable.contentWidth <= _flickable.width)
                   ? (_flickable.width - width * _scaleTransform.xScale)
                   : -_flickable.contentX

                y: (_flickable.contentHeight <= _flickable.height)
                   ? (_flickable.height - height * _scaleTransform.yScale)
                   : -_flickable.contentY

                Behavior on x { NumberAnimation { duration: 80; easing.type: Easing.OutCubic } }
                Behavior on y { NumberAnimation { duration: 80; easing.type: Easing.OutCubic } }
            }

            onPinchStarted: (pinch) => {
                pinching   = true
                startScale = _scaleTransform.xScale
            }

            onPinchUpdated: (pinch) => {
                if (Math.abs(pinch.scale - 1) < 0.01) return

                // --- scale ---
                let newScale = startScale * pinch.scale
                newScale = Math.max(minScale, Math.min(maxScale, newScale))

                // 🔑 origin follows fingers (convert to content space)
                _scaleTransform.origin.x = pinch.center.x + _flickable.contentX
                _scaleTransform.origin.y = pinch.center.y + _flickable.contentY

                _scaleTransform.xScale = newScale
                _scaleTransform.yScale = newScale

                // --- pan ---
                let dx = pinch.center.x - pinch.previousCenter.x
                let dy = pinch.center.y - pinch.previousCenter.y

                _flickable.contentX -= dx
                _flickable.contentY -= dy

                rubberClamp()
            }

            onPinchFinished: {
                Qt.callLater(() => pinching = false)
            }
        }
    }
}
