//
//  CardRenderer.swift
//  CasinoKit
//
//  Renders card images from the bundled PDF at any resolution.
//  Based on CardKit by Bret Taylor (2013), Swift conversion 2024.
//

#if canImport(UIKit)
import UIKit
#elseif canImport(AppKit)
import AppKit
#endif
import CoreGraphics
import PDFKit

/// Renders card images from the bundled PDF at any resolution.
/// Cards are vector-based and can be rendered at any size without quality loss.
public final class CardRenderer: @unchecked Sendable {
    /// Shared singleton instance.
    public static let shared = CardRenderer()
    
    private var pdfDocument: CGPDFDocument?
    private var imageCache: [CacheKey: CGImage] = [:]
    private let lock = NSLock()
    
    private struct CacheKey: Hashable {
        let index: Int
        let width: Int
        let height: Int
        let scale: CGFloat
    }
    
    private init() {
        loadPDF()
    }
    
    private func loadPDF() {
        guard let url = Bundle.module.url(forResource: "Cards", withExtension: "pdf") else {
            print("CasinoKit: Could not find Cards.pdf in bundle")
            return
        }
        pdfDocument = CGPDFDocument(url as CFURL)
    }
    
    /// The aspect ratio (height / width) of a card in the PDF.
    public static let aspectRatio: CGFloat = 1.3966
    
    /// Calculates the height for a given width, maintaining the card aspect ratio.
    public static func height(forWidth width: CGFloat) -> CGFloat {
        (width * aspectRatio).rounded(.up)
    }
    
    /// Renders a card image at the specified size.
    /// - Parameters:
    ///   - card: The card to render.
    ///   - size: The size to render at.
    ///   - scale: The display scale (defaults to 2.0 for retina).
    /// - Returns: A CGImage of the card, or nil if rendering fails.
    public func renderCard(_ card: Card, size: CGSize, scale: CGFloat = 2.0) -> CGImage? {
        renderPage(at: card.pdfIndex, size: size, scale: scale)
    }
    
    /// Renders the card back image at the specified size.
    /// Note: The app uses a custom themed card back, but this is available
    /// if you want to use the PDF card back instead.
    /// - Parameters:
    ///   - size: The size to render at.
    ///   - scale: The display scale.
    /// - Returns: A CGImage of the card back, or nil if rendering fails.
    public func renderCardBack(size: CGSize, scale: CGFloat = 2.0) -> CGImage? {
        // Card back is at index 52 (page 53 in PDF)
        renderPage(at: 52, size: size, scale: scale)
    }
    
    private func renderPage(at index: Int, size: CGSize, scale: CGFloat) -> CGImage? {
        guard let document = pdfDocument else { return nil }
        
        let cacheKey = CacheKey(
            index: index,
            width: Int(size.width),
            height: Int(size.height),
            scale: scale
        )
        
        lock.lock()
        if let cached = imageCache[cacheKey] {
            lock.unlock()
            return cached
        }
        lock.unlock()
        
        // PDF pages are 1-indexed
        guard let page = document.page(at: index + 1) else { return nil }
        
        let scaledSize = CGSize(
            width: size.width * scale,
            height: size.height * scale
        )
        
        let colorSpace = CGColorSpaceCreateDeviceRGB()
        guard let context = CGContext(
            data: nil,
            width: Int(scaledSize.width),
            height: Int(scaledSize.height),
            bitsPerComponent: 8,
            bytesPerRow: 0,
            space: colorSpace,
            bitmapInfo: CGImageAlphaInfo.premultipliedLast.rawValue
        ) else { return nil }
        
        // Clear to transparent
        context.clear(CGRect(origin: .zero, size: scaledSize))
        
        // Get the PDF page bounds
        let pdfBox = page.getBoxRect(.bleedBox)
        
        // Calculate scale to fit
        let xScale = scaledSize.width / pdfBox.width
        let yScale = scaledSize.height / pdfBox.height
        let fitScale = min(xScale, yScale)
        
        // Apply transformations
        context.scaleBy(x: fitScale, y: fitScale)
        context.drawPDFPage(page)
        
        guard let image = context.makeImage() else { return nil }
        
        lock.lock()
        imageCache[cacheKey] = image
        lock.unlock()
        
        return image
    }
    
    /// Clears the image cache to free memory.
    public func clearCache() {
        lock.lock()
        imageCache.removeAll()
        lock.unlock()
    }
}

