#![warn(clippy::pedantic)]

use std::{collections::HashMap, io::stdin};

use aoc::vec2::{Direction, Vec2};
use enum_map::EnumMap;
use rayon::prelude::{IntoParallelIterator, ParallelIterator};

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum Device {
    MirrorTopLeft,
    MirrorTopRight,
    SplitterHorizontal,
    SplitterVertical,
}

impl Device {
    pub fn get_outputs(self, input_direction: Direction) -> DeviceOutput {
        use DeviceOutput::{One, Two};

        match (self, input_direction) {
            (Device::MirrorTopLeft, Direction::Up) | (Device::MirrorTopRight, Direction::Down) => {
                One(Direction::Right)
            }

            (Device::MirrorTopLeft, Direction::Down) | (Device::MirrorTopRight, Direction::Up) => {
                One(Direction::Left)
            }
            (Device::MirrorTopLeft, Direction::Left)
            | (Device::MirrorTopRight, Direction::Right) => One(Direction::Down),
            (Device::MirrorTopLeft, Direction::Right)
            | (Device::MirrorTopRight, Direction::Left) => One(Direction::Up),

            (Device::SplitterHorizontal, Direction::Up | Direction::Down) => {
                Two(Direction::Left, Direction::Right)
            }
            (Device::SplitterVertical, Direction::Left | Direction::Right) => {
                Two(Direction::Up, Direction::Down)
            }

            (Device::SplitterHorizontal, Direction::Left | Direction::Right)
            | (Device::SplitterVertical, Direction::Up | Direction::Down) => One(input_direction),
        }
    }

    pub fn parse(c: char) -> Option<Self> {
        match c {
            '/' => Some(Device::MirrorTopLeft),
            '\\' => Some(Device::MirrorTopRight),
            '-' => Some(Device::SplitterHorizontal),
            '|' => Some(Device::SplitterVertical),
            '.' => None,
            _ => unreachable!(),
        }
    }
}

#[derive(Debug, Clone, Copy)]
enum DeviceOutput {
    One(Direction),
    Two(Direction, Direction),
}

#[derive(Debug, Clone, Copy)]
struct Field {
    device: Option<Device>,
    seen_beams: EnumMap<Direction, bool>,
}

#[derive(Debug, Clone, Copy)]
struct PathPosition {
    pos: Vec2,
    direction: Direction,
}

impl PathPosition {
    pub fn advance_to(self, direction: Direction) -> Self {
        Self {
            pos: self.pos + Vec2::from(direction),
            direction,
        }
    }
}

fn follow_path(start: PathPosition, fields: &mut HashMap<Vec2, Field>) {
    let mut pos = start;

    loop {
        let Some(field) = fields.get_mut(&pos.pos) else {
            return;
        };
        let seen = &mut field.seen_beams[pos.direction];

        if *seen {
            return;
        }

        *seen = true;

        let outputs = match field.device {
            Some(d) => d.get_outputs(pos.direction),
            None => DeviceOutput::One(pos.direction),
        };

        match outputs {
            DeviceOutput::One(d) => {
                pos = pos.advance_to(d);
            }
            DeviceOutput::Two(d1, d2) => {
                follow_path(pos.advance_to(d1), fields);
                pos = pos.advance_to(d2);
            }
        }
    }
}

fn energize_fields(start: PathPosition, mut fields: HashMap<Vec2, Field>) -> usize {
    follow_path(start, &mut fields);

    fields
        .values()
        .filter(|field| field.seen_beams.into_iter().any(|(_dir, seen)| seen))
        .count()
}

fn main() {
    let lines: Vec<_> = stdin().lines().map(Result::unwrap).collect();
    let max_y = i32::try_from(lines.len()).unwrap() - 1;
    let max_x = i32::try_from(lines[0].len()).unwrap() - 1;

    let fields: HashMap<_, _> = lines
        .iter()
        .enumerate()
        .flat_map(|(row, l)| {
            l.chars().enumerate().map(move |(col, c)| {
                let pos = Vec2::new(col.try_into().unwrap(), max_y - i32::try_from(row).unwrap());
                let device = Device::parse(c);
                (
                    pos,
                    Field {
                        device,
                        seen_beams: EnumMap::default(),
                    },
                )
            })
        })
        .collect();

    println!(
        "{}",
        energize_fields(
            PathPosition {
                pos: Vec2::new(0, max_y),
                direction: Direction::Right,
            },
            fields.clone()
        ),
    );

    let hor_starts = (0..=max_x).flat_map(|x| {
        [
            (Vec2::new(x, 0), Direction::Up),
            (Vec2::new(x, max_y), Direction::Down),
        ]
    });
    let ver_starts = (0..=max_y).flat_map(|y| {
        [
            (Vec2::new(0, y), Direction::Right),
            (Vec2::new(max_x - 1, y), Direction::Left),
        ]
    });
    let starts: Vec<_> = hor_starts.chain(ver_starts).collect();

    let max_energized = starts
        .into_par_iter()
        .map(|(pos, direction)| energize_fields(PathPosition { pos, direction }, fields.clone()))
        .max()
        .unwrap();

    println!("{max_energized}");
}
