use core::ffi::CStr;
use core::fmt;
use core::ops::{Deref, DerefMut};

use crate::c;
#[cfg(feature = "std")]
use crate::c::bpos as Bpos;

/// Rust wrapper around `c::printbuf` providing `fmt::Write` via
/// `bch2_prt_bytes_indented`, which processes `\t`, `\r`, `\n` for
/// tabstop and indent handling.
pub struct Printbuf(c::printbuf);

/// RAII guard for printbuf indentation — calls `indent_sub` on drop.
/// Use through `Printbuf::indent()`.
pub struct PrintbufIndent<'a> {
    buf: &'a mut Printbuf,
    spaces: u32,
}

impl Drop for PrintbufIndent<'_> {
    fn drop(&mut self) {
        self.buf.indent_sub(self.spaces);
    }
}

impl Deref for PrintbufIndent<'_> {
    type Target = Printbuf;
    fn deref(&self) -> &Printbuf { self.buf }
}

impl DerefMut for PrintbufIndent<'_> {
    fn deref_mut(&mut self) -> &mut Printbuf { self.buf }
}

impl Printbuf {
    pub fn new() -> Self {
        Printbuf(c::printbuf::new())
    }

    pub fn as_str(&self) -> &str {
        if self.0.buf.is_null() {
            ""
        } else {
            unsafe { CStr::from_ptr(self.0.buf) }
                .to_str()
                .unwrap_or("")
        }
    }

    /// Add a tabstop at `spaces` columns from the previous tabstop.
    pub fn tabstop_push(&mut self, spaces: u32) {
        unsafe { c::bch2_printbuf_tabstop_push(&mut self.0, spaces) };
    }

    pub fn tabstops_reset(&mut self) {
        unsafe { c::bch2_printbuf_tabstops_reset(&mut self.0) };
    }

    /// Reset tabstops and set new ones from a slice of column widths.
    pub fn tabstops(&mut self, widths: &[u32]) {
        self.tabstops_reset();
        for &w in widths {
            self.tabstop_push(w);
        }
    }

    pub fn indent_add(&mut self, spaces: u32) {
        unsafe { c::bch2_printbuf_indent_add(&mut self.0, spaces) };
    }

    pub fn indent_sub(&mut self, spaces: u32) {
        unsafe { c::bch2_printbuf_indent_sub(&mut self.0, spaces) };
    }

    /// Add indentation, returning a guard that removes it on drop.
    /// Use the guard (which derefs to `&mut Printbuf`) for all
    /// operations within the indented scope.
    pub fn indent(&mut self, spaces: u32) -> PrintbufIndent<'_> {
        self.indent_add(spaces);
        PrintbufIndent { buf: self, spaces }
    }

    /// Advance to next tabstop (equivalent to `\t` in format string).
    pub fn tab(&mut self) {
        unsafe { c::bch2_prt_tab(&mut self.0) };
    }

    /// Right-justify previous text in current tabstop column
    /// (equivalent to `\r` in format string).
    pub fn tab_rjust(&mut self) {
        unsafe { c::bch2_prt_tab_rjust(&mut self.0) };
    }

    /// Post-process the buffer, aligning columns separated by raw `\t`
    /// (left-aligned) and `\r` (right-aligned) characters. Call after
    /// writing a section of tabular data without preset tabstops.
    pub fn tabstop_align(&mut self) {
        unsafe { c::bch2_printbuf_tabstop_align(&mut self.0) };
    }

    /// Write a section of tabular data using automatic column alignment.
    /// Creates a sub-buffer with the same human_readable setting, passes
    /// it to `f`, calls `tabstop_align()`, then appends the result.
    pub fn aligned(&mut self, f: impl FnOnce(&mut Printbuf)) {
        let mut sub = Printbuf::new();
        sub.set_human_readable(self.is_human_readable());
        f(&mut sub);
        sub.tabstop_align();
        fmt::Write::write_fmt(self, format_args!("{}", sub)).unwrap();
    }

    /// Emit newline with indent handling
    /// (equivalent to `\n` in format string).
    pub fn newline(&mut self) {
        unsafe { c::bch2_prt_newline(&mut self.0) };
    }

    /// Print a u64 value using `bch2_prt_units_u64`, which respects
    /// the `human_readable_units` flag on the printbuf.
    pub fn units_u64(&mut self, v: u64) {
        unsafe { c::bch2_prt_units_u64(&mut self.0, v) };
    }

    /// Print a sector count as bytes (sectors << 9).
    pub fn units_sectors(&mut self, sectors: u64) {
        self.units_u64(sectors << 9);
    }

    pub fn is_human_readable(&self) -> bool {
        self.0.human_readable_units()
    }

    pub fn set_human_readable(&mut self, v: bool) {
        self.0.set_human_readable_units(v);
    }

    /// Print a human-readable representation of a u64 value.
    pub fn human_readable_u64(&mut self, v: u64) {
        unsafe { c::bch2_prt_human_readable_u64(&mut self.0, v) };
    }

    /// Print a bcachefs metadata version number.
    pub fn version(&mut self, v: u32) {
        unsafe { c::bch2_version_to_text(&mut self.0, c::bcachefs_metadata_version(v)) };
    }

    /// Print superblock contents.
    ///
    /// # Safety
    /// `fs` must be a valid pointer to a `bch_fs` or null.
    pub unsafe fn sb_to_text(&mut self, fs: *mut c::bch_fs, sb: &c::bch_sb,
                             layout: bool, fields: u32) {
        c::bch2_sb_to_text(&mut self.0, fs, sb as *const _ as *mut _, layout, fields);
    }

    /// Print a set of bitflags as comma-separated names.
    ///
    /// # Safety
    /// `list` must be a valid null-terminated array of C string pointers.
    pub unsafe fn prt_bitflags(&mut self, list: *const *const core::ffi::c_char, flags: u64) {
        c::bch2_prt_bitflags(&mut self.0, list, flags);
    }

    /// Access the underlying `c::printbuf` for calling C prt_* functions.
    pub fn as_raw(&mut self) -> &mut c::printbuf {
        &mut self.0
    }
}

impl fmt::Write for Printbuf {
    fn write_str(&mut self, s: &str) -> fmt::Result {
        unsafe {
            c::bch2_prt_bytes_indented(
                &mut self.0,
                s.as_ptr() as *const core::ffi::c_char,
                s.len() as core::ffi::c_uint,
            );
        }
        Ok(())
    }
}

impl Default for Printbuf {
    fn default() -> Self { Self::new() }
}

impl fmt::Display for Printbuf {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        f.write_str(self.as_str())
    }
}

impl c::printbuf {
    pub fn new() -> c::printbuf {
        let mut buf: c::printbuf = Default::default();

        buf.set_heap_allocated(true);
        buf
    }
}

impl Drop for c::printbuf {
    fn drop(&mut self) {
        unsafe { c::bch2_printbuf_exit(self) }
    }
}

#[cfg(feature = "std")]
impl fmt::Display for Bpos {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        printbuf_to_formatter(f, |buf| unsafe { c::bch2_bpos_to_text(buf, *self) })
    }
}

#[cfg(feature = "std")]
pub fn printbuf_to_formatter<F>(f: &mut fmt::Formatter<'_>, func: F) -> fmt::Result
where
    F: Fn(*mut c::printbuf),
{
    let mut buf = c::printbuf::new();

    func(&mut buf);

    if buf.buf.is_null() {
        return Ok(());
    }

    let s = unsafe { CStr::from_ptr(buf.buf) };
    f.write_str(&s.to_string_lossy())
}
