package main

import (
	"fmt"
	"log/slog"
	"strconv"
	"strings"

	"github.com/disgoorg/disgo/discord"
	"github.com/disgoorg/disgo/handler"

	"git.sunturtle.xyz/zephyr/horse"
	"git.sunturtle.xyz/zephyr/horse/cmd/horsebot/autocomplete"
)

type skillServer struct {
	skills  map[horse.SkillID]horse.Skill
	byName  map[string]horse.SkillID
	groups  map[horse.SkillGroupID]horse.SkillGroup
	autocom autocomplete.Set[discord.AutocompleteChoice]
}

func newSkillServer(skills []horse.Skill, groups []horse.SkillGroup) *skillServer {
	s := skillServer{
		skills: make(map[horse.SkillID]horse.Skill, len(skills)),
		byName: make(map[string]horse.SkillID, len(skills)),
		groups: make(map[horse.SkillGroupID]horse.SkillGroup, len(groups)),
	}
	for _, skill := range skills {
		s.skills[skill.ID] = skill
		id := strconv.Itoa(int(skill.ID))
		switch {
		case skill.UniqueOwner == "":
			s.byName[skill.Name] = skill.ID
			s.autocom.Add(skill.Name, discord.AutocompleteChoiceString{Name: skill.Name, Value: id})
		case skill.Rarity >= 3:
			s.byName[skill.Name] = skill.ID
			s.autocom.Add(skill.Name, discord.AutocompleteChoiceString{Name: skill.Name, Value: id})
			s.autocom.Add(skill.UniqueOwner, discord.AutocompleteChoiceString{Name: "Unique: " + skill.UniqueOwner, Value: id})
		default:
			name := skill.Name + " (Inherited)"
			s.byName[name] = skill.ID
			s.autocom.Add(name, discord.AutocompleteChoiceString{Name: name, Value: id})
			s.autocom.Add(skill.UniqueOwner, discord.AutocompleteChoiceString{Name: "Inherited unique: " + skill.UniqueOwner, Value: id})
		}
	}
	for _, g := range groups {
		s.groups[g.ID] = g
	}
	return &s
}

func (s *skillServer) slash(data discord.SlashCommandInteractionData, e *handler.CommandEvent) error {
	q := data.String("query")
	id, err := strconv.ParseInt(q, 10, 32)
	if err == nil {
		// note inverted condition; this is when we have an id
		id = int64(s.skills[horse.SkillID(id)].ID)
	}
	if id == 0 {
		// Either we weren't given a number or the number doesn't match any skill ID.
		v := s.byName[q]
		if v == 0 {
			// No such skill.
			m := discord.MessageCreate{
				Content: "No such skill.",
				Flags:   discord.MessageFlagEphemeral,
			}
			return e.CreateMessage(m)
		}
		id = int64(v)
	}
	m := discord.MessageCreate{
		Components: []discord.LayoutComponent{s.render(horse.SkillID(id))},
		Flags:      discord.MessageFlagIsComponentsV2,
	}
	if !data.Bool("share") {
		m.Flags |= discord.MessageFlagEphemeral
	}
	return e.CreateMessage(m)
}

func (s *skillServer) autocomplete(e *handler.AutocompleteEvent) error {
	q := e.Data.String("query")
	opts := s.autocom.Find(nil, q)
	return e.AutocompleteResult(opts[:min(len(opts), 25)])
}

func (s *skillServer) button(data discord.ButtonInteractionData, e *handler.ComponentEvent) error {
	id, err := strconv.ParseInt(e.Vars["id"], 10, 32)
	if err != nil {
		m := discord.MessageCreate{
			Content: "That button produced an invalid skill ID. That's not supposed to happen.",
			Flags:   discord.MessageFlagEphemeral,
		}
		return e.CreateMessage(m)
	}
	m := discord.MessageUpdate{
		Components: &[]discord.LayoutComponent{s.render(horse.SkillID(id))},
	}
	return e.UpdateMessage(m)
}

func (s *skillServer) menu(data discord.SelectMenuInteractionData, e *handler.ComponentEvent) error {
	d, ok := data.(discord.StringSelectMenuInteractionData)
	if !ok {
		return fmt.Errorf("wrong select menu type %T", data)
	}
	if len(d.Values) != 1 {
		return fmt.Errorf("wrong number of values: %q", d.Values)
	}
	id, err := strconv.ParseInt(d.Values[0], 10, 32)
	if err != nil {
		return err
	}
	m := discord.MessageUpdate{
		Components: &[]discord.LayoutComponent{s.render(horse.SkillID(id))},
	}
	return e.UpdateMessage(m)
}

func (s *skillServer) render(id horse.SkillID) discord.ContainerComponent {
	skill, ok := s.skills[id]
	if !ok {
		slog.Error("invalid skill id", slog.Int("id", int(id)))
		return discord.NewContainer(discord.NewTextDisplayf("invalid skill ID %v made it to render", id))
	}

	top := "### " + skill.Name
	if skill.UniqueOwner != "" {
		top += "\n-# " + skill.UniqueOwner
	}
	r := discord.NewContainer(
		discord.NewTextDisplay(top),
		discord.NewSmallSeparator(),
	)
	var skilltype string
	switch {
	case skill.Rarity == 3, skill.Rarity == 4, skill.Rarity == 5:
		// unique of various star levels
		r.AccentColor = 0xaca4d4
		skilltype = "Unique Skill"
	case skill.UniqueOwner != "":
		r.AccentColor = 0xcccccc
		skilltype = "Inherited Unique"
	case skill.Rarity == 2:
		// rare (gold)
		r.AccentColor = 0xd7c25b
		skilltype = "Rare Skill"
	case skill.GroupRate == -1:
		// negative (purple) skill
		r.AccentColor = 0x9151d4
		skilltype = "Negative Skill"
	case !skill.WitCheck:
		// should be passive (green)
		r.AccentColor = 0x66ae1c
		skilltype = "Passive Skill"
	case isDebuff(skill):
		// debuff (red)
		r.AccentColor = 0xe34747
		skilltype = "Debuff Skill"
	case skill.Rarity == 1:
		// common (white)
		r.AccentColor = 0xcccccc
		skilltype = "Common Skill"
	}
	text := make([]string, 0, 3)
	abils := make([]string, 0, 3)
	for _, act := range skill.Activations {
		text, abils = text[:0], abils[:0]
		if act.Precondition != "" {
			text = append(text, "Precondition: "+formatCondition(act.Precondition))
		}
		text = append(text, "Condition: "+formatCondition(act.Condition))
		var t string
		switch {
		case act.Duration <= 0:
			// passive or instantaneous, e.g. recoveries; do nothing
		case act.Duration >= 500e4:
			t = "Permanent "
		case act.DurScale == horse.DurationDirect:
			t = "For " + act.Duration.String() + "s, "
		default:
			t = "For " + act.Duration.String() + "s " + act.DurScale.String() + ", "
		}
		for _, a := range act.Abilities {
			abils = append(abils, a.String())
		}
		t += strings.Join(abils, ", ")
		if act.Cooldown > 0 && act.Cooldown < 500e4 {
			t += " on " + act.Cooldown.String() + "s cooldown"
		}
		text = append(text, t)
		r.Components = append(r.Components, discord.NewTextDisplay(strings.Join(text, "\n")))
	}

	l := discord.NewTextDisplayf("%s ・ SP cost %d ・ Grade value %d ・ [Conditions on GameTora](https://gametora.com/umamusume/skill-condition-viewer?skill=%d)", skilltype, skill.SPCost, skill.GradeValue, skill.ID)
	r.Components = append(r.Components, discord.NewSmallSeparator(), l)
	rel := make([]horse.Skill, 0, 4)
	group := s.groups[skill.Group]
	for _, id := range [...]horse.SkillID{group.Skill1, group.Skill2, group.Skill3, group.SkillBad} {
		if id != 0 {
			rel = append(rel, s.skills[id])
		}
	}
	if len(rel) > 1 {
		opts := make([]discord.StringSelectMenuOption, 0, 4)
		for _, rs := range rel {
			name := rs.Name
			emoji := "⚪"
			switch {
			case rs.Rarity == 3, rs.Rarity == 4, rs.Rarity == 5:
				emoji = "🟠"
			case rs.UniqueOwner != "":
				name += " (Inherited)"
			case rs.Rarity == 2:
				emoji = "🟡"
			case rs.GroupRate == -1:
				emoji = "🟣"
			}
			b := discord.NewStringSelectMenuOption(name, strconv.Itoa(int(rs.ID))).WithEmoji(discord.NewComponentEmoji(emoji))
			if rs.ID == skill.ID {
				b = b.WithDefault(true)
			}
			opts = append(opts, b)
		}
		row := discord.NewActionRow(discord.NewStringSelectMenu("/skill/swap", "Related skills", opts...))
		r.Components = append(r.Components, row)
	}
	return r
}

func formatCondition(s string) string {
	s = strings.ReplaceAll(s, "&", " & ")
	if strings.ContainsRune(s, '@') {
		return "```\n" + strings.ReplaceAll(s, "@", "\n@\n") + "```"
	}
	return "`" + s + "`"
}

func isDebuff(s horse.Skill) bool {
	for _, act := range s.Activations {
		for _, a := range act.Abilities {
			if a.Value < 0 {
				return true
			}
		}
	}
	return false
}
