use std::borrow::Cow;
use std::ops::{Index, Range};

/// A record of string fields, one per field defined in the schema.
///
/// Each field is a decoded string slice. When borrowed, the slices reference
/// the reader's internal buffer. Call [`into_owned`](StringRecord::into_owned)
/// to produce a `StringRecord<'static>` that owns its data.
#[derive(Debug, Clone)]
pub struct StringRecord<'a> {
    repr: Repr<'a>,
}

#[derive(Debug, Clone)]
enum Repr<'a> {
    /// One `Cow<str>` per field — used by the borrowed read path.
    Fields(Vec<Cow<'a, str>>),
    /// All field text packed into a single `String` with index ranges —
    /// used by `into_owned` and the iterator path.
    Packed {
        buffer: String,
        ranges: Vec<Range<usize>>,
    },
}

impl<'a> StringRecord<'a> {
    /// Creates a new `StringRecord` from per-field decoded strings.
    pub(crate) fn from_fields(fields: Vec<Cow<'a, str>>) -> Self {
        Self {
            repr: Repr::Fields(fields),
        }
    }

    /// Creates a new `StringRecord` from a packed string buffer and field ranges.
    pub(crate) fn packed(buffer: String, ranges: Vec<Range<usize>>) -> StringRecord<'static> {
        StringRecord {
            repr: Repr::Packed { buffer, ranges },
        }
    }
}

impl StringRecord<'_> {
    /// Returns the field at the given index, or `None` if out of bounds.
    #[must_use]
    #[inline]
    pub fn get(&self, index: usize) -> Option<&str> {
        match &self.repr {
            Repr::Fields(fields) => fields.get(index).map(AsRef::as_ref),
            Repr::Packed { buffer, ranges } => {
                let range = ranges.get(index)?;
                Some(&buffer[range.start..range.end])
            }
        }
    }

    /// Returns the number of fields in this record.
    #[must_use]
    #[inline]
    pub fn len(&self) -> usize {
        match &self.repr {
            Repr::Fields(fields) => fields.len(),
            Repr::Packed { ranges, .. } => ranges.len(),
        }
    }

    /// Returns `true` if this record has no fields.
    #[must_use]
    #[inline]
    pub fn is_empty(&self) -> bool {
        match &self.repr {
            Repr::Fields(fields) => fields.is_empty(),
            Repr::Packed { ranges, .. } => ranges.is_empty(),
        }
    }

    /// Converts this record into an owned `StringRecord<'static>` by packing
    /// all field text into a single contiguous `String`.
    #[must_use]
    pub fn into_owned(self) -> StringRecord<'static> {
        match self.repr {
            Repr::Packed { buffer, ranges } => StringRecord {
                repr: Repr::Packed { buffer, ranges },
            },
            Repr::Fields(fields) => {
                let total_len: usize = fields.iter().map(|c| c.len()).sum();
                let mut buffer = String::with_capacity(total_len);
                let mut ranges = Vec::with_capacity(fields.len());
                for cow in &fields {
                    let start = buffer.len();
                    buffer.push_str(cow);
                    ranges.push(start..buffer.len());
                }
                StringRecord {
                    repr: Repr::Packed { buffer, ranges },
                }
            }
        }
    }
}

impl Index<usize> for StringRecord<'_> {
    type Output = str;

    #[inline]
    fn index(&self, index: usize) -> &str {
        match &self.repr {
            Repr::Fields(fields) => &fields[index],
            Repr::Packed { buffer, ranges } => {
                let range = &ranges[index];
                &buffer[range.start..range.end]
            }
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    fn fields_record() -> StringRecord<'static> {
        StringRecord::from_fields(vec![
            Cow::Owned("Alice".to_owned()),
            Cow::Owned("30".to_owned()),
        ])
    }

    fn packed_record() -> StringRecord<'static> {
        StringRecord::packed("Alice30".to_owned(), vec![0..5, 5..7])
    }

    // -- Fields repr --

    #[test]
    fn fields_get() {
        let rec = fields_record();
        assert_eq!(rec.get(0), Some("Alice"));
        assert_eq!(rec.get(1), Some("30"));
        assert_eq!(rec.get(2), None);
    }

    #[test]
    fn fields_index() {
        let rec = fields_record();
        assert_eq!(&rec[0], "Alice");
        assert_eq!(&rec[1], "30");
    }

    #[test]
    fn fields_len() {
        let rec = fields_record();
        assert_eq!(rec.len(), 2);
        assert!(!rec.is_empty());
    }

    #[test]
    fn fields_empty() {
        let rec = StringRecord::from_fields(vec![]);
        assert_eq!(rec.len(), 0);
        assert!(rec.is_empty());
    }

    #[test]
    fn fields_into_owned() {
        let rec = fields_record().into_owned();
        assert_eq!(&rec[0], "Alice");
        assert_eq!(&rec[1], "30");
        assert_eq!(rec.len(), 2);
    }

    // -- Packed repr --

    #[test]
    fn packed_get() {
        let rec = packed_record();
        assert_eq!(rec.get(0), Some("Alice"));
        assert_eq!(rec.get(1), Some("30"));
        assert_eq!(rec.get(2), None);
    }

    #[test]
    fn packed_index() {
        let rec = packed_record();
        assert_eq!(&rec[0], "Alice");
        assert_eq!(&rec[1], "30");
    }

    #[test]
    fn packed_len() {
        let rec = packed_record();
        assert_eq!(rec.len(), 2);
        assert!(!rec.is_empty());
    }

    #[test]
    fn packed_into_owned_is_noop() {
        let rec = packed_record().into_owned();
        assert_eq!(&rec[0], "Alice");
        assert_eq!(&rec[1], "30");
    }
}
