package recipe

import (
	_ "embed"

	"github.com/df-mc/dragonfly/server/item"
	"github.com/df-mc/dragonfly/server/world"
	"github.com/sandertv/gophertunnel/minecraft/nbt"
)

var (
	//go:embed crafting_data.nbt
	vanillaCraftingData []byte
	//go:embed smithing_data.nbt
	vanillaSmithingData []byte
	//go:embed smithing_trim_data.nbt
	vanillaSmithingTrimData []byte
	//go:embed potion_data.nbt
	vanillaPotionData []byte
)

// shapedRecipe is a recipe that must be crafted in a specific shape.
type shapedRecipe struct {
	Input    inputItems  `nbt:"input"`
	Output   outputItems `nbt:"output"`
	Block    string      `nbt:"block"`
	Width    int32       `nbt:"width"`
	Height   int32       `nbt:"height"`
	Priority int32       `nbt:"priority"`
}

// shapelessRecipe is a recipe that may be crafted without a strict shape.
type shapelessRecipe struct {
	Input    inputItems  `nbt:"input"`
	Output   outputItems `nbt:"output"`
	Block    string      `nbt:"block"`
	Priority int32       `nbt:"priority"`
}

// potionRecipe is a recipe that may be crafted in a brewing stand.
type potionRecipe struct {
	Input   inputItem  `nbt:"input"`
	Reagent inputItem  `nbt:"reagent"`
	Output  outputItem `nbt:"output"`
}

// potionContainerChangeRecipe is a recipe that may be crafted in a brewing stand.
type potionContainerChangeRecipe struct {
	Input   string    `nbt:"input"`
	Reagent inputItem `nbt:"reagent"`
	Output  string    `nbt:"output"`
}

// registerVanilla can be called to register all vanilla recipes from the generated data files.
// noinspection GoUnusedFunction
//
//lint:ignore U1000 Function is used through compiler directives.
func registerVanilla() {
	var craftingRecipes struct {
		Shaped    []shapedRecipe    `nbt:"shaped"`
		Shapeless []shapelessRecipe `nbt:"shapeless"`
	}
	if err := nbt.Unmarshal(vanillaCraftingData, &craftingRecipes); err != nil {
		panic(err)
	}

	for _, s := range craftingRecipes.Shapeless {
		input, ok := s.Input.Items()
		output, okTwo := s.Output.Stacks()
		if !ok || !okTwo {
			// This can be expected to happen, as some recipes contain blocks or items that aren't currently implemented.
			continue
		}
		Register(Shapeless{recipe{
			input:    input,
			output:   output,
			block:    s.Block,
			priority: uint32(s.Priority),
		}})
	}

	for _, s := range craftingRecipes.Shaped {
		input, ok := s.Input.Items()
		output, okTwo := s.Output.Stacks()
		if !ok || !okTwo {
			// This can be expected to happen - refer to the comment above.
			continue
		}
		Register(Shaped{
			shape: Shape{int(s.Width), int(s.Height)},
			recipe: recipe{
				input:    input,
				output:   output,
				block:    s.Block,
				priority: uint32(s.Priority),
			},
		})
	}

	var smithingRecipes []shapelessRecipe
	if err := nbt.Unmarshal(vanillaSmithingData, &smithingRecipes); err != nil {
		panic(err)
	}

	for _, s := range smithingRecipes {
		input, ok := s.Input.Items()
		output, okTwo := s.Output.Stacks()
		if !ok || !okTwo {
			// This can be expected to happen - refer to the comment above.
			continue
		}
		Register(SmithingTransform{recipe{
			input:    input,
			output:   output,
			block:    s.Block,
			priority: uint32(s.Priority),
		}})
	}

	var smithingTrimRecipes []shapelessRecipe
	if err := nbt.Unmarshal(vanillaSmithingTrimData, &smithingTrimRecipes); err != nil {
		panic(err)
	}

	for _, s := range smithingTrimRecipes {
		input, ok := s.Input.Items()
		if !ok {
			// This can be expected to happen - refer to the comment above.
			continue
		}
		Register(SmithingTrim{recipe{
			input:    input,
			block:    s.Block,
			priority: uint32(s.Priority),
		}})
	}

	var potionRecipes struct {
		Potions          []potionRecipe                `nbt:"potions"`
		ContainerChanges []potionContainerChangeRecipe `nbt:"container_changes"`
	}

	if err := nbt.Unmarshal(vanillaPotionData, &potionRecipes); err != nil {
		panic(err)
	}

	for _, r := range potionRecipes.Potions {
		input, ok := r.Input.Item()
		reagent, okTwo := r.Reagent.Item()
		output, okThree := r.Output.Stack()
		if !ok || !okTwo || !okThree {
			// This can be expected to happen - refer to the comment above.
			continue
		}

		Register(Potion{recipe{
			input:  []Item{input, reagent},
			output: []item.Stack{output},
			block:  "brewing_stand",
		}})
	}

	for _, c := range potionRecipes.ContainerChanges {
		input, ok := world.ItemByName(c.Input, 0)
		reagent, okTwo := c.Reagent.Item()
		output, okThree := world.ItemByName(c.Output, 0)
		if !ok || !okTwo || !okThree {
			// This can be expected to happen - refer to the comment above.
			continue
		}

		Register(PotionContainerChange{recipe{
			input:  []Item{item.NewStack(input, 1), reagent},
			output: []item.Stack{item.NewStack(output, 1)},
			block:  "brewing_stand",
		}})
	}

	// Register dynamic recipes
	RegisterDynamic(NewDecoratedPotRecipe())
}
