package com.correx.core.artifacts.kind

import kotlin.test.AfterTest
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertTrue

class KindContractTableTest {

    @AfterTest
    fun reset() {
        KindContractTable.projectOverrides = emptyMap()
    }

    @Test
    fun `infers common TS and Kotlin kinds from path`() {
        assertEquals("react_hook", KindInference.kindFor("frontend/src/hooks/useWebSocket.ts"))
        assertEquals("react_view", KindInference.kindFor("frontend/src/views/Sessions.tsx"))
        assertEquals("react_component", KindInference.kindFor("frontend/src/components/Navbar.tsx"))
        assertEquals("ts_types", KindInference.kindFor("frontend/src/types/index.ts"))
        assertEquals("package_json", KindInference.kindFor("frontend/package.json"))
        assertEquals("tsconfig", KindInference.kindFor("frontend/tsconfig.json"))
        assertEquals("html_entry", KindInference.kindFor("frontend/index.html"))
        assertEquals("kotlin_event", KindInference.kindFor("core/events/FooEvent.kt"))
        assertEquals("kotlin_reducer", KindInference.kindFor("core/foo/BarReducer.kt"))
    }

    @Test
    fun `unknown kind falls back to universal floor, never empty`() {
        val assertions = KindContractTable.assertionsFor(KindInference.kindFor("weird.xyz") ?: "", "weird.xyz")
        assertEquals(listOf("file_exists", "file_nonempty", "parses"), assertions.map { it.id })
    }

    @Test
    fun `app entry files are react_entry, not react_view`() {
        // Regression: main.tsx / index.tsx mount the tree and export nothing. Classifying them as
        // react_view (which requires exports_default_component) made a correct file unwinnable.
        assertEquals("react_entry", KindInference.kindFor("frontend/src/main.tsx"))
        assertEquals("react_entry", KindInference.kindFor("frontend/src/index.tsx"))
        // A non-entry .tsx entry point name in .ts form is unaffected.
        assertEquals("ts_types", KindInference.kindFor("frontend/src/types/index.ts"))
    }

    @Test
    fun `react_entry contract does not require a default component export`() {
        val ids = KindContractTable.assertionsFor("react_entry", "frontend/src/main.tsx").map { it.id }
        assertTrue("file_exists" in ids && "file_nonempty" in ids)
        assertTrue("exports_default_component" !in ids, "entry file must not require a default export")
    }

    @Test
    fun `react_hook contract includes floor plus exports_hook`() {
        val ids = KindContractTable.assertionsFor("react_hook", "useWebSocket.ts").map { it.id }
        assertTrue("file_exists" in ids && "file_nonempty" in ids)
        assertTrue("exports_hook" in ids)
    }

    @Test
    fun `kotlin_event promotes the serialization-registration trap into an assertion`() {
        val ids = KindContractTable.assertionsFor("kotlin_event", "FooEvent.kt").map { it.id }
        assertTrue("registered_in_serialization" in ids)
    }

    @Test
    fun `dotfiles are exempt from file_nonempty but still must exist`() {
        // Regression: a .gitkeep placeholder is empty by definition, so a file_nonempty floor made
        // its contract unsatisfiable and live-locked scaffold_frontend rewriting src/*/.gitkeep every
        // retry. Any dotfile (basename begins with '.') may legitimately be empty.
        val gitkeep = KindContractTable.assertionsFor(
            KindInference.kindFor("frontend/src/hooks/.gitkeep") ?: "",
            "frontend/src/hooks/.gitkeep",
        ).map { it.id }
        assertTrue("file_exists" in gitkeep, "dotfile must still be required to exist")
        assertTrue("file_nonempty" !in gitkeep, "an empty .gitkeep must not fail file_nonempty")

        // A non-dotfile with the same unknown kind keeps the full floor.
        val regular = KindContractTable.assertionsFor("", "frontend/src/hooks/placeholder.ts").map { it.id }
        assertTrue("file_nonempty" in regular, "non-dotfiles keep the nonempty floor")
    }

    @Test
    fun `dotfile exemption also strips a would-be file_nonempty project override`() {
        KindContractTable.projectOverrides = mapOf(
            "" to listOf(ContractAssertion("file_nonempty", "", AssertionEvaluator.FS)),
        )
        val ids = KindContractTable.assertionsFor("", "config/.nojekyll").map { it.id }
        assertTrue("file_nonempty" !in ids, "dotfile exemption is final, even against overrides")
    }

    @Test
    fun `project override is additive and cannot remove checked-in assertions`() {
        KindContractTable.projectOverrides = mapOf(
            "css_entry" to listOf(
                ContractAssertion("contains", "", AssertionEvaluator.TEXT, args = mapOf("pattern" to "@tailwind")),
                // duplicate of a checked-in id — must not double up
                ContractAssertion("file_exists", "", AssertionEvaluator.FS),
            ),
        )
        val ids = KindContractTable.assertionsFor("css_entry", "src/index.css").map { it.id }
        assertEquals(1, ids.count { it == "file_exists" }, "checked-in file_exists must not be duplicated")
        assertTrue("contains" in ids, "override assertion must be appended")
        assertTrue("file_nonempty" in ids, "checked-in assertions remain")
    }

    @Test
    fun `toolchain follows checked in source and manifest kinds`() {
        assertEquals(KindContractTable.Toolchain.NODE, KindContractTable.toolchainFor("react_entry"))
        assertEquals(KindContractTable.Toolchain.NODE, KindContractTable.toolchainFor("package_json"))
        assertEquals(KindContractTable.Toolchain.JVM, KindContractTable.toolchainFor("kotlin_service"))
        assertEquals(KindContractTable.Toolchain.JVM, KindContractTable.toolchainFor("gradle_module"))
        assertEquals(null, KindContractTable.toolchainFor("docs"))
    }
}
