import AOC

aoc 2023, 4 do
  def p1(input) do
    input
    |> String.split("\n")
    |> Stream.map(&parse_line/1)
    |> Stream.map(&score_1/1)
    |> Enum.sum()
  end

  def p2(input) do
    cards =
      input
      |> String.split("\n")
      |> Enum.map(&parse_line/1)
      |> Enum.map(&score_2/1)

    card_counts =
      cards
      |> Stream.map(fn {id, _} -> {id, 1} end)
      |> Enum.into(%{})

    cards
    |> Enum.reduce(card_counts, fn {id, winners}, counts ->
      if winners > 0 do
        copies = Map.get(counts, id)

        copied_range =
          (id + 1)..(id + winners)

        for copy_id <- copied_range, reduce: counts do
          counts ->
            if Map.has_key?(counts, copy_id) do
              Map.update!(counts, copy_id, &(&1 + copies))
            else
              counts
            end
        end
      else
        counts
      end
    end)
    |> Map.values()
    |> Enum.sum()
  end

  def parse_line(line) do
    ["Card " <> card_string, numbers_string] = String.split(line, ": ")
    [winning_string, you_have_string] = String.split(numbers_string, " | ")

    card =
      card_string
      |> String.trim()
      |> String.to_integer()

    winning =
      winning_string
      |> String.split(" ", trim: true)
      |> Enum.map(&String.to_integer/1)

    you_have =
      you_have_string
      |> String.split(" ", trim: true)
      |> Enum.map(&String.to_integer/1)

    {card, winning, you_have}
  end

  def score_1({_, winning, you_have}) do
    number_of_winners =
      winning
      |> Enum.filter(&Enum.member?(you_have, &1))
      |> Enum.count()

    if number_of_winners < 1 do
      0
    else
      2 ** (number_of_winners - 1)
    end
  end

  def score_2({id, winning, you_have}) do
    number_of_winners =
      winning
      |> Enum.filter(&Enum.member?(you_have, &1))
      |> Enum.count()

    {id, number_of_winners}
  end
end