use anyhow::Result;
use kbnf::{
    engine_like::AcceptTokenError, AcceptTokenResult, Engine, EngineLike, Token, Vocabulary,
};
use web_rwkv::tokenizer::Tokenizer;

use super::Formatter;

#[derive(Debug)]
pub struct BnfSampler(Engine);

impl BnfSampler {
    pub fn new(tokenizer: &Tokenizer, schema: &str) -> Result<Self> {
        let tokens = tokenizer
            .token_index_to_bytes()
            .iter()
            .enumerate()
            .filter(|(_, v)| !v.is_empty())
            .map(|(k, v)| (k as u32, Token(v.clone().into_boxed_slice())))
            .collect();
        let strings = tokenizer
            .token_index_to_bytes()
            .iter()
            .enumerate()
            .filter(|(_, v)| !v.is_empty())
            .map(|(k, v)| (k as u32, String::from_utf8_lossy(v).to_string()))
            .collect();
        let vocab = Vocabulary::new(tokens, strings)?;
        let engine = Engine::new(schema, vocab)?;
        Ok(Self(engine))
    }
}

impl Formatter for BnfSampler {
    fn transform(&self, output: &mut [f32]) {
        let output = &mut output[..self.0.vocab().vocab_size()];
        self.0.mask_logits(output).expect("bnf transform error")
    }

    fn update(&mut self, token: u16) -> bool {
        let halt = match self.0.try_accept_new_token(token as u32) {
            Ok(AcceptTokenResult::Finished) | Err(AcceptTokenError::Finished) => true,
            Ok(AcceptTokenResult::Ongoing) => false,
            Err(_) => self.0.is_finished(),
        };
        self.0.compute_allowed_token_ids();
        halt
    }
}
