use super::strings::name;
use super::util::{ignore_comments, opt_spacelike};
use super::value::space_list;
use super::{PResult, Span};
use crate::sass::{CallArgs, FormalArgs, Name};
use nom::bytes::complete::tag;
use nom::character::complete::char;
use nom::combinator::{cut, map, map_res, opt};
use nom::error::context;
use nom::multi::separated_list0;
use nom::sequence::{delimited, pair, preceded, terminated};
use nom::Parser as _;

pub fn formal_args(input: Span) -> PResult<FormalArgs> {
    let (input, _) = terminated(char('('), opt_spacelike).parse(input)?;
    let (input, v) = separated_list0(
        preceded(tag(","), opt_spacelike),
        map(
            pair(
                delimited(tag("$"), name, opt_spacelike),
                opt(delimited(
                    terminated(tag(":"), opt_spacelike),
                    cut(context("Expected expression.", space_list)),
                    opt_spacelike,
                )),
            ),
            |(name, d)| (name.into(), d),
        ),
    )
    .parse(input)?;
    let (input, va) = if !v.is_empty() {
        terminated(
            opt(tag("...")),
            preceded(opt_spacelike, terminated(opt(tag(",")), opt_spacelike)),
        )
        .parse(input)?
    } else {
        (input, None)
    };
    let (input, _) = char(')')(input)?;
    Ok((
        input,
        if va.is_none() {
            FormalArgs::new(v)
        } else {
            FormalArgs::new_va(v)
        },
    ))
}

pub fn call_args(input: Span) -> PResult<CallArgs> {
    delimited(
        terminated(char('('), opt_spacelike),
        map_res(
            |input| {
                let (input, args) = separated_list0(
                    terminated(tag(","), opt_spacelike),
                    pair(
                        opt(map(
                            delimited(
                                tag("$"),
                                name,
                                delimited(
                                    ignore_comments,
                                    char(':'),
                                    opt_spacelike,
                                ),
                            ),
                            Name::from,
                        )),
                        terminated(space_list, opt_spacelike),
                    ),
                )
                .parse(input)?;
                let (input, trail) = if !args.is_empty() {
                    opt(terminated(char(','), opt_spacelike)).parse(input)?
                } else {
                    (input, None)
                };
                Ok((input, (args, trail)))
            },
            |(args, trail)| CallArgs::new(args, trail.is_some()),
        ),
        cut(char(')')),
    )
    .parse(input)
}
