1
use std::cell::RefCell;
2
use std::rc::Rc;
3
use wasm_bindgen::JsCast;
4
use web_sys::{Element, HtmlInputElement};
5

            
6
use super::account::get_input_by_id;
7
use super::component::{Autocomplete, AutocompleteConfig, attach_autocomplete};
8
use crate::data_attrs::{AutocompleteAttr, selectors};
9
use crate::types::CommoditySuggestion;
10

            
11
pub struct CommodityAutocomplete;
12

            
13
impl CommodityAutocomplete {
14
    pub fn attach(wrapper: Element) -> Option<()> {
15
        let document = web_sys::window()?.document()?;
16

            
17
        let fetch_url = wrapper
18
            .get_attribute(AutocompleteAttr::FetchUrl.as_str())
19
            .unwrap_or_else(|| "/api/commodity/search".to_string());
20
        let display_input_id = wrapper.get_attribute(AutocompleteAttr::DisplayInput.as_str())?;
21
        let hidden_input_id = wrapper.get_attribute(AutocompleteAttr::HiddenInput.as_str())?;
22
        let currency_input_id = wrapper.get_attribute(AutocompleteAttr::CurrencyInput.as_str());
23

            
24
        let display_input = get_input_by_id(&document, &display_input_id)?;
25
        let hidden_input = get_input_by_id(&document, &hidden_input_id)?;
26
        let currency_input = currency_input_id.and_then(|id| get_input_by_id(&document, &id));
27

            
28
        let container = wrapper
29
            .query_selector(selectors::RESULTS_CONTAINER)
30
            .ok()??;
31

            
32
        let autocomplete = Autocomplete::<CommoditySuggestion>::new(
33
            display_input.clone(),
34
            hidden_input,
35
            currency_input,
36
            container,
37
        );
38

            
39
        let mut config = AutocompleteConfig::post(fetch_url, commodity_body_builder);
40

            
41
        // Auto-fill "to" commodity when "from" commodity is selected
42
        if display_input
43
            .get_attribute("data-field")
44
            .is_some_and(|f| f == "from-commodity")
45
        {
46
            config = config.with_on_select(Rc::new(create_autofill_callback(display_input)));
47
        }
48

            
49
        let ac = Rc::new(RefCell::new(autocomplete));
50
        attach_autocomplete(ac, config);
51

            
52
        Some(())
53
    }
54
}
55

            
56
fn create_autofill_callback(
57
    from_input: HtmlInputElement,
58
) -> impl Fn(&HtmlInputElement, &str, &str) {
59
    move |_input, id, _secondary| {
60
        let Some(split_entry) = from_input.closest(".split-entry").ok().flatten() else {
61
            return;
62
        };
63

            
64
        let to_display: Option<HtmlInputElement> = split_entry
65
            .query_selector(r#".commodity-display[data-field="to-commodity"]"#)
66
            .ok()
67
            .flatten()
68
            .and_then(|el| el.dyn_into().ok());
69

            
70
        let to_hidden: Option<HtmlInputElement> = split_entry
71
            .query_selector(r#".commodity-value[data-field="to-commodity"]"#)
72
            .ok()
73
            .flatten()
74
            .and_then(|el| el.dyn_into().ok());
75

            
76
        if let Some(to_display) = to_display
77
            && to_display.value().is_empty()
78
        {
79
            to_display.set_value(&from_input.value());
80

            
81
            if let Some(to_hidden) = to_hidden {
82
                to_hidden.set_value(id);
83
                if let Ok(event) = web_sys::Event::new("change") {
84
                    let _ = to_hidden.dispatch_event(&event);
85
                }
86
            }
87
        }
88
    }
89
}
90

            
91
#[must_use]
92
2
pub fn commodity_body_builder(query: &str) -> String {
93
2
    format!(r#"{{"commodity-search":"{query}"}}"#)
94
2
}
95

            
96
#[cfg(test)]
97
mod tests {
98
    use super::*;
99

            
100
    #[test]
101
1
    fn body_builder_formats_correctly() {
102
1
        assert_eq!(
103
1
            commodity_body_builder("USD"),
104
            r#"{"commodity-search":"USD"}"#
105
        );
106
1
    }
107

            
108
    #[test]
109
1
    fn body_builder_handles_empty_query() {
110
1
        assert_eq!(commodity_body_builder(""), r#"{"commodity-search":""}"#);
111
1
    }
112
}