use std::{rc::Rc, slice};

use crate::{
    error::{MachineError, ValueConversionError},
    vm::{
        Value,
        env::Environment,
        value::{
            ConsCell,
            convert::{AnyFunction, TryFromValue},
        },
    },
};

pub fn load(env: &Rc<Environment>) {
    // // vectors
    // env.defun_native("getv", |vm, _, args| {
    //     let [vec, index] = args else {
    //         return Err(vm.error_at_ip(MachineErrorKind::InvalidArgument));
    //     };
    //     let Value::Vector(vec) = vec else {
    //         return Err(vm.error_at_ip(MachineErrorKind::InvalidArgument));
    //     };
    //     let index = i64::try_from_value(index).map_err(|e| vm.error_at_ip(e))? as isize;
    //     let value = if index < 0 {
    //         todo!()
    //     } else {
    //         vec.value_at(index as usize).unwrap_or(Value::Nil)
    //     };
    //     Ok(value)
    // });
    // env.defun_native("setv", |vm, _, args| {
    //     let [vec, index, value] = args else {
    //         return Err(vm.error_at_ip(MachineErrorKind::InvalidArgument));
    //     };
    //     let Value::Vector(vec) = vec else {
    //         return Err(vm.error_at_ip(MachineErrorKind::InvalidArgument));
    //     };
    //     let index = i64::try_from_value(index).map_err(|e| vm.error_at_ip(e))? as isize;
    //     if index < 0 {
    //         todo!()
    //     } else {
    //         vec.set_value_at(index as usize, value.clone());
    //     }
    //     Ok(Value::Nil)
    // });

    // lists
    env.defun_native(
        "append",
        "Concatenates the lists into one list",
        |_, _, args| match args {
            [] => Ok(Value::Nil),
            [xs] => Ok(xs.clone()),
            [head @ .., tail] => {
                let mut elements = vec![];
                for arg in head {
                    let iter = arg.proper_iter(ValueConversionError {
                        expected: "proper list".into(),
                        got: arg.clone(),
                    });
                    for element in iter {
                        elements.push(element?.clone());
                    }
                }
                let mut output = tail.clone();
                for element in elements.into_iter().rev() {
                    output = element.cons(output);
                }
                Ok(output)
            }
        },
    );
    env.defun_native(
        "car",
        "Returns the CAR value of a cons-cell",
        |_, _, args| {
            let [x] = args else {
                return Err(MachineError::InvalidArgumentCount);
            };
            let cons: &ConsCell = TryFromValue::try_from_value(x)?;
            Ok(cons.0.clone())
        },
    );
    env.defun_native(
        "cdr",
        "Returns the CDR value of a cons-cell",
        |_, _, args| {
            let [x] = args else {
                return Err(MachineError::InvalidArgumentCount);
            };
            let cons: &ConsCell = TryFromValue::try_from_value(x)?;
            Ok(cons.1.clone())
        },
    );
    env.defun_native(
        "cons",
        "Constructs a cons-cell from CAR and CDR arguments",
        |_, _, args| {
            let [car, cdr] = args else {
                return Err(MachineError::InvalidArgumentCount);
            };
            Ok(Value::Cons(Rc::new(ConsCell(car.clone(), cdr.clone()))))
        },
    );
    env.defun_native(
        "list",
        "Constructs a list from the arguments",
        |_, _, args| {
            let out = Value::list_or_nil(args.iter().cloned());
            Ok(out)
        },
    );
    env.defun_native(
        "length",
        "Returns the length of the given list",
        |_, _, args| {
            let [xs] = args else {
                return Err(MachineError::InvalidArgumentCount);
            };

            let mut xs = xs;
            let mut count = 0;
            while !xs.is_nil() {
                let Value::Cons(cons) = xs else {
                    break;
                };
                let ConsCell(_, cdr) = cons.as_ref();
                count += 1;
                xs = cdr;
            }
            Ok(count.into())
        },
    );
    env.defun_native(
        "find",
        "Finds an entry in the list matching given predicate",
        |vm, env, args| {
            let [predicate, list] = args else {
                return Err(MachineError::InvalidArgumentCount);
            };
            let predicate = AnyFunction::try_from_value(predicate)?;
            let mut list = list;
            while !list.is_nil() {
                let Value::Cons(cons) = list else {
                    break;
                };
                let pred_value = predicate.invoke(vm, env, slice::from_ref(&cons.0))?;
                if pred_value.is_trueish() {
                    return Ok(cons.0.clone());
                }
                list = &cons.1;
            }
            Ok(Value::Nil)
        },
    );
}
