use std::{fmt, path::PathBuf};

use macro_rules_attribute::derive;

use crate::{
    any,
    combine::{
        parser::{Node, Parser},
        result::{ParseResult, State},
    },
    lexer::{Lexer, token::Keyword},
    many, map, opt, seq, token,
};
use rail_diag::{Display, Source};

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum Kw {
    Fn,
    Pub,
    Let,
}
impl Keyword for Kw {
    type Tag = Kw;
    fn tag(&self) -> Self::Tag {
        *self
    }
    fn matches(src: &str) -> Option<Self> {
        match src {
            "fn" => Some(Kw::Fn),
            "pub" => Some(Kw::Pub),
            "let" => Some(Kw::Let),
            _ => None,
        }
    }
}

impl fmt::Display for Kw {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            Kw::Fn => write!(f, "fn"),
            Kw::Pub => write!(f, "pub"),
            Kw::Let => write!(f, "let"),
        }
    }
}

fn lex(src: &'static str) -> Lexer<'static, Kw> {
    Lexer::from(src)
}

#[test]
fn parse_comma_separated_idents() {
    // grammar: Ident (',' Ident)*
    // parses something like: `foo, bar, baz`
    // <ident> ( ( , <ident> )* )?
    use crate::Node;
    #[derive(Debug, Node!)]
    struct Id;
    #[derive(Debug, Node!)]
    struct Fn;
    #[derive(Debug, Node!)]
    struct Pub;
    #[derive(Debug, Node!)]
    struct LParen;
    #[derive(Debug, Node!)]
    struct RParen;
    #[derive(Debug, Node!)]
    struct Colon;
    #[derive(Debug, Node!)]
    struct Comma;
    let ident = token!(Ident(_) => Id);
    let fun = token!(Keyword(Kw::Fn) => Fn);
    let vis = token!(Keyword(Kw::Pub) => Pub);
    let colon = token!(Colon => Colon);
    let comma = token!(Comma => Comma);
    let lparen = token!(LeftParen => LParen);
    let rparen = token!(RightParen => RParen);
    let param = seq!(ident, colon, ident);
    //let params = many!(seq!(param, comma));
    //let parser = seq!(
    //    opt(vis.clone()),
    //    fun.clone(),
    //    lparen.clone(),
    //    params,
    //    rparen
    //);

    let parser = seq!(vis, fun, lparen, param, comma, param, comma, param, rparen);
    let str = "pub fn (param ty, param: , :ty)";
    //let str = "fn (a,b,c,)";
    let source = Source::new(str, PathBuf::from("example.txt"));
    let mut lexer = lex(str);
    let result = parser.try_parse(&mut lexer);
    match result {
        Ok(v) => {
            println!("item: {:?}", v);
        }
        Err(diagnostics) => {
            println!("{}", diagnostics.display_with(source));
        }
    }
}

#[test]
fn parse_fn_with_return_type() {
    // grammar: ('pub')? 'fn' '(' <ident> ':' <ident> ')' '->' <ident>
    // e.g. `pub fn(x: i32) -> bool`
    use crate::Node;

    #[derive(Debug, Node!)]
    struct Id;
    #[derive(Debug, Node!)]
    struct Fn;
    #[derive(Debug, Node!)]
    struct Pub;
    #[derive(Debug, Node!)]
    struct LParen;
    #[derive(Debug, Node!)]
    struct RParen;
    #[derive(Debug, Node!)]
    struct Colon;
    #[derive(Debug, Node!)]
    struct Arrow;

    let ident = token!(Ident(_) => Id);
    let fun = token!(Keyword(Kw::Fn) => Fn);
    let vis = token!(Keyword(Kw::Pub) => Pub);
    let colon = token!(Colon => Colon);
    let lparen = token!(LeftParen => LParen);
    let rparen = token!(RightParen => RParen);
    let arrow = token!(Arrow => Arrow);

    // pub fn ( <ident> : <ident> ) -> <ident>
    let parser = seq!(vis, fun, lparen, ident, colon, ident, rparen, arrow, ident);

    let cases: &[(&'static str, &str)] = &[
        // --- happy path ---
        ("pub fn(x: i32) -> bool", "fully valid signature"),
        ("pub fn(value: String) -> Unit", "longer ident names"),
        // --- missing visibility ---
        ("fn(x: i32) -> bool", "missing pub — should sync or error"),
        // --- missing arrow / return type ---
        ("pub fn(x: i32)", "no arrow or return type"),
        ("pub fn(x: i32) ->", "arrow but missing return type ident"),
        // --- malformed param ---
        ("pub fn(x i32) -> bool", "param missing colon"),
        ("pub fn(:i32) -> bool", "param missing name"),
        ("pub fn(x:) -> bool", "param missing type"),
        // --- missing parens ---
        ("pub fn x: i32) -> bool", "missing left paren"),
        ("pub fn(x: i32 -> bool", "missing right paren"),
        // --- empty input ---
        ("", "empty input"),
        // --- garbage ---
        ("pub fn(,) -> bool", "garbage inside parens"),
    ];

    for (src, label) in cases {
        let source = Source::new(src, PathBuf::from("example.txt"));
        let mut lexer = lex(src);
        let result = parser.try_parse(&mut lexer);
        print!("[{label}] `{src}` => ");
        match result {
            Ok(v) => {
                println!("item: {:?}", v);
            }
            Err(diagnostics) => {
                println!("{}", diagnostics.display_with(source));
            }
        }
    }
}

#[test]
fn parse_choice() {
    use crate::Node;

    #[derive(Debug, Node!)]
    struct Fn;
    #[derive(Debug, Node!)]
    struct Pub;
    #[derive(Debug, Node!)]
    struct Id;

    let fun = token!(Keyword(Kw::Fn)  => Fn);
    let vis = token!(Keyword(Kw::Pub) => Pub);
    let ident = token!(Ident(_) => Id);

    // any!(fun, vis) — picks between two keywords
    // any!(fun, vis, ident) — keywords or a fallback ident
    let kw_choice = any!(fun, vis);
    let broad_choice = any!(fun, vis, ident);

    let cases: &[(&'static str, &str)] = &[
        // --- kw_choice ---
        ("fn", "kw_choice: left branch (fn)"),
        ("pub", "kw_choice: right branch (pub)"),
        ("foo", "kw_choice: neither branch — should error"),
        ("", "kw_choice: empty input"),
        // --- broad_choice ---
        ("fn", "broad_choice: first branch (fn)"),
        ("pub", "broad_choice: second branch (pub)"),
        ("foo", "broad_choice: third branch (ident)"),
        ("", "broad_choice: empty input"),
    ];

    // split so we can use different parsers per case
    let kw_cases = &cases[..4];
    let broad_cases = &cases[4..];

    for (src, label) in kw_cases {
        let source = Source::new(src, PathBuf::from("example.txt"));
        let mut lexer = lex(src);
        let result = kw_choice.try_parse(&mut lexer);
        print!("[{label}] `{src}` => ");
        match result {
            Ok(v) => {
                println!("item: {:?}", v);
            }
            Err(diagnostics) => {
                println!("{}", diagnostics.display_with(source));
            }
        }
    }

    for (src, label) in broad_cases {
        let source = Source::new(src, PathBuf::from("example.txt"));
        let mut lexer = lex(src);
        let result = broad_choice.try_parse(&mut lexer);
        print!("[{label}] `{src}` => ");
        match result {
            Ok(v) => {
                println!("item: {:?}", v);
            }
            Err(diagnostics) => {
                println!("{}", diagnostics.display_with(source));
            }
        }
    }
}

#[test]
fn parse_visibility_item() {
    // grammar: ('pub' | 'fn') ( '(' <ident> ':' <ident> ')' | <ident> )
    // i.e. either a fn-style call with a typed param, or a bare ident
    // examples:
    //   pub foo
    //   fn (x: i32)
    //   pub (x: i32)
    //   fn foo
    use crate::Node;

    #[derive(Debug, Node!)]
    struct Fn;
    #[derive(Debug, Node!)]
    struct Pub;
    #[derive(Debug, Node!)]
    struct Id;
    #[derive(Debug, Node!)]
    struct LParen;
    #[derive(Debug, Node!)]
    struct RParen;
    #[derive(Debug, Node!)]
    struct Colon;

    let fun = token!(Keyword(Kw::Fn)  => Fn);
    let vis = token!(Keyword(Kw::Pub) => Pub);
    let ident = token!(Ident(_) => Id);
    let colon = token!(Colon     => Colon);
    let lparen = token!(LeftParen => LParen);
    let rparen = token!(RightParen => RParen);

    // either a parenthesised typed param or a bare ident
    let param_block = seq!(lparen, ident, colon, ident, rparen);
    let body = any!(param_block, ident);

    // either pub or fn as the leading keyword
    let keyword = any!(vis, fun);

    let parser = seq!(keyword, body);

    let cases: &[(&'static str, &str)] = &[
        // --- happy path ---
        ("pub foo", "pub + bare ident"),
        ("fn foo", "fn + bare ident"),
        ("pub (x: i32)", "pub + typed param"),
        ("fn (x: i32)", "fn + typed param"),
        // --- wrong leading token ---
        ("foo (x: i32)", "ident instead of keyword"),
        ("(x: i32)", "no keyword at all"),
        // --- malformed param block ---
        ("pub (x i32)", "param missing colon"),
        ("pub (: i32)", "param missing name"),
        ("pub (x:)", "param missing type"),
        ("pub (x: i32", "param missing rparen"),
        // --- body missing entirely ---
        ("pub", "keyword with no body"),
        ("fn", "fn with no body"),
        // --- empty / garbage ---
        ("", "empty input"),
        ("pub ,", "garbage body token"),
    ];

    for (src, label) in cases {
        let source = Source::new(src, PathBuf::from("example.txt"));
        let mut lexer = lex(src);
        let result = parser.try_parse(&mut lexer);
        print!("[{label}] `{src}` => ");
        match result {
            Ok(v) => {
                println!("item: {:?}", v);
            }
            Err(diagnostics) => {
                println!("{}", diagnostics.display_with(source));
            }
        }
    }
}

#[test]
fn parse_let_or_fn_decl() {
    // grammar:
    //   let_stmt  := 'let' <ident> '=' ( <ident> | <literal> )
    //   fn_decl   := ('pub')? 'fn' <ident> '(' <ident> ':' <ident> ',' <ident> ':' <ident> ')'
    //
    // examples:
    //   let x = 42
    //   let x = foo
    //   fn add(a: i32, b: i32)
    //   pub fn add(a: i32, b: i32)
    use crate::Node;

    #[derive(Debug, Node!)]
    struct Fn;
    #[derive(Debug, Node!)]
    struct Pub;
    #[derive(Debug, Node!)]
    struct Let;
    #[derive(Debug, Node!)]
    struct Ident;
    #[derive(Debug, Node!)]
    struct Lit;
    #[derive(Debug, Node!)]
    struct LParen;
    #[derive(Debug, Node!)]
    struct RParen;
    #[derive(Debug, Node!)]
    struct Colon;
    #[derive(Debug, Node!)]
    struct Comma;
    #[derive(Debug, Node!)]
    struct Eq;

    #[derive(Debug, Node!)]
    struct LetStmt;
    #[derive(Debug, Node!)]
    struct Param;
    #[derive(Debug, Node!)]
    struct Params;
    #[derive(Debug, Node!)]
    struct FnDecl;
    #[derive(Debug, Node!)]
    struct Item;

    let fun = token!(Keyword(Kw::Fn)       => Fn);
    let vis = token!(Keyword(Kw::Pub)     => Pub);
    let let_kw = token!(Keyword(Kw::Let)  => Let);
    let ident = token!(Ident(_)            => Ident);
    let lit = token!(Literal(_)           => Lit);
    let colon = token!(Colon            => Colon);
    let comma = token!(Comma            => Comma);
    let lparen = token!(LeftParen      => LParen);
    let rparen = token!(RightParen     => RParen);
    let assign = token!(Assign             => Eq);

    // let x = <ident | literal>
    let let_rhs = any!(ident, lit);
    let let_stmt = seq!(let_kw, ident, assign, let_rhs; |_| LetStmt);
    // (name: ty, name: ty)
    let param = seq!(ident, colon, ident; |_| Param);
    let param_list = seq!(lparen, param, comma, param, rparen; |_| Params);
    // ('pub')? 'fn' <ident> ( param, param )
    let fn_decl = seq!(any!(seq!(vis, fun), fun), ident, param_list; |_| {
        FnDecl
    });
    let parser = any!(let_stmt, fn_decl; |_| Item);

    let cases: &[(&'static str, &str)] = &[
        // --- let stmt happy path ---
        ("let x = 42", "let with literal"),
        ("let x = foo", "let with ident"),
        // --- fn decl happy path ---
        ("fn add(a: i32, b: i32)", "bare fn decl"),
        ("pub fn add(a: i32, b: i32)", "pub fn decl"),
        // --- let stmt errors ---
        ("let = 42", "let missing binding name"),
        ("let x 42", "let missing equals"),
        ("let x =", "let missing rhs"),
        ("let x = ,", "let garbage rhs"),
        // --- fn decl errors ---
        ("fn add(a: i32)", "fn decl only one param"),
        ("fn add(a i32, b: i32)", "first param missing colon"),
        ("fn add(a: i32, b i32)", "second param missing colon"),
        ("fn add(a: i32, b: i32", "fn decl missing rparen"),
        ("fn (a: i32, b: i32)", "fn decl missing name"),
        ("pub add(a: i32, b: i32)", "pub but missing fn keyword"),
        // --- wrong leading token ---
        ("add(a: i32, b: i32)", "no leading keyword"),
        ("42", "bare literal"),
        // --- empty / garbage ---
        ("", "empty input"),
        ("let fn pub", "all keywords no structure"),
    ];

    for (src, label) in cases {
        let source = Source::new(src, PathBuf::from("example.txt"));
        let mut lexer = lex(src);
        let result = parser.try_parse(&mut lexer);
        print!("[{label}] `{src}` => ");
        match result {
            Ok(v) => {
                println!("item: {:?}", v);
            }
            Err(diagnostics) => {
                println!("{}", diagnostics.display_with(source));
            }
        }
    }
}

#[test]
fn parse_many() {
    // grammar: ('let' <ident> '=' <ident>)*
    // parses zero or more let-bindings in sequence
    use crate::Node;

    #[derive(Debug, Node!)]
    struct Let;
    #[derive(Debug, Node!)]
    struct Id;
    #[derive(Debug, Node!)]
    struct Eq;
    #[derive(Debug, Node!)]
    struct Stmt;

    let let_kw = token!(Keyword(Kw::Let) => Let);
    let ident = token!(Ident(_)         => Id);
    let assign = token!(Assign           => Eq);

    let let_stmt = seq!(let_kw, ident, assign, ident; |_| Stmt);
    let parser = many!(let_stmt);

    let cases: &[(&'static str, &str)] = &[
        // --- happy path ---
        ("", "empty input — zero iterations"),
        ("let x = y", "single statement"),
        ("let x = y let a = b", "two statements"),
        ("let x = y let a = b let p = q", "three statements"),
        // --- partial / malformed ---
        ("let x = y let a =", "second stmt missing rhs"),
        ("let x = y foo", "trailing garbage after valid stmt"),
        ("let x y", "missing equals"),
        ("let = y", "missing binding name"),
        // --- only garbage ---
        ("fn pub let", "keywords but no valid structure"),
    ];

    for (src, label) in cases {
        let source = Source::new(src, PathBuf::from("example.txt"));
        let mut lexer = lex(src);
        let result = parser.try_parse(&mut lexer);
        print!("[{label}] `{src}` => ");
        match result {
            Ok(v) => {
                println!("item: {:?}", v);
            }
            Err(diagnostics) => {
                println!("{}", diagnostics.display_with(source));
            }
        }
    }
}

#[test]
fn parse_many_alternatives() {
    // grammar: ('pub'? 'let' <ident> '=' <ident>
    //         | 'pub'? 'fn' <ident> '(' <ident> ':' <ident> ')'
    //         | 'let' <ident>)*
    // parses zero or more optional-visibility let-bindings, fn declarations,
    // or bare let bindings in any order
    use crate::Node;

    #[derive(Debug, Node!)]
    struct Let;
    #[derive(Debug, Node!)]
    struct Fn;
    #[derive(Debug, Node!)]
    struct Pub;
    #[derive(Debug, Node!)]
    struct Id;
    #[derive(Debug, Node!)]
    struct Eq;
    #[derive(Debug, Node!)]
    struct LParen;
    #[derive(Debug, Node!)]
    struct RParen;
    #[derive(Debug, Node!)]
    struct Colon;
    #[derive(Debug, Node!)]
    struct Comma;
    #[derive(Debug, Node!)]
    struct Arrow;
    #[derive(Debug, Node!)]
    struct LetStmt;
    #[derive(Debug, Node!)]
    struct FnDecl;
    #[derive(Debug, Node!)]
    struct LetDecl;
    #[derive(Debug, Node!)]
    struct Item;
    #[derive(Debug, Node!)]
    #[derive_args(name = "parameter")]
    struct Param;

    #[derive(Debug, Node!)]
    #[derive_args(name = "return type")]
    struct RetTy;

    let let_kw = token!(Keyword(Kw::Let));
    let fn_kw = token!(Keyword(Kw::Fn));
    let vis = token!(Keyword(Kw::Pub));
    let ident = token!(Ident(_));
    let assign = token!(Assign);
    let colon = token!(Colon);
    let comma = token!(Comma);
    let lparen = token!(LeftParen);
    let rparen = token!(RightParen);
    let arrow = token!(Arrow);

    // 'pub'? 'let' <ident> '=' <ident>
    let let_stmt = seq!(let_kw, ident, assign, ident; |_| LetStmt);

    // 'pub'? 'fn' <ident> '(' <ident> ':' <ident> (',' <ident> ':' <ident>)* ')' ('->' <ident>)?
    let param = seq!(ident, colon, ident; |_| Param);
    let params = many!(seq!(param, comma));
    let ret_type = opt!(seq!(arrow, ident; |_| RetTy));
    let fn_decl = seq!(opt!(vis), fn_kw, ident, lparen, params, rparen, ret_type; |_| FnDecl);

    // 'let' <ident>  (bare binding, no rhs)
    let let_decl = seq!(let_kw, ident; |_| LetDecl);

    let item = any!(let_stmt, fn_decl; |_| Item);
    let parser = many!(item);

    let cases: &[(&'static str, &str)] = &[
        // --- happy path: single items ---
        ("", "empty — zero iterations"),
        ("let x = y", "single let stmt"),
        ("let x = y", "pub let stmt"),
        ("fn foo(x: i32)", "single fn, one param"),
        ("pub fn foo(x: i32)", "pub fn, one param"),
        ("fn foo(x: i32) -> bool", "fn with return type"),
        ("pub fn foo(x: i32) -> bool", "pub fn with return type"),
        ("fn foo(x: i32, y: u8)", "fn with two params"),
        ("fn foo(x: i32, y: u8, z: f32)", "fn with three params"),
        (
            "fn foo(x: i32, y: u8) -> bool",
            "fn multi-param with return type",
        ),
        ("let x", "bare let decl"),
        // --- happy path: sequences ---
        ("let x = y let a = b", "two let stmts"),
        ("fn foo(x: i32) fn bar(y: u8)", "two fns"),
        ("let x = y fn foo(x: i32)", "let then fn"),
        ("fn foo(x: i32) let x = y", "fn then let"),
        ("let x fn foo(x: i32) let y = z", "bare let amid others"),
        (
            "let a = b fn f(x: i32) -> bool pub fn g(y: u8, z: f32) let q",
            "mixed pub/bare, multi-param, return types",
        ),
        // --- partial / malformed ---
        ("let x = y fn foo(x i32)", "fn param missing colon"),
        ("let x = fn foo(x: i32)", "let missing rhs ident"),
        ("fn foo(x: i32) let x =", "let stmt missing rhs"),
        ("pub fn foo(x: i32, ) -> bool", "trailing comma in params"),
        ("fn foo(x: i32 -> bool", "missing rparen"),
        // --- garbage between valid items ---
        ("let x = y , fn foo(x: i32)", "garbage between items"),
        ("fn foo(x: i32) pub pub let x = y", "double pub"),
        // --- only garbage ---
        ("let fn", "no valid structure"),
        ("( ) =", "only punctuation"),
    ];

    for (src, label) in cases {
        let source = Source::new(src, PathBuf::from("example.txt"));
        let mut lexer = lex(src);
        let result = parser.try_parse(&mut lexer);
        print!("[{label}] `{src}` => ");
        match result {
            Ok(v) => {
                println!("item: {:?}", v);
            }
            Err(diagnostics) => {
                println!("{}", diagnostics.display_with(source));
            }
        }
    }
}
