import SwiftUI
import UIKit

/// Telegram-style swipe-to-reply modifier for message bubbles.
/// Adds a left-swipe gesture that offsets the bubble and reveals a reply arrow icon.
/// On threshold crossing: light haptic feedback. On release past threshold: triggers reply.
///
/// Architecture: applied BETWEEN the inner bubble (`.frame(maxWidth: maxBubbleWidth)`)
/// and the outer full-width frame (`.frame(maxWidth: .infinity)`). The `.offset(x:)`
/// is visual-only (does not affect layout), so the `.overlay(alignment: .trailing)`
/// added after it is positioned at the bubble's ORIGINAL trailing edge. As the bubble
/// shifts left, the icon is revealed in the gap between the shifted bubble and the
/// original position.
struct SwipeToReplyModifier: ViewModifier {
    let onReply: () -> Void

    @State private var offset: CGFloat = 0
    @State private var hasTriggeredHaptic = false
    @State private var lockedAxis: SwipeAxis?
    /// Start X in global coordinates — reject if near left screen edge (back gesture zone).
    @State private var gestureStartX: CGFloat?

    private enum SwipeAxis { case horizontal, vertical }

    /// Minimum drag distance to trigger reply action.
    private let threshold: CGFloat = 55
    /// Offset where elastic resistance begins.
    private let elasticCap: CGFloat = 85
    /// Reply icon circle diameter.
    private let iconSize: CGFloat = 34
    /// Ignore gestures starting within this distance from the left screen edge (iOS back gesture zone).
    private let backGestureEdge: CGFloat = 40

    func body(content: Content) -> some View {
        content
            .offset(x: offset)
            .overlay(alignment: .trailing) {
                replyIndicator
            }
            .simultaneousGesture(dragGesture)
    }

    // MARK: - Icon

    /// Progress from 0 (hidden) to 1 (fully visible) based on drag offset.
    private var iconProgress: CGFloat {
        let absOffset = abs(offset)
        guard absOffset > 4 else { return 0 }
        return min((absOffset - 4) / (threshold - 4), 1)
    }

    @ViewBuilder
    private var replyIndicator: some View {
        Circle()
            .fill(Color.white.opacity(0.12))
            .frame(width: iconSize, height: iconSize)
            .overlay {
                TelegramVectorIcon(
                    pathData: TelegramIconPath.replyArrow,
                    viewBox: CGSize(width: 16, height: 13),
                    color: .white
                )
                .frame(width: 14, height: 11)
            }
            .scaleEffect(iconProgress)
            .opacity(iconProgress)
    }

    // MARK: - Gesture

    private var dragGesture: some Gesture {
        DragGesture(minimumDistance: 20, coordinateSpace: .global)
            .onChanged { value in
                // Record start position on first event.
                if gestureStartX == nil {
                    gestureStartX = value.startLocation.x
                }

                // Reject gestures originating near the left screen edge (iOS back gesture zone).
                if let startX = gestureStartX, startX < backGestureEdge {
                    return
                }

                // Lock axis on first significant movement to avoid
                // interfering with vertical scroll or back-swipe navigation.
                if lockedAxis == nil {
                    let dx = abs(value.translation.width)
                    let dy = abs(value.translation.height)
                    if dx > 20 || dy > 20 {
                        // Require clear horizontal dominance (2.5:1 ratio)
                        // AND must be leftward — right swipe is back navigation.
                        let isLeftward = value.translation.width < 0
                        lockedAxis = (dx > dy * 2.5 && isLeftward) ? .horizontal : .vertical
                    }
                }

                guard lockedAxis == .horizontal else { return }

                // Only left swipe (negative)
                let raw = min(value.translation.width, 0)
                guard raw < 0 else {
                    if offset != 0 { offset = 0 }
                    return
                }

                // Elastic resistance past cap
                let absRaw = abs(raw)
                if absRaw > elasticCap {
                    let excess = absRaw - elasticCap
                    offset = -(elasticCap + excess * 0.15)
                } else {
                    offset = raw
                }

                // Haptic at threshold (once per gesture)
                if abs(offset) >= threshold, !hasTriggeredHaptic {
                    hasTriggeredHaptic = true
                    UIImpactFeedbackGenerator(style: .heavy).impactOccurred()
                }
            }
            .onEnded { _ in
                let shouldReply = abs(offset) >= threshold
                withAnimation(.spring(response: 0.32, dampingFraction: 0.7)) {
                    offset = 0
                }
                lockedAxis = nil
                hasTriggeredHaptic = false
                gestureStartX = nil
                if shouldReply {
                    onReply()
                }
            }
    }
}

/// Conditionally applies swipe-to-reply. When disabled, passes content through unchanged
/// (no gesture, no icon, no animation).
struct ConditionalSwipeToReply: ViewModifier {
    let enabled: Bool
    let onReply: () -> Void

    func body(content: Content) -> some View {
        if enabled {
            content.modifier(SwipeToReplyModifier(onReply: onReply))
        } else {
            content
        }
    }
}

// MARK: - View Extension

extension View {
    /// Adds Telegram-style swipe-to-reply gesture to a message bubble.
    func swipeToReply(onReply: @escaping () -> Void) -> some View {
        modifier(SwipeToReplyModifier(onReply: onReply))
    }
}
