import {removeById} from '@crumbs/core/collections'
import {
  PublicProfileSchema,
  type FriendRequest,
  type PublicProfile,
  type ReportInput,
} from '@crumbs/core/schemas'

import {createSlotStore, slotHooks} from '@/lib/async-slot'
import {unwrap} from '@/lib/errors'
import {
  createPagedStore,
  pagedHooks,
  pageParams,
} from '@/lib/paged-resource'
import {rpc} from '@/lib/rpc'
import {fromSchema} from '@/lib/snapshot-cache'
import {pagedSnapshot} from '@/lib/snapshots'
import {closeConversationWithPeer} from '@/stores/dms'

export type {FriendRequest, PublicProfile}

// the friends list: cursor-paged over the friendship edge
export const useFriendsListStore = createPagedStore<PublicProfile>(
  async (cursor) => {
    const {friends, nextCursor} = await unwrap(
      rpc.friends.$get({
        query: pageParams(cursor),
      }),
    )
    return {items: friends, nextCursor}
  },
  pagedSnapshot('friends-list', 1, fromSchema(PublicProfileSchema)),
)

// incoming friend requests are a bounded inbox
const useRequestsStore = createSlotStore<FriendRequest[]>(
  [],
  async () => {
    const {requests} = await unwrap(rpc.friends.requests.$get())
    return requests
  },
)

const removeRequest = (id: string) =>
  useRequestsStore.setState((s) => ({data: removeById(s.data, id)}))

const sendRequest = async (addresseeId: string) => {
  await unwrap(
    rpc.friends.requests.$post({
      json: {addresseeId},
    }),
  )
}

const accept = async (id: string) => {
  await unwrap(
    rpc.friends.requests[':id'].accept.$post({
      param: {id},
    }),
  )
  removeRequest(id)
  // the new friend now belongs at the top of the list
  await useFriendsListStore.getState().load()
}

const decline = async (id: string) => {
  await unwrap(
    rpc.friends.requests[':id'].decline.$post({
      param: {id},
    }),
  )
  removeRequest(id)
}

const unfriend = async (userId: string) => {
  await unwrap(
    rpc.friends[':userId'].$delete({
      param: {userId},
    }),
  )
  useFriendsListStore.setState((s) => ({
    items: removeById(s.items, userId),
  }))
  // unfriending closes the dm channel; reflect it without an inbox refresh
  closeConversationWithPeer(userId)
}

// blocking severs everything: drop them from the friends list and close the
// dm channel locally, mirroring the server-side teardown
const block = async (userId: string) => {
  await unwrap(rpc.blocks.$post({json: {userId}}))
  useFriendsListStore.setState((s) => ({
    items: removeById(s.items, userId),
  }))
  closeConversationWithPeer(userId)
}

const unblock = async (userId: string) => {
  await unwrap(rpc.blocks[':userId'].$delete({param: {userId}}))
}

const report = async (input: ReportInput) => {
  await unwrap(rpc.reports.$post({json: input}))
}

export const friendsList = pagedHooks(useFriendsListStore)
export const {
  useItems: useFriends,
  useLoading: useFriendsLoading,
  useLoadingMore: useFriendsLoadingMore,
  useReachedEnd: useFriendsReachedEnd,
  useError: useFriendsError,
  useLoad: useLoadFriends,
  useLoadMore: useLoadMoreFriends,
} = friendsList

export const {
  useData: useFriendRequests,
  useRefresh: useRefreshRequests,
} = slotHooks(useRequestsStore)
export const usePendingRequestCount = () =>
  useRequestsStore((s) => s.data.length)
export const useSendFriendRequest = () => sendRequest
export const useAcceptRequest = () => accept
export const useDeclineRequest = () => decline
export const useUnfriend = () => unfriend
export const useBlockUser = () => block
export const useUnblockUser = () => unblock
export const useReportUser = () => report
