package aoc2025

import println
import readInput
import splitInts
import java.util.*
import kotlin.math.sqrt

/*
--- Day 8: Playground ---
https://adventofcode.com/2025/day/8
*/
fun main() {

    val inlineTestInput = """
162,817,812
57,618,57
906,360,560
592,479,940
352,342,300
466,668,158
542,29,236
431,825,988
739,650,466
52,470,668
216,146,977
819,987,18
117,168,530
805,96,715
346,949,466
970,615,88
941,993,340
862,61,35
984,92,344
425,690,689
"""

    fun dist(n1: IntArray, n2: IntArray) =
        sqrt(((n1[0] - n2[0]).toFloat() * (n1[0] - n2[0]) + (n1[1] - n2[1]).toFloat() * (n1[1] - n2[1]) + (n1[2] - n2[2]).toFloat() * (n1[2] - n2[2])))

    fun find(parent: IntArray, n: Int): Int {
        if (parent[n] != n) {
            parent[n] = find(parent, parent[n])
        }
        return parent[n]
    }

    fun union(parent: IntArray, n1: Int, n2: Int): Boolean {
        val r1 = find(parent, n1)
        val r2 = find(parent, n2)
        if (r1 != r2) {
            parent[r1] = r2
            return true
        }
        return false
    }

    // having an oct-tree might speed up things here, so we skip far distances
    fun createDistances(nodes: Array<IntArray>): PriorityQueue<Pair<Pair<Int, Int>, Float>> {
        val distances = PriorityQueue<Pair<Pair<Int, Int>, Float>>(nodes.size * nodes.size / 2, Comparator.comparing { it.second })
        for (i1 in 0 until nodes.size) {
            for (i2 in i1 + 1 until nodes.size) {
                val d = dist(nodes[i1], nodes[i2])
                distances.add((i1 to i2) to d)
            }
        }
        return distances
    }

    fun part1(input: List<String>, numConnections: Int): Long {
        val nodes = input.map { it.splitInts(",").toIntArray() }.toTypedArray()
        val distances = createDistances(nodes)

        val n = nodes.size
        var numCircuits = 0
        val unionFindParentArray = IntArray(n) { it }
        while (distances.isNotEmpty()) {
            val (n1, n2) = distances.remove().first
            union(unionFindParentArray, n1, n2)
            numCircuits++
            if (numCircuits == numConnections) break
        }

        val circuitSizes = LongArray(n)
        for (i in 0 until n) {
            circuitSizes[find(unionFindParentArray, i)]++
        }
        return circuitSizes.sortedDescending().take(3).fold(1L) { acc, l -> acc * l }
    }

    fun part2(input: List<String>): Long {
        val nodes = input.map { it.splitInts(",").toIntArray() }.toTypedArray()
        val distances = createDistances(nodes)

        val n = nodes.size
        var numCircuits = 1
        val unionFindParentArray = IntArray(n) { it }
        while (distances.isNotEmpty()) {
            val (n1, n2) = distances.remove().first
            if (union(unionFindParentArray, n1, n2)) {
                numCircuits++
                if (numCircuits == n) {
                    return nodes[n1][0].toLong() * nodes[n2][0]
                }
            }
        }
        return -1L
    }

    // test if implementation meets criteria from the description, like:
    val testInput = inlineTestInput.trim().reader().readLines()
    //val testInput = readInput("aoc2025/Day08_test")
    val testInputPart1Result = part1(testInput, 10)
    println("Part 1 Test: $testInputPart1Result")
    val testInputPart2Result = part2(testInput)
    println("Part 2 Test: $testInputPart2Result")
    check(testInputPart1Result == 40L)
    check(testInputPart2Result == 25272L)

    val input = readInput("aoc2025/Day08")
    part1(input, 1000).println()
    part2(input).println()
}
