import AudioToolbox
import QuartzCore
import UIKit

// MARK: - Recording State

enum VoiceRecordingState {
    case idle
    case waiting         // finger down, waiting for threshold (0.19s)
    case recording       // actively recording, finger held
    case locked          // slid up past lock threshold, finger released
    case cancelled       // slid left past cancel threshold
    case finished        // finger released normally → send
}

// MARK: - RecordingMicButtonDelegate

@MainActor
protocol RecordingMicButtonDelegate: AnyObject {
    /// Finger down, hold timer armed.
    func micButtonRecordingArmed(_ button: RecordingMicButton)

    /// Hold was cancelled before threshold (tap / move / system cancel).
    func micButtonRecordingArmingCancelled(_ button: RecordingMicButton)

    /// Recording threshold reached (0.19s hold). Start actual recording.
    func micButtonRecordingBegan(_ button: RecordingMicButton)

    /// Finger released normally → send the recording.
    func micButtonRecordingFinished(_ button: RecordingMicButton)

    /// Slid left past cancel threshold → discard recording.
    func micButtonRecordingCancelled(_ button: RecordingMicButton)

    /// Slid up past lock threshold → lock into hands-free recording.
    func micButtonRecordingLocked(_ button: RecordingMicButton)

    /// Raw drag distances for overlay transforms (Telegram: continueTrackingWithTouch).
    /// distanceX: negative = left (cancel), distanceY: negative = up (lock)
    func micButtonDragUpdate(_ button: RecordingMicButton, distanceX: CGFloat, distanceY: CGFloat)
}

// MARK: - RecordingMicButton

/// Custom UIControl that handles voice recording gestures.
/// Ported from Telegram's `TGModernConversationInputMicButton`.
///
/// Gesture mechanics:
/// - Long press (0.19s) → begin recording
/// - Slide left → cancel (threshold: -150px, haptic at -100px)
/// - Slide up → lock (threshold: -110px, haptic at -60px)
/// - Release velocity gate: <-400 px/s on X/Y commits cancel/lock
/// - Release → finish (send)
final class RecordingMicButton: UIControl {

    weak var recordingDelegate: RecordingMicButtonDelegate?

    private(set) var recordingState: VoiceRecordingState = .idle

    // MARK: - Gesture Thresholds (Telegram parity)

    private let holdThreshold: TimeInterval = VoiceRecordingParityConstants.holdThreshold
    private let cancelDistanceThreshold: CGFloat = VoiceRecordingParityConstants.cancelDistanceThreshold
    private let cancelHapticThreshold: CGFloat = VoiceRecordingParityConstants.cancelHapticThreshold
    private let lockDistanceThreshold: CGFloat = VoiceRecordingParityConstants.lockDistanceThreshold
    private let lockHapticThreshold: CGFloat = VoiceRecordingParityConstants.lockHapticThreshold
    private let velocityGate: CGFloat = VoiceRecordingParityConstants.velocityGate
    private let preHoldCancelDistance: CGFloat = VoiceRecordingParityConstants.preHoldCancelDistance

    // MARK: - Tracking State

    private var touchStartLocation: CGPoint = .zero
    private var lastTouchLocation: CGPoint = .zero
    private var lastTouchTimestamp: TimeInterval = 0
    private var velocityX: CGFloat = 0
    private var velocityY: CGFloat = 0
    private var holdTimer: Timer?
    private var displayLink: CADisplayLink?

    // Raw target values (set by touch events)
    private var targetCancelTranslation: CGFloat = 0
    private var targetLockTranslation: CGFloat = 0

    // Smoothed values (updated by display link)
    private var currentCancelTranslation: CGFloat = 0
    private var currentLockTranslation: CGFloat = 0

    // Haptic tracking
    private var didCancelHaptic = false
    private var didLockHaptic = false

    // Haptic generator — eager init so object is ready long before first use
    private let impactFeedback = UIImpactFeedbackGenerator(style: .medium)

    // MARK: - Init

    override init(frame: CGRect) {
        super.init(frame: frame)
    }

    override func didMoveToWindow() {
        super.didMoveToWindow()
        if window != nil {
            impactFeedback.prepare()
        }
    }

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

    // MARK: - Touch Tracking

    override func beginTracking(_ touch: UITouch, with event: UIEvent?) -> Bool {
        guard recordingState == .idle else { return false }

        touchStartLocation = touch.location(in: window)
        lastTouchLocation = touchStartLocation
        lastTouchTimestamp = touch.timestamp
        velocityX = 0
        velocityY = 0
        recordingState = .waiting
        targetCancelTranslation = 0
        targetLockTranslation = 0
        currentCancelTranslation = 0
        currentLockTranslation = 0
        didCancelHaptic = false
        didLockHaptic = false

        impactFeedback.prepare()
        print("[HAPTIC-MIC] beginTracking — prepare() called")
        recordingDelegate?.micButtonRecordingArmed(self)

        // Start hold timer — after 0.19s we begin recording
        holdTimer = Timer.scheduledTimer(withTimeInterval: holdThreshold, repeats: false) { [weak self] _ in
            self?.beginRecording()
        }

        return true
    }

    override func continueTracking(_ touch: UITouch, with event: UIEvent?) -> Bool {
        guard recordingState == .waiting || recordingState == .recording else { return false }

        let location = touch.location(in: window)
        let distanceX = min(0, location.x - touchStartLocation.x)
        let distanceY = min(0, location.y - touchStartLocation.y)
        updateVelocity(with: touch, at: location)

        // Check if we moved enough to cancel the hold timer (before recording started)
        if recordingState == .waiting {
            let totalDistance = sqrt(distanceX * distanceX + distanceY * distanceY)
            if totalDistance > preHoldCancelDistance {
                // Movement before threshold — cancel the timer, don't start recording
                cancelHoldTimer()
                recordingState = .idle
                recordingDelegate?.micButtonRecordingArmingCancelled(self)
                return false
            }
            return true
        }

        // Recording state — track slide gestures
        targetCancelTranslation = distanceX
        targetLockTranslation = distanceY

        // Cancel haptic
        if distanceX < cancelHapticThreshold, !didCancelHaptic {
            didCancelHaptic = true
            fireHaptic()
        }

        // Lock haptic
        if distanceY < lockHapticThreshold, !didLockHaptic {
            didLockHaptic = true
            fireHaptic()
        }

        // Check cancel threshold
        if distanceX < cancelDistanceThreshold {
            commitCancel()
            return false
        }

        // Check lock threshold
        if distanceY < lockDistanceThreshold {
            commitLock()
            return false
        }

        return true
    }

    override func endTracking(_ touch: UITouch?, with event: UIEvent?) {
        if recordingState == .waiting {
            // Released before hold threshold — just a tap
            cancelHoldTimer()
            recordingState = .idle
            recordingDelegate?.micButtonRecordingArmingCancelled(self)
            return
        }

        if recordingState == .recording {
            // Fallback to distance thresholds on release.
            if let touch {
                let location = touch.location(in: window)
                var distanceX = min(0, location.x - touchStartLocation.x)
                var distanceY = min(0, location.y - touchStartLocation.y)

                // Telegram parity: keep only dominant direction on release.
                (distanceX, distanceY) = VoiceRecordingParityMath.dominantAxisDistances(
                    distanceX: distanceX,
                    distanceY: distanceY
                )

                switch VoiceRecordingParityMath.releaseDecision(
                    velocityX: velocityX,
                    velocityY: velocityY,
                    distanceX: distanceX,
                    distanceY: distanceY
                ) {
                case .cancel:
                    commitCancel()
                    return
                case .lock:
                    commitLock()
                    return
                case .finish:
                    break
                }
            }

            // Normal release → finish recording (send)
            commitFinish()
        }
    }

    override func cancelTracking(with event: UIEvent?) {
        if recordingState == .recording {
            // Telegram parity: delayed lock after cancelTracking.
            targetLockTranslation = 0
            DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) { [weak self] in
                guard let self, self.recordingState == .recording else { return }
                self.commitLock()
            }
        } else {
            cancelHoldTimer()
            recordingState = .idle
            recordingDelegate?.micButtonRecordingArmingCancelled(self)
        }
        stopDisplayLink()
    }

    override func point(inside point: CGPoint, with event: UIEvent?) -> Bool {
        bounds.insetBy(dx: VoiceRecordingParityConstants.micHitInsetX, dy: 0).contains(point)
    }

    // MARK: - State Transitions

    private func beginRecording() {
        guard recordingState == .waiting else {
            print("[HAPTIC-MIC] beginRecording SKIPPED — state=\(recordingState)")
            return
        }
        recordingState = .recording
        holdTimer = nil
        print("[HAPTIC-MIC] beginRecording — calling delegate")

        startDisplayLink()
        recordingDelegate?.micButtonRecordingBegan(self)
    }

    private func commitCancel() {
        guard recordingState == .recording else { return }
        recordingState = .cancelled
        stopDisplayLink()
        recordingDelegate?.micButtonRecordingCancelled(self)
    }

    private func commitLock() {
        guard recordingState == .recording else { return }
        recordingState = .locked
        stopDisplayLink()
        fireHaptic()
        recordingDelegate?.micButtonRecordingLocked(self)
    }

    private func commitFinish() {
        guard recordingState == .recording else { return }
        recordingState = .finished
        stopDisplayLink()
        recordingDelegate?.micButtonRecordingFinished(self)
    }

    // MARK: - Public API

    /// Reset to idle state (call after processing send/cancel/lock).
    func resetState() {
        cancelHoldTimer()
        stopDisplayLink()
        recordingState = .idle
        velocityX = 0
        velocityY = 0
        targetCancelTranslation = 0
        targetLockTranslation = 0
        currentCancelTranslation = 0
        currentLockTranslation = 0
    }

#if DEBUG
    func debugSetRecordingState(_ state: VoiceRecordingState) {
        recordingState = state
    }
#endif

    // MARK: - Display Link

    private func startDisplayLink() {
        guard displayLink == nil else { return }
        let link = CADisplayLink(target: self, selector: #selector(displayLinkUpdate))
        link.preferredFrameRateRange = CAFrameRateRange(minimum: 24, maximum: 30, preferred: 30)
        link.add(to: .main, forMode: .common)
        displayLink = link
    }

    private func stopDisplayLink() {
        displayLink?.invalidate()
        displayLink = nil
    }

    @objc private func displayLinkUpdate() {
        // Telegram exact: 0.7/0.3 blend (TGModernConversationInputMicButton.m line 918-919)
        currentCancelTranslation = min(0, currentCancelTranslation * 0.7 + targetCancelTranslation * 0.3)
        currentLockTranslation = min(0, currentLockTranslation * 0.7 + targetLockTranslation * 0.3)

        // Report raw smoothed distances for overlay transforms
        recordingDelegate?.micButtonDragUpdate(self, distanceX: currentCancelTranslation, distanceY: currentLockTranslation)
    }

    // MARK: - Helpers

    /// Belt-and-suspenders haptic: UIImpactFeedbackGenerator + direct system call.
    /// AudioServicesPlaySystemSound(1519) is the "peek" haptic — bypasses
    /// UIFeedbackGenerator API and hits Taptic Engine directly.
    /// Telegram uses this as fallback in HapticFeedback.swift.
    private func fireHaptic() {
        print("[HAPTIC-MIC] fireHaptic() — state=\(recordingState)")
        impactFeedback.impactOccurred()
        AudioServicesPlaySystemSound(1519)
        impactFeedback.prepare()
    }

    private func cancelHoldTimer() {
        holdTimer?.invalidate()
        holdTimer = nil
    }

    private func updateVelocity(with touch: UITouch, at location: CGPoint) {
        let dt = max(0.001, touch.timestamp - lastTouchTimestamp)
        velocityX = (location.x - lastTouchLocation.x) / dt
        velocityY = (location.y - lastTouchLocation.y) / dt
        lastTouchLocation = location
        lastTouchTimestamp = touch.timestamp
    }
}
