import SwiftUI
import UIKit

// MARK: - ZoomableImagePage

/// Single page in the image gallery viewer with UIKit-based gesture handling.
/// Android parity: `ZoomableImage` in `ImageViewerScreen.kt` — centroid-based pinch zoom,
/// double-tap to tap point, velocity-based dismiss, axis locking, edge tap navigation.
struct ZoomableImagePage: View {

    let attachmentId: String
    let onDismiss: () -> Void
    @Binding var showControls: Bool
    @Binding var currentScale: CGFloat
    let onEdgeTap: ((Int) -> Void)?

    @State private var image: UIImage?

    @State private var zoomScale: CGFloat = 1.0
    @State private var zoomOffset: CGSize = .zero
    @GestureState private var pinchScale: CGFloat = 1.0

    var body: some View {
        Group {
            if let image {
                let effectiveScale = zoomScale * pinchScale

                Image(uiImage: image)
                    .resizable()
                    .scaledToFit()
                    .scaleEffect(effectiveScale)
                    .offset(x: effectiveScale > 1.05 ? zoomOffset.width : 0,
                            y: effectiveScale > 1.05 ? zoomOffset.height : 0)
                    // Expand hit-test area to full screen — scaleEffect is visual-only
                    // and doesn't grow the Image's gesture frame. Without this,
                    // double-tap to zoom out doesn't work on zoomed-in edges.
                    .frame(maxWidth: .infinity, maxHeight: .infinity)
                    .contentShape(Rectangle())
                    // Double tap: zoom to 2.5x or reset (MUST be before single tap)
                    .onTapGesture(count: 2) {
                        withAnimation(.spring(response: 0.35, dampingFraction: 0.9)) {
                            if zoomScale > 1.1 {
                                zoomScale = 1.0
                                zoomOffset = .zero
                            } else {
                                zoomScale = 2.5
                            }
                            currentScale = zoomScale
                        }
                    }
                    // Single tap: toggle controls / edge navigation
                    .onTapGesture { location in
                        let width = UIScreen.main.bounds.width
                        let edgeZone = width * 0.20
                        if location.x < edgeZone {
                            onEdgeTap?(-1)
                        } else if location.x > width - edgeZone {
                            onEdgeTap?(1)
                        } else {
                            showControls.toggle()
                        }
                    }
                    // Pinch zoom
                    .simultaneousGesture(
                        MagnifyGesture()
                            .updating($pinchScale) { value, state, _ in
                                state = value.magnification
                            }
                            .onEnded { value in
                                let newScale = zoomScale * value.magnification
                                withAnimation(.spring(response: 0.35, dampingFraction: 0.9)) {
                                    zoomScale = min(max(newScale, 1.0), 5.0)
                                    if zoomScale <= 1.05 {
                                        zoomScale = 1.0
                                        zoomOffset = .zero
                                    }
                                    currentScale = zoomScale
                                }
                            }
                    )
                    // Pan when zoomed
                    .simultaneousGesture(
                        zoomScale > 1.05 ?
                        DragGesture()
                            .onChanged { value in
                                zoomOffset = value.translation
                            }
                            .onEnded { _ in
                                // Clamp offset
                            }
                        : nil
                    )
                    // Dismiss drag handled by HeroPanGesture on ImageGalleryViewer level.
            } else {
                placeholder
            }
        }
        .task {
            if let cached = AttachmentCache.shared.cachedImage(forAttachmentId: attachmentId) {
                image = cached
                return
            }
            await ImageLoadLimiter.shared.acquire()
            let loaded = await Task.detached(priority: .utility) {
                AttachmentCache.shared.loadImage(forAttachmentId: attachmentId)
            }.value
            await ImageLoadLimiter.shared.release()
            if !Task.isCancelled {
                image = loaded
            }
        }
    }

    // MARK: - Placeholder

    private var placeholder: some View {
        VStack(spacing: 16) {
            ProgressView()
                .tint(.white)
            Text("Loading...")
                .font(.system(size: 14))
                .foregroundStyle(.white.opacity(0.5))
        }
    }
}

// MARK: - UIViewRepresentable

/// Wraps `ImageGestureContainerView` for SwiftUI integration.
private struct ZoomableImageUIViewRepresentable: UIViewRepresentable {

    let image: UIImage
    let onDismiss: () -> Void
    let onDismissProgress: (CGFloat) -> Void
    let onDismissCancel: () -> Void
    let onToggleControls: () -> Void
    let onScaleChanged: (CGFloat) -> Void
    let onEdgeTap: ((Int) -> Void)?

    func makeUIView(context: Context) -> ImageGestureContainerView {
        let view = ImageGestureContainerView(image: image)
        view.onDismiss = onDismiss
        view.onDismissProgress = onDismissProgress
        view.onDismissCancel = onDismissCancel
        view.onToggleControls = onToggleControls
        view.onScaleChanged = onScaleChanged
        view.onEdgeTap = onEdgeTap
        return view
    }

    func updateUIView(_ view: ImageGestureContainerView, context: Context) {
        view.onDismiss = onDismiss
        view.onDismissProgress = onDismissProgress
        view.onDismissCancel = onDismissCancel
        view.onToggleControls = onToggleControls
        view.onScaleChanged = onScaleChanged
        view.onEdgeTap = onEdgeTap
    }
}

// MARK: - ImageGestureContainerView

/// UIKit view that handles all image gestures with full control:
/// - Centroid-based pinch zoom (1x–5x)
/// - Double-tap to zoom to tap point (2.5x) or reset
/// - Pan when zoomed (with offset clamping)
/// - Vertical drag to dismiss with velocity tracking
/// - Single tap: edge zones navigate, center toggles controls
/// - Axis locking: decides vertical dismiss vs pan early
///
/// Android parity: `ZoomableImage` in `ImageViewerScreen.kt`
final class ImageGestureContainerView: UIView, UIGestureRecognizerDelegate {

    // MARK: - Configuration

    private let minScale: CGFloat = 1.0
    private let maxScale: CGFloat = 5.0
    private let doubleTapScale: CGFloat = 2.5
    private let dismissDistanceThreshold: CGFloat = 100
    private let dismissVelocityThreshold: CGFloat = 500
    private let touchSlop: CGFloat = 20
    /// Android: left/right 20% zones are edge-tap navigation areas.
    private let edgeTapFraction: CGFloat = 0.20
    /// Android: spring(dampingRatio = 0.9, stiffness = 400) ≈ UIKit(damping: 0.9, velocity: 0)
    private let springDamping: CGFloat = 0.9
    private let springDuration: CGFloat = 0.35

    // MARK: - Subviews

    private let imageView = UIImageView()
    private var panGesture: UIPanGestureRecognizer?

    // MARK: - Transform State

    private var currentScale: CGFloat = 1.0
    private var currentOffset: CGPoint = .zero
    private var dismissOffset: CGFloat = 0

    // Pinch gesture tracking
    private var pinchStartScale: CGFloat = 1.0
    private var pinchStartOffset: CGPoint = .zero
    private var lastPinchCentroid: CGPoint = .zero

    // Pan gesture tracking
    private var panStartOffset: CGPoint = .zero
    private var isDismissGesture = false
    private var gestureAxisLocked = false

    // MARK: - Callbacks

    var onDismiss: (() -> Void)?
    var onDismissProgress: ((CGFloat) -> Void)?
    var onDismissCancel: (() -> Void)?
    var onToggleControls: (() -> Void)?
    var onScaleChanged: ((CGFloat) -> Void)?
    /// -1 = left edge, 1 = right edge
    var onEdgeTap: ((Int) -> Void)?

    // MARK: - Init

    init(image: UIImage) {
        super.init(frame: .zero)
        imageView.image = image
        imageView.contentMode = .scaleAspectFit
        imageView.isUserInteractionEnabled = false
        addSubview(imageView)
        clipsToBounds = true
        setupGestures()
    }

    @available(*, unavailable)
    required init?(coder: NSCoder) { fatalError() }

    // MARK: - Layout

    /// Track the last laid-out size so we only reset frame when it actually changes.
    /// Without this, SwiftUI state changes (e.g. `onDismissProgress`) trigger
    /// `layoutSubviews` → `imageView.frame = bounds` which RESETS the UIKit transform,
    /// causing the image to snap back during dismiss drag.
    private var lastLayoutSize: CGSize = .zero

    override func layoutSubviews() {
        super.layoutSubviews()
        guard lastLayoutSize != bounds.size else { return }
        lastLayoutSize = bounds.size
        // Temporarily reset transform, update frame, then re-apply.
        let savedTransform = imageView.transform
        imageView.transform = .identity
        imageView.frame = bounds
        imageView.transform = savedTransform
    }

    // MARK: - Gesture Setup

    private func setupGestures() {
        let pinch = UIPinchGestureRecognizer(target: self, action: #selector(handlePinch))
        pinch.delegate = self
        addGestureRecognizer(pinch)

        // Pan gesture REMOVED — replaced by SwiftUI DragGesture on the wrapper view.
        // UIKit UIPanGestureRecognizer on UIViewRepresentable intercepts ALL touches
        // before SwiftUI TabView gets them, preventing page swipe navigation.
        // SwiftUI DragGesture cooperates with TabView natively.

        let doubleTap = UITapGestureRecognizer(target: self, action: #selector(handleDoubleTap))
        doubleTap.numberOfTapsRequired = 2
        addGestureRecognizer(doubleTap)

        let singleTap = UITapGestureRecognizer(target: self, action: #selector(handleSingleTap))
        singleTap.numberOfTapsRequired = 1
        singleTap.require(toFail: doubleTap)
        addGestureRecognizer(singleTap)
    }

    // MARK: - Apply Transform

    private func applyTransform(animated: Bool = false) {
        // Guard against NaN/Infinity — prevents CoreGraphics crash and UI freeze.
        if currentScale.isNaN || currentScale.isInfinite { currentScale = 1.0 }
        if currentOffset.x.isNaN || currentOffset.x.isInfinite { currentOffset.x = 0 }
        if currentOffset.y.isNaN || currentOffset.y.isInfinite { currentOffset.y = 0 }
        if dismissOffset.isNaN || dismissOffset.isInfinite { dismissOffset = 0 }

        let transform = CGAffineTransform.identity
            .translatedBy(x: currentOffset.x, y: currentOffset.y + dismissOffset)
            .scaledBy(x: currentScale, y: currentScale)

        if animated {
            UIView.animate(
                withDuration: springDuration,
                delay: 0,
                usingSpringWithDamping: springDamping,
                initialSpringVelocity: 0,
                options: [.curveEaseOut]
            ) {
                self.imageView.transform = transform
            }
        } else {
            imageView.transform = transform
        }
    }

    // MARK: - Pinch Gesture (Centroid Zoom)

    @objc private func handlePinch(_ gesture: UIPinchGestureRecognizer) {
        switch gesture.state {
        case .began:
            pinchStartScale = currentScale
            pinchStartOffset = currentOffset
            if gesture.numberOfTouches >= 2 {
                lastPinchCentroid = gesture.location(in: self)
            }

        case .changed:
            let newScale = min(max(pinchStartScale * gesture.scale, minScale * 0.5), maxScale)

            // Centroid-based zoom: keep the point under fingers stationary
            if gesture.numberOfTouches >= 2 {
                let centroid = gesture.location(in: self)
                let viewCenter = CGPoint(x: bounds.midX, y: bounds.midY)
                let gesturePoint = CGPoint(x: centroid.x - viewCenter.x, y: centroid.y - viewCenter.y)

                let safeCurrentScale = max(currentScale, 0.01)
                let scaleRatio = newScale / safeCurrentScale
                guard scaleRatio.isFinite else { break }
                currentOffset = CGPoint(
                    x: gesturePoint.x - (gesturePoint.x - currentOffset.x) * scaleRatio,
                    y: gesturePoint.y - (gesturePoint.y - currentOffset.y) * scaleRatio
                )
                lastPinchCentroid = centroid
            }

            currentScale = newScale
            onScaleChanged?(currentScale)
            applyTransform()

        case .ended, .cancelled:
            if currentScale < minScale + 0.05 {
                // Snap back to 1x
                currentScale = minScale
                currentOffset = .zero
                onScaleChanged?(minScale)
                applyTransform(animated: true)
            } else {
                clampOffset(animated: true)
            }

        default: break
        }
    }

    // MARK: - Pan Gesture (Pan when zoomed ONLY)
    // Dismiss gesture moved to SwiftUI DragGesture on ZoomableImagePage wrapper
    // to allow TabView page swipe to work.

    @objc private func handlePan(_ gesture: UIPanGestureRecognizer) {
        // Only handle pan when zoomed — dismiss is handled by SwiftUI DragGesture
        guard currentScale > 1.05 else {
            gesture.state = .cancelled
            return
        }

        let translation = gesture.translation(in: self)

        switch gesture.state {
        case .began:
            panStartOffset = currentOffset

        case .changed:
            currentOffset = CGPoint(
                x: panStartOffset.x + translation.x,
                y: panStartOffset.y + translation.y
            )
            applyTransform()

        case .ended, .cancelled:
            clampOffset(animated: true)
            gestureAxisLocked = false

        default: break
        }
    }

    // MARK: - Double Tap (Zoom to tap point)

    @objc private func handleDoubleTap(_ gesture: UITapGestureRecognizer) {
        let tapPoint = gesture.location(in: self)
        let viewCenter = CGPoint(x: bounds.midX, y: bounds.midY)

        if currentScale > 1.1 {
            // Zoom out to 1x
            currentScale = minScale
            currentOffset = .zero
            onScaleChanged?(minScale)
            applyTransform(animated: true)
        } else {
            // Zoom in to tap point at 2.5x (Android: tapX - tapX * targetScale)
            let tapX = tapPoint.x - viewCenter.x
            let tapY = tapPoint.y - viewCenter.y

            currentScale = doubleTapScale
            currentOffset = CGPoint(
                x: tapX - tapX * doubleTapScale,
                y: tapY - tapY * doubleTapScale
            )
            clampOffsetImmediate()
            onScaleChanged?(doubleTapScale)
            applyTransform(animated: true)
        }
    }

    // MARK: - Single Tap (Edge navigation or toggle controls)

    @objc private func handleSingleTap(_ gesture: UITapGestureRecognizer) {
        guard currentScale <= 1.05 else {
            // When zoomed, single tap always toggles controls
            onToggleControls?()
            return
        }

        let tapX = gesture.location(in: self).x
        let width = bounds.width
        let edgeZone = width * edgeTapFraction

        if tapX < edgeZone {
            onEdgeTap?(-1) // Previous
        } else if tapX > width - edgeZone {
            onEdgeTap?(1) // Next
        } else {
            onToggleControls?()
        }
    }

    // MARK: - Offset Clamping

    private func clampOffset(animated: Bool) {
        guard currentScale > 1.0 else {
            currentOffset = .zero
            applyTransform(animated: animated)
            return
        }
        let clamped = clampedOffset()
        if currentOffset != clamped {
            currentOffset = clamped
            applyTransform(animated: animated)
        }
    }

    private func clampOffsetImmediate() {
        currentOffset = clampedOffset()
    }

    private func clampedOffset() -> CGPoint {
        let maxX = max(bounds.width * (currentScale - 1) / 2, 0)
        let maxY = max(bounds.height * (currentScale - 1) / 2, 0)
        return CGPoint(
            x: min(max(currentOffset.x, -maxX), maxX),
            y: min(max(currentOffset.y, -maxY), maxY)
        )
    }

    // MARK: - UIGestureRecognizerDelegate

    func gestureRecognizer(
        _ gestureRecognizer: UIGestureRecognizer,
        shouldRecognizeSimultaneouslyWith other: UIGestureRecognizer
    ) -> Bool {
        // Allow pinch + pan simultaneously (zoom + drag)
        let isPinchPan = (gestureRecognizer is UIPinchGestureRecognizer && other is UIPanGestureRecognizer) ||
                          (gestureRecognizer is UIPanGestureRecognizer && other is UIPinchGestureRecognizer)
        return isPinchPan
    }

    override func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool {
        if gestureRecognizer is UIPanGestureRecognizer {
            // Pan only when zoomed — dismiss handled by SwiftUI DragGesture
            return currentScale > 1.05
        }
        return true
    }
}
