import type {AppType} from '@crumbs/api/app'
import {hc} from 'hono/client'

import {IS_E2E} from '@/env'
import {toApiError} from '@/lib/errors'
import {getSessionToken} from '@/lib/session-token'

const baseUrl = process.env.EXPO_PUBLIC_API_URL

if (!baseUrl) {
  throw new Error('EXPO_PUBLIC_API_URL is missing — see .env.example')
}

const withAuth = async (init?: RequestInit): Promise<Headers> => {
  const token = await getSessionToken()
  const headers = new Headers(init?.headers)
  if (token) headers.set('Authorization', `Bearer ${token}`)
  return headers
}

// registered by auth so a 401 on any authed request ends the session in one
// place, without rpc importing the auth store (circular)
let onUnauthorized: (() => void) | undefined
export const setUnauthorizedHandler = (handler: () => void) => {
  onUnauthorized = handler
}

// e2e's /dev/network kill-switch answers a coded 503 — but real offline is a
// fetch TypeError. translate the simulated verdict into that same shape so
// the outbox exercises its true offline path (no verdict, wait for
// reconnect), never the server-error path (capped retries → dead-letter)
const isSimulatedOffline = async (
  res: Response,
): Promise<boolean> => {
  if (!IS_E2E || res.status !== 503) return false
  const text = await res
    .clone()
    .text()
    .catch(() => '')
  return text.includes('network_down')
}

const authedFetch = async (
  input: RequestInfo | URL,
  init?: RequestInit,
): Promise<Response> => {
  const headers = await withAuth(init)
  const res = await fetch(input, {...init, headers})
  if (await isSimulatedOffline(res))
    throw new TypeError('Network request failed')
  if (res.status === 401 && headers.has('Authorization'))
    onUnauthorized?.()
  return res
}

export const rpc = hc<AppType>(baseUrl, {fetch: authedFetch})

// the escape hatch for endpoints hono's client can't express (binary bodies);
// same base url + auth header as rpc, response validated by the given schema
export const authedJson = async <T>(
  path: string,
  init: RequestInit,
  schema: {parse: (value: unknown) => T},
): Promise<T> => {
  const res = await authedFetch(`${baseUrl}${path}`, init)
  if (!res.ok) throw await toApiError(res)
  return schema.parse(await res.json())
}
