use indexmap::IndexMap;
use oino_ai::{Model, ThinkingLevel};
use super::defaults::DEFAULT_THINKING_LEVEL;
use super::initial::model_thinking_level_key;
use super::scope::ScopedModel;
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub enum CycleDirection {
#[default]
Forward,
Backward,
}
fn same_model(a: &Model, b: &Model) -> bool {
a.id == b.id && a.provider == b.provider
}
fn step(len: usize, current: Option<usize>, direction: CycleDirection) -> usize {
let current = current.unwrap_or(0);
match direction {
CycleDirection::Forward => (current + 1) % len,
CycleDirection::Backward => (current + len - 1) % len,
}
}
#[must_use]
pub fn next_scoped_model(
scoped: &[ScopedModel],
available: &[Model],
current: Option<&Model>,
direction: CycleDirection,
) -> Option<ScopedModel> {
let candidates: Vec<&ScopedModel> = scoped
.iter()
.filter(|s| available.iter().any(|m| same_model(m, &s.model)))
.collect();
if candidates.len() <= 1 {
return None;
}
let index = current.and_then(|c| candidates.iter().position(|s| same_model(&s.model, c)));
Some(candidates[step(candidates.len(), index, direction)].clone())
}
#[must_use]
pub fn next_available_model(
available: &[Model],
current: Option<&Model>,
direction: CycleDirection,
) -> Option<Model> {
if available.len() <= 1 {
return None;
}
let index = current.and_then(|c| available.iter().position(|m| same_model(m, c)));
Some(available[step(available.len(), index, direction)].clone())
}
#[derive(Clone, Copy, Debug, Default)]
pub struct ThinkingSwitch<'a> {
pub explicit: Option<ThinkingLevel>,
pub model_thinking_levels: Option<&'a IndexMap<String, ThinkingLevel>>,
pub default_thinking_level: Option<ThinkingLevel>,
pub current: Option<ThinkingLevel>,
}
#[must_use]
pub fn thinking_level_for_model_switch(
target: Option<&Model>,
inputs: &ThinkingSwitch<'_>,
) -> ThinkingLevel {
if let Some(explicit) = inputs.explicit {
return explicit;
}
if let Some(level) = target.and_then(|model| {
inputs
.model_thinking_levels?
.get(&model_thinking_level_key(&model.provider, &model.id))
.copied()
}) {
return level;
}
inputs
.default_thinking_level
.or(inputs.current)
.unwrap_or(DEFAULT_THINKING_LEVEL)
}