use std::iter::{FromIterator, Peekable};

use proc_macro::token_stream::IntoIter as StreamIter;
use proc_macro::{Delimiter, Group, Ident, Literal, Punct, Spacing, Span, TokenStream, TokenTree};
use quote::quote_spanned;

use super::shared::PathComponent;

pub(crate) struct KeyPathMacroInput {
    pub(crate) root: proc_macro2::TokenStream,
    pub(crate) components: Vec<SpannedComponent>,
}

pub(crate) struct SpannedComponent {
    element: PathComponent,
    span: Span,
}

pub(crate) struct SyntaxError {
    message: String,
    span: Span,
}

enum FieldLiteral {
    Named(String),
    Unnamed(usize),
    UnnamedPair(usize, usize),
}

type TokenIter = Peekable<StreamIter>;

impl SpannedComponent {
    pub(crate) fn traverse_type(&self) -> proc_macro2::TokenStream {
        self.element.mirror_item_access(self.span.into())
    }

    pub(crate) fn to_tokens(&self) -> proc_macro2::TokenStream {
        let tokens = self.element.path_component_tokens();
        let span = self.span.into();
        quote_spanned!(span=> #tokens)
    }
}

impl KeyPathMacroInput {
    pub(crate) fn parse(input: TokenStream) -> Result<Self, SyntaxError> {
        let mut iter = input.into_iter().peekable();
        //next_token(&mut iter);

        let root = expect_root(&mut iter)?.into();
        let components = collect_path_components(&mut iter)?;
        Ok(KeyPathMacroInput { root, components })
    }
}

fn expect_root(iter: &mut TokenIter) -> Result<TokenStream, SyntaxError> {
    let mut result = Vec::new();
    let root = next_token(iter)?;
    match &root {
        TokenTree::Ident(_) => result.push(root),
        _other => {
            return Err(SyntaxError::new(
                root.span(),
                "Keypath should start with Type",
            ))
        }
    }

    if matches!(iter.peek(), Some(&TokenTree::Punct(ref p)) if p.as_char() == '<') {
        result.extend(expect_root_generics(iter)?);
    }
    Ok(result.into_iter().collect())
}

fn expect_root_generics(iter: &mut TokenIter) -> Result<Vec<TokenTree>, SyntaxError> {
    let mut result = Vec::new();
    result.push(TokenTree::Punct(expect_punct(iter, '<')?));
    let mut done = false;
    for token in iter {
        done = matches!(token, TokenTree::Punct(ref p) if p.as_char() == '>');
        result.push(token);
        if done {
            break;
        }
    }
    if !done {
        Err(SyntaxError::new(
            result.first().unwrap().span(),
            "Missing closing '>'",
        ))
    } else {
        Ok(result)
    }
}

fn collect_path_components(iter: &mut TokenIter) -> Result<Vec<SpannedComponent>, SyntaxError> {
    let mut result = Vec::new();
    loop {
        match iter.next() {
            None => return Ok(result),
            Some(TokenTree::Punct(p)) if p.as_char() == '.' => match iter
                .next()
                .ok_or_else(|| SyntaxError::new(p.span(), "'.' must be followed by a field"))?
            {
                TokenTree::Ident(ident) => result.push(SpannedComponent {
                    span: ident.span(),
                    element: PathComponent::named(ident.to_string()),
                }),
                TokenTree::Literal(lit) => append_fields_from_lit(&lit, &mut result)?,
                other => {
                    return Err(SyntaxError::new(
                        other.span(),
                        format!("expected field identifier, found '{}'", other),
                    ))
                }
            },
            Some(TokenTree::Group(g)) if matches!(g.delimiter(), Delimiter::Bracket) => {
                result.push(expect_index(&g)?);
            }
            Some(other) => {
                eprintln!("BAD TOKEN {:?}", other);
                return Err(SyntaxError::new(other.span(), "expected '.' or '['"));
            }
        }
    }
}

fn append_fields_from_lit(
    lit: &Literal,
    result: &mut Vec<SpannedComponent>,
) -> Result<(), SyntaxError> {
    let span = lit.span();
    match parse_literal(lit)? {
        FieldLiteral::Named(name) => result.push(SpannedComponent {
            element: PathComponent::named(name),
            span,
        }),
        FieldLiteral::Unnamed(idx) => result.push(SpannedComponent {
            element: PathComponent::unnamed(idx),
            span,
        }),
        FieldLiteral::UnnamedPair(first, second) => {
            result.push(SpannedComponent {
                element: PathComponent::unnamed(first),
                span,
            });
            result.push(SpannedComponent {
                element: PathComponent::unnamed(second),
                span,
            });
        }
    };
    Ok(())
}

fn expect_index(g: &Group) -> Result<SpannedComponent, SyntaxError> {
    let mut tokens = g.stream().into_iter();
    let lit = match tokens.next() {
        Some(TokenTree::Literal(lit)) => lit,
        _ => {
            return Err(SyntaxError::new(
                g.span(),
                "Brackets must contain a string or integer literal",
            ))
        }
    };
    match parse_literal(&lit)? {
        FieldLiteral::Named(name) => Ok(SpannedComponent {
            element: PathComponent::IndexStr(name),
            span: lit.span(),
        }),
        FieldLiteral::Unnamed(idx) => Ok(SpannedComponent {
            element: PathComponent::IndexInt(idx),
            span: lit.span(),
        }),
        FieldLiteral::UnnamedPair(..) => Err(SyntaxError::new(
            lit.span(),
            "collection indices must be string or unsigned integer literals",
        )),
    }
}

fn parse_literal(lit: &Literal) -> Result<FieldLiteral, SyntaxError> {
    let raw_lit = lit.to_string();
    if raw_lit.starts_with('"') {
        return Ok(FieldLiteral::Named(raw_lit.trim_matches('"').to_string()));
    }

    if let Ok(idx) = raw_lit.parse::<usize>() {
        return Ok(FieldLiteral::Unnamed(idx));
    }

    // see if it's a float, where both sides of the decimal place are valid usizes
    raw_lit
        .split_once('.')
        .and_then(|(front, back)| {
            let front = front.parse::<usize>().ok()?;
            let back = back.parse::<usize>().ok()?;
            Some(FieldLiteral::UnnamedPair(front, back))
        })
        .ok_or_else(|| SyntaxError::new(lit.span(), "identifiers must be strings or integers"))
}

fn next_token(iter: &mut TokenIter) -> Result<TokenTree, SyntaxError> {
    iter.next().ok_or_else(|| SyntaxError {
        message: "unexpected end of input".to_owned(),
        span: Span::call_site(),
    })
}

fn expect_punct(iter: &mut TokenIter, chr: char) -> Result<Punct, SyntaxError> {
    match next_token(iter)? {
        TokenTree::Punct(p) if p.as_char() == chr => Ok(p),
        other => Err(SyntaxError::new(
            other.span(),
            format!("expected '{}', found '{}'", chr, other),
        )),
    }
}

impl SyntaxError {
    fn new(span: Span, message: impl std::fmt::Display) -> Self {
        SyntaxError {
            message: message.to_string(),
            span,
        }
    }

    pub(crate) fn into_compile_error(self) -> TokenStream {
        // compile_error! { $message }
        TokenStream::from_iter(vec![
            TokenTree::Ident(Ident::new("compile_error", self.span)),
            TokenTree::Punct({
                let mut punct = Punct::new('!', Spacing::Alone);
                punct.set_span(self.span);
                punct
            }),
            TokenTree::Group({
                let mut group = Group::new(Delimiter::Brace, {
                    TokenStream::from_iter(vec![TokenTree::Literal({
                        let mut string = Literal::string(&self.message);
                        string.set_span(self.span);
                        string
                    })])
                });
                group.set_span(self.span);
                group
            }),
        ])
    }
}