import CoreGraphics
import Foundation

struct CardTextHeuristicExtractor {
    private let numberRegex = try? NSRegularExpression(pattern: #"\b\d{1,3}\s*/\s*\d{1,3}\b"#)
    private let rarityKeywords: [(needle: String, rarity: CardRarity)] = [
        ("special art rare", .specialArtRare),
        ("illustration rare", .illustrationRare),
        ("hyper rare", .hyperRare),
        ("secret rare", .secretRare),
        ("ultra rare", .ultraRare),
        ("holo rare", .holoRare),
        ("uncommon", .uncommon),
        ("common", .common),
        ("rare", .rare),
    ]

    func extract(payload: OCRTextPayload, source: RecognitionSource, notes: [String] = []) -> CardRecognitionDraft {
        let cleanedLines = payload.lines
            .map { line in
                RecognizedTextLine(
                    text: normalize(line.text),
                    confidence: line.confidence,
                    normalizedBounds: line.normalizedBounds
                )
            }
            .filter { !$0.text.isEmpty }

        let rawText = cleanedLines.map(\.text).joined(separator: "\n")
        let cardNumber = extractCardNumber(from: rawText)
        let rarity = extractRarity(from: rawText)
        let cardName = extractCardName(from: cleanedLines)
        let setIdentifier = extractSetIdentifier(from: cleanedLines, cardNumber: cardNumber, rarity: rarity)

        let foundCount = [cardName, cardNumber, setIdentifier, rarity == CardRarity.unknown.rawValue ? "" : rarity]
            .filter { !$0.isEmpty }
            .count

        let confidence: ConfidenceLevel
        if foundCount >= 3 && payload.averageConfidence >= 0.68 {
            confidence = .high
        } else if foundCount >= 2 && payload.averageConfidence >= 0.45 {
            confidence = .medium
        } else {
            confidence = .low
        }

        var draftNotes = notes
        if confidence != .high {
            draftNotes.append(confidence.helperText)
        }
        if foundCount == 0 {
            draftNotes.append("No structured match was found — manual entry is prefilled with OCR fragments.")
        }

        return CardRecognitionDraft(
            cardName: cardName,
            cardNumber: cardNumber,
            setIdentifier: setIdentifier,
            rarity: rarity,
            source: source,
            confidence: confidence,
            notes: Array(Set(draftNotes)),
            rawText: rawText
        )
    }

    private func extractCardName(from lines: [RecognizedTextLine]) -> String {
        let upperLines = lines
            .filter { $0.normalizedBounds.midY > 0.45 }
            .sorted { lhs, rhs in
                if lhs.normalizedBounds.midY == rhs.normalizedBounds.midY {
                    return lhs.confidence > rhs.confidence
                }
                return lhs.normalizedBounds.midY > rhs.normalizedBounds.midY
            }

        let candidate = upperLines.first { line in
            let text = line.text
            return text.rangeOfCharacter(from: .decimalDigits) == nil &&
                !text.localizedCaseInsensitiveContains("hp") &&
                !text.localizedCaseInsensitiveContains("trainer") &&
                text.count > 2
        }

        return candidate?.text ?? lines.first(where: { !$0.text.isEmpty })?.text ?? ""
    }

    private func extractCardNumber(from rawText: String) -> String {
        guard let numberRegex else { return "" }
        let range = NSRange(rawText.startIndex..., in: rawText)
        guard let match = numberRegex.firstMatch(in: rawText, options: [], range: range),
              let matchRange = Range(match.range, in: rawText) else {
            return ""
        }

        return String(rawText[matchRange]).replacingOccurrences(of: " ", with: "")
    }

    private func extractSetIdentifier(from lines: [RecognizedTextLine], cardNumber: String, rarity: String) -> String {
        guard !lines.isEmpty else { return "" }

        if let lineContainingNumber = lines.first(where: { !cardNumber.isEmpty && $0.text.contains(cardNumber) }) {
            let stripped = lineContainingNumber.text
                .replacingOccurrences(of: cardNumber, with: "")
                .replacingOccurrences(of: "•", with: " ")
                .replacingOccurrences(of: "·", with: " ")
                .trimmingCharacters(in: .whitespacesAndNewlines)
            if stripped.count > 2 {
                return stripped
            }
        }

        let bottomCandidates = lines
            .filter { $0.normalizedBounds.midY < 0.35 }
            .map(\.text)
            .filter {
                !$0.localizedCaseInsensitiveContains(rarity) &&
                $0.rangeOfCharacter(from: .letters) != nil &&
                !$0.localizedCaseInsensitiveContains("hp")
            }

        return bottomCandidates.first ?? ""
    }

    private func extractRarity(from rawText: String) -> String {
        let lowered = rawText.lowercased()

        if lowered.contains("★") || lowered.contains("holo") {
            return CardRarity.holoRare.rawValue
        }

        if lowered.contains("◆") {
            return CardRarity.uncommon.rawValue
        }

        if lowered.contains("●") {
            return CardRarity.common.rawValue
        }

        for keyword in rarityKeywords {
            if lowered.contains(keyword.needle) {
                return keyword.rarity.rawValue
            }
        }

        return CardRarity.unknown.rawValue
    }

    private func normalize(_ value: String) -> String {
        value
            .replacingOccurrences(of: "  ", with: " ")
            .trimmingCharacters(in: .whitespacesAndNewlines)
    }
}
