use std::collections::{HashMap, HashSet};
use gents_derives::TS;
use logisheets_base::{Addr, CellId, CellValue, SheetId, TextId, errors::BasicError};
use crate::{
calc_engine::calculator::{calc_vertex::CalcValue, calc_vertex::Value, calculator::calc},
cell::Cell,
connectors::CalcConnector,
errors::Error,
sid_assigner::ShadowKind,
};
use super::Workbook;
#[derive(Debug, Clone, Default, TS)]
#[ts(file_name = "field_validation_verdict.ts", rename_all = "camelCase")]
pub struct FieldValidationVerdict {
pub has_rule: bool,
pub violates: bool,
pub rule: String,
}
fn scalar_violates(v: &Value) -> bool {
match v {
Value::Boolean(b) => !*b,
Value::Number(n) => *n == 0.0,
Value::Error(_) => true,
Value::Blank => false,
Value::Text(_) => false,
}
}
fn violates(value: &CalcValue) -> bool {
match value {
CalcValue::Scalar(v) => scalar_violates(v),
CalcValue::Range(m) => match m.visit(0, 0) {
Ok(v) => scalar_violates(v),
Err(v) => scalar_violates(&v),
},
CalcValue::Cube(_) => true,
CalcValue::Union(parts) => parts.first().map(|p| violates(p)).unwrap_or(false),
}
}
impl Workbook {
pub fn check_field_validation(
&mut self,
sheet_idx: usize,
row: usize,
col: usize,
proposed: String,
) -> std::result::Result<FieldValidationVerdict, Error> {
let sheet_id = self
.controller
.status
.sheet_info_manager
.get_sheet_id(sheet_idx)
.ok_or(BasicError::SheetIdxExceed(sheet_idx))?;
let cell_id = self
.controller
.status
.navigator
.fetch_cell_id(&sheet_id, row, col)?;
let CellId::BlockCell(bcid) = cell_id else {
return Ok(FieldValidationVerdict::default());
};
let place = self
.controller
.status
.navigator
.get_block_place(&sheet_id, &bcid.block_id)
.ok();
let analysis = place.and_then(|bp| {
Some(crate::block_manager::analysis::AnalysisTarget {
source: bp.analyzes?,
pivot: bp.pivot.as_ref(),
})
});
let Some(rule) = crate::block_manager::derived_rules::effective_validation(
&self.controller.status.block_schema_manager,
&self.controller.status.enum_set_manager,
sheet_id,
&bcid,
analysis,
) else {
return Ok(FieldValidationVerdict::default());
};
let unknown = FieldValidationVerdict {
has_rule: true,
violates: false,
rule: rule.clone(),
};
let Some(eid) =
self.controller
.sid_assigner
.find_shadow_id(sheet_id, cell_id, ShadowKind::Validation)
else {
return Ok(unknown);
};
let Some(ast) = self
.controller
.status
.formula_manager
.formulas
.get(&(sheet_id, CellId::EphemeralCell(eid)))
.cloned()
else {
return Ok(unknown);
};
let violated = self.eval_against(sheet_id, cell_id, proposed, &ast);
Ok(FieldValidationVerdict {
has_rule: true,
violates: violated,
rule,
})
}
fn eval_against(
&mut self,
sheet_id: SheetId,
cell_id: CellId,
proposed: String,
ast: &logisheets_parser::ast::Node,
) -> bool {
let status = &mut self.controller.status;
let proposed_value = {
let text_ids = &mut status.text_id_manager;
CellValue::from_string(proposed, &mut |t| -> TextId {
text_ids.get_or_register_id(t)
})
};
let saved: Option<Cell> = status.container.get_cell(sheet_id, &cell_id).cloned();
status
.container
.update_value(sheet_id, cell_id, proposed_value);
let (row, col) = status
.navigator
.fetch_cell_idx(&sheet_id, &cell_id)
.unwrap_or((0, 0));
let mut dirty_next: imbl::HashSet<(SheetId, CellId)> = imbl::HashSet::new();
let mut calc_cells: HashSet<(SheetId, CellId)> = HashSet::new();
let mut async_func_manager = crate::async_func_manager::AsyncFuncManager::default();
let async_funcs: HashSet<String> = HashSet::new();
let value = {
let mut connector = CalcConnector {
range_manager: &status.range_manager,
cube_manager: &status.cube_manager,
navigator: &mut status.navigator,
container: &mut status.container,
ext_links: &mut status.external_links_manager,
text_id_manager: &mut status.text_id_manager,
func_id_manager: &status.func_id_manager,
sheet_id_manager: &status.sheet_id_manager,
names_storage: HashMap::new(),
cells_storage: HashMap::new(),
sheet_pos_manager: &status.sheet_info_manager,
async_func_manager: &mut async_func_manager,
async_funcs: &async_funcs,
active_sheet: sheet_id,
curr_addr: Addr { row, col },
dirty_cells_in_next_run: &mut dirty_next,
calc_cells: &mut calc_cells,
block_schema_manager: &status.block_schema_manager,
formula_manager: &status.formula_manager,
name_id_manager: &status.name_id_manager,
ext_ref_manager: &status.ext_ref_manager,
};
calc(ast, &mut connector)
};
match saved {
Some(cell) => status.container.add_cell(sheet_id, cell_id, cell),
None => {
status.container.remove_cell(sheet_id, &cell_id);
}
}
violates(&value)
}
}