package com.correx.apps.server.bridge

import com.correx.apps.server.protocol.AssessedIssueDto
import com.correx.apps.server.protocol.ReviewFindingDto
import com.correx.apps.server.protocol.ServerMessage
import com.correx.core.artifactstore.ArtifactStore
import com.correx.core.events.events.ReviewFindingsRaisedEvent
import com.correx.core.events.events.StoredEvent
import com.correx.core.events.events.ToolCallAssessedEvent
import com.correx.core.events.events.ToolExecutionCompletedEvent
import com.correx.core.events.events.ToolExecutionFailedEvent
import com.correx.core.events.events.ToolExecutionRejectedEvent
import com.correx.core.events.events.ToolInvocationRequestedEvent

/** Tool invocation / execution / assessment + review-findings events. */
@Suppress("UnusedParameter", "LongMethod")
internal suspend fun mapToolEvent(
    event: StoredEvent,
    artifactStore: ArtifactStore,
    sessionSequence: Long,
): MapOutcome {
    val seq = event.sequence
    val msg = when (val p = event.payload) {
        is ToolInvocationRequestedEvent -> ServerMessage.ToolStarted(
            sessionId = p.sessionId,
            toolName = p.toolName,
            tier = p.tier,
            params = prettyToolParams(p.request.parameters),
            sequence = seq,
            sessionSequence = sessionSequence,
        )

        is ToolExecutionCompletedEvent -> ServerMessage.ToolCompleted(
            sessionId = p.sessionId,
            toolName = p.toolName,
            outputSummary = p.receipt.outputSummary,
            occurredAt = event.metadata.timestamp.toEpochMilliseconds(),
            diff = p.receipt.diff,
            affectedEntities = p.receipt.affectedEntities,
            sequence = seq,
            sessionSequence = sessionSequence,
        )

        is ToolExecutionFailedEvent -> ServerMessage.ToolFailed(
            sessionId = p.sessionId,
            toolName = p.toolName,
            reason = p.reason,
            occurredAt = event.metadata.timestamp.toEpochMilliseconds(),
            sequence = seq,
            sessionSequence = sessionSequence,
        )

        is ToolExecutionRejectedEvent -> ServerMessage.ToolRejected(
            sessionId = p.sessionId,
            toolName = p.toolName,
            reason = p.reason,
            sequence = seq,
            sessionSequence = sessionSequence,
        )

        is ToolCallAssessedEvent -> ServerMessage.ToolAssessed(
            sessionId = p.sessionId,
            stageId = p.stageId,
            toolName = p.toolName,
            disposition = p.disposition.name,
            issues = p.issues.map { AssessedIssueDto(it.code, it.message, it.severity) },
            occurredAt = event.metadata.timestamp.toEpochMilliseconds(),
            sequence = seq,
            sessionSequence = sessionSequence,
        )

        is ReviewFindingsRaisedEvent -> ServerMessage.ReviewFindings(
            sessionId = p.sessionId,
            stageId = p.stageId,
            verdict = p.verdict.name,
            findings = p.findings.map {
                ReviewFindingDto(
                    severity = it.severity.name,
                    confidence = it.confidence,
                    category = it.category,
                    target = it.target,
                    message = it.message,
                    suggestedFix = it.suggestedFix,
                    correctness = it.correctness,
                )
            },
            blocked = p.blocked,
            sequence = seq,
            sessionSequence = sessionSequence,
        )

        else -> return MapOutcome.Skip
    }
    return MapOutcome.Emit(msg)
}

/**
 * Render a tool call's raw parameter map into compact, human-readable "key=value" cells for the
 * client's tool-call row. Primary keys (path/command/query/…) lead so the most salient argument is
 * first; long values are truncated and multi-line values collapsed so the WS frame stays small.
 */
internal fun prettyToolParams(parameters: Map<String, Any>): List<String> {
    if (parameters.isEmpty()) return emptyList()
    val primary = listOf("command", "path", "query", "url", "pattern", "content", "operation")
    val ordered = parameters.entries.sortedWith(
        compareBy({ primary.indexOf(it.key).let { i -> if (i < 0) primary.size else i } }, { it.key }),
    )
    return ordered.take(MAX_PARAM_CELLS).map { (k, v) -> "$k=${formatParamValue(v)}" }
}

private fun formatParamValue(value: Any?): String {
    val raw = when (value) {
        null -> "null"
        is String -> value
        is Collection<*> -> value.joinToString(", ", prefix = "[", postfix = "]") { formatParamValue(it) }
        else -> value.toString()
    }
    val flattened = raw.replace('\n', ' ').replace('\r', ' ').trim()
    val clipped = if (flattened.length > MAX_PARAM_VALUE_LEN) flattened.take(MAX_PARAM_VALUE_LEN) + "…" else flattened
    // Quote strings that carry whitespace so the boundary of the value is unambiguous in the row.
    return if (value is String && clipped.any { it.isWhitespace() }) "\"$clipped\"" else clipped
}

private const val MAX_PARAM_CELLS = 5
private const val MAX_PARAM_VALUE_LEN = 80
