//! A UI interface for managing the [`TrustedWorktrees`] data.

use std::{
    borrow::Cow,
    path::{Path, PathBuf},
    sync::Arc,
};

use collections::{HashMap, HashSet};
use gpui::{DismissEvent, EventEmitter, FocusHandle, Focusable, ScrollHandle, WeakEntity};

use project::{
    WorktreeId,
    trusted_worktrees::{PathTrust, RemoteHostLocation, TrustedWorktrees},
    worktree_store::WorktreeStore,
};
use smallvec::SmallVec;
use theme::ActiveTheme;
use ui::{
    AlertModal, Checkbox, FluentBuilder, KeyBinding, ListBulletItem, ToggleState, WithScrollbar,
    prelude::*,
};

use crate::{DismissDecision, ModalView, ToggleWorktreeSecurity};

pub struct SecurityModal {
    restricted_paths: HashMap<WorktreeId, RestrictedPath>,
    home_dir: Option<PathBuf>,
    trust_parents: bool,
    worktree_store: WeakEntity<WorktreeStore>,
    remote_host: Option<RemoteHostLocation>,
    focus_handle: FocusHandle,
    project_list_scroll_handle: ScrollHandle,
    trusted: Option<bool>,
}

#[derive(Debug, PartialEq, Eq)]
struct RestrictedPath {
    abs_path: Arc<Path>,
    is_file: bool,
    host: Option<RemoteHostLocation>,
}

impl Focusable for SecurityModal {
    fn focus_handle(&self, _: &ui::App) -> FocusHandle {
        self.focus_handle.clone()
    }
}

impl EventEmitter<DismissEvent> for SecurityModal {}

impl ModalView for SecurityModal {
    fn fade_out_background(&self) -> bool {
        true
    }

    fn on_before_dismiss(&mut self, _: &mut Window, _: &mut Context<Self>) -> DismissDecision {
        match self.trusted {
            Some(false) => telemetry::event!("Open in Restricted", source = "Worktree Trust Modal"),
            Some(true) => telemetry::event!("Trust and Continue", source = "Worktree Trust Modal"),
            None => telemetry::event!("Dismissed", source = "Worktree Trust Modal"),
        }
        DismissDecision::Dismiss(true)
    }
}

impl Render for SecurityModal {
    fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
        if self.restricted_paths.is_empty() {
            self.dismiss(cx);
            return v_flex().into_any_element();
        }

        let restricted_count = self.restricted_paths.len();
        let header_label: SharedString = if restricted_count == 1 {
            "Unrecognized Project".into()
        } else {
            format!("Unrecognized Projects ({})", restricted_count).into()
        };

        let trust_label = self.build_trust_label();

        AlertModal::new("security-modal")
            .width(rems(40.))
            .key_context("SecurityModal")
            .track_focus(&self.focus_handle(cx))
            .on_action(cx.listener(|this, _: &menu::Confirm, _window, cx| {
                this.trust_and_dismiss(cx);
            }))
            .on_action(cx.listener(|security_modal, _: &ToggleWorktreeSecurity, _window, cx| {
                security_modal.trusted = Some(false);
                security_modal.dismiss(cx);
            }))
            .header(
                v_flex()
                    .p_3()
                    .gap_1()
                    .rounded_t_md()
                    .bg(cx.theme().colors().editor_background.opacity(0.5))
                    .border_b_1()
                    .border_color(cx.theme().colors().border_variant)
                    .child(
                        h_flex()
                            .gap_2()
                            .child(Icon::new(IconName::Warning).color(Color::Warning))
                            .child(Label::new(header_label)),
                    )
                    .child(
                        div()
                            .size_full()
                            .vertical_scrollbar_for(&self.project_list_scroll_handle, window, cx)
                            .child(
                                v_flex()
                                    .id("paths_container")
                                    .max_h_24()
                                    .overflow_y_scroll()
                                    .track_scroll(&self.project_list_scroll_handle)
                                    .children(
                                        self.restricted_paths.values().filter_map(
                                            |restricted_path| {
                                                let abs_path = if restricted_path.is_file {
                                                    restricted_path.abs_path.parent()
                                                } else {
                                                    Some(restricted_path.abs_path.as_ref())
                                                }?;
                                                let label = match &restricted_path.host {
                                                    Some(remote_host) => {
                                                        match &remote_host.user_name {
                                                            Some(user_name) => format!(
                                                                "{} ({}@{})",
                                                                self.shorten_path(abs_path)
                                                                    .display(),
                                                                user_name,
                                                                remote_host.host_identifier
                                                            ),
                                                            None => format!(
                                                                "{} ({})",
                                                                self.shorten_path(abs_path)
                                                                    .display(),
                                                                remote_host.host_identifier
                                                            ),
                                                        }
                                                    }
                                                    None => self
                                                        .shorten_path(abs_path)
                                                        .display()
                                                        .to_string(),
                                                };
                                                Some(
                                                    h_flex()
                                                        .pl(
                                                            IconSize::default().rems() + rems(0.5),
                                                        )
                                                        .child(
                                                            Label::new(label).color(Color::Muted),
                                                        ),
                                                )
                                            },
                                        ),
                                    ),
                            ),
                    ),
            )
            .child(
                v_flex()
                    .gap_2()
                    .child(
                        v_flex()
                            .child(
                                Label::new(
                                    "Untrusted projects are opened in Restricted Mode to protect your system.",
                                )
                                .color(Color::Muted),
                            )
                            .child(
                                Label::new(
                                    "Review .zed/settings.json for any extensions or commands configured by this project.",
                                )
                                .color(Color::Muted),
                            ),
                    )
                    .child(
                        v_flex()
                            .child(Label::new("Restricted Mode prevents:").color(Color::Muted))
                            .child(ListBulletItem::new("Project settings from being applied"))
                            .child(ListBulletItem::new("Language servers from running"))
                            .child(ListBulletItem::new("MCP Server integrations from installing")),
                    )
                    .map(|this| match trust_label {
                        Some(trust_label) => this.child(
                            Checkbox::new("trust-parents", ToggleState::from(self.trust_parents))
                                .label(trust_label)
                                .on_click(cx.listener(
                                    |security_modal, state: &ToggleState, _, cx| {
                                        security_modal.trust_parents = state.selected();
                                        cx.notify();
                                        cx.stop_propagation();
                                    },
                                )),
                        ),
                        None => this,
                    }),
            )
            .footer(
                h_flex()
                    .px_3()
                    .pb_3()
                    .gap_1()
                    .justify_end()
                    .child(
                        Button::new("rm", "Stay in Restricted Mode")
                            .key_binding(
                                KeyBinding::for_action(
                                    &ToggleWorktreeSecurity,
                                    cx,
                                )
                                .map(|kb| kb.size(rems_from_px(12.))),
                            )
                            .on_click(cx.listener(move |security_modal, _, _, cx| {
                                security_modal.trusted = Some(false);
                                security_modal.dismiss(cx);
                                cx.stop_propagation();
                            })),
                    )
                    .child(
                        Button::new("tc", "Trust and Continue")
                            .style(ButtonStyle::Filled)
                            .layer(ui::ElevationIndex::ModalSurface)
                            .key_binding(
                                KeyBinding::for_action(&menu::Confirm, cx)
                                    .map(|kb| kb.size(rems_from_px(12.))),
                            )
                            .on_click(cx.listener(move |security_modal, _, _, cx| {
                                security_modal.trust_and_dismiss(cx);
                                cx.stop_propagation();
                            })),
                    ),
            )
            .into_any_element()
    }
}

impl SecurityModal {
    pub fn new(
        worktree_store: WeakEntity<WorktreeStore>,
        remote_host: Option<impl Into<RemoteHostLocation>>,
        cx: &mut Context<Self>,
    ) -> Self {
        let mut this = Self {
            worktree_store,
            remote_host: remote_host.map(|host| host.into()),
            restricted_paths: HashMap::default(),
            focus_handle: cx.focus_handle(),
            project_list_scroll_handle: ScrollHandle::new(),
            trust_parents: false,
            home_dir: std::env::home_dir(),
            trusted: None,
        };
        this.refresh_restricted_paths(cx);

        this
    }

    fn build_trust_label(&self) -> Option<Cow<'static, str>> {
        let mut has_restricted_files = false;
        let available_parents = self
            .restricted_paths
            .values()
            .filter(|restricted_path| {
                has_restricted_files |= restricted_path.is_file;
                !restricted_path.is_file
            })
            .filter_map(|restricted_path| restricted_path.abs_path.parent())
            .collect::<SmallVec<[_; 2]>>();
        match available_parents.len() {
            0 => {
                if has_restricted_files {
                    Some(Cow::Borrowed("Trust all single files"))
                } else {
                    None
                }
            }
            1 => Some(Cow::Owned(format!(
                "Trust all projects in the {:} folder",
                self.shorten_path(available_parents[0]).display()
            ))),
            _ => Some(Cow::Borrowed("Trust all projects in the parent folders")),
        }
    }

    fn shorten_path<'a>(&self, path: &'a Path) -> Cow<'a, Path> {
        match &self.home_dir {
            Some(home_dir) => path
                .strip_prefix(home_dir)
                .map(|stripped| Path::new("~").join(stripped))
                .map(Cow::Owned)
                .unwrap_or(Cow::Borrowed(path)),
            None => Cow::Borrowed(path),
        }
    }

    fn trust_and_dismiss(&mut self, cx: &mut Context<Self>) {
        if let Some((trusted_worktrees, worktree_store)) =
            TrustedWorktrees::try_get_global(cx).zip(self.worktree_store.upgrade())
        {
            trusted_worktrees.update(cx, |trusted_worktrees, cx| {
                let mut paths_to_trust = self
                    .restricted_paths
                    .keys()
                    .copied()
                    .map(PathTrust::Worktree)
                    .collect::<HashSet<_>>();
                if self.trust_parents {
                    paths_to_trust.extend(self.restricted_paths.values().filter_map(
                        |restricted_paths| {
                            if restricted_paths.is_file {
                                None
                            } else {
                                let parent_abs_path =
                                    restricted_paths.abs_path.parent()?.to_owned();
                                Some(PathTrust::AbsPath(parent_abs_path))
                            }
                        },
                    ));
                }
                trusted_worktrees.trust(&worktree_store, paths_to_trust, cx);
            });
        }

        self.trusted = Some(true);
        self.dismiss(cx);
    }

    pub fn dismiss(&mut self, cx: &mut Context<Self>) {
        cx.emit(DismissEvent);
    }

    pub fn refresh_restricted_paths(&mut self, cx: &mut Context<Self>) {
        if let Some(trusted_worktrees) = TrustedWorktrees::try_get_global(cx) {
            if let Some(worktree_store) = self.worktree_store.upgrade() {
                let new_restricted_worktrees = trusted_worktrees
                    .read(cx)
                    .restricted_worktrees(&worktree_store, cx)
                    .into_iter()
                    .filter_map(|(worktree_id, abs_path)| {
                        let worktree = worktree_store.read(cx).worktree_for_id(worktree_id, cx)?;
                        Some((
                            worktree_id,
                            RestrictedPath {
                                abs_path,
                                is_file: worktree.read(cx).is_single_file(),
                                host: self.remote_host.clone(),
                            },
                        ))
                    })
                    .collect::<HashMap<_, _>>();

                if self.restricted_paths != new_restricted_worktrees {
                    self.trust_parents = false;
                    self.restricted_paths = new_restricted_worktrees;
                    cx.notify();
                }
            }
        } else if !self.restricted_paths.is_empty() {
            self.restricted_paths.clear();
            cx.notify();
        }
    }
}
