package com.correx.core.validation.artifact

import com.correx.core.artifacts.kind.JsonSchema
import com.correx.core.artifacts.kind.JsonSchemaProperty
import kotlinx.serialization.json.JsonArray
import kotlinx.serialization.json.JsonElement
import kotlinx.serialization.json.JsonObject
import kotlinx.serialization.json.JsonPrimitive
import kotlinx.serialization.json.booleanOrNull
import kotlinx.serialization.json.doubleOrNull
import kotlinx.serialization.json.longOrNull

/**
 * Minimal, deterministic validator of a JSON value against a [JsonSchema]
 * (the subset correx models: object type, typed properties, required keys,
 * additionalProperties, and array minItems/items). Returns human-readable error
 * strings; empty = valid.
 *
 * Used to validate config-declared artifact kinds against their declared schema,
 * so an LLM-emitted custom kind is checked against its shape rather than trusted.
 */
object JsonSchemaValidator {

    fun validate(schema: JsonSchema, value: JsonElement): List<String> =
        if (schema.type == "object") {
            validateObject(schema, value)
        } else {
            checkType(schema.type, value)?.let { listOf(it) } ?: emptyList()
        }

    private fun validateObject(schema: JsonSchema, value: JsonElement): List<String> {
        val obj = value as? JsonObject ?: return listOf("expected object, got ${kindOf(value)}")
        val errors = mutableListOf<String>()
        schema.required.filterNot { it in obj }.forEach { errors += "missing required property '$it'" }
        for ((key, element) in obj) {
            val property = schema.properties[key]
            if (property == null) {
                if (!schema.additionalProperties) errors += "unknown property '$key'"
            } else {
                errors += checkProperty(key, property, element)
            }
        }
        return errors
    }

    private fun checkProperty(key: String, property: JsonSchemaProperty, element: JsonElement): List<String> {
        val typeError = checkType(property.type, element)
        val array = element as? JsonArray
        return when {
            typeError != null -> listOf("property '$key': $typeError")
            property.type != "array" || array == null -> emptyList()
            else -> buildList {
                if (array.size < property.minItems) {
                    add("property '$key': expected at least ${property.minItems} item(s), got ${array.size}")
                }
                property.items?.let { itemSchema ->
                    array.forEachIndexed { i, el ->
                        checkType(itemSchema.type, el)?.let { add("property '$key'[$i]: $it") }
                    }
                }
            }
        }
    }

    private fun checkType(type: String, value: JsonElement): String? {
        val ok = when (type) {
            "string" -> value is JsonPrimitive && value.isString
            "integer" -> value is JsonPrimitive && !value.isString && value.longOrNull != null
            "number" -> value is JsonPrimitive && !value.isString && value.doubleOrNull != null
            "boolean" -> value is JsonPrimitive && !value.isString && value.booleanOrNull != null
            "object" -> value is JsonObject
            "array" -> value is JsonArray
            else -> true // unknown declared type: don't enforce
        }
        return if (ok) null else "expected $type, got ${kindOf(value)}"
    }

    private fun kindOf(value: JsonElement): String = when (value) {
        is JsonArray -> "array"
        is JsonObject -> "object"
        is JsonPrimitive -> if (value.isString) "string" else "primitive"
    }
}
