class Solution {
    private fun wordDifference(
        lhs: String,
        rhs: String,
    ): Int = lhs.zip(rhs).count { (l, r) -> l != r }

    private enum class VertexState {
        Unvisited,
        Enqueued,
        Done,
    }

    private data class Vertex<T>(
        val label: T,
        val neighbours: MutableSet<Vertex<T>> = mutableSetOf(),
    ) {
        var distance: Int = Int.MAX_VALUE
        val parents: MutableSet<Vertex<T>> = mutableSetOf()
        var state: VertexState = VertexState.Unvisited
            private set

        fun add(neighbour: Vertex<T>) {
            neighbours.add(neighbour)
        }

        fun addParent(parent: Vertex<T>) {
            parents.add(parent)
        }

        fun transition() {
            state =
                when (state) {
                    VertexState.Unvisited -> VertexState.Enqueued
                    VertexState.Enqueued -> VertexState.Done
                    VertexState.Done -> error("Cannot transition further!")
                }
        }

        override fun hashCode(): Int = label.hashCode()

        override fun toString(): String = "$label(${neighbours.map { it.label.toString() }})"

        override fun equals(other: Any?): Boolean {
            if (this === other) return true
            if (javaClass != other?.javaClass) return false

            other as Vertex<*>

            if (label != other.label) return false

            return true
        }
    }

    private class Graph<T> {
        private val vertices: MutableMap<T, Vertex<T>> = mutableMapOf()

        fun addVertex(
            src: T,
            dst: T,
        ) {
            val u = vertices.getOrPut(src) { Vertex(src) }
            val v = vertices.getOrPut(dst) { Vertex(dst) }

            u.add(v)
        }

        fun addSymmetricVertex(
            src: T,
            dst: T,
        ) {
            addVertex(src, dst)
            addVertex(dst, src)
        }

        private fun processVertex(
            queue: ArrayDeque<Vertex<T>>,
            u: Vertex<T>,
        ) {
            u.neighbours.forEach { v ->
                if (v.state == VertexState.Unvisited) {
                    queue.addLast(v)

                    queue.last().distance = u.distance + 1
                    queue.last().transition()
                } else if (u.distance + 1 != v.distance) {
                    return@forEach
                }

                v.addParent(u)
            }

            u.transition()
        }

        fun bfs(
            src: T,
            dst: T,
        ) {
            if (vertices[src] == null) {
                return
            }

            val queue = ArrayDeque<Vertex<T>>()
            queue.addLast(vertices[src]!!)

            queue.last().distance = 0
            queue.last().transition()

            while (queue.isNotEmpty()) {
                val u = queue.removeFirst()
                if (u.label == dst) {
                    break
                }

                processVertex(queue, u)
            }
        }

        fun parents(vertex: T): List<T> = vertices[vertex]?.parents?.map(Vertex<T>::label) ?: emptyList()

        override fun toString(): String = vertices.toString()
    }

    private fun constructGraph(
        src: String,
        vertices: List<String>,
    ): Graph<String> {
        val g = Graph<String>()

        vertices
            .filter { wordDifference(src, it) == 1 }
            .forEach { g.addVertex(src, it) }

        vertices.indices
            .flatMap { listOf(it).zip(it + 1 until vertices.size) }
            .map { (i, j) -> vertices[i] to vertices[j] }
            .filter { (lhs, rhs) -> wordDifference(lhs, rhs) == 1 }
            .forEach { (lhs, rhs) -> g.addSymmetricVertex(lhs, rhs) }

        return g
    }

    private fun constructPath(
        graph: Graph<String>,
        currentPath: MutableList<String>,
        paths: MutableList<MutableList<String>>,
    ): MutableList<MutableList<String>> {
        if (graph.parents(currentPath.last()).isEmpty()) {
            paths.add(currentPath.reversed().toMutableList())
            return paths
        }

        for (parent in graph.parents(currentPath.last()).filter { !currentPath.contains(it) }) {
            currentPath.add(parent)
            constructPath(graph, currentPath, paths)
            currentPath.remove(parent)
        }

        return paths
    }

    fun findLadders(
        beginWord: String,
        endWord: String,
        wordList: List<String>,
    ): List<List<String>> {
        val graph = constructGraph(beginWord, wordList)
        graph.bfs(beginWord, endWord)
        return constructPath(graph, mutableListOf(endWord), mutableListOf()).filter { it.count() >= 2 }
    }
}

fun main() {
    val s = Solution()

    check(
        s.findLadders("hit", "cog", listOf("hot", "dot", "dog", "lot", "log", "cog")) ==
            listOf(
                listOf("hit", "hot", "dot", "dog", "cog"), listOf("hit", "hot", "lot", "log", "cog"),
            ),
    )
    check(s.findLadders("hit", "cog", listOf("hot", "dot", "dog", "lot", "log")) == emptyList<List<String>>())
    check(s.findLadders("hot", "dog", listOf("hot", "dog")) == emptyList<List<String>>())
    check(s.findLadders("hot", "dog", listOf("hot", "dog", "dot")) == listOf(listOf("hot", "dot", "dog")))
}