import { useQueryClient } from '@tanstack/react-query'
import { followAccountById, unfollowAccountById } from 'src/lib/api'
import type {
  Account,
  Relationship,
  RelationshipFromFollowAPIResponse,
} from 'src/lib/api-types'
import { haptic } from 'src/lib/haptics'
import { queryKeys } from 'src/lib/queries/keys'
import { emit } from 'src/lib/tapestry/emit'
import { useActiveProfile } from 'src/state/AuthProvider'
import { accountScopeKeys, useCacheMutation } from './cacheMutation'

type FollowVariables = {
  targetId: string
  targetUsername?: string
  action: 'follow' | 'unfollow'
  /** Locked accounts route to the follow-request queue on the server — the
   * optimistic relationship flips `requested` instead of `following`, and
   * `followers_count` does not change until the request is accepted. */
  isLocked?: boolean
}

// Shared follow/unfollow hook. Replaces both `profile/[id].tsx`'s hand-rolled
// mutations and FollowProfile.tsx's duplicate — with a real optimistic update
// on relationship + both profile count caches so the UI settles instantly.
export function useFollowMutation({
  onSuccess,
}: {
  onSuccess?: (data: RelationshipFromFollowAPIResponse, vars: FollowVariables) => void
} = {}) {
  const { profile: selfUser, summary } = useActiveProfile()
  const selfId = summary?.userId ?? selfUser?.id
  const queryClient = useQueryClient()

  const { mutate, isPending } = useCacheMutation<
    RelationshipFromFollowAPIResponse,
    FollowVariables
  >({
    label: ({ action }) => (action === 'follow' ? 'Following…' : 'Unfollowing…'),
    mutationFn: ({ targetId, action }) =>
      action === 'follow' ? followAccountById(targetId) : unfollowAccountById(targetId),

    snapshotKeys: ({ targetId, targetUsername }) =>
      accountScopeKeys(targetId, {
        username: targetUsername,
        selfId,
      }),

    applyOptimistic: (qc, vars) => {
      const isFollow = vars.action === 'follow'
      const locked = !!vars.isLocked

      qc.setQueryData<Relationship>(
        queryKeys.accounts.relationship(vars.targetId),
        (prev) =>
          prev
            ? {
                ...prev,
                following: isFollow && !locked,
                requested: isFollow && locked,
              }
            : prev
      )

      // Target's follower count only moves once the request is accepted, so a
      // locked-account follow deliberately leaves it untouched.
      if (!locked) {
        const bump = (a: Account | undefined) =>
          a
            ? {
                ...a,
                followers_count: isFollow
                  ? (a.followers_count ?? 0) + 1
                  : Math.max(0, (a.followers_count ?? 0) - 1),
              }
            : a
        qc.setQueryData<Account>(queryKeys.accounts.byId(vars.targetId), bump)
        if (vars.targetUsername) {
          qc.setQueryData<Account>(
            queryKeys.accounts.byUsername(vars.targetUsername),
            bump
          )
        }
      }

      // Viewer's following_count updates immediately for both locked and
      // unlocked targets — a follow request still counts as outbound.
      if (selfId) {
        const bumpSelf = (a: Account | undefined) =>
          a
            ? {
                ...a,
                following_count: isFollow
                  ? (a.following_count ?? 0) + 1
                  : Math.max(0, (a.following_count ?? 0) - 1),
              }
            : a
        qc.setQueryData<Account>(queryKeys.accounts.byId(selfId), bumpSelf)
      }
    },

    reconcile: (qc, data, vars) => {
      // Fold authoritative relationship into the cache. Counts stay from the
      // optimistic apply; the next natural refetch (staleTime=5min) reconciles.
      qc.setQueryData<Relationship>(
        queryKeys.accounts.relationship(vars.targetId),
        (prev) => ({ ...(prev ?? {}), ...data }) as Relationship
      )
    },

    onSuccess: (data, vars) => {
      haptic.success()
      emit(vars.action === 'follow' ? 'F' : 'u')
      onSuccess?.(data, vars)
      // Defensive refetch of the target account after the server has time to
      // commit the count change. Preserves the pre-factory behaviour and
      // catches any drift from the optimistic bump.
      setTimeout(() => {
        queryClient.invalidateQueries({
          queryKey: queryKeys.accounts.byId(vars.targetId),
        })
        if (vars.targetUsername) {
          queryClient.invalidateQueries({
            queryKey: queryKeys.accounts.byUsername(vars.targetUsername),
          })
        }
      }, 1000)
    },
  })

  return { follow: mutate, isFollowPending: isPending }
}
