//
//  MIDISearchService.swift
//  DashSmash
//
//  Searches BitMidi's public pages and downloads the selected Standard MIDI
//  file. BitMidi does not require an API key; this client intentionally keeps
//  parsing conservative so failed markup changes surface as empty results.
//

import Foundation

final class MIDISearchService {
    static let shared = MIDISearchService()

    private let baseURL = URL(string: "https://bitmidi.com")!
    private let session: URLSession

    private init(session: URLSession = .shared) {
        self.session = session
    }

    func search(query: String) async throws -> [MIDISearchResult] {
        let trimmed = query.trimmingCharacters(in: .whitespacesAndNewlines)
        guard !trimmed.isEmpty else { return [] }

        var components = URLComponents(url: baseURL.appendingPathComponent("search"), resolvingAgainstBaseURL: false)!
        components.queryItems = [URLQueryItem(name: "q", value: trimmed)]
        guard let url = components.url else { return [] }

        let (data, _) = try await session.data(from: url)
        let html = String(decoding: data, as: UTF8.self)
        return parseSearchResults(html: html)
    }

    func loadSong(from result: MIDISearchResult) async throws -> MIDISong {
        if let cached = MIDIDownloadCache.shared.song(for: result.pageURL) {
            return cached
        }
        let downloadURL = try await resolveDownloadURL(pageURL: result.pageURL)
        let (data, _) = try await session.data(from: downloadURL)
        let parsed = try MIDIFileParser.parse(data)
        let song = MIDISong(
            title: result.title,
            artistName: result.artistName,
            sourcePageURL: result.pageURL,
            downloadURL: downloadURL,
            data: data,
            parsedFile: parsed
        )
        MIDIDownloadCache.shared.store(song)
        return song
    }

    func loadSong(pageURL: URL, title: String? = nil, artistName: String? = nil) async throws -> MIDISong {
        let fallback = resultTitle(from: pageURL.lastPathComponent)
        let result = MIDISearchResult(
            title: title ?? fallback,
            artistName: artistName ?? "MIDI",
            pageURL: pageURL
        )
        return try await loadSong(from: result)
    }

    private func parseSearchResults(html: String) -> [MIDISearchResult] {
        // BitMidi result anchors look like:
        //   <a ... href="/some-song-mid" title="some-song.mid"><div>…<h2>…</h2></div></a>
        // The anchor's child markup varies, so we match only on the attributes
        // and derive a display title from the slug.
        let pattern = #"href=\"(/[A-Za-z0-9][^\"]*-mid)\""#
        guard let regex = try? NSRegularExpression(pattern: pattern, options: [.caseInsensitive]) else { return [] }
        let ns = html as NSString
        let matches = regex.matches(in: html, range: NSRange(location: 0, length: ns.length))

        var seen = Set<URL>()
        var results: [MIDISearchResult] = []
        for match in matches {
            guard match.numberOfRanges >= 2 else { continue }
            let path = ns.substring(with: match.range(at: 1))
            guard let pageURL = URL(string: path, relativeTo: baseURL)?.absoluteURL else { continue }
            guard seen.insert(pageURL).inserted else { continue }
            let title = resultTitle(from: pageURL.lastPathComponent)
            results.append(MIDISearchResult(title: title, artistName: "BitMidi", pageURL: pageURL))
            if results.count >= 30 { break }
        }
        return results
    }

    private func resolveDownloadURL(pageURL: URL) async throws -> URL {
        let (data, _) = try await session.data(from: pageURL)
        let html = String(decoding: data, as: UTF8.self)
        let patterns = [
            #"href=\"([^\"]+\.mid)\""#,
            #"href=\"([^\"]+\.midi)\""#
        ]
        let ns = html as NSString
        for pattern in patterns {
            guard let regex = try? NSRegularExpression(pattern: pattern, options: [.caseInsensitive]) else { continue }
            if let match = regex.firstMatch(in: html, range: NSRange(location: 0, length: ns.length)), match.numberOfRanges >= 2 {
                let raw = ns.substring(with: match.range(at: 1))
                if let url = URL(string: raw, relativeTo: baseURL)?.absoluteURL {
                    return url
                }
            }
        }
        throw URLError(.fileDoesNotExist)
    }

    private func resultTitle(from slug: String) -> String {
        slug
            .replacingOccurrences(of: "-mid", with: "")
            .replacingOccurrences(of: "-", with: " ")
            .split(separator: " ")
            .map { $0.capitalized }
            .joined(separator: " ")
    }
}
