import UIKit
import QuartzCore

// MARK: - RosettaListNode

/// Base class for list view cells. Manages its own animations.
/// Telegram equivalent: `ListViewItemNode` (ListViewItemNode.swift).
///
/// Each node tracks:
/// - Its index in the data source
/// - Active animations (height, alpha, position)
/// - Layout information
///
/// Subclass or wrap existing UIView (e.g., NativeMessageCell) in this.
class RosettaListNode: UIView {

    // MARK: - State

    /// Index in the data source. nil = pending removal.
    var index: Int?

    /// Reuse identifier for node pool.
    var reuseIdentifier: String = ""

    /// Current layout (set by RosettaListView after layout calculation).
    private(set) var layout: RosettaListNodeLayout?

    /// The content view — subclasses add their UI here.
    /// Using a content view allows the node to animate height independently.
    let contentView = UIView()

    // MARK: - Animation State

    /// Animated height (used during insertion/deletion).
    /// When nil, uses layout.size.height.
    var apparentHeight: CGFloat?

    /// Active animations managed by the display link loop.
    private(set) var activeAnimations: [NodeAnimation] = []

    /// Whether this node has pending animations.
    var hasActiveAnimations: Bool { !activeAnimations.isEmpty }

    // MARK: - Init

    override init(frame: CGRect) {
        super.init(frame: frame)
        contentView.frame = bounds
        contentView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
        addSubview(contentView)
        clipsToBounds = true
    }

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

    // MARK: - Layout

    func applyLayout(_ layout: RosettaListNodeLayout) {
        self.layout = layout
    }

    /// Effective height for positioning.
    /// During animations, uses animating height. Otherwise uses layout height.
    var effectiveHeight: CGFloat {
        if let apparentHeight { return apparentHeight }
        return layout?.size.height ?? 50
    }

    // MARK: - Prepare for Reuse

    /// Called when node is returned to pool. Reset transient state.
    func prepareForReuse() {
        index = nil
        apparentHeight = nil
        activeAnimations.removeAll()
        alpha = 1
        layer.removeAllAnimations()
    }

    // MARK: - Animations

    /// Add height animation (for insertion: 0 → target, for deletion: current → 0).
    /// Telegram: `addApparentHeightAnimation` (ListViewItemNode.swift:132-147).
    func addHeightAnimation(
        from: CGFloat,
        to: CGFloat,
        duration: Double,
        beginAt: CFTimeInterval,
        curve: NodeAnimation.Curve = .spring(damping: 0.78)
    ) {
        apparentHeight = from
        let animation = NodeAnimation(
            property: .height,
            from: from,
            to: to,
            duration: duration,
            beginAt: beginAt,
            curve: curve
        )
        activeAnimations.append(animation)
    }

    /// Add alpha animation (for insertion: 0 → 1).
    func addAlphaAnimation(
        from: CGFloat,
        to: CGFloat,
        duration: Double,
        beginAt: CFTimeInterval
    ) {
        alpha = from
        let animation = NodeAnimation(
            property: .alpha,
            from: from,
            to: to,
            duration: duration,
            beginAt: beginAt,
            curve: .easeInOut
        )
        activeAnimations.append(animation)
    }

    /// Update animations at current timestamp. Returns true if any animation is active.
    @discardableResult
    func updateAnimations(at timestamp: CFTimeInterval) -> Bool {
        var i = activeAnimations.count - 1
        while i >= 0 {
            let anim = activeAnimations[i]
            let progress = anim.progress(at: timestamp)

            let value = anim.interpolatedValue(progress: progress)

            switch anim.property {
            case .height:
                apparentHeight = value
            case .alpha:
                alpha = value
            case .offsetY:
                // Handled by parent during layout pass
                break
            }

            if progress >= 1.0 {
                // Animation complete
                switch anim.property {
                case .height:
                    apparentHeight = nil // Revert to layout height
                case .alpha:
                    break
                case .offsetY:
                    break
                }
                activeAnimations.remove(at: i)
            }
            i -= 1
        }
        return !activeAnimations.isEmpty
    }
}

// MARK: - Node Animation

/// Animation state for a single property.
/// Telegram uses spring physics (ListViewItemNode.swift:28-39).
struct NodeAnimation {
    enum Property {
        case height
        case alpha
        case offsetY
    }

    enum Curve {
        case spring(damping: CGFloat)
        case easeInOut
        case linear
    }

    let property: Property
    let from: CGFloat
    let to: CGFloat
    let duration: Double
    let beginAt: CFTimeInterval
    let curve: Curve

    func progress(at timestamp: CFTimeInterval) -> Double {
        let elapsed = timestamp - beginAt
        return min(1.0, max(0.0, elapsed / duration))
    }

    func interpolatedValue(progress: Double) -> CGFloat {
        let t: CGFloat
        switch curve {
        case .spring(let damping):
            // Underdamped spring approximation (Telegram: testSpringFriction/Constant)
            let omega = CGFloat.pi * 2 / CGFloat(duration)
            let decay = exp(-damping * CGFloat(progress) * omega * 0.1)
            t = 1.0 - decay * cos(omega * CGFloat(progress) * 0.5)
            return from + (to - from) * min(1, max(0, t))
        case .easeInOut:
            // Cubic ease-in-out
            let p = CGFloat(progress)
            t = p < 0.5 ? 2 * p * p : 1 - pow(-2 * p + 2, 2) / 2
            return from + (to - from) * t
        case .linear:
            return from + (to - from) * CGFloat(progress)
        }
    }
}
