use crate::ecs::world::World;
use nalgebra_glm::Vec2;

use crate::ecs::ui::state::{UiBase, UiStateTrait};

use crate::prelude::*;
pub fn ui_clipboard_text(world: &World) -> &str {
    &world.resources.retained_ui.clipboard_text
}

pub fn ui_set_clipboard_text(world: &mut World, text: impl Into<String>) {
    let text = text.into();
    world.resources.retained_ui.clipboard_text = text.clone();
    sync_to_system_clipboard(&text);
}

pub fn ui_read_system_clipboard(world: &mut World) -> String {
    if let Some(text) = read_from_system_clipboard() {
        world.resources.retained_ui.clipboard_text = text.clone();
        text
    } else {
        world.resources.retained_ui.clipboard_text.clone()
    }
}

pub fn ui_events(world: &World) -> &[crate::ecs::ui::resources::UiEvent] {
    world.resources.retained_ui.events_for_active()
}

/// Iterator over every entity that received a `ButtonClicked` event
/// this frame. Saves callers from inlining the same
/// `if let UiEvent::ButtonClicked(entity) = event { ... }` filter at
/// every menu/UI handler.
pub fn ui_button_clicks(world: &World) -> impl Iterator<Item = freecs::Entity> + '_ {
    ui_events(world).iter().filter_map(|event| match event {
        crate::ecs::ui::resources::UiEvent::ButtonClicked(entity) => Some(*entity),
        _ => None,
    })
}

pub fn ui_events_for(
    world: &World,
    entity: freecs::Entity,
) -> impl Iterator<Item = &crate::ecs::ui::resources::UiEvent> {
    use crate::ecs::ui::resources::UiEvent;
    world
        .resources
        .retained_ui
        .frame
        .events
        .iter()
        .filter(move |event| match event {
            UiEvent::ButtonClicked(event_entity)
            | UiEvent::SliderChanged {
                entity: event_entity,
                ..
            }
            | UiEvent::ToggleChanged {
                entity: event_entity,
                ..
            }
            | UiEvent::CheckboxChanged {
                entity: event_entity,
                ..
            }
            | UiEvent::RadioChanged {
                entity: event_entity,
                ..
            }
            | UiEvent::TabChanged {
                entity: event_entity,
                ..
            }
            | UiEvent::TextInputChanged {
                entity: event_entity,
                ..
            }
            | UiEvent::TextInputSubmitted {
                entity: event_entity,
                ..
            }
            | UiEvent::DropdownChanged {
                entity: event_entity,
                ..
            }
            | UiEvent::MenuItemClicked {
                entity: event_entity,
                ..
            }
            | UiEvent::ColorPickerChanged {
                entity: event_entity,
                ..
            }
            | UiEvent::SelectableLabelClicked {
                entity: event_entity,
                ..
            }
            | UiEvent::DragValueChanged {
                entity: event_entity,
                ..
            }
            | UiEvent::ContextMenuItemClicked {
                entity: event_entity,
                ..
            }
            | UiEvent::ModalClosed {
                entity: event_entity,
                ..
            }
            | UiEvent::CommandPaletteExecuted {
                entity: event_entity,
                ..
            } => *event_entity == entity,
            UiEvent::TreeNodeSelected {
                tree: event_entity, ..
            }
            | UiEvent::TreeNodeToggled {
                tree: event_entity, ..
            }
            | UiEvent::TreeNodeContextMenu {
                tree: event_entity, ..
            }
            | UiEvent::TreeNodeExpandRequested {
                tree: event_entity, ..
            } => *event_entity == entity,
            UiEvent::TileTabActivated {
                container: event_entity,
                ..
            }
            | UiEvent::TileTabClosed {
                container: event_entity,
                ..
            }
            | UiEvent::TileSplitterMoved {
                container: event_entity,
                ..
            } => *event_entity == entity,
            UiEvent::DragStarted {
                source: event_entity,
                ..
            }
            | UiEvent::DragCancelled {
                source: event_entity,
                ..
            } => *event_entity == entity,
            UiEvent::DragDropped {
                target: event_entity,
                ..
            }
            | UiEvent::CanvasClicked {
                entity: event_entity,
                ..
            } => *event_entity == entity,
            UiEvent::DragEnter {
                target: event_entity,
                ..
            }
            | UiEvent::DragOver {
                target: event_entity,
                ..
            }
            | UiEvent::DragLeave {
                target: event_entity,
                ..
            } => *event_entity == entity,
            UiEvent::ShortcutTriggered { .. } => false,
            UiEvent::VirtualListItemClicked {
                entity: event_entity,
                ..
            }
            | UiEvent::VirtualListItemRightClicked {
                entity: event_entity,
                ..
            }
            | UiEvent::TextAreaChanged {
                entity: event_entity,
                ..
            }
            | UiEvent::RichTextEditorChanged {
                entity: event_entity,
                ..
            }
            | UiEvent::DataGridFilterChanged {
                entity: event_entity,
                ..
            }
            | UiEvent::RangeSliderChanged {
                entity: event_entity,
                ..
            }
            | UiEvent::DataGridCellEdited {
                entity: event_entity,
                ..
            }
            | UiEvent::BreadcrumbClicked {
                entity: event_entity,
                ..
            }
            | UiEvent::SplitterChanged {
                entity: event_entity,
                ..
            }
            | UiEvent::MultiSelectChanged {
                entity: event_entity,
                ..
            }
            | UiEvent::DatePickerChanged {
                entity: event_entity,
                ..
            } => *event_entity == entity,
        })
}

pub fn ui_despawn_node(world: &mut World, entity: freecs::Entity) {
    if !world.resources.transform_state.children_cache_valid {
        validate_and_rebuild_children_cache(world);
    }

    let mut stack = vec![entity];
    let mut all_entities = Vec::new();
    while let Some(current) = stack.pop() {
        all_entities.push(current);
        if let Some(children) = world.resources.transform_state.children_cache.get(&current) {
            for child in children {
                stack.push(*child);
            }
        }
    }

    for descendant in &all_entities {
        if let Some(content) = world.ui.get_ui_node_content(*descendant)
            && let crate::ecs::ui::components::UiNodeContent::Text { text_slot, .. } = content
        {
            let slot = *text_slot;
            world.resources.text.cache.remove_text(slot);
        }

        if let Some(data) = world.ui.get_ui_radio(*descendant).cloned()
            && let Some(group) = world
                .resources
                .retained_ui
                .groups
                .radio
                .get_mut(&data.group_id)
        {
            group.retain(|e| *e != *descendant);
        }
        if let Some(data) = world.ui.get_ui_selectable_label(*descendant).cloned()
            && let Some(group_id) = data.group_id
            && let Some(group) = world
                .resources
                .retained_ui
                .groups
                .selectable_labels
                .get_mut(&group_id)
        {
            group.retain(|e| *e != *descendant);
        }

        let mut slots: Vec<usize> = Vec::new();
        if let Some(data) = world.ui.get_ui_button(*descendant)
            && let Some(text_slot) = data.text_slot
        {
            slots.push(text_slot);
        }
        if let Some(data) = world.ui.get_ui_slider(*descendant) {
            slots.push(data.text_slot);
        }
        if let Some(data) = world.ui.get_ui_text_input(*descendant) {
            slots.push(data.text_slot);
        }
        if let Some(data) = world.ui.get_ui_collapsing_header(*descendant) {
            slots.push(data.arrow_text_slot);
        }
        if let Some(data) = world.ui.get_ui_dropdown(*descendant) {
            slots.push(data.header_text_slot);
        }
        if let Some(data) = world.ui.get_ui_menu(*descendant) {
            slots.push(data.label_text_slot);
        }
        if let Some(data) = world.ui.get_ui_panel(*descendant) {
            slots.push(data.title_text_slot);
            if let Some(slot) = data.collapse_button_text_slot {
                slots.push(slot);
            }
        }
        if let Some(data) = world.ui.get_ui_selectable_label(*descendant) {
            slots.push(data.text_slot);
        }
        if let Some(data) = world.ui.get_ui_drag_value(*descendant) {
            slots.push(data.text_slot);
        }
        if let Some(data) = world.ui.get_ui_tree_node(*descendant) {
            slots.push(data.text_slot);
            slots.push(data.arrow_text_slot);
        }
        if let Some(data) = world.ui.get_ui_modal_dialog(*descendant) {
            slots.push(data.title_text_slot);
        }
        if let Some(data) = world.ui.get_ui_tab_bar(*descendant) {
            slots.extend_from_slice(&data.tab_text_slots);
        }
        if let Some(data) = world.ui.get_ui_text_area(*descendant) {
            slots.push(data.text_slot);
        }
        if let Some(data) = world.ui.get_ui_rich_text(*descendant) {
            slots.extend_from_slice(&data.span_text_slots);
        }
        if let Some(data) = world.ui.get_ui_data_grid(*descendant) {
            slots.extend_from_slice(&data.header_text_slots);
            for row in &data.pool_rows {
                slots.extend_from_slice(&row.cell_text_slots);
            }
        }
        if let Some(data) = world.ui.get_ui_command_palette(*descendant) {
            slots.extend_from_slice(&data.result_text_slots);
        }
        if let Some(data) = world.ui.get_ui_rich_text_editor(*descendant) {
            slots.push(data.text_slot);
        }
        if let Some(data) = world.ui.get_ui_breadcrumb(*descendant) {
            slots.extend_from_slice(&data.segment_text_slots);
        }
        if let Some(data) = world.ui.get_ui_multi_select(*descendant) {
            slots.push(data.header_text_slot);
        }
        if let Some(data) = world.ui.get_ui_date_picker(*descendant) {
            slots.push(data.header_text_slot);
            slots.push(data.month_label_slot);
            slots.extend_from_slice(&data.day_text_slots);
        }
        for slot in slots {
            world.resources.text.cache.remove_text(slot);
        }
    }

    let despawned: std::collections::HashSet<freecs::Entity> =
        all_entities.iter().copied().collect();
    world
        .resources
        .retained_ui
        .accessibility
        .named_entities
        .retain(|_, stored| !despawned.contains(stored));
    world
        .resources
        .retained_ui
        .accessibility
        .test_id_map
        .retain(|_, stored| !despawned.contains(stored));

    clear_despawned_singletons(world, &despawned);

    crate::ecs::world::commands::despawn_recursive_immediate(world, entity);
    ui_mark_children_dirty(world);
}

/// Null out every retained-UI singleton that points at one of the despawned
/// entities. Without this, a singleton like `focused_entity` or
/// `active_context_menu` would keep a stale handle, and because entity ids are
/// recycled a freshly spawned widget could inherit focus, hover, drag, or
/// open-overlay state that was never meant for it.
fn clear_despawned_singletons(
    world: &mut World,
    despawned: &std::collections::HashSet<freecs::Entity>,
) {
    let interaction = world.resources.retained_ui.interaction_for_active_mut();
    if interaction
        .focused_entity
        .is_some_and(|entity| despawned.contains(&entity))
    {
        interaction.focused_entity = None;
    }
    if interaction
        .hovered_entity
        .is_some_and(|entity| despawned.contains(&entity))
    {
        interaction.hovered_entity = None;
    }
    if interaction
        .active_entity
        .is_some_and(|entity| despawned.contains(&entity))
    {
        interaction.active_entity = None;
    }
    if interaction
        .last_click
        .is_some_and(|(entity, _, _)| despawned.contains(&entity))
    {
        interaction.last_click = None;
    }

    let overlays = &mut world.resources.retained_ui.overlays;
    overlays
        .popup_entities
        .retain(|entity| !despawned.contains(entity));
    if overlays
        .active_context_menu
        .is_some_and(|entity| despawned.contains(&entity))
    {
        overlays.active_context_menu = None;
    }
    if overlays
        .active_modal
        .is_some_and(|entity| despawned.contains(&entity))
    {
        overlays.active_modal = None;
    }
    if overlays
        .toast_container
        .is_some_and(|entity| despawned.contains(&entity))
    {
        overlays.toast_container = None;
    }
    if overlays
        .dock_indicator_panel
        .is_some_and(|entity| despawned.contains(&entity))
    {
        overlays.dock_indicator_panel = None;
        overlays.dock_indicator_active = false;
    }
    if let Some(tooltip) = &overlays.tooltip_state
        && (despawned.contains(&tooltip.entity)
            || tooltip
                .tooltip_entity
                .is_some_and(|entity| despawned.contains(&entity)))
    {
        overlays.tooltip_state = None;
    }

    let drag = &mut world.resources.retained_ui.drag;
    if drag
        .active
        .as_ref()
        .is_some_and(|active| despawned.contains(&active.source))
    {
        drag.active = None;
    }
    if drag
        .over_entity
        .is_some_and(|entity| despawned.contains(&entity))
    {
        drag.over_entity = None;
    }
}

pub fn ui_clear_children(world: &mut World, parent: freecs::Entity) {
    if !world.resources.transform_state.children_cache_valid {
        validate_and_rebuild_children_cache(world);
    }

    let children: Vec<freecs::Entity> = world
        .resources
        .transform_state
        .children_cache
        .get(&parent)
        .cloned()
        .unwrap_or_default();

    for child in children {
        ui_despawn_node(world, child);
    }
    ui_mark_layout_dirty(world);
}

pub fn ui_rebuild_children(
    world: &mut World,
    parent: freecs::Entity,
    build: impl FnOnce(&mut crate::ecs::ui::builder::UiTreeBuilder),
) {
    ui_clear_children(world, parent);
    let content = ui_widget_content(world, parent).unwrap_or(parent);
    let mut builder = crate::ecs::ui::builder::UiTreeBuilder::from_parent(world, content);
    build(&mut builder);
    builder.finish_subtree();
}

pub fn ui_focus(world: &mut World, entity: freecs::Entity) {
    world
        .resources
        .retained_ui
        .interaction_for_active_mut()
        .focused_entity = Some(entity);
}

pub fn ui_set_disabled(world: &mut World, entity: freecs::Entity, disabled: bool) {
    if let Some(interaction) = world.ui.get_ui_node_interaction_mut(entity) {
        interaction.disabled = disabled;
    }

    if disabled {
        let base_color = world
            .ui
            .get_ui_node_color(entity)
            .and_then(|color| color.colors[UiBase::INDEX]);
        if let Some(base) = base_color
            && let Some(color_comp) = world.ui.get_ui_node_color_mut(entity)
        {
            color_comp.colors[crate::ecs::ui::state::UiDisabled::INDEX] = Some(
                nalgebra_glm::Vec4::new(base.x, base.y, base.z, base.w * 0.4),
            );
        }
    } else if let Some(color_comp) = world.ui.get_ui_node_color_mut(entity) {
        color_comp.colors[crate::ecs::ui::state::UiDisabled::INDEX] = None;
    }
}

pub fn ui_set_disabled_recursive(world: &mut World, entity: freecs::Entity, disabled: bool) {
    if !world.resources.transform_state.children_cache_valid {
        validate_and_rebuild_children_cache(world);
    }
    let mut stack = vec![entity];
    while let Some(current) = stack.pop() {
        ui_set_disabled(world, current, disabled);
        if let Some(children) = world.resources.transform_state.children_cache.get(&current) {
            for child in children {
                stack.push(*child);
            }
        }
    }
}

pub fn ui_is_disabled(world: &World, entity: freecs::Entity) -> bool {
    world
        .ui
        .get_ui_node_interaction(entity)
        .is_some_and(|interaction| interaction.disabled)
}

pub fn ui_set_named(world: &mut World, entity: freecs::Entity, name: &str) {
    world
        .resources
        .retained_ui
        .accessibility
        .named_entities
        .insert(name.to_string(), entity);
}

pub fn ui_named_entity(world: &World, name: &str) -> Option<freecs::Entity> {
    world
        .resources
        .retained_ui
        .accessibility
        .named_entities
        .get(name)
        .copied()
}

pub fn ui_set_test_id(world: &mut World, entity: freecs::Entity, test_id: &str) {
    if world.ui.get_ui_node_interaction(entity).is_none() {
        world.ui.set_ui_node_interaction(
            entity,
            crate::ecs::ui::components::UiNodeInteraction::default(),
        );
        if let Some(node) = world.ui.get_ui_layout_node_mut(entity) {
            node.pointer_events = true;
        }
    }
    if let Some(interaction) = world.ui.get_ui_node_interaction_mut(entity) {
        interaction.test_id = Some(test_id.to_string());
    }
    world
        .resources
        .retained_ui
        .accessibility
        .test_id_map
        .insert(test_id.to_string(), entity);
}

pub fn ui_set_visible(world: &mut World, entity: freecs::Entity, visible: bool) {
    ui_mark_layout_dirty(world);
    if let Some(node) = world.ui.get_ui_layout_node_mut(entity) {
        if let Some(animation) = &mut node.animation {
            if visible {
                if animation.intro.is_some() {
                    animation.phase = crate::ecs::ui::components::UiAnimationPhase::IntroPlaying;
                    animation.progress = 0.0;
                }
                node.visible = true;
            } else if animation.outro.is_some() {
                animation.phase = crate::ecs::ui::components::UiAnimationPhase::OutroPlaying;
                animation.progress = 0.0;
            } else {
                animation.phase = crate::ecs::ui::components::UiAnimationPhase::Idle;
                node.visible = false;
            }
        } else {
            node.visible = visible;
        }
    }
}

pub fn ui_set_visible_exclusive(
    world: &mut World,
    entities: &[freecs::Entity],
    active: freecs::Entity,
) {
    for &entity in entities {
        ui_set_visible(world, entity, entity == active);
    }
}

pub fn ui_node_visible(world: &World, entity: freecs::Entity) -> bool {
    world
        .ui
        .get_ui_layout_node(entity)
        .is_some_and(|node| node.visible)
}

pub fn ui_node_effectively_visible(world: &World, entity: freecs::Entity) -> bool {
    let Some(node) = world.ui.get_ui_layout_node(entity) else {
        return false;
    };
    if !node.visible {
        return false;
    }
    let mut current = entity;
    while let Some(crate::ecs::transform::components::Parent(Some(parent))) =
        world.core.get_parent(current)
    {
        let parent = *parent;
        if let Some(parent_node) = world.ui.get_ui_layout_node(parent)
            && !parent_node.visible
        {
            return false;
        }
        current = parent;
    }
    true
}

pub fn ui_rect(world: &World, entity: freecs::Entity) -> Option<crate::ecs::ui::types::Rect> {
    world
        .ui
        .get_ui_layout_node(entity)
        .map(|node| node.computed_rect)
}

pub fn ui_size(world: &World, entity: freecs::Entity) -> Option<Vec2> {
    world
        .ui
        .get_ui_layout_node(entity)
        .map(|node| node.computed_rect.size())
}

pub fn ui_set_text(world: &mut World, entity: freecs::Entity, text: &str) {
    let slot = ui_text_slot_for_entity(world, entity);
    if let Some(text_slot) = slot {
        world.resources.text.cache.set_text(text_slot, text);
    }
}

pub fn ui_set_icon(
    world: &mut World,
    entity: freecs::Entity,
    icon: crate::ecs::ui::icons::IconGlyph,
) {
    let slot = ui_text_slot_for_entity(world, entity);
    if let Some(text_slot) = slot {
        let mut buf = [0u8; 4];
        let icon_str: &str = icon.codepoint.encode_utf8(&mut buf);
        world.resources.text.cache.set_text(text_slot, icon_str);
    }
    if let Some(content) = world.ui.get_ui_node_content_mut(entity)
        && let crate::ecs::ui::components::UiNodeContent::Text { font_kind, .. } = content
    {
        *font_kind = icon.font_kind;
    }
}

pub fn ui_set_label_text(world: &mut World, entity: freecs::Entity, text: &str) {
    ui_set_text(world, entity, text);
}

pub fn ui_label_text(world: &World, entity: freecs::Entity) -> Option<String> {
    ui_text_slot_for_entity(world, entity)
        .and_then(|slot| world.resources.text.cache.get_text(slot).map(String::from))
}

pub fn ui_text_slot_for_entity(world: &World, entity: freecs::Entity) -> Option<usize> {
    if let Some(crate::ecs::ui::components::UiNodeContent::Text { text_slot, .. }) =
        world.ui.get_ui_node_content(entity)
    {
        return Some(*text_slot);
    }
    if let Some(data) = world.ui.get_ui_button(entity)
        && let Some(text_slot) = data.text_slot
    {
        return Some(text_slot);
    }
    if let Some(data) = world.ui.get_ui_slider(entity) {
        return Some(data.text_slot);
    }
    if let Some(data) = world.ui.get_ui_text_input(entity) {
        return Some(data.text_slot);
    }
    if let Some(data) = world.ui.get_ui_selectable_label(entity) {
        return Some(data.text_slot);
    }
    if let Some(data) = world.ui.get_ui_drag_value(entity) {
        return Some(data.text_slot);
    }
    None
}

pub fn ui_set_tooltip_entity(
    world: &mut World,
    entity: freecs::Entity,
    tooltip_entity: Option<freecs::Entity>,
) {
    if let Some(interaction) = world.ui.get_ui_node_interaction_mut(entity) {
        interaction.tooltip_entity = tooltip_entity;
    }
}

pub fn ui_set_tooltip_text(world: &mut World, entity: freecs::Entity, text: Option<&str>) {
    if let Some(interaction) = world.ui.get_ui_node_interaction_mut(entity) {
        interaction.tooltip_text = text.map(String::from);
    }
}

/// Letterbox-fit an RGBA8 buffer into a UI image layer (preserving aspect
/// ratio) and upload it. Returns the layer index plus the UV range that
/// covers the populated region of the layer, or `None` if the layer pool
/// is exhausted. Pass the result to `with_image_uv`.
#[cfg(feature = "assets")]
pub fn ui_upload_image(
    world: &mut World,
    rgba: &[u8],
    width: u32,
    height: u32,
) -> Option<UiImageUpload> {
    use crate::render::wgpu::ui_texture_array::UI_TEXTURE_LAYER_SIZE;
    use nalgebra_glm::Vec2;

    if width == 0 || height == 0 {
        return None;
    }

    let target = UI_TEXTURE_LAYER_SIZE;
    let scale_factor = (target as f32 / width as f32).min(target as f32 / height as f32);
    let scaled_width = ((width as f32 * scale_factor).round() as u32).clamp(1, target);
    let scaled_height = ((height as f32 * scale_factor).round() as u32).clamp(1, target);

    let scaled_pixels = if scaled_width == width && scaled_height == height {
        rgba.to_vec()
    } else {
        let source: image::RgbaImage = image::ImageBuffer::from_raw(width, height, rgba.to_vec())?;
        image::imageops::resize(
            &source,
            scaled_width,
            scaled_height,
            image::imageops::FilterType::Triangle,
        )
        .into_raw()
    };

    let mut padded = vec![0u8; (target * target * 4) as usize];
    let row_bytes = (scaled_width * 4) as usize;
    let target_row_bytes = (target * 4) as usize;
    for row in 0..scaled_height as usize {
        let source_offset = row * row_bytes;
        let target_offset = row * target_row_bytes;
        padded[target_offset..target_offset + row_bytes]
            .copy_from_slice(&scaled_pixels[source_offset..source_offset + row_bytes]);
    }

    let layer = world.resources.ui_image_allocator.allocate()?;
    world
        .resources
        .commands
        .render
        .push(crate::ecs::world::RenderCommand::UploadUiImageLayer {
            layer,
            rgba_data: padded,
            width: target,
            height: target,
        });

    let uv_max = Vec2::new(
        scaled_width as f32 / target as f32,
        scaled_height as f32 / target as f32,
    );
    Some(UiImageUpload {
        layer,
        uv_min: Vec2::new(0.0, 0.0),
        uv_max,
        content_width: scaled_width,
        content_height: scaled_height,
    })
}

pub fn ui_release_image_layer(world: &mut World, layer: u32) {
    world.resources.ui_image_allocator.release(layer);
}

#[cfg(feature = "assets")]
#[derive(Clone, Copy, Debug)]
pub struct UiImageUpload {
    pub layer: u32,
    pub uv_min: nalgebra_glm::Vec2,
    pub uv_max: nalgebra_glm::Vec2,
    pub content_width: u32,
    pub content_height: u32,
}

#[cfg(not(target_arch = "wasm32"))]
fn sync_to_system_clipboard(text: &str) {
    if let Ok(mut clipboard) = arboard::Clipboard::new() {
        let _ = clipboard.set_text(text.to_string());
    }
}

#[cfg(not(target_arch = "wasm32"))]
fn read_from_system_clipboard() -> Option<String> {
    let mut clipboard = arboard::Clipboard::new().ok()?;
    clipboard.get_text().ok()
}

#[cfg(target_arch = "wasm32")]
fn sync_to_system_clipboard(_text: &str) {}

#[cfg(target_arch = "wasm32")]
fn read_from_system_clipboard() -> Option<String> {
    None
}

#[cfg(not(target_arch = "wasm32"))]
pub fn open_url_in_browser(url: &str) {
    let _ = webbrowser::open(url);
}

#[cfg(target_arch = "wasm32")]
pub fn open_url_in_browser(_url: &str) {}
