mod channel_index;

use crate::{channel_buffer::ChannelBuffer, channel_chat::ChannelChat, ChannelMessage};
use anyhow::{anyhow, Result};
use channel_index::ChannelIndex;
use client::{
    ChannelId, Client, ClientSettings, DevServerId, ProjectId, RemoteProjectId, Subscription, User,
    UserId, UserStore,
};
use collections::{hash_map, HashMap, HashSet};
use futures::{channel::mpsc, future::Shared, Future, FutureExt, StreamExt};
use gpui::{
    AppContext, AsyncAppContext, Context, EventEmitter, Global, Model, ModelContext, SharedString,
    Task, WeakModel,
};
use language::Capability;
use rpc::{
    proto::{self, ChannelRole, ChannelVisibility, DevServerStatus},
    TypedEnvelope,
};
use settings::Settings;
use std::{mem, sync::Arc, time::Duration};
use util::{maybe, ResultExt};

pub const RECONNECT_TIMEOUT: Duration = Duration::from_secs(30);

pub fn init(client: &Arc<Client>, user_store: Model<UserStore>, cx: &mut AppContext) {
    let channel_store =
        cx.new_model(|cx| ChannelStore::new(client.clone(), user_store.clone(), cx));
    cx.set_global(GlobalChannelStore(channel_store));
}

#[derive(Debug, Clone, Default, PartialEq)]
struct NotesVersion {
    epoch: u64,
    version: clock::Global,
}

#[derive(Debug, Clone)]
pub struct HostedProject {
    project_id: ProjectId,
    channel_id: ChannelId,
    name: SharedString,
    _visibility: proto::ChannelVisibility,
}
impl From<proto::HostedProject> for HostedProject {
    fn from(project: proto::HostedProject) -> Self {
        Self {
            project_id: ProjectId(project.project_id),
            channel_id: ChannelId(project.channel_id),
            _visibility: project.visibility(),
            name: project.name.into(),
        }
    }
}

#[derive(Debug, Clone)]
pub struct RemoteProject {
    pub id: RemoteProjectId,
    pub project_id: Option<ProjectId>,
    pub channel_id: ChannelId,
    pub name: SharedString,
    pub path: SharedString,
    pub dev_server_id: DevServerId,
}

impl From<proto::RemoteProject> for RemoteProject {
    fn from(project: proto::RemoteProject) -> Self {
        Self {
            id: RemoteProjectId(project.id),
            project_id: project.project_id.map(|id| ProjectId(id)),
            channel_id: ChannelId(project.channel_id),
            name: project.name.into(),
            path: project.path.into(),
            dev_server_id: DevServerId(project.dev_server_id),
        }
    }
}

#[derive(Debug, Clone)]
pub struct DevServer {
    pub id: DevServerId,
    pub channel_id: ChannelId,
    pub name: SharedString,
    pub status: DevServerStatus,
}

impl From<proto::DevServer> for DevServer {
    fn from(dev_server: proto::DevServer) -> Self {
        Self {
            id: DevServerId(dev_server.dev_server_id),
            channel_id: ChannelId(dev_server.channel_id),
            status: dev_server.status(),
            name: dev_server.name.into(),
        }
    }
}

pub struct ChannelStore {
    pub channel_index: ChannelIndex,
    channel_invitations: Vec<Arc<Channel>>,
    channel_participants: HashMap<ChannelId, Vec<Arc<User>>>,
    channel_states: HashMap<ChannelId, ChannelState>,
    hosted_projects: HashMap<ProjectId, HostedProject>,
    remote_projects: HashMap<RemoteProjectId, RemoteProject>,
    dev_servers: HashMap<DevServerId, DevServer>,

    outgoing_invites: HashSet<(ChannelId, UserId)>,
    update_channels_tx: mpsc::UnboundedSender<proto::UpdateChannels>,
    opened_buffers: HashMap<ChannelId, OpenedModelHandle<ChannelBuffer>>,
    opened_chats: HashMap<ChannelId, OpenedModelHandle<ChannelChat>>,
    client: Arc<Client>,
    user_store: Model<UserStore>,
    _rpc_subscriptions: [Subscription; 2],
    _watch_connection_status: Task<Option<()>>,
    disconnect_channel_buffers_task: Option<Task<()>>,
    _update_channels: Task<()>,
}

#[derive(Clone, Debug)]
pub struct Channel {
    pub id: ChannelId,
    pub name: SharedString,
    pub visibility: proto::ChannelVisibility,
    pub parent_path: Vec<ChannelId>,
}

#[derive(Default, Debug)]
pub struct ChannelState {
    latest_chat_message: Option<u64>,
    latest_notes_version: NotesVersion,
    observed_notes_version: NotesVersion,
    observed_chat_message: Option<u64>,
    role: Option<ChannelRole>,
    projects: HashSet<ProjectId>,
    dev_servers: HashSet<DevServerId>,
    remote_projects: HashSet<RemoteProjectId>,
}

impl Channel {
    pub fn link(&self, cx: &AppContext) -> String {
        format!(
            "{}/channel/{}-{}",
            ClientSettings::get_global(cx).server_url,
            Self::slug(&self.name),
            self.id
        )
    }

    pub fn notes_link(&self, heading: Option<String>, cx: &AppContext) -> String {
        self.link(cx)
            + "/notes"
            + &heading
                .map(|h| format!("#{}", Self::slug(&h)))
                .unwrap_or_default()
    }

    pub fn is_root_channel(&self) -> bool {
        self.parent_path.is_empty()
    }

    pub fn root_id(&self) -> ChannelId {
        self.parent_path.first().copied().unwrap_or(self.id)
    }

    pub fn slug(str: &str) -> String {
        let slug: String = str
            .chars()
            .map(|c| if c.is_alphanumeric() { c } else { '-' })
            .collect();

        slug.trim_matches(|c| c == '-').to_string()
    }
}

pub struct ChannelMembership {
    pub user: Arc<User>,
    pub kind: proto::channel_member::Kind,
    pub role: proto::ChannelRole,
}
impl ChannelMembership {
    pub fn sort_key(&self) -> MembershipSortKey {
        MembershipSortKey {
            role_order: match self.role {
                proto::ChannelRole::Admin => 0,
                proto::ChannelRole::Member => 1,
                proto::ChannelRole::Banned => 2,
                proto::ChannelRole::Talker => 3,
                proto::ChannelRole::Guest => 4,
            },
            kind_order: match self.kind {
                proto::channel_member::Kind::Member => 0,
                proto::channel_member::Kind::Invitee => 1,
            },
            username_order: self.user.github_login.as_str(),
        }
    }
}

#[derive(PartialOrd, Ord, PartialEq, Eq)]
pub struct MembershipSortKey<'a> {
    role_order: u8,
    kind_order: u8,
    username_order: &'a str,
}

pub enum ChannelEvent {
    ChannelCreated(ChannelId),
    ChannelRenamed(ChannelId),
}

impl EventEmitter<ChannelEvent> for ChannelStore {}

enum OpenedModelHandle<E> {
    Open(WeakModel<E>),
    Loading(Shared<Task<Result<Model<E>, Arc<anyhow::Error>>>>),
}

struct GlobalChannelStore(Model<ChannelStore>);

impl Global for GlobalChannelStore {}

impl ChannelStore {
    pub fn global(cx: &AppContext) -> Model<Self> {
        cx.global::<GlobalChannelStore>().0.clone()
    }

    pub fn new(
        client: Arc<Client>,
        user_store: Model<UserStore>,
        cx: &mut ModelContext<Self>,
    ) -> Self {
        let rpc_subscriptions = [
            client.add_message_handler(cx.weak_model(), Self::handle_update_channels),
            client.add_message_handler(cx.weak_model(), Self::handle_update_user_channels),
        ];

        let mut connection_status = client.status();
        let (update_channels_tx, mut update_channels_rx) = mpsc::unbounded();
        let watch_connection_status = cx.spawn(|this, mut cx| async move {
            while let Some(status) = connection_status.next().await {
                let this = this.upgrade()?;
                match status {
                    client::Status::Connected { .. } => {
                        this.update(&mut cx, |this, cx| this.handle_connect(cx))
                            .ok()?
                            .await
                            .log_err()?;
                    }
                    client::Status::SignedOut | client::Status::UpgradeRequired => {
                        this.update(&mut cx, |this, cx| this.handle_disconnect(false, cx))
                            .ok();
                    }
                    _ => {
                        this.update(&mut cx, |this, cx| this.handle_disconnect(true, cx))
                            .ok();
                    }
                }
            }
            Some(())
        });

        Self {
            channel_invitations: Vec::default(),
            channel_index: ChannelIndex::default(),
            channel_participants: Default::default(),
            hosted_projects: Default::default(),
            remote_projects: Default::default(),
            dev_servers: Default::default(),
            outgoing_invites: Default::default(),
            opened_buffers: Default::default(),
            opened_chats: Default::default(),
            update_channels_tx,
            client,
            user_store,
            _rpc_subscriptions: rpc_subscriptions,
            _watch_connection_status: watch_connection_status,
            disconnect_channel_buffers_task: None,
            _update_channels: cx.spawn(|this, mut cx| async move {
                maybe!(async move {
                    while let Some(update_channels) = update_channels_rx.next().await {
                        if let Some(this) = this.upgrade() {
                            let update_task = this.update(&mut cx, |this, cx| {
                                this.update_channels(update_channels, cx)
                            })?;
                            if let Some(update_task) = update_task {
                                update_task.await.log_err();
                            }
                        }
                    }
                    anyhow::Ok(())
                })
                .await
                .log_err();
            }),
            channel_states: Default::default(),
        }
    }

    pub fn client(&self) -> Arc<Client> {
        self.client.clone()
    }

    /// Returns the number of unique channels in the store
    pub fn channel_count(&self) -> usize {
        self.channel_index.by_id().len()
    }

    /// Returns the index of a channel ID in the list of unique channels
    pub fn index_of_channel(&self, channel_id: ChannelId) -> Option<usize> {
        self.channel_index
            .by_id()
            .keys()
            .position(|id| *id == channel_id)
    }

    /// Returns an iterator over all unique channels
    pub fn channels(&self) -> impl '_ + Iterator<Item = &Arc<Channel>> {
        self.channel_index.by_id().values()
    }

    /// Iterate over all entries in the channel DAG
    pub fn ordered_channels(&self) -> impl '_ + Iterator<Item = (usize, &Arc<Channel>)> {
        self.channel_index
            .ordered_channels()
            .iter()
            .filter_map(move |id| {
                let channel = self.channel_index.by_id().get(id)?;
                Some((channel.parent_path.len(), channel))
            })
    }

    pub fn channel_at_index(&self, ix: usize) -> Option<&Arc<Channel>> {
        let channel_id = self.channel_index.ordered_channels().get(ix)?;
        self.channel_index.by_id().get(channel_id)
    }

    pub fn channel_at(&self, ix: usize) -> Option<&Arc<Channel>> {
        self.channel_index.by_id().values().nth(ix)
    }

    pub fn has_channel_invitation(&self, channel_id: ChannelId) -> bool {
        self.channel_invitations
            .iter()
            .any(|channel| channel.id == channel_id)
    }

    pub fn channel_invitations(&self) -> &[Arc<Channel>] {
        &self.channel_invitations
    }

    pub fn channel_for_id(&self, channel_id: ChannelId) -> Option<&Arc<Channel>> {
        self.channel_index.by_id().get(&channel_id)
    }

    pub fn projects_for_id(&self, channel_id: ChannelId) -> Vec<(SharedString, ProjectId)> {
        let mut projects: Vec<(SharedString, ProjectId)> = self
            .channel_states
            .get(&channel_id)
            .map(|state| state.projects.clone())
            .unwrap_or_default()
            .into_iter()
            .flat_map(|id| Some((self.hosted_projects.get(&id)?.name.clone(), id)))
            .collect();
        projects.sort();
        projects
    }

    pub fn dev_servers_for_id(&self, channel_id: ChannelId) -> Vec<DevServer> {
        let mut dev_servers: Vec<DevServer> = self
            .channel_states
            .get(&channel_id)
            .map(|state| state.dev_servers.clone())
            .unwrap_or_default()
            .into_iter()
            .flat_map(|id| self.dev_servers.get(&id).cloned())
            .collect();
        dev_servers.sort_by_key(|s| (s.name.clone(), s.id));
        dev_servers
    }

    pub fn find_dev_server_by_id(&self, id: DevServerId) -> Option<&DevServer> {
        self.dev_servers.get(&id)
    }

    pub fn find_remote_project_by_id(&self, id: RemoteProjectId) -> Option<&RemoteProject> {
        self.remote_projects.get(&id)
    }

    pub fn remote_projects_for_id(&self, channel_id: ChannelId) -> Vec<RemoteProject> {
        let mut remote_projects: Vec<RemoteProject> = self
            .channel_states
            .get(&channel_id)
            .map(|state| state.remote_projects.clone())
            .unwrap_or_default()
            .into_iter()
            .flat_map(|id| self.remote_projects.get(&id).cloned())
            .collect();
        remote_projects.sort_by_key(|p| (p.name.clone(), p.id));
        remote_projects
    }

    pub fn has_open_channel_buffer(&self, channel_id: ChannelId, _cx: &AppContext) -> bool {
        if let Some(buffer) = self.opened_buffers.get(&channel_id) {
            if let OpenedModelHandle::Open(buffer) = buffer {
                return buffer.upgrade().is_some();
            }
        }
        false
    }

    pub fn open_channel_buffer(
        &mut self,
        channel_id: ChannelId,
        cx: &mut ModelContext<Self>,
    ) -> Task<Result<Model<ChannelBuffer>>> {
        let client = self.client.clone();
        let user_store = self.user_store.clone();
        let channel_store = cx.handle();
        self.open_channel_resource(
            channel_id,
            |this| &mut this.opened_buffers,
            |channel, cx| ChannelBuffer::new(channel, client, user_store, channel_store, cx),
            cx,
        )
    }

    pub fn fetch_channel_messages(
        &self,
        message_ids: Vec<u64>,
        cx: &mut ModelContext<Self>,
    ) -> Task<Result<Vec<ChannelMessage>>> {
        let request = if message_ids.is_empty() {
            None
        } else {
            Some(
                self.client
                    .request(proto::GetChannelMessagesById { message_ids }),
            )
        };
        cx.spawn(|this, mut cx| async move {
            if let Some(request) = request {
                let response = request.await?;
                let this = this
                    .upgrade()
                    .ok_or_else(|| anyhow!("channel store dropped"))?;
                let user_store = this.update(&mut cx, |this, _| this.user_store.clone())?;
                ChannelMessage::from_proto_vec(response.messages, &user_store, &mut cx).await
            } else {
                Ok(Vec::new())
            }
        })
    }

    pub fn has_channel_buffer_changed(&self, channel_id: ChannelId) -> bool {
        self.channel_states
            .get(&channel_id)
            .is_some_and(|state| state.has_channel_buffer_changed())
    }

    pub fn has_new_messages(&self, channel_id: ChannelId) -> bool {
        self.channel_states
            .get(&channel_id)
            .is_some_and(|state| state.has_new_messages())
    }

    pub fn set_acknowledged_message_id(&mut self, channel_id: ChannelId, message_id: Option<u64>) {
        if let Some(state) = self.channel_states.get_mut(&channel_id) {
            state.latest_chat_message = message_id;
        }
    }

    pub fn last_acknowledge_message_id(&self, channel_id: ChannelId) -> Option<u64> {
        self.channel_states.get(&channel_id).and_then(|state| {
            if let Some(last_message_id) = state.latest_chat_message {
                if state
                    .last_acknowledged_message_id()
                    .is_some_and(|id| id < last_message_id)
                {
                    return state.last_acknowledged_message_id();
                }
            }

            None
        })
    }

    pub fn acknowledge_message_id(
        &mut self,
        channel_id: ChannelId,
        message_id: u64,
        cx: &mut ModelContext<Self>,
    ) {
        self.channel_states
            .entry(channel_id)
            .or_insert_with(|| Default::default())
            .acknowledge_message_id(message_id);
        cx.notify();
    }

    pub fn update_latest_message_id(
        &mut self,
        channel_id: ChannelId,
        message_id: u64,
        cx: &mut ModelContext<Self>,
    ) {
        self.channel_states
            .entry(channel_id)
            .or_insert_with(|| Default::default())
            .update_latest_message_id(message_id);
        cx.notify();
    }

    pub fn acknowledge_notes_version(
        &mut self,
        channel_id: ChannelId,
        epoch: u64,
        version: &clock::Global,
        cx: &mut ModelContext<Self>,
    ) {
        self.channel_states
            .entry(channel_id)
            .or_insert_with(|| Default::default())
            .acknowledge_notes_version(epoch, version);
        cx.notify()
    }

    pub fn update_latest_notes_version(
        &mut self,
        channel_id: ChannelId,
        epoch: u64,
        version: &clock::Global,
        cx: &mut ModelContext<Self>,
    ) {
        self.channel_states
            .entry(channel_id)
            .or_insert_with(|| Default::default())
            .update_latest_notes_version(epoch, version);
        cx.notify()
    }

    pub fn open_channel_chat(
        &mut self,
        channel_id: ChannelId,
        cx: &mut ModelContext<Self>,
    ) -> Task<Result<Model<ChannelChat>>> {
        let client = self.client.clone();
        let user_store = self.user_store.clone();
        let this = cx.handle();
        self.open_channel_resource(
            channel_id,
            |this| &mut this.opened_chats,
            |channel, cx| ChannelChat::new(channel, this, user_store, client, cx),
            cx,
        )
    }

    /// Asynchronously open a given resource associated with a channel.
    ///
    /// Make sure that the resource is only opened once, even if this method
    /// is called multiple times with the same channel id while the first task
    /// is still running.
    fn open_channel_resource<T, F, Fut>(
        &mut self,
        channel_id: ChannelId,
        get_map: fn(&mut Self) -> &mut HashMap<ChannelId, OpenedModelHandle<T>>,
        load: F,
        cx: &mut ModelContext<Self>,
    ) -> Task<Result<Model<T>>>
    where
        F: 'static + FnOnce(Arc<Channel>, AsyncAppContext) -> Fut,
        Fut: Future<Output = Result<Model<T>>>,
        T: 'static,
    {
        let task = loop {
            match get_map(self).entry(channel_id) {
                hash_map::Entry::Occupied(e) => match e.get() {
                    OpenedModelHandle::Open(model) => {
                        if let Some(model) = model.upgrade() {
                            break Task::ready(Ok(model)).shared();
                        } else {
                            get_map(self).remove(&channel_id);
                            continue;
                        }
                    }
                    OpenedModelHandle::Loading(task) => {
                        break task.clone();
                    }
                },
                hash_map::Entry::Vacant(e) => {
                    let task = cx
                        .spawn(move |this, mut cx| async move {
                            let channel = this.update(&mut cx, |this, _| {
                                this.channel_for_id(channel_id).cloned().ok_or_else(|| {
                                    Arc::new(anyhow!("no channel for id: {}", channel_id))
                                })
                            })??;

                            load(channel, cx).await.map_err(Arc::new)
                        })
                        .shared();

                    e.insert(OpenedModelHandle::Loading(task.clone()));
                    cx.spawn({
                        let task = task.clone();
                        move |this, mut cx| async move {
                            let result = task.await;
                            this.update(&mut cx, |this, _| match result {
                                Ok(model) => {
                                    get_map(this).insert(
                                        channel_id,
                                        OpenedModelHandle::Open(model.downgrade()),
                                    );
                                }
                                Err(_) => {
                                    get_map(this).remove(&channel_id);
                                }
                            })
                            .ok();
                        }
                    })
                    .detach();
                    break task;
                }
            }
        };
        cx.background_executor()
            .spawn(async move { task.await.map_err(|error| anyhow!("{}", error)) })
    }

    pub fn is_channel_admin(&self, channel_id: ChannelId) -> bool {
        self.channel_role(channel_id) == proto::ChannelRole::Admin
    }

    pub fn is_root_channel(&self, channel_id: ChannelId) -> bool {
        self.channel_index
            .by_id()
            .get(&channel_id)
            .map_or(false, |channel| channel.is_root_channel())
    }

    pub fn is_public_channel(&self, channel_id: ChannelId) -> bool {
        self.channel_index
            .by_id()
            .get(&channel_id)
            .map_or(false, |channel| {
                channel.visibility == ChannelVisibility::Public
            })
    }

    pub fn channel_capability(&self, channel_id: ChannelId) -> Capability {
        match self.channel_role(channel_id) {
            ChannelRole::Admin | ChannelRole::Member => Capability::ReadWrite,
            _ => Capability::ReadOnly,
        }
    }

    pub fn channel_role(&self, channel_id: ChannelId) -> proto::ChannelRole {
        maybe!({
            let mut channel = self.channel_for_id(channel_id)?;
            if !channel.is_root_channel() {
                channel = self.channel_for_id(channel.root_id())?;
            }
            let root_channel_state = self.channel_states.get(&channel.id);
            root_channel_state?.role
        })
        .unwrap_or(proto::ChannelRole::Guest)
    }

    pub fn channel_participants(&self, channel_id: ChannelId) -> &[Arc<User>] {
        self.channel_participants
            .get(&channel_id)
            .map_or(&[], |v| v.as_slice())
    }

    pub fn create_channel(
        &self,
        name: &str,
        parent_id: Option<ChannelId>,
        cx: &mut ModelContext<Self>,
    ) -> Task<Result<ChannelId>> {
        let client = self.client.clone();
        let name = name.trim_start_matches('#').to_owned();
        cx.spawn(move |this, mut cx| async move {
            let response = client
                .request(proto::CreateChannel {
                    name,
                    parent_id: parent_id.map(|cid| cid.0),
                })
                .await?;

            let channel = response
                .channel
                .ok_or_else(|| anyhow!("missing channel in response"))?;
            let channel_id = ChannelId(channel.id);

            this.update(&mut cx, |this, cx| {
                let task = this.update_channels(
                    proto::UpdateChannels {
                        channels: vec![channel],
                        ..Default::default()
                    },
                    cx,
                );
                assert!(task.is_none());

                // This event is emitted because the collab panel wants to clear the pending edit state
                // before this frame is rendered. But we can't guarantee that the collab panel's future
                // will resolve before this flush_effects finishes. Synchronously emitting this event
                // ensures that the collab panel will observe this creation before the frame completes
                cx.emit(ChannelEvent::ChannelCreated(channel_id));
            })?;

            Ok(channel_id)
        })
    }

    pub fn move_channel(
        &mut self,
        channel_id: ChannelId,
        to: ChannelId,
        cx: &mut ModelContext<Self>,
    ) -> Task<Result<()>> {
        let client = self.client.clone();
        cx.spawn(move |_, _| async move {
            let _ = client
                .request(proto::MoveChannel {
                    channel_id: channel_id.0,
                    to: to.0,
                })
                .await?;

            Ok(())
        })
    }

    pub fn set_channel_visibility(
        &mut self,
        channel_id: ChannelId,
        visibility: ChannelVisibility,
        cx: &mut ModelContext<Self>,
    ) -> Task<Result<()>> {
        let client = self.client.clone();
        cx.spawn(move |_, _| async move {
            let _ = client
                .request(proto::SetChannelVisibility {
                    channel_id: channel_id.0,
                    visibility: visibility.into(),
                })
                .await?;

            Ok(())
        })
    }

    pub fn invite_member(
        &mut self,
        channel_id: ChannelId,
        user_id: UserId,
        role: proto::ChannelRole,
        cx: &mut ModelContext<Self>,
    ) -> Task<Result<()>> {
        if !self.outgoing_invites.insert((channel_id, user_id)) {
            return Task::ready(Err(anyhow!("invite request already in progress")));
        }

        cx.notify();
        let client = self.client.clone();
        cx.spawn(move |this, mut cx| async move {
            let result = client
                .request(proto::InviteChannelMember {
                    channel_id: channel_id.0,
                    user_id,
                    role: role.into(),
                })
                .await;

            this.update(&mut cx, |this, cx| {
                this.outgoing_invites.remove(&(channel_id, user_id));
                cx.notify();
            })?;

            result?;

            Ok(())
        })
    }

    pub fn remove_member(
        &mut self,
        channel_id: ChannelId,
        user_id: u64,
        cx: &mut ModelContext<Self>,
    ) -> Task<Result<()>> {
        if !self.outgoing_invites.insert((channel_id, user_id)) {
            return Task::ready(Err(anyhow!("invite request already in progress")));
        }

        cx.notify();
        let client = self.client.clone();
        cx.spawn(move |this, mut cx| async move {
            let result = client
                .request(proto::RemoveChannelMember {
                    channel_id: channel_id.0,
                    user_id,
                })
                .await;

            this.update(&mut cx, |this, cx| {
                this.outgoing_invites.remove(&(channel_id, user_id));
                cx.notify();
            })?;
            result?;
            Ok(())
        })
    }

    pub fn set_member_role(
        &mut self,
        channel_id: ChannelId,
        user_id: UserId,
        role: proto::ChannelRole,
        cx: &mut ModelContext<Self>,
    ) -> Task<Result<()>> {
        if !self.outgoing_invites.insert((channel_id, user_id)) {
            return Task::ready(Err(anyhow!("member request already in progress")));
        }

        cx.notify();
        let client = self.client.clone();
        cx.spawn(move |this, mut cx| async move {
            let result = client
                .request(proto::SetChannelMemberRole {
                    channel_id: channel_id.0,
                    user_id,
                    role: role.into(),
                })
                .await;

            this.update(&mut cx, |this, cx| {
                this.outgoing_invites.remove(&(channel_id, user_id));
                cx.notify();
            })?;

            result?;
            Ok(())
        })
    }

    pub fn rename(
        &mut self,
        channel_id: ChannelId,
        new_name: &str,
        cx: &mut ModelContext<Self>,
    ) -> Task<Result<()>> {
        let client = self.client.clone();
        let name = new_name.to_string();
        cx.spawn(move |this, mut cx| async move {
            let channel = client
                .request(proto::RenameChannel {
                    channel_id: channel_id.0,
                    name,
                })
                .await?
                .channel
                .ok_or_else(|| anyhow!("missing channel in response"))?;
            this.update(&mut cx, |this, cx| {
                let task = this.update_channels(
                    proto::UpdateChannels {
                        channels: vec![channel],
                        ..Default::default()
                    },
                    cx,
                );
                assert!(task.is_none());

                // This event is emitted because the collab panel wants to clear the pending edit state
                // before this frame is rendered. But we can't guarantee that the collab panel's future
                // will resolve before this flush_effects finishes. Synchronously emitting this event
                // ensures that the collab panel will observe this creation before the frame complete
                cx.emit(ChannelEvent::ChannelRenamed(channel_id))
            })?;
            Ok(())
        })
    }

    pub fn respond_to_channel_invite(
        &mut self,
        channel_id: ChannelId,
        accept: bool,
        cx: &mut ModelContext<Self>,
    ) -> Task<Result<()>> {
        let client = self.client.clone();
        cx.background_executor().spawn(async move {
            client
                .request(proto::RespondToChannelInvite {
                    channel_id: channel_id.0,
                    accept,
                })
                .await?;
            Ok(())
        })
    }

    pub fn create_remote_project(
        &mut self,
        channel_id: ChannelId,
        dev_server_id: DevServerId,
        name: String,
        path: String,
        cx: &mut ModelContext<Self>,
    ) -> Task<Result<proto::CreateRemoteProjectResponse>> {
        let client = self.client.clone();
        cx.background_executor().spawn(async move {
            client
                .request(proto::CreateRemoteProject {
                    channel_id: channel_id.0,
                    dev_server_id: dev_server_id.0,
                    name,
                    path,
                })
                .await
        })
    }

    pub fn create_dev_server(
        &mut self,
        channel_id: ChannelId,
        name: String,
        cx: &mut ModelContext<Self>,
    ) -> Task<Result<proto::CreateDevServerResponse>> {
        let client = self.client.clone();
        cx.background_executor().spawn(async move {
            let result = client
                .request(proto::CreateDevServer {
                    channel_id: channel_id.0,
                    name,
                })
                .await?;
            Ok(result)
        })
    }

    pub fn get_channel_member_details(
        &self,
        channel_id: ChannelId,
        cx: &mut ModelContext<Self>,
    ) -> Task<Result<Vec<ChannelMembership>>> {
        let client = self.client.clone();
        let user_store = self.user_store.downgrade();
        cx.spawn(move |_, mut cx| async move {
            let response = client
                .request(proto::GetChannelMembers {
                    channel_id: channel_id.0,
                })
                .await?;

            let user_ids = response.members.iter().map(|m| m.user_id).collect();
            let user_store = user_store
                .upgrade()
                .ok_or_else(|| anyhow!("user store dropped"))?;
            let users = user_store
                .update(&mut cx, |user_store, cx| user_store.get_users(user_ids, cx))?
                .await?;

            Ok(users
                .into_iter()
                .zip(response.members)
                .map(|(user, member)| ChannelMembership {
                    user,
                    role: member.role(),
                    kind: member.kind(),
                })
                .collect())
        })
    }

    pub fn remove_channel(&self, channel_id: ChannelId) -> impl Future<Output = Result<()>> {
        let client = self.client.clone();
        async move {
            client
                .request(proto::DeleteChannel {
                    channel_id: channel_id.0,
                })
                .await?;
            Ok(())
        }
    }

    pub fn has_pending_channel_invite_response(&self, _: &Arc<Channel>) -> bool {
        false
    }

    pub fn has_pending_channel_invite(&self, channel_id: ChannelId, user_id: UserId) -> bool {
        self.outgoing_invites.contains(&(channel_id, user_id))
    }

    async fn handle_update_channels(
        this: Model<Self>,
        message: TypedEnvelope<proto::UpdateChannels>,
        _: Arc<Client>,
        mut cx: AsyncAppContext,
    ) -> Result<()> {
        this.update(&mut cx, |this, _| {
            this.update_channels_tx
                .unbounded_send(message.payload)
                .unwrap();
        })?;
        Ok(())
    }

    async fn handle_update_user_channels(
        this: Model<Self>,
        message: TypedEnvelope<proto::UpdateUserChannels>,
        _: Arc<Client>,
        mut cx: AsyncAppContext,
    ) -> Result<()> {
        this.update(&mut cx, |this, cx| {
            for buffer_version in message.payload.observed_channel_buffer_version {
                let version = language::proto::deserialize_version(&buffer_version.version);
                this.acknowledge_notes_version(
                    ChannelId(buffer_version.channel_id),
                    buffer_version.epoch,
                    &version,
                    cx,
                );
            }
            for message_id in message.payload.observed_channel_message_id {
                this.acknowledge_message_id(
                    ChannelId(message_id.channel_id),
                    message_id.message_id,
                    cx,
                );
            }
            for membership in message.payload.channel_memberships {
                if let Some(role) = ChannelRole::from_i32(membership.role) {
                    this.channel_states
                        .entry(ChannelId(membership.channel_id))
                        .or_insert_with(|| ChannelState::default())
                        .set_role(role)
                }
            }
        })
    }

    fn handle_connect(&mut self, cx: &mut ModelContext<Self>) -> Task<Result<()>> {
        self.channel_index.clear();
        self.channel_invitations.clear();
        self.channel_participants.clear();
        self.channel_index.clear();
        self.outgoing_invites.clear();
        self.disconnect_channel_buffers_task.take();

        for chat in self.opened_chats.values() {
            if let OpenedModelHandle::Open(chat) = chat {
                if let Some(chat) = chat.upgrade() {
                    chat.update(cx, |chat, cx| {
                        chat.rejoin(cx);
                    });
                }
            }
        }

        let mut buffer_versions = Vec::new();
        for buffer in self.opened_buffers.values() {
            if let OpenedModelHandle::Open(buffer) = buffer {
                if let Some(buffer) = buffer.upgrade() {
                    let channel_buffer = buffer.read(cx);
                    let buffer = channel_buffer.buffer().read(cx);
                    buffer_versions.push(proto::ChannelBufferVersion {
                        channel_id: channel_buffer.channel_id.0,
                        epoch: channel_buffer.epoch(),
                        version: language::proto::serialize_version(&buffer.version()),
                    });
                }
            }
        }

        if buffer_versions.is_empty() {
            return Task::ready(Ok(()));
        }

        let response = self.client.request(proto::RejoinChannelBuffers {
            buffers: buffer_versions,
        });

        cx.spawn(|this, mut cx| async move {
            let mut response = response.await?;

            this.update(&mut cx, |this, cx| {
                this.opened_buffers.retain(|_, buffer| match buffer {
                    OpenedModelHandle::Open(channel_buffer) => {
                        let Some(channel_buffer) = channel_buffer.upgrade() else {
                            return false;
                        };

                        channel_buffer.update(cx, |channel_buffer, cx| {
                            let channel_id = channel_buffer.channel_id;
                            if let Some(remote_buffer) = response
                                .buffers
                                .iter_mut()
                                .find(|buffer| buffer.channel_id == channel_id.0)
                            {
                                let channel_id = channel_buffer.channel_id;
                                let remote_version =
                                    language::proto::deserialize_version(&remote_buffer.version);

                                channel_buffer.replace_collaborators(
                                    mem::take(&mut remote_buffer.collaborators),
                                    cx,
                                );

                                let operations = channel_buffer
                                    .buffer()
                                    .update(cx, |buffer, cx| {
                                        let outgoing_operations =
                                            buffer.serialize_ops(Some(remote_version), cx);
                                        let incoming_operations =
                                            mem::take(&mut remote_buffer.operations)
                                                .into_iter()
                                                .map(language::proto::deserialize_operation)
                                                .collect::<Result<Vec<_>>>()?;
                                        buffer.apply_ops(incoming_operations, cx)?;
                                        anyhow::Ok(outgoing_operations)
                                    })
                                    .log_err();

                                if let Some(operations) = operations {
                                    let client = this.client.clone();
                                    cx.background_executor()
                                        .spawn(async move {
                                            let operations = operations.await;
                                            for chunk in
                                                language::proto::split_operations(operations)
                                            {
                                                client
                                                    .send(proto::UpdateChannelBuffer {
                                                        channel_id: channel_id.0,
                                                        operations: chunk,
                                                    })
                                                    .ok();
                                            }
                                        })
                                        .detach();
                                    return true;
                                }
                            }

                            channel_buffer.disconnect(cx);
                            false
                        })
                    }
                    OpenedModelHandle::Loading(_) => true,
                });
            })
            .ok();
            anyhow::Ok(())
        })
    }

    fn handle_disconnect(&mut self, wait_for_reconnect: bool, cx: &mut ModelContext<Self>) {
        cx.notify();

        self.disconnect_channel_buffers_task.get_or_insert_with(|| {
            cx.spawn(move |this, mut cx| async move {
                if wait_for_reconnect {
                    cx.background_executor().timer(RECONNECT_TIMEOUT).await;
                }

                if let Some(this) = this.upgrade() {
                    this.update(&mut cx, |this, cx| {
                        for (_, buffer) in this.opened_buffers.drain() {
                            if let OpenedModelHandle::Open(buffer) = buffer {
                                if let Some(buffer) = buffer.upgrade() {
                                    buffer.update(cx, |buffer, cx| buffer.disconnect(cx));
                                }
                            }
                        }
                    })
                    .ok();
                }
            })
        });
    }

    pub(crate) fn update_channels(
        &mut self,
        payload: proto::UpdateChannels,
        cx: &mut ModelContext<ChannelStore>,
    ) -> Option<Task<Result<()>>> {
        if !payload.remove_channel_invitations.is_empty() {
            self.channel_invitations
                .retain(|channel| !payload.remove_channel_invitations.contains(&channel.id.0));
        }
        for channel in payload.channel_invitations {
            match self
                .channel_invitations
                .binary_search_by_key(&channel.id, |c| c.id.0)
            {
                Ok(ix) => {
                    Arc::make_mut(&mut self.channel_invitations[ix]).name = channel.name.into()
                }
                Err(ix) => self.channel_invitations.insert(
                    ix,
                    Arc::new(Channel {
                        id: ChannelId(channel.id),
                        visibility: channel.visibility(),
                        name: channel.name.into(),
                        parent_path: channel
                            .parent_path
                            .into_iter()
                            .map(|cid| ChannelId(cid))
                            .collect(),
                    }),
                ),
            }
        }

        let channels_changed = !payload.channels.is_empty()
            || !payload.delete_channels.is_empty()
            || !payload.latest_channel_message_ids.is_empty()
            || !payload.latest_channel_buffer_versions.is_empty()
            || !payload.hosted_projects.is_empty()
            || !payload.deleted_hosted_projects.is_empty()
            || !payload.dev_servers.is_empty()
            || !payload.deleted_dev_servers.is_empty()
            || !payload.remote_projects.is_empty()
            || !payload.deleted_remote_projects.is_empty();

        if channels_changed {
            if !payload.delete_channels.is_empty() {
                let delete_channels: Vec<ChannelId> = payload
                    .delete_channels
                    .into_iter()
                    .map(|cid| ChannelId(cid))
                    .collect();
                self.channel_index.delete_channels(&delete_channels);
                self.channel_participants
                    .retain(|channel_id, _| !delete_channels.contains(&channel_id));

                for channel_id in &delete_channels {
                    let channel_id = *channel_id;
                    if payload
                        .channels
                        .iter()
                        .any(|channel| channel.id == channel_id.0)
                    {
                        continue;
                    }
                    if let Some(OpenedModelHandle::Open(buffer)) =
                        self.opened_buffers.remove(&channel_id)
                    {
                        if let Some(buffer) = buffer.upgrade() {
                            buffer.update(cx, ChannelBuffer::disconnect);
                        }
                    }
                }
            }

            let mut index = self.channel_index.bulk_insert();
            for channel in payload.channels {
                let id = ChannelId(channel.id);
                let channel_changed = index.insert(channel);

                if channel_changed {
                    if let Some(OpenedModelHandle::Open(buffer)) = self.opened_buffers.get(&id) {
                        if let Some(buffer) = buffer.upgrade() {
                            buffer.update(cx, ChannelBuffer::channel_changed);
                        }
                    }
                }
            }

            for latest_buffer_version in payload.latest_channel_buffer_versions {
                let version = language::proto::deserialize_version(&latest_buffer_version.version);
                self.channel_states
                    .entry(ChannelId(latest_buffer_version.channel_id))
                    .or_default()
                    .update_latest_notes_version(latest_buffer_version.epoch, &version)
            }

            for latest_channel_message in payload.latest_channel_message_ids {
                self.channel_states
                    .entry(ChannelId(latest_channel_message.channel_id))
                    .or_default()
                    .update_latest_message_id(latest_channel_message.message_id);
            }

            for hosted_project in payload.hosted_projects {
                let hosted_project: HostedProject = hosted_project.into();
                if let Some(old_project) = self
                    .hosted_projects
                    .insert(hosted_project.project_id, hosted_project.clone())
                {
                    self.channel_states
                        .entry(old_project.channel_id)
                        .or_default()
                        .remove_hosted_project(old_project.project_id);
                }
                self.channel_states
                    .entry(hosted_project.channel_id)
                    .or_default()
                    .add_hosted_project(hosted_project.project_id);
            }

            for hosted_project_id in payload.deleted_hosted_projects {
                let hosted_project_id = ProjectId(hosted_project_id);

                if let Some(old_project) = self.hosted_projects.remove(&hosted_project_id) {
                    self.channel_states
                        .entry(old_project.channel_id)
                        .or_default()
                        .remove_hosted_project(old_project.project_id);
                }
            }

            for remote_project in payload.remote_projects {
                let remote_project: RemoteProject = remote_project.into();
                if let Some(old_remote_project) = self
                    .remote_projects
                    .insert(remote_project.id, remote_project.clone())
                {
                    self.channel_states
                        .entry(old_remote_project.channel_id)
                        .or_default()
                        .remove_remote_project(old_remote_project.id);
                }
                self.channel_states
                    .entry(remote_project.channel_id)
                    .or_default()
                    .add_remote_project(remote_project.id);
            }

            for remote_project_id in payload.deleted_remote_projects {
                let remote_project_id = RemoteProjectId(remote_project_id);

                if let Some(old_project) = self.remote_projects.remove(&remote_project_id) {
                    self.channel_states
                        .entry(old_project.channel_id)
                        .or_default()
                        .remove_remote_project(old_project.id);
                }
            }

            for dev_server in payload.dev_servers {
                let dev_server: DevServer = dev_server.into();
                if let Some(old_server) = self.dev_servers.insert(dev_server.id, dev_server.clone())
                {
                    self.channel_states
                        .entry(old_server.channel_id)
                        .or_default()
                        .remove_dev_server(old_server.id);
                }
                self.channel_states
                    .entry(dev_server.channel_id)
                    .or_default()
                    .add_dev_server(dev_server.id);
            }

            for dev_server_id in payload.deleted_dev_servers {
                let dev_server_id = DevServerId(dev_server_id);

                if let Some(old_server) = self.dev_servers.remove(&dev_server_id) {
                    self.channel_states
                        .entry(old_server.channel_id)
                        .or_default()
                        .remove_dev_server(old_server.id);
                }
            }
        }

        cx.notify();
        if payload.channel_participants.is_empty() {
            return None;
        }

        let mut all_user_ids = Vec::new();
        let channel_participants = payload.channel_participants;
        for entry in &channel_participants {
            for user_id in entry.participant_user_ids.iter() {
                if let Err(ix) = all_user_ids.binary_search(user_id) {
                    all_user_ids.insert(ix, *user_id);
                }
            }
        }

        let users = self
            .user_store
            .update(cx, |user_store, cx| user_store.get_users(all_user_ids, cx));
        Some(cx.spawn(|this, mut cx| async move {
            let users = users.await?;

            this.update(&mut cx, |this, cx| {
                for entry in &channel_participants {
                    let mut participants: Vec<_> = entry
                        .participant_user_ids
                        .iter()
                        .filter_map(|user_id| {
                            users
                                .binary_search_by_key(&user_id, |user| &user.id)
                                .ok()
                                .map(|ix| users[ix].clone())
                        })
                        .collect();

                    participants.sort_by_key(|u| u.id);

                    this.channel_participants
                        .insert(ChannelId(entry.channel_id), participants);
                }

                cx.notify();
            })
        }))
    }
}

impl ChannelState {
    fn set_role(&mut self, role: ChannelRole) {
        self.role = Some(role);
    }

    fn has_channel_buffer_changed(&self) -> bool {
        self.latest_notes_version.epoch > self.observed_notes_version.epoch
            || (self.latest_notes_version.epoch == self.observed_notes_version.epoch
                && self
                    .latest_notes_version
                    .version
                    .changed_since(&self.observed_notes_version.version))
    }

    fn has_new_messages(&self) -> bool {
        let latest_message_id = self.latest_chat_message;
        let observed_message_id = self.observed_chat_message;

        latest_message_id.is_some_and(|latest_message_id| {
            latest_message_id > observed_message_id.unwrap_or_default()
        })
    }

    fn last_acknowledged_message_id(&self) -> Option<u64> {
        self.observed_chat_message
    }

    fn acknowledge_message_id(&mut self, message_id: u64) {
        let observed = self.observed_chat_message.get_or_insert(message_id);
        *observed = (*observed).max(message_id);
    }

    fn update_latest_message_id(&mut self, message_id: u64) {
        self.latest_chat_message =
            Some(message_id.max(self.latest_chat_message.unwrap_or_default()));
    }

    fn acknowledge_notes_version(&mut self, epoch: u64, version: &clock::Global) {
        if self.observed_notes_version.epoch == epoch {
            self.observed_notes_version.version.join(version);
        } else {
            self.observed_notes_version = NotesVersion {
                epoch,
                version: version.clone(),
            };
        }
    }

    fn update_latest_notes_version(&mut self, epoch: u64, version: &clock::Global) {
        if self.latest_notes_version.epoch == epoch {
            self.latest_notes_version.version.join(version);
        } else {
            self.latest_notes_version = NotesVersion {
                epoch,
                version: version.clone(),
            };
        }
    }

    fn add_hosted_project(&mut self, project_id: ProjectId) {
        self.projects.insert(project_id);
    }

    fn remove_hosted_project(&mut self, project_id: ProjectId) {
        self.projects.remove(&project_id);
    }

    fn add_remote_project(&mut self, remote_project_id: RemoteProjectId) {
        self.remote_projects.insert(remote_project_id);
    }

    fn remove_remote_project(&mut self, remote_project_id: RemoteProjectId) {
        self.remote_projects.remove(&remote_project_id);
    }

    fn add_dev_server(&mut self, dev_server_id: DevServerId) {
        self.dev_servers.insert(dev_server_id);
    }

    fn remove_dev_server(&mut self, dev_server_id: DevServerId) {
        self.dev_servers.remove(&dev_server_id);
    }
}
