use winnow::combinator::cut_err;
use winnow::combinator::delimited;
use winnow::combinator::separated0;
use winnow::token::one_of;
use winnow::trace::trace;

use crate::key::Key;
use crate::parser::errors::CustomError;
use crate::parser::key::key;
use crate::parser::prelude::*;
use crate::parser::trivia::ws;
use crate::parser::value::value;
use crate::table::TableKeyValue;
use crate::{InlineTable, InternalString, Item, RawString, Value};

use indexmap::map::Entry;

// ;; Inline Table

// inline-table = inline-table-open inline-table-keyvals inline-table-close
pub(crate) fn inline_table<'i>(
    check: RecursionCheck,
) -> impl Parser<Input<'i>, InlineTable, ContextError> {
    trace("inline-table", move |input: &mut Input<'i>| {
        delimited(
            INLINE_TABLE_OPEN,
            cut_err(inline_table_keyvals(check).try_map(|(kv, p)| table_from_pairs(kv, p))),
            cut_err(INLINE_TABLE_CLOSE)
                .context(StrContext::Label("inline table"))
                .context(StrContext::Expected(StrContextValue::CharLiteral('}'))),
        )
        .parse_next(input)
    })
}

fn table_from_pairs(
    v: Vec<(Vec<Key>, TableKeyValue)>,
    preamble: RawString,
) -> Result<InlineTable, CustomError> {
    let mut root = InlineTable::new();
    root.set_preamble(preamble);
    // Assuming almost all pairs will be directly in `root`
    root.items.reserve(v.len());

    for (path, kv) in v {
        let table = descend_path(&mut root, &path)?;

        // "Likewise, using dotted keys to redefine tables already defined in [table] form is not allowed"
        let mixed_table_types = table.is_dotted() == path.is_empty();
        if mixed_table_types {
            return Err(CustomError::DuplicateKey {
                key: kv.key.get().into(),
                table: None,
            });
        }

        let key: InternalString = kv.key.get_internal().into();
        match table.items.entry(key) {
            Entry::Vacant(o) => {
                o.insert(kv);
            }
            Entry::Occupied(o) => {
                return Err(CustomError::DuplicateKey {
                    key: o.key().as_str().into(),
                    table: None,
                });
            }
        }
    }
    Ok(root)
}

fn descend_path<'a>(
    mut table: &'a mut InlineTable,
    path: &'a [Key],
) -> Result<&'a mut InlineTable, CustomError> {
    let dotted = !path.is_empty();
    for (i, key) in path.iter().enumerate() {
        let entry = table.entry_format(key).or_insert_with(|| {
            let mut new_table = InlineTable::new();
            new_table.set_implicit(dotted);
            new_table.set_dotted(dotted);

            Value::InlineTable(new_table)
        });
        match *entry {
            Value::InlineTable(ref mut sweet_child_of_mine) => {
                // Since tables cannot be defined more than once, redefining such tables using a
                // [table] header is not allowed. Likewise, using dotted keys to redefine tables
                // already defined in [table] form is not allowed.
                if dotted && !sweet_child_of_mine.is_implicit() {
                    return Err(CustomError::DuplicateKey {
                        key: key.get().into(),
                        table: None,
                    });
                }
                table = sweet_child_of_mine;
            }
            ref v => {
                return Err(CustomError::extend_wrong_type(path, i, v.type_name()));
            }
        }
    }
    Ok(table)
}

// inline-table-open  = %x7B ws     ; {
pub(crate) const INLINE_TABLE_OPEN: u8 = b'{';
// inline-table-close = ws %x7D     ; }
const INLINE_TABLE_CLOSE: u8 = b'}';
// inline-table-sep   = ws %x2C ws  ; , Comma
const INLINE_TABLE_SEP: u8 = b',';
// keyval-sep = ws %x3D ws ; =
pub(crate) const KEYVAL_SEP: u8 = b'=';

// inline-table-keyvals = [ inline-table-keyvals-non-empty ]
// inline-table-keyvals-non-empty =
// ( key keyval-sep val inline-table-sep inline-table-keyvals-non-empty ) /
// ( key keyval-sep val )

fn inline_table_keyvals<'i>(
    check: RecursionCheck,
) -> impl Parser<Input<'i>, (Vec<(Vec<Key>, TableKeyValue)>, RawString), ContextError> {
    move |input: &mut Input<'i>| {
        let check = check.recursing(input)?;
        (
            separated0(keyval(check), INLINE_TABLE_SEP),
            ws.span().map(RawString::with_span),
        )
            .parse_next(input)
    }
}

fn keyval<'i>(
    check: RecursionCheck,
) -> impl Parser<Input<'i>, (Vec<Key>, TableKeyValue), ContextError> {
    move |input: &mut Input<'i>| {
        (
            key,
            cut_err((
                one_of(KEYVAL_SEP)
                    .context(StrContext::Expected(StrContextValue::CharLiteral('.')))
                    .context(StrContext::Expected(StrContextValue::CharLiteral('='))),
                (ws.span(), value(check), ws.span()),
            )),
        )
            .map(|(key, (_, v))| {
                let mut path = key;
                let key = path.pop().expect("grammar ensures at least 1");

                let (pre, v, suf) = v;
                let pre = RawString::with_span(pre);
                let suf = RawString::with_span(suf);
                let v = v.decorated(pre, suf);
                (
                    path,
                    TableKeyValue {
                        key,
                        value: Item::Value(v),
                    },
                )
            })
            .parse_next(input)
    }
}

#[cfg(test)]
mod test {
    use super::*;

    #[test]
    fn inline_tables() {
        let inputs = [
            r#"{}"#,
            r#"{   }"#,
            r#"{a = 1e165}"#,
            r#"{ hello = "world", a = 1}"#,
            r#"{ hello.world = "a" }"#,
        ];
        for input in inputs {
            dbg!(input);
            let mut parsed = inline_table(Default::default()).parse(new_input(input));
            if let Ok(parsed) = &mut parsed {
                parsed.despan(input);
            }
            assert_eq!(parsed.map(|a| a.to_string()), Ok(input.to_owned()));
        }
    }

    #[test]
    fn invalid_inline_tables() {
        let invalid_inputs = [r#"{a = 1e165"#, r#"{ hello = "world", a = 2, hello = 1}"#];
        for input in invalid_inputs {
            dbg!(input);
            let mut parsed = inline_table(Default::default()).parse(new_input(input));
            if let Ok(parsed) = &mut parsed {
                parsed.despan(input);
            }
            assert!(parsed.is_err());
        }
    }
}
