//! Read-only extraction helpers for classic Mac disk images.
//!
//! The runtime VFS is not a mounted HFS volume; it is a set of data-fork,
//! resource-fork, and Finder metadata maps. These helpers turn DC42/raw
//! HFS/HFS+ images into entries that can be seeded into that existing VFS.

use std::{
    io::{Read, Seek, SeekFrom},
    path::{Component, Path},
};

use hfs_reader::HfsVolume;

const HFS_SIGNATURE: u16 = 0x4244;
const HFS_PLUS_SIGNATURE: u16 = 0x482B;
const HFSX_SIGNATURE: u16 = 0x4858;
const MFS_SIGNATURE: u16 = 0xD2D7;
const HFSPLUS_FORK_DATA: u8 = 0x00;
const HFSPLUS_FORK_RESOURCE: u8 = 0xFF;
const HFSPLUS_CATALOG_FILE_RECORD: u16 = 0x0002;
const HFSPLUS_FILE_USER_INFO_OFFSET: usize = 48;

#[derive(Debug)]
pub struct DiskImageContents {
    pub volume_name: String,
    pub dirs: Vec<String>,
    pub files: Vec<DiskImageFile>,
}

#[derive(Debug)]
pub struct DiskImageFile {
    pub path: String,
    pub data: Vec<u8>,
    pub rsrc: Vec<u8>,
    pub file_type: [u8; 4],
    pub creator: [u8; 4],
    pub finder_flags: u16,
}

pub fn looks_like_dc42_or_hfs(bytes: &[u8]) -> bool {
    raw_filesystem_signature(bytes).is_some()
        || dc42_data_range(bytes)
            .and_then(|(start, end)| raw_filesystem_signature(&bytes[start..end]))
            .is_some()
}

pub fn extract_dc42_or_hfs(bytes: &[u8]) -> Result<Option<DiskImageContents>, String> {
    if !looks_like_dc42_or_hfs(bytes) {
        return Ok(None);
    }

    let filesystem = filesystem_payload(bytes);
    match raw_filesystem_signature(filesystem) {
        Some(HFS_PLUS_SIGNATURE | HFSX_SIGNATURE) => {
            return extract_hfsplus(filesystem).map(Some);
        }
        Some(HFS_SIGNATURE | MFS_SIGNATURE) => {}
        Some(_) | None => {}
    }

    let volume = HfsVolume::parse(bytes).map_err(|e| format!("failed to parse HFS image: {e}"))?;
    let volume_name = clean_component(&volume.volume_name).unwrap_or_else(|| "Disk Image".into());
    let mut dirs = vec![volume_name.clone()];

    for dir in &volume.dirs {
        if let Some(rel_path) = path_to_vfs_path(&dir.rel_path) {
            dirs.push(prefixed_path(&volume_name, &rel_path));
        }
    }

    let mut files = Vec::with_capacity(volume.files.len());
    for file in &volume.files {
        let Some(rel_path) = path_to_vfs_path(&file.rel_path) else {
            continue;
        };
        let path = prefixed_path(&volume_name, &rel_path);
        let data = volume
            .read_data_fork(file)
            .map_err(|e| format!("failed to read HFS data fork for {path}: {e}"))?;
        let rsrc = volume
            .read_rsrc_fork(file)
            .map_err(|e| format!("failed to read HFS resource fork for {path}: {e}"))?;

        files.push(DiskImageFile {
            path,
            data,
            rsrc,
            file_type: file.file_type,
            creator: file.creator,
            // hfs-reader exposes type/creator but not fdFlags yet.
            finder_flags: 0,
        });
    }

    dirs.sort_unstable();
    dirs.dedup();
    Ok(Some(DiskImageContents {
        volume_name,
        dirs,
        files,
    }))
}

fn raw_filesystem_signature(bytes: &[u8]) -> Option<u16> {
    let sig = bytes
        .get(1024..1026)
        .map(|sig| u16::from_be_bytes([sig[0], sig[1]]))?;
    matches!(
        sig,
        HFS_SIGNATURE | HFS_PLUS_SIGNATURE | HFSX_SIGNATURE | MFS_SIGNATURE
    )
    .then_some(sig)
}

fn filesystem_payload(bytes: &[u8]) -> &[u8] {
    dc42_data_range(bytes)
        .and_then(|(start, end)| bytes.get(start..end))
        .unwrap_or(bytes)
}

fn dc42_data_range(bytes: &[u8]) -> Option<(usize, usize)> {
    const DC42_HEADER_LEN: usize = 84;

    if bytes.len() < DC42_HEADER_LEN || bytes.get(82..84) != Some(&[0x01, 0x00]) {
        return None;
    }

    let name_len = bytes[0] as usize;
    if name_len > 63 {
        return None;
    }

    let data_size = u32::from_be_bytes(bytes[64..68].try_into().ok()?) as usize;
    if data_size == 0 || data_size % 512 != 0 {
        return None;
    }

    let data_end = DC42_HEADER_LEN.checked_add(data_size)?;
    (data_end <= bytes.len()).then_some((DC42_HEADER_LEN, data_end))
}

fn extract_hfsplus(bytes: &[u8]) -> Result<DiskImageContents, String> {
    let mut reader = std::io::Cursor::new(bytes);
    let volume = hfsplus::volume::VolumeHeader::parse(&mut reader)
        .map_err(|e| format!("failed to parse HFS+ image: {e}"))?;
    let catalog =
        hfsplus::btree::read_btree_header(&mut reader, &volume.catalog_file, volume.block_size)
            .map_err(|e| format!("failed to read HFS+ catalog B-tree: {e}"))?;
    let extents = if volume.extents_file.total_blocks == 0 {
        None
    } else {
        Some(
            hfsplus::btree::read_btree_header(&mut reader, &volume.extents_file, volume.block_size)
                .map_err(|e| format!("failed to read HFS+ extents B-tree: {e}"))?,
        )
    };

    // HFS+ stores the volume name in catalog thread records rather than the
    // volume header. Keep the same deterministic VFS mount shape as nameless
    // disk-image payloads instead of guessing from optional catalog metadata.
    let volume_name = "HFS+ Disk Image".to_string();
    let mut dirs = vec![volume_name.clone()];
    let mut files = Vec::new();
    collect_hfsplus_directory(
        &mut reader,
        &volume,
        &catalog,
        extents.as_ref(),
        &volume_name,
        hfsplus::catalog::CNID_ROOT_FOLDER,
        "",
        "",
        &mut dirs,
        &mut files,
    )?;

    dirs.sort_unstable();
    dirs.dedup();
    Ok(DiskImageContents {
        volume_name,
        dirs,
        files,
    })
}

#[allow(clippy::too_many_arguments)]
fn collect_hfsplus_directory<R: Read + Seek>(
    reader: &mut R,
    volume: &hfsplus::volume::VolumeHeader,
    catalog: &hfsplus::btree::BTreeHeaderRecord,
    extents: Option<&hfsplus::btree::BTreeHeaderRecord>,
    volume_name: &str,
    parent_cnid: u32,
    raw_dir: &str,
    vfs_dir: &str,
    dirs: &mut Vec<String>,
    files: &mut Vec<DiskImageFile>,
) -> Result<(), String> {
    let entries = hfsplus::catalog::list_directory(reader, volume, catalog, parent_cnid)
        .map_err(|e| format!("failed to list HFS+ directory {vfs_dir}: {e}"))?;

    for entry in entries {
        let Some(cleaned_name) = clean_component(&entry.name) else {
            continue;
        };
        let raw_path = join_path(raw_dir, &entry.name);
        let vfs_path = join_path(vfs_dir, &cleaned_name);

        match entry.kind {
            hfsplus::EntryKind::Directory => {
                dirs.push(prefixed_path(volume_name, &vfs_path));
                collect_hfsplus_directory(
                    reader,
                    volume,
                    catalog,
                    extents,
                    volume_name,
                    entry.cnid,
                    &raw_path,
                    &vfs_path,
                    dirs,
                    files,
                )?;
            }
            hfsplus::EntryKind::File | hfsplus::EntryKind::Symlink => {
                let lookup_path = format!("/{raw_path}");
                let (record, _) =
                    hfsplus::catalog::resolve_path(reader, volume, catalog, &lookup_path)
                        .map_err(|e| format!("failed to resolve HFS+ file {vfs_path}: {e}"))?;
                let hfsplus::catalog::CatalogRecord::File(file) = record else {
                    continue;
                };
                let path = prefixed_path(volume_name, &vfs_path);
                let metadata = hfsplus_file_finder_metadata(
                    reader,
                    catalog,
                    parent_cnid,
                    &entry.name,
                    &vfs_path,
                )?;
                let data = read_hfsplus_fork(
                    reader,
                    volume,
                    extents,
                    &file.data_fork,
                    file.file_id,
                    HFSPLUS_FORK_DATA,
                )
                .map_err(|e| format!("failed to read HFS+ data fork for {path}: {e}"))?;
                let rsrc = read_hfsplus_fork(
                    reader,
                    volume,
                    extents,
                    &file.resource_fork,
                    file.file_id,
                    HFSPLUS_FORK_RESOURCE,
                )
                .map_err(|e| format!("failed to read HFS+ resource fork for {path}: {e}"))?;

                files.push(DiskImageFile {
                    path,
                    data,
                    rsrc,
                    file_type: metadata.file_type,
                    creator: metadata.creator,
                    finder_flags: metadata.finder_flags,
                });
            }
        }
    }

    Ok(())
}

#[derive(Clone, Copy, Debug, Eq, PartialEq)]
struct HfsPlusFinderMetadata {
    file_type: [u8; 4],
    creator: [u8; 4],
    finder_flags: u16,
}

impl Default for HfsPlusFinderMetadata {
    fn default() -> Self {
        Self {
            file_type: *b"????",
            creator: *b"????",
            finder_flags: 0,
        }
    }
}

fn hfsplus_file_finder_metadata<R: Read + Seek>(
    reader: &mut R,
    catalog: &hfsplus::btree::BTreeHeaderRecord,
    parent_cnid: u32,
    name: &str,
    vfs_path: &str,
) -> Result<HfsPlusFinderMetadata, String> {
    let name_utf16: Vec<u16> = name.encode_utf16().collect();
    let records = hfsplus::btree::scan_leaves(
        reader,
        catalog,
        catalog.first_leaf_node,
        &|record_data| {
            let Some((key_parent, key_name, _)) = hfsplus_catalog_key(record_data) else {
                return Some(false);
            };
            Some(key_parent == parent_cnid && key_name == name_utf16)
        },
        &|record_data| Ok(hfsplus_file_finder_metadata_from_record(record_data)),
    )
    .map_err(|e| format!("failed to read HFS+ Finder metadata for {vfs_path}: {e}"))?;

    Ok(records.into_iter().next().flatten().unwrap_or_default())
}

fn hfsplus_file_finder_metadata_from_record(record_data: &[u8]) -> Option<HfsPlusFinderMetadata> {
    let (_, _, record_offset) = hfsplus_catalog_key(record_data)?;
    let record = record_data.get(record_offset..)?;
    if record.len() < HFSPLUS_FILE_USER_INFO_OFFSET + 10 {
        return None;
    }
    let record_type = u16::from_be_bytes([record[0], record[1]]);
    if record_type != HFSPLUS_CATALOG_FILE_RECORD {
        return None;
    }
    let finder = &record[HFSPLUS_FILE_USER_INFO_OFFSET..];
    let file_type = [finder[0], finder[1], finder[2], finder[3]];
    let creator = [finder[4], finder[5], finder[6], finder[7]];
    let (file_type, creator) = if file_type == [0; 4] && creator == [0; 4] {
        (*b"????", *b"????")
    } else {
        (file_type, creator)
    };
    Some(HfsPlusFinderMetadata {
        file_type,
        creator,
        finder_flags: u16::from_be_bytes([finder[8], finder[9]]),
    })
}

fn hfsplus_catalog_key(record_data: &[u8]) -> Option<(u32, Vec<u16>, usize)> {
    let header = record_data.get(0..8)?;
    let key_length = u16::from_be_bytes([header[0], header[1]]) as usize;
    let parent_id = u32::from_be_bytes([header[2], header[3], header[4], header[5]]);
    let name_len = u16::from_be_bytes([header[6], header[7]]) as usize;
    let name_end = 8usize.checked_add(name_len.checked_mul(2)?)?;
    let name_bytes = record_data.get(8..name_end)?;
    let name = name_bytes
        .chunks_exact(2)
        .map(|chunk| u16::from_be_bytes([chunk[0], chunk[1]]))
        .collect();
    let record_offset = (2usize.checked_add(key_length)? + 1) & !1;
    (record_offset <= record_data.len()).then_some((parent_id, name, record_offset))
}

fn join_path(parent: &str, name: &str) -> String {
    if parent.is_empty() {
        name.to_string()
    } else {
        format!("{parent}/{name}")
    }
}

fn read_hfsplus_fork<R: Read + Seek>(
    reader: &mut R,
    volume: &hfsplus::volume::VolumeHeader,
    extents: Option<&hfsplus::btree::BTreeHeaderRecord>,
    fork: &hfsplus::volume::ForkData,
    file_id: u32,
    fork_type: u8,
) -> Result<Vec<u8>, String> {
    let logical_size =
        usize::try_from(fork.logical_size).map_err(|_| "fork is too large".to_string())?;
    if logical_size == 0 {
        return Ok(Vec::new());
    }

    let mut out = Vec::with_capacity(logical_size);
    for extent in &fork.extents {
        if extent.block_count == 0 || out.len() >= logical_size {
            break;
        }
        read_hfsplus_extent(reader, volume.block_size, extent, logical_size, &mut out)?;
    }

    let mut start_block = fork.extents.iter().map(|extent| extent.block_count).sum();
    while out.len() < logical_size {
        let extents = extents.ok_or_else(|| "missing HFS+ extents B-tree".to_string())?;
        let overflow = hfsplus_overflow_extents(reader, extents, file_id, fork_type, start_block)?;
        if overflow.is_empty() {
            break;
        }

        for extent in overflow {
            if extent.block_count == 0 || out.len() >= logical_size {
                break;
            }
            read_hfsplus_extent(reader, volume.block_size, &extent, logical_size, &mut out)?;
            start_block = start_block.saturating_add(extent.block_count);
        }
    }

    if out.len() < logical_size {
        return Err(format!(
            "fork truncated: read {} of {} bytes",
            out.len(),
            logical_size
        ));
    }
    out.truncate(logical_size);
    Ok(out)
}

fn read_hfsplus_extent<R: Read + Seek>(
    reader: &mut R,
    block_size: u32,
    extent: &hfsplus::volume::ExtentDescriptor,
    logical_size: usize,
    out: &mut Vec<u8>,
) -> Result<(), String> {
    let offset = u64::from(extent.start_block)
        .checked_mul(u64::from(block_size))
        .ok_or_else(|| "HFS+ extent offset overflow".to_string())?;
    let byte_len = u64::from(extent.block_count)
        .checked_mul(u64::from(block_size))
        .ok_or_else(|| "HFS+ extent length overflow".to_string())?;
    let remaining = logical_size.saturating_sub(out.len());
    let mut to_read = usize::try_from(byte_len)
        .unwrap_or(usize::MAX)
        .min(remaining);
    reader
        .seek(SeekFrom::Start(offset))
        .map_err(|e| format!("seek HFS+ extent: {e}"))?;

    while to_read > 0 {
        let chunk = to_read.min(64 * 1024);
        let start = out.len();
        out.resize(start + chunk, 0);
        reader
            .read_exact(&mut out[start..start + chunk])
            .map_err(|e| format!("read HFS+ extent: {e}"))?;
        to_read -= chunk;
    }

    Ok(())
}

fn hfsplus_overflow_extents<R: Read + Seek>(
    reader: &mut R,
    extents: &hfsplus::btree::BTreeHeaderRecord,
    file_id: u32,
    fork_type: u8,
    start_block: u32,
) -> Result<Vec<hfsplus::volume::ExtentDescriptor>, String> {
    let records = hfsplus::btree::scan_leaves(
        reader,
        extents,
        extents.first_leaf_node,
        &|record_data| {
            let key = hfsplus_extent_key(record_data)?;
            Some(key == (fork_type, file_id, start_block))
        },
        &|record_data| {
            let key_length = u16::from_be_bytes([record_data[0], record_data[1]]) as usize;
            let data_start = 2 + key_length;
            let data = record_data
                .get(data_start..data_start + 64)
                .ok_or_else(|| {
                    hfsplus::HfsPlusError::InvalidBTree("extent record too short".into())
                })?;
            let mut extents = Vec::with_capacity(8);
            for chunk in data.chunks_exact(8) {
                extents.push(hfsplus::volume::ExtentDescriptor {
                    start_block: u32::from_be_bytes(chunk[0..4].try_into().unwrap()),
                    block_count: u32::from_be_bytes(chunk[4..8].try_into().unwrap()),
                });
            }
            Ok(extents)
        },
    )
    .map_err(|e| format!("read HFS+ overflow extents: {e}"))?;

    Ok(records.into_iter().flatten().collect())
}

fn hfsplus_extent_key(record_data: &[u8]) -> Option<(u8, u32, u32)> {
    (record_data.len() >= 12).then(|| {
        (
            record_data[2],
            u32::from_be_bytes(record_data[4..8].try_into().unwrap()),
            u32::from_be_bytes(record_data[8..12].try_into().unwrap()),
        )
    })
}

fn path_to_vfs_path(path: &Path) -> Option<String> {
    let mut parts = Vec::new();
    for component in path.components() {
        let Component::Normal(part) = component else {
            continue;
        };
        let Some(cleaned) = clean_component(&part.to_string_lossy()) else {
            continue;
        };
        parts.push(cleaned);
    }

    (!parts.is_empty()).then(|| parts.join("/"))
}

fn prefixed_path(volume_name: &str, rel_path: &str) -> String {
    if volume_name.is_empty() {
        rel_path.to_string()
    } else {
        format!("{volume_name}/{rel_path}")
    }
}

fn clean_component(raw: &str) -> Option<String> {
    let cleaned: String = raw
        .chars()
        .map(|ch| match ch {
            '/' | ':' | '\\' => '_',
            ch if ch.is_control() => '_',
            ch => ch,
        })
        .collect();
    let trimmed = cleaned.trim();
    (!trimmed.is_empty() && trimmed != "." && trimmed != "..").then(|| trimmed.to_string())
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn detects_raw_hfs_volume_signature() {
        let mut bytes = vec![0; 2048];
        bytes[1024..1026].copy_from_slice(&HFS_SIGNATURE.to_be_bytes());

        assert!(looks_like_dc42_or_hfs(&bytes));
    }

    #[test]
    fn detects_raw_hfsplus_and_hfsx_volume_signatures() {
        for signature in [HFS_PLUS_SIGNATURE, HFSX_SIGNATURE] {
            let mut bytes = vec![0; 2048];
            bytes[1024..1026].copy_from_slice(&signature.to_be_bytes());

            assert!(looks_like_dc42_or_hfs(&bytes));
        }
    }

    #[test]
    fn detects_dc42_wrapped_hfs_payload() {
        let bytes = dc42_with_payload_signature(HFS_SIGNATURE);

        assert!(looks_like_dc42_or_hfs(&bytes));
    }

    #[test]
    fn extracts_hfsplus_data_fork_files() {
        let mut builder = hfsplus::testutil::HfsPlusImageBuilder::new();
        builder.add_file("hello.txt", b"hello hfs+", 0o100644);
        let bytes = builder.build();

        let image = extract_dc42_or_hfs(&bytes)
            .expect("HFS+ extraction should succeed")
            .expect("HFS+ signature should be detected");

        assert_eq!(image.volume_name, "HFS+ Disk Image");
        assert_eq!(image.dirs, vec!["HFS+ Disk Image".to_string()]);
        let file = image
            .files
            .iter()
            .find(|file| file.path == "HFS+ Disk Image/hello.txt")
            .expect("synthetic HFS+ file should be present");
        assert_eq!(file.data, b"hello hfs+");
        assert!(file.rsrc.is_empty());
        assert_eq!(file.file_type, *b"????");
        assert_eq!(file.creator, *b"????");
    }

    #[test]
    fn parses_hfsplus_catalog_finder_metadata() {
        let mut record = hfsplus_catalog_file_record("Star Trek JR Demo");
        let key_len = u16::from_be_bytes([record[0], record[1]]) as usize;
        let record_offset = (2 + key_len + 1) & !1;
        let finder = record_offset + HFSPLUS_FILE_USER_INFO_OFFSET;
        record[finder..finder + 4].copy_from_slice(b"APPL");
        record[finder + 4..finder + 8].copy_from_slice(b"MPLY");
        record[finder + 8..finder + 10].copy_from_slice(&0x0400u16.to_be_bytes());

        let metadata = hfsplus_file_finder_metadata_from_record(&record)
            .expect("file record metadata should parse");

        assert_eq!(
            hfsplus_catalog_key(&record)
                .expect("catalog key should parse")
                .0,
            42
        );
        assert_eq!(metadata.file_type, *b"APPL");
        assert_eq!(metadata.creator, *b"MPLY");
        assert_eq!(metadata.finder_flags, 0x0400);
    }

    #[test]
    fn empty_hfsplus_catalog_finder_codes_fall_back_to_unknown() {
        let record = hfsplus_catalog_file_record("Untyped");

        let metadata = hfsplus_file_finder_metadata_from_record(&record)
            .expect("file record metadata should parse");

        assert_eq!(metadata.file_type, *b"????");
        assert_eq!(metadata.creator, *b"????");
        assert_eq!(metadata.finder_flags, 0);
    }

    #[test]
    fn reads_hfsplus_resource_fork_inline_extents() {
        const BLOCK_SIZE: usize = 512;
        let mut bytes = vec![0u8; BLOCK_SIZE * 4];
        bytes[BLOCK_SIZE * 2..BLOCK_SIZE * 2 + 4].copy_from_slice(b"rsrc");
        let fork = hfsplus::volume::ForkData {
            logical_size: 4,
            clump_size: 0,
            total_blocks: 1,
            extents: {
                let mut extents = [hfsplus::volume::ExtentDescriptor::default(); 8];
                extents[0] = hfsplus::volume::ExtentDescriptor {
                    start_block: 2,
                    block_count: 1,
                };
                extents
            },
        };
        let volume = hfsplus::volume::VolumeHeader {
            signature: HFS_PLUS_SIGNATURE,
            version: 4,
            attributes: 0,
            last_mounted_version: 0,
            journal_info_block: 0,
            create_date: 0,
            modify_date: 0,
            backup_date: 0,
            checked_date: 0,
            file_count: 0,
            folder_count: 0,
            block_size: BLOCK_SIZE as u32,
            total_blocks: 4,
            free_blocks: 0,
            next_allocation: 0,
            rsrc_clump_size: 0,
            data_clump_size: 0,
            next_catalog_id: 0,
            write_count: 0,
            encoding_bitmap: 0,
            finder_info: [0; 8],
            allocation_file: hfsplus::volume::ForkData::default(),
            extents_file: hfsplus::volume::ForkData::default(),
            catalog_file: hfsplus::volume::ForkData::default(),
            attributes_file: hfsplus::volume::ForkData::default(),
            startup_file: hfsplus::volume::ForkData::default(),
            is_hfsx: false,
        };
        let mut reader = std::io::Cursor::new(bytes);

        let out = read_hfsplus_fork(&mut reader, &volume, None, &fork, 42, HFSPLUS_FORK_RESOURCE)
            .expect("inline resource fork should read");

        assert_eq!(out, b"rsrc");
    }

    #[test]
    fn rejects_dc42_like_data_without_filesystem_signature() {
        let bytes = dc42_with_payload_signature(0);

        assert!(!looks_like_dc42_or_hfs(&bytes));
    }

    fn dc42_with_payload_signature(signature: u16) -> Vec<u8> {
        const HEADER_LEN: usize = 84;
        const DATA_LEN: usize = 2048;

        let mut bytes = vec![0; HEADER_LEN + DATA_LEN];
        bytes[0] = 4;
        bytes[1..5].copy_from_slice(b"Test");
        bytes[64..68].copy_from_slice(&(DATA_LEN as u32).to_be_bytes());
        bytes[82..84].copy_from_slice(&[0x01, 0x00]);
        bytes[HEADER_LEN + 1024..HEADER_LEN + 1026].copy_from_slice(&signature.to_be_bytes());
        bytes
    }

    fn hfsplus_catalog_file_record(name: &str) -> Vec<u8> {
        let name_utf16: Vec<u16> = name.encode_utf16().collect();
        let key_len = 6 + name_utf16.len() * 2;
        let record_offset = (2 + key_len + 1) & !1;
        let mut record = vec![0u8; record_offset + 88];
        record[0..2].copy_from_slice(&(key_len as u16).to_be_bytes());
        record[2..6].copy_from_slice(&42u32.to_be_bytes());
        record[6..8].copy_from_slice(&(name_utf16.len() as u16).to_be_bytes());
        for (idx, ch) in name_utf16.iter().enumerate() {
            let start = 8 + idx * 2;
            record[start..start + 2].copy_from_slice(&ch.to_be_bytes());
        }
        record[record_offset..record_offset + 2]
            .copy_from_slice(&HFSPLUS_CATALOG_FILE_RECORD.to_be_bytes());
        record
    }
}
