use super::strings;
use super::{opt_spacelike, PResult, Span};
use crate::css::{BinOp, CallArgs, Value};
use crate::parser::input_to_str;
use crate::parser::value::{numeric, unicode_range_inner};
use crate::value::{ListSeparator, Operator};
use nom::branch::alt;
use nom::bytes::complete::{is_not, tag};
use nom::character::complete::one_of;
use nom::combinator::{into, map, map_opt, opt, peek, value};
use nom::multi::{fold_many0, many0, separated_list0, separated_list1};
use nom::sequence::{delimited, pair, preceded, terminated, tuple};
pub fn any(input: Span) -> PResult<Value> {
    let (input, list) = separated_list1(spaced(","), slash_list)(input)?;
    Ok((input, list_or_single(list, ListSeparator::Comma)))
}
pub fn slash_list(input: Span) -> PResult<Value> {
    let (input, list) =
        separated_list1(spaced("/"), slash_list_no_space)(input)?;
    Ok((input, list_or_single(list, ListSeparator::Slash)))
}
pub fn slash_list_no_space(input: Span) -> PResult<Value> {
    let (input, list) = separated_list1(tag("/"), space_list)(input)?;
    Ok((input, list_or_single(list, ListSeparator::SlashNoSpace)))
}
pub fn space_list(input: Span) -> PResult<Value> {
    let (input, first) = single(input)?;
    let (input, list) = fold_many0(
        preceded(opt_spacelike, single),
        move || vec![first.clone()],
        |mut list: Vec<Value>, item| {
            list.push(item);
            list
        },
    )(input)?;
    Ok((input, list_or_single(list, ListSeparator::Space)))
}
fn list_or_single(list: Vec<Value>, sep: ListSeparator) -> Value {
    if list.len() == 1 {
        list.into_iter().next().unwrap()
    } else {
        Value::List(list, Some(sep), false)
    }
}
pub fn single(input: Span) -> PResult<Value> {
    match input.first() {
        Some(b'[') => map(
            delimited(
                terminated(tag("["), opt_spacelike),
                any,
                preceded(opt_spacelike, tag("]")),
            ),
            |v| match v {
                Value::List(v, sep, false) => Value::List(v, sep, true),
                v => Value::List(vec![v], Default::default(), true),
            },
        )(input),
        Some(c) if b'0' <= *c && *c <= b'9' => into(numeric)(input),
        Some(c) if *c == b'-' || *c == b'.' => {
            alt((into(numeric), string_or_call))(input)
        }
        _ => alt((
            map(unicode_range_inner, Value::UnicodeRange),
            string_or_call,
        ))(input),
    }
}
fn string_or_call(input: Span) -> PResult<Value> {
    let (rest, string) = strings::css_string_any(input)?;
    if string.quotes().is_none() {
        if let Ok((rest, _)) = terminated(tag("("), opt_spacelike)(rest) {
            let endp = preceded(opt_spacelike, tag(")"));
            if string.value() == "calc" {
                let (rest, args) = terminated(calc_expression, endp)(rest)?;
                let args = CallArgs::from_single(args);
                return Ok((rest, Value::Call(string.take_value(), args)));
            } else {
                let (rest, args) = terminated(call_args, endp)(rest)?;
                return Ok((rest, Value::Call(string.take_value(), args)));
            }
        }
    }
    Ok((rest, string.into()))
}
fn calc_expression(input: Span) -> PResult<Value> {
    let (rest, first) = single_factor(input)?;
    fold_many0(
        tuple((
            delimited(
                opt_spacelike,
                alt((
                    value(Operator::Div, tag("/")),
                    value(Operator::Modulo, tag("%")),
                    value(Operator::Multiply, tag("*")),
                )),
                opt_spacelike,
            ),
            single_factor,
        )),
        move || first.clone(),
        |v, (op, v2)| BinOp::new(v, true, op, true, v2).into(),
    )(rest)
}
pub fn single_factor(input: Span) -> PResult<Value> {
    let (rest, first) = single_term(input)?;
    fold_many0(
        tuple((
            delimited(
                opt_spacelike,
                alt((
                    value(Operator::Plus, tag("+")),
                    value(Operator::Minus, tag("-")),
                )),
                opt_spacelike,
            ),
            single_term,
        )),
        move || first.clone(),
        |v, (op, v2)| BinOp::new(v, true, op, true, v2).into(),
    )(rest)
}
fn single_term(input: Span) -> PResult<Value> {
    match input.first() {
        Some(b'(') => delimited(
            terminated(tag("("), opt_spacelike),
            calc_expression,
            preceded(opt_spacelike, tag(")")),
        )(input),
        Some(c) if b'0' <= *c && *c <= b'9' => into(numeric)(input),
        _ => string_or_call(input),
    }
}
fn call_args(input: Span) -> PResult<CallArgs> {
    let (rest, named) = many0(pair(
        terminated(strings::css_string, spaced("=")),
        terminated(single, alt((spaced(","), peek(tag(")"))))),
    ))(input)?;
    let named = named
        .into_iter()
        .map(|(name, val)| (name.into(), val))
        .collect();
    let (rest, positional) = separated_list0(spaced(","), single_arg)(rest)?;
    let (rest, trail) = opt(tag(","))(rest)?;
    Ok((
        rest,
        CallArgs {
            positional,
            named,
            trailing_comma: trail.is_some(),
        },
    ))
}
fn single_arg(input: Span) -> PResult<Value> {
    fn end(input: Span) -> PResult<()> {
        peek(preceded(opt_spacelike, map(one_of(",)"), |_| ())))(input)
    }
    alt((
        terminated(space_list, end),
        terminated(into(ext_arg_as_string), end),
    ))(input)
}
fn ext_arg_as_string(input: Span) -> PResult<String> {
    map_opt(is_not("\"\\;{}()[] ,"), |s: Span| {
        if s.is_empty() {
            None
        } else {
            Some(input_to_str(s).ok()?.to_owned())
        }
    })(input)
}
fn spaced<'a>(
    the_tag: &'static str,
) -> impl FnMut(Span<'a>) -> PResult<Span<'a>> {
    delimited(opt_spacelike, tag(the_tag), opt_spacelike)
}