use core::ops::{Deref, DerefMut};

use alloc::sync::Arc;
use libk_util::OneTimeInit;
use yggdrasil_abi::{error::Error, io::FileMode};

use crate::vfs::{impls::MemoryDirectory, path::OwnedFilename, Metadata, Node, NodeFlags, NodeRef};

use super::attribute::Attribute;

pub struct KObject<D> {
    pub(super) data: D,
    pub(super) node: Arc<Node>,
}

impl<D> KObject<D> {
    pub fn new(data: D) -> Arc<Self> {
        let node = Node::directory(
            MemoryDirectory,
            NodeFlags::IN_MEMORY_SIZE | NodeFlags::IN_MEMORY_PROPS,
            Some(Metadata::now_root(FileMode::new(0o555), 0)),
            None,
        );
        Arc::new(Self { data, node })
    }

    pub fn add_attribute<A: Attribute<D>>(self: &Arc<Self>, attribute: A) -> Result<(), Error> {
        let node = attribute.instantiate(self)?;
        self.node.add_child(attribute.name(), node)?;
        Ok(())
    }

    pub fn add_object<S: AsRef<str>, T>(
        &self,
        name: S,
        child: Arc<KObject<T>>,
    ) -> Result<(), Error> {
        let filename = OwnedFilename::new(name)?;
        self.node.add_child(filename, child.node.clone())
    }

    pub fn node(&self) -> &NodeRef {
        &self.node
    }

    pub(super) fn data(&self) -> &D {
        &self.data
    }

    pub fn unlink(&self) {
        let parent = self.node.parent();
        parent.unlink_child(&self.node).ok();
    }
}

impl<D> Deref for KObject<D> {
    type Target = D;

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

impl<D> DerefMut for KObject<D> {
    fn deref_mut(&mut self) -> &mut Self::Target {
        &mut self.data
    }
}

unsafe impl<D: Send> Send for KObject<D> {}
unsafe impl<D: Send> Sync for KObject<D> {}

// Static, fixed objects
// `/`
pub static ROOT_OBJECT: OneTimeInit<Arc<KObject<()>>> = OneTimeInit::new();
// `/kernel`
pub static KERNEL_OBJECT: OneTimeInit<Arc<KObject<()>>> = OneTimeInit::new();
// `/device`
pub static DEVICE_OBJECT: OneTimeInit<Arc<KObject<()>>> = OneTimeInit::new();
// `/debug`
pub static DEBUG_OBJECT: OneTimeInit<Arc<KObject<()>>> = OneTimeInit::new();
// `/bus`
pub static BUS_OBJECT: OneTimeInit<Arc<KObject<()>>> = OneTimeInit::new();
// `/proc`
pub static PROC_OBJECT: OneTimeInit<Arc<KObject<()>>> = OneTimeInit::new();

fn setup_fixed_object(root: &Arc<KObject<()>>, obj: &OneTimeInit<Arc<KObject<()>>>, name: &str) {
    let obj = obj.init(KObject::new(()));
    root.add_object(name, obj.clone()).ok();
}

pub fn setup_fixed_objects() -> Arc<Node> {
    let root = ROOT_OBJECT.init(KObject::new(()));

    setup_fixed_object(root, &KERNEL_OBJECT, "kernel");
    setup_fixed_object(root, &DEVICE_OBJECT, "device");
    setup_fixed_object(root, &DEBUG_OBJECT, "debug");
    setup_fixed_object(root, &BUS_OBJECT, "bus");
    setup_fixed_object(root, &PROC_OBJECT, "proc");

    root.node.clone()
}
