// RaTeXFontLoader.swift — Register KaTeX fonts with CoreText so they can be used by the renderer.
//
// Fonts are loaded automatically on first render (RaTeXView/RaTeXFormula call ensureLoaded()).
// Optional — call at app startup to load earlier:
//   RaTeXFontLoader.loadFromPackageBundle()        // Swift Package Manager (recommended)
//   RaTeXFontLoader.loadFromBundle()               // when fonts are in the app bundle
//   RaTeXFontLoader.loadFromDirectory(fontsURL)    // custom directory with .ttf files

import CoreText
import Foundation

public enum RaTeXFontLoader {

    private static let loadLock = NSLock()
    private static var _didEnsureLoad = false

    /// All KaTeX font filenames (without extension) that the renderer may request.
    static let fontFileNames: [String] = [
        "KaTeX_AMS-Regular",
        "KaTeX_Caligraphic-Bold",
        "KaTeX_Caligraphic-Regular",
        "KaTeX_Fraktur-Bold",
        "KaTeX_Fraktur-Regular",
        "KaTeX_Main-Bold",
        "KaTeX_Main-BoldItalic",
        "KaTeX_Main-Italic",
        "KaTeX_Main-Regular",
        "KaTeX_Math-BoldItalic",
        "KaTeX_Math-Italic",
        "KaTeX_SansSerif-Bold",
        "KaTeX_SansSerif-Italic",
        "KaTeX_SansSerif-Regular",
        "KaTeX_Script-Regular",
        "KaTeX_Size1-Regular",
        "KaTeX_Size2-Regular",
        "KaTeX_Size3-Regular",
        "KaTeX_Size4-Regular",
        "KaTeX_Typewriter-Regular",
    ]

    // MARK: - Public API

#if SWIFT_PACKAGE
    /// Load KaTeX fonts bundled with the Swift Package.
    /// Call this once at app startup when integrating via Swift Package Manager.
    ///
    /// ```swift
    /// // In your App or AppDelegate init:
    /// RaTeXFontLoader.loadFromPackageBundle()
    /// ```
    @discardableResult
    public static func loadFromPackageBundle() -> Int {
        loadFromBundle(Bundle.module)
    }
#endif

    /// Load KaTeX fonts from the main bundle.
    /// Add all .ttf files to your Xcode project target membership, then call this once on startup.
    @discardableResult
    public static func loadFromBundle() -> Int {
        loadFromBundle(Bundle.main)
    }

    /// Load KaTeX fonts from a specific bundle (useful for Swift Package resources).
    /// Looks for .ttf at bundle root and in a "Fonts" subdirectory (e.g. Copy Bundle Resources folder).
    @discardableResult
    public static func loadFromBundle(_ bundle: Bundle) -> Int {
        var loaded = 0
        for name in fontFileNames {
            let url = bundle.url(forResource: name, withExtension: "ttf")
                ?? bundle.url(forResource: name, withExtension: "ttf", subdirectory: "Fonts")
            if let url = url, register(url) {
                loaded += 1
            }
        }
        return loaded
    }

    /// Load KaTeX fonts from a directory on disk (development / side-loading).
    /// Pass the URL to a folder containing the KaTeX .ttf files.
    @discardableResult
    public static func loadFromDirectory(_ directory: URL) -> Int {
        var loaded = 0
        for name in fontFileNames {
            let url = directory.appendingPathComponent("\(name).ttf")
            if FileManager.default.fileExists(atPath: url.path) {
                if register(url) { loaded += 1 }
            }
        }
        return loaded
    }

    /// Ensure KaTeX fonts are loaded. If none are registered, loads from the package bundle
    /// (SPM) or main bundle. Thread-safe; subsequent calls are no-ops.
    @discardableResult
    public static func ensureLoaded() -> Int {
        guard !_didEnsureLoad else { return 0 }
        loadLock.lock()
        defer { loadLock.unlock() }
        guard !_didEnsureLoad else { return 0 }
        defer { _didEnsureLoad = true }
        if isFontRegistered("KaTeX_Main-Regular") { return 0 }
#if SWIFT_PACKAGE
        let n = loadFromPackageBundle()
        if n > 0 { return n }
#endif
        return loadFromBundle()
    }

    /// Pre-load KaTeX fonts on a background thread. Call this once at app startup
    /// (e.g. in `App.init` or `AppDelegate.application(_:didFinishLaunchingWithOptions:)`)
    /// so fonts are ready before the first formula is displayed.
    ///
    /// ```swift
    /// Task { await RaTeXFontLoader.preload() }
    /// ```
    @discardableResult
    public static func preload() async -> Int {
        await withCheckedContinuation { continuation in
            DispatchQueue.global(qos: .userInitiated).async {
                continuation.resume(returning: ensureLoaded())
            }
        }
    }

    /// Check whether a specific KaTeX font is already registered.
    public static func isFontRegistered(_ postScriptName: String) -> Bool {
#if os(macOS)
        // `CTFontManagerCopyRegisteredFontDescriptors` is not available in the macOS
        // Swift overlay.  We fall back to CTFontCreateWithName, which searches all
        // font scopes (system, user, process) — not just the process scope that
        // CTFontManagerRegisterFontsForURL uses.  In practice this is fine because
        // KaTeX font names are unique, but it means a system font with the same
        // PostScript name could produce a false positive.
        let font = CTFontCreateWithName(postScriptName as CFString, 12, nil)
        let actual = CTFontCopyPostScriptName(font) as String? ?? ""
        return actual == postScriptName
#else
        let array = CTFontManagerCopyRegisteredFontDescriptors(.process, false) as NSArray
        for item in array {
            let desc = item as! CTFontDescriptor
            if let name = CTFontDescriptorCopyAttribute(desc, kCTFontNameAttribute) as? String,
               name == postScriptName { return true }
        }
        return false
#endif
    }

    // MARK: - Private

    private static func register(_ url: URL) -> Bool {
        var error: Unmanaged<CFError>?
        let ok = CTFontManagerRegisterFontsForURL(url as CFURL, .process, &error)
        if !ok, let err = error?.takeRetainedValue() {
            // Font may already be registered — that's fine
            let desc = CFErrorCopyDescription(err) as String
            if !desc.contains("already") && !desc.contains("duplicate") {
                print("[RaTeX] font registration warning for \(url.lastPathComponent): \(desc)")
            }
        }
        return ok
    }
}
