import Foundation
import UIKit
import AVFoundation

final class ResultPreviewView: UIView {
    let composition: AVComposition
    
    let player: AVPlayer
    let playerLayer: AVPlayerLayer
    
    var didPlayToEndTimeObserver: NSObjectProtocol?
    
    var trimRange: Range<Double>? {
        didSet {
            if let trimRange = self.trimRange {
                self.player.currentItem?.forwardPlaybackEndTime = CMTime(seconds: trimRange.upperBound, preferredTimescale: CMTimeScale(1000))
            } else {
                self.player.currentItem?.forwardPlaybackEndTime = .invalid
            }
        }
    }
    
    var onLoop: () -> Void = {}
    var isMuted = true {
        didSet {
            self.player.isMuted = self.isMuted
        }
    }
    
    init(composition: AVComposition) {
        self.composition = composition
        
        self.player = AVPlayer(playerItem: AVPlayerItem(asset: composition))
        self.player.isMuted = true
        
        self.playerLayer = AVPlayerLayer(player: self.player)
        
        super.init(frame: .zero)
        
        self.layer.addSublayer(self.playerLayer)
        
        self.didPlayToEndTimeObserver = NotificationCenter.default.addObserver(forName: NSNotification.Name.AVPlayerItemDidPlayToEndTime, object: self.player.currentItem, queue: nil, using: { [weak self] notification in
            guard let self else {
                return
            }
            var start: Double = 0.0
            if let trimRange = self.trimRange {
                start = trimRange.lowerBound
            }
            self.player.pause()
            self.seek(to: start, andPlay: true)
            
            self.onLoop()
        })
        
        self.player.play()
    }
    
    required public init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    deinit {
        if let didPlayToEndTimeObserver = self.didPlayToEndTimeObserver {
            NotificationCenter.default.removeObserver(didPlayToEndTimeObserver)
        }
    }
    
    func updateTrimRange(start: Double, end: Double, updatedEnd: Bool, apply: Bool) {
        if !apply {
            self.player.pause()
        } else {
            self.trimRange = start..<end
        }
        let seekTo: Double
        if updatedEnd && !apply {
            seekTo = end
        } else {
            seekTo = start
        }
        self.seek(to: seekTo, andPlay: apply)
    }
    
    func play() {
        self.player.play()
    }
    
    func pause() {
        self.player.pause()
    }
    
    private var targetTimePosition: (CMTime, Bool)?
    private var updatingTimePosition = false
    func seek(to seconds: Double, andPlay play: Bool) {
        let position = CMTime(seconds: seconds, preferredTimescale: CMTimeScale(1000.0))
        self.targetTimePosition = (position, play)
        
        if !self.updatingTimePosition {
            self.updateVideoTimePosition()
        }
    }
    
    private func updateVideoTimePosition() {
        guard let (targetPosition, _) = self.targetTimePosition else {
            return
        }
        self.updatingTimePosition = true
        
        self.player.seek(to: targetPosition, toleranceBefore: .zero, toleranceAfter: .zero, completionHandler: { [weak self] _ in
            if let self {
                if let (currentTargetPosition, play) = self.targetTimePosition, currentTargetPosition == targetPosition {
                    self.updatingTimePosition = false
                    self.targetTimePosition = nil
                    
                    if play {
                        self.player.play()
                    }
                } else {
                    self.updateVideoTimePosition()
                }
            }
        })
    }
    
    override func layoutSubviews() {
        self.playerLayer.frame = self.bounds
    }
}