use thiserror::Error;

/// Errors that can occur while parsing or decoding an RSSA file.
#[derive(Debug, Error)]
pub enum RssaError {
    #[error("I/O error: {0}")]
    Io(#[from] std::io::Error),

    #[error(
        "malformed Fortran record at byte offset {offset}: the marker before ({before}) and \
         after ({after}) the record do not match"
    )]
    RecordMarkerMismatch {
        offset: usize,
        before: i32,
        after: i32,
    },

    #[error("unexpected end of file while reading a Fortran record at byte offset {offset}")]
    UnexpectedEof { offset: usize },

    #[error(
        "unrecognized RSSA file format: the identification record did not contain a known \
         signature (expected 'SF_00001' or 'd1suned'), found: {0:?}"
    )]
    UnsupportedFormat(String),

    #[error("expected {expected} values per track record (nrcd), but the header declares {found}")]
    UnexpectedRecordLength { expected: i32, found: i32 },

    #[error(
        "unsupported header layout: np1 = {0} is not negative; only files where np1 < 0 \
         (the standard MCNP surface-source layout) are supported"
    )]
    UnsupportedNp1(i64),

    #[error(
        "unsupported header layout: niwr = {0} is not zero; RSSA files with extra weight-window \
         records are not supported"
    )]
    UnsupportedNiwr(i32),

    #[error(
        "malformed surface record at index {index}: the record declares {num_parameters} \
         parameters, which needs {expected} bytes (id, type and count as 4-byte integers{info}, \
         then one 8-byte float per parameter), but the record is {found} bytes long"
    )]
    UnexpectedSurfaceRecordLength {
        index: usize,
        num_parameters: i32,
        expected: usize,
        found: usize,
        /// Filled in with a note about the macrobody-facet field when `kjaq == 1`, so
        /// the byte arithmetic in the message above adds up in both cases.
        info: &'static str,
    },

    #[error(
        "track record at index {index} was recorded on surface {surface_id}, which the header \
         does not list (it declares surfaces {header_surfaces:?}). The surface each track \
         crossed is read from the last value of the track record, which only holds it for files \
         written with nrcd < 0"
    )]
    UnknownTrackSurfaceId {
        index: usize,
        surface_id: i64,
        header_surfaces: Vec<i32>,
    },

    #[error(
        "track data size mismatch: the header declares {nrss} tracks ({expected_bytes} bytes), \
         but {actual_bytes} bytes remain in the file"
    )]
    TrackDataSizeMismatch {
        nrss: i64,
        expected_bytes: u64,
        actual_bytes: u64,
    },

    #[error(
        "malformed track record at index {index}: expected a Fortran record marker of {expected} \
         bytes, found {before} before and {after} after the record"
    )]
    TrackRecordMarkerMismatch {
        index: usize,
        before: i32,
        after: i32,
        expected: i32,
    },

    #[error(
        "malformed track record at index {index}: the packed field that encodes both the \
         particle-type indicator and the sign of the third direction cosine (w) is zero, which \
         is not a valid value for either"
    )]
    InvalidPackedDirectionField { index: usize },

    #[error("unknown track column: {0:?}")]
    UnknownColumn(String),

    #[error("duplicate track column requested: {0:?}")]
    DuplicateColumn(String),

    #[error(
        "track byte range has length {len}, which is not a multiple of the 96-byte track \
         record size"
    )]
    InvalidTrackByteLength { len: usize },

    #[error("failed to (de)serialize RSSA file parameters: {0}")]
    Json(#[from] serde_json::Error),

    #[error("internal Polars error: {0}")]
    Polars(#[from] polars::prelude::PolarsError),
}

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