use sim_kernel::{CapabilityName, Cx, Error, Expr, Result, Symbol};
use crate::contract::{Lens, LensKind};
pub struct DispatchContext<'a> {
pub explicit: Option<Symbol>,
pub preference: Option<Symbol>,
pub active_mode: Option<Symbol>,
pub value_class: Option<Symbol>,
pub granted: &'a dyn Fn(&CapabilityName) -> bool,
}
impl<'a> DispatchContext<'a> {
pub fn permissive(grant_all: &'a dyn Fn(&CapabilityName) -> bool) -> Self {
Self {
explicit: None,
preference: None,
active_mode: None,
value_class: None,
granted: grant_all,
}
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum DispatchReason {
Explicit,
Preference,
ShapeMatch(i32),
ClassMatch,
UniversalDefault,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct DispatchOutcome {
pub lens_id: Symbol,
pub reason: DispatchReason,
}
#[derive(Default)]
pub struct LensRegistry {
lenses: Vec<Lens>,
}
impl LensRegistry {
pub fn new() -> Self {
Self::default()
}
pub fn register(&mut self, lens: Lens) {
self.lenses.push(lens);
}
pub fn get(&self, id: &Symbol) -> Option<&Lens> {
self.lenses.iter().find(|lens| &lens.meta.id == id)
}
pub fn lenses(&self) -> &[Lens] {
&self.lenses
}
pub fn dispatch_view(
&self,
cx: &mut Cx,
target: &Expr,
ctx: &DispatchContext,
) -> Result<DispatchOutcome> {
self.dispatch(cx, LensKind::View, target, ctx)
}
pub fn dispatch_editor(
&self,
cx: &mut Cx,
target: &Expr,
ctx: &DispatchContext,
) -> Result<DispatchOutcome> {
self.dispatch(cx, LensKind::Editor, target, ctx)
}
pub fn dispatch(
&self,
cx: &mut Cx,
kind: LensKind,
target: &Expr,
ctx: &DispatchContext,
) -> Result<DispatchOutcome> {
if let Some(outcome) = self.pick_named(&ctx.explicit, kind, ctx, DispatchReason::Explicit) {
return Ok(outcome);
}
if let Some(outcome) =
self.pick_named(&ctx.preference, kind, ctx, DispatchReason::Preference)
{
return Ok(outcome);
}
if let Some(outcome) = self.pick_shape_match(cx, kind, target, ctx)? {
return Ok(outcome);
}
if let Some(outcome) = self.pick_class_match(kind, ctx) {
return Ok(outcome);
}
if let Some(lens) = self.lenses.iter().find(|lens| {
lens.meta.kind == kind && lens.meta.universal_default && self.allowed(lens, ctx)
}) {
return Ok(DispatchOutcome {
lens_id: lens.meta.id.clone(),
reason: DispatchReason::UniversalDefault,
});
}
Err(Error::HostError(format!(
"no {kind:?} lens available for the value (not even a universal default)"
)))
}
pub(crate) fn allowed(&self, lens: &Lens, ctx: &DispatchContext) -> bool {
lens.meta
.required_capabilities
.iter()
.all(|capability| (ctx.granted)(capability))
}
fn pick_named(
&self,
id: &Option<Symbol>,
kind: LensKind,
ctx: &DispatchContext,
reason: DispatchReason,
) -> Option<DispatchOutcome> {
let id = id.as_ref()?;
let lens = self.get(id)?;
if lens.meta.kind == kind && self.allowed(lens, ctx) {
Some(DispatchOutcome {
lens_id: id.clone(),
reason,
})
} else {
None
}
}
fn pick_shape_match(
&self,
cx: &mut Cx,
kind: LensKind,
target: &Expr,
ctx: &DispatchContext,
) -> Result<Option<DispatchOutcome>> {
let mut best: Option<(i32, i32, i32, Symbol)> = None;
for lens in &self.lenses {
if lens.meta.kind != kind || lens.meta.universal_default || !self.allowed(lens, ctx) {
continue;
}
let Some(score) = best_shape_score(cx, lens, target)? else {
continue;
};
let candidate = (score, lens.meta.quality, -lens.meta.cost);
let better = match &best {
Some((bs, bq, bc, _)) => candidate > (*bs, *bq, *bc),
None => true,
};
if better {
best = Some((candidate.0, candidate.1, candidate.2, lens.meta.id.clone()));
}
}
Ok(best.map(|(score, _, _, lens_id)| DispatchOutcome {
lens_id,
reason: DispatchReason::ShapeMatch(score),
}))
}
fn pick_class_match(&self, kind: LensKind, ctx: &DispatchContext) -> Option<DispatchOutcome> {
let class = ctx.value_class.as_ref()?;
let mut best: Option<(i32, i32, Symbol)> = None;
for lens in &self.lenses {
if lens.meta.kind != kind
|| lens.meta.universal_default
|| !self.allowed(lens, ctx)
|| !lens.meta.claimed_classes.contains(class)
{
continue;
}
let candidate = (lens.meta.quality, -lens.meta.cost);
let better = match &best {
Some((bq, bc, _)) => candidate > (*bq, *bc),
None => true,
};
if better {
best = Some((candidate.0, candidate.1, lens.meta.id.clone()));
}
}
best.map(|(_, _, lens_id)| DispatchOutcome {
lens_id,
reason: DispatchReason::ClassMatch,
})
}
}
pub(crate) fn best_shape_score(cx: &mut Cx, lens: &Lens, target: &Expr) -> Result<Option<i32>> {
let mut best: Option<i32> = None;
for shape_value in &lens.meta.claimed_shapes {
let Some(shape) = shape_value.object().as_shape() else {
continue;
};
let matched = shape.check_expr(cx, target)?;
if matched.accepted {
let score = matched.score.value();
best = Some(best.map_or(score, |current| current.max(score)));
}
}
Ok(best)
}