package day_05

import (
	"fmt"
	"slices"
	"strconv"
	"strings"
)

type Rules map[int][]int
type Manual []int

// task:https://adventofcode.com/2024/day/5
// short summary - count valid columns
func SolveBasic(input string) int {
	manuals, rules := loadManualAndRules(input)

	sum := 0
	for _, manual := range manuals {
		if isManualValid(manual, rules) {
			sum += manual[len(manual)/2]
		}
	}
	return sum
}

// task:https://adventofcode.com/2024/day/5#part2
// short summary - fix and count invalid columns
func SolveComplex(input string) int {
	manuals, rules := loadManualAndRules(input)

	sum := 0
	for _, manual := range manuals {
		if !isManualValid(manual, rules) {
			manual = fixManual(manual, rules)
			sum += manual[len(manual)/2]
		}
	}
	return sum
}

func fixManual(manual Manual, rules Rules) Manual {
	for {
		index := violationIndex(manual, rules)
		if index == -1 {
			return manual
		}
		violation := manual[index]
		manual = slices.Delete(manual, index, index+1)
		for i := 0; i < len(manual); i++ {
			if values, present := rules[manual[i]]; present {
				if slices.Contains(values, violation) {
					manual = slices.Insert(manual, i, violation)
					break
				}
			}
		}
	}
}

// helper methods

func isManualValid(manual Manual, rules Rules) bool {
	return violationIndex(manual, rules) == -1
}

func violationIndex(manual Manual, rules Rules) int {
	illegalValues := []int{}
	for i, num := range manual {
		if slices.Contains(illegalValues, num) {
			return i
		}
		if _, contains := rules[num]; contains {
			illegalValues = append(illegalValues, rules[num]...)
		}
	}
	return -1
}

func loadManualAndRules(input string) ([]Manual, Rules) {
	substrings := strings.Split(input, "\n\n")

	rules := loadRules(substrings[0])
	manuals := loadManuals(substrings[1])

	return manuals, rules
}

func loadManuals(s string) []Manual {
	var manuals []Manual
	for _, line := range strings.Split(s, "\n") {
		manuals = append(manuals, loadManual(line))
	}
	return manuals
}

func loadManual(line string) []int {
	var manual Manual
	for _, value := range strings.Split(line, ",") {
		manual = append(manual, must(strconv.Atoi(value)))
	}
	return manual
}

func must(val int, err error) int {
	if err != nil {
		panic(err)
	}
	return val
}

func loadRules(s string) Rules {
	rules := map[int][]int{}
	for _, line := range strings.Split(s, "\n") {
		var k, v int
		_, err := fmt.Sscanf(line, "%d|%d", &k, &v)
		if err != nil {
			panic(err)
		}
		rules[v] = append(rules[v], k)
	}
	return rules
}