use core::{mem::MaybeUninit, str::FromStr};

use libk_util::sync::IrqSafeSpinlock;
use yggdrasil_abi::{error::Error, io::DirectoryEntry, util::FixedString};

use crate::vfs::node::NodeRef;

use super::DirectoryOpenPosition;

#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub(super) enum DirectoryCachePosition {
    Dot,
    DotDot,
    Index(usize),
}

#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub(super) enum DirectoryPosition {
    Cache(DirectoryCachePosition),
    Physical(u64),
}

pub struct DirectoryFile {
    pub(super) node: NodeRef,
    pub(super) position: IrqSafeSpinlock<DirectoryPosition>,
}

impl DirectoryFile {
    pub(super) fn read_cached(
        node: &NodeRef,
        mut pos: DirectoryCachePosition,
        entries: &mut [MaybeUninit<DirectoryEntry>],
    ) -> Result<(usize, DirectoryCachePosition), Error> {
        let directory = node.as_directory()?;
        let children = directory.children.lock();
        let mut rem = entries.len();
        let mut off = 0;

        while rem != 0 {
            let entry = match pos {
                DirectoryCachePosition::Dot => {
                    pos = DirectoryCachePosition::DotDot;
                    Some((FixedString::from_str(".").unwrap(), node.clone()))
                }
                DirectoryCachePosition::DotDot => {
                    pos = DirectoryCachePosition::Index(0);
                    Some((FixedString::from_str("..").unwrap(), node.parent()))
                }
                DirectoryCachePosition::Index(index)
                    if let Some((name, node)) = children.get(index) =>
                {
                    pos = DirectoryCachePosition::Index(index + 1);
                    Some((FixedString::from_str(name)?, node.flatten_hardlink()?))
                }
                DirectoryCachePosition::Index(_) => None,
            };

            let Some((name, node)) = entry else {
                break;
            };

            entries[off].write(DirectoryEntry {
                name,
                ty: Some(node.ty()),
            });

            off += 1;
            rem -= 1;
        }

        Ok((off, pos))
    }

    pub(super) fn read_physical(
        node: &NodeRef,
        pos: u64,
        entries: &mut [MaybeUninit<DirectoryEntry>],
    ) -> Result<(usize, u64), Error> {
        node.read_directory(pos, entries)
    }

    pub(super) fn read_entries(
        &self,
        entries: &mut [MaybeUninit<DirectoryEntry>],
    ) -> Result<usize, Error> {
        let mut position = self.position.lock();

        let (count, pos) = match *position {
            DirectoryPosition::Cache(pos) => {
                let (count, pos) = DirectoryFile::read_cached(&self.node, pos, entries)?;
                (count, DirectoryPosition::Cache(pos))
            }
            DirectoryPosition::Physical(off) => {
                let (count, pos) = DirectoryFile::read_physical(&self.node, off, entries)?;
                (count, DirectoryPosition::Physical(pos))
            }
        };
        *position = pos;

        Ok(count)
    }
}

impl From<DirectoryOpenPosition> for DirectoryPosition {
    fn from(value: DirectoryOpenPosition) -> Self {
        match value {
            DirectoryOpenPosition::FromCache => Self::Cache(DirectoryCachePosition::Dot),
            DirectoryOpenPosition::FromPhysical(off) => Self::Physical(off),
        }
    }
}
