import Foundation
import RideFormCore

/// Repository lifetime is independent of screen navigation or share presentation.
/// The coordinator calls this facade serially; SessionWriter owns off-main disk IO.
final class SessionRepository {
    let directory: URL
    private(set) var writer: SessionWriter?
    private var recordingManifest: SessionManifest?
    private var pendingFinalManifest: SessionManifest?
    init(directory: URL? = nil) {
        self.directory = directory ?? FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0].appendingPathComponent("Sessions", isDirectory: true)
    }
    @discardableResult
    func begin(manifest: SessionManifest, calibration: Calibration? = nil) throws -> URL {
        guard writer == nil else { throw SaveError.failed("A recording is already active or awaiting save retry") }
        let url = directory.appendingPathComponent(manifest.id.uuidString + ".rideform", isDirectory: true)
        writer = try SessionWriter(directory: url, manifest: manifest, calibration: calibration)
        recordingManifest = manifest; pendingFinalManifest = nil
        return url
    }
    func enqueue(_ record: CapturedRecord) {
        switch record {
        case .motion(let value): writer?.enqueue(value)
        case .accelerometer(let value): writer?.enqueue(value)
        case .gyroscope(let value): writer?.enqueue(value)
        }
    }
    func enqueue(_ marker: EventMarker) { writer?.enqueue(marker) }
    func enqueue(_ diagnostic: DiagnosticRecord) { writer?.enqueue(diagnostic) }
    func checkpoint(completion: @escaping (Result<URL, Error>) -> Void) {
        guard let writer else { completion(.failure(SaveError.failed("No active recording"))); return }
        writer.checkpoint(completion: completion)
    }
    func finalize(summary: CaptureSummary, interrupted: Bool = false, completion: @escaping (Result<URL, Error>) -> Void) {
        guard var manifest = recordingManifest else { completion(.failure(SaveError.failed("No active recording"))); return }
        manifest.boundaries.ended = summary.ended
        manifest.boundaries.status = interrupted ? "interrupted" : "completed"
        manifest.boundaries.stopReason = interrupted ? "App interrupted" : "User stopped"
        if interrupted { manifest.boundaries.interruptions.append(summary.ended) }
        manifest.clockMapping = summary.clockMapping; manifest.rotationConvention = summary.rotationConvention
        for index in manifest.streams.indices {
            let kind = manifest.streams[index].kind
            manifest.streams[index].capability = summary.capabilities[kind] ?? .unavailable(reason: "not captured")
            manifest.streams[index].acceptedCount = summary.acceptedCounts[kind, default: 0]
        }
        pendingFinalManifest = manifest
        retrySave(completion: completion)
    }
    func retrySave(completion: @escaping (Result<URL, Error>) -> Void) {
        guard let writer, let manifest = pendingFinalManifest else { completion(.failure(SaveError.failed("No stopped recording to save"))); return }
        writer.finalize(manifest: manifest) { [weak self] result in
            DispatchQueue.main.async {
                if case .success = result { self?.writer = nil; self?.recordingManifest = nil; self?.pendingFinalManifest = nil }
                completion(result)
            }
        }
    }
    /// Relaunch recovery preserves the original bytes and creates attributed evidence.
    func recoverIncomplete(_ source: URL, completion: @escaping (Result<URL, Error>) -> Void) {
        do {
            let recovered = try SessionIO.load(source)
            guard recovered.manifest.boundaries.status == "incomplete" else { throw SaveError.immutableMaster }
            var manifest = recovered.manifest
            manifest.id = UUID()
            manifest.boundaries.status = "interrupted"
            manifest.boundaries.stopReason = "Recovered after interruption; missing tail count unknown"
            let identity = try DerivedSidecar<[Double]>.identity(recovered)
            manifest.diagnostics.append(DiagnosticRecord(code: "recovered_source", message: "sourceUUID=\(recovered.manifest.id); inputSHA256=\(identity); recovered rows motion=\(recovered.motion.samples.count), accelerometer=\(recovered.accelerometer.samples.count), gyroscope=\(recovered.gyroscope.samples.count), events=\(recovered.events.count); missing tail accepted count unknown"))
            for index in manifest.streams.indices { manifest.streams[index].acceptedCount = 0; manifest.streams[index].writtenCount = 0; manifest.streams[index].lossCount = 0 }
            _ = try begin(manifest: manifest, calibration: recovered.calibration)
            recovered.motion.samples.forEach { writer?.enqueue($0) }
            recovered.accelerometer.samples.forEach { writer?.enqueue($0) }
            recovered.gyroscope.samples.forEach { writer?.enqueue($0) }
            recovered.events.forEach { writer?.enqueue($0) }
            pendingFinalManifest = manifest
            retrySave(completion: completion)
        } catch { completion(.failure(error)) }
    }
    func list() throws -> [URL] { try SessionIO.list(in: directory) }
    func load(_ url: URL) throws -> MotionSession { try SessionIO.load(url) }
}
