package world

import (
	"encoding/json"
	"fmt"

	"regionio/internal/worldgen"
)

type rawBlockPredicate struct {
	Type       string            `json:"type"`
	Offset     [3]int            `json:"offset"`
	Blocks     json.RawMessage   `json:"blocks"`
	Tag        string            `json:"tag"`
	Fluids     json.RawMessage   `json:"fluids"`
	Predicate  json.RawMessage   `json:"predicate"`
	Predicates []json.RawMessage `json:"predicates"`
}

func (r *decorationRegion) testBlockPredicate(set *worldgen.FeatureSet, raw json.RawMessage, position worldgen.FeaturePosition) (bool, error) {
	var predicate rawBlockPredicate
	if err := json.Unmarshal(raw, &predicate); err != nil {
		return false, fmt.Errorf("world: decode block predicate: %w", err)
	}
	position.X += predicate.Offset[0]
	position.Y += predicate.Offset[1]
	position.Z += predicate.Offset[2]
	switch predicate.Type {
	case "minecraft:true":
		return true, nil
	case "minecraft:solid":
		state := r.getBlock(position.X, position.Y, position.Z)
		return state != StateAir && stateFlags(state)&flagBlocksMotion != 0, nil
	case "minecraft:inside_world_bounds":
		return position.Y >= MinY && position.Y < MinY+WorldHeight, nil
	case "minecraft:matching_blocks":
		names, err := decodeStringList(predicate.Blocks)
		if err != nil {
			return false, fmt.Errorf("world: matching_blocks: %w", err)
		}
		state, ok := stateByID(r.getBlock(position.X, position.Y, position.Z))
		if !ok {
			return false, nil
		}
		for _, name := range names {
			if state.Name == name {
				return true, nil
			}
		}
		return false, nil
	case "minecraft:matching_block_tag":
		if predicate.Tag == "" {
			return false, fmt.Errorf("world: matching_block_tag missing tag")
		}
		state, ok := stateByID(r.getBlock(position.X, position.Y, position.Z))
		if !ok {
			return false, nil
		}
		for _, name := range flattenBlockTag(set, predicate.Tag, nil) {
			if state.Name == name {
				return true, nil
			}
		}
		return false, nil
	case "minecraft:matching_fluids":
		fluids, err := decodeStringList(predicate.Fluids)
		if err != nil {
			return false, fmt.Errorf("world: matching_fluids: %w", err)
		}
		state := r.getBlock(position.X, position.Y, position.Z)
		for _, fluid := range fluids {
			if stateFlags(state)&flagFluid != 0 &&
				(fluid == "minecraft:water" || fluid == "minecraft:flowing_water") && isWaterState(state) {
				return true, nil
			}
			if stateFlags(state)&flagFluid != 0 &&
				(fluid == "minecraft:lava" || fluid == "minecraft:flowing_lava") && isLavaState(state) {
				return true, nil
			}
		}
		return false, nil
	case "minecraft:all_of":
		for _, child := range predicate.Predicates {
			ok, err := r.testBlockPredicate(set, child, position)
			if err != nil || !ok {
				return ok, err
			}
		}
		return true, nil
	case "minecraft:any_of":
		for _, child := range predicate.Predicates {
			ok, err := r.testBlockPredicate(set, child, position)
			if err != nil {
				return false, err
			}
			if ok {
				return true, nil
			}
		}
		return false, nil
	case "minecraft:not":
		if len(predicate.Predicate) == 0 {
			return false, fmt.Errorf("world: not predicate missing child")
		}
		ok, err := r.testBlockPredicate(set, predicate.Predicate, position)
		return !ok, err
	default:
		return false, fmt.Errorf("world: unsupported block predicate %q", predicate.Type)
	}
}

func decodeStringList(raw json.RawMessage) ([]string, error) {
	var one string
	if err := json.Unmarshal(raw, &one); err == nil {
		return []string{one}, nil
	}
	var many []string
	if err := json.Unmarshal(raw, &many); err != nil || len(many) == 0 {
		return nil, fmt.Errorf("invalid string list %s", raw)
	}
	return many, nil
}

func isWaterState(state uint16) bool {
	value, ok := stateByID(state)
	return ok && stateFlags(state)&flagFluid != 0 && value.Name != "minecraft:lava"
}

func isLavaState(state uint16) bool {
	value, ok := stateByID(state)
	return ok && value.Name == "minecraft:lava"
}
