use std::collections::{HashMap, VecDeque};
use std::sync::{Arc, Mutex};

use iced::advanced::layout;
use iced::advanced::overlay;
use iced::advanced::renderer;
use iced::advanced::widget::{Operation, Tree, tree};
use iced::advanced::{Clipboard, Layout, Shell, Widget};
use iced::keyboard::{self, Key, Location, Modifiers, key};
use iced::mouse;
use iced::{Element, Event, Length, Rectangle, Size, Vector};

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum EditCommand {
    Undo,
    Redo,
    Cut,
    Copy,
    Paste,
    SelectAll,
}

pub(crate) type EditCommandQueue = Arc<Mutex<VecDeque<EditCommand>>>;

#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub(crate) struct EditAvailability {
    pub(crate) undo: bool,
    pub(crate) redo: bool,
    pub(crate) cut: bool,
    pub(crate) copy: bool,
    pub(crate) paste: bool,
    pub(crate) select_all: bool,
}

pub(crate) type EditAvailabilityState = Arc<Mutex<EditAvailability>>;

pub(crate) fn command_queue() -> EditCommandQueue {
    Arc::new(Mutex::new(VecDeque::new()))
}

pub(crate) fn availability_state() -> EditAvailabilityState {
    Arc::new(Mutex::new(EditAvailability::default()))
}

pub(crate) fn queue_command(queue: &EditCommandQueue, command: EditCommand) {
    queue
        .lock()
        .unwrap_or_else(|poisoned| poisoned.into_inner())
        .push_back(command);
}

fn pop_command(queue: &EditCommandQueue) -> Option<EditCommand> {
    queue
        .lock()
        .unwrap_or_else(|poisoned| poisoned.into_inner())
        .pop_front()
}

/// Replays native Edit menu actions through the focused Iced widget.
pub(crate) struct NativeEdit<'a, Message, Theme = iced::Theme, Renderer = iced::Renderer> {
    content: Element<'a, Message, Theme, Renderer>,
    commands: EditCommandQueue,
    availability: EditAvailabilityState,
}

impl<'a, Message, Theme, Renderer> NativeEdit<'a, Message, Theme, Renderer> {
    fn new(
        content: impl Into<Element<'a, Message, Theme, Renderer>>,
        commands: EditCommandQueue,
        availability: EditAvailabilityState,
    ) -> Self {
        Self {
            content: content.into(),
            commands,
            availability,
        }
    }
}

#[derive(Clone, Debug, Hash, PartialEq, Eq)]
enum ControlId {
    Named(iced::widget::Id),
    Position(usize),
}

#[derive(Debug)]
struct History {
    values: Vec<String>,
    current: usize,
}

#[derive(Default)]
struct State {
    histories: HashMap<ControlId, History>,
    active: Option<ControlId>,
    window_focused: bool,
}

#[derive(Default)]
struct FocusedControl {
    current: usize,
    pending_text: Option<String>,
    focused: Option<(ControlId, Option<String>)>,
}

impl<T> Operation<T> for FocusedControl {
    fn traverse(&mut self, operate: &mut dyn FnMut(&mut dyn Operation<T>)) {
        operate(self);
    }

    fn text_input(
        &mut self,
        _id: Option<&iced::widget::Id>,
        _bounds: Rectangle,
        state: &mut dyn iced::advanced::widget::operation::TextInput,
    ) {
        self.pending_text = Some(state.text().to_owned());
    }

    fn focusable(
        &mut self,
        id: Option<&iced::widget::Id>,
        _bounds: Rectangle,
        state: &mut dyn iced::advanced::widget::operation::Focusable,
    ) {
        if state.is_focused() {
            self.focused = Some((
                id.cloned()
                    .map_or(ControlId::Position(self.current), ControlId::Named),
                self.pending_text.take(),
            ));
        } else {
            self.pending_text = None;
        }
        self.current += 1;
    }
}

impl State {
    fn observe(&mut self, focused: Option<&(ControlId, Option<String>)>) {
        self.active = focused.map(|(control, _)| control.clone());
        let Some((control, Some(value))) = focused else {
            return;
        };
        let history = self
            .histories
            .entry(control.clone())
            .or_insert_with(|| History {
                values: vec![value.clone()],
                current: 0,
            });
        if history.values[history.current] != *value {
            history.values.truncate(history.current + 1);
            history.values.push(value.clone());
            history.current += 1;
        }
    }

    fn replacement(&mut self, command: EditCommand) -> Option<String> {
        let history = self.histories.get_mut(self.active.as_ref()?)?;
        match command {
            EditCommand::Undo if history.current > 0 => history.current -= 1,
            EditCommand::Redo if history.current + 1 < history.values.len() => {
                history.current += 1;
            }
            _ => return None,
        }
        Some(history.values[history.current].clone())
    }

    fn availability(&self, focused: Option<&(ControlId, Option<String>)>) -> EditAvailability {
        let Some((control, text)) = focused else {
            return EditAvailability::default();
        };
        let editable = text.is_some();
        let history = self.histories.get(control);
        EditAvailability {
            undo: editable && history.is_some_and(|history| history.current > 0),
            redo: editable
                && history.is_some_and(|history| history.current + 1 < history.values.len()),
            cut: editable,
            copy: true,
            paste: editable,
            select_all: true,
        }
    }
}

fn modifier_sync_event(event: &Event) -> Option<Event> {
    let Event::Keyboard(keyboard::Event::KeyPressed { modifiers, .. }) = event else {
        return None;
    };
    Some(Event::Keyboard(keyboard::Event::ModifiersChanged(
        *modifiers,
    )))
}

fn command_events(command: EditCommand) -> [Event; 4] {
    let (character, modified_character, physical_key, modifiers) = match command {
        EditCommand::Undo => ("z", "z", key::Code::KeyZ, Modifiers::COMMAND),
        EditCommand::Redo => (
            "z",
            "Z",
            key::Code::KeyZ,
            Modifiers::COMMAND | Modifiers::SHIFT,
        ),
        EditCommand::Cut => ("x", "x", key::Code::KeyX, Modifiers::COMMAND),
        EditCommand::Copy => ("c", "c", key::Code::KeyC, Modifiers::COMMAND),
        EditCommand::Paste => ("v", "v", key::Code::KeyV, Modifiers::COMMAND),
        EditCommand::SelectAll => ("a", "a", key::Code::KeyA, Modifiers::COMMAND),
    };
    let key = Key::Character(character.into());
    [
        Event::Keyboard(keyboard::Event::ModifiersChanged(modifiers)),
        Event::Keyboard(keyboard::Event::KeyPressed {
            key: key.clone(),
            modified_key: Key::Character(modified_character.into()),
            physical_key: key::Physical::Code(physical_key),
            location: Location::Standard,
            modifiers,
            text: None,
            repeat: false,
        }),
        Event::Keyboard(keyboard::Event::KeyReleased {
            key,
            modified_key: Key::Character(modified_character.into()),
            physical_key: key::Physical::Code(physical_key),
            location: Location::Standard,
            modifiers,
        }),
        Event::Keyboard(keyboard::Event::ModifiersChanged(Modifiers::default())),
    ]
}

impl<'a, Message, Theme, Renderer> Widget<Message, Theme, Renderer>
    for NativeEdit<'a, Message, Theme, Renderer>
where
    Renderer: renderer::Renderer,
{
    fn tag(&self) -> tree::Tag {
        tree::Tag::of::<State>()
    }

    fn state(&self) -> tree::State {
        tree::State::new(State::default())
    }

    fn children(&self) -> Vec<Tree> {
        vec![Tree::new(&self.content)]
    }

    fn diff(&self, tree: &mut Tree) {
        tree.diff_children(std::slice::from_ref(&self.content));
    }

    fn size(&self) -> Size<Length> {
        self.content.as_widget().size()
    }

    fn layout(
        &mut self,
        tree: &mut Tree,
        renderer: &Renderer,
        limits: &layout::Limits,
    ) -> layout::Node {
        self.content
            .as_widget_mut()
            .layout(&mut tree.children[0], renderer, limits)
    }

    fn operate(
        &mut self,
        tree: &mut Tree,
        layout: Layout<'_>,
        renderer: &Renderer,
        operation: &mut dyn Operation,
    ) {
        self.content
            .as_widget_mut()
            .operate(&mut tree.children[0], layout, renderer, operation);
    }

    fn update(
        &mut self,
        tree: &mut Tree,
        event: &Event,
        layout: Layout<'_>,
        cursor: mouse::Cursor,
        renderer: &Renderer,
        clipboard: &mut dyn Clipboard,
        shell: &mut Shell<'_, Message>,
        viewport: &Rectangle,
    ) {
        let state = tree.state.downcast_mut::<State>();
        match event {
            Event::Window(iced::window::Event::Focused) => state.window_focused = true,
            Event::Window(iced::window::Event::Unfocused) => state.window_focused = false,
            _ => {}
        }
        if state.window_focused
            && matches!(
                event,
                Event::Window(iced::window::Event::RedrawRequested(_))
            )
        {
            while let Some(command) = pop_command(&self.commands) {
                if let Some(replacement) = state.replacement(command) {
                    let previous = clipboard.read(iced::advanced::clipboard::Kind::Standard);
                    clipboard.write(iced::advanced::clipboard::Kind::Standard, replacement);
                    for command in [EditCommand::SelectAll, EditCommand::Paste] {
                        for command_event in command_events(command) {
                            self.content.as_widget_mut().update(
                                &mut tree.children[0],
                                &command_event,
                                layout,
                                cursor,
                                renderer,
                                clipboard,
                                shell,
                                viewport,
                            );
                        }
                    }
                    if let Some(previous) = previous {
                        clipboard.write(iced::advanced::clipboard::Kind::Standard, previous);
                    }
                    continue;
                }
                if matches!(command, EditCommand::Undo | EditCommand::Redo) {
                    continue;
                }
                for command_event in command_events(command) {
                    self.content.as_widget_mut().update(
                        &mut tree.children[0],
                        &command_event,
                        layout,
                        cursor,
                        renderer,
                        clipboard,
                        shell,
                        viewport,
                    );
                }
            }
        }

        if let Some(sync_event) = modifier_sync_event(event) {
            self.content.as_widget_mut().update(
                &mut tree.children[0],
                &sync_event,
                layout,
                cursor,
                renderer,
                clipboard,
                shell,
                viewport,
            );
        }

        self.content.as_widget_mut().update(
            &mut tree.children[0],
            event,
            layout,
            cursor,
            renderer,
            clipboard,
            shell,
            viewport,
        );

        let mut focused = FocusedControl::default();
        self.content
            .as_widget_mut()
            .operate(&mut tree.children[0], layout, renderer, &mut focused);
        if state.window_focused {
            state.observe(focused.focused.as_ref());
            *self
                .availability
                .lock()
                .unwrap_or_else(|poisoned| poisoned.into_inner()) =
                state.availability(focused.focused.as_ref());
        }
    }

    fn mouse_interaction(
        &self,
        tree: &Tree,
        layout: Layout<'_>,
        cursor: mouse::Cursor,
        viewport: &Rectangle,
        renderer: &Renderer,
    ) -> mouse::Interaction {
        self.content.as_widget().mouse_interaction(
            &tree.children[0],
            layout,
            cursor,
            viewport,
            renderer,
        )
    }

    fn draw(
        &self,
        tree: &Tree,
        renderer: &mut Renderer,
        theme: &Theme,
        style: &renderer::Style,
        layout: Layout<'_>,
        cursor: mouse::Cursor,
        viewport: &Rectangle,
    ) {
        self.content.as_widget().draw(
            &tree.children[0],
            renderer,
            theme,
            style,
            layout,
            cursor,
            viewport,
        );
    }

    fn overlay<'b>(
        &'b mut self,
        tree: &'b mut Tree,
        layout: Layout<'b>,
        renderer: &Renderer,
        viewport: &Rectangle,
        translation: Vector,
    ) -> Option<overlay::Element<'b, Message, Theme, Renderer>> {
        self.content.as_widget_mut().overlay(
            &mut tree.children[0],
            layout,
            renderer,
            viewport,
            translation,
        )
    }
}

impl<'a, Message, Theme, Renderer> From<NativeEdit<'a, Message, Theme, Renderer>>
    for Element<'a, Message, Theme, Renderer>
where
    Message: 'a,
    Theme: 'a,
    Renderer: 'a + renderer::Renderer,
{
    fn from(bridge: NativeEdit<'a, Message, Theme, Renderer>) -> Self {
        Element::new(bridge)
    }
}

pub(crate) fn native_edit<'a, Message, Theme, Renderer>(
    content: impl Into<Element<'a, Message, Theme, Renderer>>,
    commands: EditCommandQueue,
    availability: EditAvailabilityState,
) -> NativeEdit<'a, Message, Theme, Renderer> {
    NativeEdit::new(content, commands, availability)
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn paste_replays_a_complete_command_shortcut() {
        let events = command_events(EditCommand::Paste);
        assert!(matches!(
            events[0],
            Event::Keyboard(keyboard::Event::ModifiersChanged(Modifiers::COMMAND))
        ));
        assert!(matches!(
            events[1],
            Event::Keyboard(keyboard::Event::KeyPressed {
                key: Key::Character(ref key),
                modifiers: Modifiers::COMMAND,
                ..
            }) if key == "v"
        ));
        assert!(matches!(
            events[3],
            Event::Keyboard(keyboard::Event::ModifiersChanged(modifiers)) if modifiers.is_empty()
        ));
    }

    #[test]
    fn edit_history_supports_undo_and_redo() {
        let control = ControlId::Position(0);
        let mut state = State::default();
        state.observe(Some(&(control.clone(), Some("a".into()))));
        state.observe(Some(&(control.clone(), Some("ab".into()))));
        assert_eq!(state.replacement(EditCommand::Undo).as_deref(), Some("a"));
        assert_eq!(state.replacement(EditCommand::Redo).as_deref(), Some("ab"));
    }
}
