use core::fmt;

use super::def::*;
use crate::ty::{Interned, Ty};

/// Prints an expression to a writer.
pub(in crate::syntax) struct ExprPrinter<'a, T: fmt::Write> {
    f: &'a mut T,
    indent: usize,
}

impl<'a, T: fmt::Write> ExprPrinter<'a, T> {
    /// Creates a new expression printer.
    pub fn new(f: &'a mut T) -> Self {
        Self { f, indent: 0 }
    }

    /// Writes a declaration.
    pub fn write_decl(&mut self, decl: &Decl) -> fmt::Result {
        write!(self.f, "{decl:?}")
    }

    /// Writes an expression.
    pub fn write_expr(&mut self, expr: &Expr) -> fmt::Result {
        match expr {
            Expr::Block(exprs) => self.write_seq(exprs),
            Expr::Array(elems) => self.write_array(&elems.args),
            Expr::Dict(elems) => self.write_dict(&elems.args),
            Expr::Args(args) => self.write_args(&args.args),
            Expr::Pattern(pat) => self.write_pattern(pat),
            Expr::Element(elem) => self.write_element(elem),
            Expr::Unary(unary) => self.write_unary(unary),
            Expr::Binary(binary) => self.write_binary(binary),
            Expr::Apply(apply) => self.write_apply(apply),
            Expr::Func(func) => self.write_func(func),
            Expr::Let(let_expr) => self.write_let(let_expr),
            Expr::Show(show) => self.write_show(show),
            Expr::Set(set) => self.write_set(set),
            Expr::Ref(reference) => self.write_ref(reference),
            Expr::ContentRef(content_ref) => self.write_content_ref(content_ref),
            Expr::Select(sel) => self.write_select(sel),
            Expr::Import(import) => self.write_import(import),
            Expr::Include(include) => self.write_include(include),
            Expr::Contextual(contextual) => self.write_contextual(contextual),
            Expr::Conditional(if_expr) => self.write_conditional(if_expr),
            Expr::WhileLoop(while_expr) => self.write_while_loop(while_expr),
            Expr::ForLoop(for_expr) => self.write_for_loop(for_expr),
            Expr::Type(ty) => self.write_type(ty),
            Expr::Decl(decl) => self.write_decl(decl),
            Expr::Star => self.write_star(),
        }
    }

    /// Writes an indentation.
    fn write_indent(&mut self) -> fmt::Result {
        write!(self.f, "{:indent$}", "", indent = self.indent)
    }

    /// Writes a sequence of expressions.
    fn write_seq(&mut self, exprs: &Interned<Vec<Expr>>) -> fmt::Result {
        writeln!(self.f, "[")?;
        self.indent += 1;
        for expr in exprs.iter() {
            self.write_indent()?;
            self.write_expr(expr)?;
            self.f.write_str(",\n")?;
        }
        self.indent -= 1;
        self.write_indent()?;
        write!(self.f, "]")
    }

    /// Writes an array.
    fn write_array(&mut self, elems: &[ArgExpr]) -> fmt::Result {
        writeln!(self.f, "(")?;
        self.indent += 1;
        for arg in elems.iter() {
            self.write_indent()?;
            self.write_arg(arg)?;
            self.f.write_str(",\n")?;
        }
        self.indent -= 1;
        self.write_indent()?;
        write!(self.f, ")")
    }

    /// Writes a dictionary.
    fn write_dict(&mut self, elems: &[ArgExpr]) -> fmt::Result {
        writeln!(self.f, "(:")?;
        self.indent += 1;
        for arg in elems.iter() {
            self.write_indent()?;
            self.write_arg(arg)?;
            self.f.write_str(",\n")?;
        }
        self.indent -= 1;
        self.write_indent()?;
        write!(self.f, ")")
    }

    /// Writes a list of arguments.
    fn write_args(&mut self, args: &[ArgExpr]) -> fmt::Result {
        writeln!(self.f, "(")?;
        for arg in args.iter() {
            self.write_indent()?;
            self.write_arg(arg)?;
            self.f.write_str(",\n")?;
        }
        self.write_indent()?;
        write!(self.f, ")")
    }

    /// Writes an argument.
    fn write_arg(&mut self, arg: &ArgExpr) -> fmt::Result {
        match arg {
            ArgExpr::Pos(pos) => self.write_expr(pos),
            ArgExpr::Named(named) => {
                let (name, val) = named.as_ref();
                write!(self.f, "{name:?}: ")?;
                self.write_expr(val)
            }
            ArgExpr::NamedRt(named) => {
                let (key, val) = named.as_ref();
                self.write_expr(key)?;
                write!(self.f, ": ")?;
                self.write_expr(val)
            }
            ArgExpr::Spread(spread) => {
                write!(self.f, "..")?;
                self.write_expr(spread)
            }
        }
    }

    /// Writes a pattern.
    pub fn write_pattern(&mut self, pat: &Pattern) -> fmt::Result {
        match pat {
            Pattern::Expr(expr) => self.write_expr(expr),
            Pattern::Simple(decl) => self.write_decl(decl),
            Pattern::Sig(sig) => self.write_pattern_sig(sig),
        }
    }

    /// Writes a pattern signature.
    fn write_pattern_sig(&mut self, sig: &PatternSig) -> fmt::Result {
        self.f.write_str("pat(\n")?;
        self.indent += 1;
        for pos in &sig.pos {
            self.write_indent()?;
            self.write_pattern(pos)?;
            self.f.write_str(",\n")?;
        }
        for (name, named) in &sig.named {
            self.write_indent()?;
            write!(self.f, "{name:?} = ")?;
            self.write_pattern(named)?;
            self.f.write_str(",\n")?;
        }
        if let Some((name, spread_left)) = &sig.spread_left {
            self.write_indent()?;
            write!(self.f, "..{name:?}: ")?;
            self.write_pattern(spread_left)?;
            self.f.write_str(",\n")?;
        }
        if let Some((name, spread_right)) = &sig.spread_right {
            self.write_indent()?;
            write!(self.f, "..{name:?}: ")?;
            self.write_pattern(spread_right)?;
            self.f.write_str(",\n")?;
        }
        self.indent -= 1;
        self.write_indent()?;
        self.f.write_str(")")
    }

    /// Writes an element.
    fn write_element(&mut self, elem: &Interned<ElementExpr>) -> fmt::Result {
        self.f.write_str("elem(\n")?;
        self.indent += 1;
        for v in &elem.content {
            self.write_indent()?;
            self.write_expr(v)?;
            self.f.write_str(",\n")?;
        }
        self.indent -= 1;
        self.write_indent()?;
        self.f.write_str(")")
    }

    /// Writes a unary expression.
    fn write_unary(&mut self, unary: &Interned<UnExpr>) -> fmt::Result {
        write!(self.f, "un({:?})(", unary.op)?;
        self.write_expr(&unary.lhs)?;
        self.f.write_str(")")
    }

    /// Writes a binary expression.
    fn write_binary(&mut self, binary: &Interned<BinExpr>) -> fmt::Result {
        let [lhs, rhs] = binary.operands();
        write!(self.f, "bin({:?})(", binary.op)?;
        self.write_expr(lhs)?;
        self.f.write_str(", ")?;
        self.write_expr(rhs)?;
        self.f.write_str(")")
    }

    /// Writes a function application ex
    fn write_apply(&mut self, apply: &Interned<ApplyExpr>) -> fmt::Result {
        write!(self.f, "apply(")?;
        self.write_expr(&apply.callee)?;
        self.f.write_str(", ")?;
        self.write_expr(&apply.args)?;
        write!(self.f, ")")
    }

    /// Writes a function.
    fn write_func(&mut self, func: &Interned<FuncExpr>) -> fmt::Result {
        write!(self.f, "func[{:?}](", func.decl)?;
        self.write_pattern_sig(&func.params)?;
        write!(self.f, " = ")?;
        self.write_expr(&func.body)?;
        write!(self.f, ")")
    }

    /// Writes a let expression.
    fn write_let(&mut self, let_expr: &Interned<LetExpr>) -> fmt::Result {
        write!(self.f, "let(")?;
        self.write_pattern(&let_expr.pattern)?;
        if let Some(body) = &let_expr.body {
            write!(self.f, " = ")?;
            self.write_expr(body)?;
        }
        write!(self.f, ")")
    }

    /// Writes a show expression.
    fn write_show(&mut self, show: &Interned<ShowExpr>) -> fmt::Result {
        write!(self.f, "show(")?;
        if let Some(selector) = &show.selector {
            self.write_expr(selector)?;
            self.f.write_str(", ")?;
        }
        self.write_expr(&show.edit)?;
        write!(self.f, ")")
    }

    /// Writes a set expression.
    fn write_set(&mut self, set: &Interned<SetExpr>) -> fmt::Result {
        write!(self.f, "set(")?;
        self.write_expr(&set.target)?;
        self.f.write_str(", ")?;
        self.write_expr(&set.args)?;
        if let Some(cond) = &set.cond {
            self.f.write_str(", ")?;
            self.write_expr(cond)?;
        }
        write!(self.f, ")")
    }

    /// Writes a reference expression.
    fn write_ref(&mut self, reference: &Interned<RefExpr>) -> fmt::Result {
        write!(self.f, "ref({:?}", reference.decl)?;
        if let Some(step) = &reference.step {
            self.f.write_str(", step = ")?;
            self.write_expr(step)?;
        }
        if let Some(of) = &reference.root {
            self.f.write_str(", root = ")?;
            self.write_expr(of)?;
        }
        if let Some(val) = &reference.term {
            write!(self.f, ", val = {val:?}")?;
        }
        self.f.write_str(")")
    }

    /// Writes a content reference expression.
    fn write_content_ref(&mut self, content_ref: &Interned<ContentRefExpr>) -> fmt::Result {
        write!(self.f, "content_ref({:?}", content_ref.ident)?;
        if let Some(of) = &content_ref.of {
            self.f.write_str(", ")?;
            self.write_decl(of)?;
        }
        if let Some(val) = &content_ref.body {
            self.write_expr(val)?;
        }
        self.f.write_str(")")
    }

    /// Writes a select expression.
    fn write_select(&mut self, sel: &Interned<SelectExpr>) -> fmt::Result {
        write!(self.f, "(")?;
        self.write_expr(&sel.lhs)?;
        self.f.write_str(").")?;
        self.write_decl(&sel.key)
    }

    /// Writes an import expression.
    fn write_import(&mut self, import: &Interned<ImportExpr>) -> fmt::Result {
        self.f.write_str("import(")?;
        self.write_decl(&import.decl.decl)?;
        self.f.write_str(")")
    }

    /// Writes an include expression.
    fn write_include(&mut self, include: &Interned<IncludeExpr>) -> fmt::Result {
        self.f.write_str("include(")?;
        self.write_expr(&include.source)?;
        self.f.write_str(")")
    }

    /// Writes a contextual expression.
    fn write_contextual(&mut self, contextual: &Interned<Expr>) -> fmt::Result {
        self.f.write_str("contextual(")?;
        self.write_expr(contextual)?;
        self.f.write_str(")")
    }

    /// Writes a conditional expression.
    fn write_conditional(&mut self, if_expr: &Interned<IfExpr>) -> fmt::Result {
        self.f.write_str("if(")?;
        self.write_expr(&if_expr.cond)?;
        self.f.write_str(", then = ")?;
        self.write_expr(&if_expr.then)?;
        self.f.write_str(", else = ")?;
        self.write_expr(&if_expr.else_)?;
        self.f.write_str(")")
    }

    /// Writes a while loop expression.
    fn write_while_loop(&mut self, while_expr: &Interned<WhileExpr>) -> fmt::Result {
        self.f.write_str("while(")?;
        self.write_expr(&while_expr.cond)?;
        self.f.write_str(", ")?;
        self.write_expr(&while_expr.body)?;
        self.f.write_str(")")
    }

    /// Writes a for loop expression.
    fn write_for_loop(&mut self, for_expr: &Interned<ForExpr>) -> fmt::Result {
        self.f.write_str("for(")?;
        self.write_pattern(&for_expr.pattern)?;
        self.f.write_str(", ")?;
        self.write_expr(&for_expr.iter)?;
        self.f.write_str(", ")?;
        self.write_expr(&for_expr.body)?;
        self.f.write_str(")")
    }

    /// Writes a type.
    fn write_type(&mut self, ty: &Ty) -> fmt::Result {
        let formatted = ty.describe();
        let formatted = formatted.as_deref().unwrap_or("any");
        self.f.write_str(formatted)
    }

    /// Writes a star expression.
    fn write_star(&mut self) -> fmt::Result {
        self.f.write_str("*")
    }
}

/// Describes an expression to a writer.
pub(in crate::syntax) struct ExprDescriber<'a, T: fmt::Write> {
    f: &'a mut T,
    indent: usize,
}

impl<'a, T: fmt::Write> ExprDescriber<'a, T> {
    /// Creates a new expression describer.
    pub fn new(f: &'a mut T) -> Self {
        Self { f, indent: 0 }
    }

    /// Writes a declaration.
    pub fn write_decl(&mut self, decl: &Decl) -> fmt::Result {
        use DefKind::*;
        let shorter = matches!(decl.kind(), Function | Variable | Module);
        if shorter && !decl.name().is_empty() {
            return write!(self.f, "{}", decl.name());
        }

        write!(self.f, "{decl:?}")
    }

    /// Writes an expression.
    pub fn write_expr(&mut self, expr: &Expr) -> fmt::Result {
        match expr {
            Expr::Block(..) => self.f.write_str("Expr(..)"),
            Expr::Array(elems) => self.write_array(&elems.args),
            Expr::Dict(elems) => self.write_dict(&elems.args),
            Expr::Args(args) => self.write_args(&args.args),
            Expr::Pattern(pat) => self.write_pattern(pat),
            Expr::Element(elem) => self.write_element(elem),
            Expr::Unary(unary) => self.write_unary(unary),
            Expr::Binary(binary) => self.write_binary(binary),
            Expr::Apply(apply) => self.write_apply(apply),
            Expr::Func(func) => self.write_func(func),
            Expr::Ref(ref_expr) => self.write_ref(ref_expr),
            Expr::ContentRef(content_ref) => self.write_content_ref(content_ref),
            Expr::Select(sel) => self.write_select(sel),
            Expr::Import(import) => self.write_import(import),
            Expr::Include(include) => self.write_include(include),
            Expr::Contextual(..) => self.f.write_str("content"),
            Expr::Let(..) | Expr::Show(..) | Expr::Set(..) => self.f.write_str("Expr(..)"),
            Expr::Conditional(..) | Expr::WhileLoop(..) | Expr::ForLoop(..) => {
                self.f.write_str("Expr(..)")
            }
            Expr::Type(ty) => self.write_type(ty),
            Expr::Decl(decl) => self.write_decl(decl),
            Expr::Star => self.f.write_str("*"),
        }
    }

    /// Writes an indentation.
    fn write_indent(&mut self) -> fmt::Result {
        write!(self.f, "{:indent$}", "", indent = self.indent)
    }

    /// Writes an array.
    fn write_array(&mut self, elems: &[ArgExpr]) -> fmt::Result {
        if elems.len() <= 1 {
            self.f.write_char('(')?;
            if let Some(arg) = elems.first() {
                self.write_arg(arg)?;
                self.f.write_str(",")?
            }
            return self.f.write_str(")");
        }

        writeln!(self.f, "(")?;
        self.indent += 1;
        for arg in elems.iter() {
            self.write_indent()?;
            self.write_arg(arg)?;
            self.f.write_str(",\n")?;
        }
        self.indent -= 1;
        self.write_indent()?;
        write!(self.f, ")")
    }

    /// Writes a dictionary.
    fn write_dict(&mut self, elems: &[ArgExpr]) -> fmt::Result {
        if elems.len() <= 1 {
            self.f.write_char('(')?;
            if let Some(arg) = elems.first() {
                self.write_arg(arg)?;
            } else {
                self.f.write_str(":")?
            }
            return self.f.write_str(")");
        }

        writeln!(self.f, "(:")?;
        self.indent += 1;
        for arg in elems.iter() {
            self.write_indent()?;
            self.write_arg(arg)?;
            self.f.write_str(",\n")?;
        }
        self.indent -= 1;
        self.write_indent()?;
        write!(self.f, ")")
    }

    /// Writes a list of arguments.
    fn write_args(&mut self, args: &[ArgExpr]) -> fmt::Result {
        writeln!(self.f, "(")?;
        for arg in args.iter() {
            self.write_indent()?;
            self.write_arg(arg)?;
            self.f.write_str(",\n")?;
        }
        self.write_indent()?;
        write!(self.f, ")")
    }

    /// Writes an argument.
    fn write_arg(&mut self, arg: &ArgExpr) -> fmt::Result {
        match arg {
            ArgExpr::Pos(pos) => self.write_expr(pos),
            ArgExpr::Named(named) => {
                let (k, v) = named.as_ref();
                self.write_decl(k)?;
                write!(self.f, ": ")?;
                self.write_expr(v)
            }
            ArgExpr::NamedRt(named) => {
                let n = named.as_ref();
                self.write_expr(&n.0)?;
                write!(self.f, ": ")?;
                self.write_expr(&n.1)
            }
            ArgExpr::Spread(spread) => {
                write!(self.f, "..")?;
                self.write_expr(spread)
            }
        }
    }

    /// Writes a pattern.
    pub fn write_pattern(&mut self, pat: &Pattern) -> fmt::Result {
        match pat {
            Pattern::Expr(expr) => self.write_expr(expr),
            Pattern::Simple(decl) => self.write_decl(decl),
            Pattern::Sig(sig) => self.write_pattern_sig(sig),
        }
    }

    /// Writes a pattern signature.
    fn write_pattern_sig(&mut self, sig: &PatternSig) -> fmt::Result {
        self.f.write_str("pat(\n")?;
        self.indent += 1;
        for pos in &sig.pos {
            self.write_indent()?;
            self.write_pattern(pos)?;
            self.f.write_str(",\n")?;
        }
        for (name, pat) in &sig.named {
            self.write_indent()?;
            write!(self.f, "{name:?} = ")?;
            self.write_pattern(pat)?;
            self.f.write_str(",\n")?;
        }
        if let Some((k, rest)) = &sig.spread_left {
            self.write_indent()?;
            write!(self.f, "..{k:?}: ")?;
            self.write_pattern(rest)?;
            self.f.write_str(",\n")?;
        }
        if let Some((k, rest)) = &sig.spread_right {
            self.write_indent()?;
            write!(self.f, "..{k:?}: ")?;
            self.write_pattern(rest)?;
            self.f.write_str(",\n")?;
        }
        self.indent -= 1;
        self.write_indent()?;
        self.f.write_str(")")
    }

    /// Writes an element.
    fn write_element(&mut self, elem: &Interned<ElementExpr>) -> fmt::Result {
        write!(self.f, "{:?}", elem.elem.name())
    }

    /// Writes a unary expression.
    fn write_unary(&mut self, unary: &Interned<UnExpr>) -> fmt::Result {
        use UnaryOp::*;
        match unary.op {
            Pos => {
                self.f.write_str("+")?;
                self.write_expr(&unary.lhs)
            }
            Neg => {
                self.f.write_str("-")?;
                self.write_expr(&unary.lhs)
            }
            Not => {
                self.f.write_str("not ")?;
                self.write_expr(&unary.lhs)
            }
            Return => {
                self.f.write_str("return ")?;
                self.write_expr(&unary.lhs)
            }
            Context => {
                self.f.write_str("context ")?;
                self.write_expr(&unary.lhs)
            }
            Spread => {
                self.f.write_str("..")?;
                self.write_expr(&unary.lhs)
            }
            NotElementOf => {
                self.f.write_str("not elementOf(")?;
                self.write_expr(&unary.lhs)?;
                self.f.write_str(")")
            }
            ElementOf => {
                self.f.write_str("elementOf(")?;
                self.write_expr(&unary.lhs)?;
                self.f.write_str(")")
            }
            TypeOf => {
                self.f.write_str("typeOf(")?;
                self.write_expr(&unary.lhs)?;
                self.f.write_str(")")
            }
        }
    }

    /// Writes a binary expression.
    fn write_binary(&mut self, binary: &Interned<BinExpr>) -> fmt::Result {
        let [lhs, rhs] = binary.operands();
        self.write_expr(lhs)?;
        write!(self.f, " {} ", binary.op.as_str())?;
        self.write_expr(rhs)
    }

    /// Writes a function application expression.
    fn write_apply(&mut self, apply: &Interned<ApplyExpr>) -> fmt::Result {
        self.write_expr(&apply.callee)?;
        write!(self.f, "(")?;
        self.write_expr(&apply.args)?;
        write!(self.f, ")")
    }

    /// Writes a function expression.
    fn write_func(&mut self, func: &Interned<FuncExpr>) -> fmt::Result {
        self.write_decl(&func.decl)
    }

    /// Writes a reference expression.
    fn write_ref(&mut self, resolved: &Interned<RefExpr>) -> fmt::Result {
        if let Some(root) = &resolved.root {
            return self.write_expr(root);
        }
        if let Some(term) = &resolved.term {
            return self.write_type(term);
        }

        write!(self.f, "undefined({:?})", resolved.decl)
    }

    /// Writes a content reference expression.
    fn write_content_ref(&mut self, content_ref: &Interned<ContentRefExpr>) -> fmt::Result {
        write!(self.f, "@{:?}", content_ref.ident)
    }

    /// Writes a select expression.
    fn write_select(&mut self, sel: &Interned<SelectExpr>) -> fmt::Result {
        write!(self.f, "")?;
        self.write_expr(&sel.lhs)?;
        self.f.write_str(".")?;
        self.write_decl(&sel.key)
    }

    /// Writes an import expression.
    fn write_import(&mut self, import: &Interned<ImportExpr>) -> fmt::Result {
        self.f.write_str("import(")?;
        self.write_decl(&import.decl.decl)?;
        self.f.write_str(")")
    }

    /// Writes an include expression.
    fn write_include(&mut self, include: &Interned<IncludeExpr>) -> fmt::Result {
        self.f.write_str("include(")?;
        self.write_expr(&include.source)?;
        self.f.write_str(")")
    }

    /// Writes a type.
    fn write_type(&mut self, ty: &Ty) -> fmt::Result {
        let formatted = ty.describe();
        let formatted = formatted.as_deref().unwrap_or("any");
        self.f.write_str(formatted)
    }
}
