1use alloc::vec;
4use alloc::vec::Vec;
5
6use brink_format::{DefinitionId, LineEntry, LocaleData};
7
8use crate::collections::Map as HashMap;
9use crate::error::RuntimeError;
10use crate::program::Program;
11
12#[derive(Debug, Clone, Copy, PartialEq, Eq)]
14pub enum LocaleMode {
15 Strict,
18 Overlay,
20}
21
22pub fn apply_locale(
28 program: &Program,
29 locale: &LocaleData,
30 base: &[Vec<LineEntry>],
31 mode: LocaleMode,
32) -> Result<Vec<Vec<LineEntry>>, RuntimeError> {
33 if locale.base_checksum != program.source_checksum {
34 return Err(RuntimeError::LocaleChecksumMismatch {
35 expected: program.source_checksum,
36 actual: locale.base_checksum,
37 });
38 }
39
40 let scope_idx_map: HashMap<DefinitionId, usize> = program
42 .scope_ids
43 .iter()
44 .enumerate()
45 .map(|(i, &id)| (id, i))
46 .collect();
47
48 let mut result = base.to_vec();
50 let mut covered = vec![false; program.scope_ids.len()];
51
52 for locale_scope in &locale.line_tables {
53 let Some(&idx) = scope_idx_map.get(&locale_scope.scope_id) else {
54 return Err(RuntimeError::LocaleScopeNotInBase(locale_scope.scope_id));
55 };
56
57 let entries: Vec<LineEntry> = locale_scope
59 .lines
60 .iter()
61 .map(|le| {
62 let flags = brink_format::LineFlags::from_content(&le.content);
63 LineEntry {
64 content: le.content.clone(),
65 flags,
66 source_hash: 0,
67 audio_ref: le.audio_ref.clone(),
68 slot_info: Vec::new(),
69 source_location: None,
70 }
71 })
72 .collect();
73
74 result[idx] = entries;
75 covered[idx] = true;
76 }
77
78 if matches!(mode, LocaleMode::Strict) {
79 for (i, was_covered) in covered.iter().enumerate() {
80 if !was_covered {
81 return Err(RuntimeError::LocaleScopeMissing(program.scope_ids[i]));
82 }
83 }
84 }
85
86 Ok(result)
87}