//
//  TapTempoViewController.swift
//  DashSmash
//
//  Plays the chosen track and asks the player to tap along to the beat.
//  Once a stable BPM is detected, the song restarts from the start at the
//  same instant the level begins, so the level is rhythm-locked to the
//  song.
//
//  Works with both PlayableTrack sources: Apple Music catalog (MusicKit) and
//  the device's local music library (MediaPlayer).
//

import UIKit
import MusicKit
import MediaPlayer

final class TapTempoViewController: UIViewController {

    private let track: PlayableTrack
    private let previewStartFraction: Double

    private let titleLabel = UILabel()
    private let artistLabel = UILabel()
    private let instructionLabel = UILabel()
    private let bpmLabel = UILabel()
    private let tapAreaLabel = UILabel()
    private let startButton = UIButton(type: .system)
    private let cancelButton = UIButton(type: .system)

    private var tapTimes: [CFTimeInterval] = []
    private let minTapsForStart = 4
    private let resetInterval: CFTimeInterval = 2.0

    private var startedGame = false

    /// `previewStartFraction` seeks the preview into a more rhythmically
    /// stable section of the song (typically 0.3) so the player has an
    /// easier time tapping the beat than they would over a sparse intro.
    init(track: PlayableTrack, previewStartFraction: Double = 0) {
        self.track = track
        self.previewStartFraction = max(0, min(0.9, previewStartFraction))
        super.init(nibName: nil, bundle: nil)
    }

    required init?(coder: NSCoder) { fatalError() }

    override var prefersStatusBarHidden: Bool { true }

    override func viewDidLoad() {
        super.viewDidLoad()
        view.backgroundColor = .black
        setupUI()
        Task { await startPreview() }
    }

    override func viewWillDisappear(_ animated: Bool) {
        super.viewWillDisappear(animated)
        // If we're navigating away without starting, stop the music.
        if !startedGame {
            stopPreview()
        }
    }

    private func setupUI() {
        titleLabel.text = track.title
        titleLabel.font = UIFont(name: "AvenirNext-Heavy", size: 22) ?? .boldSystemFont(ofSize: 22)
        titleLabel.textColor = .white
        titleLabel.numberOfLines = 2
        titleLabel.textAlignment = .center

        artistLabel.text = track.artistName
        artistLabel.font = UIFont(name: "AvenirNext-Medium", size: 16) ?? .systemFont(ofSize: 16)
        artistLabel.textColor = UIColor.white.withAlphaComponent(0.7)
        artistLabel.textAlignment = .center

        instructionLabel.text = "Tap anywhere to the beat"
        instructionLabel.font = UIFont(name: "AvenirNext-Medium", size: 17) ?? .systemFont(ofSize: 17)
        instructionLabel.textColor = UIColor.white.withAlphaComponent(0.85)
        instructionLabel.textAlignment = .center

        bpmLabel.text = "— BPM"
        bpmLabel.font = UIFont(name: "AvenirNext-Heavy", size: 64) ?? .boldSystemFont(ofSize: 64)
        bpmLabel.textColor = .white
        bpmLabel.textAlignment = .center

        tapAreaLabel.text = "TAP"
        tapAreaLabel.font = UIFont(name: "AvenirNext-Heavy", size: 56) ?? .boldSystemFont(ofSize: 56)
        tapAreaLabel.textColor = .white
        tapAreaLabel.textAlignment = .center
        tapAreaLabel.alpha = 0.25
        tapAreaLabel.isUserInteractionEnabled = false

        startButton.setTitle("Start Level", for: .normal)
        startButton.titleLabel?.font = UIFont(name: "AvenirNext-Heavy", size: 22) ?? .boldSystemFont(ofSize: 22)
        startButton.setTitleColor(.black, for: .normal)
        startButton.backgroundColor = .white
        startButton.layer.cornerRadius = 12
        startButton.isEnabled = false
        startButton.alpha = 0.4
        startButton.addTarget(self, action: #selector(startTapped), for: .touchUpInside)

        cancelButton.setTitle("← Cancel", for: .normal)
        cancelButton.titleLabel?.font = UIFont(name: "AvenirNext-Medium", size: 17) ?? .systemFont(ofSize: 17)
        cancelButton.setTitleColor(.white, for: .normal)
        cancelButton.addTarget(self, action: #selector(cancelTapped), for: .touchUpInside)

        let stack = UIStackView(arrangedSubviews: [titleLabel, artistLabel])
        stack.axis = .vertical
        stack.spacing = 4
        stack.translatesAutoresizingMaskIntoConstraints = false

        view.addSubview(cancelButton)
        view.addSubview(stack)
        view.addSubview(instructionLabel)
        view.addSubview(bpmLabel)
        view.addSubview(tapAreaLabel)
        view.addSubview(startButton)

        cancelButton.translatesAutoresizingMaskIntoConstraints = false
        instructionLabel.translatesAutoresizingMaskIntoConstraints = false
        bpmLabel.translatesAutoresizingMaskIntoConstraints = false
        tapAreaLabel.translatesAutoresizingMaskIntoConstraints = false
        startButton.translatesAutoresizingMaskIntoConstraints = false

        NSLayoutConstraint.activate([
            cancelButton.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 16),
            cancelButton.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 12),

            stack.topAnchor.constraint(equalTo: cancelButton.bottomAnchor, constant: 16),
            stack.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 24),
            stack.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -24),

            instructionLabel.topAnchor.constraint(equalTo: stack.bottomAnchor, constant: 28),
            instructionLabel.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 24),
            instructionLabel.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -24),

            bpmLabel.centerYAnchor.constraint(equalTo: view.centerYAnchor, constant: -40),
            bpmLabel.centerXAnchor.constraint(equalTo: view.centerXAnchor),

            tapAreaLabel.topAnchor.constraint(equalTo: bpmLabel.bottomAnchor, constant: 12),
            tapAreaLabel.centerXAnchor.constraint(equalTo: view.centerXAnchor),

            startButton.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 32),
            startButton.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -32),
            startButton.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor, constant: -32),
            startButton.heightAnchor.constraint(equalToConstant: 56)
        ])

        // Whole-screen tap target for tempo input.
        let tap = UITapGestureRecognizer(target: self, action: #selector(tapped))
        tap.cancelsTouchesInView = false
        view.addGestureRecognizer(tap)
    }

    // MARK: - Source-aware playback

    private func startPreview() async {
        do {
            switch track {
            case .appleMusicCatalog(let song):
                try await AppleMusicCoordinator.shared.play(song: song)
            case .library(let item):
                let player = MPMusicPlayerController.applicationMusicPlayer
                player.setQueue(with: MPMediaItemCollection(items: [item]))
                try await player.prepareToPlay()
                if previewStartFraction > 0 {
                    let target = item.playbackDuration * previewStartFraction
                    player.currentPlaybackTime = target
                }
                player.play()
            case .midi:
                break
            }
        } catch {
            instructionLabel.text = "Couldn't start playback: \(error.localizedDescription)"
        }
    }

    private func stopPreview() {
        switch track {
        case .appleMusicCatalog:
            AppleMusicCoordinator.shared.stop()
        case .library:
            MPMusicPlayerController.applicationMusicPlayer.stop()
        case .midi:
            break
        }
    }

    private func restartFromBeginning() async {
        switch track {
        case .appleMusicCatalog:
            try? await AppleMusicCoordinator.shared.restartFromBeginning()
        case .library:
            let player = MPMusicPlayerController.applicationMusicPlayer
            player.skipToBeginning()
            player.play()
        case .midi:
            break
        }
    }

    // MARK: - Tap handling

    @objc private func tapped(_ gr: UITapGestureRecognizer) {
        let p = gr.location(in: view)
        if startButton.frame.contains(p) || cancelButton.frame.contains(p) { return }

        let now = CACurrentMediaTime()
        if let last = tapTimes.last, now - last > resetInterval {
            tapTimes.removeAll()
        }
        tapTimes.append(now)
        if tapTimes.count > 10 {
            tapTimes.removeFirst(tapTimes.count - 10)
        }

        flashTapArea()
        updateBPM()
    }

    private func flashTapArea() {
        tapAreaLabel.alpha = 0.7
        UIView.animate(withDuration: 0.25) { self.tapAreaLabel.alpha = 0.25 }
    }

    private var detectedBPM: Double = 0
    private func updateBPM() {
        guard tapTimes.count >= 2 else {
            bpmLabel.text = "— BPM"
            return
        }
        var intervals: [Double] = []
        for i in 1..<tapTimes.count {
            intervals.append(tapTimes[i] - tapTimes[i - 1])
        }
        intervals.sort()
        let median = intervals[intervals.count / 2]
        let bpm = 60.0 / median
        let clamped = max(40, min(220, bpm))
        detectedBPM = clamped
        bpmLabel.text = String(format: "%.0f BPM", clamped)

        if tapTimes.count >= minTapsForStart {
            startButton.isEnabled = true
            UIView.animate(withDuration: 0.2) { self.startButton.alpha = 1.0 }
        }
    }

    // MARK: - Actions

    @objc private func cancelTapped() {
        stopPreview()
        navigationController?.popViewController(animated: true)
    }

    @objc private func startTapped() {
        guard detectedBPM > 0 else { return }
        startedGame = true
        startButton.isEnabled = false

        // Persist the tapped BPM so the next time this song is picked we
        // skip tap-tempo entirely.
        BPMDetector.shared.remember(bpm: detectedBPM, for: track.sourceID)

        let level = LevelGenerator.generate(
            songName: track.title,
            bandName: track.artistName,
            fixedBPM: detectedBPM,
            fixedDurationSeconds: track.duration,
            trackSourceID: track.sourceID
        )

        Task { [weak self] in
            guard let self else { return }
            // Restart the song from beat 0 so the level is aligned to its start.
            await self.restartFromBeginning()
            let game = GameViewController(level: level, track: self.track)
            self.navigationController?.pushViewController(game, animated: true)
        }
    }
}
