use crate::spans::*;
use std::fmt;

#[derive(Clone, Debug, PartialEq, Eq)]
pub enum StringPart {
    Data(String),
    Word(&'static str),
    Inline(String),
}

impl fmt::Display for StringPart {
    fn fmt(self: &StringPart, f: &mut fmt::Formatter) -> fmt::Result {
        let rep = match self {
            StringPart::Word(s) => format!("{{{s}}}"),
            StringPart::Data(s) => s.to_string(),
            StringPart::Inline(s) => s.to_string(),
        };
        write!(f, "{}", rep)
    }
}

#[derive(Clone, Debug, PartialEq)]
pub enum Ast {
    // a special Error node
    // may come in handy?
    Error,

    And,
    Or,

    // expression nodes
    Placeholder,
    Nil,
    Boolean(bool),
    Number(f64),
    Keyword(&'static str),
    Method(&'static str, Box<Spanned<Self>>),
    Word(&'static str),
    String(&'static str),
    Interpolated(Vec<Spanned<StringPart>>),
    Block(Vec<Spanned<Self>>),
    If(Box<Spanned<Self>>, Box<Spanned<Self>>, Box<Spanned<Self>>),
    Tuple(Vec<Spanned<Self>>),
    Arguments(Vec<Spanned<Self>>),
    List(Vec<Spanned<Self>>),
    Dict(Vec<Spanned<Self>>),
    Let(Box<Spanned<Self>>, Box<Spanned<Self>>),
    LBox(&'static str, Box<Spanned<Self>>),
    Synthetic(Box<Spanned<Self>>, Box<Spanned<Self>>, Vec<Spanned<Self>>),
    When(Vec<Spanned<Self>>),
    WhenClause(Box<Spanned<Self>>, Box<Spanned<Self>>),
    Match(Box<Spanned<Self>>, Vec<Spanned<Self>>),
    Receive(Vec<Spanned<Self>>),
    MatchClause(
        Box<Spanned<Self>>,
        Box<Option<Spanned<Self>>>,
        Box<Spanned<Self>>,
    ),
    Fn(&'static str, Box<Spanned<Ast>>, Option<&'static str>),
    FnBody(Vec<Spanned<Ast>>),
    FnDeclaration(&'static str),
    Panic(Box<Spanned<Self>>),
    Do(Vec<Spanned<Self>>),
    Repeat(Box<Spanned<Self>>, Box<Spanned<Self>>),
    Splat(&'static str),
    StringPair(&'static str, Box<Spanned<Self>>),
    KeywordPair(&'static str, Box<Spanned<Self>>),
    Loop(Box<Spanned<Self>>, Vec<Spanned<Self>>),
    Recur(Vec<Spanned<Self>>),

    // pattern nodes
    NilPattern,
    BooleanPattern(bool),
    NumberPattern(f64),
    StringPattern(&'static str),
    InterpolatedPattern(Vec<Spanned<StringPart>>),
    KeywordPattern(&'static str),
    WordPattern(&'static str),
    AsPattern(&'static str, &'static str),
    Splattern(Box<Spanned<Self>>),
    PlaceholderPattern,
    TuplePattern(Vec<Spanned<Self>>),
    ListPattern(Vec<Spanned<Self>>),
    StrPairPattern(&'static str, Box<Spanned<Self>>),
    KeyPairPattern(&'static str, Box<Spanned<Self>>),
    DictPattern(Vec<Spanned<Self>>),
}

impl Ast {
    pub fn show(&self) -> String {
        use Ast::*;
        match self {
            And => "and".to_string(),
            Or => "or".to_string(),
            Error => unreachable!(),
            Nil | NilPattern => "nil".to_string(),
            String(s) | StringPattern(s) => format!("\"{s}\""),
            Interpolated(strs) | InterpolatedPattern(strs) => {
                let mut out = "".to_string();
                out = format!("\"{out}");
                for (part, _) in strs {
                    out = format!("{out}{part}");
                }
                format!("{out}\"")
            }
            Boolean(b) | BooleanPattern(b) => b.to_string(),
            Number(n) | NumberPattern(n) => n.to_string(),
            Keyword(k) | KeywordPattern(k) => format!(":{k}"),
            Method(m, args) => format!("::{m} {}", args.0),
            Word(w) | WordPattern(w) => w.to_string(),
            Block(lines) => {
                let mut out = "{\n".to_string();
                for (line, _) in lines {
                    out = format!("{out}\n  {}", line.show());
                }
                format!("{out}\n}}")
            }
            If(cond, then, r#else) => format!(
                "if {}\n  then {}\n  else {}",
                cond.0.show(),
                then.0.show(),
                r#else.0.show()
            ),
            Let(pattern, expression) => {
                format!("let {} = {}", pattern.0.show(), expression.0.show())
            }
            Dict(entries) | DictPattern(entries) => {
                format!(
                    "#{{{}}}",
                    entries
                        .iter()
                        .map(|(pair, _)| pair.show())
                        .collect::<Vec<_>>()
                        .join(", ")
                )
            }
            List(members) | ListPattern(members) => format!(
                "[{}]",
                members
                    .iter()
                    .map(|(member, _)| member.show())
                    .collect::<Vec<_>>()
                    .join(", ")
            ),
            Arguments(members) => format!(
                "({})",
                members
                    .iter()
                    .map(|(member, _)| member.show())
                    .collect::<Vec<_>>()
                    .join(", ")
            ),
            Tuple(members) | TuplePattern(members) => format!(
                "({})",
                members
                    .iter()
                    .map(|(member, _)| member.show())
                    .collect::<Vec<_>>()
                    .join(", ")
            ),
            Synthetic(root, first, rest) => format!(
                "{} {} {}",
                root.0.show(),
                first.0.show(),
                rest.iter()
                    .map(|(term, _)| term.show())
                    .collect::<Vec<_>>()
                    .join(" ")
            ),
            When(clauses) | Receive(clauses) => format!(
                "when {{\n  {}\n}}",
                clauses
                    .iter()
                    .map(|(clause, _)| clause.show())
                    .collect::<Vec<_>>()
                    .join("\n  ")
            ),
            Placeholder | PlaceholderPattern => "_".to_string(),
            LBox(name, rhs) => format!("box {name} = {}", rhs.0.show()),
            Match(scrutinee, clauses) => format!(
                "match {} with {{\n  {}\n}}",
                scrutinee.0.show(),
                clauses
                    .iter()
                    .map(|(clause, _)| clause.show())
                    .collect::<Vec<_>>()
                    .join("\n  ")
            ),
            FnBody(clauses) => clauses
                .iter()
                .map(|(clause, _)| clause.show())
                .collect::<Vec<_>>()
                .join("\n  "),
            Fn(name, body, doc) => {
                let mut out = format!("fn {name} {{\n");
                if let Some(doc) = doc {
                    out = format!("{out}  {doc}\n");
                }
                format!("{out}  {}\n}}", body.0.show())
            }
            FnDeclaration(name) => format!("fn {name}"),
            Panic(expr) => format!("panic! {}", expr.0.show()),
            Do(terms) => {
                format!(
                    "do {}",
                    terms
                        .iter()
                        .map(|(term, _)| term.show())
                        .collect::<Vec<_>>()
                        .join(" > ")
                )
            }
            Repeat(times, body) => format!("repeat {} {{\n{}\n}}", times.0.show(), body.0.show()),
            Splat(word) => format!("...{}", word),
            Splattern(pattern) => format!("...{}", pattern.0.show()),
            AsPattern(word, type_keyword) => format!("{word} as :{type_keyword}"),
            KeywordPair(key, value) | KeyPairPattern(key, value) => {
                format!(":{key} {}", value.0.show())
            }
            StringPair(key, value) | StrPairPattern(key, value) => {
                format!("\"{key}\" {}", value.0.show())
            }
            Loop(init, body) => format!(
                "loop {} with {{\n  {}\n}}",
                init.0.show(),
                body.iter()
                    .map(|(clause, _)| clause.show())
                    .collect::<Vec<_>>()
                    .join("\n  ")
            ),
            Recur(args) => format!(
                "recur ({})",
                args.iter()
                    .map(|(arg, _)| arg.show())
                    .collect::<Vec<_>>()
                    .join(", ")
            ),
            MatchClause(pattern, guard, body) => {
                let mut out = pattern.0.show();
                if let Some(guard) = guard.as_ref() {
                    out = format!("{out} if {}", guard.0.show());
                }
                format!("{out} -> {}", body.0.show())
            }
            WhenClause(cond, body) => format!("{} -> {}", cond.0.show(), body.0.show()),
        }
    }
}

impl fmt::Display for Ast {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        use Ast::*;
        match self {
            And => write!(f, "And"),
            Or => write!(f, "Or"),
            Error => write!(f, "Error"),
            Nil => write!(f, "nil"),
            String(s) => write!(f, "String: \"{}\"", s),
            Interpolated(strs) => {
                write!(
                    f,
                    "Interpolated: \"{}\"",
                    strs.iter()
                        .map(|(s, _)| s.to_string())
                        .collect::<Vec<_>>()
                        .join("")
                )
            }
            Boolean(b) => write!(f, "Boolean: {}", b),
            Number(n) => write!(f, "Number: {}", n),
            Keyword(k) => write!(f, "Keyword: :{}", k),
            Method(m, args) => write!(f, "Method: ::{m} ({})", args.0),
            Word(w) => write!(f, "Word: {}", w),
            Block(b) => write!(
                f,
                "Block: <{}>",
                b.iter()
                    .map(|(line, _)| line.to_string())
                    .collect::<Vec<_>>()
                    .join("\n")
            ),
            If(cond, then_branch, else_branch) => write!(
                f,
                "If: {} Then: {} Else: {}",
                cond.0, then_branch.0, else_branch.0
            ),
            Let(pattern, expression) => {
                write!(f, "Let: {} = {}", pattern.0, expression.0)
            }
            Dict(entries) => write!(
                f,
                "#{{{}}}",
                entries
                    .iter()
                    .map(|pair| pair.0.to_string())
                    .collect::<Vec<_>>()
                    .join(", ")
            ),
            List(l) => write!(
                f,
                "List: [{}]",
                l.iter()
                    .map(|(line, _)| line.to_string())
                    .collect::<Vec<_>>()
                    .join("\n")
            ),
            Arguments(a) => write!(
                f,
                "Arguments: ({})",
                a.iter()
                    .map(|(line, _)| line.to_string())
                    .collect::<Vec<_>>()
                    .join("\n")
            ),
            Tuple(t) => write!(
                f,
                "Tuple: ({})",
                t.iter()
                    .map(|(line, _)| line.to_string())
                    .collect::<Vec<_>>()
                    .join("\n")
            ),
            Synthetic(root, first, rest) => write!(
                f,
                "Synth: [{}, {}, {}]",
                root.0,
                first.0,
                rest.iter()
                    .map(|(term, _)| term.to_string())
                    .collect::<Vec<_>>()
                    .join("\n")
            ),
            When(clauses) | Receive(clauses) => write!(
                f,
                "When: [{}]",
                clauses
                    .iter()
                    .map(|clause| clause.0.to_string())
                    .collect::<Vec<_>>()
                    .join("\n")
            ),
            Placeholder => write!(f, "Placeholder"),
            LBox(_name, _rhs) => todo!(),
            Match(value, clauses) => {
                write!(
                    f,
                    "match: {} with {}",
                    &value.0.to_string(),
                    clauses
                        .iter()
                        .map(|clause| clause.0.to_string())
                        .collect::<Vec<_>>()
                        .join("\n")
                )
            }
            FnBody(clauses) => {
                write!(
                    f,
                    "{}",
                    clauses
                        .iter()
                        .map(|clause| clause.0.to_string())
                        .collect::<Vec<_>>()
                        .join("\n")
                )
            }
            Fn(name, body, ..) => {
                write!(f, "fn: {name}\n{}", body.0)
            }
            FnDeclaration(_name) => todo!(),
            Panic(_expr) => todo!(),
            Do(terms) => {
                write!(
                    f,
                    "do: {}",
                    terms
                        .iter()
                        .map(|(term, _)| term.to_string())
                        .collect::<Vec<_>>()
                        .join(" > ")
                )
            }
            Repeat(_times, _body) => todo!(),
            Splat(word) => {
                write!(f, "splat: {}", word)
            }
            KeywordPair(k, v) | KeyPairPattern(k, v) => {
                write!(f, "key_pair: {} {}", k, v.0)
            }
            StringPair(k, v) | StrPairPattern(k, v) => {
                write!(f, "str_pair: {k} {}", v.0)
            }
            Loop(init, body) => {
                write!(
                    f,
                    "loop: {} with {}",
                    init.0,
                    body.iter()
                        .map(|clause| clause.0.to_string())
                        .collect::<Vec<_>>()
                        .join("\n")
                )
            }
            Recur(args) => {
                write!(
                    f,
                    "recur: {}",
                    args.iter()
                        .map(|(arg, _)| arg.to_string())
                        .collect::<Vec<_>>()
                        .join(", ")
                )
            }
            MatchClause(pattern, guard, body) => {
                write!(
                    f,
                    "match clause: {} if {:?} -> {}",
                    pattern.0, guard, body.0
                )
            }
            WhenClause(cond, body) => {
                write!(f, "when clause: {} -> {}", cond.0, body.0)
            }

            NilPattern => write!(f, "nil"),
            BooleanPattern(b) => write!(f, "{}", b),
            NumberPattern(n) => write!(f, "{}", n),
            StringPattern(s) => write!(f, "{}", s),
            KeywordPattern(k) => write!(f, ":{}", k),
            WordPattern(w) => write!(f, "{}", w),
            AsPattern(w, t) => write!(f, "{} as :{}", w, t),
            Splattern(p) => write!(f, "...{}", p.0),
            PlaceholderPattern => write!(f, "_"),
            TuplePattern(t) => write!(
                f,
                "({})",
                t.iter()
                    .map(|x| x.0.to_string())
                    .collect::<Vec<_>>()
                    .join(", ")
            ),
            ListPattern(l) => write!(
                f,
                "({})",
                l.iter()
                    .map(|x| x.0.to_string())
                    .collect::<Vec<_>>()
                    .join(", ")
            ),
            DictPattern(entries) => write!(
                f,
                "#{{{}}}",
                entries
                    .iter()
                    .map(|(pair, _)| pair.to_string())
                    .collect::<Vec<_>>()
                    .join(", ")
            ),
            InterpolatedPattern(strprts) => write!(
                f,
                "interpolated: \"{}\"",
                strprts
                    .iter()
                    .map(|part| part.0.to_string())
                    .collect::<Vec<_>>()
                    .join("")
            ),
        }
    }
}
