lib/cw_card_utils/decklist_parser/deck.rb



# frozen_string_literal: true

module CwCardUtils
  module DecklistParser
    # A Deck is a collection of cards.
    class Deck
      IGNORED_TRIBAL_TYPES = %w[
        human wizard soldier scout shaman warrior cleric rogue advisor knight druid
      ].freeze

      def initialize(cmc_data_source)
        @main = []
        @sideboard = []
        @lands = []
        @x_to_cast = []

        @cmc_data_source = cmc_data_source
      end

      def to_h
        {
          mainboard: main.map(&:to_h),
          sideboard: sideboard.map(&:to_h),
          tribe: tribe,
        }
      end

      def color_identity
        @color_identity ||= main.map(&:color_identity).flatten.uniq
      end

      def color_identity_string
        @color_identity_string ||= CwCardUtils::DecklistParser::ColorIdentityResolver.resolve(color_identity)
      end

      def to_json(*_args)
        to_h.to_json
      end

      def main
        tag_cards(@main)
      end

      def sideboard
        tag_cards(@sideboard)
      end

      def tag_cards(cards)
        cards.map do |c|
          c.tags = CwCardUtils::DecklistParser::CardTagger.new(c, self).tags
          c
        end
      end

      def archetype
        @archetype ||= CwCardUtils::DecklistParser::ArchetypeDetector.new(self).detect
      end

      def inspect
        "<Deck: main: #{mainboard_size} sideboard: #{sideboard_size} lands: #{lands_count} x_to_cast: #{x_to_cast_count} cards: #{cards_count}>"
      end

      def collapsed_curve
        @collapsed_curve ||= CurveCalculator.new(self).collapsed_curve
      end

      def curve
        @curve ||= CurveCalculator.new(self).curve
      end

      def normalized_curve
        @normalized_curve ||= CurveCalculator.new(self).normalized_curve
      end

      def collapsed_normalized_curve
        @collapsed_normalized_curve ||= CurveCalculator.new(self).collapsed_normalized_curve
      end

      def empty?
        @main.empty? && @sideboard.empty?
      end

      def any?
        !empty?
      end

      def each(&block)
        @main.each do |c|
          block.call(c)
        end
      end

      def add(c, target = :mainboard)
        reset_counters
        card = Card.new(c[:name], c[:count], @cmc_data_source)
        card.tags = CwCardUtils::DecklistParser::CardTagger.new(card, self).tags

        if target == :mainboard
          @main << card
        else
          @sideboard << card
        end

        if card.type.include?("Land")
          @lands << card
        elsif card.cmc.nil?
          @x_to_cast << card
        end
      end

      def reset_counters
        @mainboard_size = nil
        @sideboard_size = nil
        @lands_count = nil
        @x_to_cast_count = nil
        @cards_count = nil
      end

      def format
        @format ||= detect_format_for_deck
      end

      def detect_format_for_deck
        if mainboard_size >= 100
          :commander
        elsif mainboard_size >= 60
          # Check if this looks like Commander (60+ cards, singleton except for basic lands)
          if mainboard_size >= 60 && is_singleton_deck?
            :commander
          else
            :standard
          end
        else
          :modern
        end
      end

      def is_singleton_deck?
        # Count non-basic land cards and check for duplicates
        non_basic_lands = @main.reject { |card| is_basic_land?(card) }

        # Check if any non-basic land card appears more than once
        card_counts = Hash.new(0)
        non_basic_lands.each do |card|
          card_counts[card.name] += card.count
        end

        # All non-basic land cards should appear only once
        card_counts.values.all? { |count| count == 1 }
      end

      def is_basic_land?(card)
        basic_land_names = %w[Plains Island Swamp Mountain Forest Wastes]
        basic_land_names.include?(card.name)
      end

      def mainboard_size
        @mainboard_size ||= main.sum { |card| card.count }
      end

      def sideboard_size
        @sideboard_size ||= sideboard.sum { |card| card.count }
      end

      def lands_count
        @lands.sum { |card| card.count }
      end

      def x_to_cast_count
        @x_to_cast.sum { |card| card.count }
      end

      def cards_count
        mainboard_size + sideboard_size
      end

      def count_without_lands
        cards_count - lands_count
      end

      def size
        cards_count
      end

      def tribe
        return @tribe if @tribe
        @tribe = detect_tribe_for_deck
      end

      def detect_tribe_for_deck
        subtype_counts = Hash.new(0)
        total_creatures = 0

        @main.each do |card|
          next unless card.type&.include?("Creature")

          total_creatures += 1
          subtypes = card.type.split(/[—-]/).last.to_s.strip.split
          subtypes.each do |type|
            subtype_counts[type.downcase] += 1
          end
        end

        return nil if total_creatures < 6 || subtype_counts.empty?

        most_common = subtype_counts.max_by { |_, count| count }
        return nil if most_common.nil?

        dominant_type = most_common[0]
        count = most_common[1]

        # Suppress only if it's a boring type *and* barely dominant
        if IGNORED_TRIBAL_TYPES.include?(dominant_type) && count.to_f / total_creatures < 0.7
          return nil
        end

        return dominant_type.to_sym if count.to_f / total_creatures >= 0.4
        nil
      end

    end
  end
end