//! Leptos Tiptap island 挂载上下文与 embedded/standalone delivery mode。

use std::cell::Cell;

use leptos::mount::mount_to;
use leptos::prelude::*;
use leptos_tiptap::TiptapContent;
use serde::Deserialize;
use serde_json::Value;
use wasm_bindgen::JsCast;
use web_sys::{window, EventTarget, HtmlElement};

use crate::editor_runtime::bridge_events::{RuntimePageOptions, COMMAND_EVENT};
use crate::editor_runtime::runtime_bridge::{build_command_listener, register_unmount_handle};
use crate::App;

#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(crate) enum RuntimeDeliveryMode {
    Standalone,
    Embedded,
}

#[derive(Clone, Debug, Default, Deserialize)]
#[serde(rename_all = "camelCase")]
pub(crate) struct MountOptions {
    pub(crate) document_id: Option<String>,
    pub(crate) workspace_id: Option<String>,
    pub(crate) title: Option<String>,
    pub(crate) content: Option<Value>,
    pub(crate) html: Option<String>,
    pub(crate) editable: Option<bool>,
    pub(crate) read_only: Option<bool>,
    pub(crate) revision: Option<i64>,
    pub(crate) conflict_detection_key: Option<String>,
    pub(crate) page_options: Option<RuntimePageOptions>,
    #[serde(default)]
    pub(crate) standalone_object: Option<RuntimeStandaloneObject>,
}

#[derive(Clone, Debug, Default, Deserialize)]
#[serde(rename_all = "camelCase")]
pub(crate) struct RuntimeStandaloneObject {
    pub(crate) kind: Option<String>,
    pub(crate) document_id: Option<String>,
    pub(crate) mindmap_id: Option<String>,
}

struct RuntimeMountContext {
    id: u32,
    target: EventTarget,
    mode: RuntimeDeliveryMode,
}

#[derive(Clone, Debug)]
#[allow(dead_code)]
struct RuntimeMountOptions {
    options: MountOptions,
    mode: RuntimeDeliveryMode,
}

thread_local! {
    static RUNTIME_MOUNT_CONTEXT: std::cell::RefCell<Option<RuntimeMountContext>> = const { std::cell::RefCell::new(None) };
    static RUNTIME_MOUNT_OPTIONS: std::cell::RefCell<Option<RuntimeMountOptions>> = const { std::cell::RefCell::new(None) };
    static NEXT_MOUNT_ID: Cell<u32> = const { Cell::new(1) };
}

pub(crate) fn next_mount_id() -> u32 {
    NEXT_MOUNT_ID.with(|cell| {
        let next = cell.get();
        cell.set(next.saturating_add(1).max(1));
        next
    })
}

pub(crate) fn set_runtime_mount_context(context: Option<(u32, EventTarget, RuntimeDeliveryMode)>) {
    RUNTIME_MOUNT_CONTEXT.with(|cell| {
        *cell.borrow_mut() = context.map(|(id, target, mode)| RuntimeMountContext {
            id,
            target,
            mode,
        });
    });
}

pub(crate) fn runtime_mount_context() -> Option<(u32, EventTarget, RuntimeDeliveryMode)> {
    RUNTIME_MOUNT_CONTEXT.with(|cell| {
        cell.borrow()
            .as_ref()
            .map(|context| (context.id, context.target.clone(), context.mode))
    })
}

pub(crate) fn runtime_delivery_mode() -> RuntimeDeliveryMode {
    resolve_runtime_delivery_mode(
        runtime_mount_context().map(|(_, _, mode)| mode),
        url_requests_embedded_mode(),
    )
}

pub(crate) fn set_runtime_mount_options(options: Option<MountOptions>, mode: RuntimeDeliveryMode) {
    RUNTIME_MOUNT_OPTIONS.with(|cell| {
        *cell.borrow_mut() = options.map(|options| RuntimeMountOptions { options, mode });
    });
}

pub(crate) fn runtime_editor_instance_id(mount_id: Option<u32>) -> String {
    mount_id
        .filter(|value| *value > 0)
        .map(|value| format!("mnote-leptos-tiptap-spike-{value}"))
        .unwrap_or_else(|| "mnote-leptos-tiptap-spike-standalone".to_string())
}

fn url_requests_embedded_mode() -> bool {
    window()
        .and_then(|win| win.location().search().ok())
        .map(|search| search.contains("embedded=1"))
        .unwrap_or(false)
}

pub(crate) fn resolve_runtime_delivery_mode(
    explicit_mode: Option<RuntimeDeliveryMode>,
    url_embedded: bool,
) -> RuntimeDeliveryMode {
    if let Some(mode) = explicit_mode {
        return mode;
    }

    if url_embedded {
        RuntimeDeliveryMode::Embedded
    } else {
        RuntimeDeliveryMode::Standalone
    }
}

pub(crate) fn mount_app_into(
    target: HtmlElement,
    options: MountOptions,
    mode: RuntimeDeliveryMode,
) -> u32 {
    let mount_id = next_mount_id();
    let context_target: EventTarget = target.clone().into();

    set_runtime_mount_options(Some(options.clone()), mode);
    set_runtime_mount_context(Some((mount_id, context_target.clone(), mode)));

    let handle = mount_to(target, move || {
        view! {
            <App mount_options=options />
        }
    });

    let command_listener = build_command_listener(mount_id);
    let _ = context_target
        .add_event_listener_with_callback(COMMAND_EVENT, command_listener.as_ref().unchecked_ref());

    register_unmount_handle(mount_id, context_target, command_listener, handle);
    mount_id
}

pub(crate) fn initial_content() -> TiptapContent {
    TiptapContent::json(serde_json::json!({
        "type": "doc",
        "content": []
    }))
}
