import QuartzCore
import UIKit

// MARK: - WaveformView

/// Renders audio waveform as vertical bars with rounded ellipse caps.
/// Telegram parity from AudioWaveformNode.swift:
/// - Bar width: 2pt, gap: 1pt, peak height: 12pt
/// - Each bar = rect body + top ellipse cap + bottom ellipse cap
/// - Gravity: .center (bars grow from center) or .bottom
final class WaveformView: UIView {

    enum Gravity { case center, bottom }

    // MARK: - Configuration
    // Bubble context: distance=2.0, gravity=.bottom (Telegram AudioWaveformComponent)
    // Recording preview: distance=1.0, gravity=.center (Telegram AudioWaveformNode)

    private let sampleWidth: CGFloat = 2.0
    private let halfSampleWidth: CGFloat = 1.0
    var distance: CGFloat = 1.0

    var peakHeight: CGFloat = 12.0
    var gravity: Gravity = .center
    var backgroundColor_: UIColor = UIColor.white.withAlphaComponent(0.3)
    var foregroundColor_: UIColor = UIColor(red: 0, green: 136/255.0, blue: 1, alpha: 1)

    // MARK: - Scrubbing

    /// Enable pan-to-seek gesture (only active during playback in bubble context).
    var enableScrubbing: Bool = false {
        didSet { panGesture?.isEnabled = enableScrubbing }
    }
    /// Called on gesture end with fraction 0..1.
    var onSeek: ((Double) -> Void)?

    private var panGesture: UIPanGestureRecognizer?
    private(set) var isScrubbing = false
    private var scrubbingStartProgress: CGFloat = 0

    // MARK: - State

    private var samples: [Float] = []
    var progress: CGFloat = 0 {
        didSet { setNeedsDisplay() }
    }

    /// Cached resampled data — recomputed only when samples or view width changes.
    private var cachedResampled: [Float] = []
    private var cachedWidth: CGFloat = 0

    // MARK: - Init

    override init(frame: CGRect) {
        super.init(frame: frame)
        backgroundColor = .clear
        isOpaque = false

        let pan = UIPanGestureRecognizer(target: self, action: #selector(handlePan(_:)))
        pan.isEnabled = false  // enabled only when enableScrubbing = true
        addGestureRecognizer(pan)
        panGesture = pan
    }

    convenience init(
        foregroundColor: UIColor = UIColor(red: 0, green: 136/255.0, blue: 1, alpha: 1),
        backgroundColor: UIColor = UIColor.white.withAlphaComponent(0.3)
    ) {
        self.init(frame: .zero)
        self.foregroundColor_ = foregroundColor
        self.backgroundColor_ = backgroundColor
    }

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

    // MARK: - Public

    func setSamples(_ newSamples: [Float]) {
        samples = newSamples
        cachedResampled = []  // Invalidate cache
        cachedWidth = 0
        setNeedsDisplay()
    }

    // MARK: - Drawing (Telegram AudioWaveformComponent single-pass with per-bar color blend)

    override func draw(_ rect: CGRect) {
        guard !samples.isEmpty else { return }
        guard let ctx = UIGraphicsGetCurrentContext() else { return }

        let size = rect.size
        let numSamples = Int(floor(size.width / (sampleWidth + distance)))
        guard numSamples > 0 else { return }

        // Use cached resampled data when samples and width haven't changed
        if cachedWidth != size.width || cachedResampled.count != numSamples {
            cachedResampled = resample(samples, toCount: numSamples)
            cachedWidth = size.width
        }
        let resampled = cachedResampled

        // Telegram: diff = sampleWidth * 1.5 = 3.0 (subtracted from bar height)
        let diff: CGFloat = sampleWidth * 1.5

        let gravityMultiplierY: CGFloat = gravity == .bottom ? 1.0 : 0.5

        // Pre-extract RGBA components for fast blending
        var bgR: CGFloat = 0, bgG: CGFloat = 0, bgB: CGFloat = 0, bgA: CGFloat = 0
        var fgR: CGFloat = 0, fgG: CGFloat = 0, fgB: CGFloat = 0, fgA: CGFloat = 0
        backgroundColor_.getRed(&bgR, green: &bgG, blue: &bgB, alpha: &bgA)
        foregroundColor_.getRed(&fgR, green: &fgG, blue: &fgB, alpha: &fgA)

        // Single-pass: each bar individually colored (Telegram AudioWaveformComponent)
        for i in 0..<numSamples {
            let offset = CGFloat(i) * (sampleWidth + distance)

            // Per-bar color blend at progress boundary
            let startFraction = CGFloat(i) / CGFloat(numSamples)
            let nextStartFraction = CGFloat(i + 1) / CGFloat(numSamples)
            let colorMix: CGFloat
            if startFraction < progress {
                colorMix = min(1.0, max(0.0, (progress - startFraction) / (nextStartFraction - startFraction)))
            } else {
                colorMix = 0.0
            }
            let r = bgR + (fgR - bgR) * colorMix
            let g = bgG + (fgG - bgG) * colorMix
            let b = bgB + (fgB - bgB) * colorMix
            let a = bgA + (fgA - bgA) * colorMix
            ctx.setFillColor(red: r, green: g, blue: b, alpha: a)
            ctx.setBlendMode(.copy)

            var sampleHeight = CGFloat(resampled[i]) * peakHeight
            if sampleHeight > peakHeight { sampleHeight = peakHeight }

            let adjustedSampleHeight = sampleHeight - diff

            if adjustedSampleHeight <= sampleWidth {
                // Tiny bar: single dot + small rect
                ctx.fillEllipse(in: CGRect(
                    x: offset,
                    y: (size.height - sampleWidth) * gravityMultiplierY,
                    width: sampleWidth,
                    height: sampleWidth
                ))
                ctx.fill(CGRect(
                    x: offset,
                    y: (size.height - halfSampleWidth) * gravityMultiplierY,
                    width: sampleWidth,
                    height: halfSampleWidth
                ))
            } else {
                // Normal bar: rect + top cap + bottom cap
                let barRect = CGRect(
                    x: offset,
                    y: (size.height - adjustedSampleHeight) * gravityMultiplierY,
                    width: sampleWidth,
                    height: adjustedSampleHeight
                )
                ctx.fill(barRect)
                ctx.fillEllipse(in: CGRect(
                    x: barRect.minX,
                    y: barRect.minY - halfSampleWidth,
                    width: sampleWidth,
                    height: sampleWidth
                ))
                ctx.fillEllipse(in: CGRect(
                    x: barRect.minX,
                    y: barRect.maxY - halfSampleWidth,
                    width: sampleWidth,
                    height: sampleWidth
                ))
            }
        }
    }

    // MARK: - Scrubbing Gesture (Telegram AudioWaveformComponent lines 192-306)

    @objc private func handlePan(_ gesture: UIPanGestureRecognizer) {
        let location = gesture.location(in: self)
        let verticalDistance = abs(gesture.translation(in: self).y)

        // Telegram slow-scrub: vertical drag reduces precision
        let multiplier: CGFloat
        if verticalDistance > 150 {
            multiplier = 0.01
        } else if verticalDistance > 100 {
            multiplier = 0.25
        } else if verticalDistance > 50 {
            multiplier = 0.5
        } else {
            multiplier = 1.0
        }

        switch gesture.state {
        case .began:
            isScrubbing = true
            scrubbingStartProgress = progress
        case .changed:
            let fraction = location.x / max(1, bounds.width)
            let delta = (fraction - scrubbingStartProgress) * multiplier
            progress = max(0, min(1, scrubbingStartProgress + delta))
        case .ended, .cancelled:
            isScrubbing = false
            onSeek?(Double(progress))
        default:
            break
        }
    }

    // MARK: - Resampling (Telegram: max extraction per bin)

    private func resample(_ input: [Float], toCount count: Int) -> [Float] {
        guard !input.isEmpty, count > 0 else { return Array(repeating: 0, count: count) }
        var result = [Float](repeating: 0, count: count)
        let step = Float(input.count) / Float(count)
        for i in 0..<count {
            let start = Int(Float(i) * step)
            let end = min(input.count, Int(Float(i + 1) * step))
            guard start < end else { continue }
            let slice = input[start..<end]
            result[i] = slice.max() ?? 0
        }
        return result
    }
}
