package com.correx.core.toolintent

import com.correx.core.events.events.ToolCallObservation
import com.correx.core.events.events.ToolRequest
import com.correx.core.events.risk.RiskAction
import com.correx.core.tools.contract.ParamRole
import com.correx.core.tools.contract.ToolCapability
import com.correx.core.validation.model.ValidationIssue

/**
 * Plane-2 rule. Dispatches on declared tool [ToolCapability]s (never tool names),
 * assesses the proposed call against bounded real-world state, and records the
 * facts it observed so replay reads them back (invariant #9).
 */
interface ToolCallRule {
    fun appliesTo(capabilities: Set<ToolCapability>): Boolean
    fun assess(input: ToolCallAssessmentInput): ToolCallAssessment
}

data class ToolCallAssessmentInput(
    val request: ToolRequest,
    val capabilities: Set<ToolCapability>,
    val workspace: WorkspacePolicy,
    val probe: WorldProbe,
    val paramRoles: Map<String, ParamRole> = emptyMap(),
    // Workspace-relative globs the active stage may write. Empty = unrestricted.
    val writeManifest: List<String> = emptyList(),
    // Egress hosts granted to the active session (folded from EgressHostsGrantedEvent via
    // EgressAllowlistProjection). Unioned with the static allow-list at the network gate; empty
    // means "no per-session grants", which never narrows the static allow-list.
    val sessionEgressHosts: Set<String> = emptySet(),
    // What this session has done this run (reads, writes) and the task it's working — folded via
    // SessionContextProjection. The input to the anti-hallucination rules (read-before-write reads
    // .reads, write-scope reads .activeTask). Empty by default so unrelated rules ignore it.
    val session: SessionContext = SessionContext(),
)

data class ToolCallAssessment(
    val issues: List<ValidationIssue> = emptyList(),
    val observations: List<ToolCallObservation> = emptyList(),
    val disposition: RiskAction = RiskAction.PROCEED,
)
