import Foundation

/// Parses LRC (synced lyrics) format into timestamped lines.
/// LRC format: `[mm:ss.xx] Lyric line text`
struct LyricsParser {

    /// A single timestamped lyric line.
    struct LyricLine: Identifiable, Equatable {
        let id = UUID()
        let timestamp: TimeInterval
        let text: String

        /// Format timestamp as mm:ss.
        var formattedTime: String {
            let minutes = Int(timestamp) / 60
            let seconds = Int(timestamp) % 60
            return String(format: "%d:%02d", minutes, seconds)
        }
    }

    /// Parse synced LRC lyrics into an array of timestamped lines, sorted by time.
    static func parseSynced(_ lrc: String) -> [LyricLine] {
        var lines: [LyricLine] = []

        for rawLine in lrc.components(separatedBy: .newlines) {
            let trimmed = rawLine.trimmingCharacters(in: .whitespaces)
            guard !trimmed.isEmpty else { continue }

            // Match [mm:ss.xx] or [mm:ss:xx] patterns
            // Can have multiple timestamps per line: [00:12.34][00:45.67] text
            let pattern = #"\[(\d{1,3}):(\d{2})[\.:,](\d{2,3})\]"#
            guard let regex = try? NSRegularExpression(pattern: pattern) else { continue }

            let matches = regex.matches(in: trimmed, range: NSRange(trimmed.startIndex..., in: trimmed))
            guard !matches.isEmpty else { continue }

            // Extract the text after all timestamps
            let lastMatch = matches.last!
            let textStartIndex = trimmed.index(trimmed.startIndex, offsetBy: lastMatch.range.upperBound)
            let text = String(trimmed[textStartIndex...]).trimmingCharacters(in: .whitespaces)

            // Create a line for each timestamp
            for match in matches {
                guard let minRange = Range(match.range(at: 1), in: trimmed),
                      let secRange = Range(match.range(at: 2), in: trimmed),
                      let msRange = Range(match.range(at: 3), in: trimmed),
                      let minutes = Double(trimmed[minRange]),
                      let seconds = Double(trimmed[secRange]),
                      let rawMs = Double(trimmed[msRange]) else { continue }

                // Handle both 2-digit (centiseconds) and 3-digit (milliseconds) fractional parts
                let fractional: Double
                let msString = String(trimmed[msRange])
                if msString.count <= 2 {
                    fractional = rawMs / 100.0
                } else {
                    fractional = rawMs / 1000.0
                }

                let timestamp = minutes * 60.0 + seconds + fractional
                lines.append(LyricLine(timestamp: timestamp, text: text))
            }
        }

        return lines.sorted { $0.timestamp < $1.timestamp }
    }

    /// Parse plain (unsynced) lyrics into lines with no timestamps.
    static func parsePlain(_ text: String) -> [LyricLine] {
        text.components(separatedBy: .newlines)
            .enumerated()
            .map { index, line in
                LyricLine(timestamp: TimeInterval(index), text: line)
            }
    }

    /// Find the index of the current lyric line for a given playback time.
    static func currentLineIndex(in lines: [LyricLine], at time: TimeInterval) -> Int? {
        guard !lines.isEmpty else { return nil }

        // Binary search for the last line whose timestamp <= current time
        var low = 0
        var high = lines.count - 1
        var result = -1

        while low <= high {
            let mid = (low + high) / 2
            if lines[mid].timestamp <= time {
                result = mid
                low = mid + 1
            } else {
                high = mid - 1
            }
        }

        return result >= 0 ? result : nil
    }
}
