package main

import (
	"fmt"
	"sort"

	"regionio/internal/world"
	"regionio/internal/worldgen"
)

// gendump prints diagnostics about the current generator output so we can see
// concretely what terrain/biomes/surface look like without a client.
func main() {
	const seed = 12345
	od, err := worldgen.LoadOverworldFinalDensity(seed)
	if err != nil {
		panic(err)
	}
	gen := world.NewVanillaGenerator(seed)

	// 1) Biome distribution over a 16x16 chunk area (surface biome per column).
	biomeCounts := map[string]int{}
	surfaceBlockCounts := map[uint16]int{}
	var minH, maxH = 1 << 30, -(1 << 30)
	sumH := 0
	nH := 0
	for cx := int32(-64); cx < 64; cx += 8 {
		for cz := int32(-64); cz < 64; cz += 8 {
			c := gen(cx, cz)
			for lx := 0; lx < 16; lx += 4 {
				for lz := 0; lz < 16; lz += 4 {
					// surface biome
					s2 := worldgen.SampleColumn2D(od, world.SeaLevel, int(cx)*16+lx, int(cz)*16+lz)
					name := loadName(od, s2, int(cx)*16+lx, int(cz)*16+lz)
					biomeCounts[name]++
					// top solid block + height
					for wy := world.MinY + world.WorldHeight - 1; wy >= world.MinY; wy-- {
						b := c.GetBlock(lx, wy, lz)
						if b != world.StateAir && b != world.StateWater {
							surfaceBlockCounts[b]++
							if wy < minH {
								minH = wy
							}
							if wy > maxH {
								maxH = wy
							}
							sumH += wy
							nH++
							break
						}
					}
				}
			}
		}
	}

	// Climate axis ranges across the sampled area.
	type ax struct{ lo, hi, sum float64; n int }
	axes := map[string]*ax{"temp": {lo: 1e9, hi: -1e9}, "humid": {lo: 1e9, hi: -1e9}, "cont": {lo: 1e9, hi: -1e9}, "ero": {lo: 1e9, hi: -1e9}, "weird": {lo: 1e9, hi: -1e9}}
	upd := func(name string, v float64) { a := axes[name]; if v < a.lo { a.lo = v }; if v > a.hi { a.hi = v }; a.sum += v; a.n++ }
	for cx := int32(-64); cx < 64; cx += 2 {
		for cz := int32(-64); cz < 64; cz += 2 {
			for lx := 0; lx < 16; lx += 8 {
				for lz := 0; lz < 16; lz += 8 {
					s2 := worldgen.SampleColumn2D(od, world.SeaLevel, int(cx)*16+lx, int(cz)*16+lz)
					upd("temp", s2.Temperature)
					upd("humid", s2.Humidity)
					upd("cont", s2.Continentalness)
					upd("ero", s2.Erosion)
					upd("weird", s2.Weirdness)
				}
			}
		}
	}
	fmt.Println("=== Climate axis ranges (should span roughly [-1,1]) ===")
	for _, k := range []string{"temp", "humid", "cont", "ero", "weird"} {
		a := axes[k]
		fmt.Printf("  %-6s min=%+.3f max=%+.3f avg=%+.3f\n", k, a.lo, a.hi, a.sum/float64(a.n))
	}

	fmt.Println("\n=== Surface biome distribution (seed 12345, 256 chunks sampled) ===")
	printSorted(biomeCounts)
	fmt.Printf("\n=== Surface height: min=%d max=%d avg=%.1f (sea=%d) ===\n", minH, maxH, float64(sumH)/float64(nH), world.SeaLevel)
	fmt.Println("\n=== Top surface block IDs ===")
	printSortedU(surfaceBlockCounts)

	// Deep-layer composition: deepslate should dominate below y=0, stone above.
	deepStone := map[string]int{}
	cc := gen(0, 0)
	countAt := func(yLo, yHi int, label string) {
		stone, deep, other := 0, 0, 0
		for wy := yLo; wy <= yHi; wy++ {
			for lx := 0; lx < 16; lx++ {
				for lz := 0; lz < 16; lz++ {
					switch cc.GetBlock(lx, wy, lz) {
					case world.StateStone:
						stone++
					case 27924: // minecraft:deepslate
						deep++
					case world.StateAir:
					default:
						other++
					}
				}
			}
		}
		deepStone[label] = deep
		fmt.Printf("  %-18s stone=%d deepslate=%d other=%d\n", label, stone, deep, other)
	}
	fmt.Println("\n=== Deep-layer composition, chunk(0,0) ===")
	countAt(16, 40, "y=16..40")
	countAt(1, 7, "y=1..7 (transition)")
	countAt(-64, -1, "y<0 (deepslate)")
	// The deepslate rule is a vertical_gradient over absolute anchors 0..8:
	// everything solid below y=0 is deepslate, everything above y=8 is stone,
	// and the band between them is a scatter. A zero here means the rule is
	// firing but its block is being dropped, or its anchors are misread.
	switch {
	case deepStone["y<0 (deepslate)"] == 0:
		fmt.Println("  FAIL: no deepslate below y=0")
	case deepStone["y=16..40"] != 0:
		fmt.Println("  FAIL: deepslate above the transition band")
	case deepStone["y=1..7 (transition)"] == 0:
		fmt.Println("  FAIL: the stone/deepslate transition band is empty")
	default:
		fmt.Println("  OK: deepslate below y=0, scattered through y=1..7, none above")
	}

	// Bedrock floor: y=-64 must be solid bedrock everywhere, y=-63..-59 a
	// thinning scatter of bedrock over stone/deepslate, and NOTHING in that band
	// may be air or water. Air here means the surface-rule loop skipped the
	// bottom layers and the sub-sea-level pass then flooded them.
	fmt.Println("\n=== Bedrock floor, chunk(0,0) (expect no air/water, y=-64 fully bedrock) ===")
	badFloor := 0
	for wy := world.MinY; wy <= world.MinY+5; wy++ {
		bedrock, solid, empty := 0, 0, 0
		for lx := 0; lx < 16; lx++ {
			for lz := 0; lz < 16; lz++ {
				switch b := cc.GetBlock(lx, wy, lz); b {
				case world.StateBedrock:
					bedrock++
				case world.StateAir, world.StateWater:
					empty++
				default:
					solid++
				}
			}
		}
		badFloor += empty
		fmt.Printf("  y=%-4d bedrock=%-4d other-solid=%-4d air/water=%d\n", wy, bedrock, solid, empty)
	}
	if badFloor > 0 {
		fmt.Printf("  FAIL: %d air/water blocks in the bedrock band\n", badFloor)
	} else {
		fmt.Println("  OK: bedrock band is fully solid")
	}


	// Caves are dry: the aquifer decides fluid per position, so the open volume
	// underground is overwhelmingly air, with occasional aquifer pools and lava
	// down low. The defect this catches is the old unconditional "flood every
	// air block below sea level" pass, under which this number was 100%.
	fmt.Println("\n=== Underground fluids: water fraction y=-50..40 over inland chunks, lava anywhere ===")
	air, water, lava, solidU := 0, 0, 0, 0
	deepLava := 0
	inland := 0
	for cx := int32(-12); cx <= 12; cx += 4 {
		for cz := int32(-12); cz <= 12; cz += 4 {
			ch := gen(cx, cz)
			// Lava is counted everywhere; the water fraction only over land,
			// since an ocean's water legitimately reaches its floor. Lava
			// pockets cluster, so a narrow sample can miss them entirely.
			land := isInland(ch)
			if land {
				inland++
			}
			for wy := world.MinY; wy <= 40; wy++ {
				// The water fraction is measured over y=-50..40, above the band
				// where the global fluid rule makes lava unconditional.
				census := land && wy >= -50
				for lx := 0; lx < 16; lx++ {
					for lz := 0; lz < 16; lz++ {
						switch ch.GetBlock(lx, wy, lz) {
						case world.StateAir:
							if census {
								air++
							}
						case world.StateWater:
							if census {
								water++
							}
						case world.StateLava:
							lava++
							if wy < -54 {
								deepLava++
							}
							if census {
								air++ // open volume, just not water
							}
						default:
							if census {
								solidU++
							}
						}
					}
				}
			}
		}
	}
	open := air + water
	fmt.Printf("  chunks=%d solid=%d open=%d (air+lava=%d water=%d) | lava total=%d, of it below y=-54: %d\n",
		inland, solidU, open, air, water, lava, deepLava)
	switch {
	case open == 0:
		fmt.Println("  FAIL: no open volume underground at all")
	default:
		frac := float64(water) / float64(open)
		fmt.Printf("  water is %.1f%% of the open volume\n", frac*100)
		if frac > 0.35 {
			fmt.Println("  FAIL: caves are flooded; the aquifer is not deciding fluid")
		} else if lava == 0 {
			fmt.Println("  FAIL: no lava anywhere underground")
		} else {
			fmt.Println("  OK: caves are dry and lava exists")
		}
	}

	// Subsurface banding: find grass-topped land columns and print the top ~8
	// blocks (grass cap → dirt band → stone) to confirm surfaceDepth widened the
	// dirt band beyond a single block.
	fmt.Println("\n=== Subsurface banding (grass columns: expect grass=9, dirt=10 band, stone=1) ===")
	found := 0
	bandDepths := map[int]int{}
	for cx := int32(-40); cx < 40 && found < 6; cx += 3 {
		for cz := int32(-40); cz < 40 && found < 6; cz += 3 {
			ch := gen(cx, cz)
			for lx := 0; lx < 16; lx++ {
				for lz := 0; lz < 16; lz++ {
					topY := world.MinY - 1
					for wy := world.MinY + world.WorldHeight - 1; wy >= world.MinY; wy-- {
						b := ch.GetBlock(lx, wy, lz)
						if b != world.StateAir && b != world.StateWater && b != world.StateLava &&
							b != world.StateOakLog && b != world.StateOakLeaf {
							topY = wy
							break
						}
					}
					if topY < world.SeaLevel || ch.GetBlock(lx, topY, lz) != world.StateGrass {
						continue
					}
					depth := 0
					for wy := topY - 1; wy >= topY-6 && ch.GetBlock(lx, wy, lz) == world.StateDirt; wy-- {
						depth++
					}
					bandDepths[depth]++
				}
			}
			for lx := 0; lx < 16 && found < 6; lx += 5 {
				for lz := 0; lz < 16 && found < 6; lz += 5 {
					topY := world.MinY - 1
					for wy := world.MinY + world.WorldHeight - 1; wy >= world.MinY; wy-- {
						b := ch.GetBlock(lx, wy, lz)
						if b != world.StateAir && b != world.StateWater &&
							b != world.StateOakLog && b != world.StateOakLeaf {
							topY = wy
							break
						}
					}
					if topY < world.SeaLevel || ch.GetBlock(lx, topY, lz) != world.StateGrass {
						continue
					}
					row := fmt.Sprintf("  (%d,%d)+[%d,%d] top=y%d: ", cx, cz, lx, lz, topY)
					for wy := topY; wy >= topY-9 && wy >= world.MinY; wy-- {
						row += fmt.Sprintf("%d ", ch.GetBlock(lx, wy, lz))
					}
					fmt.Println(row)
					found++
				}
			}
		}
	}
	if found == 0 {
		fmt.Println("  (no grass columns found in scan area)")
	}
	// The band depth over every grass column scanned. Vanilla is 2..4; a
	// histogram piled entirely on 0 means the biome surface subtree is gated to
	// one block per column again.
	banded, allGrass := 0, 0
	for d, n := range bandDepths {
		allGrass += n
		if d >= 2 {
			banded += n
		}
	}
	fmt.Printf("  dirt-band depth over %d grass columns: %v\n", allGrass, bandDepths)
	switch {
	case allGrass == 0:
		fmt.Println("  (no grass columns to measure)")
	case banded*4 < allGrass*3:
		fmt.Printf("  FAIL: only %d of %d grass columns carry 2+ blocks of dirt\n", banded, allGrass)
	default:
		fmt.Printf("  OK: %d of %d grass columns carry 2+ blocks of dirt\n", banded, allGrass)
	}

	// Badlands banding: the clay band table is 192 entries of seven terracotta
	// colours. The stand-in it replaced cycled four, so brown, red and light
	// grey never appeared anywhere in the world.
	fmt.Println("\n=== Badlands clay bands (expect several terracotta colours down a column) ===")
	terracottas := map[uint16]string{
		12912: "terracotta", 11444: "white", 11445: "orange", 11448: "yellow",
		11452: "light_gray", 11456: "brown", 11458: "red",
	}
	seenBands := map[uint16]int{}
	badlandsCols := 0
	for cx := int32(-300); cx < 300 && badlandsCols < 8; cx += 7 {
		for cz := int32(-300); cz < 300 && badlandsCols < 8; cz += 7 {
			name := world.BiomeNameAt(od, int(cx)*16+8, int(cz)*16+8)
			if name != "minecraft:badlands" && name != "minecraft:eroded_badlands" && name != "minecraft:wooded_badlands" {
				continue
			}
			ch := gen(cx, cz)
			for lx := 0; lx < 16; lx += 4 {
				for lz := 0; lz < 16; lz += 4 {
					for wy := world.MinY + world.WorldHeight - 1; wy >= world.MinY; wy-- {
						if _, isBand := terracottas[ch.GetBlock(lx, wy, lz)]; isBand {
							seenBands[ch.GetBlock(lx, wy, lz)]++
						}
					}
				}
			}
			badlandsCols++
		}
	}
	if badlandsCols == 0 {
		fmt.Println("  (no badlands in the scan area)")
	} else {
		for id, label := range terracottas {
			fmt.Printf("  %-11s %d\n", label, seenBands[id])
		}
		if len(seenBands) < 6 {
			fmt.Printf("  FAIL: only %d of 7 terracotta colours placed\n", len(seenBands))
		} else {
			fmt.Printf("  OK: %d of 7 terracotta colours across %d badlands chunks\n", len(seenBands), badlandsCols)
		}
	}

	// 2) Cross-section at chunk (0,0): column x=8, over full Y, ASCII.
	fmt.Println("\n=== Cross-section chunk(0,0) z=8, x=0..15 (side view, top 96 blocks near surface) ===")
	c := gen(0, 0)
	crossSection(c)
}

func loadName(od *worldgen.OverworldDensity, s2 worldgen.Sample2D, wx, wz int) string {
	return world.BiomeNameAt(od, wx, wz)
}

// isInland reports whether most of the chunk's columns break the surface above
// sea level. Ocean chunks are excluded from the cave-fluid census because their
// water legitimately reaches all the way down to the sea floor.
func isInland(c *world.Chunk) bool {
	aboveSea := 0
	for lx := 0; lx < 16; lx += 2 {
		for lz := 0; lz < 16; lz += 2 {
			for wy := world.MinY + world.WorldHeight - 1; wy >= world.MinY; wy-- {
				b := c.GetBlock(lx, wy, lz)
				if b == world.StateAir {
					continue
				}
				if b != world.StateWater && wy >= world.SeaLevel {
					aboveSea++
				}
				break
			}
		}
	}
	return aboveSea > 48 // of 64 sampled columns
}

func crossSection(c *world.Chunk) {
	// vertical band from y=40..136
	for wy := 130; wy >= 40; wy-- {
		row := fmt.Sprintf("%4d ", wy)
		for lx := 0; lx < 16; lx++ {
			row += glyph(c.GetBlock(lx, wy, 8))
		}
		fmt.Println(row)
	}
}

func glyph(b uint16) string {
	switch b {
	case world.StateAir:
		return "."
	case world.StateWater:
		return "~"
	case world.StateLava:
		return "!"
	case world.StateStone:
		return "#"
	case world.StateDirt:
		return "d"
	case world.StateGrass:
		return "g"
	case world.StateSand:
		return "s"
	case world.StateBedrock:
		return "B"
	case world.StateOakLog:
		return "L"
	case world.StateOakLeaf:
		return "o"
	default:
		return "?"
	}
}

func printSorted(m map[string]int) {
	type kv struct {
		k string
		v int
	}
	var s []kv
	for k, v := range m {
		s = append(s, kv{k, v})
	}
	sort.Slice(s, func(i, j int) bool { return s[i].v > s[j].v })
	for _, e := range s {
		fmt.Printf("  %-40s %d\n", e.k, e.v)
	}
}

func printSortedU(m map[uint16]int) {
	type kv struct {
		k uint16
		v int
	}
	var s []kv
	for k, v := range m {
		s = append(s, kv{k, v})
	}
	sort.Slice(s, func(i, j int) bool { return s[i].v > s[j].v })
	for _, e := range s {
		fmt.Printf("  id=%-6d %d\n", e.k, e.v)
	}
}
