use itertools::Itertools;
use sycamore::{prelude::*, web::tags::div};
use web_sys::{
    Element,
    Event,
    HtmlInputElement,
    KeyboardEvent,
    MouseEvent,
    wasm_bindgen::JsCast
};

use crate::{AppState, assembly::Constraint};

// an editable view of the Lorentz product representing a constraint
#[component(inline_props)]
fn LorentzProductInput(constraint: Constraint) -> View {
    view! {
        input(
            r#type="text",
            bind:value=constraint.rep_text,
            on:change=move |event: Event| {
                let target: HtmlInputElement = event.target().unwrap().unchecked_into();
                match target.value().parse::<f64>() {
                    Ok(rep) => batch(|| {
                        constraint.rep.set(rep);
                        constraint.rep_valid.set(true);
                    }),
                    Err(_) => constraint.rep_valid.set(false)
                };
            }
        )
    }
}

// a component that lists the elements of the current assembly, showing the
// constraints on each element as a collapsible sub-list. its implementation
// is based on Kate Morley's HTML + CSS tree views:
//
//   https://iamkate.com/code/tree-views/
//
#[component]
pub fn Outline() -> View {
    // sort the elements alphabetically by ID
    let elements_sorted = create_memo(|| {
        let state = use_context::<AppState>();
        state.assembly.elements
            .get_clone()
            .into_iter()
            .sorted_by_key(|(_, elt)| elt.id.clone())
            .collect()
    });
    
    view! {
        ul(
            id="outline",
            on:click={
                let state = use_context::<AppState>();
                move |_| state.selection.update(|sel| sel.clear())
            }
        ) {
            Keyed(
                list=elements_sorted,
                view=|(key, elt)| {
                    let state = use_context::<AppState>();
                    let class = create_memo({
                        move || {
                            if state.selection.with(|sel| sel.contains(&key)) {
                                "selected"
                            } else {
                                ""
                            }
                        }
                    });
                    let label = elt.label.clone();
                    let rep_components = elt.representation.iter().map(|u| {
                        let u_coord = u.to_string().replace("-", "\u{2212}");
                        View::from(div().children(u_coord))
                    }).collect::<Vec<_>>();
                    let constrained = elt.constraints.len() > 0;
                    let details_node = create_node_ref();
                    view! {
                        li {
                            details(ref=details_node) {
                                summary(
                                    class=class.get(),
                                    on:keydown={
                                        move |event: KeyboardEvent| {
                                            match event.key().as_str() {
                                                "Enter" => {
                                                    if event.shift_key() {
                                                        state.selection.update(|sel| {
                                                            if !sel.remove(&key) {
                                                                sel.insert(key);
                                                            }
                                                        });
                                                    } else {
                                                        state.selection.update(|sel| {
                                                            sel.clear();
                                                            sel.insert(key);
                                                        });
                                                    }
                                                    event.prevent_default();
                                                },
                                                "ArrowRight" if constrained => {
                                                    let _ = details_node
                                                        .get()
                                                        .unchecked_into::<Element>()
                                                        .set_attribute("open", "");
                                                },
                                                "ArrowLeft" => {
                                                    let _ = details_node
                                                        .get()
                                                        .unchecked_into::<Element>()
                                                        .remove_attribute("open");
                                                },
                                                _ => ()
                                            }
                                        }
                                    }
                                ) {
                                    div(
                                        class="elt-switch",
                                        on:click=|event: MouseEvent| event.stop_propagation()
                                    )
                                    div(
                                        class="elt",
                                        on:click={
                                            move |event: MouseEvent| {
                                                if event.shift_key() {
                                                    state.selection.update(|sel| {
                                                        if !sel.remove(&key) {
                                                            sel.insert(key);
                                                        }
                                                    });
                                                } else {
                                                    state.selection.update(|sel| {
                                                        sel.clear();
                                                        sel.insert(key);
                                                    });
                                                }
                                                event.stop_propagation();
                                                event.prevent_default();
                                            }
                                        }
                                    ) {
                                        div(class="elt-label") { (label) }
                                        div(class="elt-rep") { (rep_components) }
                                    }
                                }
                                ul(class="constraints") {
                                    Keyed(
                                        list=elt.constraints.into_iter().collect::<Vec<_>>(),
                                        view=move |c_key| {
                                            let c_state = use_context::<AppState>();
                                            let assembly = &c_state.assembly;
                                            let cst = assembly.constraints.with(|csts| csts[c_key].clone());
                                            let other_arg = if cst.subjects.0 == key {
                                                cst.subjects.1
                                            } else {
                                                cst.subjects.0
                                            };
                                            let other_arg_label = assembly.elements.with(|elts| elts[other_arg].label.clone());
                                            view! {
                                                li(class="cst") {
                                                    input(r#type="checkbox", bind:checked=cst.active)
                                                    div(class="cst-label") { (other_arg_label) }
                                                    LorentzProductInput(constraint=cst)
                                                }
                                            }
                                        },
                                        key=|c_key| c_key.clone()
                                    )
                                }
                            }
                        }
                    }
                },
                key=|(key, elt)| (
                    key.clone(),
                    elt.id.clone(),
                    elt.label.clone(),
                    elt.constraints.clone()
                )
            )
        }
    }
}