use colored::Colorize;
use nom::{bytes::complete::tag, character::complete::{char, multispace0}, combinator::opt, sequence::{delimited, preceded}, IResult, Parser};
use rustc_hash::FxHashMap;
use crate::{parser::{context::ParseContext, doc::{err_fail, StofParseError}, expr::expr, ident::ident, whitespace::whitespace}, runtime::Val};
pub mod semver;
pub mod whitespace;
pub mod number;
pub mod types;
pub mod ident;
pub mod string;
pub mod literal;
pub mod expr;
pub mod statement;
pub mod context;
pub mod func;
pub mod field;
pub mod doc;
pub mod import;
pub mod data;
pub(self) fn parse_attributes<'a>(input: &'a str, context: &mut ParseContext) -> IResult<&'a str, (FxHashMap<String, Val>, bool), StofParseError> {
let mut map = FxHashMap::default();
let mut input = input;
loop {
let res = parse_attribute(input, context);
match res {
Ok(attr) => {
input = attr.0;
map.insert(attr.1.0, attr.1.1);
},
Err(error) => {
match error {
nom::Err::Error(_) => {
break;
},
nom::Err::Incomplete(_) => {
break;
},
nom::Err::Failure(_) => {
return Err(error);
}
}
}
}
}
let mut do_action = true;
if !map.is_empty() {
for (k, v) in &map {
match k.as_str() {
"if" => {
do_action = v.truthy();
},
"not" => {
do_action = !v.truthy();
},
"any" => {
match v {
Val::List(vals) => {
for v in vals {
if v.read().truthy() {
do_action = true;
break;
}
}
},
Val::Set(set) => {
for v in set {
if v.read().truthy() {
do_action = true;
break;
}
}
},
Val::Tup(vals) => {
for v in vals {
if v.read().truthy() {
do_action = true;
break;
}
}
},
v => {
do_action = v.truthy();
}
}
},
attr => {
if context.profile.exclude_attributes.contains(attr) {
do_action = false;
}
}
}
}
}
Ok((input, (map, do_action)))
}
pub(self) fn parse_attribute<'a>(input: &'a str, context: &mut ParseContext) -> IResult<&'a str, (String, Val), StofParseError> {
let (input, _) = whitespace(input)?;
let (input, _) = tag("#[").parse(input)?;
let (input, name) = preceded(multispace0, ident).parse(input).map_err(err_fail)?;
let (input, value_expr) = opt(delimited(char('('), expr, char(')'))).parse(input).map_err(err_fail)?;
let (input, _) = preceded(multispace0, char(']')).parse(input).map_err(err_fail)?;
let mut val = Val::Null;
if let Some(expr) = value_expr {
match context.eval(expr) {
Ok(res) => {
val = res;
},
Err(error) => {
return Err(nom::Err::Failure(StofParseError::from(format!("{} '{name}' errored with {}", "attribute eval error:".dimmed(), error.to_string()))));
}
}
}
Ok((input, (String::from(name), val)))
}