use crate::FwfError;
use crate::byte_record::ByteRecord;
use crate::fw_error::Result;
use crate::fw_reader_schema::FwReaderSchema;
#[cfg(feature = "encoding_rs")]
use crate::string_record::StringRecord;

/// The outcome of attempting to fill a buffer from a reader.
#[derive(Clone, Copy)]
pub(crate) enum ReadOutcome {
    /// The buffer was filled.
    Filled,
    /// Zero bytes were read (clean EOF).
    Eof,
    /// Some bytes were read, but the buffer was not filled.
    Partial(usize),
}

/// Shared state for [`FwReader`](crate::FwReader) and
/// [`AsyncFwReader`](crate::AsyncFwReader), holding the schema,
/// internal buffers, and record-processing logic.
///
/// The sync and async reader wrappers handle I/O, then delegate to
/// this struct for validation and record construction.
#[derive(Debug)]
pub(crate) struct FwReaderState {
    schema: FwReaderSchema,
    record_number: u64,
    complete: bool,
    #[cfg(feature = "encoding_rs")]
    encoding: &'static encoding_rs::Encoding,
}

impl FwReaderState {
    #[must_use]
    pub fn new(
        schema: FwReaderSchema,
        #[cfg(feature = "encoding_rs")] encoding: &'static encoding_rs::Encoding,
    ) -> Self {
        Self {
            schema,
            record_number: 0,
            complete: false,
            #[cfg(feature = "encoding_rs")]
            encoding,
        }
    }

    #[must_use]
    pub fn record_number(&self) -> u64 {
        self.record_number
    }

    #[must_use]
    pub fn is_complete(&self) -> bool {
        self.complete
    }

    #[must_use]
    pub fn has_separator(&self) -> bool {
        self.schema.has_separator()
    }

    #[must_use]
    pub fn buffer_mut(&mut self) -> &mut [u8] {
        self.schema.buffer_mut()
    }

    #[must_use]
    pub fn separator_buffer_mut(&mut self) -> &mut [u8] {
        self.schema.separator_buffer_mut()
    }

    /// Processes the outcome of reading record bytes.
    /// Returns `Ok(true)` to continue, `Ok(false)` for clean EOF.
    pub fn handle_record_read(&mut self, outcome: ReadOutcome) -> Result<bool> {
        match outcome {
            ReadOutcome::Filled => Ok(true),
            ReadOutcome::Eof => {
                self.complete = true;
                Ok(false)
            }
            ReadOutcome::Partial(bytes_read) => {
                self.complete = true;
                Err(FwfError::IncompleteRecord {
                    bytes_read,
                    expected: self.schema.buffer_len(),
                })
            }
        }
    }

    /// Processes the outcome of reading separator bytes.
    pub fn handle_separator_read(&mut self, outcome: ReadOutcome) -> Result<()> {
        match outcome {
            ReadOutcome::Filled => {
                if !self.schema.is_valid_separator_in_buffer() {
                    self.complete = true;
                    return Err(FwfError::InvalidSeparator);
                }
                Ok(())
            }
            ReadOutcome::Eof => {
                self.complete = true;
                Ok(())
            }
            ReadOutcome::Partial(bytes_read) => {
                self.complete = true;
                Err(FwfError::IncompleteSeparator {
                    bytes_read,
                    expected: self.schema.separator_buffer_len(),
                })
            }
        }
    }

    /// Fills field ranges from the buffer and increments the record counter.
    /// Call this after both record and separator reads succeed.
    pub fn fill_field_ranges(&mut self) {
        self.schema.fill_field_ranges();
        self.record_number += 1;
    }

    /// Creates a borrowed [`ByteRecord`] from the current buffer state.
    pub fn byte_record(&self) -> ByteRecord<'_> {
        self.schema.byte_record()
    }

    /// Creates an owned [`ByteRecord`] packed from the current buffer state.
    pub fn owned_byte_record(&self) -> ByteRecord<'static> {
        self.schema.owned_byte_record()
    }

    /// Creates a borrowed [`StringRecord`] by decoding the current buffer state.
    #[cfg(feature = "encoding_rs")]
    pub fn string_record(&self) -> Result<StringRecord<'_>> {
        self.schema.string_record(self.encoding)
    }

    /// Creates an owned [`StringRecord`] packed from the current buffer state.
    #[cfg(feature = "encoding_rs")]
    pub fn owned_string_record(&self) -> Result<StringRecord<'static>> {
        self.schema.owned_string_record(self.encoding)
    }

    #[must_use]
    pub fn for_schema(self, schema: FwReaderSchema) -> (Self, FwReaderSchema) {
        let old_schema = self.schema;
        let new_state = Self {
            schema,
            record_number: self.record_number,
            complete: self.complete,
            #[cfg(feature = "encoding_rs")]
            encoding: self.encoding,
        };
        (new_state, old_schema)
    }
}
