package plugin

import (
	"fmt"
	"sort"
	"strings"
)

func (p *AdventurePlugin) handleDnDAbilitiesCmd(ctx MessageContext) error {
	c, err := LoadDnDCharacter(ctx.Sender)
	if err != nil {
		return p.SendDM(ctx.Sender, "Couldn't load your character: "+err.Error())
	}
	if c == nil || c.PendingSetup {
		return p.SendDM(ctx.Sender, "No Adv 2.0 character yet — run `!setup` (or just enter combat and we'll auto-build one).")
	}
	ri, _ := raceInfo(c.Race)
	ci, _ := classInfo(c.Class)
	ab := dndClassAbilities[c.Class]

	var b strings.Builder
	b.WriteString(fmt.Sprintf("**%s %s — Abilities**\n\n", ri.Display, ci.Display))
	b.WriteString(fmt.Sprintf("**Class passive — %s**\n  %s\n\n", ab.Name, ab.Description))
	b.WriteString(fmt.Sprintf("**Race trait**\n  %s\n", ri.Passive))

	// Active abilities (Phase 6 + Phase 10 subclass-gated)
	actives := characterActiveAbilities(c)
	if len(actives) > 0 {
		resType, _ := classResourceMax(c.Class)
		cur, max, _ := getResource(c.UserID, resType)
		b.WriteString(fmt.Sprintf("\n**Active abilities** (%s %d/%d)\n", resType, cur, max))
		for _, a := range actives {
			b.WriteString(fmt.Sprintf("  • **%s** (1 %s) — %s\n", a.Name, a.Resource, a.Description))
		}
		b.WriteString("\nUse `!arm <ability>` to ready one for your next combat. Refreshes on long rest.\n")
		if c.ArmedAbility != "" {
			b.WriteString(fmt.Sprintf("\n_Currently armed: **%s**_\n", displayAbility(c.ArmedAbility)))
		}
	}
	return p.SendDM(ctx.Sender, b.String())
}

// !sheet — read-only D&D character sheet renderer.
//
// Sources:
//   dnd_character (D&D layer)
//   player_meta (skills, pet, housing — for at-a-glance context)
//   adventure_equipment (current gear)
//   adventure_treasures (= attunement substrate per v1.1 §7.4)

func (p *AdventurePlugin) handleDnDSheetCmd(ctx MessageContext) error {
	c, err := LoadDnDCharacter(ctx.Sender)
	if err != nil {
		return p.SendDM(ctx.Sender, "Couldn't load your sheet: "+err.Error())
	}
	if c == nil || c.PendingSetup {
		return p.SendDM(ctx.Sender,
			"You don't have an Adv 2.0 character yet.\n\nRun `!setup` to begin character creation. "+
				"Your existing adventure progress (skills, pets, coins, arena streak) will be preserved.")
	}

	advChar, _ := loadAdvCharacter(ctx.Sender)
	equip, _ := loadAdvEquipment(ctx.Sender)
	treasures, _ := loadAdvTreasureBonuses(ctx.Sender)
	meta, _ := loadPlayerMeta(ctx.Sender)
	house, _ := loadHouseState(ctx.Sender)
	magicEquip, _ := loadEquippedMagicItems(ctx.Sender)

	return p.SendDM(ctx.Sender, renderDnDSheet(c, advChar, meta, house, equip, treasures, magicEquip))
}

func renderDnDSheet(c *DnDCharacter, adv *AdventureCharacter, meta *PlayerMeta, house HouseState, equip map[EquipmentSlot]*AdvEquipment, treasures []AdvTreasureBonus, magicEquip map[DnDSlot]EquippedMagicItem) string {
	ri, _ := raceInfo(c.Race)
	ci, _ := classInfo(c.Class)
	mods := c.Modifiers()

	var b strings.Builder
	name := string(c.UserID)
	if adv != nil && adv.DisplayName != "" {
		name = adv.DisplayName
	}
	renownLevel := 0
	if adv != nil {
		renownLevel = adv.RenownLevel()
	}
	nameLine := name
	if m := renownMarker(renownLevel); m != "" {
		nameLine = fmt.Sprintf("%s %s", name, m)
	}
	b.WriteString(fmt.Sprintf("⚔️ **%s** — Level %d %s %s\n", nameLine, c.Level, ri.Display, ci.Display))
	if renownLevel > 0 {
		if rank := renownRankFor(renownLevel); rank != "" {
			b.WriteString(fmt.Sprintf("    _Renown %d — %s_\n", renownLevel, rank))
		}
	}
	if c.Subclass != "" {
		if si, ok := subclassInfo(c.Subclass); ok {
			b.WriteString(fmt.Sprintf("    _%s_\n", si.Display))
		}
	} else if c.Level >= dndSubclassMinLevel {
		b.WriteString("    _Subclass: unchosen — run `!subclass`_\n")
	}
	b.WriteString(strings.Repeat("─", 36) + "\n")

	// Vitals
	b.WriteString(fmt.Sprintf("**HP** %d/%d", c.HPCurrent, c.HPMax))
	if c.TempHP > 0 {
		b.WriteString(fmt.Sprintf(" (+%d temp)", c.TempHP))
	}
	b.WriteString(fmt.Sprintf("    **AC** %d", c.ArmorClass))
	if c.Level >= dndMaxLevel {
		b.WriteString("    **XP** capped (L20)\n")
	} else {
		b.WriteString(fmt.Sprintf("    **XP** %d / %d (next L%d)\n",
			c.XP, dndXPToNextLevel(c.Level), c.Level+1))
	}

	// Ability scores with modifiers
	b.WriteString(fmt.Sprintf("STR %2d (%+d)   DEX %2d (%+d)   CON %2d (%+d)\n",
		c.STR, mods[0], c.DEX, mods[1], c.CON, mods[2]))
	b.WriteString(fmt.Sprintf("INT %2d (%+d)   WIS %2d (%+d)   CHA %2d (%+d)\n",
		c.INT, mods[3], c.WIS, mods[4], c.CHA, mods[5]))
	b.WriteString(fmt.Sprintf("\n_%s_\n", ri.Passive))

	// Equipment — D&D 10-slot view, with rarity inferred from legacy fields.
	b.WriteString("\n**Equipment**\n")
	dndEquip := map[DnDSlot]*AdvEquipment{}
	for _, slot := range allSlots {
		eq, ok := equip[slot]
		if !ok || eq == nil {
			continue
		}
		dndEquip[mapLegacySlot(slot)] = eq
	}
	anyEquipped := false
	for _, ds := range dndSlotOrder {
		eq := dndEquip[ds]
		if eq == nil {
			continue
		}
		anyEquipped = true
		rarity := equipmentRarity(eq)
		tag := ""
		if eq.Masterwork {
			tag = " ★"
		}
		b.WriteString(fmt.Sprintf("  %s %-9s T%d  %d%%%s  %s _(%s)_\n",
			rarityIcon(rarity), string(ds), eq.Tier, eq.Condition, tag, eq.Name, rarity))
	}
	if !anyEquipped {
		b.WriteString("  _(none equipped)_\n")
	}

	// Magic items — registry items worn in the D&D slots (separate from the
	// legacy tier-gear above). Attunement items mark themselves "(inactive)"
	// when worn but not bonded — they grant nothing until the player frees
	// an attunement slot via `!adventure equip-magic`.
	if len(magicEquip) > 0 {
		attunedNow := countAttunedMagicItems(magicEquip)
		b.WriteString("\n**Magic Items**\n")
		for _, ds := range dndSlotOrder {
			e, ok := magicEquip[ds]
			if !ok || e.Item.ID == "" {
				continue
			}
			status := ""
			if e.Item.Attunement {
				switch {
				case e.Attuned:
					status = fmt.Sprintf(" — bonded (%d/%d)", attunedNow, dndMagicItemAttuneLimit)
				case attunedNow >= dndMagicItemAttuneLimit:
					status = " — **(inactive)** _bond cap full_"
				default:
					status = " — **(inactive)** _unbonded_"
				}
			}
			mi := e.Effective()
			temperMark := ""
			if e.Temper > 0 {
				temperMark = " 🔨"
			}
			b.WriteString(fmt.Sprintf("  %s %-9s %s%s _(%s)_%s\n    %s\n",
				rarityIcon(mi.Rarity), string(ds), mi.Name, temperMark, mi.Rarity,
				status, magicItemEffectSummary(mi)))
		}
	}

	// Treasure bonuses (re-using adventure_treasures per v1.1 §7.4).
	// Player-facing label kept distinct from the magic-item "bond" vocab
	// so the sheet doesn't read as two competing attunement systems.
	if len(treasures) > 0 {
		b.WriteString("\n**Treasures**\n")
		// Group by treasure_key — one treasure can have multiple bonuses.
		byKey := map[string][]AdvTreasureBonus{}
		var keys []string
		for _, t := range treasures {
			if _, seen := byKey[t.TreasureKey]; !seen {
				keys = append(keys, t.TreasureKey)
			}
			byKey[t.TreasureKey] = append(byKey[t.TreasureKey], t)
		}
		sort.Strings(keys)
		for _, k := range keys {
			bonuses := byKey[k]
			name := bonuses[0].Name
			parts := make([]string, 0, len(bonuses))
			for _, bn := range bonuses {
				parts = append(parts, fmt.Sprintf("%s %+.1f", bn.BonusType, bn.BonusValue))
			}
			b.WriteString(fmt.Sprintf("  • %s — %s\n", name, strings.Join(parts, ", ")))
		}
	}

	// Legacy adventure context — preserved progress at a glance
	if adv != nil {
		b.WriteString("\n**Adventure progress** _(preserved)_\n")
		b.WriteString(fmt.Sprintf("  Mining %d  Foraging %d  Fishing %d\n",
			adv.MiningSkill, adv.ForagingSkill, adv.FishingSkill))
		wins, losses := adv.ArenaWins, adv.ArenaLosses
		if meta != nil {
			wins, losses = meta.ArenaWins, meta.ArenaLosses
		}
		b.WriteString(fmt.Sprintf("  Arena %dW/%dL   Streak %d (best %d)\n",
			wins, losses, adv.CurrentStreak, adv.BestStreak))
		if adv.PetName != "" {
			b.WriteString(fmt.Sprintf("  Pet: %s the %s (lv %d)\n", adv.PetName, adv.PetType, adv.PetLevel))
		}
		if adv.Pet2Name != "" {
			b.WriteString(fmt.Sprintf("  Pet: %s the %s (lv %d)\n", adv.Pet2Name, adv.Pet2Type, adv.Pet2Level))
		}
		if house.Tier > 0 {
			b.WriteString(fmt.Sprintf("  Housing: tier %d\n", house.Tier))
		}
	}

	return b.String()
}
