/// A struct that guesses the MIME type of a reader by peeking at the first few bytes.
use std::io;
use std::sync::LazyLock;

use infer::Infer;
use regex::Regex;

use crate::peakreader::PeekReader;

const BUF_SIZE: usize = 4096;

fn is_gbff(buf: &[u8]) -> bool {
    buf.starts_with(b"LOCUS ")
}

static RE_FASTA: LazyLock<Regex> = LazyLock::new(|| Regex::new(r"^>[^ ]").unwrap());
fn is_fasta(buf: &[u8]) -> bool {
    std::str::from_utf8(buf).map_or(false, |s| RE_FASTA.is_match(s))
}

static RE_FASTQ: LazyLock<Regex> = LazyLock::new(|| Regex::new(r"^@[^ ].*\n[A-Za-z.-]+").unwrap());
fn is_fastq(buf: &[u8]) -> bool {
    std::str::from_utf8(buf).map_or(false, |s| RE_FASTQ.is_match(s))
}

fn is_text(buf: &[u8]) -> bool {
    !buf.is_empty()
        && buf
            .iter()
            .all(|&b| b.is_ascii() && (b >= 0x20 || matches!(b, b'\t' | b'\n' | b'\r')))
}

// Custom matchers are checked in insertion order (first added = first checked).
// Most specific formats (fastq, fasta) come before the generic text/plain fallback.
static INFER: LazyLock<Infer> = LazyLock::new(|| {
    let mut infer = Infer::new();
    infer.add("text/gbff",  "gbff", is_gbff);
    infer.add("text/fastq", "fastq", is_fastq);
    infer.add("text/fasta", "fasta", is_fasta);
    infer.add("text/plain", "txt", is_text);
    infer
});

/// A struct that guesses the MIME type of a reader by peeking at the first few bytes.
pub struct MimeTypeGuesser<R: std::io::Read>(PeekReader<R>);

impl<R: std::io::Read> MimeTypeGuesser<R> {
    /// Creates a new MimeTypeGuesser that wraps the given reader.
    pub fn new(reader: R) -> Self {
        Self(PeekReader::new(reader, BUF_SIZE))
    }

    /// Returns the detected MIME type, if any.
    pub fn mime_type(&mut self) -> Option<&'static str> {
        let buf = self.0.header();
        INFER.get(buf).map(|kind| kind.mime_type())
    }
}

impl<R: std::io::Read> io::Read for MimeTypeGuesser<R> {
    fn read(&mut self, out: &mut [u8]) -> io::Result<usize> {
        self.0.read(out)
    }
}
