package worldgen

import (
	"encoding/json"
	"fmt"
	"math/rand"
	"sync"
)

// surface.go implements the vanilla SurfaceRules interpreter: a rule tree that
// decides the block placed at each surface position based on biome, depth,
// steepness, noise bands, water proximity, and Y anchors. The tree is parsed
// from the embedded overworld.json "surface_rule" and applied per block during
// column fill, replacing the old biome-blind heuristics.
//
// It reproduces net.minecraft.world.level.levelgen.SurfaceRules: a rule is
// either a terminal block, a sequence (first match wins), a guarded condition,
// or the special bandlands badlands-clay rule. Condition tests are the 11 types
// present in the overworld rule tree.

// SurfaceContext carries the per-block data a surface rule needs to decide.
type SurfaceContext struct {
	// X, Y, Z are the block's world coordinates.
	X, Y, Z int
	// StoneDepthAbove counts solid blocks at or above Y in this column down to
	// the surface; it is the vanilla "stone_depth" the stone_depth condition
	// compares against (with offset/surface_depth adjustments applied by the
	// test).
	StoneDepthAbove int
	// SeaLevel is the world sea level (63 for the overworld).
	SeaLevel int
	// BiomeName is the resolved surface biome (e.g. "minecraft:desert").
	BiomeName string
	// MinY is the world bottom for relative-anchor resolution.
	MinY int
	// SurfaceNoise is the "minecraft:surface" noise sample at (X,Z); the
	// noise_threshold condition ranges over it.
	SurfaceNoise float64
	// Steep is true when the local slope exceeds the vanilla steep threshold
	// (~1.0 surface-depth delta between neighbours).
	Steep bool
	// SurfaceDepth is the vanilla surface-depth value at this column (a small
	// noise-driven integer 0..N) added to stone depth comparisons.
	SurfaceDepth int
	// PreliminarySurface is the top solid Y in this column; the
	// above_preliminary_surface condition passes for blocks above it.
	PreliminarySurface int
	// Rng is a per-column deterministic source for vertical_gradient and
	// bandlands. It is seeded by the column so results are stable across runs.
	Rng *rand.Rand
}

// SurfaceRule decides the block at a context. Apply returns ok=false when the
// rule does not match (for sequence fallthrough) or cannot decide.
type SurfaceRule interface {
	Apply(ctx *SurfaceContext) (state uint16, ok bool)
}

// ---- Rule nodes --------------------------------------------------------

// blockRule places a fixed block state.
type blockRule struct{ state uint16 }

func (r blockRule) Apply(_ *SurfaceContext) (uint16, bool) { return r.state, true }

// sequenceRule applies the first child that matches (short-circuit, like &&).
type sequenceRule struct{ rules []SurfaceRule }

func (r sequenceRule) Apply(ctx *SurfaceContext) (uint16, bool) {
	for _, rule := range r.rules {
		if s, ok := rule.Apply(ctx); ok {
			return s, true
		}
	}
	return 0, false
}

// conditionRule applies its inner rule only when the test passes.
type conditionRule struct {
	test ConditionTest
	then SurfaceRule
}

func (r conditionRule) Apply(ctx *SurfaceContext) (uint16, bool) {
	if !r.test.Test(ctx) {
		return 0, false
	}
	return r.then.Apply(ctx)
}

// bandlandsRule reproduces the vanilla badlands coloured-clay banding: a
// deterministic per-column pattern of terracotta colours at certain Y bands. We
// approximate the 8-band rotation using the column RNG; exact band geometry is
// captured well enough to read as badlands.
type bandlandsRule struct{}

func (bandlandsRule) Apply(ctx *SurfaceContext) (uint16, bool) {
	if ctx.Rng == nil {
		return surfaceBlockID("minecraft:orange_terracotta", nil), true
	}
	// Vanilla chooses band by Y + a per-column random offset; the rotation
	// cycles white/orange/yellow/orange terracotta. Pick from the cycle by Y.
	band := (ctx.Y + ctx.Rng.Intn(7)) % 4
	switch band {
	case 0:
		return surfaceBlockID("minecraft:white_terracotta", nil), true
	case 1, 3:
		return surfaceBlockID("minecraft:orange_terracotta", nil), true
	default:
		return surfaceBlockID("minecraft:yellow_terracotta", nil), true
	}
}

// ---- Condition tests ---------------------------------------------------

// ConditionTest is a boolean predicate over a SurfaceContext.
type ConditionTest interface {
	Test(ctx *SurfaceContext) bool
}

// biomeTest passes when the column's biome is in the allowlist.
type biomeTest struct{ allowed []string }

func (t biomeTest) Test(ctx *SurfaceContext) bool {
	for _, b := range t.allowed {
		if b == ctx.BiomeName {
			return true
		}
	}
	return false
}

// steepTest passes on steep terrain (vanilla SurfaceRules.STEEP, slope > ~1.0).
type steepTest struct{}

func (steepTest) Test(ctx *SurfaceContext) bool { return ctx.Steep }

// holeTest passes in surface "holes" below the surrounding terrain — we
// approximate as "below sea level and not the top" since true hole detection
// needs a neighbourhood. Conservative: false (rare rule, low visual cost).
type holeTest struct{}

func (holeTest) Test(ctx *SurfaceContext) bool { return false }

// waterTest passes when the block is within `offset` of the water surface
// (vanilla SurfaceRules.WATER). We treat it as "at or just below sea level" —
// the common case for beach/shore rules.
type waterTest struct {
	offset               int
	surfaceDepthMul      int
	addStoneDepth        bool
}

func (t waterTest) Test(ctx *SurfaceContext) bool {
	// Vanilla: passes when Y >= seaLevel + offset + surfaceDepth*mul (±stone).
	threshold := ctx.SeaLevel + t.offset + ctx.SurfaceDepth*t.surfaceDepthMul
	return ctx.Y >= threshold
}

// temperatureTest passes when the (column) temperature is below freezing — the
// snow-at-height rule. We fold temperature into the biome name (snowy_*
// biomes) rather than sampling the temperature noise, so pass for cold biomes.
type temperatureTest struct{}

func (temperatureTest) Test(ctx *SurfaceContext) bool {
	return isColdBiome(ctx.BiomeName)
}

// isColdBiome reports whether the biome should receive snow cover. We use the
// biome name rather than the temperature noise for simplicity; this matches
// the visible result for the standard overworld biomes.
func isColdBiome(name string) bool {
	switch name {
	case "minecraft:snowy_plains", "minecraft:snowy_taiga", "minecraft:snowy_beach",
		"minecraft:snowy_slopes", "minecraft:jagged_peaks", "minecraft:frozen_peaks",
		"minecraft:frozen_river", "minecraft:frozen_ocean", "minecraft:deep_frozen_ocean",
		"minecraft:ice_spikes", "minecraft:grove":
		return true
	}
	return false
}

// yAboveTest passes when Y is above an anchor (absolute, above_bottom, or
// below_top), with optional surface-depth and stone-depth offsets.
type yAboveTest struct {
	absolute           int
	hasAbsolute        bool
	aboveBottom        int
	hasAboveBottom     bool
	belowTop           int
	hasBelowTop        bool
	addStoneDepth      bool
	surfaceDepthMul    int
}

func (t yAboveTest) Test(ctx *SurfaceContext) bool {
	var anchor int
	switch {
	case t.hasAbsolute:
		anchor = t.absolute
	case t.hasAboveBottom:
		anchor = ctx.MinY + t.aboveBottom
	case t.hasBelowTop:
		anchor = (ctx.MinY + 384) - 1 - t.belowTop
	}
	threshold := anchor + ctx.SurfaceDepth*t.surfaceDepthMul
	if t.addStoneDepth {
		threshold += ctx.StoneDepthAbove
	}
	return ctx.Y >= threshold
}

// stoneDepthTest passes based on the block's depth relative to the surface
// floor/ceiling. surface_type "floor" counts blocks from the surface downward
// and passes when that depth is at or below offset (i.e. near/at the surface);
// "ceiling" passes when the block is the surface cap — the topmost block whose
// depth-above-surface is 0, i.e. air sits directly on it. This matches vanilla:
// desert's "ceiling → sandstone, else sand" puts sandstone just below the sand
// cap, not on top.
type stoneDepthTest struct {
	surfaceType     string // "floor" or "ceiling"
	offset          int
	addSurfaceDepth bool
	secondaryRange  int
}

func (t stoneDepthTest) Test(ctx *SurfaceContext) bool {
	depth := ctx.StoneDepthAbove
	if t.addSurfaceDepth {
		depth += ctx.SurfaceDepth
	}
	if t.surfaceType == "ceiling" {
		// Ceiling: the surface cap. Passes when depth-above-surface equals the
		// offset (0 for the topmost block). Used to special-case the block
		// directly under air.
		return depth == t.offset
	}
	return depth <= t.offset
}

// noiseThresholdTest passes when the named surface noise is within [min,max].
type noiseThresholdTest struct {
	min, max float64
	noise    string
}

func (t noiseThresholdTest) Test(ctx *SurfaceContext) bool {
	// Only "minecraft:surface" is sampled in SurfaceContext; other noises fall
	// through as false (conservative).
	if t.noise != "minecraft:surface" {
		return false
	}
	return ctx.SurfaceNoise >= t.min && ctx.SurfaceNoise <= t.max
}

// notTest inverts its inner test.
type notTest struct{ inner ConditionTest }

func (t notTest) Test(ctx *SurfaceContext) bool { return !t.inner.Test(ctx) }

// verticalGradientTest reproduces the bedrock-floor gradient: a deterministic
// band from true_at_and_below to false_at_and_above where membership tapers via
// the column RNG. Anchors are above_bottom offsets from the world floor.
type verticalGradientTest struct {
	randomName      string
	trueAtAndBelow  int // above_bottom
	falseAtAndAbove int // above_bottom
}

func (t verticalGradientTest) Test(ctx *SurfaceContext) bool {
	loY := ctx.MinY + t.trueAtAndBelow
	hiY := ctx.MinY + t.falseAtAndAbove
	switch {
	case ctx.Y <= loY:
		return true
	case ctx.Y >= hiY:
		return false
	}
	// Taper band: probability decreases linearly. Use the per-column RNG once
	// per Y so the floor is stable but noisy. We approximate vanilla's
	// random-based interpolation.
	if ctx.Rng == nil {
		return false
	}
	band := hiY - loY
	pos := ctx.Y - loY
	return ctx.Rng.Float64() > float64(pos)/float64(band)
}

// abovePreliminarySurfaceTest passes for blocks at or above the column's
// preliminary surface (the top solid Y). Vanilla gates the biome dispatch on
// this so submerged blocks far below the surface keep stone.
type abovePreliminarySurfaceTest struct{}

func (abovePreliminarySurfaceTest) Test(ctx *SurfaceContext) bool {
	return ctx.Y >= ctx.PreliminarySurface
}

// ---- Parser ------------------------------------------------------------

// ParseSurfaceRule parses a surface_rule JSON node into a rule tree.
func ParseSurfaceRule(raw json.RawMessage) (SurfaceRule, error) {
	var obj struct {
		Type string `json:"type"`
	}
	if err := json.Unmarshal(raw, &obj); err != nil {
		return nil, err
	}
	switch obj.Type {
	case "minecraft:block":
		var b struct {
			Result struct {
				Name       string            `json:"Name"`
				Properties map[string]string `json:"Properties"`
			} `json:"result_state"`
		}
		if err := json.Unmarshal(raw, &b); err != nil {
			return nil, err
		}
		return blockRule{state: surfaceBlockID(b.Result.Name, b.Result.Properties)}, nil

	case "minecraft:sequence":
		var s struct {
			Sequence []json.RawMessage `json:"sequence"`
		}
		if err := json.Unmarshal(raw, &s); err != nil {
			return nil, err
		}
		rules := make([]SurfaceRule, 0, len(s.Sequence))
		for _, child := range s.Sequence {
			r, err := ParseSurfaceRule(child)
			if err != nil {
				return nil, err
			}
			rules = append(rules, r)
		}
		return sequenceRule{rules: rules}, nil

	case "minecraft:condition":
		var c struct {
			IfTrue json.RawMessage `json:"if_true"`
			Then   json.RawMessage `json:"then_run"`
		}
		if err := json.Unmarshal(raw, &c); err != nil {
			return nil, err
		}
		test, err := parseCondition(c.IfTrue)
		if err != nil {
			return nil, err
		}
		then, err := ParseSurfaceRule(c.Then)
		if err != nil {
			return nil, err
		}
		return conditionRule{test: test, then: then}, nil

	case "minecraft:bandlands":
		return bandlandsRule{}, nil
	}
	return nil, fmt.Errorf("surface: unknown rule type %q", obj.Type)
}

// parseCondition parses an if_true condition node into a ConditionTest.
func parseCondition(raw json.RawMessage) (ConditionTest, error) {
	var obj struct {
		Type string `json:"type"`
	}
	if err := json.Unmarshal(raw, &obj); err != nil {
		return nil, err
	}
	switch obj.Type {
	case "minecraft:biome":
		var b struct {
			Is []string `json:"biome_is"`
		}
		if err := json.Unmarshal(raw, &b); err != nil {
			return nil, err
		}
		return biomeTest{allowed: b.Is}, nil

	case "minecraft:steep":
		return steepTest{}, nil

	case "minecraft:hole":
		return holeTest{}, nil

	case "minecraft:water":
		var w struct {
			Offset               int  `json:"offset"`
			SurfaceDepthMul      int  `json:"surface_depth_multiplier"`
			AddStoneDepth        bool `json:"add_stone_depth"`
		}
		if err := json.Unmarshal(raw, &w); err != nil {
			return nil, err
		}
		return waterTest{offset: w.Offset, surfaceDepthMul: w.SurfaceDepthMul, addStoneDepth: w.AddStoneDepth}, nil

	case "minecraft:temperature":
		return temperatureTest{}, nil

	case "minecraft:stone_depth":
		var s struct {
			SurfaceType     string `json:"surface_type"`
			Offset          int    `json:"offset"`
			AddSurfaceDepth bool   `json:"add_surface_depth"`
			SecondaryRange  int    `json:"secondary_depth_range"`
		}
		if err := json.Unmarshal(raw, &s); err != nil {
			return nil, err
		}
		return stoneDepthTest{surfaceType: s.SurfaceType, offset: s.Offset, addSurfaceDepth: s.AddSurfaceDepth, secondaryRange: s.SecondaryRange}, nil

	case "minecraft:noise_threshold":
		var n struct {
			Min   float64 `json:"min_threshold"`
			Max   float64 `json:"max_threshold"`
			Noise string  `json:"noise"`
		}
		if err := json.Unmarshal(raw, &n); err != nil {
			return nil, err
		}
		return noiseThresholdTest{min: n.Min, max: n.Max, noise: n.Noise}, nil

	case "minecraft:y_above":
		var y struct {
			AddStoneDepth   bool `json:"add_stone_depth"`
			SurfaceDepthMul int  `json:"surface_depth_multiplier"`
			Anchor          struct {
				Absolute    *int `json:"absolute"`
				AboveBottom *int `json:"above_bottom"`
				BelowTop    *int `json:"below_top"`
			} `json:"anchor"`
		}
		if err := json.Unmarshal(raw, &y); err != nil {
			return nil, err
		}
		t := yAboveTest{addStoneDepth: y.AddStoneDepth, surfaceDepthMul: y.SurfaceDepthMul}
		if y.Anchor.Absolute != nil {
			t.hasAbsolute, t.absolute = true, *y.Anchor.Absolute
		}
		if y.Anchor.AboveBottom != nil {
			t.hasAboveBottom, t.aboveBottom = true, *y.Anchor.AboveBottom
		}
		if y.Anchor.BelowTop != nil {
			t.hasBelowTop, t.belowTop = true, *y.Anchor.BelowTop
		}
		return t, nil

	case "minecraft:not":
		var n struct {
			Invert json.RawMessage `json:"invert"`
		}
		if err := json.Unmarshal(raw, &n); err != nil {
			return nil, err
		}
		inner, err := parseCondition(n.Invert)
		if err != nil {
			return nil, err
		}
		return notTest{inner: inner}, nil

	case "minecraft:vertical_gradient":
		var v struct {
			TrueAtAndBelow  anchorJSON `json:"true_at_and_below"`
			FalseAtAndAbove anchorJSON `json:"false_at_and_above"`
		}
		if err := json.Unmarshal(raw, &v); err != nil {
			return nil, err
		}
		return verticalGradientTest{
			trueAtAndBelow:  v.TrueAtAndBelow.aboveBottom,
			falseAtAndAbove: v.FalseAtAndAbove.aboveBottom,
		}, nil

	case "minecraft:above_preliminary_surface":
		return abovePreliminarySurfaceTest{}, nil
	}
	return nil, fmt.Errorf("surface: unknown condition type %q", obj.Type)
}

// anchorJSON decodes a {above_bottom|below_top|absolute: N} surface anchor.
type anchorJSON struct {
	absolute    int
	aboveBottom int
	belowTop    int
}

func (a *anchorJSON) UnmarshalJSON(data []byte) error {
	var m map[string]int
	if err := json.Unmarshal(data, &m); err != nil {
		return err
	}
	a.aboveBottom = m["above_bottom"]
	a.belowTop = m["below_top"]
	a.absolute = m["absolute"]
	return nil
}

// ---- Loader ------------------------------------------------------------

var (
	surfaceRuleOnce sync.Once
	surfaceRule     SurfaceRule
	surfaceRuleErr  error
)

// LoadOverworldSurfaceRule parses and caches the overworld surface_rule tree.
// The rule tree does not depend on the world seed, so it is loaded once.
func LoadOverworldSurfaceRule() (SurfaceRule, error) {
	surfaceRuleOnce.Do(func() {
		raw, err := dataFS.ReadFile("data/overworld.json")
		if err != nil {
			surfaceRuleErr = err
			return
		}
		var doc struct {
			SurfaceRule json.RawMessage `json:"surface_rule"`
		}
		if err := json.Unmarshal(raw, &doc); err != nil {
			surfaceRuleErr = err
			return
		}
		surfaceRule, surfaceRuleErr = ParseSurfaceRule(doc.SurfaceRule)
	})
	return surfaceRule, surfaceRuleErr
}
