use crate::{EncodingSelector, Toggle};

use editor::Editor;
use encoding_rs::{Encoding, UTF_8};
use gpui::{
    App, Context, Entity, IntoElement, ParentElement, Render, Styled, Subscription, WeakEntity,
    Window, div,
};
use project::Project;
use ui::{Button, ButtonCommon, Clickable, LabelSize, Tooltip};
use workspace::{
    EncodingDisplayOptions, HideStatusItem, StatusBarSettings, StatusItemView, Workspace,
    item::{ItemHandle, Settings},
};

pub struct ActiveBufferEncoding {
    active_encoding: Option<&'static Encoding>,
    workspace: WeakEntity<Workspace>,
    project: Entity<Project>,
    _observe_active_editor: Option<Subscription>,
    has_bom: bool,
    is_dirty: bool,
    is_shared: bool,
    is_via_remote_server: bool,
}

impl ActiveBufferEncoding {
    pub fn new(workspace: &Workspace) -> Self {
        Self {
            active_encoding: None,
            workspace: workspace.weak_handle(),
            project: workspace.project().clone(),
            _observe_active_editor: None,
            has_bom: false,
            is_dirty: false,
            is_shared: false,
            is_via_remote_server: false,
        }
    }

    fn update_encoding(&mut self, editor: Entity<Editor>, _: &mut Window, cx: &mut Context<Self>) {
        self.active_encoding = None;
        self.has_bom = false;
        self.is_dirty = false;

        let project = self.project.read(cx);
        self.is_shared = project.is_shared();
        self.is_via_remote_server = project.is_via_remote_server();

        if let Some(buffer) = editor.read(cx).active_buffer(cx) {
            let buffer = buffer.read(cx);
            self.active_encoding = Some(buffer.encoding());
            self.has_bom = buffer.has_bom();
            self.is_dirty = buffer.is_dirty();
        }

        cx.notify();
    }
}

impl Render for ActiveBufferEncoding {
    fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
        let Some(active_encoding) = self.active_encoding else {
            return div().hidden();
        };

        let display_option = StatusBarSettings::get_global(cx).active_encoding_button;
        let is_utf8 = active_encoding == UTF_8;
        if !display_option.should_show(is_utf8, self.has_bom) {
            return div().hidden();
        }

        let mut text = active_encoding.name().to_string();
        if self.has_bom {
            text.push_str(" (BOM)");
        }

        let (disabled, tooltip_text) = if self.is_dirty {
            (true, "Save file to change encoding")
        } else if self.is_shared {
            (true, "Cannot change encoding during collaboration")
        } else if self.is_via_remote_server {
            (true, "Cannot change encoding of remote server file")
        } else {
            (false, "Reopen with Encoding")
        };

        div().child(
            Button::new("change-encoding", text)
                .label_size(LabelSize::Small)
                .on_click(cx.listener(move |this, _, window, cx| {
                    if disabled {
                        return;
                    }
                    if let Some(workspace) = this.workspace.upgrade() {
                        workspace.update(cx, |workspace, cx| {
                            EncodingSelector::toggle(workspace, window, cx)
                        });
                    }
                }))
                .tooltip(move |_window, cx| {
                    if disabled {
                        Tooltip::text(tooltip_text)(_window, cx)
                    } else {
                        Tooltip::for_action(tooltip_text, &Toggle, cx)
                    }
                }),
        )
    }
}

impl StatusItemView for ActiveBufferEncoding {
    fn set_active_pane_item(
        &mut self,
        active_pane_item: Option<&dyn ItemHandle>,
        window: &mut Window,
        cx: &mut Context<Self>,
    ) {
        if let Some(editor) = active_pane_item.and_then(|item| item.downcast::<Editor>()) {
            self._observe_active_editor =
                Some(cx.observe_in(&editor, window, Self::update_encoding));
            self.update_encoding(editor, window, cx);
        } else {
            self.active_encoding = None;
            self.has_bom = false;
            self.is_dirty = false;
            self.is_shared = false;
            self.is_via_remote_server = false;
            self._observe_active_editor = None;
        }

        cx.notify();
    }

    fn hide_setting(&self, _: &App) -> Option<HideStatusItem> {
        Some(HideStatusItem::new(|settings| {
            settings
                .status_bar
                .get_or_insert_default()
                .active_encoding_button = Some(EncodingDisplayOptions::Disabled);
        }))
    }
}
