package systems

import (
	"log/slog"
	"openquell/components"
	. "openquell/components"
	. "openquell/config"

	"openquell/grid"
	"openquell/observers"

	"github.com/hajimehoshi/ebiten/v2"
	"github.com/mlange-42/arche/ecs"
	"github.com/mlange-42/arche/generic"
)

type DestroyableSystem struct {
	World         *ecs.World
	Selector      *generic.Filter3[Position, Renderable, Destroyable]
	GridContainer *grid.GridContainer
	SolidMapper   generic.Map4[ // needed for replacement
		components.Position,
		components.Renderable,
		components.Tilish,
		components.Solid]
	Cellsize int
}

type DoorToRemove struct {
	Entity   ecs.Entity
	Position *components.Position
}

func NewDestroyableSystem(world *ecs.World, gridcontainer *grid.GridContainer,
	cellsize int) System {

	solidmapper := generic.NewMap4[
		components.Position,
		components.Renderable,
		components.Tilish,
		components.Solid](world)

	system := &DestroyableSystem{
		Selector:      generic.NewFilter3[Position, Renderable, Destroyable](),
		World:         world,
		GridContainer: gridcontainer,
		SolidMapper:   solidmapper,
		Cellsize:      cellsize,
	}

	return system
}

func (system *DestroyableSystem) Update() error {
	observer := observers.GetGameObserver(system.World)
	posID := ecs.ComponentID[components.Position](system.World)
	veloID := ecs.ComponentID[components.Velocity](system.World)

	query := system.Selector.Query(system.World)

	EntitiestoRemove := []*DoorToRemove{}

	for query.Next() {
		doorposition, renderable, door := query.Get()

		for _, player := range observer.GetPlayers() {
			if !system.World.Alive(player) {
				continue
			}

			playerposition := (*Position)(system.World.Get(player, posID))
			playervelocity := (*Velocity)(system.World.Get(player, veloID))

			ok, newpos := doorposition.Intersects(playerposition, playervelocity)
			if ok {
				// player bumped into hidden wall, activate it, snap in player
				slog.Debug("bump not die", "originalpos", playerposition)
				playervelocity.Change(Stop)
				playerposition.Set(newpos)

				if door.Activated {
					// player bumps into the door a second time, now hide it
					EntitiestoRemove = append(EntitiestoRemove,
						&DoorToRemove{Entity: query.Entity(), Position: doorposition})
				} else {
					slog.Debug("activating destroyable", "doorpos", doorposition)
					door.Activated = true
					renderable.Damaged = 1
				}
			}
		}
	}

	for _, door := range EntitiestoRemove {
		slog.Debug("hiding destroyable", "doorpos", door.Position.Point())

		// remove door entity
		system.World.RemoveEntity(door.Entity)
	}

	return nil
}

func (system *DestroyableSystem) Draw(screen *ebiten.Image) {
	// write transients (these are no tiles!)
	op := &ebiten.DrawRectShaderOptions{}

	query := system.Selector.Query(system.World)

	for query.Next() {
		pos, render, _ := query.Get()

		op.GeoM.Reset()

		op.Uniforms = map[string]any{
			"Damaged": render.Damaged,
		}

		op.Images[0] = render.Image
		op.Images[1] = render.DamageImage

		op.GeoM.Translate(float64(pos.X), float64(pos.Y))

		screen.DrawRectShader(system.Cellsize, system.Cellsize, render.Shader, op)
	}
}
