use action_log::DiffStats;
use agent_client_protocol::schema as acp;
use agent_ui::thread_metadata_store::ThreadMetadata;
use gpui::{
    Action as _, DismissEvent, Entity, EventEmitter, FocusHandle, Focusable, Modifiers,
    ModifiersChangedEvent, Render, ScrollHandle, SharedString, prelude::*,
};
use ui::{AgentThreadStatus, ThreadItem, ThreadItemWorktreeInfo, WithScrollbar, prelude::*};
use workspace::{ModalView, Workspace};
use zed_actions::agents_sidebar::ToggleThreadSwitcher;

pub(crate) struct ThreadSwitcherEntry {
    pub session_id: acp::SessionId,
    pub title: SharedString,
    pub icon: IconName,
    pub icon_from_external_svg: Option<SharedString>,
    pub status: AgentThreadStatus,
    pub metadata: ThreadMetadata,
    pub workspace: Entity<Workspace>,
    pub project_name: Option<SharedString>,
    pub worktrees: Vec<ThreadItemWorktreeInfo>,
    pub diff_stats: DiffStats,
    pub is_title_generating: bool,
    pub notified: bool,
    pub timestamp: SharedString,
}

pub(crate) enum ThreadSwitcherEvent {
    Preview {
        metadata: ThreadMetadata,
        workspace: Entity<Workspace>,
    },
    Confirmed {
        metadata: ThreadMetadata,
        workspace: Entity<Workspace>,
    },
    Dismissed,
}

pub(crate) struct ThreadSwitcher {
    focus_handle: FocusHandle,
    entries: Vec<ThreadSwitcherEntry>,
    selected_index: usize,
    init_modifiers: Option<Modifiers>,
    scroll_handle: ScrollHandle,
}

impl ThreadSwitcher {
    pub fn new(
        entries: Vec<ThreadSwitcherEntry>,
        select_last: bool,
        window: &mut gpui::Window,
        cx: &mut Context<Self>,
    ) -> Self {
        let init_modifiers = window.modifiers().modified().then_some(window.modifiers());
        let selected_index = if entries.is_empty() {
            0
        } else if select_last {
            entries.len() - 1
        } else {
            1.min(entries.len().saturating_sub(1))
        };

        if let Some(entry) = entries.get(selected_index) {
            cx.emit(ThreadSwitcherEvent::Preview {
                metadata: entry.metadata.clone(),
                workspace: entry.workspace.clone(),
            });
        }

        let focus_handle = cx.focus_handle();
        cx.on_focus_out(&focus_handle, window, |_this, _event, _window, cx| {
            cx.emit(ThreadSwitcherEvent::Dismissed);
            cx.emit(DismissEvent);
        })
        .detach();

        let scroll_handle = ScrollHandle::new();
        scroll_handle.scroll_to_item(selected_index);

        Self {
            focus_handle,
            entries,
            selected_index,
            init_modifiers,
            scroll_handle,
        }
    }

    pub fn selected_entry(&self) -> Option<&ThreadSwitcherEntry> {
        self.entries.get(self.selected_index)
    }

    #[cfg(test)]
    pub fn entries(&self) -> &[ThreadSwitcherEntry] {
        &self.entries
    }

    #[cfg(test)]
    pub fn selected_index(&self) -> usize {
        self.selected_index
    }

    pub fn cycle_selection(&mut self, cx: &mut Context<Self>) {
        if self.entries.is_empty() {
            return;
        }
        self.selected_index = (self.selected_index + 1) % self.entries.len();
        self.emit_preview(cx);
    }

    pub fn select_last(&mut self, cx: &mut Context<Self>) {
        if self.entries.is_empty() {
            return;
        }
        if self.selected_index == 0 {
            self.selected_index = self.entries.len() - 1;
        } else {
            self.selected_index -= 1;
        }
        self.emit_preview(cx);
    }

    fn emit_preview(&mut self, cx: &mut Context<Self>) {
        self.scroll_handle.scroll_to_item(self.selected_index);
        if let Some(entry) = self.entries.get(self.selected_index) {
            cx.emit(ThreadSwitcherEvent::Preview {
                metadata: entry.metadata.clone(),
                workspace: entry.workspace.clone(),
            });
        }
    }

    fn confirm(&mut self, _: &menu::Confirm, _window: &mut gpui::Window, cx: &mut Context<Self>) {
        self.confirm_selected(cx);
    }

    fn confirm_selected(&mut self, cx: &mut Context<Self>) {
        if let Some(entry) = self.entries.get(self.selected_index) {
            cx.emit(ThreadSwitcherEvent::Confirmed {
                metadata: entry.metadata.clone(),
                workspace: entry.workspace.clone(),
            });
        }
        cx.emit(DismissEvent);
    }

    fn select_and_confirm(&mut self, index: usize, cx: &mut Context<Self>) {
        if index < self.entries.len() {
            self.selected_index = index;
            self.confirm_selected(cx);
        }
    }

    fn select_index(&mut self, index: usize, cx: &mut Context<Self>) {
        if index >= self.entries.len() || index == self.selected_index {
            return;
        }
        self.selected_index = index;
        self.scroll_handle.scroll_to_item(index);
        self.emit_preview(cx);
        cx.notify();
    }

    fn cancel(&mut self, _: &menu::Cancel, _window: &mut gpui::Window, cx: &mut Context<Self>) {
        cx.emit(ThreadSwitcherEvent::Dismissed);
        cx.emit(DismissEvent);
    }

    fn toggle(
        &mut self,
        action: &ToggleThreadSwitcher,
        _window: &mut gpui::Window,
        cx: &mut Context<Self>,
    ) {
        if action.select_last {
            self.select_last(cx);
        } else {
            self.cycle_selection(cx);
        }
    }

    fn handle_modifiers_changed(
        &mut self,
        event: &ModifiersChangedEvent,
        window: &mut gpui::Window,
        cx: &mut Context<Self>,
    ) {
        let Some(init_modifiers) = self.init_modifiers else {
            return;
        };
        if !event.modified() || !init_modifiers.is_subset_of(event) {
            self.init_modifiers = None;
            if self.entries.is_empty() {
                cx.emit(DismissEvent);
            } else {
                window.dispatch_action(menu::Confirm.boxed_clone(), cx);
            }
        }
    }
}

impl ModalView for ThreadSwitcher {}

impl EventEmitter<DismissEvent> for ThreadSwitcher {}
impl EventEmitter<ThreadSwitcherEvent> for ThreadSwitcher {}

impl Focusable for ThreadSwitcher {
    fn focus_handle(&self, _cx: &gpui::App) -> FocusHandle {
        self.focus_handle.clone()
    }
}

impl Render for ThreadSwitcher {
    fn render(&mut self, window: &mut gpui::Window, cx: &mut Context<Self>) -> impl IntoElement {
        let selected_index = self.selected_index;

        v_flex()
            .key_context("ThreadSwitcher")
            .track_focus(&self.focus_handle)
            .p_1p5()
            .w(rems_from_px(440.))
            .elevation_3(cx)
            .on_modifiers_changed(cx.listener(Self::handle_modifiers_changed))
            .on_action(cx.listener(Self::confirm))
            .on_action(cx.listener(Self::cancel))
            .on_action(cx.listener(Self::toggle))
            .child(
                v_flex()
                    .id("thread-switcher-list")
                    .gap_0p5()
                    .max_h_128()
                    .overflow_y_scroll()
                    .track_scroll(&self.scroll_handle)
                    .children(self.entries.iter().enumerate().map(|(ix, entry)| {
                        let id =
                            SharedString::from(format!("thread-switcher-{}", entry.session_id));

                        ThreadItem::new(id, entry.title.clone())
                            .rounded(true)
                            .icon(entry.icon)
                            .status(entry.status)
                            .when_some(entry.icon_from_external_svg.clone(), |this, svg| {
                                this.custom_icon_from_external_svg(svg)
                            })
                            .when_some(entry.project_name.clone(), |this, name| {
                                this.project_name(name)
                            })
                            .worktrees(entry.worktrees.clone())
                            .timestamp(entry.timestamp.clone())
                            .title_generating(entry.is_title_generating)
                            .notified(entry.notified)
                            .when(entry.diff_stats.lines_added > 0, |this| {
                                this.added(entry.diff_stats.lines_added as usize)
                            })
                            .when(entry.diff_stats.lines_removed > 0, |this| {
                                this.removed(entry.diff_stats.lines_removed as usize)
                            })
                            .selected(ix == selected_index)
                            .base_bg(cx.theme().colors().elevated_surface_background)
                            .on_hover(cx.listener(move |this, hovered: &bool, _window, cx| {
                                if *hovered {
                                    this.select_index(ix, cx);
                                }
                            }))
                            // TODO: This is not properly propagating to the tread item.
                            .on_click(cx.listener(
                                move |this, _event: &gpui::ClickEvent, _window, cx| {
                                    this.select_and_confirm(ix, cx);
                                },
                            ))
                            .into_any_element()
                    })),
            )
            .vertical_scrollbar_for(&self.scroll_handle, window, cx)
    }
}
