package com.correx.core.artifacts.kind

/**
 * The checked-in `kind → assertions` registry (design doc 2026-07-06-kind-assertion-table.md).
 *
 * Most contracts are *derived*: a stage's `produces[].kind` maps to a fixed assertion set here, and
 * the planner writes nothing. An unrecognized kind never yields an empty contract — it degrades to
 * the [UNIVERSAL_FLOOR] (`file_exists · file_nonempty · parses`), so e.g. an empty `Sessions.tsx`
 * fails regardless of table coverage.
 *
 * A project may supply an **additive** override ([projectOverrides]) — add assertions to a kind,
 * never remove the checked-in ones (monotonic, one level up). Merge keeps the checked-in set and
 * appends project entries not already present by id.
 *
 * This is deliberately data, not behaviour: it is environment-independent and must replay
 * identically. Evaluation lives behind the `ContractAssertionEvaluator` seam in the kernel/
 * infrastructure; this table only says *what* to check, never *how*.
 */
object KindContractTable {

    /** Toolchain selected by the build gate for a produced source or manifest kind. */
    enum class Toolchain(val profileKey: String) {
        NODE("node"),
        JVM("jvm"),
    }

    /** A single assertion template — a kind's requirement before it is bound to a concrete path. */
    private data class Template(
        val id: String,
        val evaluator: AssertionEvaluator,
        val args: Map<String, String> = emptyMap(),
    )

    private val FLOOR = listOf(
        Template("file_exists", AssertionEvaluator.FS),
        Template("file_nonempty", AssertionEvaluator.FS),
        Template("parses", AssertionEvaluator.COMPILER),
    )

    // TS / React ---------------------------------------------------------------------------------
    private val TS_REACT = mapOf(
        "react_hook" to FLOOR + listOf(
            Template("imports_resolve", AssertionEvaluator.COMPILER),
            Template("exports_hook", AssertionEvaluator.AST),
        ),
        "react_component" to FLOOR + listOf(
            Template("imports_resolve", AssertionEvaluator.COMPILER),
            Template("exports_default_component", AssertionEvaluator.AST),
        ),
        "react_view" to FLOOR + listOf(
            Template("imports_resolve", AssertionEvaluator.COMPILER),
            Template("exports_default_component", AssertionEvaluator.AST),
        ),
        // App entry point (main.tsx / index.tsx): mounts the tree, exports nothing. The honest bar
        // is parse + imports resolve; a default-export assertion here false-fails a correct file.
        "react_entry" to FLOOR + listOf(
            Template("imports_resolve", AssertionEvaluator.COMPILER),
        ),
        "ts_module" to FLOOR + listOf(
            Template("imports_resolve", AssertionEvaluator.COMPILER),
        ),
        // pure type module: may legitimately export only types, which erase at runtime. `parses`
        // (in the floor) is the honest bar; an export assertion here would false-fail.
        "ts_types" to FLOOR,
        "api_client" to FLOOR + listOf(
            Template("imports_resolve", AssertionEvaluator.COMPILER),
            Template("exports_symbol", AssertionEvaluator.COMPILER),
        ),
        "html_entry" to listOf(
            Template("file_exists", AssertionEvaluator.FS),
            Template("file_nonempty", AssertionEvaluator.FS),
            Template("contains", AssertionEvaluator.TEXT, mapOf("pattern" to "id=\"root\"")),
            Template("contains", AssertionEvaluator.TEXT, mapOf("pattern" to "<script")),
        ),
        "package_json" to listOf(
            Template("file_exists", AssertionEvaluator.FS),
            Template("valid_json", AssertionEvaluator.TEXT),
            Template("json_has_key", AssertionEvaluator.TEXT, mapOf("key" to "name")),
            Template("script_defined", AssertionEvaluator.TEXT, mapOf("name" to "build")),
        ),
        "tsconfig" to listOf(
            Template("file_exists", AssertionEvaluator.FS),
            Template("valid_json", AssertionEvaluator.TEXT),
        ),
        "vite_config" to FLOOR,
        "css_entry" to listOf(
            Template("file_exists", AssertionEvaluator.FS),
            Template("file_nonempty", AssertionEvaluator.FS),
        ),
        "router_config" to FLOOR + listOf(
            Template("imports_resolve", AssertionEvaluator.COMPILER),
        ),
    )

    // Kotlin (correx itself) ---------------------------------------------------------------------
    private val KOTLIN = mapOf(
        // Promotes the CLAUDE.md SILENT FAILURE TRAP into a deterministic gate: every EventPayload
        // subclass must be registered in Serialization.kt or deserialization fails silently at runtime.
        "kotlin_event" to FLOOR + listOf(
            Template("implements_interface", AssertionEvaluator.COMPILER, mapOf("interface" to "EventPayload")),
            Template("registered_in_serialization", AssertionEvaluator.AST),
        ),
        "kotlin_reducer" to FLOOR + listOf(
            Template("implements_interface", AssertionEvaluator.COMPILER, mapOf("interface" to "Reducer")),
        ),
        "kotlin_projector" to FLOOR + listOf(
            Template("implements_interface", AssertionEvaluator.COMPILER, mapOf("interface" to "Projection")),
        ),
        "kotlin_repository" to FLOOR,
        "kotlin_service" to FLOOR,
        "kotlin_test" to FLOOR + listOf(
            Template("has_test", AssertionEvaluator.TEXT),
        ),
        "gradle_module" to listOf(
            Template("file_exists", AssertionEvaluator.FS),
            Template("valid_json", AssertionEvaluator.TEXT),
        ),
    )

    // Generic / cross-stack ----------------------------------------------------------------------
    private val GENERIC = mapOf(
        "config" to FLOOR,
        "json_config" to listOf(
            Template("file_exists", AssertionEvaluator.FS),
            Template("valid_json", AssertionEvaluator.TEXT),
        ),
        "migration" to listOf(
            Template("file_exists", AssertionEvaluator.FS),
            Template("migration_present", AssertionEvaluator.TEXT),
        ),
        "toolchain" to listOf(
            Template("toolchain_installed", AssertionEvaluator.FS),
        ),
        "docs" to listOf(
            Template("file_exists", AssertionEvaluator.FS),
            Template("file_nonempty", AssertionEvaluator.FS),
        ),
    )

    private val CHECKED_IN: Map<String, List<Template>> = TS_REACT + KOTLIN + GENERIC

    private val TOOLCHAINS = TS_REACT.keys.associateWith { Toolchain.NODE } +
        KOTLIN.keys.associateWith { Toolchain.JVM }

    /** Project-supplied additive overrides, keyed by kind id. Set at wiring time; empty by default. */
    @Volatile
    var projectOverrides: Map<String, List<ContractAssertion>> = emptyMap()

    /**
     * The derived-layer contract for a produced file of [kindId] at [path]. Falls back to the
     * universal floor for an unknown kind, then appends any project override assertions not already
     * present by id (additive/monotonic).
     */
    fun assertionsFor(kindId: String, path: String): List<ContractAssertion> {
        val base = (CHECKED_IN[kindId] ?: FLOOR).map {
            ContractAssertion(it.id, path, it.evaluator, AssertionLayer.DERIVED, it.args)
        }
        val overrides = projectOverrides[kindId].orEmpty()
            .filter { ov -> base.none { it.id == ov.id } }
            .map { it.copy(target = path) }
        // A dotfile (basename begins with '.') may legitimately be empty: .gitkeep/.keep directory
        // placeholders, .nojekyll markers, an empty .gitignore. Asserting file_nonempty on one makes
        // the contract unsatisfiable by construction — a real .gitkeep IS empty — which live-locks the
        // producing stage rewriting the same placeholder every retry (observed: scaffold_frontend
        // looping on four src/*/.gitkeep files). file_exists still applies, so the file must exist.
        return (base + overrides).filterNot { it.id == "file_nonempty" && isDotfile(path) }
    }

    /** The build toolchain implied by a checked-in kind, or null for stack-neutral kinds. */
    fun toolchainFor(kindId: String): Toolchain? = TOOLCHAINS[kindId]

    private fun isDotfile(path: String): Boolean = path.substringAfterLast('/').startsWith(".")
}
