import { InvalidCursorError } from './errors.js'
import type { AuditCursor } from './types.js'

function toBase64Url(value: string): string {
  return btoa(value).replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '')
}

function fromBase64Url(value: string): string {
  let base64 = value.replace(/-/g, '+').replace(/_/g, '/')
  const pad = base64.length % 4
  if (pad) {
    base64 += '='.repeat(4 - pad)
  }
  return atob(base64)
}

const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i

function isAuditCursor(value: unknown): value is AuditCursor {
  return (
    typeof value === 'object' &&
    value !== null &&
    'createdAt' in value &&
    'id' in value &&
    typeof value.createdAt === 'string' &&
    typeof value.id === 'string' &&
    !Number.isNaN(Date.parse(value.createdAt)) &&
    // id is bound against a uuid column — a forged non-uuid would surface a raw
    // driver error instead of the contracted InvalidCursorError (spec amendment C).
    UUID_RE.test(value.id)
  )
}

export function encodeCursor(row: { createdAt: Date; id: string }): string {
  const payload: AuditCursor = {
    createdAt: row.createdAt.toISOString(),
    id: row.id,
  }
  return toBase64Url(JSON.stringify(payload))
}

export function decodeCursor(cursor: string): AuditCursor {
  try {
    const json = fromBase64Url(cursor)
    const parsed: unknown = JSON.parse(json)
    if (!isAuditCursor(parsed)) {
      throw new InvalidCursorError()
    }
    return parsed
  } catch (error) {
    if (error instanceof InvalidCursorError) {
      throw error
    }
    throw new InvalidCursorError()
  }
}
