import Foundation
import CryptoKit

public struct CropReport: Codable, Sendable {
    public var pairID: String
    public var role: String
    public var rider: String
    public var sourceHash: String?
    public var crop: PixelRect
    public var threeD: PoseResult?
    public var twoD: PoseResult?
    public var localization3D: Localization?
    public var localization2D: Localization?
    public var error: String?
}
public struct FeasibilityReport: Codable, Sendable {
    public var schemaVersion = 1
    public var session: String
    public var operatingSystem: String
    public var machineArchitecture: String
    public var generatedAt: String
    public var annotationProvenance: String
    public var manifestHash: String
    public var crops: [CropReport]
}
public enum FeasibilityRunner {
    public static func run(manifestURL: URL, outputDirectory: URL) throws -> FeasibilityReport {
        let manifest = try SessionManifest.load(url: manifestURL)
        guard !FileManager.default.fileExists(atPath: outputDirectory.path) else {
            throw PoseError.invalidInput("Output directory already exists; choose a new run directory to preserve evidence.")
        }
        try FileManager.default.createDirectory(at: outputDirectory,withIntermediateDirectories:true)
        var crops: [CropReport]=[]
        for pair in manifest.pairs {
            for rider in pair.riders {
                var row=CropReport(pairID:pair.id,role:rider.role,rider:rider.rider,sourceHash:nil,crop:rider.crop)
                do {
                    let image = try ImageInput.load(url: manifest.assetURL(for: pair,rider:rider,manifestURL:manifestURL))
                    row.sourceHash=image.sha256
                    let cropImage=try image.cropped(to:rider.crop)
                    let panelImage=try image.cropped(to:rider.panel)
                    row.threeD=VisionPoseEstimator.estimate3D(image:cropImage,crop:rider.crop)
                    row.twoD=VisionPoseEstimator.estimate2D(image:cropImage,crop:rider.crop)
                    row.localization3D=Localization.evaluate(predicted:Dictionary(uniqueKeysWithValues:row.threeD!.joints.map { ($0.name,$0.point) }),annotations:rider.annotations,torsoLength:rider.torsoLength)
                    row.localization2D=Localization.evaluate(predicted:Dictionary(uniqueKeysWithValues:row.twoD!.joints.filter { ($0.confidence2D ?? 0) >= 0.2 }.map { ($0.name,$0.point) }),annotations:rider.annotations,torsoLength:rider.torsoLength)
                    for pose in [row.threeD!,row.twoD!] {
                        try InspectionRenderer.write(image:panelImage,panel:rider.panel,crop:rider.crop,pose:pose,annotations:rider.annotations,
                                                     url:outputDirectory.appendingPathComponent("\(pair.id)-\(rider.role)-\(pose.kind).png"))
                    }
                } catch { row.error=error.localizedDescription }
                crops.append(row)
                print("\(pair.id) \(rider.role): 3D=\(row.threeD?.status ?? "unavailable") 2D=\(row.twoD?.status ?? "unavailable")")
            }
        }
        let manifestData=try Data(contentsOf:manifestURL)
        let report=FeasibilityReport(session:manifest.session,operatingSystem:ProcessInfo.processInfo.operatingSystemVersionString,
                                     machineArchitecture:architecture,generatedAt:ISO8601DateFormatter().string(from:Date()),
                                     annotationProvenance:manifest.annotationProvenance,manifestHash:hash(manifestData),crops:crops)
        let encoder=JSONEncoder(); encoder.outputFormatting=[.prettyPrinted,.sortedKeys]
        try encoder.encode(report).write(to:outputDirectory.appendingPathComponent("results.json"),options:.atomic)
        try html(report).write(to:outputDirectory.appendingPathComponent("index.html"),atomically:true,encoding:.utf8)
        return report
    }
    private static var architecture: String {
        #if arch(arm64)
        return "arm64"
        #elseif arch(x86_64)
        return "x86_64"
        #else
        return "unknown"
        #endif
    }
    private static func html(_ report: FeasibilityReport) -> String {
        let rows=report.crops.map { row -> String in
            let title="\(row.pairID) · \(row.role) · \(row.rider)"
            let images=["3D","2D"].map { kind in
                let pose = kind == "3D" ? row.threeD : row.twoD
                let legend = (pose?.joints ?? []).sorted { $0.name < $1.name }.map { joint in
                    "<tr><td>\(InspectionRenderer.jointID(joint.name).map(String.init) ?? "?")</td><td>\(escape(joint.name))</td><td>\(String(format: "%.1f, %.1f", joint.point.x, joint.point.y))</td></tr>"
                }.joined()
                return "<figure><img src=\"\(row.pairID)-\(row.role)-\(kind).png\" alt=\"\(escape(title)) \(kind) inspection\"><figcaption>\(kind) — unreviewed</figcaption><details><summary>Joint key and source pixel coordinates</summary><table><tr><th>ID</th><th>Joint</th><th>x, y</th></tr>\(legend)</table></details></figure>"
            }.joined()
            return "<section><h2>\(escape(title))</h2><p>3D: \(escape(row.threeD?.status ?? "unavailable")) · 2D: \(escape(row.twoD?.status ?? "unavailable"))</p><p>\(escape(row.error ?? row.threeD?.message ?? ""))</p><div>\(images)</div></section>"
        }.joined(separator:"\n")
        return """
        <!doctype html><html lang="en"><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>Pose feasibility inspection</title>
        <style>body{font:16px system-ui;background:#101519;color:#edf1f5;max-width:1000px;margin:32px auto;padding:16px}section{border-top:1px solid #64748b;padding:16px 0}section div{display:flex;flex-wrap:wrap;gap:24px}figure{margin:0}img{max-width:100%;image-rendering:auto}figcaption{padding:8px 0}p{max-width:75ch;color:#ced6df}</style>
        <h1>\(escape(report.session))</h1><p>Feasibility inspection only — no coaching or verified 3D anatomy.</p><p>Yellow: selected rider crop. Cyan: raw Vision joints (2D points below 0.2 confidence hidden). Numbers: fixed joint IDs shared across every image and both pose types. Each PNG includes its own legend beside the photo; expand the table for source coordinates. Left/right always means the rider’s anatomy. Pink circles: provisional source-only references and their uncertainty. Source pixels are preserved beneath annotations.</p><p>\(escape(report.annotationProvenance))</p>\(rows)</html>
        """
    }
    private static func escape(_ s:String)->String { s.replacingOccurrences(of:"&",with:"&amp;").replacingOccurrences(of:"<",with:"&lt;").replacingOccurrences(of:">",with:"&gt;").replacingOccurrences(of:"\"",with:"&quot;") }
}
private func hash(_ data:Data)->String { SHA256.hash(data:data).map { String(format:"%02x",$0) }.joined() }
