use crate::{
    parser::{Child, SyntaxTree, Tree, TreeKind, TreeRef},
    tokens::{Lines, Token, TokenKind},
};
use std::{cell::RefCell, collections::HashMap, fmt, rc::Rc};

// TODO: Unused variables?

// TODO: An error should have:
//
//   - a start
//   - an end
//   - a focus
//   - descriptive messages
//
// that will have to wait for now
#[derive(Clone, PartialEq, Eq)]
pub struct Error {
    pub start: (usize, usize),
    pub end: (usize, usize),
    pub message: String,
}

impl Error {
    pub fn new<T>(line: usize, column: usize, message: T) -> Self
    where
        T: ToString,
    {
        Error {
            start: (line, column),
            end: (line, column),
            message: message.to_string(),
        }
    }

    pub fn new_spanned<T>(start: (usize, usize), end: (usize, usize), message: T) -> Self
    where
        T: ToString,
    {
        Error {
            start,
            end,
            message: message.to_string(),
        }
    }
}

impl fmt::Debug for Error {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "{self}")
    }
}

impl fmt::Display for Error {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "{}:{}: {}", self.start.0, self.start.1, self.message)
    }
}

pub struct FieldDecl {
    pub name: Rc<str>,
    pub field_type: Type,
    pub declaration: TreeRef,
}

pub struct MethodDecl {
    pub name: Rc<str>,
    pub decl_type: Type,
    pub declaration: TreeRef,
    pub is_static: bool,
}

pub struct ClassDecl {
    pub name: Rc<str>,
    pub fields: Vec<FieldDecl>,
    pub methods: Vec<MethodDecl>,
    pub decl_tree: TreeRef,

    pub env: EnvironmentRef,
    pub static_env: EnvironmentRef,
}

#[derive(Clone)]
pub struct ClassRef(Rc<ClassDecl>);

impl ClassRef {
    pub fn new(class: ClassDecl) -> Self {
        ClassRef(Rc::new(class))
    }
}

impl std::ops::Deref for ClassRef {
    type Target = ClassDecl;

    fn deref(&self) -> &Self::Target {
        &self.0
    }
}

#[derive(Clone)]
pub enum Type {
    // Signals a type error. If you receive this then you know that an error
    // has already been reported; if you produce this be sure to also note
    // the error in the errors collection.
    Error,

    // Signals that the expression has a control-flow side-effect and that no
    // value will ever result from this expression. Usually this means
    // everything's fine.
    Unreachable,

    // The type of an assignment expression. Assignments look like
    // expressions but cannot be used in places that expect them (e.g., in
    // `if` conditions), and so this is how we signal that. (We can, however,
    // chain assignments, and so we flow the type of the assignment through.)
    Assignment(Box<Type>),

    // An potentially-bound type variable.
    // We need to ... like ... unify these things if possible.
    TypeVariable(TreeRef),

    Nothing,
    // TODO: Numeric literals should be implicitly convertable, unlike other
    //       types. Maybe just "numeric literal" type?
    F64,
    I64,
    String,
    Bool,

    Function(Vec<Box<Type>>, Box<Type>),

    // A method is like a function except that it takes a self parameter.
    // This is how we signal the difference. We do *not* count them as the
    // same.
    Method(Box<Type>, Vec<Box<Type>>, Box<Type>),

    List(Box<Type>),

    // A class is the static type of a class; when the class is referred to
    // by name it has this type. (Distinct from an instance!)
    Class(TreeRef, Rc<str>),

    // An object is the type of an allocated object instance. Details of its
    // class need to be fetched explicitly from the semantics via the
    // TreeRef and `Semantics::class_of`; they are computed lazily.
    Object(TreeRef, Rc<str>),

    // An alternate is one or another type.
    Alternate(Box<Type>, Box<Type>),
}

impl Type {
    pub fn is_error(&self) -> bool {
        match self {
            Type::Error => true,
            _ => false,
        }
    }
}

impl fmt::Debug for Type {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "{self}")
    }
}

impl fmt::Display for Type {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        use Type::*;
        match self {
            Error => write!(f, "<< INTERNAL ERROR >>"),
            Unreachable => write!(f, "<< UNREACHABLE >>"),
            Assignment(_) => write!(f, "assignment"),
            Nothing => write!(f, "()"),
            F64 => write!(f, "f64"),
            I64 => write!(f, "i64"),
            String => write!(f, "string"),
            Bool => write!(f, "bool"),
            Function(args, ret) => {
                write!(f, "fun (")?;
                let mut first = true;
                for arg in args.iter() {
                    if !first {
                        write!(f, ", ")?;
                    }
                    write!(f, "{arg}")?;
                    first = false;
                }
                write!(f, ") -> {ret}")
            }

            Method(self_type, args, ret) => {
                write!(f, "method of {self_type} (")?;
                let mut first = true;
                for arg in args.iter() {
                    if !first {
                        write!(f, ", ")?;
                    }
                    write!(f, "{arg}")?;
                    first = false;
                }
                write!(f, ") -> {ret}")
            }

            TypeVariable(_) => {
                // TODO: Better names for type variable
                write!(f, "$_")
            }
            List(t) => write!(f, "list<{t}>"),
            Object(_, name) => write!(f, "{} instance", name),
            Class(_, name) => write!(f, "class {}", name),
            Alternate(l, r) => write!(f, "{l} or {r}"),
        }
    }
}

#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum Location {
    Argument,
    Local,
    Module,
    Slot,
    // TODO: ArrayIndex
}

// TODO: Is `usize` what we want? Do we want e.g. dyn trait for invoke?
#[derive(Clone, Debug)]
pub struct ExternalFunctionId(usize);

impl ExternalFunctionId {
    pub fn id(&self) -> usize {
        self.0
    }
}

pub enum Declaration {
    Variable {
        declaration: TreeRef,
        location: Location,
        index: usize,
    },
    Function {
        declaration: TreeRef, //?
    },
    ExternFunction {
        declaration_type: Type,
        id: ExternalFunctionId,
    },
    Class {
        declaration: TreeRef, //?
    },
}

pub struct Environment {
    pub parent: Option<EnvironmentRef>,
    pub location: Location,
    pub next_index: usize,
    pub declarations: HashMap<Box<str>, Declaration>,
    pub is_error: bool,
}

impl Environment {
    pub fn new(parent: Option<EnvironmentRef>, location: Location) -> Self {
        let parent_location = parent
            .as_ref()
            .map(|p| p.location)
            .unwrap_or(Location::Module);
        let base = parent.as_ref().map(|p| p.next_index).unwrap_or(0);
        let next_index = match (parent_location, location) {
            (_, Location::Argument) => 0,
            (_, Location::Slot) => 0,

            (Location::Local, Location::Local) => base,
            (_, Location::Local) => 0,

            (Location::Module, Location::Module) => base,
            (_, Location::Module) => panic!("What?"),
        };

        Environment {
            parent,
            location,
            next_index,
            declarations: HashMap::new(),
            is_error: false,
        }
    }

    pub fn error() -> EnvironmentRef {
        // TODO: Exactly once?
        EnvironmentRef::new(Environment {
            parent: None,
            location: Location::Local,
            next_index: 0,
            declarations: HashMap::new(),
            is_error: true,
        })
    }

    pub fn insert(&mut self, token: &Token, t: TreeRef) -> Option<Declaration> {
        self.insert_name(token.as_str().into(), t)
    }

    pub fn insert_name(&mut self, name: Box<str>, t: TreeRef) -> Option<Declaration> {
        let result = self.declarations.insert(
            name,
            Declaration::Variable {
                declaration: t,
                location: self.location,
                index: self.next_index,
            },
        );
        self.next_index += 1;
        result
    }

    pub fn bind(&self, token: &Token) -> Option<&Declaration> {
        if let Some(decl) = self.declarations.get(token.as_str()) {
            return Some(decl);
        }

        let mut current = &self.parent;
        while let Some(env) = current {
            if let Some(decl) = env.declarations.get(token.as_str()) {
                return Some(decl);
            }
            current = &env.parent;
        }

        None
    }
}

#[derive(Clone)]
pub struct EnvironmentRef(Rc<Environment>);

impl EnvironmentRef {
    pub fn new(environment: Environment) -> Self {
        EnvironmentRef(Rc::new(environment))
    }
}

impl std::ops::Deref for EnvironmentRef {
    type Target = Environment;

    fn deref(&self) -> &Self::Target {
        &self.0
    }
}

fn set_logical_parents(
    parents: &mut Vec<Option<TreeRef>>,
    syntax_tree: &SyntaxTree,
    t: TreeRef,
    parent: Option<TreeRef>,
) {
    parents[t.index()] = parent.clone();

    let tree = &syntax_tree[t];
    // eprintln!("SET PARENT {parent:?} => CHILD {tree:?} ({t:?})");
    match tree.kind {
        TreeKind::Block | TreeKind::File => {
            // In a block (or at the top level), each child actually points
            // to the previous child as the logical parent, so that variable
            // declarations that occur as part of statements in the block are
            // available to statements later in the block.
            let mut parent = Some(t);
            for child in &tree.children {
                match child {
                    Child::Token(_) => (),
                    Child::Tree(ct) => {
                        set_logical_parents(parents, syntax_tree, *ct, parent);
                        parent = Some(*ct);
                    }
                }
            }
        }
        TreeKind::LetStatement => {
            // In a let statement, the logical parent of the children is
            // actually the logical parent of the let statement, so that the
            // variable doesn't have itself in scope. :P
            for child in &tree.children {
                match child {
                    Child::Token(_) => (),
                    Child::Tree(ct) => set_logical_parents(parents, syntax_tree, *ct, parent),
                }
            }
        }
        TreeKind::FunctionDecl => {
            // In a function declaration, the logical parent of the body is
            // the parameter list.
            let param_list = tree.child_of_kind(syntax_tree, TreeKind::ParamList);
            let body = tree.child_of_kind(syntax_tree, TreeKind::Block);
            for child in &tree.children {
                match child {
                    Child::Token(_) => (),
                    Child::Tree(ct) => {
                        if Some(*ct) == body {
                            set_logical_parents(parents, syntax_tree, *ct, param_list);
                        } else {
                            set_logical_parents(parents, syntax_tree, *ct, Some(t));
                        }
                    }
                }
            }
        }
        TreeKind::ForStatement => {
            let body = tree.child_of_kind(syntax_tree, TreeKind::Block);
            for child in &tree.children {
                match child {
                    Child::Token(_) => (),
                    Child::Tree(ct) => {
                        if Some(*ct) == body {
                            set_logical_parents(parents, syntax_tree, *ct, Some(t));
                        } else {
                            // If it's not the body then it must be the
                            // iterable and the iterable doesn't have the
                            // loop variable in scope.
                            set_logical_parents(parents, syntax_tree, *ct, parent);
                        }
                    }
                }
            }
        }
        TreeKind::MemberAccess => {
            // The LHS has the environment of my parent, because I set up an
            // environment containing only members of the type of the LHS. The
            // RHS has that one.
            if let Some(lhs) = tree.nth_tree(0) {
                set_logical_parents(parents, syntax_tree, lhs, parent);
            }
            if let Some(rhs) = tree.nth_tree(2) {
                set_logical_parents(parents, syntax_tree, rhs, Some(t));
            }
        }
        TreeKind::ConditionalExpression => {
            // Special case! If the condition expression is a `is` expression
            // then it is the parent for the body of the `then` clause so
            // that any variable bindings it makes remain in scope. The
            // `else` clause and the condition itself do not have the
            // bindings in scope, obviously.
            let body_parent = if let Some(is_condition) =
                tree.child_of_kind(syntax_tree, TreeKind::IsExpression)
            {
                Some(is_condition)
            } else {
                Some(t)
            };

            let then_body = tree.nth_tree(2);
            for child in &tree.children {
                match child {
                    Child::Token(_) => (),
                    Child::Tree(ct) => {
                        if Some(*ct) == then_body {
                            set_logical_parents(parents, syntax_tree, *ct, body_parent);
                        } else {
                            set_logical_parents(parents, syntax_tree, *ct, Some(t));
                        }
                    }
                }
            }
        }
        _ => {
            // By default, the parent for each child is current tree.
            for child in &tree.children {
                match child {
                    Child::Token(_) => (),
                    Child::Tree(ct) => set_logical_parents(parents, syntax_tree, *ct, Some(t)),
                }
            }
        }
    }
}

#[derive(Clone, Copy, Debug)]
enum Incremental<T> {
    None,
    InProgress,
    Complete(T),
}

pub struct Semantics<'a> {
    // TODO: Do I really want my own copy here? Should we standardize on Arc
    //       or Rc or some other nice sharing mechanism?
    syntax_tree: &'a SyntaxTree<'a>,
    lines: &'a Lines,

    // Instead of physical parents, this is the set of *logical* parents.
    // This is what is used for binding.
    logical_parents: Vec<Option<TreeRef>>,

    // TODO: State should be externalized instead of this refcell nonsense.
    errors: RefCell<Vec<Error>>,
    types: RefCell<Vec<Incremental<Type>>>,
    environments: RefCell<Vec<Incremental<EnvironmentRef>>>,
    root_environment: EnvironmentRef,
    classes: RefCell<Vec<Incremental<ClassRef>>>,
}

impl<'a> Semantics<'a> {
    pub fn new(tree: &'a SyntaxTree<'a>, lines: &'a Lines) -> Self {
        let mut logical_parents = vec![None; tree.len()];
        if let Some(root) = tree.root() {
            set_logical_parents(&mut logical_parents, tree, root, None);
        }

        let root_environment = Environment::new(None, Location::Module);

        let mut semantics = Semantics {
            syntax_tree: tree,
            lines,
            logical_parents,
            errors: RefCell::new(vec![]),
            types: RefCell::new(vec![Incremental::None; tree.len()]),
            environments: RefCell::new(vec![Incremental::None; tree.len()]),
            root_environment: EnvironmentRef::new(root_environment),
            classes: RefCell::new(vec![Incremental::None; tree.len()]),
        };

        // NOTE: We ensure all the known errors are reported before we move
        //       on to answering any other questions. We're going to work as
        //       hard as we can from a partial tree.
        if let Some(tr) = semantics.syntax_tree.root() {
            semantics.gather_errors(tr);
        }

        semantics
    }

    pub fn tree(&self) -> &SyntaxTree<'a> {
        &self.syntax_tree
    }

    pub fn snapshot_errors(&self) -> Vec<Error> {
        let mut result = (*self.errors.borrow()).clone();
        result.sort_by(|a, b| match a.start.0.cmp(&b.start.0) {
            std::cmp::Ordering::Equal => a.start.1.cmp(&b.start.1),
            o => o,
        });
        result
    }

    pub fn logical_parent(&self, tr: TreeRef) -> Option<TreeRef> {
        if tr.index() < self.logical_parents.len() {
            self.logical_parents[tr.index()]
        } else {
            None
        }
    }

    fn report_error<T>(&self, position: usize, error: T)
    where
        T: ToString,
    {
        let (line, col) = self.lines.position(position);
        self.errors
            .borrow_mut()
            .push(Error::new(line, col, error.to_string()));
    }

    fn report_error_span<T>(&self, start: usize, end: usize, error: T)
    where
        T: ToString,
    {
        let start = self.lines.position(start);
        let end = self.lines.position(end);
        self.errors
            .borrow_mut()
            .push(Error::new_spanned(start, end, error.to_string()));
    }

    fn report_error_tree<T>(&self, tree: &Tree<'a>, error: T)
    where
        T: ToString,
    {
        self.report_error_span(tree.start_pos, tree.end_pos, error)
    }

    fn report_error_tree_ref<T>(&self, tree: TreeRef, error: T)
    where
        T: ToString,
    {
        let tree = &self.syntax_tree[tree];
        self.report_error_span(tree.start_pos, tree.end_pos, error)
    }

    // pub fn lvalue_declaration(&self, t: TreeRef) -> Option<&Declaration> {
    // }

    fn gather_errors(&mut self, tree: TreeRef) {
        let mut stack = vec![tree];
        while let Some(tr) = stack.pop() {
            let tree = &self.syntax_tree[tr];
            for child in &tree.children {
                match child {
                    Child::Token(t) => {
                        if t.kind == TokenKind::Error {
                            self.report_error(t.start, t.as_str());
                        }
                    }
                    Child::Tree(t) => stack.push(*t),
                }
            }
        }
    }

    pub fn environment_of(&self, t: TreeRef) -> EnvironmentRef {
        {
            // I want to make sure that this borrow is dropped after this block.
            let mut borrow = self.environments.borrow_mut();
            let state = &mut borrow[t.index()];
            match state {
                Incremental::None => (),
                Incremental::Complete(e) => return e.clone(),
                Incremental::InProgress => {
                    // NOTE: Set the state so the ICE doesn't loop on itself.
                    *state = Incremental::Complete(self.root_environment.clone());
                    drop(borrow);

                    //eprintln!("environment_of circular => {t:?}");
                    self.internal_compiler_error(Some(t), "circular environment dependency");
                }
            }
            *state = Incremental::InProgress;
        }

        let tree = &self.syntax_tree[t];
        // eprintln!(">>> environment_of => {tree:?}");

        let parent = match self.logical_parents[t.index()] {
            Some(t) => self.environment_of(t),
            None => self.root_environment.clone(),
        };

        let result = match tree.kind {
            TreeKind::Block => self.environment_of_block(parent, tree),
            TreeKind::File => self.environment_of_file(parent, tree),
            TreeKind::ForStatement => self.environment_of_for(parent, tree),
            TreeKind::IsExpression => self.environment_of_is_expression(parent, tree),
            TreeKind::LetStatement => self.environment_of_let(parent, tree),
            TreeKind::MatchArm => self.environment_of_match_arm(parent, t, tree),
            TreeKind::MemberAccess => self.environment_of_member_access(tree),
            TreeKind::ParamList => self.environment_of_paramlist(parent, tree),

            _ => parent,
        };

        self.environments.borrow_mut()[t.index()] = Incremental::Complete(result.clone());
        // eprintln!("<<< environment_of => {tree:?}");
        result
    }

    fn environment_of_block(&self, parent: EnvironmentRef, tree: &Tree) -> EnvironmentRef {
        let mut environment = Environment::new(Some(parent), Location::Local);
        for child in tree.children.iter() {
            match child {
                Child::Tree(t) => {
                    let ct = &self.syntax_tree[*t];
                    if ct.kind == TreeKind::FunctionDecl {
                        let Some(name) = ct.nth_token(1) else {
                            continue;
                        };

                        let existing = environment.declarations.insert(
                            name.as_str().into(),
                            Declaration::Function { declaration: *t },
                        );
                        if existing.is_some() {
                            self.report_error_tree(
                                ct,
                                format!("duplicate definition of function '{name}'"),
                            );
                        }
                    }
                }
                _ => {}
            }
        }

        EnvironmentRef::new(environment)
    }

    fn environment_of_file(&self, parent: EnvironmentRef, tree: &Tree) -> EnvironmentRef {
        let mut environment = Environment::new(Some(parent), Location::Module);
        for child in tree.children.iter() {
            match child {
                Child::Tree(t) => {
                    let ct = &self.syntax_tree[*t];
                    let binding = match ct.kind {
                        TreeKind::FunctionDecl => {
                            let Some(name) = ct.nth_token(1) else {
                                continue;
                            };

                            let declaration = Declaration::Function { declaration: *t };
                            Some(("function", name, declaration))
                        }
                        TreeKind::ClassDecl => {
                            let Some(name) = ct.nth_token(1) else {
                                continue;
                            };

                            let declaration = Declaration::Class { declaration: *t };
                            Some(("class", name, declaration))
                        }
                        _ => None,
                    };

                    if let Some((what, name, declaration)) = binding {
                        let existing = environment
                            .declarations
                            .insert(name.as_str().into(), declaration);
                        if existing.is_some() {
                            self.report_error_tree(
                                ct,
                                format!("duplicate definition of {what} '{name}'"),
                            );
                        }
                    }
                }
                _ => {}
            }
        }

        EnvironmentRef::new(environment)
    }

    fn environment_of_let(&self, parent: EnvironmentRef, tree: &Tree) -> EnvironmentRef {
        let Some(name) = tree.nth_token(1) else {
            return parent; // Error is already reported?
        };

        let Some(declaration) = tree.nth_tree(3) else {
            return parent;
        };

        // eprintln!("{} => {}", name, declaration_type);

        let location = match parent.location {
            Location::Local => Location::Local,
            Location::Module => Location::Module,
            Location::Argument => Location::Local,
            Location::Slot => Location::Local,
        };

        let mut environment = Environment::new(Some(parent), location);
        environment.insert(name, declaration);

        EnvironmentRef::new(environment)
    }

    fn environment_of_paramlist(&self, parent: EnvironmentRef, tree: &Tree) -> EnvironmentRef {
        assert!(tree.kind == TreeKind::ParamList);

        let mut environment = Environment::new(Some(parent), Location::Argument);
        for (i, child) in tree.children.iter().enumerate() {
            let Child::Tree(ct) = child else {
                continue;
            };
            let param = &self.syntax_tree[*ct];
            match param.kind {
                TreeKind::SelfParameter => {
                    let param_name = param.nth_token(0).unwrap();
                    if environment.insert(param_name, *ct).is_some() {
                        self.report_error_tree(
                            param,
                            format!("duplicate definition of self parameter"),
                        );
                    } else if i != 1 {
                        self.report_error_tree(
                            param,
                            "self parameter must be the first parameter in the list",
                        );
                    }
                }
                TreeKind::Parameter => {
                    let Some(param_name) = param.nth_token(0) else {
                        continue;
                    };

                    if environment.insert(param_name, *ct).is_some() {
                        self.report_error_tree(
                            param,
                            format!("duplicate definition of parameter '{param_name}'"),
                        );
                    }
                }
                _ => (),
            }
        }

        EnvironmentRef::new(environment)
    }

    fn environment_of_for(&self, parent: EnvironmentRef, _tree: &Tree) -> EnvironmentRef {
        // let Some(id) = tree.nth_token(1) else {
        //     return parent;
        // };

        // let Some(enumerable) = tree.nth_tree(3) else {
        //     return parent;
        // };

        // let item_type = match self.type_of(enumerable) {
        //     Type::Error => Type::Error,
        //     Type::List(x) => (&*x).clone(),
        //     _ => {
        //         self.report_error_tree_ref(enumerable, "this expression is not enumerable");
        //         Type::Error
        //     }
        // };

        // TODO: This is broken, need a sub-tree to point the decl at that
        // has the right type.
        //
        // let mut environment = Environment::new(Some(parent), Location::Local);
        // environment.insert(id, item_type);
        // EnvironmentRef::new(environment)
        parent
    }

    fn environment_of_member_access(&self, tree: &Tree) -> EnvironmentRef {
        assert_eq!(tree.kind, TreeKind::MemberAccess);

        // Build environment out of members of type of lhs
        let Some(lhs) = tree.nth_tree(0) else {
            return Environment::error();
        };
        let Some(op) = tree.nth_token(1) else {
            return Environment::error();
        };
        let typ = self.type_of(lhs);
        match &typ {
            Type::Object(ct, _) => {
                let class = self.class_of(*ct);
                class.env.clone()
            }
            Type::Class(ct, _) => {
                let class = self.class_of(*ct);
                class.static_env.clone()
            }
            Type::Error => Environment::error(),
            _ => {
                // TODO: This is probably wrong, yeah?
                self.report_error(op.start, format!("cannot access members of '{typ}'"));
                Environment::error()
            }
        }
    }

    fn environment_of_is_expression(&self, parent: EnvironmentRef, tree: &Tree) -> EnvironmentRef {
        assert_eq!(tree.kind, TreeKind::IsExpression);

        // The environment of an `is` expression is the environment produced by the pattern.
        let Some(pattern) = tree.child_tree_of_kind(self.syntax_tree, TreeKind::Pattern) else {
            // Should really have a pattern in there; otherwise there was a
            // parse error, don't make more trouble.
            return Environment::error();
        };

        // The left hand side of the `is` expression is used for wildcard types.
        let Some(lhs) = tree.nth_tree(0) else {
            return Environment::error();
        };
        self.environment_of_pattern(parent, pattern, lhs)
    }

    fn environment_of_match_arm(
        &self,
        parent: EnvironmentRef,
        t: TreeRef,
        tree: &Tree,
    ) -> EnvironmentRef {
        assert_eq!(tree.kind, TreeKind::MatchArm);

        // The environment of a `match arm` expression is the environment produced by the pattern.
        let Some(pattern) = tree.child_tree_of_kind(self.syntax_tree, TreeKind::Pattern) else {
            // Should really have a pattern in there; otherwise there was a
            // parse error, don't make more trouble.
            return Environment::error();
        };

        // The expression in the match expression is the binding for the wildcard pattern.
        // If we are somewhere weird then... uh....
        let Some(match_body) = tree.parent else {
            self.internal_compiler_error(Some(t), "no parent on match arm");
        };
        let match_body = &self.syntax_tree[match_body];
        if match_body.kind != TreeKind::MatchBody {
            self.internal_compiler_error(Some(t), "match arm parent not match body");
        }
        let Some(match_expression) = match_body.parent else {
            self.internal_compiler_error(Some(t), "no parent on match body");
        };
        let match_expression = &self.syntax_tree[match_expression];
        if match_expression.kind != TreeKind::MatchExpression {
            self.internal_compiler_error(Some(t), "match body parent not match expression");
        }

        // The expression is the first tree child of match expression.
        let Some(lhs) = tree.nth_tree(1) else {
            return Environment::error();
        };
        self.environment_of_pattern(parent, pattern, lhs)
    }

    // NOTE: THIS IS CALLED DIRECTLY, NOT VIA `environment_of` TO AVOID CYCLES.
    fn environment_of_pattern(
        &self,
        parent: EnvironmentRef,
        tree: &Tree,
        value_expr: TreeRef,
    ) -> EnvironmentRef {
        assert_eq!(tree.kind, TreeKind::Pattern);

        let Some(binding) = tree.child_tree_of_kind(self.syntax_tree, TreeKind::VariableBinding)
        else {
            // No binding, no new environment.
            return parent;
        };
        let Some(variable) = binding.nth_token(0) else {
            return Environment::error();
        };

        let is_wildcard = tree
            .child_of_kind(self.syntax_tree, TreeKind::WildcardPattern)
            .is_some();

        let variable_decl = if is_wildcard {
            // If the variable is bound to a wildcard then treat the value
            // expression as the declaration for the purpose of determining
            // type.
            value_expr
        } else {
            // Otherwise the binding is to the type expression which must
            // match for the variable to have a value.
            let Some(type_expr) = tree.child_of_kind(self.syntax_tree, TreeKind::TypeExpression)
            else {
                return Environment::error();
            };
            type_expr
        };

        // TODO: This binding should be un-assignable! Don't assign to this!

        let mut env = Environment::new(Some(parent), Location::Local);
        env.insert(variable, variable_decl);
        EnvironmentRef::new(env)
    }

    pub fn class_of(&self, t: TreeRef) -> ClassRef {
        {
            // I want to make sure that this borrow is dropped after this block.
            let mut borrow = self.classes.borrow_mut();
            let state = &mut borrow[t.index()];
            match state {
                Incremental::None => (),
                Incremental::Complete(e) => return e.clone(),
                Incremental::InProgress => {
                    drop(borrow);
                    self.internal_compiler_error(Some(t), "circular class dependency");
                }
            }
            *state = Incremental::InProgress;
        }

        // TODO: Right now there's only one way to make a class decl. :P
        let tree = &self.syntax_tree[t];
        assert_eq!(tree.kind, TreeKind::ClassDecl);

        let name = tree.nth_token(1).map(|t| t.as_str()).unwrap_or("<??>");

        // Fields
        let mut fields = Vec::new();
        for field in tree.children_of_kind(self.syntax_tree, TreeKind::FieldDecl) {
            let f = &self.syntax_tree[field];
            if let Some(field_name) = f.nth_token(0) {
                let field_type = f
                    .nth_tree(2)
                    .map(|t| self.type_of(t))
                    .unwrap_or(Type::Error);
                fields.push(FieldDecl {
                    name: field_name.as_str().into(),
                    declaration: field,
                    field_type,
                });
            }
        }

        // Methods
        let mut methods = Vec::new();
        for method in tree.children_of_kind(self.syntax_tree, TreeKind::FunctionDecl) {
            let m = &self.syntax_tree[method];
            if let Some(method_name) = m.nth_token(1) {
                // TODO: Check to see if it is actually a method, or if it is a static function.
                let decl_type = self.type_of(method);
                match decl_type {
                    Type::Method(..) => {
                        methods.push(MethodDecl {
                            name: method_name.as_str().into(),
                            decl_type,
                            declaration: method,
                            is_static: false,
                        });
                    }
                    _ => {
                        // TODO: Default to method or static?
                        methods.push(MethodDecl {
                            name: method_name.as_str().into(),
                            decl_type,
                            declaration: method,
                            is_static: true,
                        });
                    }
                }
            }
        }

        // Build into an environment
        let mut env = Environment::new(None, Location::Slot);
        let mut static_env = Environment::new(None, Location::Slot);
        for (index, field) in fields.iter().enumerate() {
            env.declarations.insert(
                (&*field.name).into(),
                Declaration::Variable {
                    index,
                    declaration: field.declaration,
                    location: Location::Slot,
                },
            );
        }
        for method in methods.iter() {
            let existing = if method.is_static {
                static_env.declarations.insert(
                    (&*method.name).into(),
                    Declaration::Function {
                        declaration: method.declaration,
                    },
                )
            } else {
                env.declarations.insert(
                    (&*method.name).into(),
                    Declaration::Function {
                        declaration: method.declaration,
                    },
                )
            };
            if existing.is_some() {
                self.report_error_tree_ref(
                    method.declaration,
                    format!("duplicate definition of method '{}'", method.name),
                );
            }
        }

        let result = ClassRef::new(ClassDecl {
            name: name.into(),
            fields: fields.into(),
            methods: methods.into(),
            decl_tree: t,
            env: EnvironmentRef::new(env),
            static_env: EnvironmentRef::new(static_env),
        });

        self.classes.borrow_mut()[t.index()] = Incremental::Complete(result.clone());
        result
    }

    pub fn can_convert(&self, from: &Type, to: &Type) -> bool {
        // TODO: This is wrong; we because of numeric literals etc.
        match (from, to) {
            (Type::F64, Type::F64) => true,
            (Type::String, Type::String) => true,
            (Type::Bool, Type::Bool) => true,
            (Type::Unreachable, Type::Unreachable) => true,
            (Type::Nothing, Type::Nothing) => true,

            (Type::List(from), Type::List(to)) => self.can_convert(from, to),
            (Type::Function(from_args, from_ret), Type::Function(to_args, to_ret)) => {
                from_args.len() == from_args.len()
                    && self.can_convert(to_ret, from_ret)
                    && from_args
                        .iter()
                        .zip(to_args.iter())
                        .all(|(from, to)| self.can_convert(from, to))
            }

            (Type::Object(c_from, _), Type::Object(c_to, _)) => {
                // TODO: Structural comparisons. All that matters is that
                //       c_to has a subset of fields and methods, and the
                //       fields and methods are all compatible.
                //
                c_from == c_to
            }

            // Avoid introducing more errors
            (Type::Error, _) => true,
            (_, Type::Error) => true,

            // Can... I... convert unreachable always? Is this sound?
            (Type::Unreachable, _) => true,

            // Alternates convert if either side can convert.
            (_, Type::Alternate(left, right)) => {
                self.can_convert(from, left) || self.can_convert(from, right)
            }

            // TODO: Unification on type variables! :D
            (_, _) => false,
        }
    }

    pub fn type_of(&self, t: TreeRef) -> Type {
        {
            let state = &mut self.types.borrow_mut()[t.index()];
            match state {
                Incremental::None => (),
                Incremental::Complete(existing) => return existing.clone(),
                Incremental::InProgress => {
                    // eprintln!("type_of circular => {t:?}");
                    self.report_error_tree_ref(t, "The type of this expression depends on itself");
                    *state = Incremental::Complete(Type::Error);
                    return Type::Error;
                }
            }
            *state = Incremental::InProgress;
        }

        let tree = &self.syntax_tree[t];
        // eprintln!(">>> type_of => {tree:?}");

        let result = match tree.kind {
            TreeKind::Error => Some(Type::Error),

            TreeKind::AlternateType => self.type_of_alternate_type(tree),
            TreeKind::Argument => self.type_of_argument(tree),
            TreeKind::BinaryExpression => self.type_of_binary(tree),
            TreeKind::Block => self.type_of_block(tree),
            TreeKind::CallExpression => self.type_of_call(tree),
            TreeKind::ClassDecl => self.type_of_class_decl(t, tree),
            TreeKind::FieldDecl => self.type_of_field_decl(tree),
            TreeKind::ConditionalExpression => self.type_of_conditional(tree),
            TreeKind::ExpressionStatement => self.type_of_expression_statement(tree),
            TreeKind::FieldValue => self.type_of_field_value(t, tree),
            TreeKind::ForStatement => Some(Type::Nothing),
            TreeKind::FunctionDecl => self.type_of_function_decl(tree),
            TreeKind::GroupingExpression => self.type_of_grouping(tree),
            TreeKind::Identifier => self.type_of_identifier(t, tree),
            TreeKind::IfStatement => self.type_of_if_statement(tree),
            TreeKind::IsExpression => Some(Type::Bool),
            TreeKind::LetStatement => Some(Type::Nothing),
            TreeKind::ListConstructor => self.type_of_list_constructor(t, tree),
            TreeKind::ListConstructorElement => self.type_of_list_constructor_element(tree),
            TreeKind::LiteralExpression => self.type_of_literal(tree),
            TreeKind::MemberAccess => self.type_of_member_access(tree),
            TreeKind::NewObjectExpression => self.type_of_new_object_expression(tree),
            TreeKind::Parameter => self.type_of_parameter(tree),
            TreeKind::ReturnStatement => Some(Type::Unreachable),
            TreeKind::ReturnType => self.type_of_return_type(tree),
            TreeKind::SelfParameter => self.type_of_self_parameter(tree),
            TreeKind::SelfReference => self.type_of_self_reference(t, tree),
            TreeKind::TypeExpression => self.type_of_type_expr(tree),
            TreeKind::TypeIdentifier => self.type_of_type_identifier(t, tree),
            TreeKind::TypeParameter => self.type_of_type_parameter(tree),
            TreeKind::UnaryExpression => self.type_of_unary(tree),

            TreeKind::MatchExpression => self.type_of_match_expression(tree),
            TreeKind::MatchBody => self.type_of_match_body(tree),
            TreeKind::MatchArm => self.type_of_match_arm(tree),

            _ => self.internal_compiler_error(Some(t), "asking for a nonsense type"),
        };

        // NOTE: These return `None` if they encounter some problem.
        let result = result.unwrap_or(Type::Error);
        self.types.borrow_mut()[t.index()] = Incremental::Complete(result.clone());
        // eprintln!("<<< type_of => {tree:?}");

        result
    }

    fn type_of_unary(&self, tree: &Tree) -> Option<Type> {
        assert_eq!(tree.kind, TreeKind::UnaryExpression);

        let op = tree.nth_token(0)?;
        let expr = tree.nth_tree(1)?;

        let argument_type = self.type_of(expr);
        match (op.kind, argument_type) {
            (TokenKind::Plus, Type::F64) => Some(Type::F64),
            (TokenKind::Minus, Type::F64) => Some(Type::F64),
            (TokenKind::Bang, Type::Bool) => Some(Type::Bool),

            // This is dumb and should be punished, probably.
            (_, Type::Unreachable) => {
                self.report_error(
                    op.start,
                    "cannot apply a unary operator to something that doesn't yield a value",
                );
                Some(Type::Error)
            }

            // Propagate existing errors without additional complaint.
            (_, Type::Error) => Some(Type::Error),

            (_, arg_type) => {
                self.report_error(
                    op.start,
                    format!(
                        "cannot apply unary operator '{}' to value of type {}",
                        op.as_str(),
                        arg_type
                    ),
                );
                Some(Type::Error)
            }
        }
    }

    fn type_of_binary(&self, tree: &Tree) -> Option<Type> {
        assert_eq!(tree.kind, TreeKind::BinaryExpression);
        let left_tree = tree.nth_tree(0)?;
        let lhs = self.type_of(left_tree);
        let op = tree.nth_token(1)?;
        let rhs = self.type_of(tree.nth_tree(2)?);

        match (op.kind, lhs, rhs) {
            (
                TokenKind::Plus | TokenKind::Minus | TokenKind::Star | TokenKind::Slash,
                Type::F64,
                Type::F64,
            ) => Some(Type::F64),

            (TokenKind::Plus, Type::String, Type::String) => Some(Type::String),

            (TokenKind::And | TokenKind::Or, Type::Bool, Type::Bool) => Some(Type::Bool),

            (TokenKind::EqualEqual, Type::F64, Type::F64) => Some(Type::Bool),
            (TokenKind::EqualEqual, Type::String, Type::String) => Some(Type::Bool),
            (TokenKind::EqualEqual, Type::Bool, Type::Bool) => Some(Type::Bool),
            (TokenKind::EqualEqual, Type::Nothing, Type::Nothing) => Some(Type::Bool),

            // This is dumb and should be punished, probably.
            (_, _, Type::Unreachable) => {
                self.report_error(
                    op.start,
                    format!("cannot apply '{op}' to an argument that doesn't yield a value (on the right)"),
                );
                Some(Type::Error)
            }
            (_, Type::Unreachable, _) => {
                self.report_error(
                    op.start,
                    format!("cannot apply '{op}' to an argument that doesn't yield a value (on the left)"),
                );
                Some(Type::Error)
            }

            // Propagate existing errors without additional complaint.
            (_, Type::Error, _) => Some(Type::Error),
            (_, _, Type::Error) => Some(Type::Error),

            // Assignments are fun.
            (TokenKind::Equal, a, b) => self.type_of_assignment(left_tree, a, b, op),

            // Missed the whole table, it must be an error.
            (_, left_type, right_type) => {
                self.report_error(
                    op.start,
                    format!("cannot apply binary operator '{op}' to expressions of type '{left_type}' (on the left) and '{right_type}' (on the right)"),
                );
                Some(Type::Error)
            }
        }
    }

    fn type_of_assignment(
        &self,
        left_tree: TreeRef,
        left_type: Type,
        right_type: Type,
        op: &Token,
    ) -> Option<Type> {
        // Ensure the left tree is an lvalue
        let environment = self.environment_of(left_tree);
        let tree = &self.syntax_tree[left_tree];
        let declaration = match tree.kind {
            // TODO: Assign to member access or list access
            TreeKind::Identifier => environment.bind(tree.nth_token(0)?),
            _ => None,
        };
        match declaration {
            Some(d) => match d {
                Declaration::Variable { .. } => (),
                Declaration::ExternFunction { .. } | Declaration::Function { .. } => {
                    self.report_error_tree_ref(
                        left_tree,
                        "cannot assign a new value to a function declaration",
                    );
                    return Some(Type::Error);
                }
                Declaration::Class { .. } => {
                    self.report_error_tree_ref(
                        left_tree,
                        "cannot assign a new value to a class declaration",
                    );
                    return Some(Type::Error);
                }
            },
            None => {
                self.report_error_tree_ref(
                    left_tree,
                    "cannot assign a value to this expression, it is not a place you can store things",
                );
                return Some(Type::Error);
            }
        }

        let left_type = match left_type {
            Type::Assignment(x) => *x,
            t => t,
        };
        let right_type = match right_type {
            Type::Assignment(x) => *x,
            t => t,
        };

        if left_type.is_error() || right_type.is_error() {
            Some(Type::Error)
        } else if self.can_convert(&right_type, &left_type) {
            Some(Type::Assignment(Box::new(left_type)))
        } else {
            self.report_error(
                op.start,
                format!("cannot assign a value of type '{right_type}' to type '{left_type}'"),
            );
            Some(Type::Error)
        }
    }

    fn type_of_type_expr(&self, tree: &Tree) -> Option<Type> {
        assert_eq!(tree.kind, TreeKind::TypeExpression);
        Some(self.type_of(tree.nth_tree(0)?))
    }

    fn type_of_type_identifier(&self, t: TreeRef, tree: &Tree) -> Option<Type> {
        assert_eq!(tree.kind, TreeKind::TypeIdentifier);

        // TODO: This will *clearly* need to get better.
        let token = tree.nth_token(0)?;
        match token.as_str() {
            "f64" => Some(Type::F64),
            "string" => Some(Type::String),
            "bool" => Some(Type::Bool),
            "()" => Some(Type::Nothing),
            "list" => {
                let args =
                    tree.child_tree_of_kind(self.syntax_tree, TreeKind::TypeParameterList)?;
                let mut arg_types: Vec<_> = args.child_trees().map(|t| self.type_of(t)).collect();

                if arg_types.len() != 1 {
                    self.report_error_tree(tree, "list takes a single type argument");
                    Some(Type::Error)
                } else {
                    Some(Type::List(Box::new(arg_types.pop().unwrap())))
                }
            }
            _ => {
                let environment = self.environment_of(t);
                match environment.bind(token) {
                    Some(Declaration::Class { declaration, .. }) => {
                        Some(self.type_of(*declaration))
                    }
                    Some(Declaration::Variable { .. }) => {
                        self.report_error_tree(
                            tree,
                            format!("'{token}' is a variable and cannot be used as a type"),
                        );
                        Some(Type::Error)
                    }
                    Some(Declaration::Function { .. } | Declaration::ExternFunction { .. }) => {
                        self.report_error_tree(
                            tree,
                            format!("'{token}' is a function and cannot be used as a type"),
                        );
                        Some(Type::Error)
                    }
                    None => {
                        if !environment.is_error {
                            self.report_error_tree(tree, format!("Unrecognized type: '{token}'"));
                        }
                        Some(Type::Error)
                    }
                }
            }
        }
    }

    fn type_of_type_parameter(&self, tree: &Tree) -> Option<Type> {
        assert_eq!(tree.kind, TreeKind::TypeParameter);
        Some(self.type_of(tree.nth_tree(0)?))
    }

    fn type_of_block(&self, tree: &Tree) -> Option<Type> {
        assert_eq!(tree.kind, TreeKind::Block);

        if tree.children.len() < 2 {
            return None;
        }

        if tree.children.len() == 2 {
            // Empty blocks generate Nothing.
            return Some(Type::Nothing);
        }

        // The type of the block is the type of the last expression.
        // (But the last child is the closing brace probably?)
        let last_is_brace = tree.nth_token(tree.children.len() - 1).is_some();
        let last_index = tree.children.len() - if last_is_brace { 2 } else { 1 };

        let mut is_unreachable = false;
        for i in 1..last_index {
            // TODO: if `is_unreachable` here then we actually have
            //       unreachable code here! We should warn about it
            //       I guess.
            is_unreachable =
                matches!(self.type_of(tree.nth_tree(i)?), Type::Unreachable) || is_unreachable;
        }

        // NOTE: If for some reason the last statement is unsuitable for a
        //       type then we consider the type of the block to be Nothing.
        //       (And explicitly not Error, which is what returning None
        //       would yield.)
        let last_type = self.type_of(tree.nth_tree(last_index)?);

        // If anything in this block generated an "Unreachable" then the
        // whole type of the block is "unreachable" no matter what.
        Some(if is_unreachable {
            Type::Unreachable
        } else {
            last_type
        })
    }

    fn type_of_literal(&self, tree: &Tree) -> Option<Type> {
        assert_eq!(tree.kind, TreeKind::LiteralExpression);

        let tok = tree.nth_token(0)?;
        let pig = match tok.kind {
            TokenKind::Number => Type::F64,
            TokenKind::String => Type::String,
            TokenKind::True | TokenKind::False => Type::Bool,
            _ => panic!("the token {tok} doesn't have a type!"),
        };
        Some(pig)
    }

    fn type_of_grouping(&self, tree: &Tree) -> Option<Type> {
        assert_eq!(tree.kind, TreeKind::GroupingExpression);

        tree.nth_tree(1).map(|t| self.type_of(t))
    }

    fn type_of_conditional(&self, tree: &Tree) -> Option<Type> {
        assert_eq!(tree.kind, TreeKind::ConditionalExpression);

        let cond_tree = tree.nth_tree(1)?;
        let cond_type = self.type_of(cond_tree);
        let then_type = self.type_of(tree.nth_tree(2)?);

        let has_else = tree
            .nth_token(3)
            .map(|t| t.kind == TokenKind::Else)
            .unwrap_or(false);
        let else_type = if has_else {
            Some(self.type_of(tree.nth_tree(4)?))
        } else {
            None
        };

        if !self.can_convert(&cond_type, &Type::Bool) {
            if !cond_type.is_error() {
                self.report_error_tree_ref(cond_tree, "conditions must yield a boolean");
            }
            Some(Type::Error)
        } else {
            match (then_type, else_type) {
                (Type::Error, _) => Some(Type::Error),
                (_, Some(Type::Error)) => Some(Type::Error),

                (Type::Unreachable, None) => Some(Type::Nothing),
                (Type::Unreachable, Some(t)) => Some(t),
                (t, Some(Type::Unreachable)) => Some(t),

                (then_type, else_type) => {
                    let else_type = else_type.unwrap_or(Type::Nothing);
                    if self.can_convert(&then_type, &else_type) {
                        Some(else_type)
                    // I know, I know, as of this writing there is no real
                    // reason to ever check this, because this relationship
                    // is symmetrical. But someday it won't be, and you'll be
                    // glad I had the forethought to be thorough.
                    } else if self.can_convert(&else_type, &then_type) {
                        Some(then_type)
                    } else {
                        self.report_error_tree(
                            tree,
                            format!("the type of the 'then' branch ('{then_type}') must match the type of the 'else' branch ('{else_type}')"),
                        );
                        Some(Type::Error)
                    }
                }
            }
        }
    }

    fn type_of_call(&self, tree: &Tree) -> Option<Type> {
        assert_eq!(tree.kind, TreeKind::CallExpression);

        // TODO: Move the vast majority of error checking out of this
        //       function: once you know that the 0th tree (the function
        //       expression) yields a function type, assume the type of the
        //       call is the type of the function return. Don't bother
        //       matching argument types &c; do that in an explicit
        //       check_call_expression function below.

        let f_ref = tree.nth_tree(0)?;
        let f = self.type_of(f_ref);

        let arg_list = &self.syntax_tree[tree.nth_tree(1)?];
        let arg_types: Vec<_> = arg_list
            .children
            .iter()
            .filter_map(|c| match c {
                Child::Tree(t) => Some((*t, self.type_of(*t))),
                _ => None,
            })
            .collect();

        if f.is_error() || arg_types.iter().any(|(_, t)| t.is_error()) {
            return Some(Type::Error);
        }

        match f {
            Type::Function(params, ret) => {
                let mut any_errors = false;
                if params.len() != arg_types.len() {
                    // TODO: Augment with function name if known
                    self.report_error_tree(tree, format!("expected {} parameters", params.len()));
                    any_errors = true;
                }

                for (i, ((t, a), p)) in arg_types.iter().zip(params.iter()).enumerate() {
                    // a here is the type of the argument expression; p is
                    // the declared type of the parameter.
                    if !self.can_convert(&a, p) {
                        self.report_error_tree_ref(
                            *t,
                            format!(
                                "parameter {i} has an incompatible type: expected {} but got {}",
                                p, a
                            ),
                        );
                        any_errors = true;
                    }
                }

                if any_errors {
                    return Some(Type::Error);
                }

                Some(*ret.clone())
            }

            Type::Method(_, params, ret) => {
                let mut any_errors = false;

                // For the purposes of type checking ignore the self type.
                if params.len() != arg_types.len() {
                    // TODO: Augment with function name if known
                    self.report_error_tree(tree, format!("expected {} parameters", params.len()));
                    any_errors = true;
                }

                for (i, ((t, a), p)) in arg_types.iter().zip(params.iter()).enumerate() {
                    // a here is the type of the argument expression; p is
                    // the declared type of the parameter.
                    if !self.can_convert(&a, p) {
                        self.report_error_tree_ref(
                            *t,
                            format!(
                                "parameter {i} has an incompatible type: expected {} but got {}",
                                p, a
                            ),
                        );
                        any_errors = true;
                    }
                }

                if any_errors {
                    return Some(Type::Error);
                }

                Some(*ret.clone())
            }

            _ => {
                self.report_error_tree_ref(f_ref, format!("expected a function type, got: {f}"));
                Some(Type::Error)
            }
        }
    }

    fn type_of_argument(&self, tree: &Tree) -> Option<Type> {
        assert_eq!(tree.kind, TreeKind::Argument);

        let result = self.type_of(tree.nth_tree(0)?);
        Some(result)
    }

    fn type_of_member_access(&self, tree: &Tree) -> Option<Type> {
        assert_eq!(tree.kind, TreeKind::MemberAccess);

        // Type of member access is the type of the RHS.
        Some(self.type_of(tree.nth_tree(2)?))
    }

    fn type_of_expression_statement(&self, tree: &Tree) -> Option<Type> {
        assert_eq!(tree.kind, TreeKind::ExpressionStatement);
        let last_is_semicolon = tree
            .nth_token(tree.children.len() - 1)
            .map(|t| t.kind == TokenKind::Semicolon)
            .unwrap_or(false);

        let expression_type = self.type_of(tree.nth_tree(0)?);
        Some(match expression_type {
            Type::Unreachable => Type::Unreachable,
            _ => {
                // A semicolon at the end of an expression statement discards
                // the value, leaving us with nothing. (Even if the
                // expression otherwise generated a type error!)
                if last_is_semicolon {
                    Type::Nothing
                } else {
                    expression_type
                }
            }
        })
    }

    fn type_of_identifier(&self, t: TreeRef, tree: &Tree) -> Option<Type> {
        assert_eq!(tree.kind, TreeKind::Identifier);

        let id = tree.nth_token(0)?;
        let environment = self.environment_of(t);
        if let Some(declaration) = environment.bind(id) {
            return Some(match declaration {
                Declaration::Variable { declaration, .. } => self.type_of(*declaration),
                Declaration::Function { declaration, .. } => self.type_of(*declaration),
                Declaration::ExternFunction {
                    declaration_type, ..
                } => declaration_type.clone(),
                Declaration::Class { declaration, .. } => match self.type_of(*declaration) {
                    Type::Object(cd, name) => Type::Class(cd, name.clone()),
                    _ => self.internal_compiler_error(Some(t), "bound to a class not understood"),
                },
            });
        }

        if !environment.is_error {
            self.report_error_tree(tree, format!("cannot find value {id} here"));
        }
        Some(Type::Error)
    }

    fn type_of_self_parameter(&self, tree: &Tree) -> Option<Type> {
        let pl = tree.parent?;
        let param_list = &self.syntax_tree[pl];

        let fd = param_list.parent?;
        let function_decl = &self.syntax_tree[fd];

        let cd = function_decl.parent?;
        let class_decl = &self.syntax_tree[cd];

        if class_decl.kind != TreeKind::ClassDecl {
            self.report_error_tree(tree, "self parameter only allowed in methods");
            Some(Type::Error)
        } else {
            Some(self.type_of(cd))
        }
    }

    fn type_of_self_reference(&self, t: TreeRef, tree: &Tree) -> Option<Type> {
        assert_eq!(tree.kind, TreeKind::SelfReference);

        let id = tree.nth_token(0)?;
        let environment = self.environment_of(t);
        if let Some(declaration) = environment.bind(id) {
            return Some(match declaration {
                Declaration::Variable { declaration, .. } => self.type_of(*declaration),
                _ => self.internal_compiler_error(
                    Some(t),
                    "how did I bind something other than a variable to self?",
                ),
            });
        }

        if !environment.is_error {
            self.report_error_tree(tree, "`self` is only valid in methods");
        }
        Some(Type::Error)
    }

    fn type_of_function_decl(&self, tree: &Tree) -> Option<Type> {
        let param_list = tree.child_tree_of_kind(self.syntax_tree, TreeKind::ParamList)?;

        // NOTE: The methodness here is determined by the presence of a self
        //       parameter, even if that parameter is incorrect (e.g., this
        //       declaration is not nested in a class, or it is not the first
        //       parameter.) We could have also chosen to signal it by our
        //       nesting but we want to extract the self parameter to a
        //       distinguished place in the function type.
        let mut self_type = None;
        let mut parameter_types = Vec::new();
        for p in param_list.child_trees() {
            let p_type = Box::new(self.type_of(p));
            if self.syntax_tree[p].kind == TreeKind::SelfParameter {
                self_type = Some(p_type);
            } else {
                parameter_types.push(p_type);
            }
        }

        let return_type = match tree.child_of_kind(self.syntax_tree, TreeKind::ReturnType) {
            Some(t) => self.type_of(t),
            None => Type::Nothing,
        };
        let return_type = Box::new(return_type);

        Some(match self_type {
            Some(self_type) => Type::Method(self_type, parameter_types, return_type),
            None => Type::Function(parameter_types, return_type),
        })
    }

    fn type_of_parameter(&self, tree: &Tree) -> Option<Type> {
        assert_eq!(tree.kind, TreeKind::Parameter);
        match tree.child_of_kind(self.syntax_tree, TreeKind::TypeExpression) {
            Some(t) => Some(self.type_of(t)),
            None => {
                self.report_error_tree(tree, format!("the parameter is missing a type"));
                Some(Type::Error)
            }
        }
    }

    fn type_of_list_constructor(&self, t: TreeRef, tree: &Tree) -> Option<Type> {
        assert_eq!(tree.kind, TreeKind::ListConstructor);
        let mut element_type = None;
        for ct in tree.child_trees() {
            let child_type = self.type_of(ct);
            element_type = match element_type {
                None => Some(child_type),
                Some(list_type) => {
                    if list_type.is_error() {
                        Some(child_type)
                    } else if child_type.is_error() {
                        Some(list_type)
                    } else if self.can_convert(&child_type, &list_type) {
                        Some(list_type)
                    } else if self.can_convert(&list_type, &child_type) {
                        Some(child_type)
                    } else {
                        // Even if there is some incompatibility we stick
                        // with the list type, preferring to guess what was
                        // intended rather than just error out.
                        self.report_error_tree_ref(ct, format!("list element of type {child_type} is not compatible with the list type {list_type}"));
                        Some(list_type)
                    }
                }
            }
        }

        let element_type = element_type.unwrap_or_else(|| Type::TypeVariable(t));
        Some(Type::List(Box::new(element_type)))
    }

    fn type_of_new_object_expression(&self, tree: &Tree) -> Option<Type> {
        assert_eq!(tree.kind, TreeKind::NewObjectExpression);

        // NOTE: Matching fields is done in the check function, as is
        //       reporting on the suitability of the type.
        Some(self.type_of(tree.nth_tree(1)?))
    }

    fn type_of_class_decl(&self, t: TreeRef, tree: &Tree) -> Option<Type> {
        assert_eq!(tree.kind, TreeKind::ClassDecl);

        // The details of a class are computed lazily, but this is enough of
        // a belly-button.
        let name = tree.nth_token(1)?;
        Some(Type::Object(t, name.as_str().into()))
    }

    fn type_of_field_decl(&self, tree: &Tree) -> Option<Type> {
        assert_eq!(tree.kind, TreeKind::FieldDecl);

        // Type of a field declaration is the type of the type expression.
        Some(self.type_of(tree.nth_tree(2)?))
    }

    fn type_of_field_value(&self, t: TreeRef, tree: &Tree) -> Option<Type> {
        assert_eq!(tree.kind, TreeKind::FieldValue);

        if let Some(colon) = tree.nth_token(1) {
            if colon.kind == TokenKind::Colon {
                // Form 1: { x: e, ... }
                return Some(self.type_of(tree.nth_tree(2)?));
            }
        }

        // Form 2: { x, ... }
        let environment = self.environment_of(t);
        let id = tree.nth_token(0)?;
        let declaration = match environment.bind(id) {
            Some(d) => d,
            None => {
                if !environment.is_error {
                    self.report_error_tree(tree, format!("cannot find value {id} here"));
                }
                return Some(Type::Error);
            }
        };
        match declaration {
            Declaration::Variable { declaration, .. }
            | Declaration::Function { declaration, .. } => Some(self.type_of(*declaration)),

            Declaration::ExternFunction {
                declaration_type, ..
            } => Some(declaration_type.clone()),

            Declaration::Class { .. } => {
                self.report_error_tree(
                    tree,
                    format!("'{id}' is a class, and cannot be the value of a field"),
                );
                Some(Type::Error)
            }
        }
    }

    fn type_of_list_constructor_element(&self, tree: &Tree) -> Option<Type> {
        assert_eq!(tree.kind, TreeKind::ListConstructorElement);
        Some(self.type_of(tree.nth_tree(0)?))
    }

    fn type_of_return_type(&self, tree: &Tree) -> Option<Type> {
        assert_eq!(tree.kind, TreeKind::ReturnType);
        Some(self.type_of(tree.nth_tree(1)?)) // type expression
    }

    fn type_of_if_statement(&self, tree: &Tree) -> Option<Type> {
        Some(self.type_of(tree.nth_tree(0)?))
    }

    fn type_of_alternate_type(&self, tree: &Tree) -> Option<Type> {
        // TODO: IDEA: nth_tree returns a bogus tree if not a tree, stop returning Option?
        let left = self.type_of(tree.nth_tree(0)?);
        let right = self.type_of(tree.nth_tree(2)?);
        Some(Type::Alternate(Box::new(left), Box::new(right)))
    }

    fn type_of_match_expression(&self, tree: &Tree) -> Option<Type> {
        Some(self.type_of(tree.child_of_kind(self.syntax_tree, TreeKind::MatchBody)?))
    }

    fn type_of_match_body(&self, tree: &Tree) -> Option<Type> {
        let arms: Vec<_> = tree
            .children_of_kind(self.syntax_tree, TreeKind::MatchArm)
            .collect();

        if arms.len() == 0 {
            self.report_error_tree(tree, "a match expression must have at least one arm");
            Some(Type::Error)
        } else {
            let mut actual_type = self.type_of(arms[0]);
            for arm in &arms[1..] {
                let arm_type = self.type_of(*arm);
                if !self.can_convert(&arm_type, &actual_type) {
                    if self.can_convert(&actual_type, &arm_type) {
                        // New lowest-common-denominator type
                        actual_type = arm_type;
                    } else {
                        self.report_error_tree_ref(*arm, format!("this arm produces a value of type {arm_type} which is incompatible with the general result type {actual_type}"));
                    }
                }
            }

            Some(actual_type)
        }
    }

    fn type_of_match_arm(&self, tree: &Tree) -> Option<Type> {
        // The type of a match arm is the type of the expression to the right.
        Some(self.type_of(tree.nth_tree(2)?))
    }

    pub fn dump_compiler_state(&self, tr: Option<TreeRef>) {
        eprintln!("Parsed the tree as:");
        eprintln!("\n{}", self.syntax_tree.dump(true));

        {
            let errors = self.snapshot_errors();
            if errors.len() == 0 {
                eprintln!("There were no errors reported during checking.\n");
            } else {
                eprintln!(
                    "{} error{} reported during checking:",
                    errors.len(),
                    if errors.len() == 1 { "" } else { "s" }
                );
                for error in errors.iter() {
                    eprintln!("  Error: {error}");
                }
                eprintln!();
            }
        }

        if let Some(tr) = tr {
            eprintln!("This is about the tree: {:?}", &self.syntax_tree[tr]);
            eprintln!("The logical parent chain of the tree was:\n");
            let mut current = Some(tr);
            while let Some(c) = current {
                let t = &self.syntax_tree[c];
                eprintln!("  {:?} [{}-{})", t.kind, t.start_pos, t.end_pos);
                current = self.logical_parents[c.index()];
            }
            eprintln!("\nThe environment of the tree was:");
            let mut environment = Some(self.environment_of(tr));
            while let Some(env) = environment {
                for (k, v) in env.declarations.iter() {
                    eprint!("  {k}: ");
                    match v {
                        Declaration::Variable {
                            location, index, ..
                        } => {
                            eprintln!("(variable {location:?} {index})");
                        }
                        Declaration::Function { declaration, .. } => {
                            eprintln!(" (function {declaration:?})");
                        }
                        Declaration::ExternFunction { id, .. } => {
                            eprintln!(" (extern {id:?})");
                        }
                        Declaration::Class { declaration, .. } => {
                            eprintln!(" (class {declaration:?})");
                        }
                    };
                }
                environment = env.parent.clone();
            }
            eprintln!();
        }
    }

    pub fn internal_compiler_error(&self, tr: Option<TreeRef>, message: &str) -> ! {
        eprintln!("Internal compiler error: {message}!");
        self.dump_compiler_state(tr);
        panic!("INTERNAL COMPILER ERROR: {message}")
    }
}

pub fn check(s: &Semantics) {
    for t in s.syntax_tree.trees() {
        let tree = &s.syntax_tree[t];
        match tree.kind {
            TreeKind::Error => {} // already reported
            TreeKind::File => {}
            TreeKind::FunctionDecl => check_function_decl(s, t, tree),
            TreeKind::ParamList => {
                let _ = s.environment_of(t);
            }
            TreeKind::Parameter => {
                let _ = s.type_of(t);
            }
            TreeKind::TypeExpression | TreeKind::AlternateType | TreeKind::TypeIdentifier => {
                let _ = s.type_of(t);
            }
            TreeKind::Block => {
                let _ = s.type_of(t);
            }
            TreeKind::LetStatement => check_let(s, tree),
            TreeKind::ReturnStatement => check_return_statement(s, tree),
            TreeKind::ExpressionStatement
            | TreeKind::LiteralExpression
            | TreeKind::GroupingExpression
            | TreeKind::UnaryExpression
            | TreeKind::ConditionalExpression
            | TreeKind::BinaryExpression
            | TreeKind::MemberAccess => {
                let _ = s.type_of(t);
            }
            TreeKind::CallExpression => {
                let _ = s.type_of(t);
            }

            TreeKind::ArgumentList => {}
            TreeKind::Argument => {
                let _ = s.type_of(t);
            }
            TreeKind::IfStatement => {}
            TreeKind::Identifier => {
                let _ = s.type_of(t);
            }
            TreeKind::ReturnType => {}
            TreeKind::TypeParameter => {}
            TreeKind::TypeParameterList => {}
            TreeKind::ListConstructor => {
                let _ = s.type_of(t);
            }
            TreeKind::ListConstructorElement => {
                let _ = s.type_of(t);
            }
            TreeKind::ForStatement => check_for_statement(s, t),

            TreeKind::ClassDecl => check_class_declaration(s, tree),
            TreeKind::FieldDecl => {}
            TreeKind::FieldList => {}
            TreeKind::NewObjectExpression => check_new_object_expression(s, tree),
            TreeKind::FieldValue => {}
            TreeKind::SelfParameter => {}
            TreeKind::SelfReference => {}

            TreeKind::IsExpression => {}
            TreeKind::VariableBinding => {}
            TreeKind::Pattern => check_pattern(s, tree),
            TreeKind::WildcardPattern => {}

            TreeKind::MatchArm => check_match_arm(s, tree),
            TreeKind::MatchBody => check_match_body(s, t, tree),
            TreeKind::MatchExpression => {}
        }
    }
}

fn check_function_decl(s: &Semantics, t: TreeRef, tree: &Tree) {
    assert_eq!(tree.kind, TreeKind::FunctionDecl);
    let _ = s.environment_of(t);

    let return_type_tree = tree.child_of_kind(s.syntax_tree, TreeKind::ReturnType);
    let return_type = return_type_tree
        .map(|t| s.type_of(t))
        .unwrap_or(Type::Nothing);

    if let Some(body) = tree.child_of_kind(s.syntax_tree, TreeKind::Block) {
        let body_type = s.type_of(body);
        if !s.can_convert(&body_type, &return_type) {
            // Just work very hard to get an appropriate error span.
            let (start, end) = return_type_tree
                .map(|t| {
                    let rtt = &s.syntax_tree[t];
                    (rtt.start_pos, rtt.end_pos)
                })
                .unwrap_or_else(|| {
                    let start = tree.start_pos;
                    let end_tok = tree
                        .nth_token(1)
                        .unwrap_or_else(|| tree.nth_token(0).unwrap());
                    let end_pos = end_tok.start + end_tok.as_str().len();
                    (start, end_pos)
                });

            s.report_error_span(start, end, format!("the body of this function yields a value of type '{body_type}', but callers expect this function to produce a '{return_type}'"));
        }
    }
}

fn check_let(s: &Semantics, tree: &Tree) {
    let Some(name) = tree.nth_token(1) else {
        return;
    };
    let Some(expr) = tree.nth_tree(3) else { return };

    if let Type::Method(..) = s.type_of(expr) {
        let start = name.start;
        let end = name.start + name.as_str().len();
        s.report_error_span(start, end, "methods cannot be assigned to variables");
    }
}

fn check_return_statement(s: &Semantics, tree: &Tree) {
    assert_eq!(tree.kind, TreeKind::ReturnStatement);

    let mut enclosing_function = tree.parent;
    while let Some(fp) = enclosing_function {
        let fpt = &s.syntax_tree[fp];
        if fpt.kind == TreeKind::FunctionDecl {
            break;
        }

        enclosing_function = fpt.parent;
    }
    let Some(enclosing_function) = enclosing_function else {
        s.report_error_tree(
            tree,
            "a return statement can only be used inside a function",
        );
        return;
    };

    let function_type = s.type_of(enclosing_function);
    match function_type {
        Type::Function(_, expected_type) | Type::Method(_, _, expected_type) => {
            let actual_type = if let Some(expr) = tree.nth_tree(1) {
                s.type_of(expr)
            } else {
                Type::Error
            };

            if !s.can_convert(&actual_type, &expected_type) {
                s.report_error_tree(tree, format!("callers of this function expect a value of type '{expected_type}' but this statement returns a value of type '{actual_type}'"));
            }
        }
        Type::Error => (),
        _ => s.internal_compiler_error(
            Some(enclosing_function),
            "a return statement in here expected this to yield a function type",
        ),
    }

    // OK this one is a little bit messed up because it reaches *up*, sorry.
}

fn check_for_statement(s: &Semantics, t: TreeRef) {
    let _ = s.environment_of(t);
}

fn check_new_object_expression(s: &Semantics, tree: &Tree) {
    let Some(type_expression) = tree.nth_tree(1) else {
        return;
    };
    let Some(field_list) = tree.child_tree_of_kind(s.syntax_tree, TreeKind::FieldList) else {
        return;
    };

    let class_type = s.type_of(type_expression); // TODO: Should yield a ClassType not an ObjectType?
    match &class_type {
        Type::Object(c, _) => {
            let class = s.class_of(*c);

            let mut any_errors = false;
            let mut field_bindings = HashMap::new();
            for field in field_list.children_of_kind(s.syntax_tree, TreeKind::FieldValue) {
                let f = &s.syntax_tree[field];
                if let Some(name) = f.nth_token(0) {
                    let field_type = s.type_of(field);
                    field_bindings.insert(name.as_str(), (field, field_type));
                } else {
                    any_errors = true;
                }
            }

            // Check individual bindings...
            for f in class.fields.iter() {
                if let Some((field_tree, expr_type)) = field_bindings.get(&*f.name) {
                    if !s.can_convert(expr_type, &f.field_type) {
                        s.report_error_tree_ref(
                            *field_tree,
                            format!(
                                "field {} is of type {}, but this expression generates a {}",
                                f.name, f.field_type, expr_type,
                            ),
                        );
                    }
                    field_bindings.remove(&*f.name);
                } else if !any_errors {
                    s.report_error_tree(
                        tree,
                        format!("missing an initializer for field {}", f.name),
                    );
                }
            }

            if !any_errors {
                for (n, (field_tree, _)) in field_bindings.iter() {
                    s.report_error_tree_ref(
                        *field_tree,
                        format!("{} does not have a field named {}", class_type, n),
                    );
                }
            }
        }
        Type::Error => (),
        ct => s.report_error_tree_ref(
            type_expression,
            format!("expected this to be a class type, but it is {ct}"),
        ),
    }
}

fn check_class_declaration(s: &Semantics, tree: &Tree) {
    let mut fields = HashMap::new();
    for field in tree.children_of_kind(s.syntax_tree, TreeKind::FieldDecl) {
        let f = &s.syntax_tree[field];
        let Some(name) = f.nth_token(0) else {
            continue;
        };
        match fields.insert(name.as_str(), field) {
            Some(_) => {
                s.report_error_tree(f, format!("duplicate definition of field '{name}'"));
            }
            None => {}
        }
    }
}

fn check_pattern(s: &Semantics, tree: &Tree) {
    // If there's an AND then it must produce a boolean.
    let and_index = tree.children.iter().position(|c| match c {
        Child::Token(t) => t.kind == TokenKind::And,
        _ => false,
    });
    if let Some(and_index) = and_index {
        if let Some(pred) = tree.nth_tree(and_index + 1) {
            let predicate_type = s.type_of(pred);
            if !s.can_convert(&predicate_type, &Type::Bool) {
                // TODO: TEST
                s.report_error_tree_ref(
                    pred,
                    format!("this predicate produces {predicate_type}, but must produce a boolean"),
                )
            }
        }
    }
}

fn check_match_body(s: &Semantics, t: TreeRef, tree: &Tree) {
    let _ = s.type_of(t); // Checks arm count and compatibility.

    let arms: Vec<_> = tree
        .children_of_kind(s.syntax_tree, TreeKind::MatchArm)
        .collect();

    if arms.len() > 0 {
        for arm in &arms[1..] {
            // TODO: How do I know if it's complete?
            // TODO: How do I know if it's redundant?
        }
    }
}

fn check_match_arm(s: &Semantics, tree: &Tree) {}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::parser::parse;

    #[test]
    #[should_panic(expected = "INTERNAL COMPILER ERROR: oh no")]
    pub fn ice() {
        let (tree, lines) = parse("1 + 1");
        let semantics = Semantics::new(&tree, &lines);
        semantics.internal_compiler_error(tree.root(), "oh no");
    }
}
