import {SQL} from 'bun'
import {sql, type Column, type SQL as SqlFragment} from 'drizzle-orm'
import {HTTPException} from 'hono/http-exception'

// escape postgres LIKE/ILIKE metacharacters so user input matches literally
export const escapeLike = (s: string) => s.replace(/[\\%_]/g, '\\$&')

// count(*)::int (or count(expr)::int) so drizzle yields a number, not a string
export const countInt = (
  expr?: SqlFragment | Column,
): SqlFragment<number> =>
  expr ? sql<number>`count(${expr})::int` : sql<number>`count(*)::int`

const EARTH_RADIUS_KM = 6371

// great-circle distance in km between a lat/lng column pair and a fixed point
export const haversineKmSql = (
  latExpr: SqlFragment,
  lngExpr: SqlFragment,
  center: {latitude: number; longitude: number},
): SqlFragment => sql`
  2 * ${EARTH_RADIUS_KM} * asin(sqrt(
    power(sin(radians(${latExpr} - ${center.latitude}) / 2), 2)
    + cos(radians(${center.latitude})) * cos(radians(${latExpr}))
      * power(sin(radians(${lngExpr} - ${center.longitude}) / 2), 2)
  ))`

const UNIQUE_VIOLATION = '23505'

// bun carries the sqlstate on `errno`; some paths surface it on `code`
const isUniqueViolationError = (err: unknown): boolean =>
  err instanceof SQL.PostgresError &&
  (err.errno === UNIQUE_VIOLATION || err.code === UNIQUE_VIOLATION)

// drizzle wraps driver errors, leaving the postgres error on `.cause`
export const isUniqueViolation = (err: unknown): boolean =>
  isUniqueViolationError(err) ||
  (err instanceof Error && isUniqueViolationError(err.cause))

// map a unique violation to a coded 409; anything else rethrows untouched
export const rethrowUniqueAs =
  (code: string) =>
  (err: unknown): never => {
    if (isUniqueViolation(err))
      throw new HTTPException(409, {message: code})
    throw err
  }
