use crate::{ByteRecord, FwReaderField, FwfError, RecordSeparator, Result};
use std::ops::Range;

/// A reusable record-shape definition for an [`FwReader`](crate::FwReader).
///
/// A schema describes the byte width of a record, the fields within it, and
/// the optional separator that terminates each record. It also owns the
/// internal buffers used to read records, so swapping schemas via
/// [`FwReader::into_reader_for_schema`](crate::FwReader::into_reader_for_schema)
/// is allocation-free for both the outgoing and incoming schema.
///
/// Construct one via [`FwReaderSchema::builder`].
#[derive(Debug)]
pub struct FwReaderSchema {
    fields: Vec<FwReaderField>,
    separator: RecordSeparator,
    buffer: Vec<u8>,
    separator_buffer: Vec<u8>,
    field_ranges: Vec<Range<usize>>,
}

impl FwReaderSchema {
    pub(crate) fn new(
        width: usize,
        fields: Vec<FwReaderField>,
        separator: RecordSeparator,
    ) -> Self {
        let buffer = vec![0u8; width];
        let separator_buffer = vec![0u8; separator.len()];
        let field_ranges = Vec::with_capacity(fields.len());
        Self {
            fields,
            separator,
            buffer,
            separator_buffer,
            field_ranges,
        }
    }

    /// Returns a [`FwReaderSchemaBuilder`] for a record of the given byte width.
    #[must_use]
    pub fn builder(width: usize) -> FwReaderSchemaBuilder {
        FwReaderSchemaBuilder::new(width)
    }

    #[must_use]
    pub(crate) fn has_separator(&self) -> bool {
        !self.separator_buffer.is_empty()
    }

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

    #[must_use]
    pub(crate) fn buffer_len(&self) -> usize {
        self.buffer.len()
    }

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

    #[must_use]
    pub(crate) fn separator_buffer_len(&self) -> usize {
        self.separator_buffer.len()
    }

    #[must_use]
    pub(crate) fn is_valid_separator_in_buffer(&self) -> bool {
        self.separator_buffer == self.separator.as_bytes()
    }

    /// Fills field ranges from the buffer.
    /// Call this after both record and separator reads succeed.
    pub(crate) fn fill_field_ranges(&mut self) {
        self.field_ranges.clear();
        for field in &self.fields {
            self.field_ranges.push(field.stripped_range(&self.buffer));
        }
    }

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

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

    /// Creates a borrowed [`StringRecord`](crate::StringRecord) by decoding
    /// the current buffer state.
    #[cfg(feature = "encoding_rs")]
    pub(crate) fn string_record(
        &self,
        encoding: &'static encoding_rs::Encoding,
    ) -> Result<crate::StringRecord<'_>> {
        let mut strings = Vec::with_capacity(self.field_ranges.len());
        for (index, range) in self.field_ranges.iter().enumerate() {
            let bytes = &self.buffer[range.start..range.end];
            let Some(decoded) = encoding.decode_without_bom_handling_and_without_replacement(bytes)
            else {
                return Err(FwfError::Encoding { field_index: index });
            };
            strings.push(decoded);
        }
        Ok(crate::StringRecord::from_fields(strings))
    }

    /// Creates an owned [`StringRecord`](crate::StringRecord) packed from the
    /// current buffer state.
    #[cfg(feature = "encoding_rs")]
    pub(crate) fn owned_string_record(
        &self,
        encoding: &'static encoding_rs::Encoding,
    ) -> Result<crate::StringRecord<'static>> {
        let total_bytes: usize = self.field_ranges.iter().map(|r| r.end - r.start).sum();
        let mut buffer = String::with_capacity(total_bytes);
        let mut ranges = Vec::with_capacity(self.field_ranges.len());
        for (index, range) in self.field_ranges.iter().enumerate() {
            let bytes = &self.buffer[range.start..range.end];
            let Some(decoded) = encoding.decode_without_bom_handling_and_without_replacement(bytes)
            else {
                return Err(FwfError::Encoding { field_index: index });
            };
            let start = buffer.len();
            buffer.push_str(&decoded);
            ranges.push(start..buffer.len());
        }
        Ok(crate::StringRecord::packed(buffer, ranges))
    }
}

/// Builder for constructing a [`FwReaderSchema`].
///
/// Created via [`FwReaderSchema::builder`]. Add fields with
/// [`field`](Self::field), set the record separator with
/// [`separator`](Self::separator), and call [`build`](Self::build) to
/// validate and produce a schema.
#[derive(Debug, Clone)]
pub struct FwReaderSchemaBuilder {
    width: usize,
    fields: Vec<FwReaderField>,
    separator: RecordSeparator,
}

impl FwReaderSchemaBuilder {
    #[must_use]
    pub(crate) fn new(width: usize) -> Self {
        Self {
            width,
            fields: Vec::new(),
            separator: RecordSeparator::None,
        }
    }

    /// Adds a field to the schema. Fields are returned in the order they are added.
    #[must_use]
    #[inline]
    pub fn field(mut self, field: FwReaderField) -> Self {
        self.fields.push(field);
        self
    }

    /// Sets the record separator. Defaults to [`RecordSeparator::None`].
    #[must_use]
    #[inline]
    pub fn separator(mut self, separator: RecordSeparator) -> Self {
        self.separator = separator;
        self
    }

    /// Validates the configured fields and produces a [`FwReaderSchema`].
    ///
    /// # Errors
    ///
    /// Returns [`crate::FwfError::FieldOutOfBounds`] if any field range extends
    /// beyond the configured record width.
    pub fn build(self) -> Result<FwReaderSchema> {
        self.validate()?;
        Ok(FwReaderSchema::new(self.width, self.fields, self.separator))
    }

    fn validate(&self) -> Result<()> {
        for (i, field) in self.fields.iter().enumerate() {
            if field.range().end > self.width {
                return Err(FwfError::FieldOutOfBounds { field_index: i });
            }
        }
        Ok(())
    }
}
