1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
use crate::constants::SKIP_EXTRA_PROP;
use crate::models::common::{
    eq_update, resource_update_with_vector_content, Loadable, ResourceAction, ResourceLoadable,
};
use crate::models::ctx::Ctx;
use crate::runtime::msg::{Action, ActionCatalogsWithExtra, ActionLoad, Internal, Msg};
use crate::runtime::{EffectFuture, Effects, Env, EnvFutureExt, UpdateWithCtx};
use crate::types::addon::{AggrRequest, ExtraExt, ExtraValue, ResourcePath, ResourceRequest};
use crate::types::profile::Profile;
use crate::types::resource::MetaItemPreview;
use futures::FutureExt;
use serde::{Deserialize, Serialize};
use std::ops::Range;

#[derive(Clone, PartialEq, Eq, Serialize, Deserialize, Debug)]
pub struct Selected {
    pub r#type: Option<String>,
    #[serde(default)]
    pub extra: Vec<ExtraValue>,
}

pub type CatalogPage<T> = ResourceLoadable<Vec<T>>;

pub type Catalog<T> = Vec<CatalogPage<T>>;

#[derive(Default, Clone, Serialize, Debug)]
pub struct CatalogsWithExtra {
    pub selected: Option<Selected>,
    pub catalogs: Vec<Catalog<MetaItemPreview>>,
}

impl<E: Env + 'static> UpdateWithCtx<E> for CatalogsWithExtra {
    fn update(&mut self, msg: &Msg, ctx: &Ctx) -> Effects {
        match msg {
            Msg::Action(Action::Load(ActionLoad::CatalogsWithExtra(selected))) => {
                let selected_effects = selected_update(&mut self.selected, selected);
                let catalogs_effects =
                    catalogs_update::<E>(&mut self.catalogs, &self.selected, None, &ctx.profile);
                let search_effects = match &self.selected {
                    Some(Selected { extra, .. }) => match extra
                        .iter()
                        .find(|ExtraValue { name, .. }| name == "search")
                    {
                        Some(ExtraValue { value, .. }) => {
                            Effects::msg(Msg::Internal(Internal::CatalogsWithExtraSearch {
                                query: value.to_owned(),
                            }))
                            .unchanged()
                        }
                        None => Effects::none().unchanged(),
                    },
                    None => Effects::none().unchanged(),
                };
                selected_effects.join(catalogs_effects).join(search_effects)
            }
            Msg::Action(Action::Unload) => {
                let selected_effects = eq_update(&mut self.selected, None);
                let catalogs_effects = eq_update(&mut self.catalogs, vec![]);
                selected_effects.join(catalogs_effects)
            }
            Msg::Action(Action::CatalogsWithExtra(ActionCatalogsWithExtra::LoadRange(range))) => {
                catalogs_update::<E>(
                    &mut self.catalogs,
                    &self.selected,
                    Some(range),
                    &ctx.profile,
                )
            }
            Msg::Action(Action::CatalogsWithExtra(ActionCatalogsWithExtra::LoadNextPage(
                index,
            ))) => match self.catalogs.get_mut(*index) {
                Some(catalog) => match catalog.last() {
                    Some(ResourceLoadable {
                        content: Some(Loadable::Ready(items)),
                        request,
                    }) if ctx
                        .profile
                        .addons
                        .iter()
                        .find(|addon| addon.transport_url == request.base)
                        .and_then(|addon| {
                            addon.manifest.catalogs.iter().find(|manifest_catalog| {
                                manifest_catalog.id == request.path.id
                                    && manifest_catalog.r#type == request.path.r#type
                            })
                        })
                        .map(|manifest_catalog| {
                            manifest_catalog
                                .extra
                                .iter()
                                .any(|extra_prop| extra_prop.name == SKIP_EXTRA_PROP.name)
                        })
                        .unwrap_or_default() =>
                    {
                        let skip = request
                            .path
                            .extra
                            .iter()
                            .find(|extra_prop| extra_prop.name == SKIP_EXTRA_PROP.name)
                            .and_then(|extra_prop| extra_prop.value.parse::<usize>().ok())
                            .unwrap_or_default();
                        let skip = skip + items.len();
                        let request = ResourceRequest {
                            base: request.base.to_owned(),
                            path: ResourcePath {
                                id: request.path.id.to_owned(),
                                r#type: request.path.r#type.to_owned(),
                                resource: request.path.resource.to_owned(),
                                extra: request
                                    .path
                                    .extra
                                    .to_owned()
                                    .extend_one(&SKIP_EXTRA_PROP, Some(skip.to_string())),
                            },
                        };
                        catalog.push(ResourceLoadable {
                            request: request.to_owned(),
                            content: Some(Loadable::Loading),
                        });
                        Effects::one(
                            EffectFuture::Concurrent(
                                E::addon_transport(&request.base)
                                    .resource(&request.path)
                                    .map(|result| {
                                        Msg::Internal(Internal::ResourceRequestResult(
                                            request,
                                            Box::new(result),
                                        ))
                                    })
                                    .boxed_env(),
                            )
                            .into(),
                        )
                    }
                    _ => Effects::none().unchanged(),
                },
                _ => Effects::none().unchanged(),
            },
            Msg::Internal(Internal::ResourceRequestResult(request, result)) => self
                .catalogs
                .iter_mut()
                .find_map(|catalog| catalog.last_mut().filter(|page| page.request == *request))
                .map(|page| {
                    resource_update_with_vector_content::<E, _>(
                        page,
                        ResourceAction::ResourceRequestResult { request, result },
                    )
                })
                .unwrap_or_else(|| Effects::none().unchanged()),
            Msg::Internal(Internal::ProfileChanged) => {
                catalogs_update::<E>(&mut self.catalogs, &self.selected, None, &ctx.profile)
            }
            Msg::Internal(Internal::LibraryChanged(_)) => Effects::none(),
            _ => Effects::none().unchanged(),
        }
    }
}

fn selected_update(selected: &mut Option<Selected>, next_selected: &Selected) -> Effects {
    let mut next_selected = next_selected.to_owned();
    next_selected.extra = next_selected.extra.remove_all(&SKIP_EXTRA_PROP);
    eq_update(selected, Some(next_selected))
}

fn catalogs_update<E: Env + 'static>(
    catalogs: &mut Vec<Catalog<MetaItemPreview>>,
    selected: &Option<Selected>,
    range: Option<&Range<usize>>,
    profile: &Profile,
) -> Effects {
    let (next_catalogs, effects) = match selected {
        Some(selected) => {
            let request = AggrRequest::AllCatalogs {
                extra: &selected.extra,
                r#type: &selected.r#type,
            };
            request
                .plan(&profile.addons)
                .into_iter()
                .map(|(_, request)| request)
                .enumerate()
                .map(|(index, request)| {
                    catalogs
                        .iter()
                        .find(|catalog| {
                            matches!(catalog.first(), Some(resource) if resource.request == request && resource.content.is_some())
                        })
                        .map(|catalog| (catalog.to_owned(), None))
                        .unwrap_or_else(|| match range {
                            Some(range) if range.start <= index && index <= range.end => (
                                vec![ResourceLoadable {
                                    request: request.to_owned(),
                                    content: Some(Loadable::Loading),
                                }],
                                Some(
                                    EffectFuture::Concurrent(
                                        E::addon_transport(&request.base)
                                            .resource(&request.path)
                                            .map(|result| {
                                                Msg::Internal(Internal::ResourceRequestResult(
                                                    request,
                                                    Box::new(result),
                                                ))
                                            })
                                            .boxed_env(),
                                    )
                                    .into(),
                                ),
                            ),
                            _ => (
                                vec![ResourceLoadable {
                                    request,
                                    content: None,
                                }],
                                None,
                            ),
                        })
                })
                .unzip::<_, _, Vec<_>, Vec<_>>()
        }
        _ => Default::default(),
    };
    Effects::many(effects.into_iter().flatten().collect())
        .unchanged()
        .join(eq_update(catalogs, next_catalogs))
}