package messagewall

import (
	"fmt"
	"sort"
)

const (
	versionV1        = 1
	unitGrid         = "grid"
	fitModeCover     = "cover"
	fitModeContain   = "contain"
	defaultDuration  = 20
	defaultLoadTime  = 15
	defaultOnError   = "skip"
)

func Resolve(request ResolveRequest) (ResolveResult, error) {
	if err := Validate(request.Layout); err != nil {
		return ResolveResult{}, err
	}

	result := ResolveResult{
		Version:         request.Layout.Version,
		CoordinateSpace: request.Layout.CoordinateSpace,
		FitMode:         request.Layout.FitMode,
		Scenes:          make([]ResolvedScene, 0, len(request.Layout.Slots)),
	}

	duration := request.DurationSeconds
	if duration <= 0 {
		duration = defaultDuration
	}

	loadTimeout := request.LoadTimeoutSeconds
	if loadTimeout <= 0 {
		loadTimeout = defaultLoadTime
	}

	onError := request.OnError
	if onError == "" {
		onError = defaultOnError
	}

	for _, slot := range request.Layout.Slots {
		result.Scenes = append(result.Scenes, ResolvedScene{
			SlotID:             slot.SlotID,
			Source:             request.Source,
			DurationSeconds:    duration,
			LoadTimeoutSeconds: loadTimeout,
			OnError:            onError,
			Crop: Crop{
				X:      slot.X,
				Y:      slot.Y,
				Width:  slot.Width,
				Height: slot.Height,
				Unit:   request.Layout.CoordinateSpace.Unit,
			},
		})
	}

	sort.Slice(result.Scenes, func(i, j int) bool {
		return result.Scenes[i].SlotID < result.Scenes[j].SlotID
	})

	return result, nil
}

func Validate(layout Layout) error {
	if layout.Version != versionV1 {
		return fmt.Errorf("unsupported layout version: %d", layout.Version)
	}

	if layout.CoordinateSpace.Width <= 0 || layout.CoordinateSpace.Height <= 0 {
		return fmt.Errorf("coordinate_space width and height must be positive")
	}

	if layout.CoordinateSpace.Unit != unitGrid {
		return fmt.Errorf("unsupported coordinate_space unit: %s", layout.CoordinateSpace.Unit)
	}

	if layout.FitMode != fitModeCover && layout.FitMode != fitModeContain {
		return fmt.Errorf("unsupported fit_mode: %s", layout.FitMode)
	}

	if len(layout.Slots) == 0 {
		return fmt.Errorf("layout must contain at least one slot")
	}

	seen := make(map[string]struct{}, len(layout.Slots))
	for i, slot := range layout.Slots {
		if slot.SlotID == "" {
			return fmt.Errorf("slot %d has empty slot_id", i)
		}

		if _, ok := seen[slot.SlotID]; ok {
			return fmt.Errorf("duplicate slot_id: %s", slot.SlotID)
		}
		seen[slot.SlotID] = struct{}{}

		if slot.Width <= 0 || slot.Height <= 0 {
			return fmt.Errorf("slot %s must have positive width and height", slot.SlotID)
		}

		if slot.X < 0 || slot.Y < 0 {
			return fmt.Errorf("slot %s must not start outside coordinate space", slot.SlotID)
		}

		if slot.X+slot.Width > layout.CoordinateSpace.Width {
			return fmt.Errorf("slot %s exceeds coordinate_space width", slot.SlotID)
		}

		if slot.Y+slot.Height > layout.CoordinateSpace.Height {
			return fmt.Errorf("slot %s exceeds coordinate_space height", slot.SlotID)
		}
	}

	for i := range layout.Slots {
		for j := i + 1; j < len(layout.Slots); j++ {
			if overlaps(layout.Slots[i], layout.Slots[j]) {
				return fmt.Errorf("slots %s and %s overlap", layout.Slots[i].SlotID, layout.Slots[j].SlotID)
			}
		}
	}

	return nil
}

func overlaps(a, b Slot) bool {
	return a.X < b.X+b.Width && a.X+a.Width > b.X && a.Y < b.Y+b.Height && a.Y+a.Height > b.Y
}
