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) {
                // A schema with no declared properties describes "any object" — flagging every
                // key there would reject all real content. Unknown-property enforcement only
                // makes sense against a declared shape.
                if (!schema.additionalProperties && schema.properties.isNotEmpty()) {
                    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)
        return when {
            typeError != null -> listOf("property '$key': $typeError")
            property.type == "array" -> checkArray(key, property, element as JsonArray)
            property.type == "object" && property.properties.isNotEmpty() ->
                validateProperty(property, element as JsonObject).map { "property '$key': $it" }
            else -> emptyList()
        }
    }

    private fun checkArray(key: String, property: JsonSchemaProperty, array: JsonArray): List<String> = 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 ->
                val itemTypeError = checkType(itemSchema.type, el)
                when {
                    itemTypeError != null -> add("property '$key'[$i]: $itemTypeError")
                    itemSchema.type == "object" && itemSchema.properties.isNotEmpty() ->
                        validateProperty(itemSchema, el as JsonObject).forEach { add("property '$key'[$i]: $it") }
                }
            }
        }
    }

    // Validate a nested object against a [JsonSchemaProperty] carrying its own shape — mirrors
    // [validateObject] but keyed off the property's fields (they diverge only in defaults).
    private fun validateProperty(property: JsonSchemaProperty, value: JsonObject): List<String> {
        val errors = mutableListOf<String>()
        property.required.filterNot { it in value }.forEach { errors += "missing required property '$it'" }
        for ((key, element) in value) {
            val nested = property.properties[key]
            if (nested == null) {
                if (!property.additionalProperties && property.properties.isNotEmpty()) {
                    errors += "unknown property '$key'"
                }
            } else {
                errors += checkProperty(key, nested, element)
            }
        }
        return errors
    }

    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"
    }
}
