package storage import ( "context" "errors" "fmt" "github.com/jackc/pgx/v5" "github.com/jackc/pgx/v5/pgxpool" "github.com/denisovdennis/autohero/internal/model" ) // GearStore handles all gear CRUD operations against PostgreSQL. type GearStore struct { pool *pgxpool.Pool } // NewGearStore creates a new GearStore backed by the given connection pool. func NewGearStore(pool *pgxpool.Pool) *GearStore { return &GearStore{pool: pool} } // CreateItem inserts a new gear item into the database and populates item.ID. func (s *GearStore) CreateItem(ctx context.Context, item *model.GearItem) error { err := s.pool.QueryRow(ctx, ` INSERT INTO gear (slot, form_id, name, subtype, rarity, ilvl, base_primary, primary_stat, stat_type, speed_modifier, crit_chance, agility_bonus, set_name, special_effect) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14) RETURNING id `, string(item.Slot), item.FormID, item.Name, item.Subtype, string(item.Rarity), item.Ilvl, item.BasePrimary, item.PrimaryStat, item.StatType, item.SpeedModifier, item.CritChance, item.AgilityBonus, item.SetName, item.SpecialEffect, ).Scan(&item.ID) if err != nil { return fmt.Errorf("create gear item: %w", err) } return nil } // GetItem loads a single gear item by ID. Returns (nil, nil) if not found. func (s *GearStore) GetItem(ctx context.Context, id int64) (*model.GearItem, error) { var item model.GearItem var slot, rarity string err := s.pool.QueryRow(ctx, ` SELECT id, slot, form_id, name, subtype, rarity, ilvl, base_primary, primary_stat, stat_type, speed_modifier, crit_chance, agility_bonus, set_name, special_effect FROM gear WHERE id = $1 `, id).Scan( &item.ID, &slot, &item.FormID, &item.Name, &item.Subtype, &rarity, &item.Ilvl, &item.BasePrimary, &item.PrimaryStat, &item.StatType, &item.SpeedModifier, &item.CritChance, &item.AgilityBonus, &item.SetName, &item.SpecialEffect, ) if err != nil { if errors.Is(err, pgx.ErrNoRows) { return nil, nil } return nil, fmt.Errorf("get gear item: %w", err) } item.Slot = model.EquipmentSlot(slot) item.Rarity = model.Rarity(rarity) return &item, nil } // GetHeroGear returns all equipped gear for a hero, keyed by slot. func (s *GearStore) GetHeroGear(ctx context.Context, heroID int64) (map[model.EquipmentSlot]*model.GearItem, error) { rows, err := s.pool.Query(ctx, ` SELECT g.id, g.slot, g.form_id, g.name, g.subtype, g.rarity, g.ilvl, g.base_primary, g.primary_stat, g.stat_type, g.speed_modifier, g.crit_chance, g.agility_bonus, g.set_name, g.special_effect FROM hero_gear hg JOIN gear g ON hg.gear_id = g.id WHERE hg.hero_id = $1 `, heroID) if err != nil { return nil, fmt.Errorf("get hero gear: %w", err) } defer rows.Close() gear := make(map[model.EquipmentSlot]*model.GearItem) for rows.Next() { var item model.GearItem var slot, rarity string if err := rows.Scan( &item.ID, &slot, &item.FormID, &item.Name, &item.Subtype, &rarity, &item.Ilvl, &item.BasePrimary, &item.PrimaryStat, &item.StatType, &item.SpeedModifier, &item.CritChance, &item.AgilityBonus, &item.SetName, &item.SpecialEffect, ); err != nil { return nil, fmt.Errorf("scan gear item: %w", err) } item.Slot = model.EquipmentSlot(slot) item.Rarity = model.Rarity(rarity) gear[item.Slot] = &item } if err := rows.Err(); err != nil { return nil, fmt.Errorf("hero gear rows: %w", err) } return gear, nil } // EquipItem equips a gear item into the given slot for a hero (upsert). func (s *GearStore) EquipItem(ctx context.Context, heroID int64, slot model.EquipmentSlot, gearID int64) error { _, err := s.pool.Exec(ctx, ` INSERT INTO hero_gear (hero_id, slot, gear_id) VALUES ($1, $2, $3) ON CONFLICT (hero_id, slot) DO UPDATE SET gear_id = $3 `, heroID, string(slot), gearID) if err != nil { return fmt.Errorf("equip gear item: %w", err) } return nil } // UnequipSlot removes the gear from the given slot for a hero. func (s *GearStore) UnequipSlot(ctx context.Context, heroID int64, slot model.EquipmentSlot) error { _, err := s.pool.Exec(ctx, ` DELETE FROM hero_gear WHERE hero_id = $1 AND slot = $2 `, heroID, string(slot)) if err != nil { return fmt.Errorf("unequip gear slot: %w", err) } return nil }