package com.correx.core.tasks

import com.correx.core.events.types.TaskLinkType
import com.correx.core.events.types.TaskTargetKind

/**
 * Dependency reasoning over the task work graph — turns the `DEPENDS_ON`/`BLOCKS` link types from
 * inert metadata into answerable questions: what is a task waiting on, what is it ready to be
 * worked, and what does finishing it unblock.
 *
 * Two directions express the same edge, so both count: a `DEPENDS_ON B` link on A means A waits on
 * B; a `BLOCKS A` link on B means the same. A blocker is "unmet" until it reaches a terminal
 * status ([FINISHED]); a CANCELLED dependency no longer blocks. All reasoning is over the supplied
 * board, so callers scope it (one project, or the whole cross-project set); a link to a task absent
 * from the board is treated as already-resolved rather than an unknown blocker.
 */
object TaskGraph {

    private val FINISHED = setOf(TaskStatus.DONE, TaskStatus.CANCELLED)

    /** Tasks that must finish before [task] proceeds: its `DEPENDS_ON` targets plus tasks that `BLOCKS` it. */
    fun blockers(task: Task, board: Collection<Task>): List<Task> {
        val byId = board.associateBy { it.taskId.value }
        val dependsOn = task.linkTargets(TaskLinkType.DEPENDS_ON).mapNotNull { byId[it] }
        val inbound = board.filter { it.hasTaskLink(TaskLinkType.BLOCKS, task.taskId.value) }
        return (dependsOn + inbound).distinctBy { it.taskId.value }
    }

    /** The blockers that have not finished — the concrete reason [task] cannot start yet. */
    fun unmetBlockers(task: Task, board: Collection<Task>): List<Task> =
        blockers(task, board).filter { it.state.status !in FINISHED }

    /** True when [task] is TODO and has no unmet blockers — workable now (to be claimed, not assigned). */
    fun isReady(task: Task, board: Collection<Task>): Boolean =
        task.state.status == TaskStatus.TODO && unmetBlockers(task, board).isEmpty()

    /** Every workable task on the board, in board order. */
    fun ready(board: Collection<Task>): List<Task> =
        board.filter { isReady(it, board) }

    /** Tasks [task] is holding up: its own `BLOCKS` targets plus tasks that `DEPENDS_ON` it. */
    fun blocking(task: Task, board: Collection<Task>): List<Task> {
        val byId = board.associateBy { it.taskId.value }
        val downstream = task.linkTargets(TaskLinkType.BLOCKS).mapNotNull { byId[it] }
        val dependents = board.filter { it.hasTaskLink(TaskLinkType.DEPENDS_ON, task.taskId.value) }
        return (downstream + dependents).distinctBy { it.taskId.value }
    }

    private fun Task.linkTargets(type: TaskLinkType): List<String> =
        state.links.filter { it.targetKind == TaskTargetKind.TASK && it.type == type }.map { it.targetId }

    private fun Task.hasTaskLink(type: TaskLinkType, targetId: String): Boolean =
        state.links.any { it.targetKind == TaskTargetKind.TASK && it.type == type && it.targetId == targetId }
}
