//! Virtual Filesystem (VFS) Interface Definitions
//!
//! This module defines the core traits and data structures for the kernel's I/O subsystem.
//! It is based on a layered design:
//!
//! 1. `BlockDevice`: An abstraction for raw block-based hardware (e.g., disks).
//! 2. `Filesystem`: An abstraction for a mounted filesystem instance (e.g.,
//!    ext4, fat32). Its main role is to provide the root `Inode`.
//! 3. `Inode`: A stateless representation of a filesystem object (file,
//!    directory, etc.). It handles operations by explicit offsets (`read_at`,
//!    `write_at`).
//! 4. `File`: A stateful open file handle. It maintains a cursor and provides
//!    the familiar `read`, `write`, and `seek` operations.
extern crate alloc;

pub mod attr;
pub mod blk;
pub mod filesystems;
pub mod path;
pub mod pathbuf;

use core::any::Any;

use crate::{
    driver::CharDevDescriptor,
    error::{FsError, KernelError, Result},
    fs::{path::Path, pathbuf::PathBuf},
};
use alloc::vec::Vec;
use alloc::{boxed::Box, string::String, sync::Arc};
use async_trait::async_trait;
use attr::{FileAttr, FilePermissions};
use core::time::Duration;

mod _open_flags {
    #![allow(missing_docs)]
    bitflags::bitflags! {
        /// Flags used when opening a file, corresponding to POSIX `O_*` constants.
        #[derive(Clone, Copy, Debug, PartialEq, Eq)]
        pub struct OpenFlags: u32 {
            const O_RDONLY    = 0b000;
            const O_WRONLY    = 0b001;
            const O_RDWR      = 0b010;
            const O_ACCMODE   = 0b011;
            const O_CREAT     = 0o100;
            const O_EXCL      = 0o200;
            const O_TRUNC     = 0o1000;
            const O_DIRECTORY = 0o200000;
            const O_APPEND    = 0o2000;
            const O_NONBLOCK  = 0o4000;
            const O_CLOEXEC   = 0o2000000;
        }
    }
}
pub use _open_flags::OpenFlags;

// Reserved pseudo filesystem instances created internally in the kernel.
/// Filesystem instance ID for the device filesystem.
pub const DEVFS_ID: u64 = 1;
/// Filesystem instance ID for the proc filesystem.
pub const PROCFS_ID: u64 = 2;
/// Filesystem instance ID for the sys filesystem.
pub const SYSFS_ID: u64 = 3;
/// Filesystem instance ID for the cgroup filesystem.
pub const CGROUPFS_ID: u64 = 4;
/// Starting ID for user-mounted filesystem instances.
pub const FS_ID_START: u64 = 10;

/// Trait for a mounted filesystem instance. Its main role is to act as a
/// factory for Inodes.
#[async_trait]
pub trait Filesystem: Send + Sync {
    /// Get the root inode of this filesystem.
    async fn root_inode(&self) -> Result<Arc<dyn Inode>>;

    /// Returns the instance ID for this FS.
    fn id(&self) -> u64;

    /// Get magic
    fn magic(&self) -> u64;

    /// Flushes all pending data to the underlying storage device(s).
    ///
    /// The default implementation is a no-op so that read-only filesystems do
    /// not need to override it.
    async fn sync(&self) -> Result<()> {
        Ok(())
    }
}

/// A unique identifier for an inode across the entire VFS, combining a filesystem ID and inode number.
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub struct InodeId(u64, u64);

impl InodeId {
    /// Creates an `InodeId` from a filesystem ID and an inode number.
    pub fn from_fsid_and_inodeid(fs_id: u64, inode_id: u64) -> Self {
        Self(fs_id, inode_id)
    }

    /// Returns a sentinel `InodeId` used as a placeholder.
    pub fn dummy() -> Self {
        Self(u64::MAX, u64::MAX)
    }

    /// Returns the filesystem ID component.
    pub fn fs_id(self) -> u64 {
        self.0
    }

    /// Returns the inode number component.
    pub fn inode_id(self) -> u64 {
        self.1
    }
}

/// Standard POSIX file types.
#[allow(missing_docs)]
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum FileType {
    File,
    Directory,
    Symlink,
    BlockDevice(CharDevDescriptor),
    CharDevice(CharDevDescriptor),
    Fifo,
    Socket,
}

impl From<FileType> for u32 {
    fn from(file_type: FileType) -> Self {
        match file_type {
            FileType::Directory => 0o040000,
            FileType::CharDevice(_) => 0o020000,
            FileType::BlockDevice(_) => 0o060000,
            FileType::File => 0o100000,
            FileType::Fifo => 0o010000,
            FileType::Symlink => 0o120000,
            FileType::Socket => 0o140000,
        }
    }
}

/// A stateful, streaming iterator for reading directory entries.
#[async_trait]
pub trait DirStream: Send + Sync {
    /// Fetches the next directory entry in the stream. Returns `Ok(None)` when
    /// the end of the directory is reached.
    async fn next_entry(&mut self) -> Result<Option<Dirent>>;
}

/// Represents a single directory entry.
#[derive(Debug, Clone)]
pub struct Dirent {
    /// The inode identifier of this entry.
    pub id: InodeId,
    /// The name of this directory entry.
    pub name: String,
    /// The type of file this entry represents.
    pub file_type: FileType,
    /// The byte offset of this entry within the directory.
    pub offset: u64,
}

impl Dirent {
    /// Creates a new directory entry.
    pub fn new(name: String, id: InodeId, file_type: FileType, offset: u64) -> Self {
        Self {
            id,
            name,
            file_type,
            offset,
        }
    }
}

/// Specifies how to seek within a file, mirroring `std::io::SeekFrom`.
#[derive(Debug, Copy, Clone)]
pub enum SeekFrom {
    /// Seek from the beginning of the file.
    Start(u64),
    /// Seek from the end of the file.
    End(i64),
    /// Seek relative to the current position.
    Current(i64),
}

/// Trait for a raw block device.
#[async_trait]
pub trait BlockDevice: Send + Sync {
    /// Read one or more blocks starting at `block_id`.
    /// The `buf` length must be a multiple of `block_size`.
    async fn read(&self, block_id: u64, buf: &mut [u8]) -> Result<()>;

    /// Write one or more blocks starting at `block_id`.
    /// The `buf` length must be a multiple of `block_size`.
    async fn write(&self, block_id: u64, buf: &[u8]) -> Result<()>;

    /// The size of a single block in bytes.
    fn block_size(&self) -> usize;

    /// Flushes any caches to the underlying device.
    async fn sync(&self) -> Result<()>;
}

/// A stateless representation of a filesystem object.
///
/// This trait represents an object on the disk (a file, a directory, etc.). All
/// operations are stateless from the VFS's perspective; for instance, `read_at`
/// takes an explicit offset instead of using a hidden cursor.
#[async_trait]
pub trait Inode: Send + Sync + Any {
    /// Get the unique ID for this inode.
    fn id(&self) -> InodeId;

    /// Reads data from the inode at a specific `offset`.
    /// Returns the number of bytes read.
    async fn read_at(&self, _offset: u64, _buf: &mut [u8]) -> Result<usize> {
        Err(KernelError::NotSupported)
    }

    /// Writes data to the inode at a specific `offset`.
    /// Returns the number of bytes written.
    async fn write_at(&self, _offset: u64, _buf: &[u8]) -> Result<usize> {
        Err(KernelError::NotSupported)
    }

    /// Truncates the inode to a specific `size`.
    async fn truncate(&self, _size: u64) -> Result<()> {
        Err(KernelError::NotSupported)
    }

    /// Gets the metadata for this inode.
    async fn getattr(&self) -> Result<FileAttr> {
        Err(KernelError::NotSupported)
    }

    /// Sets the metadata for this inode.
    async fn setattr(&self, _attr: FileAttr) -> Result<()> {
        Err(KernelError::NotSupported)
    }

    /// Gets an extended attribute.
    async fn getxattr(&self, _name: &str) -> Result<Vec<u8>> {
        Err(KernelError::NotSupported)
    }

    /// Sets an extended attribute.
    /// Can only create an attribute if `create` is true.
    /// Can only replace an existing attribute if `replace` is true.
    async fn setxattr(
        &self,
        _name: &str,
        _buf: &[u8],
        _create: bool,
        _replace: bool,
    ) -> Result<()> {
        Err(KernelError::NotSupported)
    }

    /// Removes an extended attribute.
    async fn removexattr(&self, _name: &str) -> Result<()> {
        Err(KernelError::NotSupported)
    }

    /// Lists all extended attribute names.
    async fn listxattr(&self) -> Result<Vec<String>> {
        Ok(Vec::new())
    }

    /// Looks up a name within a directory, returning the corresponding inode.
    async fn lookup(&self, _name: &str) -> Result<Arc<dyn Inode>> {
        Err(KernelError::NotSupported)
    }

    /// Creates a new object within a directory.
    async fn create(
        &self,
        _name: &str,
        _file_type: FileType,
        _permissions: FilePermissions,
        _time: Option<Duration>,
    ) -> Result<Arc<dyn Inode>> {
        Err(KernelError::NotSupported)
    }

    /// Removes a link to an inode from a directory.
    async fn unlink(&self, _name: &str) -> Result<()> {
        Err(KernelError::NotSupported)
    }

    /// Creates a new link to an inode in a directory.
    async fn link(&self, _name: &str, _inode: Arc<dyn Inode>) -> Result<()> {
        Err(KernelError::NotSupported)
    }

    /// Creates a new symlink
    async fn symlink(&self, _name: &str, _target: &Path) -> Result<()> {
        Err(KernelError::NotSupported)
    }

    /// Renames an inode originating from an old parent directory.
    async fn rename_from(
        &self,
        _old_parent: Arc<dyn Inode>,
        _old_name: &str,
        _new_name: &str,
        _no_replace: bool,
    ) -> Result<()> {
        Err(KernelError::NotSupported)
    }

    /// Exchanges two inodes.
    async fn exchange(
        &self,
        _first_name: &str,
        _second_parent: Arc<dyn Inode>,
        _second_name: &str,
    ) -> Result<()> {
        Err(KernelError::NotSupported)
    }

    /// Checks if a directory is empty.
    fn dir_is_empty(&self) -> Result<bool> {
        Err(FsError::NotADirectory.into())
    }

    /// Reads the contents of a directory.
    async fn readdir(&self, _start_offset: u64) -> Result<Box<dyn DirStream>> {
        Err(FsError::NotADirectory.into())
    }

    /// Reads the path of a symlink.
    async fn readlink(&self) -> Result<PathBuf> {
        Err(KernelError::NotSupported)
    }

    /// Flushes all modified data, including metadata, to the disk device containing the inode.
    ///
    /// The default implementation is a no-op so that read-only filesystems do
    /// not need to override it.
    async fn sync(&self) -> Result<()> {
        self.datasync().await
    }

    /// Flushes modified data, excluding metadata, to the disk device containing the inode.
    ///
    /// The default implementation is a no-op so that read-only filesystems do
    /// not need to override it.
    async fn datasync(&self) -> Result<()> {
        Ok(())
    }

    /// Return this inode as an `Any` object, suitable for downcasting.
    fn as_any(&self) -> &dyn Any;
}

/// A simplified trait for read-only files in procfs/sysfs that provides default implementations
/// for common inode operations.
#[async_trait]
pub trait SimpleFile {
    /// Returns the inode ID of this file.
    fn id(&self) -> InodeId;
    /// Returns the file metadata.
    async fn getattr(&self) -> Result<FileAttr>;
    /// Reads the entire file contents into a byte vector.
    async fn read(&self) -> Result<Vec<u8>>;
    /// Reads the target of a symbolic link, if applicable.
    async fn readlink(&self) -> Result<PathBuf> {
        Err(KernelError::NotSupported)
    }
}

#[allow(missing_docs)]
#[async_trait]
impl<T> Inode for T
where
    T: SimpleFile + Send + Sync + 'static,
{
    fn id(&self) -> InodeId {
        self.id()
    }

    async fn read_at(&self, offset: u64, buf: &mut [u8]) -> Result<usize> {
        let bytes = self.read().await?;
        let end = usize::min(bytes.len().saturating_sub(offset as usize), buf.len());
        if end == 0 {
            return Ok(0);
        }
        let slice = &bytes[offset as usize..offset as usize + end];
        buf[..end].copy_from_slice(slice);
        Ok(end)
    }

    async fn getattr(&self) -> Result<FileAttr> {
        self.getattr().await
    }

    async fn lookup(&self, _name: &str) -> Result<Arc<dyn Inode>> {
        Err(FsError::NotADirectory.into())
    }

    async fn readdir(&self, _start_offset: u64) -> crate::error::Result<Box<dyn DirStream>> {
        Err(FsError::NotADirectory.into())
    }

    async fn readlink(&self) -> Result<PathBuf> {
        self.readlink().await
    }

    fn as_any(&self) -> &dyn Any {
        self
    }
}

/// A simple in-memory directory stream backed by a `Vec` of entries.
pub struct SimpleDirStream {
    entries: Vec<Dirent>,
    idx: usize,
}

impl SimpleDirStream {
    /// Creates a new `SimpleDirStream` starting at the given offset.
    pub fn new(entries: Vec<Dirent>, start_offset: u64) -> Self {
        Self {
            entries,
            idx: start_offset as usize,
        }
    }
}

#[async_trait]
impl DirStream for SimpleDirStream {
    async fn next_entry(&mut self) -> Result<Option<Dirent>> {
        Ok(if let Some(entry) = self.entries.get(self.idx).cloned() {
            self.idx += 1;
            Some(entry)
        } else {
            None
        })
    }
}
