import UIKit
import ComponentFlow
import Display
import AsyncDisplayKit
import TelegramCore
import Postbox
import AccountContext
import AvatarNode
import TextFormat
import Markdown
import WallpaperBackgroundNode

final class BlurredRoundedRectangle: Component {
    let color: UIColor

    init(color: UIColor) {
        self.color = color
    }

    static func ==(lhs: BlurredRoundedRectangle, rhs: BlurredRoundedRectangle) -> Bool {
        if !lhs.color.isEqual(rhs.color) {
            return false
        }
        return true
    }

    final class View: UIView {
        private let background: NavigationBackgroundNode

        init() {
            self.background = NavigationBackgroundNode(color: .clear)

            super.init(frame: CGRect())

            self.addSubview(self.background.view)
        }

        required init?(coder aDecoder: NSCoder) {
            preconditionFailure()
        }

        func update(component: BlurredRoundedRectangle, availableSize: CGSize, transition: Transition) -> CGSize {
            transition.setFrame(view: self.background.view, frame: CGRect(origin: CGPoint(), size: availableSize))
            self.background.updateColor(color: component.color, transition: .immediate)
            self.background.update(size: availableSize, cornerRadius: min(availableSize.width, availableSize.height) / 2.0, transition: .immediate)

            return availableSize
        }
    }

    func makeView() -> View {
        return View()
    }

    func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: Transition) -> CGSize {
        return view.update(component: self, availableSize: availableSize, transition: transition)
    }
}

final class RadialProgressComponent: Component {
    let color: UIColor
    let lineWidth: CGFloat
    let value: CGFloat

    init(
        color: UIColor,
        lineWidth: CGFloat,
        value: CGFloat
    ) {
        self.color = color
        self.lineWidth = lineWidth
        self.value = value
    }

    static func ==(lhs: RadialProgressComponent, rhs: RadialProgressComponent) -> Bool {
        if !lhs.color.isEqual(rhs.color) {
            return false
        }
        if lhs.lineWidth != rhs.lineWidth {
            return false
        }
        if lhs.value != rhs.value {
            return false
        }
        return true
    }

    final class View: UIView {
        init() {
            super.init(frame: CGRect())
        }

        required init?(coder aDecoder: NSCoder) {
            preconditionFailure()
        }

        func update(component: RadialProgressComponent, availableSize: CGSize, transition: Transition) -> CGSize {
            func draw(context: CGContext) {
                let diameter = availableSize.width

                context.saveGState()

                context.setBlendMode(.normal)
                context.setFillColor(component.color.cgColor)
                context.setStrokeColor(component.color.cgColor)

                var progress: CGFloat
                var startAngle: CGFloat
                var endAngle: CGFloat

                let value = component.value

                progress = value
                startAngle = -CGFloat.pi / 2.0
                endAngle = CGFloat(progress) * 2.0 * CGFloat.pi + startAngle

                if progress > 1.0 {
                    progress = 2.0 - progress
                    let tmp = startAngle
                    startAngle = endAngle
                    endAngle = tmp
                }
                progress = min(1.0, progress)

                let lineWidth: CGFloat = component.lineWidth

                let pathDiameter: CGFloat

                pathDiameter = diameter - lineWidth

                var angle: Double = 0.0
                angle *= 4.0

                context.translateBy(x: diameter / 2.0, y: diameter / 2.0)
                context.rotate(by: CGFloat(angle.truncatingRemainder(dividingBy: Double.pi * 2.0)))
                context.translateBy(x: -diameter / 2.0, y: -diameter / 2.0)

                let path = UIBezierPath(arcCenter: CGPoint(x: diameter / 2.0, y: diameter / 2.0), radius: pathDiameter / 2.0, startAngle: startAngle, endAngle: endAngle, clockwise: true)
                path.lineWidth = lineWidth
                path.lineCapStyle = .round
                path.stroke()

                context.restoreGState()
            }

            if #available(iOS 10.0, *) {
                let renderer = UIGraphicsImageRenderer(bounds: CGRect(origin: CGPoint(), size: availableSize))
                let image = renderer.image { context in
                    UIGraphicsPushContext(context.cgContext)
                    draw(context: context.cgContext)
                    UIGraphicsPopContext()
                }
                self.layer.contents = image.cgImage
            } else {
                UIGraphicsBeginImageContextWithOptions(availableSize, false, 0.0)
                draw(context: UIGraphicsGetCurrentContext()!)
                self.layer.contents = UIGraphicsGetImageFromCurrentImageContext()?.cgImage
                UIGraphicsEndImageContext()
            }

            return availableSize
        }
    }

    func makeView() -> View {
        return View()
    }

    func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: Transition) -> CGSize {
        return view.update(component: self, availableSize: availableSize, transition: transition)
    }
}

final class CheckComponent: Component {
    let color: UIColor
    let lineWidth: CGFloat
    let value: CGFloat

    init(
        color: UIColor,
        lineWidth: CGFloat,
        value: CGFloat
    ) {
        self.color = color
        self.lineWidth = lineWidth
        self.value = value
    }

    static func ==(lhs: CheckComponent, rhs: CheckComponent) -> Bool {
        if !lhs.color.isEqual(rhs.color) {
            return false
        }
        if lhs.lineWidth != rhs.lineWidth {
            return false
        }
        if lhs.value != rhs.value {
            return false
        }
        return true
    }

    final class View: UIView {
        private var currentValue: CGFloat?
        private var animator: DisplayLinkAnimator?

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

        required init?(coder aDecoder: NSCoder) {
            preconditionFailure()
        }

        private func updateContent(size: CGSize, color: UIColor, lineWidth: CGFloat, value: CGFloat) {
            func draw(context: CGContext) {
                let diameter = size.width

                let factor = diameter / 50.0

                context.saveGState()

                context.setBlendMode(.normal)
                context.setFillColor(color.cgColor)
                context.setStrokeColor(color.cgColor)

                let center = CGPoint(x: diameter / 2.0, y: diameter / 2.0)

                context.setLineWidth(max(1.7, lineWidth * factor))
                context.setLineCap(.round)
                context.setLineJoin(.round)
                context.setMiterLimit(10.0)

                let progress = value
                let firstSegment: CGFloat = max(0.0, min(1.0, progress * 3.0))

                var s = CGPoint(x: center.x - 10.0 * factor, y: center.y + 1.0 * factor)
                var p1 = CGPoint(x: 7.0 * factor, y: 7.0 * factor)
                var p2 = CGPoint(x: 13.0 * factor, y: -15.0 * factor)

                if diameter < 36.0 {
                    s = CGPoint(x: center.x - 7.0 * factor, y: center.y + 1.0 * factor)
                    p1 = CGPoint(x: 4.5 * factor, y: 4.5 * factor)
                    p2 = CGPoint(x: 10.0 * factor, y: -11.0 * factor)
                }

                if !firstSegment.isZero {
                    if firstSegment < 1.0 {
                        context.move(to: CGPoint(x: s.x + p1.x * firstSegment, y: s.y + p1.y * firstSegment))
                        context.addLine(to: s)
                    } else {
                        let secondSegment = (progress - 0.33) * 1.5
                        context.move(to: CGPoint(x: s.x + p1.x + p2.x * secondSegment, y: s.y + p1.y + p2.y * secondSegment))
                        context.addLine(to: CGPoint(x: s.x + p1.x, y: s.y + p1.y))
                        context.addLine(to: s)
                    }
                }
                context.strokePath()

                context.restoreGState()
            }

            if #available(iOS 10.0, *) {
                let renderer = UIGraphicsImageRenderer(bounds: CGRect(origin: CGPoint(), size: size))
                let image = renderer.image { context in
                    UIGraphicsPushContext(context.cgContext)
                    draw(context: context.cgContext)
                    UIGraphicsPopContext()
                }
                self.layer.contents = image.cgImage
            } else {
                UIGraphicsBeginImageContextWithOptions(size, false, 0.0)
                draw(context: UIGraphicsGetCurrentContext()!)
                self.layer.contents = UIGraphicsGetImageFromCurrentImageContext()?.cgImage
                UIGraphicsEndImageContext()
            }
        }

        func update(component: CheckComponent, availableSize: CGSize, transition: Transition) -> CGSize {
            if let currentValue = self.currentValue, currentValue != component.value, case .curve = transition.animation {
                self.animator?.invalidate()

                let animator = DisplayLinkAnimator(duration: 0.15, from: currentValue, to: component.value, update: { [weak self] value in
                    guard let strongSelf = self else {
                        return
                    }
                    strongSelf.updateContent(size: availableSize, color: component.color, lineWidth: component.lineWidth, value: value)
                }, completion: { [weak self] in
                    guard let strongSelf = self else {
                        return
                    }
                    strongSelf.animator?.invalidate()
                    strongSelf.animator = nil
                })
                self.animator = animator
            } else {
                if self.animator == nil {
                    self.updateContent(size: availableSize, color: component.color, lineWidth: component.lineWidth, value: component.value)
                }
            }

            self.currentValue = component.value

            return availableSize
        }
    }

    func makeView() -> View {
        return View()
    }

    func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: Transition) -> CGSize {
        return view.update(component: self, availableSize: availableSize, transition: transition)
    }
}

final class BadgeComponent: CombinedComponent {
    let count: Int
    let backgroundColor: UIColor
    let foregroundColor: UIColor
    let rect: CGRect
    let withinSize: CGSize
    let wallpaperNode: WallpaperBackgroundNode?

    init(
        count: Int,
        backgroundColor: UIColor,
        foregroundColor: UIColor,
        rect: CGRect,
        withinSize: CGSize,
        wallpaperNode: WallpaperBackgroundNode?
    ) {
        self.count = count
        self.backgroundColor = backgroundColor
        self.foregroundColor = foregroundColor
        self.rect = rect
        self.withinSize = withinSize
        self.wallpaperNode = wallpaperNode
    }

    static func ==(lhs: BadgeComponent, rhs: BadgeComponent) -> Bool {
        if lhs.count != rhs.count {
            return false
        }
        if !lhs.backgroundColor.isEqual(rhs.backgroundColor) {
            return false
        }
        if !lhs.foregroundColor.isEqual(rhs.foregroundColor) {
            return false
        }
        if lhs.rect != rhs.rect {
            return false
        }
        if lhs.withinSize != rhs.withinSize {
            return false
        }
        if lhs.wallpaperNode !== rhs.wallpaperNode {
            return false
        }
        return true
    }

    static var body: Body {
        let background = Child(WallpaperBlurComponent.self)
        let text = Child(Text.self)

        return { context in
            let text = text.update(
                component: Text(
                    text: "\(context.component.count)",
                    font: Font.regular(13.0),
                    color: context.component.foregroundColor
                ),
                availableSize: CGSize(width: 100.0, height: 100.0),
                transition: .immediate
            )

            let height = text.size.height + 4.0
            let backgroundSize = CGSize(width: max(height, text.size.width + 8.0), height: height)

            let background = background.update(
                component: WallpaperBlurComponent(
                    rect: CGRect(origin: context.component.rect.origin, size: backgroundSize),
                    withinSize: context.component.withinSize,
                    color: context.component.backgroundColor,
                    wallpaperNode: context.component.wallpaperNode
                ),
                availableSize: backgroundSize,
                transition: .immediate
            )

            context.add(background
                .position(CGPoint(x: backgroundSize.width / 2.0, y: backgroundSize.height / 2.0))
                .cornerRadius(min(backgroundSize.width, backgroundSize.height) / 2.0)
                .clipsToBounds(true)
            )

            context.add(text
                .position(CGPoint(x: backgroundSize.width / 2.0, y: backgroundSize.height / 2.0))
            )

            return backgroundSize
        }
    }
}

final class AvatarComponent: Component {
    final class Badge: Equatable {
        let count: Int
        let backgroundColor: UIColor
        let foregroundColor: UIColor

        init(count: Int, backgroundColor: UIColor, foregroundColor: UIColor) {
            self.count = count
            self.backgroundColor = backgroundColor
            self.foregroundColor = foregroundColor
        }

        static func ==(lhs: Badge, rhs: Badge) -> Bool {
            if lhs.count != rhs.count {
                return false
            }
            if !lhs.backgroundColor.isEqual(rhs.backgroundColor) {
                return false
            }
            if !lhs.foregroundColor.isEqual(rhs.foregroundColor) {
                return false
            }
            return true
        }
    }

    let context: AccountContext
    let peer: EnginePeer
    let badge: Badge?
    let rect: CGRect
    let withinSize: CGSize
    let wallpaperNode: WallpaperBackgroundNode?

    init(
        context: AccountContext,
        peer: EnginePeer,
        badge: Badge?,
        rect: CGRect,
        withinSize: CGSize,
        wallpaperNode: WallpaperBackgroundNode?
    ) {
        self.context = context
        self.peer = peer
        self.badge = badge
        self.rect = rect
        self.withinSize = withinSize
        self.wallpaperNode = wallpaperNode
    }

    static func ==(lhs: AvatarComponent, rhs: AvatarComponent) -> Bool {
        if lhs.context !== rhs.context {
            return false
        }
        if lhs.peer != rhs.peer {
            return false
        }
        if lhs.badge != rhs.badge {
            return false
        }
        if lhs.rect != rhs.rect {
            return false
        }
        if lhs.withinSize != rhs.withinSize {
            return false
        }
        if lhs.wallpaperNode !== rhs.wallpaperNode {
            return false
        }
        return true
    }

    final class View: UIView {
        private let avatarNode: AvatarNode
        private let avatarMask: CAShapeLayer
        private var badgeView: ComponentHostView<Empty>?

        init() {
            self.avatarNode = AvatarNode(font: avatarPlaceholderFont(size: 26.0))
            self.avatarMask = CAShapeLayer()

            super.init(frame: CGRect())

            self.addSubview(self.avatarNode.view)
        }

        required init?(coder aDecoder: NSCoder) {
            preconditionFailure()
        }

        func update(component: AvatarComponent, availableSize: CGSize, transition: Transition) -> CGSize {
            self.avatarNode.frame = CGRect(origin: CGPoint(), size: availableSize)
            let theme = component.context.sharedContext.currentPresentationData.with({ $0 }).theme
            self.avatarNode.setPeer(context: component.context, theme: theme, peer: component.peer, emptyColor: theme.list.mediaPlaceholderColor, synchronousLoad: true)

            if let badge = component.badge {
                let badgeView: ComponentHostView<Empty>
                let animateIn = self.badgeView == nil
                if let current = self.badgeView {
                    badgeView = current
                } else {
                    badgeView = ComponentHostView<Empty>()
                    self.badgeView = badgeView
                    self.addSubview(badgeView)
                }

                let badgeSize = badgeView.update(
                    transition: .immediate,
                    component: AnyComponent(BadgeComponent(
                        count: badge.count,
                        backgroundColor: badge.backgroundColor,
                        foregroundColor: badge.foregroundColor,
                        rect: CGRect(origin: component.rect.offsetBy(dx: 0.0, dy: 0.0).origin, size: CGSize()),
                        withinSize: component.withinSize,
                        wallpaperNode: component.wallpaperNode
                    )),
                    environment: {},
                    containerSize: CGSize(width: 100.0, height: 100.0
                ))
                let badgeDiameter = min(badgeSize.width, badgeSize.height)
                let circlePoint = CGPoint(
                    x: availableSize.width / 2.0 + cos(CGFloat.pi / 4) * availableSize.width / 2.0,
                    y: availableSize.height / 2.0 - sin(CGFloat.pi / 4) * availableSize.width / 2.0
                )
                badgeView.frame = CGRect(origin: CGPoint(x: circlePoint.x - badgeDiameter / 2.0, y: circlePoint.y - badgeDiameter / 2.0), size: badgeSize)

                self.avatarMask.frame = self.avatarNode.bounds
                self.avatarMask.fillRule = .evenOdd

                let path = UIBezierPath(rect: self.avatarMask.bounds)
                path.append(UIBezierPath(roundedRect: badgeView.frame.insetBy(dx: -2.0, dy: -2.0), cornerRadius: badgeDiameter / 2.0))
                self.avatarMask.path = path.cgPath

                self.avatarNode.view.layer.mask = self.avatarMask

                if animateIn {
                    badgeView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.14)
                }
            } else if let badgeView = self.badgeView {
                self.badgeView = nil

                badgeView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.14, removeOnCompletion: false, completion: { [weak badgeView] _ in
                    badgeView?.removeFromSuperview()
                })

                self.avatarNode.view.layer.mask = nil
            }

            return availableSize
        }
    }

    func makeView() -> View {
        return View()
    }

    func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: Transition) -> CGSize {
        return view.update(component: self, availableSize: availableSize, transition: transition)
    }
}

private final class WallpaperBlurNode: ASDisplayNode {
    private var backgroundNode: WallpaperBubbleBackgroundNode?
    private let colorNode: ASDisplayNode

    override init() {
        self.colorNode = ASDisplayNode()

        super.init()

        //self.addSubnode(self.colorNode)
    }

    func update(rect: CGRect, within size: CGSize, color: UIColor, wallpaperNode: WallpaperBackgroundNode?, transition: ContainedViewLayoutTransition) {
        var transition = transition
        if self.backgroundNode == nil {
            if let backgroundNode = wallpaperNode?.makeBubbleBackground(for: .free) {
                self.backgroundNode = backgroundNode
                self.insertSubnode(backgroundNode, at: 0)
                transition = .immediate
            }
        }

        self.colorNode.backgroundColor = color
        transition.updateFrame(node: self.colorNode, frame: CGRect(origin: CGPoint(), size: rect.size))

        if let backgroundNode = self.backgroundNode {
            transition.updateFrame(node: backgroundNode, frame: CGRect(origin: CGPoint(), size: rect.size))
            backgroundNode.update(rect: rect, within: size, transition: transition)
        }
    }
}

private final class WallpaperBlurComponent: Component {
    let rect: CGRect
    let withinSize: CGSize
    let color: UIColor
    let wallpaperNode: WallpaperBackgroundNode?

    init(
        rect: CGRect,
        withinSize: CGSize,
        color: UIColor,
        wallpaperNode: WallpaperBackgroundNode?
    ) {
        self.rect = rect
        self.withinSize = withinSize
        self.color = color
        self.wallpaperNode = wallpaperNode
    }

    static func ==(lhs: WallpaperBlurComponent, rhs: WallpaperBlurComponent) -> Bool {
        if lhs.rect != rhs.rect {
            return false
        }
        if lhs.withinSize != rhs.withinSize {
            return false
        }
        if !lhs.color.isEqual(rhs.color) {
            return false
        }
        if lhs.wallpaperNode !== rhs.wallpaperNode {
            return false
        }
        return true
    }

    final class View: UIView {
        private let background: WallpaperBlurNode

        init() {
            self.background = WallpaperBlurNode()

            super.init(frame: CGRect())

            self.addSubview(self.background.view)
        }

        required init?(coder aDecoder: NSCoder) {
            preconditionFailure()
        }

        func update(component: WallpaperBlurComponent, availableSize: CGSize, transition: Transition) -> CGSize {
            transition.setFrame(view: self.background.view, frame: CGRect(origin: CGPoint(), size: availableSize))
            self.background.update(rect: component.rect, within: component.withinSize, color: component.color, wallpaperNode: component.wallpaperNode, transition: .immediate)

            return availableSize
        }
    }

    func makeView() -> View {
        return View()
    }

    func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: Transition) -> CGSize {
        return view.update(component: self, availableSize: availableSize, transition: transition)
    }
}

final class OverscrollContentsComponent: Component {
    let context: AccountContext
    let backgroundColor: UIColor
    let foregroundColor: UIColor
    let peer: EnginePeer?
    let unreadCount: Int
    let location: TelegramEngine.NextUnreadChannelLocation
    let expandOffset: CGFloat
    let freezeProgress: Bool
    let absoluteRect: CGRect
    let absoluteSize: CGSize
    let wallpaperNode: WallpaperBackgroundNode?

    init(
        context: AccountContext,
        backgroundColor: UIColor,
        foregroundColor: UIColor,
        peer: EnginePeer?,
        unreadCount: Int,
        location: TelegramEngine.NextUnreadChannelLocation,
        expandOffset: CGFloat,
        freezeProgress: Bool,
        absoluteRect: CGRect,
        absoluteSize: CGSize,
        wallpaperNode: WallpaperBackgroundNode?
    ) {
        self.context = context
        self.backgroundColor = backgroundColor
        self.foregroundColor = foregroundColor
        self.peer = peer
        self.unreadCount = unreadCount
        self.location = location
        self.expandOffset = expandOffset
        self.freezeProgress = freezeProgress
        self.absoluteRect = absoluteRect
        self.absoluteSize = absoluteSize
        self.wallpaperNode = wallpaperNode
    }

    static func ==(lhs: OverscrollContentsComponent, rhs: OverscrollContentsComponent) -> Bool {
        if lhs.context !== rhs.context {
            return false
        }
        if !lhs.backgroundColor.isEqual(rhs.backgroundColor) {
            return false
        }
        if !lhs.foregroundColor.isEqual(rhs.foregroundColor) {
            return false
        }
        if lhs.peer != rhs.peer {
            return false
        }
        if lhs.unreadCount != rhs.unreadCount {
            return false
        }
        if lhs.location != rhs.location {
            return false
        }
        if lhs.expandOffset != rhs.expandOffset {
            return false
        }
        if lhs.freezeProgress != rhs.freezeProgress {
            return false
        }
        if lhs.absoluteRect != rhs.absoluteRect {
            return false
        }
        if lhs.absoluteSize != rhs.absoluteSize {
            return false
        }
        if lhs.wallpaperNode !== rhs.wallpaperNode {
            return false
        }
        return true
    }

    final class View: UIView {
        private let backgroundScalingContainer: ASDisplayNode
        private let backgroundNode: WallpaperBlurNode
        private let backgroundFolderMask: UIImageView
        private let backgroundClippingNode: ASDisplayNode
        private let avatarView = ComponentHostView<Empty>()
        private let checkView = ComponentHostView<Empty>()
        private let arrowNode: ASImageNode
        private let avatarScalingContainer: ASDisplayNode
        private let avatarExtraScalingContainer: ASDisplayNode
        private let avatarOffsetContainer: ASDisplayNode
        private let arrowOffsetContainer: ASDisplayNode

        private let titleOffsetContainer: ASDisplayNode
        private let titleBackgroundNode: WallpaperBlurNode
        private let titleNode: ImmediateTextNode

        private var isFullyExpanded: Bool = false

        private var validForegroundColor: UIColor?

        init() {
            self.backgroundScalingContainer = ASDisplayNode()
            self.backgroundNode = WallpaperBlurNode()
            self.backgroundNode.clipsToBounds = true

            self.backgroundFolderMask = UIImageView()
            self.backgroundFolderMask.image = UIImage(bundleImageName: "Chat/OverscrollFolder")?.stretchableImage(withLeftCapWidth: 0, topCapHeight: 40)

            self.backgroundClippingNode = ASDisplayNode()
            self.backgroundClippingNode.clipsToBounds = true
            self.arrowNode = ASImageNode()
            self.avatarScalingContainer = ASDisplayNode()
            self.avatarExtraScalingContainer = ASDisplayNode()
            self.avatarOffsetContainer = ASDisplayNode()
            self.arrowOffsetContainer = ASDisplayNode()

            self.titleOffsetContainer = ASDisplayNode()
            self.titleBackgroundNode = WallpaperBlurNode()
            self.titleBackgroundNode.clipsToBounds = true
            self.titleNode = ImmediateTextNode()

            super.init(frame: CGRect())

            self.addSubview(self.backgroundScalingContainer.view)

            self.backgroundClippingNode.addSubnode(self.backgroundNode)
            self.backgroundScalingContainer.addSubnode(self.backgroundClippingNode)

            self.avatarScalingContainer.view.addSubview(self.avatarView)
            self.avatarScalingContainer.view.addSubview(self.checkView)
            self.avatarExtraScalingContainer.addSubnode(self.avatarScalingContainer)
            self.avatarOffsetContainer.addSubnode(self.avatarExtraScalingContainer)
            self.arrowOffsetContainer.addSubnode(self.arrowNode)
            self.backgroundNode.addSubnode(self.arrowOffsetContainer)
            self.addSubnode(self.avatarOffsetContainer)

            self.titleOffsetContainer.addSubnode(self.titleBackgroundNode)
            self.titleOffsetContainer.addSubnode(self.titleNode)
            self.addSubnode(self.titleOffsetContainer)
        }

        required init?(coder aDecoder: NSCoder) {
            preconditionFailure()
        }

        func update(component: OverscrollContentsComponent, availableSize: CGSize, transition: Transition) -> CGSize {
            if let _ = component.peer {
                self.avatarView.isHidden = false
                self.checkView.isHidden = true
            } else {
                self.avatarView.isHidden = true
                self.checkView.isHidden = false
            }

            let fullHeight: CGFloat = 94.0
            let backgroundWidth: CGFloat = 56.0
            let minBackgroundHeight: CGFloat = backgroundWidth + 5.0
            let avatarInset: CGFloat = 6.0

            let apparentExpandOffset: CGFloat
            if component.freezeProgress {
                apparentExpandOffset = fullHeight
            } else {
                apparentExpandOffset = component.expandOffset
            }

            let isFullyExpanded = apparentExpandOffset >= fullHeight

            let isFolderMask: Bool
            switch component.location {
            case .archived, .folder:
                isFolderMask = true
            default:
                isFolderMask = false
            }

            let expandProgress: CGFloat = max(0.1, min(1.0, apparentExpandOffset / fullHeight))
            let trueExpandProgress: CGFloat = max(0.1, min(1.0, component.expandOffset / fullHeight))

            func interpolate(from: CGFloat, to: CGFloat, value: CGFloat) -> CGFloat {
                return (1.0 - value) * from + value * to
            }

            let backgroundHeight: CGFloat = interpolate(from: minBackgroundHeight, to: fullHeight, value: trueExpandProgress)

            let backgroundFrame = CGRect(origin: CGPoint(x: floor((availableSize.width - backgroundWidth) / 2.0), y: fullHeight - backgroundHeight), size: CGSize(width: backgroundWidth, height: backgroundHeight))

            let alphaProgress: CGFloat = max(0.0, min(1.0, apparentExpandOffset / 10.0))

            let maxAvatarScale: CGFloat = 1.0
            var avatarExpandProgress: CGFloat = max(0.01, min(maxAvatarScale, apparentExpandOffset / fullHeight))
            avatarExpandProgress *= expandProgress

            let avatarOffsetProgress = interpolate(from: 0.1, to: 1.0, value: avatarExpandProgress)

            transition.setAlpha(view: self.backgroundScalingContainer.view, alpha: alphaProgress)
            transition.setFrame(view: self.backgroundScalingContainer.view, frame: CGRect(origin: CGPoint(x: floor(availableSize.width / 2.0), y: fullHeight), size: CGSize(width: 0.0, height: 0.0)))
            transition.setSublayerTransform(view: self.backgroundScalingContainer.view, transform: CATransform3DMakeScale(expandProgress, expandProgress, 1.0))

            transition.setFrame(view: self.backgroundNode.view, frame: CGRect(origin: CGPoint(x: 0.0, y: fullHeight - backgroundFrame.size.height), size: backgroundFrame.size))
            self.backgroundNode.update(rect: backgroundFrame.offsetBy(dx: component.absoluteRect.minX, dy: component.absoluteRect.minY), within: component.absoluteSize, color: component.backgroundColor, wallpaperNode: component.wallpaperNode, transition: .immediate)
            self.backgroundFolderMask.frame = CGRect(origin: CGPoint(), size: backgroundFrame.size)

            let avatarFrame = CGRect(origin: CGPoint(x: floor(-backgroundWidth / 2.0), y: floor(-backgroundWidth / 2.0)), size: CGSize(width: backgroundWidth, height: backgroundWidth))
            self.avatarView.frame = avatarFrame

            transition.setFrame(view: self.avatarOffsetContainer.view, frame: CGRect())
            transition.setFrame(view: self.avatarScalingContainer.view, frame: CGRect())
            transition.setFrame(view: self.avatarExtraScalingContainer.view, frame: CGRect(origin: CGPoint(x: availableSize.width / 2.0, y: fullHeight - backgroundWidth / 2.0), size: CGSize()).offsetBy(dx: 0.0, dy: (1.0 - avatarOffsetProgress) * backgroundWidth * 0.5))
            transition.setSublayerTransform(view: self.avatarScalingContainer.view, transform: CATransform3DMakeScale(avatarExpandProgress, avatarExpandProgress, 1.0))

            let titleText: String
            if let peer = component.peer {
                titleText = peer.compactDisplayTitle
            } else {
                titleText = component.context.sharedContext.currentPresentationData.with({ $0 }).strings.Chat_NavigationNoChannels
            }
            self.titleNode.attributedText = NSAttributedString(string: titleText, font: Font.semibold(13.0), textColor: component.foregroundColor)
            let titleSize = self.titleNode.updateLayout(CGSize(width: availableSize.width - 32.0, height: 100.0))
            let titleBackgroundSize = CGSize(width: titleSize.width + 18.0, height: titleSize.height + 8.0)
            let titleBackgroundFrame = CGRect(origin: CGPoint(x: floor((availableSize.width - titleBackgroundSize.width) / 2.0), y: fullHeight - titleBackgroundSize.height - 8.0), size: titleBackgroundSize)
            self.titleBackgroundNode.frame = titleBackgroundFrame
            self.titleBackgroundNode.update(rect: titleBackgroundFrame.offsetBy(dx: component.absoluteRect.minX, dy: component.absoluteRect.minY), within: component.absoluteSize, color: component.backgroundColor, wallpaperNode: component.wallpaperNode, transition: .immediate)
            self.titleBackgroundNode.cornerRadius = min(titleBackgroundFrame.width, titleBackgroundFrame.height) / 2.0
            self.titleNode.frame = titleSize.centered(in: titleBackgroundFrame)

            let backgroundClippingFrame = CGRect(origin: CGPoint(x: floor(-backgroundWidth / 2.0), y: -fullHeight), size: CGSize(width: backgroundWidth, height: isFullyExpanded ? backgroundWidth : fullHeight))
            self.backgroundClippingNode.cornerRadius = isFolderMask ? 10.0 : backgroundWidth / 2.0
            self.backgroundNode.cornerRadius = isFolderMask ? 0.0 : backgroundWidth / 2.0
            self.backgroundNode.view.mask = isFolderMask ? self.backgroundFolderMask : nil

            if !(self.validForegroundColor?.isEqual(component.foregroundColor) ?? false) {
                self.validForegroundColor = component.foregroundColor
                self.arrowNode.image = generateTintedImage(image: UIImage(bundleImageName: "Chat/OverscrollArrow"), color: component.foregroundColor)
            }

            if let arrowImage = self.arrowNode.image {
                self.arrowNode.frame = CGRect(origin: CGPoint(x: floor((backgroundWidth - arrowImage.size.width) / 2.0), y: floor((backgroundWidth - arrowImage.size.width) / 2.0)), size: arrowImage.size)
            }

            let transformTransition: ContainedViewLayoutTransition
            if self.isFullyExpanded != isFullyExpanded {
                self.isFullyExpanded = isFullyExpanded
                transformTransition = .animated(duration: 0.12, curve: .easeInOut)

                if isFullyExpanded {
                    func animateBounce(layer: CALayer) {
                        layer.animateScale(from: 1.0, to: 1.1, duration: 0.1, removeOnCompletion: false, completion: { [weak layer] _ in
                            layer?.animateScale(from: 1.1, to: 1.0, duration: 0.14, timingFunction: CAMediaTimingFunctionName.easeOut.rawValue)
                        })
                    }

                    animateBounce(layer: self.backgroundClippingNode.layer)
                    animateBounce(layer: self.avatarExtraScalingContainer.layer)

                    func animateOffsetBounce(layer: CALayer) {
                        let firstAnimation = layer.makeAnimation(from: 0.0 as NSNumber, to: -5.0 as NSNumber, keyPath: "transform.translation.y", timingFunction: CAMediaTimingFunctionName.easeInEaseOut.rawValue, duration: 0.1, removeOnCompletion: false, additive: true, completion: { [weak layer] _ in
                            guard let layer = layer else {
                                return
                            }
                            let secondAnimation = layer.makeAnimation(from: -5.0 as NSNumber, to: 0.0 as NSNumber, keyPath: "transform.translation.y", timingFunction: CAMediaTimingFunctionName.easeOut.rawValue, duration: 0.14, removeOnCompletion: true, additive: true)
                            layer.add(secondAnimation, forKey: "bounceY")
                        })
                        layer.add(firstAnimation, forKey: "bounceY")
                    }

                    animateOffsetBounce(layer: self.layer)
                }
            } else {
                transformTransition = .immediate
            }

            let checkSize: CGFloat = 56.0
            self.checkView.frame = CGRect(origin: CGPoint(x: floor(-checkSize / 2.0), y: floor(-checkSize / 2.0)), size: CGSize(width: checkSize, height: checkSize))
            let _ = self.checkView.update(
                transition: Transition(animation: transformTransition.isAnimated ? .curve(duration: 0.2, curve: .easeInOut) : .none),
                component: AnyComponent(CheckComponent(
                    color: component.foregroundColor,
                    lineWidth: 3.0,
                    value: isFullyExpanded ? 1.0 : 0.0
                )),
                environment: {},
                containerSize: CGSize(width: checkSize, height: checkSize)
            )

            if let peer = component.peer {
                let _ = self.avatarView.update(
                    transition: Transition(animation: transformTransition.isAnimated ? .curve(duration: 0.2, curve: .easeInOut) : .none),
                    component: AnyComponent(AvatarComponent(
                        context: component.context,
                        peer: peer,
                        badge: isFullyExpanded ? AvatarComponent.Badge(count: component.unreadCount, backgroundColor: component.backgroundColor, foregroundColor: component.foregroundColor) : nil,
                        rect: avatarFrame.offsetBy(dx: self.avatarExtraScalingContainer.frame.midX + component.absoluteRect.minX, dy: self.avatarExtraScalingContainer.frame.midY + component.absoluteRect.minY),
                        withinSize: component.absoluteSize,
                        wallpaperNode: component.wallpaperNode
                    )),
                    environment: {},
                    containerSize: self.avatarView.bounds.size
                )
            }

            transformTransition.updateAlpha(node: self.backgroundNode, alpha: (isFullyExpanded && component.peer != nil) ? 0.0 : 1.0)
            transformTransition.updateAlpha(node: self.arrowNode, alpha: isFullyExpanded ? 0.0 : 1.0)

            transformTransition.updateSublayerTransformOffset(layer: self.avatarOffsetContainer.layer, offset: CGPoint(x: 0.0, y: isFullyExpanded ? -(fullHeight - backgroundWidth) : 0.0))
            transformTransition.updateSublayerTransformOffset(layer: self.arrowOffsetContainer.layer, offset: CGPoint(x: 0.0, y: isFullyExpanded ? -(fullHeight - backgroundWidth) : 0.0))

            transformTransition.updateSublayerTransformOffset(layer: self.titleOffsetContainer.layer, offset: CGPoint(x: 0.0, y: isFullyExpanded ? 0.0 : (titleBackgroundSize.height + 50.0)))

            transformTransition.updateSublayerTransformScale(node: self.avatarExtraScalingContainer, scale: isFullyExpanded ? 1.0 : ((backgroundWidth - avatarInset * 2.0) / backgroundWidth))

            transformTransition.updateFrame(node: self.backgroundClippingNode, frame: backgroundClippingFrame)

            return CGSize(width: availableSize.width, height: fullHeight)
        }
    }

    func makeView() -> View {
        return View()
    }

    func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: Transition) -> CGSize {
        return view.update(component: self, availableSize: availableSize, transition: transition)
    }
}

final class ChatOverscrollControl: CombinedComponent {
    let backgroundColor: UIColor
    let foregroundColor: UIColor
    let peer: EnginePeer?
    let unreadCount: Int
    let location: TelegramEngine.NextUnreadChannelLocation
    let context: AccountContext
    let expandDistance: CGFloat
    let freezeProgress: Bool
    let absoluteRect: CGRect
    let absoluteSize: CGSize
    let wallpaperNode: WallpaperBackgroundNode?

    init(
        backgroundColor: UIColor,
        foregroundColor: UIColor,
        peer: EnginePeer?,
        unreadCount: Int,
        location: TelegramEngine.NextUnreadChannelLocation,
        context: AccountContext,
        expandDistance: CGFloat,
        freezeProgress: Bool,
        absoluteRect: CGRect,
        absoluteSize: CGSize,
        wallpaperNode: WallpaperBackgroundNode?
    ) {
        self.backgroundColor = backgroundColor
        self.foregroundColor = foregroundColor
        self.peer = peer
        self.unreadCount = unreadCount
        self.location = location
        self.context = context
        self.expandDistance = expandDistance
        self.freezeProgress = freezeProgress
        self.absoluteRect = absoluteRect
        self.absoluteSize = absoluteSize
        self.wallpaperNode = wallpaperNode
    }

    static func ==(lhs: ChatOverscrollControl, rhs: ChatOverscrollControl) -> Bool {
        if !lhs.backgroundColor.isEqual(rhs.backgroundColor) {
            return false
        }
        if !lhs.foregroundColor.isEqual(rhs.foregroundColor) {
            return false
        }
        if lhs.peer != rhs.peer {
            return false
        }
        if lhs.unreadCount != rhs.unreadCount {
            return false
        }
        if lhs.location != rhs.location {
            return false
        }
        if lhs.context !== rhs.context {
            return false
        }
        if lhs.expandDistance != rhs.expandDistance {
            return false
        }
        if lhs.freezeProgress != rhs.freezeProgress {
            return false
        }
        if lhs.absoluteRect != rhs.absoluteRect {
            return false
        }
        if lhs.absoluteSize != rhs.absoluteSize {
            return false
        }
        if lhs.wallpaperNode !== rhs.wallpaperNode {
            return false
        }
        return true
    }

    static var body: Body {
        let contents = Child(OverscrollContentsComponent.self)

        return { context in
            let contents = contents.update(
                component: OverscrollContentsComponent(
                    context: context.component.context,
                    backgroundColor: context.component.backgroundColor,
                    foregroundColor: context.component.foregroundColor,
                    peer: context.component.peer,
                    unreadCount: context.component.unreadCount,
                    location: context.component.location,
                    expandOffset: context.component.expandDistance,
                    freezeProgress: context.component.freezeProgress,
                    absoluteRect: context.component.absoluteRect,
                    absoluteSize: context.component.absoluteSize,
                    wallpaperNode: context.component.wallpaperNode
                ),
                availableSize: context.availableSize,
                transition: context.transition
            )

            let size = CGSize(width: context.availableSize.width, height: contents.size.height)

            context.add(contents
                .position(CGPoint(x: size.width / 2.0, y: size.height / 2.0))
            )

            return size
        }
    }
}

final class ChatInputPanelOverscrollNode: ASDisplayNode {
    let text: (String, [(Int, NSRange)])
    let priority: Int
    private let titleNode: ImmediateTextNode

    init(text: (String, [(Int, NSRange)]), color: UIColor, priority: Int) {
        self.text = text
        self.priority = priority
        self.titleNode = ImmediateTextNode()

        super.init()

        let body = MarkdownAttributeSet(font: Font.regular(14.0), textColor: color)
        let bold = MarkdownAttributeSet(font: Font.bold(14.0), textColor: color)

        self.titleNode.attributedText = addAttributesToStringWithRanges(text, body: body, argumentAttributes: [0: bold])

        self.addSubnode(self.titleNode)
    }

    func update(size: CGSize) {
        let titleSize = self.titleNode.updateLayout(size)
        self.titleNode.frame = titleSize.centered(in: CGRect(origin: CGPoint(), size: size))
    }
}