use std::any::Any;
use std::cell::{Cell, RefCell};
use std::collections::HashMap;
use std::rc::Rc;

use crate::scope::Scope;
use crate::{Rect, Scene, View, semantics::Role};

thread_local! {
    pub static COMPOSER: RefCell<Composer> = RefCell::new(Composer::default());
    static ROOT_SCOPE: RefCell<Option<Scope>> = const { RefCell::new(None) };

    /// A programmatic focus request, set by `FocusRequester::request_focus()`.
    /// Stores the view ID that should receive focus on the next frame.
    static FOCUS_REQUEST: Cell<Option<u64>> = const { Cell::new(None) };
}

pub fn take_focus_request() -> Option<u64> {
    FOCUS_REQUEST.with(|r| r.replace(None))
}

/// A handle that can programmatically request focus for a widget.
///
/// Similar to Compose's `FocusRequester`. Create one via `remember(FocusRequester::new)`,
/// attach it via `.focus_requester(...)` on a modifier, and call `request_focus()` to
/// move keyboard focus to the associated widget on the next frame.
#[derive(Clone)]
pub struct FocusRequester {
    /// Target view ID, set during layout/paint by the modifier system.
    pub target: Rc<RefCell<Option<u64>>>,
}

impl FocusRequester {
    pub fn new() -> Self {
        Self {
            target: Rc::new(RefCell::new(None)),
        }
    }

    /// Request focus for the associated widget on the next frame.
    pub fn request_focus(&self) {
        if let Some(id) = *self.target.borrow() {
            FOCUS_REQUEST.with(|r| r.set(Some(id)));
        }
    }
}

impl Default for FocusRequester {
    fn default() -> Self {
        Self::new()
    }
}

/// Direction for focus movement.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum FocusDirection {
    Next,
    Previous,
    Left,
    Right,
    Up,
    Down,
}

/// A manager for programmatic focus navigation.
///
/// Wraps a `&Scheduler` and provides methods to move focus.
/// Can also be used standalone with a focus chain and focused element.
#[derive(Clone)]
pub struct FocusManager {
    /// The ordered list of focusable element IDs.
    pub chain: Vec<u64>,
    /// The currently focused element (if any).
    pub focused: Option<u64>,
}

impl FocusManager {
    pub fn new(chain: Vec<u64>, focused: Option<u64>) -> Self {
        Self { chain, focused }
    }

    /// Move focus in the given direction.
    /// Returns the new focused element ID, or `None` if no movement is possible.
    pub fn move_focus(&mut self, dir: FocusDirection) -> Option<u64> {
        match dir {
            FocusDirection::Next | FocusDirection::Previous => {
                self.move_tab(dir == FocusDirection::Previous)
            }
            _ => None, // use move_focus_spatial when hit regions are available
        }
    }

    /// Spatial focus navigation: find the closest focusable element in a given
    /// direction using bounding rect geometry.
    pub fn move_focus_spatial(
        &mut self,
        dir: FocusDirection,
        hit_regions: &[HitRegion],
    ) -> Option<u64> {
        let next = spatial_focus_next(&self.chain, hit_regions, self.focused, dir)?;
        self.focused = Some(next);
        Some(next)
    }

    /// Tab forward or backward in the focus chain.
    pub fn move_tab(&mut self, reverse: bool) -> Option<u64> {
        if self.chain.is_empty() {
            return None;
        }
        let next = if let Some(cur) = self.focused {
            if let Some(idx) = self.chain.iter().position(|&id| id == cur) {
                if reverse {
                    if idx == 0 {
                        self.chain[self.chain.len() - 1]
                    } else {
                        self.chain[idx - 1]
                    }
                } else {
                    self.chain[(idx + 1) % self.chain.len()]
                }
            } else {
                self.chain[0]
            }
        } else {
            self.chain[0]
        };
        self.focused = Some(next);
        Some(next)
    }

    /// Set target ID on a FocusRequester (called during layout).
    pub fn set_requester_target(requester: &FocusRequester, id: u64) {
        *requester.target.borrow_mut() = Some(id);
    }
}

/// Find the next focusable element in a given spatial direction.
///
/// Uses the bounding rects from `hit_regions` to determine which element is
/// "next" in the given direction from the currently focused element.
pub fn spatial_focus_next(
    chain: &[u64],
    hit_regions: &[HitRegion],
    current: Option<u64>,
    dir: FocusDirection,
) -> Option<u64> {
    if chain.is_empty() {
        return None;
    }

    let current_rect =
        current.and_then(|id| hit_regions.iter().find(|h| h.id == id).map(|h| h.rect));

    // For Next/Previous, use tab-order navigation
    match dir {
        FocusDirection::Next | FocusDirection::Previous => {
            let mut fm = FocusManager {
                chain: chain.to_vec(),
                focused: current,
            };
            return fm.move_tab(dir == FocusDirection::Previous);
        }
        _ => {}
    }

    let (cx, cy) = match current_rect {
        Some(r) => (r.x + r.w / 2.0, r.y + r.h / 2.0),
        None => return chain.first().copied(),
    };

    let mut best: Option<(u64, f32)> = None;

    for &id in chain {
        if Some(id) == current {
            continue;
        }
        let Some(hr) = hit_regions.iter().find(|h| h.id == id) else {
            continue;
        };
        let r = hr.rect;
        let other_cx = r.x + r.w / 2.0;
        let other_cy = r.y + r.h / 2.0;
        let dx = other_cx - cx;
        let dy = other_cy - cy;

        let in_direction = match dir {
            FocusDirection::Left => dx < 0.0 && dy.abs() <= r.h.max(1.0),
            FocusDirection::Right => dx > 0.0 && dy.abs() <= r.h.max(1.0),
            FocusDirection::Up => dy < 0.0 && dx.abs() <= r.w.max(1.0),
            FocusDirection::Down => dy > 0.0 && dx.abs() <= r.w.max(1.0),
            _ => false,
        };

        if !in_direction {
            continue;
        }

        let dist = dx * dx + dy * dy;
        let weight = dist / (r.w * r.h + 1.0).max(1.0);

        match best {
            Some((_, best_weight)) if weight >= best_weight => {}
            _ => best = Some((id, weight)),
        }
    }

    best.map(|(id, _)| id)
}

#[derive(Default)]
pub struct Composer {
    pub slots: Vec<Box<dyn Any>>,
    pub cursor: usize,
    pub keyed_slots: HashMap<String, Box<dyn Any>>,
}

pub struct ComposeGuard {
    scope: Scope,
}

impl ComposeGuard {
    pub fn begin() -> Self {
        COMPOSER.with(|c| c.borrow_mut().cursor = 0);

        let scope = ROOT_SCOPE.with(|rs| {
            if let Some(existing) = rs.borrow().clone() {
                existing
            } else {
                let s = Scope::new();
                *rs.borrow_mut() = Some(s.clone());
                s
            }
        });

        ComposeGuard { scope }
    }

    pub fn scope(&self) -> &Scope {
        &self.scope
    }
}

impl Drop for ComposeGuard {
    fn drop(&mut self) {
        // ROOT_SCOPE.with(|rs| { Do not clear every frame
        //     *rs.borrow_mut() = None;
        // });
    }
}

/// Slot-based remember (sequential composition only)
pub fn remember<T: 'static>(init: impl FnOnce() -> T) -> Rc<T> {
    COMPOSER.with(|c| {
        let mut c = c.borrow_mut();
        let cursor = c.cursor;
        c.cursor += 1;

        if cursor >= c.slots.len() {
            let rc: Rc<T> = Rc::new(init());
            c.slots.push(Box::new(rc.clone()));
            return rc;
        }

        if let Some(rc) = c.slots[cursor].downcast_ref::<Rc<T>>() {
            rc.clone()
        } else {
            log::warn!(
                "remember: slot {} type changed {}. \
                 Use remember_with_key(key, || ...) for conditional branches.",
                cursor,
                std::any::type_name::<T>(),
            );
            // debug_assert!(
            //     false,
            //     "remember slot {} type changed. This is likely a composition order bug.",
            //     cursor,
            // );
            let rc: Rc<T> = Rc::new(init());
            c.slots[cursor] = Box::new(rc.clone());
            rc
        }
    })
}

/// Key-based remember
pub fn remember_with_key<T: 'static>(key: impl Into<String>, init: impl FnOnce() -> T) -> Rc<T> {
    COMPOSER.with(|c| {
        let mut c = c.borrow_mut();
        let key = key.into();

        if let Some(existing) = c.keyed_slots.get(&key) {
            if let Some(rc) = existing.downcast_ref::<Rc<T>>() {
                return rc.clone();
            } else {
                log::warn!(
                    "remember_with_key: key '{}' reused with a different type; replacing.",
                    key
                );
            }
        }

        if cfg!(debug_assertions) && c.keyed_slots.len() > 10_000 {
            log::warn!(
                "remember_with_key: more than 10k keys stored; \
                are you generating unbounded dynamic keys (e.g., using timestamps)?"
            );
        }

        let rc: Rc<T> = Rc::new(init());
        c.keyed_slots.insert(key, Box::new(rc.clone()));
        rc
    })
}

pub fn remember_state<T: 'static>(init: impl FnOnce() -> T) -> Rc<RefCell<T>> {
    remember(|| RefCell::new(init()))
}

pub fn remember_state_with_key<T: 'static>(
    key: impl Into<String>,
    init: impl FnOnce() -> T,
) -> Rc<RefCell<T>> {
    remember_with_key(key, || RefCell::new(init()))
}

/// Frame - output of composition for a tick: scene + input/semantics.
pub struct Frame {
    pub scene: Scene,
    pub hit_regions: Vec<HitRegion>,
    pub semantics_nodes: Vec<SemNode>,
    pub focus_chain: Vec<u64>,
}

#[derive(Clone, Default)]
pub struct HitRegion {
    pub id: u64,
    pub rect: Rect,
    pub on_click: Option<Rc<dyn Fn()>>,
    pub on_scroll: Option<Rc<dyn Fn(crate::Vec2) -> crate::Vec2>>,
    pub focusable: bool,
    pub on_pointer_down: Option<Rc<dyn Fn(crate::input::PointerEvent)>>,
    pub on_pointer_move: Option<Rc<dyn Fn(crate::input::PointerEvent)>>,
    pub on_pointer_up: Option<Rc<dyn Fn(crate::input::PointerEvent)>>,
    pub on_pointer_enter: Option<Rc<dyn Fn(crate::input::PointerEvent)>>,
    pub on_pointer_leave: Option<Rc<dyn Fn(crate::input::PointerEvent)>>,
    pub z_index: f32,
    pub on_text_change: Option<Rc<dyn Fn(String)>>,
    pub on_text_submit: Option<Rc<dyn Fn(String)>>,
    /// If this hit region belongs to a TextField, this persistent key is used
    /// for looking up platform-managed TextFieldState. Falls back to `id` if None.
    pub tf_state_key: Option<u64>,

    /// True if this hit region corresponds to a multiline text input (TextArea).
    pub tf_multiline: bool,

    // internal
    pub on_drag_start: Option<Rc<dyn Fn(crate::dnd::DragStart) -> Option<crate::dnd::DragPayload>>>,
    pub on_drag_end: Option<Rc<dyn Fn(crate::dnd::DragEnd)>>,
    pub on_drag_enter: Option<Rc<dyn Fn(crate::dnd::DragOver)>>,
    pub on_drag_over: Option<Rc<dyn Fn(crate::dnd::DragOver)>>,
    pub on_drag_leave: Option<Rc<dyn Fn(crate::dnd::DragOver)>>,
    pub on_drop: Option<Rc<dyn Fn(crate::dnd::DropEvent) -> bool>>,

    pub on_action: Option<Rc<dyn Fn(crate::shortcuts::Action) -> bool>>,

    /// Cursor hint for desktop/web.
    pub cursor: Option<crate::CursorIcon>,
}

impl HitRegion {
    /// Seed a HitRegion with all the modifier's event handlers + dnd + cursor.
    /// Call‑sites should only override the fields that differ (on_click, focusable, etc.)
    /// via struct‑update syntax: `HitRegion { focusable: true, ..from_modifier(..) }`.
    pub fn from_modifier(id: u64, rect: Rect, m: &crate::modifier::Modifier) -> Self {
        Self {
            id,
            rect,
            z_index: m.z_index,
            on_pointer_down: m.on_pointer_down.clone(),
            on_pointer_move: m.on_pointer_move.clone(),
            on_pointer_up: m.on_pointer_up.clone(),
            on_pointer_enter: m.on_pointer_enter.clone(),
            on_pointer_leave: m.on_pointer_leave.clone(),
            on_action: m.on_action.clone(),
            cursor: m.cursor,
            on_drag_start: m.on_drag_start.clone(),
            on_drag_end: m.on_drag_end.clone(),
            on_drag_enter: m.on_drag_enter.clone(),
            on_drag_over: m.on_drag_over.clone(),
            on_drag_leave: m.on_drag_leave.clone(),
            on_drop: m.on_drop.clone(),
            ..Default::default()
        }
    }
}

/// Flattened semantics node produced by `layout_and_paint`.
///
/// This is the source of truth for accessibility backends: it contains the
/// resolved screen rect, role, label, and focus/enabled state.
///
/// The platform runner should convert this into OS‑specific accessibility trees (when implemented)
/// (AT‑SPI on Linux, TalkBack on Android, etc.).
#[derive(Clone)]
pub struct SemNode {
    /// Stable id, shared with the associated `HitRegion` / `ViewId`.
    pub id: u64,

    /// `None` means direct child of the window root.
    pub parent: Option<u64>,

    pub role: Role,
    pub label: Option<String>,
    pub rect: Rect,
    pub focused: bool,
    pub enabled: bool,
}

pub struct Scheduler {
    next_id: u64,
    pub focused: Option<u64>,
    pub size: (u32, u32),
}

impl Default for Scheduler {
    fn default() -> Self {
        Self::new()
    }
}

impl Scheduler {
    pub fn new() -> Self {
        Self {
            next_id: 1,
            focused: None,
            size: (1280, 800),
        }
    }

    pub fn id(&mut self) -> u64 {
        let id = self.next_id;
        self.next_id += 1;
        id
    }

    pub fn id_count(&self) -> u64 {
        self.next_id - 1
    }

    pub fn repose<F>(
        &mut self,
        mut build_root: F,
        layout_paint: impl Fn(&View, (u32, u32)) -> (Scene, Vec<HitRegion>, Vec<SemNode>),
    ) -> Frame
    where
        F: FnMut(&mut Scheduler) -> View,
    {
        let guard = ComposeGuard::begin();
        let root = guard.scope.run(|| build_root(self));
        let (scene, hits, sem) = layout_paint(&root, self.size);

        let focus_chain: Vec<u64> = hits.iter().filter(|h| h.focusable).map(|h| h.id).collect();

        Frame {
            scene,
            hit_regions: hits,
            semantics_nodes: sem,
            focus_chain,
        }
    }
}

/// Avoids cross-test pollution
#[cfg(test)]
pub fn clear_composer() {
    COMPOSER.with(|c| {
        let mut c = c.borrow_mut();
        c.slots.clear();
        c.keyed_slots.clear();
        c.cursor = 0;
    });
    ROOT_SCOPE.with(|rs| {
        *rs.borrow_mut() = None;
    });
}
