use std::{
    fs::{self, File},
    io::{BufReader, BufWriter, Read, Write},
    path::{Component, Path, PathBuf},
};

use flate2::read::DeflateDecoder;

use crate::{format::*, Compression, Entry, Result, ZipError};

pub(crate) fn locate_scan_backward(data: &[u8]) -> Result<Eocd> {
    let lower = data.len().saturating_sub(65_557);
    for pos in (lower..data.len().saturating_sub(3)).rev() {
        if u32le(data, pos)? == EOCD_SIG {
            if let Ok(eocd) = parse_eocd(data, pos) {
                return Ok(eocd);
            }
        }
    }
    Err(ZipError::Invalid("end of central directory not found"))
}

pub(crate) fn locate_rposition(data: &[u8]) -> Result<Eocd> {
    let lower = data.len().saturating_sub(65_557);
    let mut end = data.len();
    while end >= lower + 4 {
        if let Some(rel) = data[lower..end]
            .windows(4)
            .rposition(|w| w == EOCD_SIG.to_le_bytes())
        {
            let pos = lower + rel;
            if let Ok(eocd) = parse_eocd(data, pos) {
                return Ok(eocd);
            }
            end = pos;
        } else {
            break;
        }
    }
    Err(ZipError::Invalid("end of central directory not found"))
}

pub(crate) fn locate_forward_latest(data: &[u8]) -> Result<Eocd> {
    let lower = data.len().saturating_sub(65_557);
    let mut latest = None;
    for (relative, window) in data[lower..].windows(4).enumerate() {
        if window == EOCD_SIG.to_le_bytes() {
            if let Ok(record) = parse_eocd(data, lower + relative) {
                latest = Some(record);
            }
        }
    }
    latest.ok_or(ZipError::Invalid("end of central directory not found"))
}

pub(crate) fn locate_comment_length(data: &[u8]) -> Result<Eocd> {
    let max_comment = data.len().saturating_sub(22).min(u16::MAX as usize);
    for comment_len in 0..=max_comment {
        let pos = data.len() - 22 - comment_len;
        if data.get(pos..pos + 4) == Some(EOCD_SIG.to_le_bytes().as_slice())
            && u16le(data, pos + 20)? as usize == comment_len
        {
            if let Ok(record) = parse_eocd(data, pos) {
                return Ok(record);
            }
        }
    }
    Err(ZipError::Invalid("end of central directory not found"))
}

pub(crate) fn locate_signature_state(data: &[u8]) -> Result<Eocd> {
    let lower = data.len().saturating_sub(65_557);
    let signature = EOCD_SIG.to_le_bytes();
    let mut matched = 0usize;
    let mut candidates = Vec::new();
    for (index, byte) in data[lower..].iter().copied().enumerate() {
        if byte == signature[matched] {
            matched += 1;
            if matched == signature.len() {
                candidates.push(lower + index + 1 - signature.len());
                matched = 0;
            }
        } else {
            matched = usize::from(byte == signature[0]);
        }
    }
    candidates
        .into_iter()
        .rev()
        .find_map(|pos| parse_eocd(data, pos).ok())
        .ok_or(ZipError::Invalid("end of central directory not found"))
}

/// Controlled fault: incorrectly assumes the EOCD is in the final 1 KiB.
pub(crate) fn locate_faulty_short_comment(data: &[u8]) -> Result<Eocd> {
    let lower = data.len().saturating_sub(1_024);
    let rel = data[lower..]
        .windows(4)
        .rposition(|w| w == EOCD_SIG.to_le_bytes())
        .ok_or(ZipError::Invalid("end of central directory not found"))?;
    parse_eocd(data, lower + rel)
}

fn parse_eocd(data: &[u8], pos: usize) -> Result<Eocd> {
    let disk = u16le(data, pos + 4)?;
    let cd_disk = u16le(data, pos + 6)?;
    let disk_entries = u16le(data, pos + 8)? as usize;
    let entries = u16le(data, pos + 10)? as usize;
    let central_size = u32le(data, pos + 12)? as usize;
    let central_offset = u32le(data, pos + 16)? as usize;
    let comment_len = u16le(data, pos + 20)? as usize;
    if disk != 0 || cd_disk != 0 || disk_entries != entries {
        return Err(ZipError::Unsupported("multi-disk archive"));
    }
    if entries == 0xffff || central_size == u32::MAX as usize || central_offset == u32::MAX as usize
    {
        return Err(ZipError::Unsupported("ZIP64"));
    }
    if pos.checked_add(22 + comment_len) != Some(data.len()) {
        return Err(ZipError::Invalid("EOCD comment length"));
    }
    if central_offset
        .checked_add(central_size)
        .is_none_or(|end| end > pos)
    {
        return Err(ZipError::Invalid("central directory bounds"));
    }
    Ok(Eocd {
        entries,
        central_offset,
        central_size,
    })
}

pub(crate) struct RawCentral {
    pub entry: Entry,
    pub next: usize,
}

pub(crate) fn central_indexed(
    data: &[u8],
    at: usize,
    decode: fn(&[u8], u16) -> Result<String>,
) -> Result<RawCentral> {
    if u32le(data, at)? != CENTRAL_SIG {
        return Err(ZipError::Invalid("central directory signature"));
    }
    let flags = u16le(data, at + 8)?;
    reject_encryption(flags)?;
    let compression = Compression::from_u16(u16le(data, at + 10)?)?;
    let crc32 = u32le(data, at + 16)?;
    let compressed_size = u32le(data, at + 20)? as u64;
    let uncompressed_size = u32le(data, at + 24)? as u64;
    let name_len = u16le(data, at + 28)? as usize;
    let extra_len = u16le(data, at + 30)? as usize;
    let comment_len = u16le(data, at + 32)? as usize;
    if u16le(data, at + 34)? != 0 {
        return Err(ZipError::Unsupported("multi-disk entry"));
    }
    let local_offset = u32le(data, at + 42)? as usize;
    let external_attributes = u32le(data, at + 38)?;
    let name_at = at
        .checked_add(46)
        .ok_or(ZipError::Invalid("entry length overflow"))?;
    let next = name_at
        .checked_add(name_len)
        .and_then(|v| v.checked_add(extra_len))
        .and_then(|v| v.checked_add(comment_len))
        .ok_or(ZipError::Invalid("entry length overflow"))?;
    let raw_name = data
        .get(name_at..name_at + name_len)
        .ok_or(ZipError::Invalid("truncated filename"))?;
    if data.get(at..next).is_none() {
        return Err(ZipError::Invalid("truncated central entry"));
    }
    let name = decode(raw_name, flags)?;
    let is_directory = name.ends_with('/')
        || external_attributes & 0x10 != 0
        || ((external_attributes >> 16) & 0o170000) == 0o040000;
    Ok(RawCentral {
        entry: Entry {
            name,
            compression,
            compressed_size,
            uncompressed_size,
            crc32,
            is_directory,
            external_attributes,
            flags,
            local_offset,
            raw_name: raw_name.to_vec(),
        },
        next,
    })
}

pub(crate) fn central_cursor(
    data: &[u8],
    at: usize,
    decode: fn(&[u8], u16) -> Result<String>,
) -> Result<RawCentral> {
    // A bounds-first implementation with independent cursor arithmetic.
    let fixed = data
        .get(at..at.saturating_add(46))
        .ok_or(ZipError::Invalid("truncated central header"))?;
    if u32le(fixed, 0)? != CENTRAL_SIG {
        return Err(ZipError::Invalid("central directory signature"));
    }
    central_indexed(data, at, decode)
}

pub(crate) fn central_fixed_slice(
    data: &[u8],
    at: usize,
    decode: fn(&[u8], u16) -> Result<String>,
) -> Result<RawCentral> {
    let fixed: &[u8; 46] = data
        .get(at..at.saturating_add(46))
        .and_then(|slice| slice.try_into().ok())
        .ok_or(ZipError::Invalid("truncated central header"))?;
    if u32::from_le_bytes(fixed[0..4].try_into().unwrap()) != CENTRAL_SIG {
        return Err(ZipError::Invalid("central directory signature"));
    }
    central_indexed(data, at, decode)
}

pub(crate) fn central_lengths_first(
    data: &[u8],
    at: usize,
    decode: fn(&[u8], u16) -> Result<String>,
) -> Result<RawCentral> {
    let fixed = data
        .get(
            at..at
                .checked_add(46)
                .ok_or(ZipError::Invalid("entry length overflow"))?,
        )
        .ok_or(ZipError::Invalid("truncated central header"))?;
    let variable = usize::from(u16le(fixed, 28)?)
        .checked_add(usize::from(u16le(fixed, 30)?))
        .and_then(|value| value.checked_add(usize::from(u16le(fixed, 32).ok()?)))
        .ok_or(ZipError::Invalid("entry length overflow"))?;
    data.get(
        at..at
            .checked_add(46 + variable)
            .ok_or(ZipError::Invalid("entry length overflow"))?,
    )
    .ok_or(ZipError::Invalid("truncated central entry"))?;
    central_indexed(data, at, decode)
}

pub(crate) fn central_signature_guard(
    data: &[u8],
    at: usize,
    decode: fn(&[u8], u16) -> Result<String>,
) -> Result<RawCentral> {
    if !data
        .get(at..)
        .is_some_and(|tail| tail.starts_with(&CENTRAL_SIG.to_le_bytes()))
    {
        return Err(ZipError::Invalid("central directory signature"));
    }
    central_cursor(data, at, decode)
}

pub(crate) fn local_indexed(data: &[u8], at: usize) -> Result<LocalHeader> {
    if u32le(data, at)? != LOCAL_SIG {
        return Err(ZipError::Invalid("local header signature"));
    }
    let flags = u16le(data, at + 6)?;
    reject_encryption(flags)?;
    let method = Compression::from_u16(u16le(data, at + 8)?)?;
    let name_len = u16le(data, at + 26)? as usize;
    let extra_len = u16le(data, at + 28)? as usize;
    let data_offset = at
        .checked_add(30)
        .and_then(|v| v.checked_add(name_len))
        .and_then(|v| v.checked_add(extra_len))
        .ok_or(ZipError::Invalid("local header overflow"))?;
    if data_offset > data.len() {
        return Err(ZipError::Invalid("truncated local header"));
    }
    Ok(LocalHeader {
        data_offset,
        method,
        flags,
        name_offset: at + 30,
        name_len,
    })
}

pub(crate) fn local_sliced(data: &[u8], at: usize) -> Result<LocalHeader> {
    let header = data
        .get(at..at.saturating_add(30))
        .ok_or(ZipError::Invalid("truncated local header"))?;
    if u32le(header, 0)? != LOCAL_SIG {
        return Err(ZipError::Invalid("local header signature"));
    }
    let flags = u16le(header, 6)?;
    reject_encryption(flags)?;
    let method = Compression::from_u16(u16le(header, 8)?)?;
    let variable = usize::from(u16le(header, 26)?) + usize::from(u16le(header, 28)?);
    let data_offset = at
        .checked_add(30 + variable)
        .ok_or(ZipError::Invalid("local header overflow"))?;
    if data_offset > data.len() {
        return Err(ZipError::Invalid("truncated local fields"));
    }
    Ok(LocalHeader {
        data_offset,
        method,
        flags,
        name_offset: at + 30,
        name_len: usize::from(u16le(header, 26)?),
    })
}

pub(crate) fn local_fixed_array(data: &[u8], at: usize) -> Result<LocalHeader> {
    let header: &[u8; 30] = data
        .get(at..at.saturating_add(30))
        .and_then(|slice| slice.try_into().ok())
        .ok_or(ZipError::Invalid("truncated local header"))?;
    if u32::from_le_bytes(header[0..4].try_into().unwrap()) != LOCAL_SIG {
        return Err(ZipError::Invalid("local header signature"));
    }
    local_indexed(data, at)
}

pub(crate) fn local_lengths_first(data: &[u8], at: usize) -> Result<LocalHeader> {
    let fixed = data
        .get(
            at..at
                .checked_add(30)
                .ok_or(ZipError::Invalid("local header overflow"))?,
        )
        .ok_or(ZipError::Invalid("truncated local header"))?;
    let variable = usize::from(u16le(fixed, 26)?)
        .checked_add(usize::from(u16le(fixed, 28)?))
        .ok_or(ZipError::Invalid("local header overflow"))?;
    data.get(
        at..at
            .checked_add(30 + variable)
            .ok_or(ZipError::Invalid("local header overflow"))?,
    )
    .ok_or(ZipError::Invalid("truncated local fields"))?;
    local_sliced(data, at)
}

pub(crate) fn local_signature_guard(data: &[u8], at: usize) -> Result<LocalHeader> {
    if !data
        .get(at..)
        .is_some_and(|tail| tail.starts_with(&LOCAL_SIG.to_le_bytes()))
    {
        return Err(ZipError::Invalid("local header signature"));
    }
    local_indexed(data, at)
}

fn reject_encryption(flags: u16) -> Result<()> {
    if flags & 1 != 0 {
        Err(ZipError::Unsupported("encrypted entry"))
    } else {
        Ok(())
    }
}

pub(crate) fn decode_checked(raw: &[u8], flags: u16) -> Result<String> {
    if flags & (1 << 11) != 0 {
        std::str::from_utf8(raw)
            .map(str::to_owned)
            .map_err(|_| ZipError::InvalidUtf8)
    } else {
        Ok(decode_cp437(raw))
    }
}

pub(crate) fn decode_owned(raw: &[u8], flags: u16) -> Result<String> {
    if flags & (1 << 11) != 0 {
        return decode_checked(raw, flags);
    }
    Ok(raw
        .iter()
        .map(|&byte| {
            if byte < 128 {
                char::from(byte)
            } else {
                CP437[(byte - 128) as usize]
            }
        })
        .collect())
}

pub(crate) fn decode_push_chars(raw: &[u8], flags: u16) -> Result<String> {
    if flags & (1 << 11) != 0 {
        return String::from_utf8(raw.to_vec()).map_err(|_| ZipError::InvalidUtf8);
    }
    let mut output = String::with_capacity(raw.len());
    for &byte in raw {
        output.push(if byte.is_ascii() {
            char::from(byte)
        } else {
            CP437[usize::from(byte - 128)]
        });
    }
    Ok(output)
}

pub(crate) fn decode_ascii_fast_path(raw: &[u8], flags: u16) -> Result<String> {
    if flags & (1 << 11) != 0 {
        return std::str::from_utf8(raw)
            .map(ToOwned::to_owned)
            .map_err(|_| ZipError::InvalidUtf8);
    }
    if raw.is_ascii() {
        return Ok(raw.iter().map(|&byte| char::from(byte)).collect());
    }
    Ok(decode_cp437(raw))
}

pub(crate) fn decode_codepoints(raw: &[u8], flags: u16) -> Result<String> {
    if flags & (1 << 11) != 0 {
        return std::str::from_utf8(raw)
            .map(str::to_string)
            .map_err(|_| ZipError::InvalidUtf8);
    }
    raw.iter()
        .map(|&byte| {
            let character = if byte < 0x80 {
                char::from_u32(u32::from(byte))
            } else {
                Some(CP437[usize::from(byte - 0x80)])
            };
            character.ok_or(ZipError::Invalid("invalid filename codepoint"))
        })
        .collect()
}

fn decode_cp437(raw: &[u8]) -> String {
    raw.iter()
        .map(|&byte| {
            if byte < 128 {
                char::from(byte)
            } else {
                CP437[(byte - 128) as usize]
            }
        })
        .collect()
}

const CP437: [char; 128] = [
    'Ç', 'ü', 'é', 'â', 'ä', 'à', 'å', 'ç', 'ê', 'ë', 'è', 'ï', 'î', 'ì', 'Ä', 'Å', 'É', 'æ', 'Æ',
    'ô', 'ö', 'ò', 'û', 'ù', 'ÿ', 'Ö', 'Ü', '¢', '£', '¥', '₧', 'ƒ', 'á', 'í', 'ó', 'ú', 'ñ', 'Ñ',
    'ª', 'º', '¿', '⌐', '¬', '½', '¼', '¡', '«', '»', '░', '▒', '▓', '│', '┤', '╡', '╢', '╖', '╕',
    '╣', '║', '╗', '╝', '╜', '╛', '┐', '└', '┴', '┬', '├', '─', '┼', '╞', '╟', '╚', '╔', '╩', '╦',
    '╠', '═', '╬', '╧', '╨', '╤', '╥', '╙', '╘', '╒', '╓', '╫', '╪', '┘', '┌', '█', '▄', '▌', '▐',
    '▀', 'α', 'ß', 'Γ', 'π', 'Σ', 'σ', 'µ', 'τ', 'Φ', 'Θ', 'Ω', 'δ', '∞', 'φ', 'ε', '∩', '≡', '±',
    '≥', '≤', '⌠', '⌡', '÷', '≈', '°', '∙', '·', '√', 'ⁿ', '²', '■', '\u{00a0}',
];

pub(crate) fn normalize_components(root: &Path, name: &str) -> Result<PathBuf> {
    if name.contains('\0') || name.contains('\\') || has_windows_drive_prefix(name) {
        return Err(ZipError::UnsafePath(name.into()));
    }
    let path = Path::new(name);
    if path.is_absolute() {
        return Err(ZipError::UnsafePath(name.into()));
    }
    let mut relative = PathBuf::new();
    for component in path.components() {
        match component {
            Component::Normal(v) => relative.push(v),
            Component::CurDir => {}
            _ => return Err(ZipError::UnsafePath(name.into())),
        }
    }
    if relative.as_os_str().is_empty() && !name.ends_with('/') {
        return Err(ZipError::UnsafePath(name.into()));
    }
    Ok(root.join(relative))
}

pub(crate) fn normalize_fold(root: &Path, name: &str) -> Result<PathBuf> {
    if has_windows_drive_prefix(name) {
        return Err(ZipError::UnsafePath(name.into()));
    }
    name.split('/')
        .try_fold(PathBuf::from(root), |mut out, part| {
            match part {
                "" | "." => {}
                ".." => return Err(ZipError::UnsafePath(name.into())),
                value if value.contains('\\') || value.contains('\0') => {
                    return Err(ZipError::UnsafePath(name.into()))
                }
                value => out.push(value),
            }
            Ok(out)
        })
}

pub(crate) fn normalize_segments(root: &Path, name: &str) -> Result<PathBuf> {
    reject_path_prefix(name)?;
    let segments: Vec<&str> = name
        .split('/')
        .filter(|segment| !segment.is_empty() && *segment != ".")
        .collect();
    if segments.contains(&"..") || (segments.is_empty() && !name.ends_with('/')) {
        return Err(ZipError::UnsafePath(name.into()));
    }
    let mut output = root.to_path_buf();
    output.extend(segments);
    Ok(output)
}

pub(crate) fn normalize_bytes(root: &Path, name: &str) -> Result<PathBuf> {
    reject_path_prefix(name)?;
    let bytes = name.as_bytes();
    let mut output = root.to_path_buf();
    let mut start = 0usize;
    for end in 0..=bytes.len() {
        if end == bytes.len() || bytes[end] == b'/' {
            let part = &name[start..end];
            match part {
                "" | "." => {}
                ".." => return Err(ZipError::UnsafePath(name.into())),
                _ => output.push(part),
            }
            start = end + 1;
        }
    }
    if output == root && !name.ends_with('/') {
        return Err(ZipError::UnsafePath(name.into()));
    }
    Ok(output)
}

pub(crate) fn normalize_collect(root: &Path, name: &str) -> Result<PathBuf> {
    reject_path_prefix(name)?;
    let parts = name.split('/').try_fold(Vec::new(), |mut parts, part| {
        match part {
            "" | "." => {}
            ".." => return Err(ZipError::UnsafePath(name.into())),
            _ => parts.push(part),
        }
        Ok(parts)
    })?;
    if parts.is_empty() && !name.ends_with('/') {
        return Err(ZipError::UnsafePath(name.into()));
    }
    Ok(parts
        .into_iter()
        .fold(root.to_path_buf(), |path, part| path.join(part)))
}

fn reject_path_prefix(name: &str) -> Result<()> {
    if name.contains(['\0', '\\']) || name.starts_with('/') || has_windows_drive_prefix(name) {
        Err(ZipError::UnsafePath(name.into()))
    } else {
        Ok(())
    }
}

fn has_windows_drive_prefix(name: &str) -> bool {
    let bytes = name.as_bytes();
    bytes.len() >= 2 && bytes[0].is_ascii_alphabetic() && bytes[1] == b':'
}

/// Controlled fault: rejects otherwise valid nested paths at depth three.
pub(crate) fn normalize_faulty_depth(root: &Path, name: &str) -> Result<PathBuf> {
    if name.split('/').filter(|p| !p.is_empty()).count() >= 3 {
        return Err(ZipError::UnsafePath(name.into()));
    }
    normalize_components(root, name)
}

pub(crate) fn decompress_reader(
    method: Compression,
    compressed: &[u8],
    expected: u64,
) -> Result<Vec<u8>> {
    match method {
        Compression::Stored => Ok(compressed.to_vec()),
        Compression::Deflate => {
            let mut out = Vec::new();
            DeflateDecoder::new(compressed)
                .take(expected.saturating_add(1))
                .read_to_end(&mut out)?;
            Ok(out)
        }
    }
}

pub(crate) fn decompress_buffered(
    method: Compression,
    compressed: &[u8],
    expected: u64,
) -> Result<Vec<u8>> {
    match method {
        Compression::Stored => Ok(Vec::from(compressed)),
        Compression::Deflate => {
            let mut out = Vec::new();
            DeflateDecoder::new(BufReader::new(compressed))
                .take(expected.saturating_add(1))
                .read_to_end(&mut out)?;
            Ok(out)
        }
    }
}

pub(crate) fn decompress_preallocated(
    method: Compression,
    compressed: &[u8],
    expected: u64,
) -> Result<Vec<u8>> {
    if method == Compression::Stored {
        return Ok(compressed.to_owned());
    }
    let capacity = usize::try_from(expected.min(8 * 1024 * 1024)).unwrap_or(0);
    let mut output = Vec::with_capacity(capacity);
    DeflateDecoder::new(compressed)
        .take(expected.saturating_add(1))
        .read_to_end(&mut output)?;
    Ok(output)
}

pub(crate) fn decompress_chunks(
    method: Compression,
    compressed: &[u8],
    expected: u64,
) -> Result<Vec<u8>> {
    if method == Compression::Stored {
        return Ok(compressed.to_vec());
    }
    let mut decoder = DeflateDecoder::new(compressed);
    let mut output = Vec::new();
    let mut buffer = [0u8; 8192];
    while output.len() as u64 <= expected {
        let read = decoder.read(&mut buffer)?;
        if read == 0 {
            break;
        }
        output.extend_from_slice(&buffer[..read]);
    }
    Ok(output)
}

pub(crate) fn decompress_copy(
    method: Compression,
    compressed: &[u8],
    expected: u64,
) -> Result<Vec<u8>> {
    if method == Compression::Stored {
        return Ok(compressed.to_vec());
    }
    let mut decoder = DeflateDecoder::new(compressed).take(expected.saturating_add(1));
    let mut output = Vec::new();
    std::io::copy(&mut decoder, &mut output)?;
    Ok(output)
}

pub(crate) fn crc_one_shot(data: &[u8]) -> u32 {
    crc32fast::hash(data)
}
pub(crate) fn crc_chunked(data: &[u8]) -> u32 {
    let mut h = crc32fast::Hasher::new();
    for chunk in data.chunks(4096) {
        h.update(chunk);
    }
    h.finalize()
}
pub(crate) fn crc_bytewise(data: &[u8]) -> u32 {
    let mut hasher = crc32fast::Hasher::new();
    data.iter()
        .for_each(|byte| hasher.update(std::slice::from_ref(byte)));
    hasher.finalize()
}
pub(crate) fn crc_fixed_blocks(data: &[u8]) -> u32 {
    let mut hasher = crc32fast::Hasher::new();
    data.chunks(64).for_each(|chunk| hasher.update(chunk));
    hasher.finalize()
}
pub(crate) fn crc_halves(data: &[u8]) -> u32 {
    let mut hasher = crc32fast::Hasher::new();
    let (left, right) = data.split_at(data.len() / 2);
    hasher.update(left);
    hasher.update(right);
    hasher.finalize()
}

pub(crate) fn write_direct(path: &Path, bytes: &[u8]) -> Result<()> {
    if let Some(parent) = path.parent() {
        fs::create_dir_all(parent)?;
    }
    fs::write(path, bytes)?;
    Ok(())
}
pub(crate) fn write_create_new(path: &Path, bytes: &[u8]) -> Result<()> {
    if let Some(parent) = path.parent() {
        fs::create_dir_all(parent)?;
    }
    let mut tmp = path.to_path_buf();
    tmp.set_extension("zipdiv-part");
    fs::write(&tmp, bytes)?;
    fs::rename(tmp, path)?;
    Ok(())
}
pub(crate) fn write_file(path: &Path, bytes: &[u8]) -> Result<()> {
    if let Some(parent) = path.parent() {
        fs::create_dir_all(parent)?;
    }
    let mut file = File::create(path)?;
    file.write_all(bytes)?;
    file.sync_data()?;
    Ok(())
}
pub(crate) fn write_buffered(path: &Path, bytes: &[u8]) -> Result<()> {
    if let Some(parent) = path.parent() {
        fs::create_dir_all(parent)?;
    }
    let file = File::create(path)?;
    let mut writer = BufWriter::new(file);
    writer.write_all(bytes)?;
    writer.flush()?;
    Ok(())
}
pub(crate) fn write_chunked(path: &Path, bytes: &[u8]) -> Result<()> {
    if let Some(parent) = path.parent() {
        fs::create_dir_all(parent)?;
    }
    let mut file = File::create(path)?;
    for chunk in bytes.chunks(16 * 1024) {
        file.write_all(chunk)?;
    }
    Ok(())
}
