//! PDF viewer tab: embeds the Tauri-served PDF viewer in an iframe.
//!
//! The custom `brittle://` URI scheme serves:
//!   - `brittle://app/viewer?ref_id=<uuid>` — the viewer HTML page (PDF.js)
//!   - `brittle://app/pdf?ref_id=<uuid>`    — the raw PDF bytes
//!
//! Using an `<iframe>` keeps the viewer alive when the tab is hidden (via
//! `display:none`), so scrolling position and zoom are preserved across
//! tab switches.
//!
//! ## Annotation flow
//!
//! ```text
//! iframe (viewer.js)                   Leptos (this component)
//! ─────────────────────────────────    ──────────────────────────────────
//! brittle:annotations-request      →   get_annotations (Tauri IPC)
//!                                  ←   brittle:annotations-set
//! brittle:annotation-create        →   create_annotation (Tauri IPC)
//!                                  ←   brittle:annotations-set (updated)
//! brittle:annotation-click         →   show comment editor
//! (save / delete in comment editor) →  update_annotation / delete_annotation
//!                                  ←   brittle:annotations-set (updated)
//! ```

use brittle_keymap::actions;
use brittle_model::{Annotation, AnnotationId, AnnotationType, Color, Quad, ReferenceId,
                    TextMarkupType};
use leptos::prelude::*;
use uuid::Uuid;
use wasm_bindgen::JsValue;

// ── Helper types for IPC message deserialization ──────────────────────────────

#[derive(serde::Deserialize)]
struct IframeMsgHeader {
    #[serde(rename = "type")]
    kind: String,
    #[serde(rename = "refId", default)]
    ref_id: String,
}

#[derive(serde::Deserialize)]
struct IframeAnnotCreateMsg {
    page: u32,
    quads: Vec<Quad>,
    #[serde(rename = "selectedText")]
    selected_text: Option<String>,
}

#[derive(serde::Deserialize)]
struct IframeAnnotClickMsg {
    #[serde(rename = "annotationId")]
    annotation_id: String,
}

// ── Outbound message ──────────────────────────────────────────────────────────

#[derive(serde::Serialize)]
struct AnnotationsSetMsg<'a> {
    #[serde(rename = "type")]
    kind: &'static str,
    annotations: &'a [Annotation],
}

// ── Component ─────────────────────────────────────────────────────────────────

/// Renders the PDF viewer for a single reference.
///
/// `ref_id` must be the UUID string of a reference that has an attached PDF.
/// `is_active` indicates whether this is the currently visible PDF tab; when
/// `true`, keymap actions for PDF page navigation are forwarded into the iframe.
#[component]
pub fn PdfViewer(
    ref_id: String,
    is_active: Signal<bool>,
    initial_zoom: Option<f64>,
    initial_scroll_top: Option<f64>,
) -> impl IntoView {
    let mut url = format!("brittle://app/viewer?ref_id={ref_id}");
    if let Some(z) = initial_zoom       { url.push_str(&format!("&zoom={z}")); }
    if let Some(s) = initial_scroll_top { url.push_str(&format!("&scroll_top={s}")); }

    let iframe_ref = NodeRef::<leptos::html::Iframe>::new();

    // Store ref_id in a Copy handle so it can be captured in multiple closures
    let ref_id_sv: StoredValue<String> = StoredValue::new(ref_id);

    // Per-tab annotation state
    let annotations: RwSignal<Vec<Annotation>> = RwSignal::new(vec![]);

    // (annotation_id_string, current_content) — Some when comment editor is open
    let selected_annotation: RwSignal<Option<(String, Option<String>)>> = RwSignal::new(None);
    let comment_text: RwSignal<String> = RwSignal::new(String::new());

    // ── Keymap → iframe forwarding ────────────────────────────────────────────
    let keymap_action = use_context::<crate::KeymapAction>()
        .expect("KeymapAction context missing")
        .0;

    Effect::new(move |_| {
        let Some(ev) = keymap_action.get() else { return };
        if !is_active.get_untracked() { return }

        let cmd = match ev.name.as_str() {
            actions::PDF_PAGE_NEXT => actions::PDF_PAGE_NEXT,
            actions::PDF_PAGE_PREV => actions::PDF_PAGE_PREV,
            _ => return,
        };

        let Some(iframe) = iframe_ref.get() else { return };
        if let Some(win) = iframe.content_window() {
            let _ = win.post_message(&JsValue::from_str(cmd), "*");
        }
    });

    // ── Window message listener for annotation IPC ────────────────────────────
    {
        use wasm_bindgen::{closure::Closure, JsCast as _};

        let cb: Closure<dyn Fn(web_sys::MessageEvent)> =
            Closure::new(move |ev: web_sys::MessageEvent| {
                let Ok(header) =
                    serde_wasm_bindgen::from_value::<IframeMsgHeader>(ev.data())
                else {
                    return;
                };
                if header.ref_id != ref_id_sv.get_value() { return; }

                match header.kind.as_str() {
                    // Viewer is ready and asking for the annotation set
                    "brittle:annotations-request" => {
                        let anns = annotations.get_untracked();
                        send_annotations(&anns, &iframe_ref);
                    }

                    // User finished selecting text → create a highlight
                    "brittle:annotation-create" => {
                        let Ok(msg) =
                            serde_wasm_bindgen::from_value::<IframeAnnotCreateMsg>(ev.data())
                        else {
                            return;
                        };
                        let ref_id_c = ref_id_sv.get_value();
                        leptos::task::spawn_local(async move {
                            let Ok(uuid) = Uuid::parse_str(&ref_id_c) else { return };
                            let reference_id = ReferenceId(uuid);
                            let annotation_type = AnnotationType::TextMarkup {
                                markup_type: TextMarkupType::Highlight,
                                quads: msg.quads,
                                color: Color::YELLOW,
                                selected_text: msg.selected_text,
                            };
                            if crate::tauri::create_annotation(
                                &reference_id,
                                msg.page,
                                annotation_type,
                                None,
                            )
                            .await
                            .is_err()
                            {
                                return;
                            }
                            // Reload the full set and push to the iframe
                            if let Ok(anns) =
                                crate::tauri::get_annotations(&reference_id).await
                            {
                                annotations.set(anns.clone());
                                send_annotations(&anns, &iframe_ref);
                            }
                        });
                    }

                    // User clicked an existing annotation → open comment editor
                    "brittle:annotation-click" => {
                        let Ok(msg) =
                            serde_wasm_bindgen::from_value::<IframeAnnotClickMsg>(ev.data())
                        else {
                            return;
                        };
                        let content = annotations.with_untracked(|anns| {
                            anns.iter()
                                .find(|a| a.id.to_string() == msg.annotation_id)
                                .and_then(|a| a.content.clone())
                        });
                        comment_text.set(content.clone().unwrap_or_default());
                        selected_annotation.set(Some((msg.annotation_id, content)));
                    }

                    _ => {}
                }
            });

        // Keep the JS function ref for cleanup so the listener is removed when
        // the tab is closed.
        let js_fn = cb.as_ref().unchecked_ref::<js_sys::Function>().clone();
        if let Some(win) = web_sys::window() {
            let _ = win.add_event_listener_with_callback("message", &js_fn);
        }
        let js_fn_cleanup = js_fn;
        on_cleanup(move || {
            if let Some(win) = web_sys::window() {
                let _ = win.remove_event_listener_with_callback("message", &js_fn_cleanup);
            }
        });
        cb.forget();
    }

    // ── Initial annotation load ───────────────────────────────────────────────
    // The viewer also sends brittle:annotations-request on startup, but pre-
    // loading here means the response is instant when the request arrives.
    Effect::new(move |_| {
        leptos::task::spawn_local(async move {
            let Ok(uuid) = Uuid::parse_str(&ref_id_sv.get_value()) else { return };
            let reference_id = ReferenceId(uuid);
            if let Ok(anns) = crate::tauri::get_annotations(&reference_id).await {
                annotations.set(anns);
            }
        });
    });

    // ── View ──────────────────────────────────────────────────────────────────
    view! {
        <div class="pdf-tab-container">
            <iframe
                node_ref=iframe_ref
                class="pdf-frame"
                src=url
                // Intentionally no `sandbox` attribute — the brittle:// protocol
                // and PDF.js require unrestricted access within the webview.
            />
            <Show when=move || selected_annotation.get().is_some()>
                <div class="comment-editor">
                    <div class="comment-editor-header">
                        <span>"Highlight note"</span>
                        <button class="comment-close" on:click=move |_| {
                            selected_annotation.set(None);
                            comment_text.set(String::new());
                        }>"×"</button>
                    </div>
                    <textarea
                        class="comment-textarea"
                        prop:value=move || comment_text.get()
                        on:input=move |ev| comment_text.set(event_target_value(&ev))
                        placeholder="Add a note to this highlight…"
                    />
                    <div class="comment-actions">
                        <button class="comment-save" on:click=move |_| {
                            let Some((ann_id_str, _)) = selected_annotation.get_untracked()
                            else { return };
                            let text = comment_text.get_untracked();
                            leptos::task::spawn_local(async move {
                                let Ok(ref_uuid) = Uuid::parse_str(&ref_id_sv.get_value())
                                else { return };
                                let reference_id = ReferenceId(ref_uuid);
                                let ann_opt = annotations.with_untracked(|anns| {
                                    anns.iter()
                                        .find(|a| a.id.to_string() == ann_id_str)
                                        .cloned()
                                });
                                let Some(mut ann) = ann_opt else { return };
                                ann.content = if text.is_empty() { None } else { Some(text) };
                                if crate::tauri::update_annotation(ann).await.is_ok() {
                                    if let Ok(anns) =
                                        crate::tauri::get_annotations(&reference_id).await
                                    {
                                        annotations.set(anns.clone());
                                        send_annotations(&anns, &iframe_ref);
                                    }
                                }
                                selected_annotation.set(None);
                            });
                        }>"Save"</button>
                        <button class="comment-delete" on:click=move |_| {
                            let Some((ann_id_str, _)) = selected_annotation.get_untracked()
                            else { return };
                            leptos::task::spawn_local(async move {
                                let Ok(ref_uuid) = Uuid::parse_str(&ref_id_sv.get_value())
                                else { return };
                                let Ok(ann_uuid) = Uuid::parse_str(&ann_id_str) else { return };
                                let reference_id  = ReferenceId(ref_uuid);
                                let annotation_id = AnnotationId(ann_uuid);
                                if crate::tauri::delete_annotation(&reference_id, &annotation_id)
                                    .await
                                    .is_ok()
                                {
                                    if let Ok(anns) =
                                        crate::tauri::get_annotations(&reference_id).await
                                    {
                                        annotations.set(anns.clone());
                                        send_annotations(&anns, &iframe_ref);
                                    }
                                }
                                selected_annotation.set(None);
                            });
                        }>"Delete highlight"</button>
                    </div>
                </div>
            </Show>
        </div>
    }
}

// ── Helpers ───────────────────────────────────────────────────────────────────

/// Serialize `annotations` to a `brittle:annotations-set` message and post it
/// into the given iframe's content window.
fn send_annotations(annotations: &[Annotation], iframe_ref: &NodeRef<leptos::html::Iframe>) {
    let Some(iframe) = iframe_ref.get_untracked() else { return };
    let Some(win) = iframe.content_window() else { return };
    let msg = AnnotationsSetMsg { kind: "brittle:annotations-set", annotations };
    let Ok(js_val) = serde_wasm_bindgen::to_value(&msg) else { return };
    let _ = win.post_message(&js_val, "*");
}
