use bumpalo::collections::Vec as BumpVec;
use chumsky::prelude::*;
use num::Num;

use crate::{constant::Integer, data::PlutusData};

use super::{
    types::{Extra, MapExtra},
    utils::hex_bytes,
};

pub fn parser<'a>() -> impl Parser<'a, &'a str, &'a PlutusData<'a>, Extra<'a>> {
    recursive(|data| {
        choice((
            just('B')
                .padded()
                .ignore_then(just('#').ignore_then(hex_bytes()).padded())
                .map_with(|v, e: &mut MapExtra<'a, '_>| {
                    let state = e.state();

                    let bytes = BumpVec::from_iter_in(v, state.arena.as_bump());
                    let bytes = state.arena.alloc(bytes);

                    PlutusData::byte_string(state.arena, bytes)
                }),
            just('I')
                .padded()
                .ignore_then(
                    just('-')
                        .ignored()
                        .or_not()
                        .padded()
                        .then(text::int(10))
                        .padded(),
                )
                .map_with(|(maybe_negative, v), e: &mut MapExtra<'a, '_>| {
                    let state = e.state();

                    let mut i = Integer::from_str_radix(v, 10).unwrap();

                    if maybe_negative.is_some() {
                        i = -i;
                    };

                    let value = state.arena.alloc_integer(i);

                    PlutusData::integer(state.arena, value)
                }),
            just("Constr")
                .padded()
                .ignore_then(text::int(10).padded())
                .then(
                    data.clone()
                        .separated_by(just(',').padded())
                        .collect()
                        .delimited_by(just('['), just(']')),
                )
                .map_with(
                    |(tag, fields): (_, Vec<&PlutusData<'_>>), e: &mut MapExtra<'a, '_>| {
                        let state = e.state();

                        let fields = BumpVec::from_iter_in(fields, state.arena.as_bump());
                        let fields = state.arena.alloc(fields);

                        PlutusData::constr(state.arena, tag.parse().unwrap(), fields)
                    },
                ),
            just("List")
                .padded()
                .ignore_then(
                    data.clone()
                        .separated_by(just(',').padded())
                        .collect()
                        .delimited_by(just('['), just(']')),
                )
                .map_with(|items: Vec<&PlutusData<'_>>, e: &mut MapExtra<'a, '_>| {
                    let state = e.state();

                    let fields = BumpVec::from_iter_in(items, state.arena.as_bump());
                    let fields = state.arena.alloc(fields);

                    PlutusData::list(state.arena, fields)
                }),
            just("Map")
                .padded()
                .ignore_then(
                    data.clone()
                        .padded()
                        .then_ignore(just(',').padded())
                        .then(data.padded())
                        .delimited_by(just('('), just(')'))
                        .separated_by(just(',').padded())
                        .collect()
                        .padded()
                        .delimited_by(just('['), just(']'))
                        .padded(),
                )
                .map_with(
                    |items: Vec<(&PlutusData<'_>, &PlutusData<'_>)>, e: &mut MapExtra<'a, '_>| {
                        let state = e.state();

                        let fields = BumpVec::from_iter_in(items, state.arena.as_bump());
                        let fields = state.arena.alloc(fields);

                        PlutusData::map(state.arena, fields)
                    },
                ),
        ))
        .boxed()
    })
}
