//! Experiments with writing magister card code

use std::{collections::HashMap, rc::Rc};

use chumsky::Parser;
use magister::Color;
use steel::{
    rerrs,
    steel_vm::{engine::Engine, register_fn::RegisterFn},
    SteelErr, SteelVal,
};
use steel_derive::Steel;

#[derive(thiserror::Error, Debug)]
pub enum MagisterSteelError {}

/// What can a card be played as?
#[derive(Clone, Copy, Steel, PartialEq, Eq, Hash, Debug)]
pub enum PlayedAs {
    Magister,
    Aide,
    Fragment { is_set: bool },
}

#[derive(Clone, Debug, Steel)]
pub enum CardEvent {
    PlayedAs(PlayedAs),
}

impl CardEvent {
    pub(crate) fn played_as(val: SteelVal) -> Result<Self, SteelErr> {
        let sym = val.symbol_or_else(|| {
            SteelErr::new(
                rerrs::ErrorKind::TypeMismatch,
                format!("expected symbol, found {}", val),
            )
        })?;
        match sym.to_ascii_lowercase().as_str() {
            "magister" => Ok(Self::PlayedAs(PlayedAs::Magister)),
            "aide" => Ok(Self::PlayedAs(PlayedAs::Aide)),
            "fragment" => Ok(Self::PlayedAs(PlayedAs::Fragment { is_set: false })),
            "set-fragment" => Ok(Self::PlayedAs(PlayedAs::Fragment { is_set: true })),
            _ => Err(SteelErr::new(rerrs::ErrorKind::Generic, format!("invalid spec, expected one of ['magister, 'aide, 'fragment, 'set-fragment], found '{}", sym))),
        }
    }
}

// TODO Revome from this crate and put in `magister` once we've determined how this should be shaped
/// A struct that is given input board state, event input (if applicable), and variables (X and O if applicable)
/// and outputs a list of actions.
#[derive(Clone, Steel)]
pub struct Effect {
    /// The number of required costs that *must* be satisfied
    // required: usize,
    // In the input cost array, all required costs are specfied first, then any optional costs.
    implementation: Rc<Box<dyn Fn(&[()]) -> Vec<Action>>>,
}

impl std::fmt::Debug for Effect {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.debug_struct("Effect")
            // .field("required", &self.required)
            .finish_non_exhaustive()
    }
}

impl Effect {
    fn draw(amount: usize) -> Self {
        Effect {
            // required: 1, // this is
            implementation: Rc::new(Box::new(move |_costs| {
                (0..amount).map(|_| Action::Draw).collect()
            })),
        }
    }
}

// TODO instead of "moveing to magister", create a separate magister type that Into's into the script representation,
// and have the "magister_reducer" take in anything that can Into into the script version, as that's what'll be
// running the game.

/// A possible game action
#[derive(Debug, Clone, Steel)]
pub enum Action {
    Draw,
}

/// Representaions of mana for scripting
#[derive(Debug, Clone, Copy, Steel, PartialEq, Eq)]
pub enum Mana {
    // TODO allow these to refer to amounts
    // 0 [x] static amounts
    // 1 [ ] input variable amounts (cost pre-declared X and Y, overlay count O)
    // 2 [ ] arbitrary amounts ("number of Horrowshow magister/aides")
    Color(usize, Color),
    Any(usize),
}

impl Mana {
    fn from_symbol_to_color<S: AsRef<str>>(s: S) -> Option<Color> {
        let symbol = s.as_ref().to_ascii_lowercase();
        match symbol.as_str() {
            "mundane" | "m" | "grey" | "gray" => Some(Color::Mundane),
            "divine" | "d" | "gold" => Some(Color::Divine),
            "revelation" | "r" | "blue" => Some(Color::Revelation),
            "grace" | "a" | "white" => Some(Color::Grace),
            "growth" | "g" | "green" => Some(Color::Growth),
            "crusade" | "u" | "red" => Some(Color::Crusade),
            "suffering" | "s" | "black" => Some(Color::Suffering),
            _ => None,
        }
    }

    /// Any type of mana suffices
    fn any(amount: usize) -> Self {
        Self::Any(amount)
    }

    /// Only mana of a specific color suffices
    fn color(amount: usize, val: SteelVal) -> Result<Self, String> {
        if let Some(color) = val
            .as_symbol()
            .and_then(|s| Self::from_symbol_to_color(s.as_str()))
        {
            Ok(Self::Color(amount, color))
        } else {
            Err(format!("expected mana symbol, found {}", val))
        }
    }

    fn compact_parser<'a>() -> impl Parser<
        'a,
        &'a str,
        Vec<(Option<Color>, usize)>,
        chumsky::extra::Err<chumsky::prelude::Rich<'a, char>>,
    > {
        use chumsky::prelude::*;
        choice((
            just('m')
                .repeated()
                .at_least(1)
                .collect::<String>()
                .map(|s: String| (Some(Color::Mundane), s.len())),
            just('d')
                .repeated()
                .at_least(1)
                .collect::<String>()
                .map(|s: String| (Some(Color::Divine), s.len())),
            just('r')
                .repeated()
                .at_least(1)
                .collect::<String>()
                .map(|s: String| (Some(Color::Revelation), s.len())),
            just('a')
                .repeated()
                .at_least(1)
                .collect::<String>()
                .map(|s: String| (Some(Color::Grace), s.len())),
            just('g')
                .repeated()
                .at_least(1)
                .collect::<String>()
                .map(|s: String| (Some(Color::Growth), s.len())),
            just('u')
                .repeated()
                .at_least(1)
                .collect::<String>()
                .map(|s: String| (Some(Color::Crusade), s.len())),
            just('s')
                .repeated()
                .at_least(1)
                .collect::<String>()
                .map(|s: String| (Some(Color::Suffering), s.len())),
            text::int(10).try_map(|i: &str, span| {
                i.parse::<usize>()
                    .map(|i| (None, i))
                    .map_err(|e| Rich::custom(span, e))
            }),
        ))
        .repeated()
        .collect()
    }

    fn compact(notation: String) -> Result<Vec<Mana>, String> {
        let notation_input = notation.to_ascii_lowercase();
        let (output, errs) = Self::compact_parser()
            .parse(&notation_input)
            .into_output_errors();
        match output {
            Some(outputs) => {
                let mut mana = HashMap::new();
                for (maybe_color, count) in outputs.into_iter() {
                    *mana.entry(maybe_color).or_insert(0) += count;
                }
                Ok(mana
                    .into_iter()
                    .map(|(k, v)| {
                        if let Some(color) = k {
                            Mana::Color(v, color)
                        } else {
                            Mana::Any(v)
                        }
                    })
                    .collect())
            }
            None => {
                // Technically incorrect usage (we assume stderr), but w/e, this should
                // be running on desktops/laptops/non-web anyways
                errs.into_iter().for_each(|e| {
                    ariadne::Report::build(ariadne::ReportKind::Error, (), e.span().start)
                        .with_message(e.to_string())
                        .with_label(
                            ariadne::Label::new(e.span().into_range())
                                .with_message(e.reason().to_string())
                                .with_color(ariadne::Color::Red),
                        )
                        .finish()
                        .eprint(ariadne::Source::from(&notation))
                        .unwrap()
                });
                Err(format!("Failed to parse compact mana string: {notation}"))
            }
        }
    }
}

/// Represents a cost that can be paid.
#[derive(Clone, Debug, Steel)]
pub enum Cost {
    Mana(Vec<Mana>),
    PerTurn(usize),
}

impl Cost {
    /// Specify a cost that can only be paid in mana
    fn mana(mana: Vec<Mana>) -> Self {
        Self::Mana(mana)
    }

    /// Specfiy a cost that can only be paid a certain number of times per turn
    fn per_turn(times: usize) -> Self {
        Self::PerTurn(times)
    }
}

#[derive(Clone, Debug, Steel)]
pub enum Ability {
    Trigger {},
    Activated {},
    /// This is the "ability" of a Fragment
    Invoked {},
}

impl Ability {
    fn trigger(card_event: CardEvent, effect: Effect) -> Self {
        dbg!(card_event);
        dbg!(&effect);
        dbg!((effect.implementation)(&[]));
        Ability::Trigger {}
    }

    fn activated(cost: Cost, effect: Effect) -> Self {
        dbg!(&cost);
        dbg!(&effect);
        dbg!((effect.implementation)(&[]));
        Ability::Activated {}
    }

    // The cost of this kind of ability is the cost of the card itself
    fn invoked(effect: Effect) -> Self {
        dbg!(&effect);
        dbg!((effect.implementation)(&[]));
        Ability::Invoked {}
    }
}

pub fn expose_steel() -> Result<Engine, MagisterSteelError> {
    let mut engine = Engine::new();
    engine.register_type::<CardEvent>("event?");
    engine.register_type::<Effect>("effect?");
    engine.register_type::<Action>("action?");
    engine.register_type::<Cost>("cost?");
    engine.register_type::<Mana>("mana?");
    engine.register_type::<Ability>("ability?");

    engine
        .register_fn("ability/trigger", Ability::trigger)
        .register_fn("ability/activated", Ability::activated)
        .register_fn("ability/invoked", Ability::invoked)
        .register_fn("event/played-as", CardEvent::played_as)
        .register_fn("effect/draw", Effect::draw)
        .register_fn("cost/mana", Cost::mana)
        .register_fn("cost/per-turn", Cost::per_turn)
        .register_fn("mana/any", Mana::any)
        .register_fn("mana/color", Mana::color)
        .register_fn("mana/compact", Mana::compact);

    Ok(engine)
}