defmodule Day11Part2 do
  def run do
    File.read!("input")
    |> String.split("\n", trim: true)
    |> Enum.map(&String.to_charlist/1)
    |> parse_seat_plan()
    |> run_until_stable()
    |> count_occupied()
    |> IO.inspect()
  end

  def run_until_stable(seats) do
    new_seats = tick(seats)

    if new_seats == seats do
      seats
    else
      run_until_stable(new_seats)
    end
  end

  def tick(seats) do
    Map.new(seats, fn {{x, y}, old_seat} -> toggle(old_seat, x, y, seats) end)
  end

  def toggle(:empty, x, y, seats) do
    new_value =
      if occupied_visible(x, y, seats) == 0 do
        :occupied
      else
        :empty
      end

    {{x, y}, new_value}
  end

  def toggle(:occupied, x, y, seats) do
    new_value =
      if occupied_visible(x, y, seats) >= 5 do
        :empty
      else
        :occupied
      end

    {{x, y}, new_value}
  end

  def toggle(:floor, x, y, _), do: {{x, y}, :floor}

  def occupied_visible(x, y, seats) do
    directions = [
      {-1, -1},
      {-1, 0},
      {-1, +1},
      {0, -1},
      {0, +1},
      {+1, -1},
      {+1, 0},
      {+1, +1}
    ]

    Enum.count(directions, fn {vx, vy} -> check_visible_in_direction(x, y, vx, vy, 1, seats) end)
  end

  def count_occupied(seats) do
    Enum.count(seats, &match?({_, :occupied}, &1))
  end

  def check_visible_in_direction(x, y, vx, vy, distance, seats) do
    next = seats[{x + vx * distance, y + vy * distance}]

    case next do
      nil -> false
      :empty -> false
      :occupied -> true
      :floor -> check_visible_in_direction(x, y, vx, vy, distance + 1, seats)
    end
  end

  def parse_seat_plan(input) do
    {_, seats} =
      Enum.reduce(input, {0, %{}}, fn row, {y, seats} ->
        {_, seats} =
          Enum.reduce(row, {0, seats}, fn seat, {x, seats} ->
            seat =
              case seat do
                ?L -> :empty
                ?. -> :floor
                ?# -> :occupied
              end

            {x + 1, Map.put(seats, {x, y}, seat)}
          end)

        {y + 1, seats}
      end)

    seats
  end
end

Day11Part2.run()