use ruin_reactivity::{Cell, cell};
use ruin_ui::{
    Color, PlatformEvent, Point, PreparedText, Rect, SceneSnapshot, UiRuntime, UiSize,
    WindowController, WindowSpec, WindowUpdate,
};
use tracing_subscriber::layer::SubscriberExt;
use tracing_subscriber::util::SubscriberInitExt;
use tracing_subscriber::{EnvFilter, fmt};

fn install_tracing() {
    let filter = EnvFilter::try_from_default_env().unwrap_or_else(|_| {
        EnvFilter::new(
            "info,ruin_runtime::runtime=debug,ruin_ui::platform=debug,ruin_reactivity::effect=debug",
        )
    });

    let fmt_layer = fmt::layer()
        .with_target(true)
        .with_thread_ids(true)
        .with_thread_names(true)
        .compact();

    let _ = tracing_subscriber::registry()
        .with(filter)
        .with(fmt_layer)
        .try_init();
}

fn log_platform_event(event: &PlatformEvent) {
    match event {
        PlatformEvent::Opened { window_id } => {
            tracing::info!(event = "window_opened", window_id = window_id.raw());
        }
        PlatformEvent::Configured {
            window_id,
            configuration,
        } => {
            tracing::info!(
                event = "window_configured",
                window_id = window_id.raw(),
                width = configuration.actual_inner_size.width,
                height = configuration.actual_inner_size.height,
                visible = configuration.visible,
                "window configured"
            );
        }
        PlatformEvent::VisibilityChanged { window_id, visible } => {
            tracing::info!(
                event = "visibility_changed",
                window_id = window_id.raw(),
                visible,
                "window visibility changed"
            );
        }
        PlatformEvent::FramePresented {
            window_id,
            scene_version,
            item_count,
        } => {
            tracing::info!(
                event = "frame_presented",
                window_id = window_id.raw(),
                scene_version,
                item_count,
                "headless backend presented scene"
            );
        }
        PlatformEvent::Closed { window_id } => {
            tracing::info!(event = "window_closed", window_id = window_id.raw());
        }
        PlatformEvent::Pointer { window_id, event } => {
            tracing::debug!(
                event = "pointer_event",
                window_id = window_id.raw(),
                pointer_id = event.pointer_id,
                x = event.position.x,
                y = event.position.y,
                ?event.kind,
                "pointer event received"
            );
        }
        PlatformEvent::Keyboard { window_id, event } => {
            tracing::debug!(
                event = "keyboard_event",
                window_id = window_id.raw(),
                keycode = event.keycode,
                ?event.kind,
                ?event.key,
                ?event.modifiers,
                text = event.text.as_deref().unwrap_or(""),
                "keyboard event received"
            );
        }
        PlatformEvent::Wake { window_id, token } => {
            tracing::debug!(
                event = "wake_event",
                window_id = window_id.raw(),
                token,
                "internal wake event received"
            );
        }
        PlatformEvent::ClipboardText { window_id, text } => {
            tracing::debug!(
                event = "clipboard_text",
                window_id = window_id.raw(),
                text,
                "clipboard text received"
            );
        }
        #[cfg(target_os = "linux")]
        PlatformEvent::PrimarySelectionText { window_id, text } => {
            tracing::debug!(
                event = "primary_selection_text",
                window_id = window_id.raw(),
                text,
                "primary selection text received"
            );
        }
        PlatformEvent::CloseRequested { window_id } => {
            tracing::info!(
                event = "close_requested",
                window_id = window_id.raw(),
                "compositor-like close request received"
            );
        }
    }
}

fn attach_window_scene(window: &WindowController) -> (Cell<usize>, ruin_reactivity::EffectHandle) {
    let counter = cell(0usize);
    let scene_window = window.clone();
    let scene_effect = scene_window.attach_scene_effect({
        let counter = counter.clone();
        move || {
            let version = counter.get() as u64 + 1;
            let value = counter.get();
            let mut scene = SceneSnapshot::new(version, UiSize::new(640.0, 360.0));
            scene
                .push_quad(
                    Rect::new(0.0, 0.0, 640.0, 360.0),
                    Color::rgb(0x12, 0x19, 0x28),
                )
                .push_quad(
                    Rect::new(24.0, 24.0, 592.0, 64.0),
                    Color::rgb(0x2B, 0x3A, 0x67),
                )
                .push_text(PreparedText::monospace(
                    format!("RUIN explicit prototype | counter = {value}"),
                    Point::new(40.0, 64.0),
                    18.0,
                    9.0,
                    Color::rgb(0xFF, 0xFF, 0xFF),
                ));
            scene
        }
    });

    (counter, scene_effect)
}

#[ruin_runtime::async_main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    install_tracing();

    tracing::info!(
        event = "prototype_start",
        "starting explicit-construction UI prototype with a headless backend"
    );

    let mut ui = UiRuntime::headless();
    let window = ui.create_window(
        WindowSpec::new("RUIN Explicit Prototype")
            .app_id("dev.ruin.prototype")
            .requested_inner_size(UiSize::new(640.0, 360.0)),
    )?;
    let (counter, _scene_effect) = attach_window_scene(&window);
    let _ = counter.set(1);
    let _ = counter.set(2);
    let _ = counter.set(3);

    let event = ui
        .wait_for_event_matching(|event| {
            matches!(
                event,
                PlatformEvent::Configured { window_id, .. } if *window_id == window.id()
            )
        })
        .await?;
    log_platform_event(&event);

    let event = ui
        .wait_for_event_matching(
            |event| matches!(event, PlatformEvent::Opened { window_id } if *window_id == window.id()),
        )
        .await?;
    log_platform_event(&event);

    let event = ui
        .wait_for_event_matching(|event| {
            matches!(
                event,
                PlatformEvent::FramePresented {
                    window_id,
                    scene_version: 4,
                    ..
                } if *window_id == window.id()
            )
        })
        .await?;
    log_platform_event(&event);

    window.update(WindowUpdate::new().visible(false))?;
    let event = ui
        .wait_for_event_matching(|event| {
            matches!(
                event,
                PlatformEvent::VisibilityChanged {
                    window_id,
                    visible: false,
                } if *window_id == window.id()
            )
        })
        .await?;
    log_platform_event(&event);

    window.update(WindowUpdate::new().visible(true))?;
    let event = ui
        .wait_for_event_matching(|event| {
            matches!(
                event,
                PlatformEvent::VisibilityChanged {
                    window_id,
                    visible: true,
                } if *window_id == window.id()
            )
        })
        .await?;
    log_platform_event(&event);

    let _ = counter.set(4);
    let event = ui
        .wait_for_event_matching(|event| {
            matches!(
                event,
                PlatformEvent::FramePresented {
                    window_id,
                    scene_version: 4,
                    ..
                } if *window_id == window.id()
            )
        })
        .await?;
    log_platform_event(&event);

    let event = ui
        .wait_for_event_matching(|event| {
            matches!(
                event,
                PlatformEvent::FramePresented {
                    window_id,
                    scene_version: 5,
                    ..
                } if *window_id == window.id()
            )
        })
        .await?;
    log_platform_event(&event);

    window.emit_close_requested()?;
    let event = ui
        .wait_for_event_matching(|event| {
            matches!(
                event,
                PlatformEvent::CloseRequested { window_id } if *window_id == window.id()
            )
        })
        .await?;
    log_platform_event(&event);

    window.update(WindowUpdate::new().open(false))?;
    let event = ui
        .wait_for_event_matching(
            |event| matches!(event, PlatformEvent::Closed { window_id } if *window_id == window.id()),
        )
        .await?;
    log_platform_event(&event);

    ui.shutdown()?;

    tracing::info!(
        event = "prototype_done",
        "explicit-construction UI prototype finished"
    );
    Ok(())
}
