import APIClient
import Foundation
import SwiftUI

public struct HookVideoLyricsOverlay: View {
    let lyrics: LyricsDataV2
    let snippetStart: Double
    let snippetEnd: Double
    let isPaused: Bool
    let displayMode: LyricDisplay
    let containerSize: CGSize

    @ObservedObject var overlayPlaybackViewModel: OverlayPlaybackViewModel

    private let allWords: [WordToken]
    private let isCentered: Bool
    private let duration: Double
    private let lyricsHeight: CGFloat
    private let maxLineWidth: CGFloat

    @State private var currentWord: WordToken?
    @State private var currentWordText: String = ""
    @State private var currentLine: LyricsLine?
    @State private var currentLineWords: [WordToken] = []

    @State private var previousWordText: String = ""
    @State private var previousLineWords: [WordToken] = []

    public init(
        lyrics: LyricsDataV2,
        snippetStart: Double,
        snippetEnd: Double,
        isPaused: Bool,
        lyricDisplay displayMode: LyricDisplay,
        containerSize: CGSize,
        overlayPlaybackViewModel: OverlayPlaybackViewModel
    ) {
        self.lyrics = lyrics
        self.snippetStart = snippetStart
        self.snippetEnd = snippetEnd
        self.isPaused = isPaused
        self.displayMode = displayMode
        self.containerSize = containerSize
        self.overlayPlaybackViewModel = overlayPlaybackViewModel

        self.allWords = lyrics.lines.flatMap { $0.words }
        self.isCentered = displayMode == .centeredLine || displayMode == .centeredWord
        self.duration = max(0, snippetEnd - snippetStart)

        let isLineLyric = displayMode == .bottomLeftLine || displayMode == .centeredLine
        let fontSizeScalar = isLineLyric ? 16 : 28
        let fontSize = UIFontMetrics.default.scaledValue(for: CGFloat(fontSizeScalar))
        let lineHeight = fontSize * 1.5
        let maxLines = 2
        self.lyricsHeight = lineHeight * CGFloat(maxLines)
        self.maxLineWidth = containerSize.width * 0.67
    }

    public var body: some View {
        let elapsedTimeInSnippet = duration > 0
            ? overlayPlaybackViewModel.playbackTime.truncatingRemainder(dividingBy: duration)
            : 0.0
        let absoluteTimeInSong = snippetStart + elapsedTimeInSnippet

        content(absoluteTimeInSong: absoluteTimeInSong)
            .frame(width: maxLineWidth, height: lyricsHeight, alignment: isCentered ? .center : .leading)
            .onAppear {
                updateLyrics(at: absoluteTimeInSong)
            }
            .onChange(of: absoluteTimeInSong) { _, time in
                updateLyrics(at: time)
            }
    }

    @ViewBuilder
    private func content(absoluteTimeInSong: Double) -> some View {
        switch displayMode {
        case .bottomLeftLine, .centeredLine:
            let words = currentLineWords.isEmpty ? previousLineWords : currentLineWords
            let line = createKaraokeText(
                words: words,
                time: absoluteTimeInSong,
                baseOpacity: Constants.Opacity.activeOrPast
            )
            Text(line)
                .typographyV1(.lyricsHookUserGeneratedContent)
                .multilineTextAlignment(isCentered ? .center : .leading)
                .opacity(Constants.Opacity.activeOrPast)

        case .bottomLeftWord, .centeredWord:
            let word = currentWordText.isEmpty ? previousWordText : currentWordText
            Text(word)
                .typographyV1(.lyricsHookVideoCover)
                .foregroundColor(.white)
                .multilineTextAlignment(isCentered ? .center : .leading)
                .shadow(
                    color: Constants.Shadow.color,
                    radius: Constants.Shadow.radius,
                    x: Constants.Shadow.xOffset,
                    y: Constants.Shadow.yOffset
                )
                .opacity(Constants.Opacity.activeOrPast)
        }
    }
}

private extension HookVideoLyricsOverlay {
    func updateLyrics(at time: TimeInterval) {
        guard !lyrics.lines.isEmpty else {
            clearAll()
            return
        }

        switch displayMode {
        case .bottomLeftWord, .centeredWord:
            updateWord(at: time)
        case .bottomLeftLine, .centeredLine:
            updateLine(at: time)
        }
    }

    func updateWord(at time: TimeInterval) {
        let word = allWords.first { $0.startTime <= time && time < $0.endTime }

        if let newWord = word, newWord != currentWord {
            if !currentWordText.isEmpty {
                // Store previous word for smooth transition
                previousWordText = currentWordText
            }
            currentWord = newWord
            currentWordText = newWord.text.trimmingCharacters(in: .whitespaces)

            withAnimation(.easeInOut(duration: Constants.Timing.transitionAnimationDuration)) {
                previousWordText = ""
            }
        } else if word == nil {
            let isPastEndOfLyrics = allWords.last.map { time > $0.endTime + Constants.Timing.clearBufferAfterEnd } ?? false
            let isBeforeStartOfLyrics = allWords.first.map { time < $0.startTime - Constants.Timing.clearBufferBeforeStart } ?? false
            let shouldClearLyrics = isPastEndOfLyrics || isBeforeStartOfLyrics

            if shouldClearLyrics {
                currentWord = nil
                currentWordText = ""
                previousWordText = ""
            }
        }
    }

    func updateLine(at time: TimeInterval) {
        let line = lyrics.lines.first { $0.startTime <= time && time < $0.endTime }

        if let newLine = line, newLine != currentLine {
            if !currentLineWords.isEmpty {
                // Store previous line words for smooth transition
                previousLineWords = currentLineWords
            }
            currentLine = newLine
            currentLineWords = newLine.words

            withAnimation(.easeInOut(duration: Constants.Timing.transitionAnimationDuration)) {
                previousLineWords = []
            }
        } else if line == nil {
            let isPastEndOfLyrics = lyrics.lines.last.map { time > $0.endTime + Constants.Timing.clearBufferAfterEnd } ?? false
            let isBeforeStartOfLyrics = lyrics.lines.first.map { time < $0.startTime - Constants.Timing.clearBufferBeforeStart } ?? false
            let shouldClearLyrics = isPastEndOfLyrics || isBeforeStartOfLyrics

            if shouldClearLyrics {
                currentLine = nil
                currentLineWords = []
                previousLineWords = []
            }
        }
    }

    // Create a new string that highlights each new active lyric word as the playback time progresses
    // until the entire lyric line is highlighted.
    func createKaraokeText(words: [WordToken], time: TimeInterval, baseOpacity: CGFloat) -> AttributedString {
        var result = AttributedString()

        for (_, word) in words.enumerated() {
            let wordOpacity: CGFloat
            if time >= word.endTime {
                wordOpacity = baseOpacity
            } else if time >= word.startTime {
                wordOpacity = baseOpacity
            } else {
                wordOpacity = baseOpacity * 0.5
            }

            var styledWord = AttributedString(word.text)
            styledWord.foregroundColor = .white.opacity(wordOpacity)
            result.append(styledWord)
        }
        return result
    }

    func clearAll() {
        currentWord = nil
        currentWordText = ""
        currentLine = nil
        currentLineWords = []
        previousWordText = ""
        previousLineWords = []
    }
}

private extension HookVideoLyricsOverlay {
    enum Constants {
        enum Opacity {
            static let activeOrPast: CGFloat = 1.0
        }

        enum Shadow {
            static let color: Color = .black.opacity(0.6)
            static let radius: CGFloat = 0
            static let xOffset: CGFloat = 1
            static let yOffset: CGFloat = 1
        }

        enum Timing {
            static let clearBufferAfterEnd: TimeInterval = 1.0
            static let clearBufferBeforeStart: TimeInterval = 0.05
            static let transitionAnimationDuration: TimeInterval = 0.05
        }
    }
}

private extension TypographyV1 {
    static let lyricsHookUserGeneratedContent: TypographyV1 = .init(
        name: "Lyrics Hook User Generated Content",
        size: 16,
        style: .body,
        weight: .ppNeueMontrealSemiBold
    )

    static let lyricsHookVideoCover: TypographyV1 = .init(
        name: "Lyrics Hook Video Cover",
        size: 28,
        style: .body,
        weight: .epilogueExtraBold
    )
}
