use bitflags::bitflags;

bitflags! {
    /// Finder flags word (fdFlags / frFlags) stored in bytes 8–9 of the 32-byte Finder Info blob.
    ///
    /// Bit definitions from *Inside Macintosh: Files* and the Finder Interface chapter.
    /// Bits 1–3 encode a 3-bit label/colour index and are not representable as individual
    /// flags; read them with `FinderFlags::label()` instead.
    #[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
    pub struct FinderFlags: u16 {
        /// Icon is on the desktop (MFS only; ignored by HFS).
        const IS_ON_DESK      = 0x0001;
        /// Application can be launched by multiple users at the same time.
        const IS_SHARED       = 0x0040;
        /// Application has no system-extension (INIT) resources.
        const HAS_NO_INITS    = 0x0080;
        /// The Finder has already loaded the application's bundle resources.
        const HAS_BEEN_INITED = 0x0100;
        /// File has a custom icon stored in its resource fork.
        const HAS_CUSTOM_ICON = 0x0400;
        /// File is a stationery pad; opening it creates a copy.
        const IS_STATIONERY   = 0x0800;
        /// File's name cannot be changed in the Finder.
        const NAME_LOCKED     = 0x1000;
        /// File has a `'BNDL'` resource associating icons with file types.
        const HAS_BUNDLE      = 0x2000;
        /// File is hidden from the Finder's directory listings.
        const IS_INVISIBLE    = 0x4000;
        /// File is an alias (points to another file or folder).
        const IS_ALIAS        = 0x8000;
    }
}

impl FinderFlags {
    /// Extract the 3-bit label/colour index from bits 1–3 (values 0–7).
    /// 0 means no label; 1–7 correspond to the seven Finder label colours.
    pub fn label(self) -> u8 {
        ((self.bits() & 0x000E) >> 1) as u8
    }

    /// Return a copy of these flags with the label index set to `value` (0–7).
    pub fn with_label(self, value: u8) -> Self {
        Self::from_bits_retain((self.bits() & !0x000E) | (((value as u16) & 0x7) << 1))
    }
}

/// AFP Error Codes. For AppleTalk implementations these result codes are passed via the
/// ASP CmdResult field (i.e the 4 user bytes of an ATP packet)
#[derive(Debug, Clone, PartialEq, Eq)]
#[repr(i16)]
pub enum AfpError {
    /// Packet could not be parsed as it was of insufficient size
    InvalidSize = -1,
    /// End of File reached
    EoFErr = -39,
    /// Unknown or unsupported AFP version
    BadVersNum = -1072,
    /// Unknown or unsupported UAM
    BadUam = -1073,
    /// User does not have the correct access rights
    AccessDenied = -5000,
    /// A request to operate on a directory that is not empty
    DirNotEmpty = -5007,
    /// Bitmap is invalid
    BitmapErr = -5006,
    /// A request to operate on a file that is currently in use
    FileBusy = -5010,
    /// Item not found
    ItemNotFound = -5012,
    /// General lock error
    LockErr = -5013,
    /// Object (file or directory) already exists
    ObjectExists = -5017,
    /// Object (file or directory) not found
    ObjectNotFound = -5018,
    /// AFP command block size is zero or invalid
    ParamError = -5019,
    /// Attempt to unlock a byte range that is not locked
    RangeNotLocked = -5020,
    /// Attempt to lock a byte range that overlaps with an existing lock
    RangeOverlap = -5021,
    /// Object is the wrong type (e.g. file vs directory)
    ObjectTypeErr = -5025,
    /// Miscellaneous error
    MiscErr = -10000,
}

#[repr(u16)]
pub enum VolumeSignature {
    /// Indicates no directories, only files, i.e MFS. Only supported in old AFP versions.
    Flat = 1,
    /// Indicates directory IDs do not change. Should be the default.
    FixedDirectoryID = 2,
    /// Indicates directory IDs can change. No need to implement this one.
    VariableDirectoryID = 3,
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub enum AfpVersion {
    Version1,
    Version1_1,
    Version2,
    Version2_1,
}

impl TryFrom<&str> for AfpVersion {
    type Error = AfpError;
    fn try_from(value: &str) -> Result<Self, Self::Error> {
        match value {
            "AFPVersion 1.0" => Ok(AfpVersion::Version1),
            "AFPVersion 1.1" => Ok(AfpVersion::Version1_1),
            "AFPVersion 2.0" => Ok(AfpVersion::Version2),
            "AFPVersion 2.1" => Ok(AfpVersion::Version2_1),
            _ => Err(AfpError::BadVersNum),
        }
    }
}

impl AfpVersion {
    pub fn as_str(&self) -> &'static str {
        match self {
            AfpVersion::Version1 => "AFPVersion 1.0",
            AfpVersion::Version1_1 => "AFPVersion 1.1",
            AfpVersion::Version2 => "AFPVersion 2.0",
            AfpVersion::Version2_1 => "AFPVersion 2.1",
        }
    }
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub enum AfpUam {
    NoUserAuthent,
    CleartxtPasswrd,
    RandnumExchange,
    TwoWayRandnumExchange,
}

impl TryFrom<&str> for AfpUam {
    type Error = AfpError;
    fn try_from(value: &str) -> Result<Self, Self::Error> {
        // Inside AppleTalk shows these with capitalised values, but
        // the actual values are case insensitive.
        let value_lower = value.to_lowercase();
        match value_lower.as_str() {
            "no user authent" => Ok(AfpUam::NoUserAuthent),
            "cleartxt passwrd" => Ok(AfpUam::CleartxtPasswrd),
            "randnum exchange" => Ok(AfpUam::RandnumExchange),
            "2-way randnum exchange" => Ok(AfpUam::TwoWayRandnumExchange),
            _ => Err(AfpError::BadUam),
        }
    }
}

impl AfpUam {
    pub fn as_str(&self) -> &'static str {
        match self {
            AfpUam::NoUserAuthent => "No User Authent",
            AfpUam::CleartxtPasswrd => "Cleartxt Passwrd",
            AfpUam::RandnumExchange => "Randnum Exchange",
            AfpUam::TwoWayRandnumExchange => "2-Way Randnum Exchange",
        }
    }
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ForkType {
    Data,
    Resource,
}

impl From<u8> for ForkType {
    fn from(value: u8) -> Self {
        match value {
            0b10000000 => Self::Resource,
            _ => Self::Data,
        }
    }
}

impl From<ForkType> for u8 {
    fn from(val: ForkType) -> Self {
        match val {
            ForkType::Data => 0,
            ForkType::Resource => 0b10000000,
        }
    }
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum PathType {
    ShortName = 1,
    LongName = 2,
}

impl From<u8> for PathType {
    fn from(value: u8) -> Self {
        match value {
            2 => Self::LongName,
            _ => Self::ShortName,
        }
    }
}

#[derive(Debug)]
pub enum CreateFlag {
    Soft,
    Hard,
}

impl From<u8> for CreateFlag {
    fn from(value: u8) -> Self {
        match value {
            0b10000000 => Self::Hard,
            _ => Self::Soft,
        }
    }
}

impl From<CreateFlag> for u8 {
    fn from(val: CreateFlag) -> Self {
        match val {
            CreateFlag::Hard => 0b10000000,
            CreateFlag::Soft => 0,
        }
    }
}

pub enum FileType {
    Directory,
    File,
}

/// The 32-byte Finder Info blob stored as the `com.apple.FinderInfo` xattr.
///
/// Layout (Inside Macintosh, Files):
///   [0..4]   file_type  — OSType (e.g. `TEXT`, `APPL`)
///   [4..8]   creator    — OSType (e.g. `ttxt`, `MACS`)
///   [8..10]  flags      — fdFlags / frFlags (big-endian u16)
///   [10..32] reserved   — icon position, folder ref, extended info
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub struct FinderInfo {
    pub file_type: [u8; 4],
    pub creator: [u8; 4],
    pub flags: FinderFlags,
    pub reserved: [u8; 22],
}

impl From<[u8; 32]> for FinderInfo {
    fn from(raw: [u8; 32]) -> Self {
        let mut reserved = [0u8; 22];
        reserved.copy_from_slice(&raw[10..32]);
        Self {
            file_type: *raw[0..4].as_array().unwrap(),
            creator: *raw[4..8].as_array().unwrap(),
            flags: FinderFlags::from_bits_retain(u16::from_be_bytes([raw[8], raw[9]])),
            reserved,
        }
    }
}

impl From<FinderInfo> for [u8; 32] {
    fn from(info: FinderInfo) -> Self {
        let mut raw = [0u8; 32];
        raw[0..4].copy_from_slice(&info.file_type);
        raw[4..8].copy_from_slice(&info.creator);
        let flags = info.flags.bits().to_be_bytes();
        raw[8] = flags[0];
        raw[9] = flags[1];
        raw[10..32].copy_from_slice(&info.reserved);
        raw
    }
}

impl From<u8> for FileType {
    fn from(value: u8) -> Self {
        match value {
            0b10000000 => Self::Directory,
            _ => Self::File,
        }
    }
}

impl From<FileType> for u8 {
    fn from(val: FileType) -> Self {
        match val {
            FileType::Directory => 0b10000000,
            FileType::File => 0,
        }
    }
}
