import Foundation

/// 药盒识别结果(结构化,与 UserProfile.currentMedications 的字符串条目解耦)。
struct ParsedMedication: Sendable, Identifiable {
    let id = UUID()
    var name: String
    var strength: String   // 规格,如 "80mg×7粒"
    var usage: String      // 用法,如 "口服,一次1片,一日2次"

    /// 写入 UserProfile.currentMedications 的单行文本,
    /// 与手动录入习惯一致(placeholder "如:缬沙坦 80mg qd")。
    var entryText: String {
        var s = name.trimmingCharacters(in: .whitespaces)
        let st = strength.trimmingCharacters(in: .whitespaces)
        let u = usage.trimmingCharacters(in: .whitespaces)
        if !st.isEmpty { s += " \(st)" }
        if !u.isEmpty { s += " · \(u)" }
        return s
    }
}

/// 「拍药盒入档」服务:OCR 文本 → LLM(MNN/SME2 主链路)结构化抽药品。
/// 与 CaptureService.recognizeIndicators 同构:UI 不直接碰 AIRuntime(§3.1),
/// 失败抛 CaptureError,UI 回退手动录入(§3.2)。
/// actor 原因同 CaptureService:方法要等 AIRuntime(actor),自身无可变状态。
actor MedicationScanService {
    static let shared = MedicationScanService()
    private init() {}

    /// 药盒/说明书/处方的 OCR 文本 → [ParsedMedication]。
    /// 调用方(MainActor)先做 OCR 再传文本进来,避免 UIImage 跨 actor。
    func recognizeMedications(fromOCRText text: String) async throws -> [ParsedMedication] {
        do {
            try await AIRuntime.shared.prepare()   // 载 LLM(与 VL 互斥卸载由 AIRuntime 闸门处理)
        } catch {
            throw CaptureError.modelNotReady
        }

        let prompt = MedicationPrompts.medicationsFromText(text)
        var collected = ""
        do {
            // 药盒一般 1-2 种药,512 token 足够;与其他推理由 AIRuntime 闸门串行。
            let stream = await AIRuntime.shared.generate(prompt: prompt, maxTokens: 512)
            for try await chunk in stream {
                collected += chunk.text
            }
        } catch {
            throw CaptureError.inferenceFailed("\(error)")
        }

        let cleaned = CaptureService.stripThink(collected)
        do {
            return try Self.parseMedicationsJSON(cleaned)
        } catch let CaptureError.parseFailed(msg) {
            let preview = cleaned.isEmpty ? "(strip 后为空)" : String(cleaned.prefix(60))
            throw CaptureError.parseFailed("\(msg)〔前缀:\(preview)〕")
        } catch {
            throw CaptureError.parseFailed("\(error)")
        }
    }

    // MARK: - JSON parse(static 纯函数 → 方便单测)

    /// 兼容 `{"medications":[...]}` 与裸数组 `[...]`。
    /// 解析不到任何药品返回空数组(不抛),UI 据此走「手动补充」分支;JSON 不合法才抛。
    static func parseMedicationsJSON(_ raw: String) throws -> [ParsedMedication] {
        let jsonString = CaptureService.repairJSON(CaptureService.extractBalancedJSON(from: raw))
        guard let data = jsonString.data(using: .utf8) else {
            throw CaptureError.parseFailed("非 UTF-8 输出")
        }
        let obj: Any
        do {
            obj = try JSONSerialization.jsonObject(with: data, options: [.fragmentsAllowed])
        } catch {
            throw CaptureError.parseFailed("JSON 不合法:\(error.localizedDescription)")
        }
        let rawList: [[String: Any]]
        if let dict = obj as? [String: Any] {
            rawList = arrayValue(dict, keys: ["medications", "meds", "drugs", "药品", "用药", "items"])
        } else if let arr = obj as? [[String: Any]] {
            rawList = arr
        } else {
            throw CaptureError.parseFailed("根节点既不是对象也不是数组")
        }
        var seen = Set<String>()
        return rawList.compactMap { parseMedication($0) }.filter { seen.insert($0.name).inserted }
    }

    private static func parseMedication(_ d: [String: Any]) -> ParsedMedication? {
        guard let name = stringValue(d, keys: ["name", "drug", "medication", "药名", "药品", "名称"])?
                .trimmingCharacters(in: .whitespaces),
              !name.isEmpty else { return nil }
        let strength = stringValue(d, keys: ["strength", "spec", "specification", "规格", "剂量"]) ?? ""
        let usage = stringValue(d, keys: ["usage", "dosage", "用法", "用量", "用法用量"]) ?? ""
        return ParsedMedication(name: name,
                                strength: strength.trimmingCharacters(in: .whitespaces),
                                usage: usage.trimmingCharacters(in: .whitespaces))
    }

    private static func stringValue(_ d: [String: Any], keys: [String]) -> String? {
        for key in keys {
            if let s = d[key] as? String { return s }
            if let n = d[key] as? NSNumber { return n.stringValue }
        }
        return nil
    }

    private static func arrayValue(_ d: [String: Any], keys: [String]) -> [[String: Any]] {
        for key in keys {
            if let arr = d[key] as? [[String: Any]] { return arr }
        }
        return []
    }
}
