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
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
use std::{cell::RefCell, rc::Rc};

use floating_ui_utils::dom::{
    get_document_element, get_overflow_ancestors, get_window, OverflowAncestor,
};
use web_sys::{
    wasm_bindgen::{closure::Closure, JsCast, JsValue},
    window, AddEventListenerOptions, Element, EventTarget, IntersectionObserver,
    IntersectionObserverEntry, IntersectionObserverInit, ResizeObserver, ResizeObserverEntry,
};

use crate::{
    types::{ElementOrVirtual, OwnedElementOrVirtual},
    utils::get_bounding_client_rect::get_bounding_client_rect,
};

fn request_animation_frame(callback: &Closure<dyn FnMut()>) -> i32 {
    window()
        .expect("Window should exist.")
        .request_animation_frame(callback.as_ref().unchecked_ref())
        .expect("Request animation frame should be successful.")
}

fn cancel_animation_frame(handle: i32) {
    window()
        .expect("Window should exist.")
        .cancel_animation_frame(handle)
        .expect("Cancel animation frame should be successful.")
}

fn observe_move(element: Element, on_move: Rc<dyn Fn()>) -> Box<dyn Fn()> {
    let io: Rc<RefCell<Option<IntersectionObserver>>> = Rc::new(RefCell::new(None));
    let timeout_id: Rc<RefCell<Option<i32>>> = Rc::new(RefCell::new(None));

    let window = get_window(Some(&element));
    let root = get_document_element(Some((&element).into()));

    type ObserveClosure = Closure<dyn Fn(Vec<IntersectionObserverEntry>)>;
    let observe_closure: Rc<RefCell<Option<ObserveClosure>>> = Rc::new(RefCell::new(None));

    let cleanup_io = io.clone();
    let cleanup_timeout_id = timeout_id.clone();
    let cleanup_window = window.clone();
    let cleanup_observe_closure = observe_closure.clone();
    let cleanup = move || {
        if let Some(timeout_id) = cleanup_timeout_id.take() {
            cleanup_window.clear_timeout_with_handle(timeout_id);
        }

        if let Some(io) = cleanup_io.take() {
            io.disconnect();
        }

        _ = cleanup_observe_closure.take();
    };
    let cleanup_rc = Rc::new(cleanup);
    type RefreshFn = Box<dyn Fn(bool, f64)>;
    let refresh_closure: Rc<RefCell<Option<RefreshFn>>> = Rc::new(RefCell::new(None));
    let refresh_closure_clone = refresh_closure.clone();

    let refresh_cleanup = cleanup_rc.clone();
    *refresh_closure_clone.borrow_mut() = Some(Box::new(move |skip: bool, threshold: f64| {
        refresh_cleanup();

        let rect = element.get_bounding_client_rect();

        if !skip {
            on_move();
        }

        if rect.width() == 0.0 || rect.height() == 0.0 {
            return;
        }

        let inset_top = rect.top().floor();
        let inset_right = (root.client_width() as f64 - (rect.left() + rect.width())).floor();
        let inset_bottom = (root.client_height() as f64 - (rect.top() + rect.height())).floor();
        let inset_left = rect.left().floor();
        let root_margin = format!(
            "{}px {}px {}px {}px",
            -inset_top, -inset_right, -inset_bottom, -inset_left
        );

        let is_first_update: Rc<RefCell<bool>> = Rc::new(RefCell::new(true));

        let timeout_refresh = refresh_closure.clone();
        let timeout_closure: Rc<Closure<dyn Fn()>> = Rc::new(Closure::new(move || {
            timeout_refresh
                .borrow()
                .as_ref()
                .expect("Refresh closure should exist.")(false, 1e-7)
        }));

        let observe_timeout_id = timeout_id.clone();
        let observe_window = window.clone();
        let observe_refresh = refresh_closure.clone();
        let local_observe_closure = Closure::new(move |entries: Vec<IntersectionObserverEntry>| {
            let ratio = entries[0].intersection_ratio();

            if ratio != threshold {
                if !*is_first_update.borrow() {
                    observe_refresh
                        .borrow()
                        .as_ref()
                        .expect("Refresh closure should exist.")(false, 1.0);
                    return;
                }

                if ratio == 0.0 {
                    observe_timeout_id.replace(Some(
                        observe_window
                            .set_timeout_with_callback_and_timeout_and_arguments_0(
                                (*timeout_closure).as_ref().unchecked_ref(),
                                100,
                            )
                            .expect("Set timeout should be successful."),
                    ));
                } else {
                    observe_refresh
                        .borrow()
                        .as_ref()
                        .expect("Refresh closure should exist.")(false, ratio);
                }

                is_first_update.replace(false);
            }
        });

        let local_io = IntersectionObserver::new_with_options(
            local_observe_closure.as_ref().unchecked_ref(),
            IntersectionObserverInit::new()
                .root_margin(&root_margin)
                .threshold(&JsValue::from_f64(threshold.clamp(0.0, 1.0))),
        )
        .expect("Intersection observer should be created.");

        observe_closure.replace(Some(local_observe_closure));

        local_io.observe(&element);
        io.replace(Some(local_io));
    }));

    refresh_closure_clone
        .borrow()
        .as_ref()
        .expect("Refresh closure should exist.")(true, 1.0);

    Box::new(move || {
        cleanup_rc();
    })
}

/// Options for [`auto_update`].
#[derive(Clone, Debug, Default)]
pub struct AutoUpdateOptions {
    /// Whether to update the position when an overflow ancestor is scrolled.
    ///
    /// Defaults to `true`.
    pub ancestor_scroll: Option<bool>,

    /// Whether to update the position when an overflow ancestor is resized. This uses the native `resize` event.
    ///
    /// Defaults to `true`.
    pub ancestor_resize: Option<bool>,

    /// Whether to update the position when either the reference or floating elements resized. This uses a `ResizeObserver`.
    ///
    /// Defaults to `true`.
    pub element_resize: Option<bool>,

    /// Whether to update the position when the reference relocated on the screen due to layout shift.
    ///
    /// Defaults to `true`.
    pub layout_shift: Option<bool>,

    /// Whether to update on every animation frame if necessary.
    /// Only use if you need to update the position in response to an animation using transforms.
    ///
    /// Defaults to `false`.
    pub animation_frame: Option<bool>,
}

impl AutoUpdateOptions {
    /// Set `ancestor_scroll` option.
    pub fn ancestor_scroll(mut self, value: bool) -> Self {
        self.ancestor_scroll = Some(value);
        self
    }

    /// Set `ancestor_resize` option.
    pub fn ancestor_resize(mut self, value: bool) -> Self {
        self.ancestor_resize = Some(value);
        self
    }

    /// Set `element_resize` option.
    pub fn element_resize(mut self, value: bool) -> Self {
        self.element_resize = Some(value);
        self
    }

    /// Set `layout_shift` option.
    pub fn layout_shift(mut self, value: bool) -> Self {
        self.layout_shift = Some(value);
        self
    }

    /// Set `animation_frame` option.
    pub fn animation_frame(mut self, value: bool) -> Self {
        self.animation_frame = Some(value);
        self
    }
}

/// Automatically updates the position of the floating element when necessary.
/// Should only be called when the floating element is mounted on the DOM or visible on the screen.
pub fn auto_update(
    reference: ElementOrVirtual,
    floating: &Element,
    update: Rc<dyn Fn()>,
    options: AutoUpdateOptions,
) -> Box<dyn Fn()> {
    let ancestor_scoll = options.ancestor_scroll.unwrap_or(true);
    let ancestor_resize = options.ancestor_resize.unwrap_or(true);
    let element_resize = options.element_resize.unwrap_or(true);
    let layout_shift = options.layout_shift.unwrap_or(true);
    let animation_frame = options.animation_frame.unwrap_or(false);

    let reference_element = reference.clone().resolve();

    let owned_reference = match reference.clone() {
        ElementOrVirtual::Element(e) => OwnedElementOrVirtual::Element(e.clone()),
        ElementOrVirtual::VirtualElement(ve) => OwnedElementOrVirtual::VirtualElement(ve.clone()),
    };

    let ancestors = match ancestor_scoll || ancestor_resize {
        true => {
            let mut ancestors = vec![];

            if let Some(reference) = reference_element.as_ref() {
                ancestors = get_overflow_ancestors(reference, ancestors, true);
            }

            ancestors.append(&mut get_overflow_ancestors(floating, vec![], true));

            ancestors
        }
        false => vec![],
    };

    let update_closure_update = update.clone();
    let update_closure: Closure<dyn Fn()> = Closure::new(move || {
        update_closure_update();
    });

    for ancestor in &ancestors {
        let event_target: &EventTarget = match ancestor {
            OverflowAncestor::Element(element) => element,
            OverflowAncestor::Window(window) => window,
        };

        if ancestor_scoll {
            _ = event_target.add_event_listener_with_callback_and_add_event_listener_options(
                "scroll",
                update_closure.as_ref().unchecked_ref(),
                AddEventListenerOptions::new().passive(true),
            );
        }

        if ancestor_resize {
            _ = event_target.add_event_listener_with_callback(
                "resize",
                update_closure.as_ref().unchecked_ref(),
            );
        }
    }

    let cleanup_observe_move =
        reference_element
            .as_ref()
            .and_then(|reference_element| match layout_shift {
                true => Some(observe_move(reference_element.clone(), update.clone())),
                false => None,
            });

    let reobserve_frame: Rc<RefCell<Option<i32>>> = Rc::new(RefCell::new(None));
    let resize_observer: Rc<RefCell<Option<ResizeObserver>>> = Rc::new(RefCell::new(None));

    if element_resize {
        let reobserve_floating = floating.clone();
        let reobserve_resize_observer = resize_observer.clone();
        let reobserve_closure: Rc<Closure<dyn FnMut()>> = Rc::new(Closure::new(move || {
            reobserve_resize_observer
                .borrow()
                .as_ref()
                .expect("Resize observer should exist.")
                .observe(&reobserve_floating);
        }));

        let resize_reference_element = reference_element.clone();
        let resize_update = update.clone();
        let resize_closure: Closure<dyn Fn(Vec<ResizeObserverEntry>)> =
            Closure::new(move |entries: Vec<ResizeObserverEntry>| {
                if let Some(first_entry) = entries.first() {
                    if resize_reference_element
                        .as_ref()
                        .is_some_and(|reference_element| first_entry.target() == *reference_element)
                    {
                        if let Some(reobserve_frame) = reobserve_frame.take() {
                            cancel_animation_frame(reobserve_frame);
                        }

                        reobserve_frame
                            .replace(Some(request_animation_frame(reobserve_closure.as_ref())));
                    }
                }

                resize_update();
            });

        resize_observer.replace(Some(
            ResizeObserver::new(resize_closure.into_js_value().unchecked_ref())
                .expect("Resize observer should be created."),
        ));

        if let Some(reference) = reference_element.as_ref() {
            if !animation_frame {
                resize_observer
                    .borrow()
                    .as_ref()
                    .expect("Resize observer should exist.")
                    .observe(reference);
            }
        }

        resize_observer
            .borrow()
            .as_ref()
            .expect("Resize observer should exist.")
            .observe(floating);
    }

    let frame_id: Rc<RefCell<Option<i32>>> = Rc::new(RefCell::new(None));
    let mut prev_ref_rect = match animation_frame {
        true => Some(get_bounding_client_rect(reference, false, false, None)),
        false => None,
    };

    let frame_loop_frame_id = frame_id.clone();
    let frame_loop_closure = Rc::new(RefCell::new(None));
    let frame_loop_closure_clone = frame_loop_closure.clone();
    let frame_loop_closure_update = update.clone();

    *frame_loop_closure_clone.borrow_mut() = Some(Closure::new(move || {
        let next_ref_rect = get_bounding_client_rect((&owned_reference).into(), false, false, None);

        if let Some(prev_ref_rect) = &prev_ref_rect {
            if next_ref_rect.x != prev_ref_rect.x
                || next_ref_rect.y != prev_ref_rect.y
                || next_ref_rect.width != prev_ref_rect.width
                || next_ref_rect.height != prev_ref_rect.height
            {
                frame_loop_closure_update();
            }
        }

        prev_ref_rect = Some(next_ref_rect);
        frame_loop_frame_id.replace(Some(request_animation_frame(
            frame_loop_closure
                .borrow()
                .as_ref()
                .expect("Frame loop closure should exist."),
        )));
    }));

    if animation_frame {
        request_animation_frame(
            frame_loop_closure_clone
                .borrow()
                .as_ref()
                .expect("Frame loop closure should exist."),
        );
    }

    update();

    Box::new(move || {
        for ancestor in &ancestors {
            let event_target: &EventTarget = match ancestor {
                OverflowAncestor::Element(element) => element,
                OverflowAncestor::Window(window) => window,
            };

            if ancestor_scoll {
                _ = event_target.remove_event_listener_with_callback(
                    "scroll",
                    update_closure.as_ref().unchecked_ref(),
                );
            }

            if ancestor_resize {
                _ = event_target.remove_event_listener_with_callback(
                    "resize",
                    update_closure.as_ref().unchecked_ref(),
                );
            }
        }

        if let Some(cleanup_observe_move) = &cleanup_observe_move {
            cleanup_observe_move();
        }

        if let Some(resize_observer) = resize_observer.take() {
            resize_observer.disconnect();
        }

        if let Some(frame_id) = frame_id.take() {
            cancel_animation_frame(frame_id);
        }
    })
}