// Mastodon Pass §3.1: Pixelfed-only API surface.
//
// Endpoints in this module are not part of the canonical Mastodon API. They
// either live under Pixelfed-specific paths (`api/v1.1/*`, `api/v1.2/*`,
// `api/pixelfed/v1/*`) or carry Pixelfed-specific shapes the Mastodon-side
// callers can never satisfy (compose-settings with Pixelfed defaults, RSVP
// against Pixelfed-extended event statuses, the Pixelfed-shaped admin
// surface, the Pixelfed-extended `notifications` `types[]` filter, etc.).
//
// Every helper here goes through `pixelfedActiveContext()` so the runtime
// gate fires loudly when one of these endpoints is reached against a
// Mastodon active account. Phase 4 hides the UI surfaces that lead here on
// Mastodon contexts; the assertion is the seatbelt for any path that slips
// through during development.

import { objectToForm } from 'src/requests'
import { AccountStore } from 'src/state/accountStore'
import { ApiError, pixelfedActiveContext } from '../api-context'
import type {
  Account,
  ComposeSettings,
  InstanceV1,
  NewsroomItem,
  OpenServersResponse,
  Status,
} from '../api-types'
import { fetchCursorPagination, fetchData, fetchPaginatedData } from './shared'

// --- Reports (Pixelfed-shaped; Mastodon uses `/api/v1/reports`) ---

export type NewReport = {
  object_id: string
  report_type: string
  object_type: 'user' | 'post' | 'story'
}

export async function report(report: NewReport) {
  const api = pixelfedActiveContext()
  return await api.post('api/v1.1/report', report)
}

// --- Pixelfed account lookup helpers ---

export async function getAccountByUsername(username: string): Promise<Account> {
  const api = pixelfedActiveContext()
  const account = await api.get(`api/v1.1/accounts/username/${username}`)
  if (Array.isArray(account) && account.length === 0) {
    throw new Error(`Account "${username}" not found`)
  }
  return account
}

export async function getMutualFollowing(userId: string) {
  const api = pixelfedActiveContext()
  return await api.get(`api/v1.1/accounts/mutuals/${userId}`)
}

export async function deleteAvatar() {
  const api = pixelfedActiveContext()
  return await api.delete('api/v1.1/accounts/avatar')
}

// --- Direct (Pixelfed-shaped DMs; Mastodon uses `/api/v1/conversations`) ---

export async function getMutualFollowers() {
  const api = pixelfedActiveContext()
  return await api.get('api/v1.1/direct/compose/mutuals')
}

export async function getConversations({
  pageParam = false,
}: {
  pageParam?: string | false
}) {
  if (pageParam) {
    return await fetchPaginatedData(pageParam)
  }
  const instance = AccountStore.activeAccountOrThrow().instance
  const url = `https://${instance}/api/v1/conversations`
  return await fetchPaginatedData(url)
}

export async function fetchChatThread(id: string) {
  const api = pixelfedActiveContext()
  return await api.get('api/v1.1/direct/thread', { pid: id })
}

export async function deleteChatMessage(id: string) {
  const api = pixelfedActiveContext()
  return await api.delete(`api/v1.1/direct/thread/message`, { id })
}

export async function sendChatMessage(id: string, message: string) {
  const api = pixelfedActiveContext()
  return await api.post('api/v1.1/direct/thread/send', {
    to_id: id,
    message,
    type: 'text',
  })
}

// --- Compose settings (Pixelfed-only; Mastodon reads limits from
// `/api/v2/instance.configuration` via the capability cache instead). ---

export async function getComposeSettings(): Promise<ComposeSettings> {
  const api = pixelfedActiveContext()
  return (await api.get('api/v1.1/compose/settings')) as ComposeSettings
}

// --- Server config + instance v1 (the v1 endpoint is shared but `_pe=1`
// adds Pixelfed-specific fields; keep here while no Mastodon UI consumes it). ---

export async function getInstanceV1(): Promise<InstanceV1> {
  const api = pixelfedActiveContext()
  return (await api.get('api/v1/instance')) as InstanceV1
}

export async function getConfig() {
  const api = pixelfedActiveContext()
  return await api.get('api/v2/config')
}

export async function getAppSettings() {
  const api = pixelfedActiveContext()
  return await api.get('api/pixelfed/v1/app/settings')
}

// --- Events / RSVP (Pixelfed-extended on `pf_type === 'event'` statuses) ---

export type RsvpAction = 'going' | 'interested' | 'declined' | 'withdraw'

export async function rsvpEvent(statusId: string, status: RsvpAction) {
  const api = pixelfedActiveContext()
  return (await api.post(`api/v1/statuses/${statusId}/rsvp`, { status })) as Status
}

// --- Open servers (no auth needed) ---
//
// These hit `pixelfed.org` (the Pixelfed project's marketing site), not the
// active account's instance — they only run during the pre-auth signup
// flow, where there is no active account. Keep in `pixelfed.ts` rather than
// `shared.ts` because the surface they describe is Pixelfed-server discovery.

export async function getOpenServers() {
  const response = await fetch(
    'https://pixelfed.org/api/v1/mobile-app/servers/open.json',
    {
      method: 'get',
      headers: new Headers({
        Accept: 'application/json',
        'X-Pixelfed-App': '1',
        'Content-Type': 'application/json',
      }),
    }
  )
  return (await response.json()) as OpenServersResponse
}

export async function getRegisterServers() {
  const response = await fetch(
    'https://pixelfed.org/api/v1/mobile-app/servers/register.json',
    {
      method: 'get',
      headers: new Headers({
        Accept: 'application/json',
        'X-Pixelfed-App': '1',
        'Content-Type': 'application/json',
      }),
    }
  )
  return (await response.json()) as OpenServersResponse
}

// --- Discover (Pixelfed-shaped; Mastodon's trends/suggestions are
// explicitly out of scope for v1 per plan §1.5) ---

export async function getTrendingHashtags() {
  const api = pixelfedActiveContext()
  return await api.get('api/v1.1/discover/posts/hashtags')
}

// --- Collections ---

export async function getSelfCollections({ pageParam = 1 }) {
  // Asserts the active account is Pixelfed before constructing the URL.
  pixelfedActiveContext()
  const instance = AccountStore.activeAccountOrThrow().instance
  const url = `https://${instance}/api/v1.1/collections/self?page=${pageParam}`
  return await fetchData(url)
}

// --- Stories ---

export async function getStoryCarousel() {
  const api = pixelfedActiveContext()
  return await api.get('api/v1.2/stories/carousel')
}

export async function getStoryMentionAutocomplete(query: string) {
  const api = pixelfedActiveContext()
  return await api.get('api/v1.2/stories/mention-autocomplete', { q: query })
}

export async function postStoryViewed(params: Record<string, any>) {
  const api = pixelfedActiveContext()
  return await api.post('api/v1.1/stories/seen', params)
}

export async function getStoryViewers(storyId: string, cursor: string | null) {
  if (cursor) {
    return await fetchCursorPagination(cursor)
  }
  pixelfedActiveContext()
  const instance = AccountStore.activeAccountOrThrow().instance
  const url = `https://${instance}/api/v1.2/stories/viewers?sid=${storyId}`
  return await fetchCursorPagination(url)
}

export async function postStorySelfExpire(id: string) {
  const api = pixelfedActiveContext()
  return await api.post(`api/v1.1/stories/self-expire/${id}`)
}

export async function uploadNewStory(params: Record<string, any>) {
  const api = pixelfedActiveContext()
  return await api.postFormIdempotent(
    'api/v1.2/stories/publish',
    objectToForm(params),
    true
  )
}

export async function addStoryMedia(params: {
  file: { uri: string; type: string; name: string }
  duration?: number
}) {
  const api = pixelfedActiveContext()
  return await api.postFormIdempotent('api/v1.1/stories/add', objectToForm(params), true)
}

export async function publishStory(params: {
  media_id: string
  duration: number
  can_reply: boolean
  can_react: boolean
}) {
  const api = pixelfedActiveContext()
  return await api.post('api/v1.1/stories/publish', params)
}

// --- Notifications (Pixelfed-shaped: `_pe=1` extended grouping, `types[]`
// filter, Pixelfed-only types `event:*` / `story:*`). Mastodon notifications
// will live in `mastodon.ts` once Phase 4 wires them up. ---

export async function fetchNotifications({
  tab,
  pageParam = false,
}: {
  tab: string
  pageParam?: string | false
}) {
  if (pageParam) {
    return await fetchPaginatedData(pageParam)
  }
  pixelfedActiveContext()
  const instance = AccountStore.activeAccountOrThrow().instance
  const filterMap: Record<string, string> = {
    likes: 'favourite',
    follows: 'follow',
    mentions: 'mention',
    reblogs: 'reblog',
  }
  const url =
    tab !== 'all'
      ? `https://${instance}/api/v1/notifications?types[]=${filterMap[tab]}`
      : `https://${instance}/api/v1/notifications`
  return await fetchPaginatedData(url)
}

// Single-call capability probe (Pixelfed-extended grouped v2 — the response
// has `notification_groups` only when Pixelfed's grouped extension is
// enabled). Mastodon's grouped v2 has the same field but Phase 4 calls a
// different helper for it.
export async function probeNotificationsV2(): Promise<boolean> {
  try {
    const api = pixelfedActiveContext()
    const res = await api.get('api/v2/notifications', { limit: 1 })
    return (
      res && typeof res === 'object' && Array.isArray((res as any).notification_groups)
    )
  } catch {
    return false
  }
}

export async function fetchNotificationsV2({
  tab,
  pageParam = false,
}: {
  tab: string
  pageParam?: string | false
}) {
  if (pageParam) {
    return await fetchPaginatedData(pageParam)
  }
  pixelfedActiveContext()
  const instance = AccountStore.activeAccountOrThrow().instance
  const filterMap: Record<string, string> = {
    likes: 'favourite',
    follows: 'follow',
    mentions: 'mention',
    reblogs: 'reblog',
  }
  // Only group the types the NotificationGroup component renders well.
  const groupedTypes = ['favourite', 'follow', 'reblog']
  const groupedParam = groupedTypes.map((t) => `grouped_types[]=${t}`).join('&')
  const base = `https://${instance}/api/v2/notifications?${groupedParam}`
  const url = tab !== 'all' ? `${base}&types[]=${filterMap[tab]}` : base
  return await fetchPaginatedData(url)
}

export async function getNotificationPolicy() {
  const api = pixelfedActiveContext()
  return await api.get('api/v2/notifications/policy')
}

export async function updateNotificationPolicy(body: Record<string, unknown>) {
  const api = pixelfedActiveContext()
  return await api.patch('api/v2/notifications/policy', body)
}

export async function getFilteredNotificationRequests({
  pageParam = false,
}: {
  pageParam?: string | false
}) {
  if (pageParam) {
    return await fetchPaginatedData(pageParam)
  }
  pixelfedActiveContext()
  const instance = AccountStore.activeAccountOrThrow().instance
  const url = `https://${instance}/api/v1/notifications/requests`
  return await fetchPaginatedData(url)
}

// --- Push (Pixelfed-shaped — `api/v1.1/push/*`, with `X-PIXELFED-APP`
// header gated by ApiContext). Mastodon push is RFC 8030 / `/api/v1/push/
// subscription`, lives in `mastodon.ts` once Phase 4 wires it up. ---

export async function pushNotificationSupported() {
  const api = pixelfedActiveContext()
  return await api.get('api/v1.1/nag/state')
}

export async function pushState() {
  const api = pixelfedActiveContext()
  return await api.get('api/v1.1/push/state')
}

export async function pushStateDisable() {
  const api = pixelfedActiveContext()
  return await api.postWithAppHeader('api/v1.1/push/disable')
}

export async function pushStateCompare(params: Record<string, any>) {
  const api = pixelfedActiveContext()
  return await api.postWithAppHeader('api/v1.1/push/compare', params)
}

export async function pushStateUpdate(params: Record<string, any>) {
  const api = pixelfedActiveContext()
  return await api.postWithAppHeader('api/v1.1/push/update', params)
}

// --- Newsroom (admin-authored posts) ---

export async function getNewsroomTimeline(): Promise<NewsroomItem[]> {
  const api = pixelfedActiveContext()
  try {
    const res = await api.get('api/pixelfed/v1/newsroom/timeline')
    return Array.isArray(res) ? (res as NewsroomItem[]) : []
  } catch (e) {
    // Older Pixelfed servers gate this endpoint behind the session guard, so
    // OAuth callers get 404 even when authenticated. Treat as empty instead of
    // surfacing the scary error state. 401/403 still bubble (handled upstream
    // as logout) so a revoked token isn't silently hidden.
    if (e instanceof ApiError && e.status === 404) return []
    throw e
  }
}

export async function markNewsroomRead(id: number) {
  const api = pixelfedActiveContext()
  return await api.postForm('api/pixelfed/v1/newsroom/markasread', objectToForm({ id }))
}

// --- Admin (Pixelfed-shaped; Mastodon admin surface explicitly out of
// scope per plan §1.5) ---

export async function getAdminStats() {
  const api = pixelfedActiveContext()
  return await api.get('api/admin/stats')
}

export async function adminInstances(
  sort: string,
  sortBy: string,
  queryKey?: { pageParam?: string }
) {
  if (queryKey?.pageParam) {
    return await fetchCursorPagination(queryKey.pageParam)
  }
  pixelfedActiveContext()
  const instance = AccountStore.activeAccountOrThrow().instance
  const path = `https://${instance}/api/admin/instances/list?order_by=sort=${sort}&sort_by=${sortBy}`
  return await fetchCursorPagination(path)
}

export async function adminInstanceGet() {
  const api = pixelfedActiveContext()
  return await api.get('api/admin/instances/get')
}

export async function getAdminConfig() {
  const api = pixelfedActiveContext()
  return await api.get('api/admin/config')
}

export async function updateAdminConfig(params: Record<string, any>) {
  const api = pixelfedActiveContext()
  return await api.post('api/admin/config/update', params)
}

export async function getAdminUsers(cursor: string | null) {
  if (cursor) {
    return await fetchCursorPagination(cursor)
  }
  pixelfedActiveContext()
  const instance = AccountStore.activeAccountOrThrow().instance
  const url = `https://${instance}/api/admin/users/list?sort=desc`
  return await fetchCursorPagination(url)
}

export async function getAdminUser(id: string) {
  const api = pixelfedActiveContext()
  return await api.get('api/admin/users/get', { user_id: id })
}

export async function getModReports() {
  const api = pixelfedActiveContext()
  return await api.get('api/admin/mod-reports/list')
}

export async function getAutospamReports() {
  const api = pixelfedActiveContext()
  return await api.get('api/admin/autospam/list')
}

export async function postUserHandle(params: Record<string, any>) {
  const api = pixelfedActiveContext()
  return await api.post('api/admin/users/action', params)
}

export async function postReportHandle(params: Record<string, any>) {
  const api = pixelfedActiveContext()
  return await api.post('api/admin/mod-reports/handle', params)
}

export async function postAutospamHandle(params: Record<string, any>) {
  const api = pixelfedActiveContext()
  return await api.post('api/admin/autospam/handle', params)
}
