use std::{
    rc::Rc,
    sync::{Arc, Mutex},
};
use dioxus_core::{AttributeValue, Scope, ScopeState};
use dioxus_hooks::{use_effect, use_ref, use_state, UseRef, UseState};
use freya_common::{CursorLayoutResponse, EventMessage};
use freya_elements::events::{KeyboardData, MouseData};
use freya_node_state::{CursorReference, CustomAttributeValues};
pub use ropey::Rope;
use tokio::sync::{mpsc::unbounded_channel, mpsc::UnboundedSender};
use torin::geometry::CursorPoint;
use uuid::Uuid;
use crate::{use_platform, RopeEditor, TextCursor, TextEditor, TextEvent, UsePlatform};
pub enum EditableEvent {
    Click,
    MouseOver(Rc<MouseData>, usize),
    MouseDown(Rc<MouseData>, usize),
    KeyDown(Rc<KeyboardData>),
}
#[derive(PartialEq, Eq, Clone, Copy)]
pub enum EditableMode {
    SingleLineMultipleEditors,
    MultipleLinesSingleEditor,
}
impl Default for EditableMode {
    fn default() -> Self {
        Self::MultipleLinesSingleEditor
    }
}
pub type ClickNotifier = UnboundedSender<EditableEvent>;
pub type EditorState = UseState<RopeEditor>;
#[derive(Clone)]
pub struct UseEditable {
    pub(crate) editor: EditorState,
    pub(crate) cursor_reference: CursorReference,
    pub(crate) selecting_text_with_mouse: UseRef<Option<CursorPoint>>,
    pub(crate) platform: UsePlatform,
}
impl UseEditable {
    pub fn editor(&self) -> &EditorState {
        &self.editor
    }
    pub fn cursor_attr<'a, T>(&self, cx: Scope<'a, T>) -> AttributeValue<'a> {
        cx.any_value(CustomAttributeValues::CursorReference(
            self.cursor_reference.clone(),
        ))
    }
    pub fn highlights_attr<'a, T>(&self, cx: Scope<'a, T>, editor_id: usize) -> AttributeValue<'a> {
        cx.any_value(CustomAttributeValues::TextHighlights(
            self.editor
                .get()
                .highlights(editor_id)
                .map(|v| vec![v])
                .unwrap_or_default(),
        ))
    }
    pub fn process_event(&self, edit_event: &EditableEvent) {
        match edit_event {
            EditableEvent::MouseDown(e, id) => {
                let coords = e.get_element_coordinates();
                *self.selecting_text_with_mouse.write_silent() = Some(coords);
                self.cursor_reference.set_id(Some(*id));
                self.cursor_reference.set_cursor_position(Some(coords));
                self.editor.with_mut(|editor| {
                    editor.unhighlight();
                });
            }
            EditableEvent::MouseOver(e, id) => {
                self.selecting_text_with_mouse.with(|selecting_text| {
                    if let Some(current_dragging) = selecting_text {
                        let coords = e.get_element_coordinates();
                        self.cursor_reference.set_id(Some(*id));
                        self.cursor_reference
                            .set_cursor_selections(Some((*current_dragging, coords)));
                    }
                });
            }
            EditableEvent::Click => {
                *self.selecting_text_with_mouse.write_silent() = None;
            }
            EditableEvent::KeyDown(e) => {
                self.editor.with_mut(|editor| {
                    let event = editor.process_key(&e.key, &e.code, &e.modifiers);
                    if event == TextEvent::TextChanged {
                        *self.selecting_text_with_mouse.write_silent() = None;
                    }
                });
            }
        }
        if self.selecting_text_with_mouse.read().is_some() {
            self.platform
                .send(EventMessage::RemeasureTextGroup(
                    self.cursor_reference.text_id,
                ))
                .unwrap()
        }
    }
}
pub struct EditableConfig {
    pub(crate) content: String,
    pub(crate) cursor: TextCursor,
}
impl EditableConfig {
    pub fn new(content: String) -> Self {
        Self {
            content,
            cursor: TextCursor::default(),
        }
    }
    pub fn with_cursor(mut self, (row, col): (usize, usize)) -> Self {
        self.cursor = TextCursor::new(row, col);
        self
    }
}
pub fn use_editable(
    cx: &ScopeState,
    initializer: impl Fn() -> EditableConfig,
    mode: EditableMode,
) -> UseEditable {
    let id = cx.use_hook(Uuid::new_v4);
    let platform = use_platform(cx);
    let text_editor = use_state(cx, || {
        let config = initializer();
        RopeEditor::new(config.content, config.cursor, mode)
    });
    let cursor_channels = cx.use_hook(|| {
        let (tx, rx) = unbounded_channel::<CursorLayoutResponse>();
        (tx, Some(rx))
    });
    let selecting_text_with_mouse = use_ref(cx, || None);
    let cursor_reference = cx.use_hook(|| CursorReference {
        text_id: *id,
        agent: cursor_channels.0.clone(),
        cursor_position: Arc::new(Mutex::new(None)),
        cursor_id: Arc::new(Mutex::new(None)),
        cursor_selections: Arc::new(Mutex::new(None)),
    });
    let use_editable = UseEditable {
        editor: text_editor.clone(),
        cursor_reference: cursor_reference.clone(),
        selecting_text_with_mouse: selecting_text_with_mouse.clone(),
        platform,
    };
    use_effect(cx, (), move |_| {
        let cursor_reference = cursor_reference.clone();
        let cursor_receiver = cursor_channels.1.take();
        let editor = text_editor.clone();
        async move {
            let mut cursor_receiver = cursor_receiver.unwrap();
            while let Some(message) = cursor_receiver.recv().await {
                match message {
                    CursorLayoutResponse::CursorPosition { position, id } => {
                        let text_editor = editor.current();
                        let new_cursor_row = match mode {
                            EditableMode::MultipleLinesSingleEditor => {
                                text_editor.char_to_line(position)
                            }
                            EditableMode::SingleLineMultipleEditors => id,
                        };
                        let new_cursor_col = match mode {
                            EditableMode::MultipleLinesSingleEditor => {
                                position - text_editor.line_to_char(new_cursor_row)
                            }
                            EditableMode::SingleLineMultipleEditors => position,
                        };
                        let new_current_line = text_editor.line(new_cursor_row).unwrap();
                        let new_cursor = if new_cursor_col >= new_current_line.len_chars() {
                            (new_current_line.len_chars(), new_cursor_row)
                        } else {
                            (new_cursor_col, new_cursor_row)
                        };
                        if text_editor.cursor().as_tuple() != new_cursor {
                            editor.with_mut(|text_editor| {
                                text_editor.cursor_mut().set_col(new_cursor.0);
                                text_editor.cursor_mut().set_row(new_cursor.1);
                                text_editor.unhighlight();
                            })
                        }
                        cursor_reference.set_cursor_position(None);
                    }
                    CursorLayoutResponse::TextSelection { from, to, id } => {
                        editor.with_mut(|text_editor| {
                            text_editor.highlight_text(from, to, id);
                        });
                        cursor_reference.set_cursor_selections(None);
                    }
                }
            }
        }
    });
    use_editable
}