package com.correx.core.kernel.orchestration

// Context-entry builders kept top-level (not orchestrator members) for unit-testability
// and to stay under detekt's function-count threshold (same pattern as isBackEdge).

import com.correx.core.artifacts.kind.ArtifactKind
import com.correx.core.events.events.RepoKnowledgeHit
import com.correx.core.context.model.ContextEntry
import com.correx.core.context.model.ContextLayer
import com.correx.core.context.model.EntryRole
import com.correx.core.events.events.RefinementIterationEvent
import com.correx.core.events.events.RetryAttemptedEvent
import com.correx.core.events.events.StoredEvent
import com.correx.core.events.types.ArtifactId
import com.correx.core.events.types.ContextEntryId
import com.correx.core.events.types.StageId
import com.correx.core.sessions.BoundAgentInstructions
import com.correx.core.sessions.BoundProjectProfile
import com.correx.core.transitions.graph.WorkflowGraph
import java.util.UUID

fun buildRetryFeedbackEntry(events: List<StoredEvent>, stageId: StageId): ContextEntry? {
    val latest = events
        .mapNotNull { it.payload as? RetryAttemptedEvent }
        .lastOrNull { it.stageId == stageId } ?: return null
    val content = "## Retry feedback\n" +
        "Attempt ${latest.attemptNumber} of ${latest.maxAttempts} for stage '${stageId.value}'. " +
        "The previous attempt failed: ${latest.failureReason}\n" +
        "Address the failure cause directly. Do not repeat the identical approach."
    return ContextEntry(
        id = ContextEntryId(UUID.randomUUID().toString()),
        layer = ContextLayer.L1,
        content = content,
        sourceType = "retryFeedback",
        sourceId = stageId.value,
        tokenEstimate = content.length / 4,
        role = EntryRole.SYSTEM,
    )
}

fun criticArtifactIds(
    events: List<StoredEvent>,
    graph: WorkflowGraph,
    stageId: StageId,
): Set<ArtifactId> {
    val cycle = events
        .mapNotNull { it.payload as? RefinementIterationEvent }
        .lastOrNull { it.cycleKey.endsWith("->${stageId.value}") } ?: return emptySet()
    val fromStage = StageId(cycle.cycleKey.substringBefore("->"))
    return graph.stages[fromStage]?.produces?.map { it.name }?.toSet() ?: emptySet()
}

fun buildArtifactKindVocabularyEntry(kinds: Collection<ArtifactKind>): ContextEntry {
    val listing = kinds.joinToString(", ") { k ->
        if (k.llmEmitted) "${k.id} (llm-emitted)" else k.id
    }
    val content = "## Available artifact kinds\n" +
        "Every stage's \"kind\" MUST be exactly one of: $listing\n" +
        "Stages that write or edit files use \"file_written\"; stages that run commands use " +
        "\"process_result\".\n" +
        "Do not invent kinds — a plan referencing an unknown kind fails to compile."
    return ContextEntry(
        id = ContextEntryId(UUID.randomUUID().toString()),
        layer = ContextLayer.L1,
        content = content,
        sourceType = "artifactKindVocabulary",
        sourceId = "artifact-kinds",
        tokenEstimate = content.length / 4,
        role = EntryRole.SYSTEM,
    )
}

fun buildRelevantFilesEntry(hits: List<RepoKnowledgeHit>): ContextEntry {
    val content = buildString {
        append("## Relevant files (retrieved by semantic similarity)\n")
        hits.map { it.text }.distinct().forEach { append("- ").append(it).append("\n") }
    }.trimEnd()
    return ContextEntry(
        id = ContextEntryId(UUID.randomUUID().toString()),
        layer = ContextLayer.L3,
        content = content,
        sourceType = "relevantFiles",
        sourceId = "repo-knowledge",
        tokenEstimate = content.length / 4,
        role = EntryRole.SYSTEM,
    )
}

fun buildProjectProfileEntry(profile: BoundProjectProfile): ContextEntry {
    val content = buildString {
        append("## Project profile\n")
        if (profile.about.isNotBlank()) append(profile.about).append("\n")
        if (profile.conventions.isNotEmpty()) {
            append("### Conventions\n")
            profile.conventions.forEach { append("- ").append(it).append("\n") }
        }
        if (profile.commands.isNotEmpty()) {
            append("### Commands\n")
            profile.commands.forEach { (name, cmd) -> append(name).append(": ").append(cmd).append("\n") }
        }
    }.trimEnd()
    return ContextEntry(
        id = ContextEntryId(UUID.randomUUID().toString()),
        layer = ContextLayer.L0,
        content = content,
        sourceType = "projectProfile",
        sourceId = "project-profile",
        tokenEstimate = content.length / 4,
        role = EntryRole.SYSTEM,
    )
}

// CLAUDE.md / AGENTS.md injected as L0 standing context (feat/backlog-burndown).
fun buildAgentInstructionsEntry(instructions: BoundAgentInstructions): ContextEntry {
    val content = instructions.content
    return ContextEntry(
        id = ContextEntryId(UUID.randomUUID().toString()),
        layer = ContextLayer.L0,
        content = content,
        sourceType = "agentInstructions",
        sourceId = "agent-instructions",
        tokenEstimate = content.length / 4,
        role = EntryRole.SYSTEM,
    )
}
