package com.nate.posedetection

import androidx.compose.runtime.Composable
import com.performancecoachlab.posedetection.recording.AnalysisObject

/**
 * Persists experiment-mode capture buffers from the sample app to a platform-
 * specific location so the Mac orchestrator can `adb pull` them back for the
 * model-comparison report. Android writes JSON to the app's external files
 * directory under `experiment_logs/`. iOS is a no-op stub (out of scope).
 */
interface ExperimentLogger {
    /** Human-readable device label, e.g. "Google Pixel 7". Embedded in the log. */
    val deviceLabel: String

    /**
     * Writes [json] to a platform file named [filename] inside the experiment
     * logs directory and returns its absolute path, or null if writing failed
     * or is unsupported on this platform.
     */
    fun write(filename: String, json: String): String?
}

@Composable
expect fun rememberExperimentLogger(): ExperimentLogger

internal data class ExperimentEvent(
    val wallMs: Long,
    val objects: List<AnalysisObject>,
)

/**
 * Hand-builds the experiment log JSON document. Format matches the schema in
 * the closed-loop training plan: device + model identification, start/stop
 * wall-clock bracket, and an `events` array. The `skeleton` field per event
 * is reserved but always emitted as null since this MVP compares object
 * detection models only.
 */
internal fun buildExperimentLogJson(
    deviceModel: String,
    modelName: String,
    runId: Long,
    startedWallMs: Long,
    stoppedWallMs: Long,
    events: List<ExperimentEvent>,
): String {
    val sb = StringBuilder()
    sb.append('{')
    sb.append("\"device_model\":\"").append(jsonEscape(deviceModel)).append("\",")
    sb.append("\"model_name\":\"").append(jsonEscape(modelName)).append("\",")
    sb.append("\"run_id\":").append(runId).append(',')
    sb.append("\"started_wall_ms\":").append(startedWallMs).append(',')
    sb.append("\"stopped_wall_ms\":").append(stoppedWallMs).append(',')
    sb.append("\"events\":[")
    events.forEachIndexed { idx, e ->
        if (idx > 0) sb.append(',')
        sb.append('{')
        sb.append("\"wall_ms\":").append(e.wallMs).append(',')
        sb.append("\"skeleton\":null,")
        sb.append("\"objects\":[")
        e.objects.forEachIndexed { i, obj ->
            if (i > 0) sb.append(',')
            val top = obj.labels.maxByOrNull { it.confidence }
            sb.append('{')
            sb.append("\"label\":\"").append(jsonEscape(top?.text ?: "")).append("\",")
            sb.append("\"confidence\":").append(jsonNumber(top?.confidence ?: 0f)).append(',')
            sb.append("\"bbox\":[")
                .append(jsonNumber(obj.boundingBox.left)).append(',')
                .append(jsonNumber(obj.boundingBox.top)).append(',')
                .append(jsonNumber(obj.boundingBox.right)).append(',')
                .append(jsonNumber(obj.boundingBox.bottom))
                .append("],")
            sb.append("\"frame_size\":[")
                .append(obj.frameSize.width).append(',')
                .append(obj.frameSize.height)
                .append(']')
            sb.append('}')
        }
        sb.append(']')
        sb.append('}')
    }
    sb.append(']')
    sb.append('}')
    return sb.toString()
}

private fun jsonEscape(s: String): String {
    val sb = StringBuilder(s.length + 2)
    for (c in s) {
        when (c) {
            '"' -> sb.append("\\\"")
            '\\' -> sb.append("\\\\")
            '\n' -> sb.append("\\n")
            '\r' -> sb.append("\\r")
            '\t' -> sb.append("\\t")
            else -> if (c.code < 0x20) {
                sb.append("\\u").append(c.code.toString(16).padStart(4, '0'))
            } else sb.append(c)
        }
    }
    return sb.toString()
}

private fun jsonNumber(f: Float): String =
    if (f.isFinite()) f.toString() else "0"
