import type { BigDecimal } from './type'

export const createBigDecimal = (
  value: string | number | bigint,
  decimals?: number,
): BigDecimal => {
  if (typeof value === 'string') {
    return createBigDecimalFromString(value, decimals)
  }
  if (typeof value === 'number') {
    return createBigDecimalFromString(value.toString(), decimals)
  }
  return createBigDecimalFromBigint(value, decimals)
}

const createBigDecimalFromString = (
  value: string,
  decimals?: number,
): BigDecimal => {
  // e で分割
  const splitedValue = value.replaceAll(' ', '').split(/[eE]/)
  const baseValue = splitedValue[0]
  const exponent: string | undefined = splitedValue[1]
  // マイナスかどうか
  const isNegative = baseValue.startsWith('-')
  const absText = isNegative ? baseValue.slice(1) : baseValue
  // '+' ',' '_' を除外する
  const numberText = absText
    .replaceAll('+', '')
    .replaceAll(',', '')
    .replaceAll('_', '')
  // . で文字列分割
  // それぞれの block が数字のみで構成されていなかったらエラー
  const [intText, decimalText] = numberText.split('.')
  const numberRegExp = /^\d+$/
  if (
    !numberRegExp.test(intText) ||
    (decimalText !== undefined &&
      decimalText.length > 0 &&
      !numberRegExp.test(decimalText))
  ) {
    throw new Error(`Invalid decimal format value: ${baseValue}`)
  }

  // decimal 末尾の 0 は削除 (正規表現で良い感じに)
  const formattedDecimalText =
    decimalText !== undefined && decimalText.length > 0
      ? decimalText.replace(/0+$/, '')
      : ''
  const formattedDecimals = formattedDecimalText.length

  // decimals の末尾を調整
  const adjustedDecimalText = adjustDecimalText(
    decimals,
    formattedDecimalText,
    formattedDecimals,
  )

  const valueBn = BigInt(
    `${isNegative ? '-' : ''}${intText}${adjustedDecimalText}`,
  )
  const baseDecimals = decimals === undefined ? formattedDecimals : decimals

  // 指数がない場合はそのまま返す
  if (exponent === undefined) {
    return {
      value: valueBn,
      decimals: baseDecimals,
    }
  }

  // 指数がある場合は指数を考慮して返す
  const exponentNumber = Number(exponent)
  const decimalsWithExponent = baseDecimals - exponentNumber

  return decimalsWithExponent < 0
    ? {
        value: valueBn * 10n ** BigInt(-decimalsWithExponent),
        decimals: 0,
      }
    : {
        value: valueBn,
        decimals: decimalsWithExponent,
      }
}

const adjustDecimalText = (
  decimals: number | undefined,
  formattedDecimalText: string,
  formattedDecimals: number,
) => {
  if (decimals === undefined) {
    return formattedDecimalText
  }
  if (decimals > formattedDecimals) {
    return formattedDecimalText.padEnd(decimals, '0')
  }
  if (decimals < formattedDecimals) {
    return formattedDecimalText.slice(0, decimals)
  }
  return formattedDecimalText
}

const createBigDecimalFromBigint = (
  value: bigint,
  decimals?: number,
): BigDecimal => ({
  value,
  decimals: decimals ?? 0,
})
