package com.correx.core.tasks

import com.correx.core.events.events.StoredEvent
import com.correx.core.events.events.TaskAcceptanceCriteriaSetEvent
import com.correx.core.events.events.TaskAffectedPathsSetEvent
import com.correx.core.events.events.TaskBlockedEvent
import com.correx.core.events.events.TaskCancelledEvent
import com.correx.core.events.events.TaskClaimedEvent
import com.correx.core.events.events.TaskCompletedEvent
import com.correx.core.events.events.TaskCreatedEvent
import com.correx.core.events.events.TaskDeletedEvent
import com.correx.core.events.events.TaskEditedEvent
import com.correx.core.events.events.TaskEvent
import com.correx.core.events.events.TaskLinkedEvent
import com.correx.core.events.events.TaskNoteAddedEvent
import com.correx.core.events.events.TaskReleasedEvent
import com.correx.core.events.events.TaskReopenedEvent
import com.correx.core.events.events.TaskSubmittedForReviewEvent
import com.correx.core.events.events.TaskUnblockedEvent
import com.correx.core.events.events.TaskUnlinkedEvent
import kotlinx.datetime.Instant

/**
 * Folds task events into [TaskState]. Field-update events always apply; lifecycle events
 * apply only on a legal transition — an illegal transition is ignored and counted in
 * [TaskState.invalidTransitions] (mirrors how `SessionState` tracks invalid transitions).
 */
class DefaultTaskReducer : TaskReducer {

    override fun reduce(state: TaskState, event: StoredEvent): TaskState {
        val payload = event.payload as? TaskEvent ?: return state
        val ts = event.metadata.timestamp
        val base = state.copy(
            createdAt = state.createdAt ?: ts,
            updatedAt = ts,
        )
        return reduceContent(state, base, payload, ts)
            ?: reduceTransition(state, base, payload)
    }

    /** Field updates that do not touch status. Returns null for lifecycle events. */
    private fun reduceContent(
        state: TaskState,
        base: TaskState,
        payload: TaskEvent,
        ts: Instant,
    ): TaskState? = when (payload) {
        is TaskCreatedEvent -> base.copy(
            status = TaskStatus.TODO,
            key = payload.key,
            projectId = payload.projectId,
            title = payload.title,
            goal = payload.goal,
            acceptanceCriteria = payload.acceptanceCriteria,
            affectedPaths = payload.affectedPaths,
        )

        is TaskEditedEvent -> base.copy(
            title = payload.title ?: state.title,
            goal = payload.goal ?: state.goal,
        )

        is TaskAcceptanceCriteriaSetEvent -> base.copy(acceptanceCriteria = payload.criteria)

        is TaskAffectedPathsSetEvent -> base.copy(affectedPaths = payload.paths)

        is TaskLinkedEvent -> base.copy(
            links = (state.links + TaskLink(payload.targetId, payload.type, payload.targetKind)).distinct(),
        )

        is TaskUnlinkedEvent -> base.copy(
            links = state.links.filterNot {
                it.targetId == payload.targetId && it.type == payload.type
            },
        )

        is TaskNoteAddedEvent -> base.copy(
            notes = state.notes + TaskNote(payload.author, payload.body, ts),
        )

        is TaskDeletedEvent -> base.copy(deleted = true)

        else -> null
    }

    /** Lifecycle events: apply on a legal transition, else count it invalid. */
    private fun reduceTransition(
        state: TaskState,
        base: TaskState,
        payload: TaskEvent,
    ): TaskState {
        val from = state.status
        return when (payload) {
            is TaskClaimedEvent ->
                base.transitionTo(TaskStatus.IN_PROGRESS, from == TaskStatus.TODO) {
                    it.copy(claimant = payload.claimant)
                }

            is TaskReleasedEvent ->
                base.transitionTo(TaskStatus.TODO, from == TaskStatus.IN_PROGRESS) {
                    it.copy(claimant = null)
                }

            is TaskBlockedEvent ->
                base.transitionTo(TaskStatus.BLOCKED, from in WORKING)

            is TaskUnblockedEvent ->
                base.transitionTo(
                    if (state.claimant != null) TaskStatus.IN_PROGRESS else TaskStatus.TODO,
                    from == TaskStatus.BLOCKED,
                )

            is TaskSubmittedForReviewEvent ->
                base.transitionTo(TaskStatus.IN_REVIEW, from == TaskStatus.IN_PROGRESS)

            is TaskCompletedEvent ->
                base.transitionTo(TaskStatus.DONE, from == TaskStatus.IN_REVIEW)

            is TaskReopenedEvent ->
                base.transitionTo(TaskStatus.TODO, from in TERMINAL) {
                    it.copy(claimant = null)
                }

            is TaskCancelledEvent ->
                base.transitionTo(TaskStatus.CANCELLED, from in CANCELLABLE)

            else -> state
        } ?: state.copy(invalidTransitions = state.invalidTransitions + 1)
    }

    private inline fun TaskState.transitionTo(
        to: TaskStatus,
        legal: Boolean,
        mutate: (TaskState) -> TaskState = { it },
    ): TaskState? = if (legal) mutate(copy(status = to)) else null

    private companion object {
        val WORKING = setOf(TaskStatus.TODO, TaskStatus.IN_PROGRESS, TaskStatus.IN_REVIEW)
        val TERMINAL = setOf(TaskStatus.DONE, TaskStatus.CANCELLED)
        val CANCELLABLE = setOf(
            TaskStatus.TODO,
            TaskStatus.IN_PROGRESS,
            TaskStatus.IN_REVIEW,
            TaskStatus.BLOCKED,
        )
    }
}
