import CoreML
import Darwin
import Foundation
import SileroCoreML

private final class ChanoraSileroVadBox {
    let vad: SileroVADRunner

    init() throws {
        let configuration = MLModelConfiguration()
        vad = try SileroVADRunner(configuration: configuration)
    }
}

private let chanoraSileroErrorLock = NSLock()
private var chanoraSileroLastError = ""

private func setChanoraSileroLastError(_ message: String) {
    chanoraSileroErrorLock.lock()
    chanoraSileroLastError = message
    chanoraSileroErrorLock.unlock()
}

@_cdecl("chanora_silero_vad_create")
public func chanoraSileroVadCreate() -> UnsafeMutableRawPointer? {
    do {
        let box = try ChanoraSileroVadBox()
        return Unmanaged.passRetained(box).toOpaque()
    } catch {
        setChanoraSileroLastError(String(describing: error))
        return nil
    }
}

@_cdecl("chanora_silero_vad_destroy")
public func chanoraSileroVadDestroy(_ handle: UnsafeMutableRawPointer?) {
    guard let handle else { return }
    Unmanaged<ChanoraSileroVadBox>.fromOpaque(handle).release()
}

@_cdecl("chanora_silero_vad_reset")
public func chanoraSileroVadReset(_ handle: UnsafeMutableRawPointer?) -> Int32 {
    guard let handle else {
        setChanoraSileroLastError("SileroVAD handle is null")
        return -1
    }
    let box = Unmanaged<ChanoraSileroVadBox>.fromOpaque(handle).takeUnretainedValue()
    box.vad.reset()
    return 0
}

@_cdecl("chanora_silero_vad_process")
public func chanoraSileroVadProcess(
    _ handle: UnsafeMutableRawPointer?,
    _ samples: UnsafePointer<Float>?,
    _ sampleCount: Int,
    _ probabilityOut: UnsafeMutablePointer<Float>?
) -> Int32 {
    guard let handle else {
        setChanoraSileroLastError("SileroVAD handle is null")
        return -1
    }
    guard let samples else {
        setChanoraSileroLastError("SileroVAD samples pointer is null")
        return -2
    }
    guard let probabilityOut else {
        setChanoraSileroLastError("SileroVAD probability output pointer is null")
        return -3
    }
    guard sampleCount == SileroVADRunner.chunkSize else {
        setChanoraSileroLastError("SileroVAD expected \(SileroVADRunner.chunkSize) samples, got \(sampleCount)")
        return -4
    }

    let box = Unmanaged<ChanoraSileroVadBox>.fromOpaque(handle).takeUnretainedValue()
    do {
        let chunk = Array(UnsafeBufferPointer(start: samples, count: sampleCount))
        probabilityOut.pointee = try box.vad.process(chunk)
        return 0
    } catch {
        setChanoraSileroLastError(String(describing: error))
        return -5
    }
}

@_cdecl("chanora_silero_vad_last_error")
public func chanoraSileroVadLastError() -> UnsafeMutablePointer<CChar>? {
    chanoraSileroErrorLock.lock()
    let message = chanoraSileroLastError
    chanoraSileroErrorLock.unlock()
    return strdup(message)
}

@_cdecl("chanora_silero_vad_free_string")
public func chanoraSileroVadFreeString(_ string: UnsafeMutablePointer<CChar>?) {
    guard let string else { return }
    free(string)
}

@objc public final class ChanoraSileroSelfTest: NSObject {
    // Validates the same code path the Rust framework uses: dlsym(RTLD_DEFAULT) for all
    // six @_cdecl symbols, then exercises create -> reset -> process -> destroy. Catches
    // the dead-strip / linker-export class of bug that broke TestFlight; calling the Swift
    // functions directly would mask it because direct calls bypass the dynamic symbol table.
    @objc public static func run() {
        let started = DispatchTime.now()

        // Static linker references: keep the Swift compiler / linker from
        // dead-stripping the @_cdecl symbols under Whole-Module-Optimization
        // + LTO in Archive builds. dlsym(RTLD_DEFAULT) below does NOT count
        // as a static reference for the dead-stripper — these `_ = ` lines
        // do. Without them, TestFlight builds shipped without the symbols
        // even though Debug builds (no LTO) worked.
        //
        // The `withoutActuallyEscaping` dance prevents the optimizer from
        // proving the references are unused: assigning the function value
        // to a `@convention(c)` typealias forces address-taken semantics.
        _ = unsafeBitCast(
            chanoraSileroVadCreate as @convention(c) () -> UnsafeMutableRawPointer?,
            to: UnsafeRawPointer.self,
        )
        _ = unsafeBitCast(
            chanoraSileroVadDestroy as @convention(c) (UnsafeMutableRawPointer?) -> Void,
            to: UnsafeRawPointer.self,
        )
        _ = unsafeBitCast(
            chanoraSileroVadReset as @convention(c) (UnsafeMutableRawPointer?) -> Int32,
            to: UnsafeRawPointer.self,
        )
        _ = unsafeBitCast(
            chanoraSileroVadProcess
                as @convention(c) (
                    UnsafeMutableRawPointer?, UnsafePointer<Float>?, Int,
                    UnsafeMutablePointer<Float>?
                ) -> Int32,
            to: UnsafeRawPointer.self,
        )
        _ = unsafeBitCast(
            chanoraSileroVadLastError as @convention(c) () -> UnsafeMutablePointer<CChar>?,
            to: UnsafeRawPointer.self,
        )
        _ = unsafeBitCast(
            chanoraSileroVadFreeString as @convention(c) (UnsafeMutablePointer<CChar>?) -> Void,
            to: UnsafeRawPointer.self,
        )

        typealias CreateFn = @convention(c) () -> UnsafeMutableRawPointer?
        typealias DestroyFn = @convention(c) (UnsafeMutableRawPointer?) -> Void
        typealias ResetFn = @convention(c) (UnsafeMutableRawPointer?) -> Int32
        typealias ProcessFn = @convention(c) (
            UnsafeMutableRawPointer?, UnsafePointer<Float>?, Int, UnsafeMutablePointer<Float>?
        ) -> Int32
        typealias LastErrorFn = @convention(c) () -> UnsafeMutablePointer<CChar>?
        typealias FreeStringFn = @convention(c) (UnsafeMutablePointer<CChar>?) -> Void

        func resolve<T>(_ name: String, as type: T.Type) -> T? {
            guard let raw = dlsym(UnsafeMutableRawPointer(bitPattern: -2), name) else {
                return nil
            }
            return unsafeBitCast(raw, to: type)
        }

        let names = [
            "chanora_silero_vad_create",
            "chanora_silero_vad_destroy",
            "chanora_silero_vad_reset",
            "chanora_silero_vad_process",
            "chanora_silero_vad_last_error",
            "chanora_silero_vad_free_string",
        ]
        let missing = names.filter { dlsym(UnsafeMutableRawPointer(bitPattern: -2), $0) == nil }
        if !missing.isEmpty {
            NSLog("chanora_flutter: SileroCoreML self-test FAILED dlsym missing=\(missing.joined(separator: ","))")
            return
        }

        guard
            let create = resolve("chanora_silero_vad_create", as: CreateFn.self),
            let destroy = resolve("chanora_silero_vad_destroy", as: DestroyFn.self),
            let reset = resolve("chanora_silero_vad_reset", as: ResetFn.self),
            let process = resolve("chanora_silero_vad_process", as: ProcessFn.self),
            let lastError = resolve("chanora_silero_vad_last_error", as: LastErrorFn.self),
            let freeString = resolve("chanora_silero_vad_free_string", as: FreeStringFn.self)
        else {
            NSLog("chanora_flutter: SileroCoreML self-test FAILED unsafeBitCast resolution")
            return
        }

        func readError() -> String {
            guard let ptr = lastError() else { return "unknown" }
            let msg = String(cString: ptr)
            freeString(ptr)
            return msg
        }

        guard let handle = create() else {
            let elapsedMs = elapsedMs(since: started)
            NSLog("chanora_flutter: SileroCoreML self-test FAILED at create err=\(readError()) elapsed_ms=\(elapsedMs)")
            return
        }

        let resetRc = reset(handle)
        if resetRc != 0 {
            destroy(handle)
            let elapsedMs = elapsedMs(since: started)
            NSLog("chanora_flutter: SileroCoreML self-test FAILED at reset rc=\(resetRc) err=\(readError()) elapsed_ms=\(elapsedMs)")
            return
        }

        let chunkSize = SileroVADRunner.chunkSize
        var probability: Float = 0
        let samples = [Float](repeating: 0, count: chunkSize)
        let processRc = samples.withUnsafeBufferPointer { buf -> Int32 in
            process(handle, buf.baseAddress, chunkSize, &probability)
        }

        destroy(handle)

        let elapsedMs = elapsedMs(since: started)
        if processRc == 0 {
            NSLog("chanora_flutter: SileroCoreML self-test OK probability=\(probability) elapsed_ms=\(elapsedMs)")
        } else {
            NSLog("chanora_flutter: SileroCoreML self-test FAILED at process rc=\(processRc) err=\(readError()) elapsed_ms=\(elapsedMs)")
        }
    }

    private static func elapsedMs(since start: DispatchTime) -> String {
        let ns = DispatchTime.now().uptimeNanoseconds &- start.uptimeNanoseconds
        return String(format: "%.1f", Double(ns) / 1_000_000.0)
    }
}
