//! Scene editor components.

use leptos::prelude::*;
use uuid::Uuid;

use chattyness_db::models::SpotSummary;

/// Drawing mode for spot editor.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum DrawingMode {
    #[default]
    Select,
    Polygon,
    Rectangle,
}

/// Toolbar for selecting drawing mode.
#[component]
pub fn DrawingModeToolbar(
    #[prop(into)] mode: Signal<DrawingMode>,
    on_change: Callback<DrawingMode>,
) -> impl IntoView {
    view! {
        <div class="flex gap-2" role="radiogroup" aria-label="Drawing mode">
            <button
                type="button"
                class=move || {
                    let base = "px-3 py-1 rounded text-sm transition-colors";
                    if mode.get() == DrawingMode::Select {
                        format!("{} bg-blue-600 text-white", base)
                    } else {
                        format!("{} bg-gray-700 text-gray-300 hover:bg-gray-600", base)
                    }
                }
                on:click=move |_| on_change.run(DrawingMode::Select)
                aria-pressed=move || mode.get() == DrawingMode::Select
            >
                "Select"
            </button>
            <button
                type="button"
                class=move || {
                    let base = "px-3 py-1 rounded text-sm transition-colors";
                    if mode.get() == DrawingMode::Rectangle {
                        format!("{} bg-blue-600 text-white", base)
                    } else {
                        format!("{} bg-gray-700 text-gray-300 hover:bg-gray-600", base)
                    }
                }
                on:click=move |_| on_change.run(DrawingMode::Rectangle)
                aria-pressed=move || mode.get() == DrawingMode::Rectangle
            >
                "Rectangle"
            </button>
            <button
                type="button"
                class=move || {
                    let base = "px-3 py-1 rounded text-sm transition-colors";
                    if mode.get() == DrawingMode::Polygon {
                        format!("{} bg-blue-600 text-white", base)
                    } else {
                        format!("{} bg-gray-700 text-gray-300 hover:bg-gray-600", base)
                    }
                }
                on:click=move |_| on_change.run(DrawingMode::Polygon)
                aria-pressed=move || mode.get() == DrawingMode::Polygon
            >
                "Polygon"
            </button>
        </div>
    }
}

/// Canvas for displaying scene with spots.
#[component]
pub fn SceneCanvas(
    #[prop(into)] width: Signal<u32>,
    #[prop(into)] height: Signal<u32>,
    #[prop(into)] background_color: Signal<Option<String>>,
    #[prop(into)] background_image: Signal<Option<String>>,
    #[prop(into)] spots: Signal<Vec<SpotSummary>>,
    #[prop(into)] selected_spot_id: Signal<Option<Uuid>>,
    on_spot_click: Callback<Uuid>,
) -> impl IntoView {
    let canvas_style = Signal::derive(move || {
        let w = width.get();
        let h = height.get();
        let bg_color = background_color
            .get()
            .unwrap_or_else(|| "#1a1a2e".to_string());

        if let Some(img) = background_image.get() {
            format!(
                "width: {}px; height: {}px; background-image: url('{}'); background-size: cover; background-position: center;",
                w, h, img
            )
        } else {
            format!(
                "width: {}px; height: {}px; background-color: {};",
                w, h, bg_color
            )
        }
    });

    view! {
        <div class="relative overflow-auto border border-gray-700 rounded-lg bg-gray-900">
            <div class="relative" style=move || canvas_style.get()>
                {move || {
                    spots
                        .get()
                        .into_iter()
                        .map(|spot| {
                            let spot_id = spot.id;
                            let is_selected = selected_spot_id.get() == Some(spot_id);
                            let style = parse_wkt_to_style(&spot.region_wkt);
                            view! {
                                <div
                                    class=move || {
                                        let base = "absolute border-2 cursor-pointer transition-colors";
                                        if is_selected {
                                            format!("{} border-blue-500 bg-blue-500/30", base)
                                        } else {
                                            format!("{} border-green-500/50 bg-green-500/20 hover:bg-green-500/30", base)
                                        }
                                    }
                                    style=style
                                    on:click=move |_| on_spot_click.run(spot_id)
                                    role="button"
                                    tabindex="0"
                                />
                            }
                        })
                        .collect_view()
                }}
            </div>
        </div>
    }
}

/// Canvas for drawing new spots.
#[component]
pub fn SpotDrawer(
    #[prop(into)] width: Signal<u32>,
    #[prop(into)] height: Signal<u32>,
    #[prop(into)] mode: Signal<DrawingMode>,
    on_complete: Callback<String>,
    #[prop(into)] background_color: Signal<Option<String>>,
    #[prop(into)] background_image: Signal<Option<String>>,
    #[prop(into)] existing_spots_wkt: Signal<Vec<String>>,
) -> impl IntoView {
    let (drawing_points, set_drawing_points) = signal(Vec::<(f64, f64)>::new());
    let (is_drawing, set_is_drawing) = signal(false);
    let (start_point, set_start_point) = signal(Option::<(f64, f64)>::None);

    let canvas_style = Signal::derive(move || {
        let w = width.get();
        let h = height.get();
        let bg_color = background_color
            .get()
            .unwrap_or_else(|| "#1a1a2e".to_string());

        if let Some(img) = background_image.get() {
            format!(
                "width: {}px; height: {}px; background-image: url('{}'); background-size: cover; background-position: center; cursor: crosshair;",
                w, h, img
            )
        } else {
            format!(
                "width: {}px; height: {}px; background-color: {}; cursor: crosshair;",
                w, h, bg_color
            )
        }
    });

    let on_mouse_down = move |ev: leptos::ev::MouseEvent| {
        #[cfg(feature = "hydrate")]
        {
            let rect = ev.target().and_then(|t| {
                use wasm_bindgen::JsCast;
                t.dyn_ref::<web_sys::HtmlElement>()
                    .map(|el| el.get_bounding_client_rect())
            });

            if let Some(rect) = rect {
                let x = ev.client_x() as f64 - rect.left();
                let y = ev.client_y() as f64 - rect.top();

                match mode.get() {
                    DrawingMode::Rectangle => {
                        set_start_point.set(Some((x, y)));
                        set_is_drawing.set(true);
                    }
                    DrawingMode::Polygon => {
                        let mut points = drawing_points.get();
                        points.push((x, y));
                        set_drawing_points.set(points);
                    }
                    DrawingMode::Select => {}
                }
            }
        }
    };

    let on_mouse_up = move |ev: leptos::ev::MouseEvent| {
        #[cfg(feature = "hydrate")]
        {
            if mode.get() == DrawingMode::Rectangle && is_drawing.get() {
                if let Some((start_x, start_y)) = start_point.get() {
                    let rect = ev.target().and_then(|t| {
                        use wasm_bindgen::JsCast;
                        t.dyn_ref::<web_sys::HtmlElement>()
                            .map(|el| el.get_bounding_client_rect())
                    });

                    if let Some(rect) = rect {
                        let end_x = ev.client_x() as f64 - rect.left();
                        let end_y = ev.client_y() as f64 - rect.top();

                        let min_x = start_x.min(end_x);
                        let min_y = start_y.min(end_y);
                        let max_x = start_x.max(end_x);
                        let max_y = start_y.max(end_y);

                        if (max_x - min_x) > 10.0 && (max_y - min_y) > 10.0 {
                            let wkt = format!(
                                "POLYGON(({} {}, {} {}, {} {}, {} {}, {} {}))",
                                min_x,
                                min_y,
                                max_x,
                                min_y,
                                max_x,
                                max_y,
                                min_x,
                                max_y,
                                min_x,
                                min_y
                            );
                            on_complete.run(wkt);
                        }
                    }
                }
                set_is_drawing.set(false);
                set_start_point.set(None);
            }
        }
    };

    let on_double_click = move |_| {
        #[cfg(feature = "hydrate")]
        {
            if mode.get() == DrawingMode::Polygon {
                let points = drawing_points.get();
                if points.len() >= 3 {
                    let wkt = points_to_wkt(&points);
                    on_complete.run(wkt);
                }
                set_drawing_points.set(Vec::new());
            }
        }
    };

    view! {
        <div class="relative overflow-auto border border-gray-700 rounded-lg bg-gray-900">
            <div
                class="relative"
                style=move || canvas_style.get()
                on:mousedown=on_mouse_down
                on:mouseup=on_mouse_up
                on:dblclick=on_double_click
            >
                // Render existing spots
                {move || {
                    existing_spots_wkt
                        .get()
                        .into_iter()
                        .map(|wkt| {
                            let style = parse_wkt_to_style(&wkt);
                            view! {
                                <div
                                    class="absolute border-2 border-gray-500/50 bg-gray-500/20"
                                    style=style
                                />
                            }
                        })
                        .collect_view()
                }}

                // Render drawing preview
                {move || {
                    let points = drawing_points.get();
                    if !points.is_empty() && mode.get() == DrawingMode::Polygon {
                        let svg_points: String = points
                            .iter()
                            .map(|(x, y)| format!("{},{}", x, y))
                            .collect::<Vec<_>>()
                            .join(" ");
                        Some(view! {
                            <svg class="absolute inset-0 pointer-events-none">
                                <polyline
                                    points=svg_points
                                    fill="none"
                                    stroke="#3b82f6"
                                    stroke-width="2"
                                />
                            </svg>
                        })
                    } else {
                        None
                    }
                }}
            </div>
        </div>
    }
}

/// Parse WKT polygon to CSS positioning style.
fn parse_wkt_to_style(wkt: &str) -> String {
    let trimmed = wkt.trim();
    if let Some(coords_str) = trimmed
        .strip_prefix("POLYGON((")
        .and_then(|s| s.strip_suffix("))"))
    {
        let points: Vec<(f64, f64)> = coords_str
            .split(',')
            .filter_map(|p| {
                let coords: Vec<&str> = p.trim().split_whitespace().collect();
                if coords.len() >= 2 {
                    Some((coords[0].parse().ok()?, coords[1].parse().ok()?))
                } else {
                    None
                }
            })
            .collect();

        if points.len() >= 4 {
            let min_x = points.iter().map(|(x, _)| *x).fold(f64::INFINITY, f64::min);
            let min_y = points.iter().map(|(_, y)| *y).fold(f64::INFINITY, f64::min);
            let max_x = points
                .iter()
                .map(|(x, _)| *x)
                .fold(f64::NEG_INFINITY, f64::max);
            let max_y = points
                .iter()
                .map(|(_, y)| *y)
                .fold(f64::NEG_INFINITY, f64::max);

            return format!(
                "left: {}px; top: {}px; width: {}px; height: {}px;",
                min_x,
                min_y,
                max_x - min_x,
                max_y - min_y
            );
        }
    }
    String::new()
}

/// Convert points to WKT polygon.
#[allow(dead_code)]
fn points_to_wkt(points: &[(f64, f64)]) -> String {
    if points.is_empty() {
        return String::new();
    }

    let coords: String = points
        .iter()
        .chain(std::iter::once(&points[0]))
        .map(|(x, y)| format!("{} {}", x, y))
        .collect::<Vec<_>>()
        .join(", ");

    format!("POLYGON(({})", coords)
}
