use std::{
    fs::File,
    io::Read,
    path::{Path, PathBuf},
};

mod editor;
pub mod formatting;
mod maintenance;
mod organization;
mod reader;
mod selection;
pub mod toml;
mod typed;
mod writer;

pub use editor::{DeletionPacking, Editor};
pub use maintenance::Maintenance;
pub use organization::Organizer;
pub use reader::{FrameIter, MultiRangeFrameIter, Reader};
pub use selection::{FrameSelection, KeySelector};
pub use typed::{TypedEditor, TypedFrameIter, TypedMultiRangeFrameIter, TypedReader, TypedWriter};
pub use writer::{MutationOptions, OperationOptions, Writer, WriterOpenOptions};

pub type Result<T> = std::result::Result<T, Error>;

#[derive(Debug, thiserror::Error)]
pub enum Error {
    #[error("I/O error: {0}")]
    Io(#[from] std::io::Error),
    #[error("unsupported FWOB file format: {0}")]
    UnsupportedFormat(PathBuf),
    #[error("at least one source file is required")]
    EmptySources,
    #[error("at least one split key is required")]
    EmptySplitKeys,
    #[error("split keys must be sorted")]
    UnsortedSplitKeys,
    #[error("source files use different FWOB format versions")]
    IncompatibleFormat,
    #[error("source files use incompatible schemas")]
    IncompatibleSchema,
    #[error("source files use incompatible titles")]
    IncompatibleTitle,
    #[error("source files use incompatible string tables")]
    IncompatibleStringTable,
    #[error("source frame keys are not globally ordered")]
    IncompatibleKeyOrder,
    #[error("typed frame schema does not match the file schema")]
    SchemaMismatch,
    #[error("invalid key selector: {0}")]
    InvalidSelector(String),
    #[error("selector range is reversed: {first:?}..{last:?}")]
    ReversedSelector {
        first: fwob_core::Key,
        last: fwob_core::Key,
    },
    #[error("field '{0}' points outside its frame")]
    InvalidFieldSlice(String),
    #[error("unsupported integer field width {0}")]
    InvalidIntegerWidth(usize),
    #[error("string-table index {0} is out of range")]
    InvalidStringTableIndex(u64),
    #[error(transparent)]
    Core(#[from] fwob_core::FwobError),
    #[error(transparent)]
    V1(#[from] fwob_v1::V1Error),
    #[error(transparent)]
    V2(#[from] fwob_v2::V2Error),
}

pub use fwob_core::{FormatVersion, ReaderOptions, VerificationReport};

pub fn detect_format(path: impl AsRef<Path>) -> Result<FormatVersion> {
    let path = path.as_ref();
    let mut file = File::open(path)?;
    let mut magic = [0u8; 4];
    file.read_exact(&mut magic)?;
    if &magic == fwob_v1::SIGNATURE {
        Ok(FormatVersion::V1)
    } else if &magic == fwob_v2::MAGIC {
        Ok(FormatVersion::V2)
    } else {
        Err(Error::UnsupportedFormat(path.to_path_buf()))
    }
}
