package com.correx.infrastructure.workflow

import com.correx.core.tools.contract.ToolCapability
import com.correx.core.transitions.graph.StageConfig
import com.correx.core.transitions.graph.WorkflowGraph

/**
 * A stage whose declared intent implies a tool capability it was not granted (Vikunja #30 part 1).
 * Advisory only — recorded so a future reflection rung (part 2, LLM "are you sure?") can consume it;
 * it never blocks the plan-compile gate and never grants a tool itself (invariants #3 LLM-proposes/
 * core-decides, #4 policy-absolute, #5 tiers-declared-not-inferred).
 */
data class CapabilityGap(
    val stageId: String,
    val impliedCapability: ToolCapability,
    val phrase: String,
    val grantedCapabilities: Set<ToolCapability>,
)

/**
 * Deterministic, pure-Kotlin detector over a compiled plan graph — the plan-compile-gate counterpart
 * to [PlanLinter]'s structural checks. Zero inference, zero I/O: a function of the graph and a fixed
 * verb map, so it is replay-safe by construction (invariant #8).
 *
 * The verb→capability map below is intentionally small and conservative: each entry is a single
 * word/phrase that, in an imperative stage brief ("write the file", "run the build"), reliably
 * signals a concrete tool action. Words that are ambiguous or too common in ordinary planning
 * prose (e.g. "make", "handle", "process", "check") are deliberately excluded — a false positive
 * here is just a discarded advisory finding, but a map that fires on every brief would drown the
 * signal the future reflection rung needs.
 */
object CapabilityGapDetector {

    private val impliedCapabilities: Map<ToolCapability, List<String>> = mapOf(
        ToolCapability.FILE_WRITE to listOf("write", "create", "generate", "scaffold", "save"),
        ToolCapability.FILE_READ to listOf("read", "inspect"),
        ToolCapability.SHELL_EXEC to listOf("run", "build", "execute", "compile"),
        ToolCapability.NETWORK_ACCESS to listOf("search the web", "fetch a url", "download"),
    )

    /**
     * [toolCapabilities] resolves each stage's `allowedTools` name to the capabilities that tool
     * grants (a tool's [com.correx.core.tools.contract.Tool.requiredCapabilities]) — the caller
     * supplies it (typically `ToolRegistry.all().associate { it.name to it.requiredCapabilities }`)
     * so this detector stays a pure function of plain data, not a live registry.
     */
    fun detect(graph: WorkflowGraph, toolCapabilities: Map<String, Set<ToolCapability>>): List<CapabilityGap> =
        graph.stages.entries.flatMap { (id, stage) ->
            val granted = grantedCapabilities(stage, toolCapabilities)
            val brief = briefOf(stage).lowercase()
            impliedCapabilities.entries.flatMap { (capability, phrases) ->
                if (capability in granted) {
                    emptyList()
                } else {
                    phrases.filter { phrase -> containsPhrase(brief, phrase) }
                        .map { phrase -> CapabilityGap(id.value, capability, phrase, granted) }
                }
            }
        }

    private fun grantedCapabilities(
        stage: StageConfig,
        toolCapabilities: Map<String, Set<ToolCapability>>,
    ): Set<ToolCapability> = stage.allowedTools.flatMap { toolCapabilities[it].orEmpty() }.toSet()

    private fun briefOf(stage: StageConfig): String = stage.metadata["promptInline"] ?: ""

    private fun containsPhrase(text: String, phrase: String): Boolean =
        Regex("\\b${Regex.escape(phrase)}\\b").containsMatchIn(text)
}
