"""Deck model for Mantimon TCG. This module defines the Deck model for player deck configurations. Decks store card lists as JSONB for flexibility and can be validated against campaign rules or used freely in freeplay mode. Example: deck = Deck( user_id=user.id, name="Electric Storm", cards={"pikachu_base_001": 2, "raichu_base_001": 1, ...}, energy_cards={"lightning": 10, "colorless": 4} ) """ from typing import TYPE_CHECKING from uuid import UUID from sqlalchemy import Boolean, ForeignKey, Index, String, Text, text from sqlalchemy.dialects.postgresql import JSONB from sqlalchemy.orm import Mapped, mapped_column, relationship from app.db.base import Base if TYPE_CHECKING: from app.db.models.user import User class Deck(Base): """Player deck configuration. Stores a deck's card composition and validation state. Cards are stored as JSONB mapping card_definition_id -> quantity. Attributes: id: Unique identifier (UUID). user_id: Foreign key to the owning user. name: Display name for the deck. cards: JSONB mapping card IDs to quantities. energy_cards: JSONB mapping energy types to quantities. is_valid: Whether deck passes validation rules. validation_errors: JSONB list of validation error messages. is_starter: Whether this is a pre-built starter deck. starter_type: Type of starter deck (grass, fire, water, etc.). created_at: Record creation timestamp. updated_at: Record update timestamp. Relationships: user: The user who owns this deck. Notes: - Campaign mode requires deck ownership validation - Freeplay mode unlocks all cards (validation skipped) - Free users limited to 5 decks, premium unlimited """ __tablename__ = "decks" # Ownership user_id: Mapped[UUID] = mapped_column( ForeignKey("users.id", ondelete="CASCADE"), nullable=False, index=True, doc="Foreign key to owning user", ) # Deck identity name: Mapped[str] = mapped_column( String(100), nullable=False, doc="Display name for the deck", ) # Card composition (JSONB for flexibility) # Format: {"card_definition_id": quantity, ...} cards: Mapped[dict] = mapped_column( JSONB, default=dict, nullable=False, doc="Mapping of card IDs to quantities", ) # Energy cards tracked separately for easier UI display # Format: {"lightning": 10, "fire": 5, ...} energy_cards: Mapped[dict] = mapped_column( JSONB, default=dict, nullable=False, doc="Mapping of energy types to quantities", ) # Validation state is_valid: Mapped[bool] = mapped_column( Boolean, default=False, nullable=False, doc="Whether deck passes validation rules", ) validation_errors: Mapped[list | None] = mapped_column( JSONB, nullable=True, doc="List of validation error messages", ) # Starter deck flags is_starter: Mapped[bool] = mapped_column( Boolean, default=False, nullable=False, doc="Whether this is a pre-built starter deck", ) starter_type: Mapped[str | None] = mapped_column( String(20), nullable=True, doc="Type of starter deck (grass, fire, water, etc.)", ) # Optional notes/description description: Mapped[str | None] = mapped_column( Text, nullable=True, doc="Optional deck description or notes", ) # Relationship user: Mapped["User"] = relationship( "User", back_populates="decks", ) # Indexes __table_args__ = ( Index("ix_decks_user_name", "user_id", "name"), # Partial unique index: only one starter deck per user Index( "ix_decks_user_starter_unique", "user_id", unique=True, postgresql_where=text("is_starter = true"), ), ) @property def total_cards(self) -> int: """Get total number of cards in deck (excluding energy). Returns: Sum of all card quantities. """ return sum(self.cards.values()) if self.cards else 0 @property def total_energy(self) -> int: """Get total number of energy cards. Returns: Sum of all energy card quantities. """ return sum(self.energy_cards.values()) if self.energy_cards else 0 @property def deck_size(self) -> int: """Get total deck size (cards + energy). Returns: Total cards in deck. """ return self.total_cards + self.total_energy def __repr__(self) -> str: return f""