use serde_spanned::Spanned;

use crate::alloc_prelude::*;
use crate::de::parser::array::on_array;
use crate::de::parser::key::on_key;
use crate::de::parser::prelude::*;
use crate::de::parser::value::on_scalar;
use crate::de::DeString;
use crate::de::DeTable;
use crate::de::DeValue;
use crate::map::Entry;

/// ```abnf
/// ;; Inline Table
///
/// inline-table = inline-table-open [ inline-table-keyvals ] ws-comment-newline inline-table-close
/// ```
pub(crate) fn on_inline_table<'i>(
    open_event: &toml_parser::parser::Event,
    input: &mut Input<'_>,
    source: toml_parser::Source<'i>,
    errors: &mut dyn ErrorSink,
) -> Spanned<DeValue<'i>> {
    #[cfg(feature = "debug")]
    let _scope = TraceScope::new("inline_table::on_inline_table");
    let mut result = DeTable::new();
    result.set_inline(true);
    let mut close_span = open_event.span();

    let mut state = State::default();
    while let Some(event) = input.next_token() {
        close_span = event.span();
        match event.kind() {
            EventKind::StdTableOpen
            | EventKind::ArrayTableOpen
            | EventKind::StdTableClose
            | EventKind::ArrayClose
            | EventKind::ArrayTableClose
            | EventKind::KeySep => {
                #[cfg(feature = "debug")]
                trace(
                    &format!("unexpected {event:?}"),
                    anstyle::AnsiColor::Red.on_default(),
                );
                break;
            }
            EventKind::Error => {
                #[cfg(feature = "debug")]
                trace(
                    &format!("unexpected {event:?}"),
                    anstyle::AnsiColor::Red.on_default(),
                );
                continue;
            }
            EventKind::SimpleKey => {
                let (path, key) = on_key(event, input, source, errors);
                state.capture_key(event, path, key);
            }
            EventKind::KeyValSep => {
                state.finish_key(event);
            }
            EventKind::InlineTableOpen => {
                let value = on_inline_table(event, input, source, errors);
                state.capture_value(event, value);
            }
            EventKind::ArrayOpen => {
                let value = on_array(event, input, source, errors);
                state.capture_value(event, value);
            }
            EventKind::Scalar => {
                let value = on_scalar(event, source, errors);
                state.capture_value(event, value);
            }
            EventKind::ValueSep => {
                state.finish_value(event, &mut result, errors);
            }
            EventKind::Whitespace | EventKind::Comment | EventKind::Newline => {
                state.whitespace(event);
            }
            EventKind::InlineTableClose => {
                state.finish_value(event, &mut result, errors);
                state.close(open_event, event, &mut result);
                break;
            }
        }
    }

    let span = open_event.span().start()..close_span.end();

    Spanned::new(span, DeValue::Table(result))
}

#[derive(Default)]
struct State<'i> {
    current_key: Option<(Vec<Spanned<DeString<'i>>>, Spanned<DeString<'i>>)>,
    seen_keyval_sep: bool,
    current_value: Option<Spanned<DeValue<'i>>>,
}

impl<'i> State<'i> {
    fn whitespace(&mut self, _event: &toml_parser::parser::Event) {}

    fn capture_key(
        &mut self,
        _event: &toml_parser::parser::Event,
        path: Vec<Spanned<DeString<'i>>>,
        key: Option<Spanned<DeString<'i>>>,
    ) {
        #[cfg(feature = "debug")]
        let _scope = TraceScope::new("inline_table::capture_key");
        if let Some(key) = key {
            self.current_key = Some((path, key));
        }
    }

    fn finish_key(&mut self, _event: &toml_parser::parser::Event) {
        #[cfg(feature = "debug")]
        let _scope = TraceScope::new("inline_table::finish_key");
        self.seen_keyval_sep = true;
    }

    fn capture_value(&mut self, _event: &toml_parser::parser::Event, value: Spanned<DeValue<'i>>) {
        #[cfg(feature = "debug")]
        let _scope = TraceScope::new("inline_table::capture_value");
        self.current_value = Some(value);
    }

    fn finish_value(
        &mut self,
        _event: &toml_parser::parser::Event,
        result: &mut DeTable<'i>,
        errors: &mut dyn ErrorSink,
    ) {
        #[cfg(feature = "debug")]
        let _scope = TraceScope::new("inline_table::finish_value");
        self.seen_keyval_sep = false;
        if let (Some((path, key)), Some(value)) =
            (self.current_key.take(), self.current_value.take())
        {
            let Some(table) = descend_path(result, &path, true, errors) else {
                return;
            };

            // "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 {
                #[cfg(feature = "debug")]
                trace(
                    &format!("table.dotted={}", table.is_dotted()),
                    anstyle::AnsiColor::Red.on_default(),
                );
                #[cfg(feature = "debug")]
                trace(
                    &format!("path.is_empty={}", path.is_empty()),
                    anstyle::AnsiColor::Red.on_default(),
                );
                let key_span = get_key_span(&key);
                errors.report_error(ParseError::new("duplicate key").with_unexpected(key_span));
            } else {
                let key_span = get_key_span(&key);
                match table.entry(key) {
                    Entry::Vacant(o) => {
                        o.insert(value);
                    }
                    Entry::Occupied(o) => {
                        let old_span = get_key_span(o.key());
                        errors.report_error(
                            ParseError::new("duplicate key")
                                .with_unexpected(key_span)
                                .with_context(old_span),
                        );
                    }
                }
            }
        }
    }

    fn close(
        &mut self,
        _open_event: &toml_parser::parser::Event,
        _close_event: &toml_parser::parser::Event,
        _result: &mut DeTable<'i>,
    ) {
        #[cfg(feature = "debug")]
        let _scope = TraceScope::new("inline_table::close");
    }
}

fn descend_path<'a, 'i>(
    mut table: &'a mut DeTable<'i>,
    path: &'a [Spanned<DeString<'i>>],
    dotted: bool,
    errors: &mut dyn ErrorSink,
) -> Option<&'a mut DeTable<'i>> {
    #[cfg(feature = "debug")]
    let _scope = TraceScope::new("inline_table::descend_path");
    #[cfg(feature = "debug")]
    trace(
        &format!(
            "path={:?}",
            path.iter().map(|k| k.get_ref()).collect::<Vec<_>>()
        ),
        anstyle::AnsiColor::Blue.on_default(),
    );
    for key in path.iter() {
        #[cfg(feature = "debug")]
        trace(
            &format!("path[_]={:?}", key.get_ref()),
            anstyle::AnsiColor::Blue.on_default(),
        );
        table = match table.entry(key.clone()) {
            Entry::Vacant(entry) => {
                let mut new_table = DeTable::new();
                new_table.set_implicit(true);
                new_table.set_dotted(dotted);
                new_table.set_inline(true);
                let value = DeValue::Table(new_table);
                let value = Spanned::new(key.span(), value);
                let value = entry.insert(value);
                value.as_mut().as_table_mut().unwrap()
            }
            Entry::Occupied(entry) => {
                let spanned = entry.into_mut();
                let old_span = spanned.span();
                match spanned.as_mut() {
                    DeValue::Table(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.
                        let mixed_table_types = dotted && !sweet_child_of_mine.is_implicit();
                        if mixed_table_types {
                            #[cfg(feature = "debug")]
                            trace(
                                &format!("dotted={dotted}"),
                                anstyle::AnsiColor::Red.on_default(),
                            );
                            #[cfg(feature = "debug")]
                            trace(
                                &format!(
                                    "sweet_child_of_mine.is_implicit={}",
                                    sweet_child_of_mine.is_implicit()
                                ),
                                anstyle::AnsiColor::Red.on_default(),
                            );
                            let key_span = get_key_span(key);
                            errors.report_error(
                                ParseError::new("duplicate key").with_unexpected(key_span),
                            );
                            return None;
                        }
                        sweet_child_of_mine
                    }
                    existing => {
                        let old_span =
                            toml_parser::Span::new_unchecked(old_span.start, old_span.end);
                        let key_span = get_key_span(key);
                        errors.report_error(
                            ParseError::new(format!(
                                "cannot extend value of type {} with a dotted key",
                                existing.type_str()
                            ))
                            .with_unexpected(key_span)
                            .with_context(old_span),
                        );
                        return None;
                    }
                }
            }
        };
    }
    Some(table)
}

fn get_key_span(key: &Spanned<DeString<'_>>) -> toml_parser::Span {
    let key_span = key.span();
    toml_parser::Span::new_unchecked(key_span.start, key_span.end)
}
