use std::rc::Rc;

use crate::{
    error::MachineErrorAt,
    vm::{
        env::{Environment, Macro},
        machine::Machine,
        value::{ClosureValue, ConsCell, Value},
    },
};

pub trait MacroExpand: Sized {
    fn macro_expand(
        &self,
        vm: &mut Machine,
        env: &Rc<Environment>,
        tail: bool,
    ) -> Result<Self, MachineErrorAt>;
}

impl MacroExpand for Value {
    fn macro_expand(
        &self,
        vm: &mut Machine,
        env: &Rc<Environment>,
        tail: bool,
    ) -> Result<Self, MachineErrorAt> {
        match self {
            Self::Nil
            | Self::Identifier(_)
            | Self::Number(_)
            | Self::Boolean(_)
            | Self::String(_)
            | Self::Keyword(_)
            // | Self::OpaqueValue(_)
            // | Self::BytecodeFunction(_)
            | Self::Quote(_)
            | Self::Closure(_)
            | Self::Function(_)
            | Self::NativeFunction(_)
            | Self::NativeValue(_)
            | Self::Vector(_)
            | Self::UnquoteSplice(_)
            | Self::Unquote(_) => Ok(self.clone()),
            // | Self::NativeFunction(_) => Ok(self.clone()),
            Self::Cons(cons) => {
                let ConsCell(car, cdr) = cons.as_ref();
                let car = car.macro_expand(vm, env, false)?;
                let cdr = cdr.macro_expand(vm, env, true)?;

                if tail {
                    let cons = Rc::new(ConsCell(car, cdr.clone()));
                    return Ok(Self::Cons(cons));
                }

                let (Self::Identifier(identifier), Some(args)) = (&car, cdr.as_proper_list())
                else {
                    let cons = Rc::new(ConsCell(car, cdr.clone()));
                    return Ok(Self::Cons(cons));
                };
                let Some(mac) = env.global_macro(identifier) else {
                    // eprintln!("{identifier} is not a macro");
                    let cons = Rc::new(ConsCell(car, cdr.clone()));
                    return Ok(Self::Cons(cons));
                };

                 match mac {
                     Macro::Native(native) => {
                         let result = native.invoke(vm, env, &args[..]).expect("TODO");
                         Ok(result)
                     }
                     Macro::Bytecode(bytecode) => {
                         let closure = ClosureValue { function: bytecode.clone(), upvalues: vec![] };
                         let result = vm.evaluate_closure_args(env, closure, &args[..])?;
                         Ok(result)
                     }
                 }
            }
            Self::Quasi(value) => {
                let value = expand_quasiquote(value);
                value.macro_expand(vm, env, false)
            }
        }
    }
}

fn expand_quasiquote(value: &Value) -> Value {
    match value {
        Value::Nil => Value::Nil,
        // Toplevel-only
        Value::Unquote(inner) => inner.as_ref().clone(),
        Value::UnquoteSplice(inner) => inner.as_ref().clone(),
        Value::Cons(_) => {
            let mut elements = vec![];
            let mut current = value;

            elements.push(Value::Identifier("append".into()));

            while !current.is_nil() {
                let Value::Cons(cons) = current else { todo!() };
                let ConsCell(car, cdr) = cons.as_ref();

                match car {
                    Value::UnquoteSplice(splice) => {
                        elements.push(splice.as_ref().clone());
                    }
                    _ => {
                        let exp_car = expand_quasiquote(car);
                        elements.push(Value::list_or_nil([
                            Value::Identifier("list".into()),
                            exp_car,
                        ]));
                    }
                }

                current = cdr;
            }

            Value::list_or_nil(elements)
        }
        _ => value.clone().quote(),
    }
}
