use std::collections::HashMap;
use std::sync::Arc;

use leptos::html;
use leptos::prelude::*;
use serde::{Deserialize, Serialize};
use wasm_bindgen::prelude::*;
use web_sys::{DragEvent, HtmlElement, MouseEvent, TouchEvent};

#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq)]
pub struct GridPosition {
    pub x: i32,
    pub y: i32,
}

#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq)]
pub struct GridSize {
    pub width: i32,
    pub height: i32,
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct GridLayout {
    pub columns: i32,
    pub row_height: i32,
    pub margin: (i32, i32),
    pub container_padding: (i32, i32),
    pub breakpoints: HashMap<String, BreakpointConfig>,
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct BreakpointConfig {
    pub columns: i32,
    pub margin: (i32, i32),
    pub container_padding: (i32, i32),
}

impl Default for GridLayout {
    fn default() -> Self {
        let mut breakpoints = HashMap::new();

        breakpoints.insert(
            "lg".to_string(),
            BreakpointConfig {
                columns: 12,
                margin: (10, 10),
                container_padding: (10, 10),
            },
        );

        breakpoints.insert(
            "md".to_string(),
            BreakpointConfig {
                columns: 10,
                margin: (8, 8),
                container_padding: (8, 8),
            },
        );

        breakpoints.insert(
            "sm".to_string(),
            BreakpointConfig {
                columns: 6,
                margin: (5, 5),
                container_padding: (5, 5),
            },
        );

        breakpoints.insert(
            "xs".to_string(),
            BreakpointConfig {
                columns: 4,
                margin: (3, 3),
                container_padding: (3, 3),
            },
        );

        Self {
            columns: 12,
            row_height: 30,
            margin: (10, 10),
            container_padding: (10, 10),
            breakpoints,
        }
    }
}

#[component]
pub fn DashboardGrid(
    layout: ReadSignal<GridLayout>,
    is_editing: ReadSignal<bool>,
    is_mobile: ReadSignal<bool>,
    on_layout_change: Box<dyn Fn(GridLayout) + 'static + Send + Sync>,
    children: Children,
) -> impl IntoView {
    let container_ref = NodeRef::new();
    let (drag_state, set_drag_state) = signal(Option::<DragState>::None);
    let (container_width, set_container_width) = signal(1200i32);

    // Responsive breakpoint detection
    let current_breakpoint = Memo::new(move |_| {
        let width = container_width.get();
        if width >= 1200 {
            "lg"
        } else if width >= 996 {
            "md"
        } else if width >= 768 {
            "sm"
        } else {
            "xs"
        }
    });

    // Update layout based on breakpoint
    Effect::new(move |_| {
        let breakpoint = current_breakpoint.get();
        let current_layout = layout.get();

        // Check if breakpoint config exists and extract values
        if let Some(bp_config) = current_layout.breakpoints.get(breakpoint) {
            let bp_config_copy = bp_config.clone();
            let mut new_layout = current_layout;
            new_layout.columns = bp_config_copy.columns;
            new_layout.margin = bp_config_copy.margin;
            new_layout.container_padding = bp_config_copy.container_padding;
            on_layout_change(new_layout);
        }
    });

    // Resize observer for responsive behavior
    Effect::new(move |_| {
        if let Some(el) = container_ref.get() {
            if let Ok(container) =
                wasm_bindgen::prelude::JsCast::dyn_into::<web_sys::HtmlElement>(el)
            {
                let container_clone = container.clone();
                let set_width = set_container_width;

                let closure = Closure::wrap(Box::new(move |entries: js_sys::Array| {
                    if let Ok(entry) = entries.get(0).dyn_into::<web_sys::ResizeObserverEntry>() {
                        let content_rect = entry.content_rect();
                        set_width.set(content_rect.width() as i32);
                    }
                }) as Box<dyn FnMut(js_sys::Array)>);

                let observer =
                    web_sys::ResizeObserver::new(closure.as_ref().unchecked_ref()).unwrap();
                observer.observe(&container_clone);
                closure.forget();
            }
        }
    });

    let grid_style = Memo::new(move |_| {
        let layout = layout.get();
        let (pad_x, pad_y) = layout.container_padding;

        format!(
            "padding: {}px {}px; min-height: 100vh; position: relative; background: \
             var(--bg-primary);",
            pad_y, pad_x
        )
    });

    // Drag and drop handlers
    let on_drag_over = move |event: DragEvent| {
        event.prevent_default();
        event.data_transfer().unwrap().set_drop_effect("move");
    };

    let on_drop = move |event: DragEvent| {
        event.prevent_default();

        if let Some(data_transfer) = event.data_transfer() {
            if let Ok(widget_data) = data_transfer.get_data("application/json") {
                if let Ok(drop_data) = serde_json::from_str::<DropData>(&widget_data) {
                    // Calculate grid position from mouse coordinates
                    if let Some(el) = container_ref.get() {
                        if let Ok(container) =
                            wasm_bindgen::prelude::JsCast::dyn_into::<web_sys::Element>(el)
                        {
                            let rect = container.get_bounding_client_rect();
                            let x = event.client_x() as f64 - rect.left();
                            let y = event.client_y() as f64 - rect.top();

                            let grid_pos =
                                pixel_to_grid_position(x, y, &layout.get(), container_width.get());

                            // Emit drop event with calculated position
                            web_sys::console::log_2(
                                &"Widget dropped at position:".into(),
                                &format!("x: {}, y: {}", grid_pos.x, grid_pos.y).into(),
                            );
                        }
                    }
                }
            }
        }

        set_drag_state.set(None);
    };

    view! {
        <div
            node_ref=container_ref
            class=move || format!(
                "dashboard-grid {} {}",
                if is_editing.get() { "editing" } else { "" },
                if is_mobile.get() { "mobile" } else { "desktop" }
            )
            style=move || grid_style.get()
            on:dragover=on_drag_over
            on:drop=on_drop
        >
            <div class="grid-background">
                <GridBackground
                    layout=layout
                    container_width=container_width
                    show_grid=is_editing
                />
            </div>

            <div class="grid-items">
                {children()}
            </div>

            // Drop indicator
            <Show when=move || drag_state.get().is_some()>
                <div class="drop-indicator">
                    // Visual indicator for where item will be dropped
                </div>
            </Show>
        </div>
    }
}

#[component]
pub fn GridItem(
    id: String,
    position: GridPosition,
    size: GridSize,
    draggable: ReadSignal<bool>,
    #[prop(optional)] on_drag_start: Option<Box<dyn Fn(DragEvent) + 'static + Send + Sync>>,
    #[prop(optional)] on_resize: Option<Box<dyn Fn(GridSize) + 'static + Send + Sync>>,
    #[prop(optional)] on_remove: Option<Box<dyn Fn() + 'static + Send + Sync>>,
    children: Children,
) -> impl IntoView {
    let item_ref = NodeRef::new();
    let (is_dragging, set_is_dragging) = signal(false);
    let (is_resizing, set_is_resizing) = signal(false);
    let (current_position, set_current_position) = signal(position);
    let (current_size, set_current_size) = signal(size);

    // Calculate item style based on grid position and size
    let item_style = Memo::new(move |_| {
        let pos = current_position.get();
        let size = current_size.get();

        // This would be calculated based on the grid layout
        // For now, using a simple calculation
        let x = pos.x * 100; // Column width in pixels
        let y = pos.y * 40; // Row height in pixels
        let width = size.width * 100 - 10; // Account for margins
        let height = size.height * 40 - 10;

        format!(
            "position: absolute; left: {}px; top: {}px; width: {}px; height: {}px; z-index: {};",
            x,
            y,
            width,
            height,
            if is_dragging.get() { 1000 } else { 1 }
        )
    });

    let drag_start_handler = {
        let id_clone = id.clone();
        move |event: DragEvent| {
            set_is_dragging.set(true);

            // Set drag data
            let drag_data = DropData {
                widget_id: id_clone.clone(),
                widget_type: "existing".to_string(),
                original_position: current_position.get(),
                original_size: current_size.get(),
            };

            if let Ok(data_json) = serde_json::to_string(&drag_data) {
                event
                    .data_transfer()
                    .unwrap()
                    .set_data("application/json", &data_json)
                    .unwrap();
            }

            // Call custom handler if provided
            if let Some(handler) = &on_drag_start {
                handler(event.clone());
            }
        }
    };

    let drag_end_handler = move |_event: DragEvent| {
        set_is_dragging.set(false);
    };

    // Wrap on_resize in Arc so it can be referenced safely
    let on_resize_rc: Arc<Option<Box<dyn Fn(GridSize) + 'static + Send + Sync>>> =
        Arc::new(on_resize);

    // Clone for each handler to avoid move issues
    let on_resize_rc_e = Arc::clone(&on_resize_rc);
    let on_resize_rc_s = Arc::clone(&on_resize_rc);
    let on_resize_rc_se = Arc::clone(&on_resize_rc);

    // Create East resize handler
    let on_mousedown_e = move |event: MouseEvent| {
        event.prevent_default();
        set_is_resizing.set(true);

        let start_x = event.client_x();
        let start_size = current_size.get();

        let document = web_sys::window().unwrap().document().unwrap();

        let mouse_move_closure = Closure::wrap(Box::new(move |event: MouseEvent| {
            let delta_x = event.client_x() - start_x;

            let mut new_size = start_size;
            new_size.width = (start_size.width as f64 + delta_x as f64 / 100.0) as i32;

            // Constrain to minimum size
            new_size.width = new_size.width.max(1);

            set_current_size.set(new_size);
        }) as Box<dyn FnMut(MouseEvent)>);

        let on_resize_clone_e = Arc::clone(&on_resize_rc_e);
        let mouse_up_closure = Closure::wrap(Box::new(move |_event: MouseEvent| {
            set_is_resizing.set(false);

            if let Some(handler) = on_resize_clone_e.as_ref() {
                handler(current_size.get());
            }
        }) as Box<dyn FnMut(MouseEvent)>);

        document
            .add_event_listener_with_callback(
                "mousemove",
                mouse_move_closure.as_ref().unchecked_ref(),
            )
            .unwrap();
        document
            .add_event_listener_with_callback("mouseup", mouse_up_closure.as_ref().unchecked_ref())
            .unwrap();

        mouse_move_closure.forget();
        mouse_up_closure.forget();
    };

    // Create South resize handler
    let on_mousedown_s = move |event: MouseEvent| {
        event.prevent_default();
        set_is_resizing.set(true);

        let start_y = event.client_y();
        let start_size = current_size.get();

        let document = web_sys::window().unwrap().document().unwrap();

        let mouse_move_closure = Closure::wrap(Box::new(move |event: MouseEvent| {
            let delta_y = event.client_y() - start_y;

            let mut new_size = start_size;
            new_size.height = (start_size.height as f64 + delta_y as f64 / 40.0) as i32;

            // Constrain to minimum size
            new_size.height = new_size.height.max(1);

            set_current_size.set(new_size);
        }) as Box<dyn FnMut(MouseEvent)>);

        let on_resize_clone_s = Arc::clone(&on_resize_rc_s);
        let mouse_up_closure = Closure::wrap(Box::new(move |_event: MouseEvent| {
            set_is_resizing.set(false);

            if let Some(handler) = on_resize_clone_s.as_ref() {
                handler(current_size.get());
            }
        }) as Box<dyn FnMut(MouseEvent)>);

        document
            .add_event_listener_with_callback(
                "mousemove",
                mouse_move_closure.as_ref().unchecked_ref(),
            )
            .unwrap();
        document
            .add_event_listener_with_callback("mouseup", mouse_up_closure.as_ref().unchecked_ref())
            .unwrap();

        mouse_move_closure.forget();
        mouse_up_closure.forget();
    };

    // Create Southeast resize handler
    let on_mousedown_se = move |event: MouseEvent| {
        event.prevent_default();
        set_is_resizing.set(true);

        let start_x = event.client_x();
        let start_y = event.client_y();
        let start_size = current_size.get();

        let document = web_sys::window().unwrap().document().unwrap();

        let mouse_move_closure = Closure::wrap(Box::new(move |event: MouseEvent| {
            let delta_x = event.client_x() - start_x;
            let delta_y = event.client_y() - start_y;

            let mut new_size = start_size;
            new_size.width = (start_size.width as f64 + delta_x as f64 / 100.0) as i32;
            new_size.height = (start_size.height as f64 + delta_y as f64 / 40.0) as i32;

            // Constrain to minimum size
            new_size.width = new_size.width.max(1);
            new_size.height = new_size.height.max(1);

            set_current_size.set(new_size);
        }) as Box<dyn FnMut(MouseEvent)>);

        let on_resize_clone_se = Arc::clone(&on_resize_rc_se);
        let mouse_up_closure = Closure::wrap(Box::new(move |_event: MouseEvent| {
            set_is_resizing.set(false);

            if let Some(handler) = on_resize_clone_se.as_ref() {
                handler(current_size.get());
            }
        }) as Box<dyn FnMut(MouseEvent)>);

        document
            .add_event_listener_with_callback(
                "mousemove",
                mouse_move_closure.as_ref().unchecked_ref(),
            )
            .unwrap();
        document
            .add_event_listener_with_callback("mouseup", mouse_up_closure.as_ref().unchecked_ref())
            .unwrap();

        mouse_move_closure.forget();
        mouse_up_closure.forget();
    };

    // Wrap on_remove in Arc to allow sharing across multiple closures
    let on_remove_rc: Arc<Option<Box<dyn Fn() + 'static + Send + Sync>>> = Arc::new(on_remove);

    // Wrap handlers in Arc BEFORE the view! macro to avoid moving them inside the
    // macro
    let on_mousedown_e_rc: Arc<dyn Fn(MouseEvent) + Send + Sync> = Arc::new(on_mousedown_e);
    let on_mousedown_s_rc: Arc<dyn Fn(MouseEvent) + Send + Sync> = Arc::new(on_mousedown_s);
    let on_mousedown_se_rc: Arc<dyn Fn(MouseEvent) + Send + Sync> = Arc::new(on_mousedown_se);

    view! {
        <div
            node_ref=item_ref
            class=move || format!(
                "grid-item {} {} {}",
                if draggable.get() { "draggable" } else { "" },
                if is_dragging.get() { "dragging" } else { "" },
                if is_resizing.get() { "resizing" } else { "" }
            )
            style=move || item_style.get()
            draggable=move || draggable.get()
            on:dragstart=drag_start_handler
            on:dragend=drag_end_handler
        >
            // Widget controls (visible in editing mode)
            <Show when=move || draggable.get()>
                <div class="widget-controls">
                    <div class="drag-handle">
                        <i class="bi-arrows-move"></i>
                    </div>

                    <RemoveButton on_remove=Arc::clone(&on_remove_rc) />
                </div>
            </Show>

            // Widget content
            <div class="widget-content">
                {children()}
            </div>

            // Resize handles (visible when draggable)
            <Show when=move || draggable.get()>
                <ResizeHandles
                    on_mousedown_e=Arc::clone(&on_mousedown_e_rc)
                    on_mousedown_s=Arc::clone(&on_mousedown_s_rc)
                    on_mousedown_se=Arc::clone(&on_mousedown_se_rc)
                />
            </Show>
        </div>
    }
}

/// RemoveButton component - handles the remove button logic in isolation
#[component]
fn RemoveButton(on_remove: Arc<Option<Box<dyn Fn() + 'static + Send + Sync>>>) -> impl IntoView {
    let on_remove_sig = signal(Arc::clone(&on_remove));

    view! {
        <Show when=move || on_remove_sig.0.get().as_ref().is_some()>
            <button
                class="control-btn remove-btn"
                on:click=move |_| {
                    if let Some(handler) = on_remove_sig.0.get().as_ref() {
                        handler();
                    }
                }
            >
                <i class="bi-x"></i>
            </button>
        </Show>
    }
}

/// ResizeHandles component - isolates resize handler closures to prevent view!
/// macro FnOnce issue
#[component]
fn ResizeHandles(
    on_mousedown_e: Arc<dyn Fn(MouseEvent) + Send + Sync>,
    on_mousedown_s: Arc<dyn Fn(MouseEvent) + Send + Sync>,
    on_mousedown_se: Arc<dyn Fn(MouseEvent) + Send + Sync>,
) -> impl IntoView {
    let e_handler = Arc::clone(&on_mousedown_e);
    let s_handler = Arc::clone(&on_mousedown_s);
    let se_handler = Arc::clone(&on_mousedown_se);

    view! {
        <div class="resize-handles">
            <div
                class="resize-handle resize-e"
                on:mousedown=move |event: MouseEvent| { e_handler(event) }
            ></div>
            <div
                class="resize-handle resize-s"
                on:mousedown=move |event: MouseEvent| { s_handler(event) }
            ></div>
            <div
                class="resize-handle resize-se"
                on:mousedown=move |event: MouseEvent| { se_handler(event) }
            ></div>
        </div>
    }
}

#[component]
pub fn GridBackground(
    layout: ReadSignal<GridLayout>,
    container_width: ReadSignal<i32>,
    show_grid: ReadSignal<bool>,
) -> impl IntoView {
    let grid_lines_style = Memo::new(move |_| {
        if !show_grid.get() {
            return "display: none;".to_string();
        }

        let layout = layout.get();
        let width = container_width.get();
        let column_width = width / layout.columns;
        let row_height = layout.row_height;

        format!(
            "background-image:
                linear-gradient(to right, rgba(0,0,0,0.1) 1px, transparent 1px),
                linear-gradient(to bottom, rgba(0,0,0,0.1) 1px, transparent 1px);
            background-size: {}px {}px;
            position: absolute;
            top: 0;
            left: 0;
            right: 0;
            bottom: 0;
            pointer-events: none;",
            column_width, row_height
        )
    });

    view! {
        <div
            class="grid-background"
            style=move || grid_lines_style.get()
        ></div>
    }
}

// Helper types and functions
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DropData {
    pub widget_id: String,
    pub widget_type: String,
    pub original_position: GridPosition,
    pub original_size: GridSize,
}

#[derive(Debug, Clone)]
pub struct DragState {
    pub widget_id: String,
    pub start_position: GridPosition,
    pub current_position: GridPosition,
}

#[derive(Debug, Clone, Copy)]
pub enum ResizeDirection {
    E,  // East
    S,  // South
    SE, // Southeast
}

pub fn pixel_to_grid_position(
    x: f64,
    y: f64,
    layout: &GridLayout,
    container_width: i32,
) -> GridPosition {
    let column_width = container_width as f64 / layout.columns as f64;
    let row_height = layout.row_height as f64;

    let grid_x = (x / column_width).floor() as i32;
    let grid_y = (y / row_height).floor() as i32;

    GridPosition {
        x: grid_x.max(0).min(layout.columns - 1),
        y: grid_y.max(0),
    }
}

pub fn grid_to_pixel_position(
    position: GridPosition,
    layout: &GridLayout,
    container_width: i32,
) -> (f64, f64) {
    let column_width = container_width as f64 / layout.columns as f64;
    let row_height = layout.row_height as f64;

    let x = position.x as f64 * column_width;
    let y = position.y as f64 * row_height;

    (x, y)
}
