import type { JsonObject } from '../types/json.js'
import { createHttpError } from './http.js'

const LARGE_INTEGER_PATTERN = /(:\s*)(-?\d{16,})(\s*[,}\]])/g

type ParseJsonObjectOptions = {
  preserveLargeIntegers?: boolean
  throwOnError?: boolean
}

export function parseJsonObject(
  rawText: unknown,
  { preserveLargeIntegers = false, throwOnError = false }: ParseJsonObjectOptions = {},
): JsonObject {
  const normalized = String(rawText || '').trim()

  if (!normalized) {
    return {}
  }

  try {
    const prepared = preserveLargeIntegers ? wrapLargeIntegerLiterals(normalized) : normalized
    const parsed = JSON.parse(prepared)
    return isPlainObject(parsed) ? parsed : {}
  } catch (error) {
    if (!throwOnError) {
      return {}
    }

    throw createHttpError('json 参数不是合法 JSON', {
      statusCode: 400,
      errorCode: 'invalid_json_payload',
      cause: error,
    })
  }
}

export function wrapLargeIntegerLiterals(rawText: unknown): string {
  return String(rawText || '').replace(LARGE_INTEGER_PATTERN, (_match, prefix, digits, suffix) => {
    if (!shouldPreserveIntegerToken(digits)) {
      return `${prefix}${digits}${suffix}`
    }

    return `${prefix}"${digits}"${suffix}`
  })
}

function shouldPreserveIntegerToken(rawDigits: unknown): boolean {
  const digits = String(rawDigits || '').trim()
  const unsigned = digits.startsWith('-') ? digits.slice(1) : digits

  if (!/^\d+$/.test(unsigned)) {
    return false
  }

  if (unsigned.length > 16) {
    return true
  }

  if (unsigned.length < 16) {
    return false
  }

  return unsigned > '9007199254740991'
}

function isPlainObject(value: unknown): value is JsonObject {
  return Object.prototype.toString.call(value) === '[object Object]'
}
