package block

import (
	"github.com/df-mc/dragonfly/server/block/cube"
	"github.com/df-mc/dragonfly/server/block/model"
	"github.com/df-mc/dragonfly/server/item"
	"github.com/df-mc/dragonfly/server/world"
	"github.com/go-gl/mathgl/mgl64"
)

// RedstoneWire is a block that is used to transfer a charge between objects. Charged objects can be used to open doors
// or activate certain items. This block is the placed form of redstone which can be found by mining redstone ore with
// an iron pickaxe or better. Deactivated redstone wire will appear dark red, but activated redstone wire will appear
// bright red with a sparkling particle effect.
type RedstoneWire struct {
	empty
	transparent

	// Power is the current power level of the redstone wire. It ranges from 0 to 15.
	Power int
}

// HasLiquidDrops ...
func (RedstoneWire) HasLiquidDrops() bool {
	return true
}

// BreakInfo ...
func (r RedstoneWire) BreakInfo() BreakInfo {
	return newBreakInfo(0, alwaysHarvestable, nothingEffective, oneOf(RedstoneWire{})).withBreakHandler(func(pos cube.Pos, tx *world.Tx, _ item.User) {
		updateStrongRedstone(pos, tx)
	})
}

// EncodeBlock ...
func (r RedstoneWire) EncodeBlock() (string, map[string]any) {
	return "minecraft:redstone_wire", map[string]any{
		"redstone_signal": int32(r.Power),
	}
}

// EncodeItem ...
func (RedstoneWire) EncodeItem() (name string, meta int16) {
	return "minecraft:redstone", 0
}

// UseOnBlock ...
func (r RedstoneWire) UseOnBlock(pos cube.Pos, face cube.Face, _ mgl64.Vec3, tx *world.Tx, user item.User, ctx *item.UseContext) bool {
	pos, _, used := firstReplaceable(tx, pos, face, r)
	if !used {
		return false
	}
	belowPos := pos.Side(cube.FaceDown)
	if !tx.Block(belowPos).Model().FaceSolid(belowPos, cube.FaceUp, tx) {
		return false
	}
	r.Power = r.calculatePower(pos, tx)
	place(tx, pos, r, user, ctx)
	if placed(ctx) {
		updateStrongRedstone(pos, tx)
		return true
	}
	return false
}

// NeighbourUpdateTick ...
func (r RedstoneWire) NeighbourUpdateTick(pos, neighbour cube.Pos, tx *world.Tx) {
	if pos == neighbour {
		// Ignore the self-update sent after this wire's block state changes.
		return
	}
	below := pos.Side(cube.FaceDown)
	if !tx.Block(below).Model().FaceSolid(below, cube.FaceUp, tx) {
		breakBlock(r, pos, tx)
		return
	}
	if changed, ok := r.updateFromNeighbour(pos, tx); ok && !changed {
		updateStrongRedstone(pos, tx)
	}
}

// RedstoneUpdate ...
func (r RedstoneWire) RedstoneUpdate(pos cube.Pos, tx *world.Tx) {
	r.updatePower(pos, tx)
}

// updateFromNeighbour updates the wire after a neighbour change. changed reports whether the wire's power changed,
// and ok reports whether the update was allowed by the redstone update handler.
func (r RedstoneWire) updateFromNeighbour(pos cube.Pos, tx *world.Tx) (changed bool, ok bool) {
	if redstoneUpdateCancelled(pos, tx) {
		return false, false
	}
	return r.updatePower(pos, tx), true
}

// updatePower recalculates the wire's power and propagates the network when the power changes.
func (r RedstoneWire) updatePower(pos cube.Pos, tx *world.Tx) bool {
	if power := r.calculatePower(pos, tx); r.Power != power {
		r.Power = power
		tx.SetBlock(pos, r, &world.SetOpts{DisableBlockUpdates: true})
		updateStrongRedstone(pos, tx)
		return true
	}
	return false
}

// RedstoneSource ...
func (RedstoneWire) RedstoneSource() bool {
	return false
}

// WeaklyPowersBlocks returns true because powered redstone dust weakly powers conductive blocks it points into or rests on top of.
func (RedstoneWire) WeaklyPowersBlocks() bool {
	return true
}

// WeakPower returns the power emitted by the wire toward a neighbouring receiver. Dust powers upward, never powers
// downward, and only powers horizontal receivers in connected directions. A powered wire with no horizontal
// connections behaves as an unconnected cross and powers every horizontal side.
func (r RedstoneWire) WeakPower(pos cube.Pos, face cube.Face, tx *world.Tx, accountForDust bool) int {
	if !accountForDust {
		return 0
	}
	if face == cube.FaceUp {
		return r.Power
	}
	if face == cube.FaceDown {
		return 0
	}
	if !r.hasHorizontalRedstoneConnection(pos, tx) {
		return r.Power
	}
	if r.connection(pos, face.Opposite(), tx) {
		return r.Power
	}
	if r.connection(pos, face, tx) && !r.connection(pos, face.RotateLeft(), tx) && !r.connection(pos, face.RotateRight(), tx) {
		return r.Power
	}
	return 0
}

// StrongPower returns 0 because redstone dust weakly powers conductive blocks rather than strongly powering them.
func (RedstoneWire) StrongPower(cube.Pos, cube.Face, *world.Tx, bool) int {
	return 0
}

// calculatePower returns the highest level of received redstone power at the provided position.
func (r RedstoneWire) calculatePower(pos cube.Pos, tx *world.Tx) int {
	return calculateRedstoneWirePower(pos, tx, tx.Block)
}

// calculateRedstoneWirePower returns the highest level of received redstone power at the provided position. blockAt is
// injected so direct updates and the BFS wire network share the same rules while the BFS path may still read cached
// node state.
func calculateRedstoneWirePower(pos cube.Pos, tx *world.Tx, blockAt func(cube.Pos) world.Block) int {
	aboveBlocksVerticalTravel := blocksRedstoneWireVerticalTravel(blockAt(pos.Side(cube.FaceUp)))
	var blockPower, wirePower int
	for _, side := range cube.Faces() {
		neighbourPos := pos.Side(side)
		neighbour := blockAt(neighbourPos)

		wirePower = maxRedstoneWirePower(neighbour, wirePower)
		blockPower = max(blockPower, tx.RedstonePower(neighbourPos, side, false))

		if side.Axis() == cube.Y {
			// Only check horizontal neighbours from here on.
			continue
		}

		if canRedstoneWireStepDown(pos, neighbourPos, neighbour, tx) && !aboveBlocksVerticalTravel {
			wirePower = maxRedstoneWirePower(blockAt(neighbourPos.Side(cube.FaceUp)), wirePower)
		}
		if canRedstoneWireStepDown(neighbourPos.Side(cube.FaceDown), neighbourPos, neighbour, tx) && !blocksRedstoneWireVerticalTravel(neighbour) {
			wirePower = maxRedstoneWirePower(blockAt(neighbourPos.Side(cube.FaceDown)), wirePower)
		}

		if _, neighbourSolid := neighbour.Model().(model.Solid); !neighbourSolid {
			wirePower = maxRedstoneWirePower(blockAt(neighbourPos.Side(cube.FaceDown)), wirePower)
		}
	}
	return max(blockPower, wirePower-1)
}

// hasHorizontalRedstoneConnection checks if the dust connects horizontally to redstone wire or a redstone source. It
// does not include passive receivers such as doors, trapdoors, or note blocks.
func (r RedstoneWire) hasHorizontalRedstoneConnection(pos cube.Pos, tx *world.Tx) bool {
	for _, face := range cube.HorizontalFaces() {
		if r.connection(pos, face, tx) {
			return true
		}
	}
	return false
}

// connection returns true if the dust shape connects through the given face to another wire or a redstone source. It
// also accounts for valid one-block vertical wire connections.
func (r RedstoneWire) connection(pos cube.Pos, face cube.Face, tx *world.Tx) bool {
	sidePos := pos.Side(face)
	sideBlock := tx.Block(sidePos)
	if r.connectsAbove(pos, sidePos, sideBlock, tx) || r.connectsTo(sideBlock, true) {
		return true
	}
	return r.connectsBelow(sidePos, sideBlock, tx)
}

// connectsAbove checks if the redstone wire can connect to the block above it.
func (r RedstoneWire) connectsAbove(pos, sidePos cube.Pos, sideBlock world.Block, tx *world.Tx) bool {
	if blocksRedstoneWireVerticalTravel(tx.Block(pos.Side(cube.FaceUp))) || !r.canRunOnTop(tx, sidePos, sideBlock) {
		return false
	}
	return r.connectsTo(tx.Block(sidePos.Side(cube.FaceUp)), false)
}

// connectsBelow checks if the redstone wire can connect to the block below it.
func (r RedstoneWire) connectsBelow(sidePos cube.Pos, sideBlock world.Block, tx *world.Tx) bool {
	_, sideSolid := sideBlock.Model().(model.Solid)
	return !sideSolid && r.connectsTo(tx.Block(sidePos.Side(cube.FaceDown)), false)
}

// connectsTo reports whether a block is part of the redstone wire connection graph. Passive redstone receivers are not
// connections; direct source conductors count only when allowDirectSources is true.
func (RedstoneWire) connectsTo(block world.Block, allowDirectSources bool) bool {
	if _, ok := block.(RedstoneWire); ok {
		return true
	}
	c, ok := block.(world.Conductor)
	return ok && allowDirectSources && c.RedstoneSource()
}

// canRunOnTop checks whether redstone dust can be placed on top of the block.
func (RedstoneWire) canRunOnTop(tx *world.Tx, pos cube.Pos, block world.Block) bool {
	return block.Model().FaceSolid(pos, cube.FaceUp, tx)
}

// blocksRedstoneWireVerticalTravel checks if the block above redstone wire blocks vertical wire travel.
func blocksRedstoneWireVerticalTravel(block world.Block) bool {
	if _, ok := block.Model().(model.Solid); !ok {
		return false
	}
	diffuser, ok := block.(LightDiffuser)
	return !ok || diffuser.LightDiffusionLevel() != 0
}

// canRedstoneWireStepDown checks if redstone dust can provide power while travelling down around the side block.
func canRedstoneWireStepDown(from, side cube.Pos, block world.Block, tx *world.Tx) bool {
	if stepDowner, ok := block.(RedstoneWireStepDowner); ok {
		return stepDowner.CanRedstoneWireStepDown(side, from, tx)
	}
	for _, face := range cube.Faces() {
		if !block.Model().FaceSolid(side, face, tx) {
			return false
		}
	}
	return true
}

// TrimMaterial delegates to item.RedstoneWire so the block form stays valid for smithing trim decoding too.
func (RedstoneWire) TrimMaterial() string {
	return item.RedstoneWire{}.TrimMaterial()
}

// MaterialColour delegates to item.RedstoneWire to keep trim metadata defined in one place.
func (RedstoneWire) MaterialColour() string {
	return item.RedstoneWire{}.MaterialColour()
}

// allRedstoneWires returns a list of all redstone dust states.
func allRedstoneWires() (all []world.Block) {
	for i := range 16 {
		all = append(all, RedstoneWire{Power: i})
	}
	return
}
