// Copyright 2014 The PDFium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

// Original code copyright 2014 Foxit Software Inc. http://www.foxitsoftware.com

#include "fxjs/xfa/cfxjse_formcalc_context.h"

#include <math.h>
#include <stdint.h>
#include <stdlib.h>

#include <algorithm>
#include <array>
#include <limits>
#include <memory>
#include <optional>
#include <utility>
#include <vector>

#include "core/fxcrt/cfx_datetime.h"
#include "core/fxcrt/check_op.h"
#include "core/fxcrt/code_point_view.h"
#include "core/fxcrt/compiler_specific.h"
#include "core/fxcrt/containers/contains.h"
#include "core/fxcrt/data_vector.h"
#include "core/fxcrt/fx_extension.h"
#include "core/fxcrt/fx_random.h"
#include "core/fxcrt/fx_safe_types.h"
#include "core/fxcrt/fx_string.h"
#include "core/fxcrt/numerics/safe_conversions.h"
#include "core/fxcrt/span_util.h"
#include "core/fxcrt/widetext_buffer.h"
#include "fxjs/fxv8.h"
#include "fxjs/xfa/cfxjse_class.h"
#include "fxjs/xfa/cfxjse_context.h"
#include "fxjs/xfa/cfxjse_engine.h"
#include "fxjs/xfa/cjx_object.h"
#include "v8/include/v8-container.h"
#include "v8/include/v8-function-callback.h"
#include "v8/include/v8-local-handle.h"
#include "v8/include/v8-object.h"
#include "v8/include/v8-primitive.h"
#include "xfa/fgas/crt/cfgas_decimal.h"
#include "xfa/fxfa/cxfa_ffnotify.h"
#include "xfa/fxfa/formcalc/cxfa_fmparser.h"
#include "xfa/fxfa/formcalc/cxfa_fmtojavascriptdepth.h"
#include "xfa/fxfa/parser/cxfa_document.h"
#include "xfa/fxfa/parser/cxfa_localevalue.h"
#include "xfa/fxfa/parser/cxfa_node.h"
#include "xfa/fxfa/parser/cxfa_thisproxy.h"
#include "xfa/fxfa/parser/cxfa_timezoneprovider.h"
#include "xfa/fxfa/parser/gced_locale_iface.h"
#include "xfa/fxfa/parser/xfa_utils.h"

using pdfium::fxjse::kClassTag;
using pdfium::fxjse::kFuncTag;

namespace {

// Maximum number of characters Acrobat can fit in a text box.
constexpr int kMaxCharCount = 15654908;

const double kFinancialPrecision = 0.00000001;

constexpr std::array<wchar_t, 16> kStrCode = {
    {L'0', L'1', L'2', L'3', L'4', L'5', L'6', L'7', L'8', L'9', L'a', L'b',
     L'c', L'd', L'e', L'f'}};

struct XFA_FMHtmlReserveCode {
  uint16_t code_;
  // Inline string data reduces size for small strings.
  const char html_reserve_[10];
};

// Sorted by |html_reserve_|.
const XFA_FMHtmlReserveCode kReservesForDecode[] = {
    {198, "AElig"},   {193, "Aacute"},   {194, "Acirc"},    {192, "Agrave"},
    {913, "Alpha"},   {197, "Aring"},    {195, "Atilde"},   {196, "Auml"},
    {914, "Beta"},    {199, "Ccedil"},   {935, "Chi"},      {8225, "Dagger"},
    {916, "Delta"},   {208, "ETH"},      {201, "Eacute"},   {202, "Ecirc"},
    {200, "Egrave"},  {917, "Epsilon"},  {919, "Eta"},      {203, "Euml"},
    {915, "Gamma"},   {922, "Kappa"},    {923, "Lambda"},   {924, "Mu"},
    {209, "Ntilde"},  {925, "Nu"},       {338, "OElig"},    {211, "Oacute"},
    {212, "Ocirc"},   {210, "Ograve"},   {937, "Omega"},    {927, "Omicron"},
    {216, "Oslash"},  {213, "Otilde"},   {214, "Ouml"},     {934, "Phi"},
    {928, "Pi"},      {936, "Psi"},      {929, "Rho"},      {352, "Scaron"},
    {931, "Sigma"},   {222, "THORN"},    {932, "Tau"},      {920, "Theta"},
    {218, "Uacute"},  {219, "Ucirc"},    {217, "Ugrave"},   {933, "Upsilon"},
    {220, "Uuml"},    {926, "Xi"},       {221, "Yacute"},   {376, "Yuml"},
    {918, "Zeta"},    {225, "aacute"},   {226, "acirc"},    {180, "acute"},
    {230, "aelig"},   {224, "agrave"},   {8501, "alefsym"}, {945, "alpha"},
    {38, "amp"},      {8743, "and"},     {8736, "ang"},     {39, "apos"},
    {229, "aring"},   {8776, "asymp"},   {227, "atilde"},   {228, "auml"},
    {8222, "bdquo"},  {946, "beta"},     {166, "brvbar"},   {8226, "bull"},
    {8745, "cap"},    {231, "ccedil"},   {184, "cedil"},    {162, "cent"},
    {967, "chi"},     {710, "circ"},     {9827, "clubs"},   {8773, "cong"},
    {169, "copy"},    {8629, "crarr"},   {8746, "cup"},     {164, "current"},
    {8659, "dArr"},   {8224, "dagger"},  {8595, "darr"},    {176, "deg"},
    {948, "delta"},   {9830, "diams"},   {247, "divide"},   {233, "eacute"},
    {234, "ecirc"},   {232, "egrave"},   {8709, "empty"},   {8195, "emsp"},
    {8194, "ensp"},   {949, "epsilon"},  {8801, "equiv"},   {951, "eta"},
    {240, "eth"},     {235, "euml"},     {8364, "euro"},    {8707, "exist"},
    {402, "fnof"},    {8704, "forall"},  {189, "frac12"},   {188, "frac14"},
    {190, "frac34"},  {8260, "frasl"},   {947, "gamma"},    {8805, "ge"},
    {62, "gt"},       {8660, "hArr"},    {8596, "harr"},    {9829, "hearts"},
    {8230, "hellip"}, {237, "iacute"},   {238, "icirc"},    {161, "iexcl"},
    {236, "igrave"},  {8465, "image"},   {8734, "infin"},   {8747, "int"},
    {953, "iota"},    {191, "iquest"},   {8712, "isin"},    {239, "iuml"},
    {954, "kappa"},   {8656, "lArr"},    {205, "lacute"},   {955, "lambda"},
    {9001, "lang"},   {171, "laquo"},    {8592, "larr"},    {8968, "lceil"},
    {206, "lcirc"},   {8220, "ldquo"},   {8804, "le"},      {8970, "lfloor"},
    {204, "lgrave"},  {921, "lota"},     {8727, "lowast"},  {9674, "loz"},
    {8206, "lrm"},    {8249, "lsaquo"},  {8216, "lsquo"},   {60, "lt"},
    {207, "luml"},    {175, "macr"},     {8212, "mdash"},   {181, "micro"},
    {183, "middot"},  {8722, "minus"},   {956, "mu"},       {8711, "nabla"},
    {160, "nbsp"},    {8211, "ndash"},   {8800, "ne"},      {8715, "ni"},
    {172, "not"},     {8713, "notin"},   {8836, "nsub"},    {241, "ntilde"},
    {957, "nu"},      {243, "oacute"},   {244, "ocirc"},    {339, "oelig"},
    {242, "ograve"},  {8254, "oline"},   {969, "omega"},    {959, "omicron"},
    {8853, "oplus"},  {8744, "or"},      {170, "ordf"},     {186, "ordm"},
    {248, "oslash"},  {245, "otilde"},   {8855, "otimes"},  {246, "ouml"},
    {182, "para"},    {8706, "part"},    {8240, "permil"},  {8869, "perp"},
    {966, "phi"},     {960, "pi"},       {982, "piv"},      {177, "plusmn"},
    {8242, "prime"},  {8719, "prod"},    {8733, "prop"},    {968, "psi"},
    {163, "pund"},    {34, "quot"},      {8658, "rArr"},    {8730, "radic"},
    {9002, "rang"},   {187, "raquo"},    {8594, "rarr"},    {8969, "rceil"},
    {8476, "real"},   {174, "reg"},      {8971, "rfloor"},  {961, "rho"},
    {8207, "rlm"},    {8250, "rsaquo"},  {8217, "rsquo"},   {353, "saron"},
    {8218, "sbquo"},  {8901, "sdot"},    {167, "sect"},     {173, "shy"},
    {963, "sigma"},   {962, "sigmaf"},   {8764, "sim"},     {9824, "spades"},
    {8834, "sub"},    {8838, "sube"},    {8721, "sum"},     {8835, "sup"},
    {185, "sup1"},    {178, "sup2"},     {179, "sup3"},     {8839, "supe"},
    {223, "szlig"},   {964, "tau"},      {8221, "tdquo"},   {8756, "there4"},
    {952, "theta"},   {977, "thetasym"}, {8201, "thinsp"},  {254, "thorn"},
    {732, "tilde"},   {215, "times"},    {8482, "trade"},   {8657, "uArr"},
    {250, "uacute"},  {8593, "uarr"},    {251, "ucirc"},    {249, "ugrave"},
    {168, "uml"},     {978, "upsih"},    {965, "upsilon"},  {252, "uuml"},
    {8472, "weierp"}, {958, "xi"},       {253, "yacute"},   {165, "yen"},
    {255, "yuml"},    {950, "zeta"},     {8205, "zwj"},     {8204, "zwnj"},
};

// Sorted by |code_|.
const XFA_FMHtmlReserveCode kReservesForEncode[] = {
    {34, "quot"},     {38, "amp"},      {39, "apos"},      {60, "lt"},
    {62, "gt"},       {160, "nbsp"},    {161, "iexcl"},    {162, "cent"},
    {163, "pund"},    {164, "current"}, {165, "yen"},      {166, "brvbar"},
    {167, "sect"},    {168, "uml"},     {169, "copy"},     {170, "ordf"},
    {171, "laquo"},   {172, "not"},     {173, "shy"},      {174, "reg"},
    {175, "macr"},    {176, "deg"},     {177, "plusmn"},   {178, "sup2"},
    {179, "sup3"},    {180, "acute"},   {181, "micro"},    {182, "para"},
    {183, "middot"},  {184, "cedil"},   {185, "sup1"},     {186, "ordm"},
    {187, "raquo"},   {188, "frac14"},  {189, "frac12"},   {190, "frac34"},
    {191, "iquest"},  {192, "Agrave"},  {193, "Aacute"},   {194, "Acirc"},
    {195, "Atilde"},  {196, "Auml"},    {197, "Aring"},    {198, "AElig"},
    {199, "Ccedil"},  {200, "Egrave"},  {201, "Eacute"},   {202, "Ecirc"},
    {203, "Euml"},    {204, "lgrave"},  {205, "lacute"},   {206, "lcirc"},
    {207, "luml"},    {208, "ETH"},     {209, "Ntilde"},   {210, "Ograve"},
    {211, "Oacute"},  {212, "Ocirc"},   {213, "Otilde"},   {214, "Ouml"},
    {215, "times"},   {216, "Oslash"},  {217, "Ugrave"},   {218, "Uacute"},
    {219, "Ucirc"},   {220, "Uuml"},    {221, "Yacute"},   {222, "THORN"},
    {223, "szlig"},   {224, "agrave"},  {225, "aacute"},   {226, "acirc"},
    {227, "atilde"},  {228, "auml"},    {229, "aring"},    {230, "aelig"},
    {231, "ccedil"},  {232, "egrave"},  {233, "eacute"},   {234, "ecirc"},
    {235, "euml"},    {236, "igrave"},  {237, "iacute"},   {238, "icirc"},
    {239, "iuml"},    {240, "eth"},     {241, "ntilde"},   {242, "ograve"},
    {243, "oacute"},  {244, "ocirc"},   {245, "otilde"},   {246, "ouml"},
    {247, "divide"},  {248, "oslash"},  {249, "ugrave"},   {250, "uacute"},
    {251, "ucirc"},   {252, "uuml"},    {253, "yacute"},   {254, "thorn"},
    {255, "yuml"},    {338, "OElig"},   {339, "oelig"},    {352, "Scaron"},
    {353, "saron"},   {376, "Yuml"},    {402, "fnof"},     {710, "circ"},
    {732, "tilde"},   {913, "Alpha"},   {914, "Beta"},     {915, "Gamma"},
    {916, "Delta"},   {917, "Epsilon"}, {918, "Zeta"},     {919, "Eta"},
    {920, "Theta"},   {921, "lota"},    {922, "Kappa"},    {923, "Lambda"},
    {924, "Mu"},      {925, "Nu"},      {926, "Xi"},       {927, "Omicron"},
    {928, "Pi"},      {929, "Rho"},     {931, "Sigma"},    {932, "Tau"},
    {933, "Upsilon"}, {934, "Phi"},     {935, "Chi"},      {936, "Psi"},
    {937, "Omega"},   {945, "alpha"},   {946, "beta"},     {947, "gamma"},
    {948, "delta"},   {949, "epsilon"}, {950, "zeta"},     {951, "eta"},
    {952, "theta"},   {953, "iota"},    {954, "kappa"},    {955, "lambda"},
    {956, "mu"},      {957, "nu"},      {958, "xi"},       {959, "omicron"},
    {960, "pi"},      {961, "rho"},     {962, "sigmaf"},   {963, "sigma"},
    {964, "tau"},     {965, "upsilon"}, {966, "phi"},      {967, "chi"},
    {968, "psi"},     {969, "omega"},   {977, "thetasym"}, {978, "upsih"},
    {982, "piv"},     {8194, "ensp"},   {8195, "emsp"},    {8201, "thinsp"},
    {8204, "zwnj"},   {8205, "zwj"},    {8206, "lrm"},     {8207, "rlm"},
    {8211, "ndash"},  {8212, "mdash"},  {8216, "lsquo"},   {8217, "rsquo"},
    {8218, "sbquo"},  {8220, "ldquo"},  {8221, "tdquo"},   {8222, "bdquo"},
    {8224, "dagger"}, {8225, "Dagger"}, {8226, "bull"},    {8230, "hellip"},
    {8240, "permil"}, {8242, "prime"},  {8249, "lsaquo"},  {8250, "rsaquo"},
    {8254, "oline"},  {8260, "frasl"},  {8364, "euro"},    {8465, "image"},
    {8472, "weierp"}, {8476, "real"},   {8482, "trade"},   {8501, "alefsym"},
    {8592, "larr"},   {8593, "uarr"},   {8594, "rarr"},    {8595, "darr"},
    {8596, "harr"},   {8629, "crarr"},  {8656, "lArr"},    {8657, "uArr"},
    {8658, "rArr"},   {8659, "dArr"},   {8660, "hArr"},    {8704, "forall"},
    {8706, "part"},   {8707, "exist"},  {8709, "empty"},   {8711, "nabla"},
    {8712, "isin"},   {8713, "notin"},  {8715, "ni"},      {8719, "prod"},
    {8721, "sum"},    {8722, "minus"},  {8727, "lowast"},  {8730, "radic"},
    {8733, "prop"},   {8734, "infin"},  {8736, "ang"},     {8743, "and"},
    {8744, "or"},     {8745, "cap"},    {8746, "cup"},     {8747, "int"},
    {8756, "there4"}, {8764, "sim"},    {8773, "cong"},    {8776, "asymp"},
    {8800, "ne"},     {8801, "equiv"},  {8804, "le"},      {8805, "ge"},
    {8834, "sub"},    {8835, "sup"},    {8836, "nsub"},    {8838, "sube"},
    {8839, "supe"},   {8853, "oplus"},  {8855, "otimes"},  {8869, "perp"},
    {8901, "sdot"},   {8968, "lceil"},  {8969, "rceil"},   {8970, "lfloor"},
    {8971, "rfloor"}, {9001, "lang"},   {9002, "rang"},    {9674, "loz"},
    {9824, "spades"}, {9827, "clubs"},  {9829, "hearts"},  {9830, "diams"},
};

const FXJSE_FUNCTION_DESCRIPTOR kFormCalcFunctions[] = {
    {kFuncTag, "Abs", CFXJSE_FormCalcContext::Abs},
    {kFuncTag, "Avg", CFXJSE_FormCalcContext::Avg},
    {kFuncTag, "Ceil", CFXJSE_FormCalcContext::Ceil},
    {kFuncTag, "Count", CFXJSE_FormCalcContext::Count},
    {kFuncTag, "Floor", CFXJSE_FormCalcContext::Floor},
    {kFuncTag, "Max", CFXJSE_FormCalcContext::Max},
    {kFuncTag, "Min", CFXJSE_FormCalcContext::Min},
    {kFuncTag, "Mod", CFXJSE_FormCalcContext::Mod},
    {kFuncTag, "Round", CFXJSE_FormCalcContext::Round},
    {kFuncTag, "Sum", CFXJSE_FormCalcContext::Sum},
    {kFuncTag, "Date", CFXJSE_FormCalcContext::Date},
    {kFuncTag, "Date2Num", CFXJSE_FormCalcContext::Date2Num},
    {kFuncTag, "DateFmt", CFXJSE_FormCalcContext::DateFmt},
    {kFuncTag, "IsoDate2Num", CFXJSE_FormCalcContext::IsoDate2Num},
    {kFuncTag, "IsoTime2Num", CFXJSE_FormCalcContext::IsoTime2Num},
    {kFuncTag, "LocalDateFmt", CFXJSE_FormCalcContext::LocalDateFmt},
    {kFuncTag, "LocalTimeFmt", CFXJSE_FormCalcContext::LocalTimeFmt},
    {kFuncTag, "Num2Date", CFXJSE_FormCalcContext::Num2Date},
    {kFuncTag, "Num2GMTime", CFXJSE_FormCalcContext::Num2GMTime},
    {kFuncTag, "Num2Time", CFXJSE_FormCalcContext::Num2Time},
    {kFuncTag, "Time", CFXJSE_FormCalcContext::Time},
    {kFuncTag, "Time2Num", CFXJSE_FormCalcContext::Time2Num},
    {kFuncTag, "TimeFmt", CFXJSE_FormCalcContext::TimeFmt},
    {kFuncTag, "Apr", CFXJSE_FormCalcContext::Apr},
    {kFuncTag, "Cterm", CFXJSE_FormCalcContext::CTerm},
    {kFuncTag, "FV", CFXJSE_FormCalcContext::FV},
    {kFuncTag, "Ipmt", CFXJSE_FormCalcContext::IPmt},
    {kFuncTag, "NPV", CFXJSE_FormCalcContext::NPV},
    {kFuncTag, "Pmt", CFXJSE_FormCalcContext::Pmt},
    {kFuncTag, "PPmt", CFXJSE_FormCalcContext::PPmt},
    {kFuncTag, "PV", CFXJSE_FormCalcContext::PV},
    {kFuncTag, "Rate", CFXJSE_FormCalcContext::Rate},
    {kFuncTag, "Term", CFXJSE_FormCalcContext::Term},
    {kFuncTag, "Choose", CFXJSE_FormCalcContext::Choose},
    {kFuncTag, "Exists", CFXJSE_FormCalcContext::Exists},
    {kFuncTag, "HasValue", CFXJSE_FormCalcContext::HasValue},
    {kFuncTag, "Oneof", CFXJSE_FormCalcContext::Oneof},
    {kFuncTag, "Within", CFXJSE_FormCalcContext::Within},
    {kFuncTag, "If", CFXJSE_FormCalcContext::If},
    {kFuncTag, "Eval", CFXJSE_FormCalcContext::Eval},
    {kFuncTag, "Translate", CFXJSE_FormCalcContext::eval_translation},
    {kFuncTag, "Ref", CFXJSE_FormCalcContext::Ref},
    {kFuncTag, "UnitType", CFXJSE_FormCalcContext::UnitType},
    {kFuncTag, "UnitValue", CFXJSE_FormCalcContext::UnitValue},
    {kFuncTag, "At", CFXJSE_FormCalcContext::At},
    {kFuncTag, "Concat", CFXJSE_FormCalcContext::Concat},
    {kFuncTag, "Decode", CFXJSE_FormCalcContext::Decode},
    {kFuncTag, "Encode", CFXJSE_FormCalcContext::Encode},
    {kFuncTag, "Format", CFXJSE_FormCalcContext::Format},
    {kFuncTag, "Left", CFXJSE_FormCalcContext::Left},
    {kFuncTag, "Len", CFXJSE_FormCalcContext::Len},
    {kFuncTag, "Lower", CFXJSE_FormCalcContext::Lower},
    {kFuncTag, "Ltrim", CFXJSE_FormCalcContext::Ltrim},
    {kFuncTag, "Parse", CFXJSE_FormCalcContext::Parse},
    {kFuncTag, "Replace", CFXJSE_FormCalcContext::Replace},
    {kFuncTag, "Right", CFXJSE_FormCalcContext::Right},
    {kFuncTag, "Rtrim", CFXJSE_FormCalcContext::Rtrim},
    {kFuncTag, "Space", CFXJSE_FormCalcContext::Space},
    {kFuncTag, "Str", CFXJSE_FormCalcContext::Str},
    {kFuncTag, "Stuff", CFXJSE_FormCalcContext::Stuff},
    {kFuncTag, "Substr", CFXJSE_FormCalcContext::Substr},
    {kFuncTag, "Uuid", CFXJSE_FormCalcContext::Uuid},
    {kFuncTag, "Upper", CFXJSE_FormCalcContext::Upper},
    {kFuncTag, "WordNum", CFXJSE_FormCalcContext::WordNum},
    {kFuncTag, "Get", CFXJSE_FormCalcContext::Get},
    {kFuncTag, "Post", CFXJSE_FormCalcContext::Post},
    {kFuncTag, "Put", CFXJSE_FormCalcContext::Put},
    {kFuncTag, "pos_op", CFXJSE_FormCalcContext::positive_operator},
    {kFuncTag, "neg_op", CFXJSE_FormCalcContext::negative_operator},
    {kFuncTag, "log_or_op", CFXJSE_FormCalcContext::logical_or_operator},
    {kFuncTag, "log_and_op", CFXJSE_FormCalcContext::logical_and_operator},
    {kFuncTag, "log_not_op", CFXJSE_FormCalcContext::logical_not_operator},
    {kFuncTag, "eq_op", CFXJSE_FormCalcContext::equality_operator},
    {kFuncTag, "neq_op", CFXJSE_FormCalcContext::notequality_operator},
    {kFuncTag, "lt_op", CFXJSE_FormCalcContext::less_operator},
    {kFuncTag, "le_op", CFXJSE_FormCalcContext::lessequal_operator},
    {kFuncTag, "gt_op", CFXJSE_FormCalcContext::greater_operator},
    {kFuncTag, "ge_op", CFXJSE_FormCalcContext::greaterequal_operator},
    {kFuncTag, "plus_op", CFXJSE_FormCalcContext::plus_operator},
    {kFuncTag, "minus_op", CFXJSE_FormCalcContext::minus_operator},
    {kFuncTag, "mul_op", CFXJSE_FormCalcContext::multiple_operator},
    {kFuncTag, "div_op", CFXJSE_FormCalcContext::divide_operator},
    {kFuncTag, "asgn_val_op", CFXJSE_FormCalcContext::assign_value_operator},
    {kFuncTag, "dot_acc", CFXJSE_FormCalcContext::dot_accessor},
    {kFuncTag, "dotdot_acc", CFXJSE_FormCalcContext::dotdot_accessor},
    {kFuncTag, "concat_obj", CFXJSE_FormCalcContext::concat_fm_object},
    {kFuncTag, "is_obj", CFXJSE_FormCalcContext::is_fm_object},
    {kFuncTag, "is_ary", CFXJSE_FormCalcContext::is_fm_array},
    {kFuncTag, "get_val", CFXJSE_FormCalcContext::get_fm_value},
    {kFuncTag, "get_jsobj", CFXJSE_FormCalcContext::get_fm_jsobj},
    {kFuncTag, "var_filter", CFXJSE_FormCalcContext::fm_var_filter},
};

const uint8_t kAltTableDate[] = {
    255, 255, 255, 3,   9,   255, 255, 255, 255, 255, 255,
    255, 2,   255, 255, 255, 255, 255, 255, 255, 255, 255,
    255, 255, 1,   255, 255, 255, 255, 255, 255, 255, 255,
};
static_assert(std::size(kAltTableDate) == L'a' - L'A' + 1,
              "Invalid kAltTableDate size.");

const uint8_t kAltTableTime[] = {
    14,  255, 255, 3,   9,   255, 255, 15,  255, 255, 255,
    255, 6,   255, 255, 255, 255, 255, 7,   255, 255, 255,
    255, 255, 1,   17,  255, 255, 255, 255, 255, 255, 255,
};
static_assert(std::size(kAltTableTime) == L'a' - L'A' + 1,
              "Invalid kAltTableTime size.");

void AlternateDateTimeSymbols(WideString* pPattern,
                              const WideString& wsAltSymbols,
                              bool bIsDate) {
  const uint8_t* pAltTable = bIsDate ? kAltTableDate : kAltTableTime;
  int32_t nLength = pPattern->GetLength();
  bool bInConstRange = false;
  bool bEscape = false;
  int32_t i = 0;
  UNSAFE_TODO({
    while (i < nLength) {
      wchar_t wc = (*pPattern)[i];
      if (wc == L'\'') {
        bInConstRange = !bInConstRange;
        if (bEscape) {
          i++;
        } else {
          pPattern->Delete(i);
          nLength--;
        }
        bEscape = !bEscape;
        continue;
      }
      if (!bInConstRange && wc >= L'A' && wc <= L'a') {
        uint8_t nAlt = pAltTable[wc - L'A'];
        if (nAlt != 255) {
          pPattern->SetAt(i, wsAltSymbols[nAlt]);
        }
      }
      i++;
      bEscape = false;
    }
  });
}

std::pair<bool, CXFA_LocaleValue::ValueType> PatternStringType(
    ByteStringView bsPattern) {
  WideString wsPattern = WideString::FromUTF8(bsPattern);
  if (wsPattern.First(8).EqualsASCII("datetime")) {
    return {true, CXFA_LocaleValue::ValueType::kDateTime};
  }
  if (wsPattern.First(4).EqualsASCII("date")) {
    auto pos = wsPattern.Find(L"time");
    if (pos.has_value() && pos.value() != 0) {
      return {true, CXFA_LocaleValue::ValueType::kDateTime};
    }
    return {true, CXFA_LocaleValue::ValueType::kDate};
  }
  if (wsPattern.First(4).EqualsASCII("time")) {
    return {true, CXFA_LocaleValue::ValueType::kTime};
  }
  if (wsPattern.First(4).EqualsASCII("text")) {
    return {true, CXFA_LocaleValue::ValueType::kText};
  }
  if (wsPattern.First(3).EqualsASCII("num")) {
    if (wsPattern.Substr(4, 7).EqualsASCII("integer")) {
      return {true, CXFA_LocaleValue::ValueType::kInteger};
    }
    if (wsPattern.Substr(4, 7).EqualsASCII("decimal")) {
      return {true, CXFA_LocaleValue::ValueType::kDecimal};
    }
    if (wsPattern.Substr(4, 8).EqualsASCII("currency")) {
      return {true, CXFA_LocaleValue::ValueType::kFloat};
    }
    if (wsPattern.Substr(4, 7).EqualsASCII("percent")) {
      return {true, CXFA_LocaleValue::ValueType::kFloat};
    }
    return {true, CXFA_LocaleValue::ValueType::kFloat};
  }

  CXFA_LocaleValue::ValueType type = CXFA_LocaleValue::ValueType::kNull;
  wsPattern.MakeLower();
  const wchar_t* pData = wsPattern.c_str();
  int32_t iLength = wsPattern.GetLength();
  int32_t index = 0;
  bool bSingleQuotation = false;
  UNSAFE_TODO({
    while (index < iLength) {
      wchar_t wsPatternChar = pData[index];
      if (wsPatternChar == 0x27) {
        bSingleQuotation = !bSingleQuotation;
        index++;
        continue;
      }
      if (bSingleQuotation) {
        index++;
        continue;
      }

      if (wsPatternChar == 'h' || wsPatternChar == 'k') {
        return {false, CXFA_LocaleValue::ValueType::kTime};
      }
      if (wsPatternChar == 'x' || wsPatternChar == 'o' ||
          wsPatternChar == '0') {
        return {false, CXFA_LocaleValue::ValueType::kText};
      }
      if (wsPatternChar == 'v' || wsPatternChar == '8' ||
          wsPatternChar == '$') {
        return {false, CXFA_LocaleValue::ValueType::kFloat};
      }
      if (wsPatternChar == 'y' || wsPatternChar == 'j') {
        index++;
        wchar_t timePatternChar;
        while (index < iLength) {
          timePatternChar = pData[index];
          if (timePatternChar == 0x27) {
            bSingleQuotation = !bSingleQuotation;
            index++;
            continue;
          }
          if (!bSingleQuotation && timePatternChar == 't') {
            return {false, CXFA_LocaleValue::ValueType::kDateTime};
          }
          index++;
        }
        return {false, CXFA_LocaleValue::ValueType::kDate};
      }

      if (wsPatternChar == 'a') {
        type = CXFA_LocaleValue::ValueType::kText;
      } else if (wsPatternChar == 'z' || wsPatternChar == 's' ||
                 wsPatternChar == 'e' || wsPatternChar == ',' ||
                 wsPatternChar == '.') {
        type = CXFA_LocaleValue::ValueType::kFloat;
      }
      index++;
    }
    return {false, type};
  });
}

CFXJSE_FormCalcContext* ToFormCalcContext(CFXJSE_HostObject* pHostObj) {
  return pHostObj ? pHostObj->AsFormCalcContext() : nullptr;
}

GCedLocaleIface* LocaleFromString(CXFA_Document* doc,
                                  CXFA_LocaleMgr* pMgr,
                                  ByteStringView bsLocale) {
  if (!bsLocale.IsEmpty()) {
    return pMgr->GetLocaleByName(WideString::FromUTF8(bsLocale));
  }

  CXFA_Node* pThisNode = ToNode(doc->GetScriptContext()->GetThisObject());
  return pThisNode->GetLocale();
}

WideString FormatFromString(LocaleIface* pLocale, ByteStringView bsFormat) {
  if (!bsFormat.IsEmpty()) {
    return WideString::FromUTF8(bsFormat);
  }

  return pLocale->GetDatePattern(LocaleIface::DateTimeSubcategory::kDefault);
}

LocaleIface::DateTimeSubcategory SubCategoryFromInt(int32_t iStyle) {
  switch (iStyle) {
    case 1:
      return LocaleIface::DateTimeSubcategory::kShort;
    case 3:
      return LocaleIface::DateTimeSubcategory::kLong;
    case 4:
      return LocaleIface::DateTimeSubcategory::kFull;
    case 0:
    case 2:
    default:
      return LocaleIface::DateTimeSubcategory::kMedium;
  }
}

ByteString GetLocalDateTimeFormat(CXFA_Document* doc,
                                  int32_t iStyle,
                                  ByteStringView bsLocale,
                                  bool bStandard,
                                  bool bIsDate) {
  CXFA_LocaleMgr* pMgr = doc->GetLocaleMgr();
  LocaleIface* pLocale = LocaleFromString(doc, pMgr, bsLocale);
  if (!pLocale) {
    return ByteString();
  }

  LocaleIface::DateTimeSubcategory category = SubCategoryFromInt(iStyle);
  WideString wsLocal = bIsDate ? pLocale->GetDatePattern(category)
                               : pLocale->GetTimePattern(category);
  if (!bStandard) {
    AlternateDateTimeSymbols(&wsLocal, pLocale->GetDateTimeSymbols(), bIsDate);
  }
  return wsLocal.ToUTF8();
}

bool IsWhitespace(char c) {
  return c == 0x20 || c == 0x09 || c == 0x0B || c == 0x0C || c == 0x0A ||
         c == 0x0D;
}

bool IsPartOfNumber(char ch) {
  return FXSYS_IsDecimalDigit(ch) || ch == '-' || ch == '.';
}

bool IsPartOfNumberW(wchar_t ch) {
  return FXSYS_IsDecimalDigit(ch) || ch == L'-' || ch == L'.';
}

ByteString GUIDString(bool bSeparator) {
  std::array<uint8_t, 16> data;
  FX_Random::Fill(
      fxcrt::reinterpret_span<uint32_t, uint8_t>(pdfium::span(data)));
  data[6] = (data[6] & 0x0F) | 0x40;

  ByteString bsGUID;
  {
    // Span's lifetime must end before ReleaseBuffer() below.
    pdfium::span<char> guid_span = bsGUID.GetBuffer(40);
    size_t out_index = 0;
    for (size_t i = 0; i < 16; ++i, out_index += 2) {
      if (bSeparator && (i == 4 || i == 6 || i == 8 || i == 10)) {
        guid_span[out_index++] = L'-';
      }
      FXSYS_IntToTwoHexChars(data[i], guid_span.subspan(out_index).first<2u>());
    }
  }
  bsGUID.ReleaseBuffer(bSeparator ? 36 : 32);
  return bsGUID;
}

void GetLocalTimeZone(int32_t* pHour, int32_t* pMin, int32_t* pSec) {
  time_t now;
  FXSYS_time(&now);

  struct tm* pGmt = gmtime(&now);
  struct tm* pLocal = FXSYS_localtime(&now);
  *pHour = pLocal->tm_hour - pGmt->tm_hour;
  *pMin = pLocal->tm_min - pGmt->tm_min;
  *pSec = pLocal->tm_sec - pGmt->tm_sec;
}

bool HTMLSTR2Code(const WideString& pData, uint32_t* iCode) {
  auto cmpFunc = [](const XFA_FMHtmlReserveCode& iter, ByteStringView val) {
    return UNSAFE_TODO(strcmp(val.unterminated_c_str(), iter.html_reserve_)) >
           0;
  };
  if (!pData.IsASCII()) {
    return false;
  }
  ByteString temp = pData.ToASCII();
  const XFA_FMHtmlReserveCode* result = std::lower_bound(
      std::begin(kReservesForDecode), std::end(kReservesForDecode),
      temp.AsStringView(), cmpFunc);
  if (result != std::end(kReservesForDecode) &&
      !UNSAFE_TODO(strcmp(temp.c_str(), result->html_reserve_))) {
    *iCode = result->code_;
    return true;
  }
  return false;
}

bool HTMLCode2STR(uint32_t iCode, WideString* wsHTMLReserve) {
  auto cmpFunc = [](const XFA_FMHtmlReserveCode iter, const uint32_t& val) {
    return iter.code_ < val;
  };
  const XFA_FMHtmlReserveCode* result =
      std::lower_bound(std::begin(kReservesForEncode),
                       std::end(kReservesForEncode), iCode, cmpFunc);
  if (result != std::end(kReservesForEncode) && result->code_ == iCode) {
    *wsHTMLReserve = WideString::FromASCII(result->html_reserve_);
    return true;
  }
  return false;
}

WideString DecodeURL(const WideString& wsURL) {
  const wchar_t* pData = wsURL.c_str();
  size_t iLen = wsURL.GetLength();
  WideTextBuffer wsResultBuf;
  UNSAFE_TODO({
    for (size_t i = 0; i < iLen; ++i) {
      wchar_t ch = pData[i];
      if ('%' != ch) {
        wsResultBuf.AppendChar(ch);
        continue;
      }
      wchar_t chTemp = 0;
      int32_t iCount = 0;
      while (iCount < 2) {
        if (++i >= iLen) {
          return WideString();
        }
        chTemp *= 16;
        ch = pData[i];
        if (!FXSYS_IsWideHexDigit(ch)) {
          return WideString();
        }
        chTemp += FXSYS_WideHexCharToInt(ch);
        ++iCount;
      }
      wsResultBuf.AppendChar(chTemp);
    }
    return wsResultBuf.MakeString();
  });
}

WideString DecodeMLInternal(const WideString& wsHTML, bool bIsHTML) {
  const wchar_t* pData = wsHTML.c_str();
  size_t iLen = wsHTML.GetLength();
  WideTextBuffer wsResultBuf;
  UNSAFE_TODO({
    for (size_t i = 0; i < iLen; ++i) {
      wchar_t ch = pData[i];
      if (ch != '&') {
        wsResultBuf.AppendChar(ch);
        continue;
      }

      if (++i >= iLen) {
        break;
      }
      ch = pData[i];
      if (ch == '#') {
        if (++i >= iLen) {
          break;
        }
        ch = pData[i];
        if (ch != 'x' && ch != 'X') {
          return WideString();
        }
        if (++i >= iLen) {
          break;
        }
        ch = pData[i];
        uint32_t iCode = 0;
        while (ch != ';' && i < iLen) {
          iCode *= 16;
          if (!FXSYS_IsWideHexDigit(ch)) {
            return WideString();
          }
          iCode += FXSYS_WideHexCharToInt(ch);
          if (++i >= iLen) {
            break;
          }
          ch = pData[i];
        }
        wsResultBuf.AppendChar(iCode);
        continue;
      }

      wchar_t szBuffer[9];
      size_t iStrIndex = 0;
      while (ch != ';' && i < iLen) {
        if (iStrIndex < 8) {
          szBuffer[iStrIndex++] = ch;
        }
        if (++i >= iLen) {
          break;
        }
        ch = pData[i];
      }
      szBuffer[iStrIndex] = 0;
      if (bIsHTML) {
        uint32_t iData = 0;
        if (HTMLSTR2Code(szBuffer, &iData)) {
          wsResultBuf.AppendChar((wchar_t)iData);
        }
      } else {
        if (wcscmp(szBuffer, L"quot") == 0) {
          wsResultBuf.AppendChar('"');
        } else if (wcscmp(szBuffer, L"amp") == 0) {
          wsResultBuf.AppendChar('&');
        } else if (wcscmp(szBuffer, L"apos") == 0) {
          wsResultBuf.AppendChar('\'');
        } else if (wcscmp(szBuffer, L"lt") == 0) {
          wsResultBuf.AppendChar('<');
        } else if (wcscmp(szBuffer, L"gt") == 0) {
          wsResultBuf.AppendChar('>');
        }
      }
    }
    return wsResultBuf.MakeString();
  });
}

WideString DecodeHTML(const WideString& wsHTML) {
  return DecodeMLInternal(wsHTML, true);
}

WideString DecodeXML(const WideString& wsXML) {
  return DecodeMLInternal(wsXML, false);
}

WideString EncodeURL(const ByteString& bsURL) {
  static constexpr char32_t kStrUnsafe[] = {' ', '<', '>', '"', '#',
                                            '%', '{', '}', '|', '\\',
                                            '^', '~', '[', ']', '`'};
  static constexpr char32_t kStrReserved[] = {';', '/', '?', ':',
                                              '@', '=', '&'};

  WideString wsURL = WideString::FromUTF8(bsURL.AsStringView());
  WideTextBuffer wsResultBuf;
  std::array<wchar_t, 3> encode_buffer = {L'%'};  // Starts with %.
  for (char32_t ch : pdfium::CodePointView(wsURL.AsStringView())) {
    if (ch <= 0x1f || (ch >= 0x7f && ch <= 0xff) ||
        pdfium::Contains(kStrUnsafe, ch) ||
        pdfium::Contains(kStrReserved, ch)) {
      int32_t index = ch / 16;
      encode_buffer[1] = kStrCode[index];
      encode_buffer[2] = kStrCode[ch - index * 16];
      wsResultBuf << WideStringView(encode_buffer);
      continue;
    }
    if (ch >= 0x20 && ch <= 0x7e) {
      wsResultBuf.AppendChar(ch);
      continue;
    }
    const wchar_t iRadix = 16;
    WideString wsBuffer;
    while (ch >= iRadix) {
      wchar_t tmp = kStrCode[ch % iRadix];
      ch /= iRadix;
      wsBuffer += tmp;
    }
    wsBuffer += kStrCode[ch];
    int32_t iLen = wsBuffer.GetLength();
    if (iLen < 2) {
      break;
    }

    int32_t index = 0;
    if (iLen % 2 != 0) {
      encode_buffer[1] = '0';
      encode_buffer[2] = wsBuffer[iLen - 1];
      index = iLen - 2;
    } else {
      encode_buffer[1] = wsBuffer[iLen - 1];
      encode_buffer[2] = wsBuffer[iLen - 2];
      index = iLen - 3;
    }
    wsResultBuf << WideStringView(encode_buffer);
    while (index > 0) {
      encode_buffer[1] = wsBuffer[index];
      encode_buffer[2] = wsBuffer[index - 1];
      index -= 2;
      wsResultBuf << WideStringView(encode_buffer);
    }
  }
  return wsResultBuf.MakeString();
}

WideString EncodeHTML(const ByteString& bsHTML) {
  WideString wsHTML = WideString::FromUTF8(bsHTML.AsStringView());
  std::array<wchar_t, 8> encode_buffer;
  encode_buffer[0] = '&';
  encode_buffer[1] = '#';
  encode_buffer[2] = 'x';
  WideTextBuffer wsResultBuf;
  for (char32_t ch : pdfium::CodePointView(wsHTML.AsStringView())) {
    WideString htmlReserve;
    if (HTMLCode2STR(ch, &htmlReserve)) {
      wsResultBuf.AppendChar(L'&');
      wsResultBuf << htmlReserve;
      wsResultBuf.AppendChar(L';');
    } else if (ch >= 32 && ch <= 126) {
      wsResultBuf.AppendChar(static_cast<wchar_t>(ch));
    } else if (ch < 256) {
      int32_t index = ch / 16;
      encode_buffer[3] = kStrCode[index];
      encode_buffer[4] = kStrCode[ch - index * 16];
      encode_buffer[5] = ';';
      wsResultBuf << WideStringView(encode_buffer).First(6);
    } else if (ch < 65536) {
      int32_t iBigByte = ch / 256;
      int32_t iLittleByte = ch % 256;
      encode_buffer[3] = kStrCode[iBigByte / 16];
      encode_buffer[4] = kStrCode[iBigByte % 16];
      encode_buffer[5] = kStrCode[iLittleByte / 16];
      encode_buffer[6] = kStrCode[iLittleByte % 16];
      encode_buffer[7] = ';';
      wsResultBuf << WideStringView(encode_buffer).First(8);
    } else {
      // TODO(tsepez): Handle codepoint not in BMP.
    }
  }
  return wsResultBuf.MakeString();
}

WideString EncodeXML(const ByteString& bsXML) {
  WideString wsXML = WideString::FromUTF8(bsXML.AsStringView());
  WideTextBuffer wsResultBuf;
  std::array<wchar_t, 8> encode_buffer;
  encode_buffer[0] = '&';
  encode_buffer[1] = '#';
  encode_buffer[2] = 'x';
  for (char32_t ch : pdfium::CodePointView(wsXML.AsStringView())) {
    switch (ch) {
      case '"':
        wsResultBuf.AppendChar('&');
        wsResultBuf << WideStringView(L"quot");
        wsResultBuf.AppendChar(';');
        break;
      case '&':
        wsResultBuf.AppendChar('&');
        wsResultBuf << WideStringView(L"amp");
        wsResultBuf.AppendChar(';');
        break;
      case '\'':
        wsResultBuf.AppendChar('&');
        wsResultBuf << WideStringView(L"apos");
        wsResultBuf.AppendChar(';');
        break;
      case '<':
        wsResultBuf.AppendChar('&');
        wsResultBuf << WideStringView(L"lt");
        wsResultBuf.AppendChar(';');
        break;
      case '>':
        wsResultBuf.AppendChar('&');
        wsResultBuf << WideStringView(L"gt");
        wsResultBuf.AppendChar(';');
        break;
      default: {
        if (ch >= 32 && ch <= 126) {
          wsResultBuf.AppendChar(static_cast<wchar_t>(ch));
        } else if (ch < 256) {
          int32_t index = ch / 16;
          encode_buffer[3] = kStrCode[index];
          encode_buffer[4] = kStrCode[ch - index * 16];
          encode_buffer[5] = ';';
          wsResultBuf << WideStringView(encode_buffer).First(6);
        } else if (ch < 65536) {
          int32_t iBigByte = ch / 256;
          int32_t iLittleByte = ch % 256;
          encode_buffer[3] = kStrCode[iBigByte / 16];
          encode_buffer[4] = kStrCode[iBigByte % 16];
          encode_buffer[5] = kStrCode[iLittleByte / 16];
          encode_buffer[6] = kStrCode[iLittleByte % 16];
          encode_buffer[7] = ';';
          wsResultBuf << WideStringView(encode_buffer).First(8);
        } else {
          // TODO(tsepez): Handle codepoint not in BMP.
        }
        break;
      }
    }
  }
  return wsResultBuf.MakeString();
}

ByteString TrillionUS(ByteStringView bsData) {
  static const char kUnits[][6] = {"zero", "one", "two",   "three", "four",
                                   "five", "six", "seven", "eight", "nine"};
  static const char kCapUnits[][6] = {"Zero", "One", "Two",   "Three", "Four",
                                      "Five", "Six", "Seven", "Eight", "Nine"};
  static const char kTens[][10] = {
      "Ten",     "Eleven",  "Twelve",    "Thirteen", "Fourteen",
      "Fifteen", "Sixteen", "Seventeen", "Eighteen", "Nineteen"};
  static const char kLastTens[][8] = {"Twenty", "Thirty",  "Forty",  "Fifty",
                                      "Sixty",  "Seventy", "Eighty", "Ninety"};
  static const char kComm[][11] = {" Hundred ", " Thousand ", " Million ",
                                   " Billion ", "Trillion"};
  const char* pData = bsData.unterminated_c_str();
  int32_t iLength = bsData.GetLength();
  int32_t iComm = 0;
  if (iLength > 12) {
    iComm = 4;
  } else if (iLength > 9) {
    iComm = 3;
  } else if (iLength > 6) {
    iComm = 2;
  } else if (iLength > 3) {
    iComm = 1;
  }

  int32_t iFirstCount = iLength % 3;
  if (iFirstCount == 0) {
    iFirstCount = 3;
  }

  ByteString strBuf;
  int32_t index = 0;
  UNSAFE_TODO({
    if (iFirstCount == 3) {
      if (pData[index] != '0') {
        strBuf += kCapUnits[pData[index] - '0'];
        strBuf += kComm[0];
      }
      if (pData[index + 1] == '0') {
        strBuf += kCapUnits[pData[index + 2] - '0'];
      } else {
        if (pData[index + 1] > '1') {
          strBuf += kLastTens[pData[index + 1] - '2'];
          strBuf += "-";
          strBuf += kUnits[pData[index + 2] - '0'];
        } else if (pData[index + 1] == '1') {
          strBuf += kTens[pData[index + 2] - '0'];
        } else if (pData[index + 1] == '0') {
          strBuf += kCapUnits[pData[index + 2] - '0'];
        }
      }
      index += 3;
    } else if (iFirstCount == 2) {
      if (pData[index] == '0') {
        strBuf += kCapUnits[pData[index + 1] - '0'];
      } else {
        if (pData[index] > '1') {
          strBuf += kLastTens[pData[index] - '2'];
          strBuf += "-";
          strBuf += kUnits[pData[index + 1] - '0'];
        } else if (pData[index] == '1') {
          strBuf += kTens[pData[index + 1] - '0'];
        } else if (pData[index] == '0') {
          strBuf += kCapUnits[pData[index + 1] - '0'];
        }
      }
      index += 2;
    } else if (iFirstCount == 1) {
      strBuf += kCapUnits[pData[index] - '0'];
      ++index;
    }
    if (iLength > 3 && iFirstCount > 0) {
      strBuf += kComm[iComm];
      --iComm;
    }
    while (index < iLength) {
      if (pData[index] != '0') {
        strBuf += kCapUnits[pData[index] - '0'];
        strBuf += kComm[0];
      }
      if (pData[index + 1] == '0') {
        strBuf += kCapUnits[pData[index + 2] - '0'];
      } else {
        if (pData[index + 1] > '1') {
          strBuf += kLastTens[pData[index + 1] - '2'];
          strBuf += "-";
          strBuf += kUnits[pData[index + 2] - '0'];
        } else if (pData[index + 1] == '1') {
          strBuf += kTens[pData[index + 2] - '0'];
        } else if (pData[index + 1] == '0') {
          strBuf += kCapUnits[pData[index + 2] - '0'];
        }
      }
      if (index < iLength - 3) {
        strBuf += kComm[iComm];
        --iComm;
      }
      index += 3;
    }
    return strBuf;
  });
}

ByteString WordUS(ByteStringView bsData, int32_t iStyle) {
  if (iStyle < 0 || iStyle > 2) {
    return ByteString();
  }

  int32_t iLength = bsData.GetLength();
  ByteString strBuf;
  int32_t index = 0;
  while (index < iLength) {
    if (bsData[index] == '.') {
      break;
    }
    ++index;
  }
  int32_t iInteger = index;
  index = 0;
  while (index < iInteger) {
    int32_t iCount = (iInteger - index) % 12;
    if (!iCount && iInteger - index > 0) {
      iCount = 12;
    }

    strBuf += TrillionUS(bsData.Substr(index, iCount));
    index += iCount;
    if (index < iInteger) {
      strBuf += " Trillion ";
    }
  }

  if (iStyle > 0) {
    strBuf += " Dollars";
  }

  if (iStyle > 1 && iInteger < iLength) {
    strBuf += " And ";
    index = iInteger + 1;
    while (index < iLength) {
      int32_t iCount = (iLength - index) % 12;
      if (!iCount && iLength - index > 0) {
        iCount = 12;
      }

      strBuf += TrillionUS(bsData.Substr(index, iCount));
      index += iCount;
      if (index < iLength) {
        strBuf += " Trillion ";
      }
    }
    strBuf += " Cents";
  }
  return strBuf;
}

v8::Local<v8::Value> GetObjectDefaultValue(v8::Isolate* pIsolate,
                                           v8::Local<v8::Object> pObject) {
  CXFA_Node* pNode = ToNode(CFXJSE_Engine::ToObject(pIsolate, pObject));
  if (!pNode) {
    return fxv8::NewNullHelper(pIsolate);
  }

  v8::Local<v8::Value> value;
  pNode->JSObject()->ScriptSomDefaultValue(pIsolate, &value, false,
                                           XFA_Attribute::Unknown);
  return value;
}

bool SetObjectDefaultValue(v8::Isolate* pIsolate,
                           v8::Local<v8::Object> pObject,
                           v8::Local<v8::Value> hNewValue) {
  CXFA_Node* pNode = ToNode(CFXJSE_Engine::ToObject(pIsolate, pObject));
  if (!pNode) {
    return false;
  }

  pNode->JSObject()->ScriptSomDefaultValue(pIsolate, &hNewValue, true,
                                           XFA_Attribute::Unknown);
  return true;
}

v8::Local<v8::Value> GetExtractedValue(v8::Isolate* pIsolate,
                                       v8::Local<v8::Value> pValue) {
  if (pValue.IsEmpty()) {
    return v8::Local<v8::Value>();
  }

  if (fxv8::IsArray(pValue)) {
    v8::Local<v8::Array> arr = pValue.As<v8::Array>();
    uint32_t iLength = fxv8::GetArrayLengthHelper(arr);
    if (iLength < 3) {
      return fxv8::NewUndefinedHelper(pIsolate);
    }

    v8::Local<v8::Value> propertyValue =
        fxv8::ReentrantGetArrayElementHelper(pIsolate, arr, 1);
    v8::Local<v8::Value> jsValue =
        fxv8::ReentrantGetArrayElementHelper(pIsolate, arr, 2);
    if (!fxv8::IsObject(jsValue)) {
      return fxv8::NewUndefinedHelper(pIsolate);
    }

    v8::Local<v8::Object> jsObjectValue = jsValue.As<v8::Object>();
    if (fxv8::IsNull(propertyValue)) {
      return GetObjectDefaultValue(pIsolate, jsObjectValue);
    }

    ByteString bsName =
        fxv8::ReentrantToByteStringHelper(pIsolate, propertyValue);
    return fxv8::ReentrantGetObjectPropertyHelper(pIsolate, jsObjectValue,
                                                  bsName.AsStringView());
  }

  if (fxv8::IsObject(pValue)) {
    return GetObjectDefaultValue(pIsolate, pValue.As<v8::Object>());
  }

  return pValue;
}

v8::Local<v8::Value> GetSimpleValue(
    const v8::FunctionCallbackInfo<v8::Value>& info,
    uint32_t index) {
  DCHECK(index < static_cast<uint32_t>(info.Length()));
  return GetExtractedValue(info.GetIsolate(), info[index]);
}

bool ValueIsNull(v8::Isolate* pIsolate, v8::Local<v8::Value> arg) {
  v8::Local<v8::Value> extracted = GetExtractedValue(pIsolate, arg);
  return extracted.IsEmpty() || fxv8::IsNull(extracted);
}

int32_t ValueToInteger(v8::Isolate* pIsolate, v8::Local<v8::Value> arg) {
  v8::Local<v8::Value> extracted = GetExtractedValue(pIsolate, arg);
  if (extracted.IsEmpty()) {
    return 0;
  }

  if (fxv8::IsObject(extracted) || fxv8::IsArray(extracted)) {
    return ValueToInteger(pIsolate, extracted);
  }

  if (fxv8::IsString(extracted)) {
    ByteString value = fxv8::ReentrantToByteStringHelper(pIsolate, extracted);
    return StringToInt(value.AsStringView());
  }

  return fxv8::ReentrantToInt32Helper(pIsolate, extracted);
}

float ValueToFloat(v8::Isolate* pIsolate, v8::Local<v8::Value> arg) {
  v8::Local<v8::Value> extracted = GetExtractedValue(pIsolate, arg);
  if (extracted.IsEmpty()) {
    return 0.0f;
  }

  if (fxv8::IsUndefined(extracted)) {
    return 0.0f;
  }

  if (fxv8::IsObject(extracted) || fxv8::IsArray(extracted)) {
    return ValueToFloat(pIsolate, extracted);
  }

  if (fxv8::IsString(extracted)) {
    ByteString bsValue = fxv8::ReentrantToByteStringHelper(pIsolate, extracted);
    return StringToFloat(bsValue.AsStringView());
  }

  return fxv8::ReentrantToFloatHelper(pIsolate, extracted);
}

double ValueToDouble(v8::Isolate* pIsolate, v8::Local<v8::Value> arg) {
  v8::Local<v8::Value> extracted = GetExtractedValue(pIsolate, arg);
  if (extracted.IsEmpty()) {
    return 0.0;
  }

  if (fxv8::IsUndefined(extracted)) {
    return 0.0;
  }

  if (fxv8::IsObject(extracted) || fxv8::IsArray(extracted)) {
    return ValueToDouble(pIsolate, extracted);
  }

  if (fxv8::IsString(extracted)) {
    ByteString bsValue = fxv8::ReentrantToByteStringHelper(pIsolate, extracted);
    return StringToDouble(bsValue.AsStringView());
  }

  return fxv8::ReentrantToDoubleHelper(pIsolate, extracted);
}

std::optional<double> ExtractDouble(v8::Isolate* pIsolate,
                                    v8::Local<v8::Value> src) {
  if (src.IsEmpty()) {
    return 0.0;
  }

  if (!fxv8::IsArray(src)) {
    return ValueToDouble(pIsolate, src);
  }

  v8::Local<v8::Array> arr = src.As<v8::Array>();
  uint32_t iLength = fxv8::GetArrayLengthHelper(arr);
  if (iLength < 3) {
    return std::nullopt;
  }

  v8::Local<v8::Value> propertyValue =
      fxv8::ReentrantGetArrayElementHelper(pIsolate, arr, 1);
  v8::Local<v8::Value> jsValue =
      fxv8::ReentrantGetArrayElementHelper(pIsolate, arr, 2);
  if (fxv8::IsNull(propertyValue) || !fxv8::IsObject(jsValue)) {
    return ValueToDouble(pIsolate, jsValue);
  }

  ByteString bsName =
      fxv8::ReentrantToByteStringHelper(pIsolate, propertyValue);
  return ValueToDouble(
      pIsolate, fxv8::ReentrantGetObjectPropertyHelper(
                    pIsolate, jsValue.As<v8::Object>(), bsName.AsStringView()));
}

ByteString ValueToUTF8String(v8::Isolate* pIsolate, v8::Local<v8::Value> arg) {
  if (arg.IsEmpty()) {
    return ByteString();
  }

  if (fxv8::IsNull(arg) || fxv8::IsUndefined(arg)) {
    return ByteString();
  }

  if (fxv8::IsBoolean(arg)) {
    return fxv8::ReentrantToBooleanHelper(pIsolate, arg) ? "1" : "0";
  }

  return fxv8::ReentrantToByteStringHelper(pIsolate, arg);
}

bool SimpleValueCompare(v8::Isolate* pIsolate,
                        v8::Local<v8::Value> firstValue,
                        v8::Local<v8::Value> secondValue) {
  if (firstValue.IsEmpty()) {
    return false;
  }

  if (fxv8::IsString(firstValue)) {
    const ByteString first = ValueToUTF8String(pIsolate, firstValue);
    const ByteString second = ValueToUTF8String(pIsolate, secondValue);
    return first == second;
  }
  if (fxv8::IsNumber(firstValue)) {
    const float first = ValueToFloat(pIsolate, firstValue);
    const float second = ValueToFloat(pIsolate, secondValue);
    return first == second;
  }
  if (fxv8::IsBoolean(firstValue)) {
    const bool first = fxv8::ReentrantToBooleanHelper(pIsolate, firstValue);
    const bool second = fxv8::ReentrantToBooleanHelper(pIsolate, secondValue);
    return first == second;
  }
  return fxv8::IsNull(firstValue) && fxv8::IsNull(secondValue);
}

v8::LocalVector<v8::Value> UnfoldArgs(
    const v8::FunctionCallbackInfo<v8::Value>& info) {
  v8::LocalVector<v8::Value> results(info.GetIsolate());
  v8::Isolate* pIsolate = info.GetIsolate();
  for (int i = 1; i < info.Length(); ++i) {
    v8::Local<v8::Value> arg = info[i];
    if (fxv8::IsArray(arg)) {
      v8::Local<v8::Array> arr = arg.As<v8::Array>();
      uint32_t iLength = fxv8::GetArrayLengthHelper(arr);
      if (iLength < 3) {
        continue;
      }

      v8::Local<v8::Value> propertyValue =
          fxv8::ReentrantGetArrayElementHelper(pIsolate, arr, 1);

      for (uint32_t j = 2; j < iLength; j++) {
        v8::Local<v8::Value> jsValue =
            fxv8::ReentrantGetArrayElementHelper(pIsolate, arr, j);

        if (!fxv8::IsObject(jsValue)) {
          results.push_back(fxv8::NewUndefinedHelper(pIsolate));
        } else if (fxv8::IsNull(propertyValue)) {
          results.push_back(
              GetObjectDefaultValue(pIsolate, jsValue.As<v8::Object>()));
        } else {
          ByteString bsName =
              fxv8::ReentrantToByteStringHelper(pIsolate, propertyValue);
          results.push_back(fxv8::ReentrantGetObjectPropertyHelper(
              pIsolate, jsValue.As<v8::Object>(), bsName.AsStringView()));
        }
      }
    } else if (fxv8::IsObject(arg)) {
      results.push_back(GetObjectDefaultValue(pIsolate, arg.As<v8::Object>()));
    } else {
      results.push_back(arg);
    }
  }
  return results;
}

// Returns empty value on failure.
v8::Local<v8::Value> GetObjectForName(CFXJSE_HostObject* pHostObject,
                                      ByteStringView bsAccessorName) {
  CXFA_Document* doc = ToFormCalcContext(pHostObject)->GetDocument();
  if (!doc) {
    return v8::Local<v8::Value>();
  }

  CFXJSE_Engine* pScriptContext = doc->GetScriptContext();
  std::optional<CFXJSE_Engine::ResolveResult> maybeResult =
      pScriptContext->ResolveObjects(
          pScriptContext->GetThisObject(),
          WideString::FromUTF8(bsAccessorName).AsStringView(),
          Mask<XFA_ResolveFlag>{
              XFA_ResolveFlag::kChildren, XFA_ResolveFlag::kProperties,
              XFA_ResolveFlag::kSiblings, XFA_ResolveFlag::kParent});
  if (!maybeResult.has_value() ||
      maybeResult.value().type != CFXJSE_Engine::ResolveResult::Type::kNodes ||
      maybeResult.value().objects.empty()) {
    return v8::Local<v8::Value>();
  }
  return pScriptContext->GetOrCreateJSBindingFromMap(
      maybeResult.value().objects.front().Get());
}

std::optional<CFXJSE_Engine::ResolveResult> ResolveObjects(
    CFXJSE_HostObject* pHostObject,
    v8::Local<v8::Value> pRefValue,
    ByteStringView bsSomExp,
    bool bDotAccessor,
    bool bHasNoResolveName) {
  CXFA_Document* doc = ToFormCalcContext(pHostObject)->GetDocument();
  if (!doc) {
    return std::nullopt;
  }

  v8::Isolate* pIsolate = ToFormCalcContext(pHostObject)->GetIsolate();
  WideString wsSomExpression = WideString::FromUTF8(bsSomExp);
  CFXJSE_Engine* pScriptContext = doc->GetScriptContext();
  CXFA_Object* pNode = nullptr;
  Mask<XFA_ResolveFlag> dwFlags;
  if (bDotAccessor) {
    if (fxv8::IsNull(pRefValue)) {
      pNode = pScriptContext->GetThisObject();
      dwFlags = {XFA_ResolveFlag::kSiblings, XFA_ResolveFlag::kParent};
    } else {
      pNode = CFXJSE_Engine::ToObject(pIsolate, pRefValue);
      if (!pNode) {
        return std::nullopt;
      }

      if (bHasNoResolveName) {
        WideString wsName;
        if (CXFA_Node* pXFANode = pNode->AsNode()) {
          std::optional<WideString> ret =
              pXFANode->JSObject()->TryAttribute(XFA_Attribute::Name, false);
          if (ret.has_value()) {
            wsName = ret.value();
          }
        }
        if (wsName.IsEmpty()) {
          wsName = L"#" + WideString::FromASCII(pNode->GetClassName());
        }

        wsSomExpression = wsName + wsSomExpression;
        dwFlags = XFA_ResolveFlag::kSiblings;
      } else {
        dwFlags = (bsSomExp == "*")
                      ? Mask<XFA_ResolveFlag>{XFA_ResolveFlag::kChildren}
                      : Mask<XFA_ResolveFlag>{XFA_ResolveFlag::kChildren,
                                              XFA_ResolveFlag::kAttributes,
                                              XFA_ResolveFlag::kProperties};
      }
    }
  } else {
    pNode = CFXJSE_Engine::ToObject(pIsolate, pRefValue);
    dwFlags = XFA_ResolveFlag::kAnyChild;
  }
  return pScriptContext->ResolveObjects(pNode, wsSomExpression.AsStringView(),
                                        dwFlags);
}

v8::LocalVector<v8::Value> ParseResolveResult(
    CFXJSE_HostObject* pHostObject,
    const CFXJSE_Engine::ResolveResult& resolveNodeRS,
    v8::Local<v8::Value> pParentValue,
    bool* bAttribute) {
  CFXJSE_FormCalcContext* context = ToFormCalcContext(pHostObject);
  v8::Isolate* pIsolate = context->GetIsolate();
  v8::LocalVector<v8::Value> resultValues(pIsolate);

  if (resolveNodeRS.type == CFXJSE_Engine::ResolveResult::Type::kNodes) {
    *bAttribute = false;
    CFXJSE_Engine* pScriptContext = context->GetDocument()->GetScriptContext();
    for (auto& pObject : resolveNodeRS.objects) {
      resultValues.push_back(
          pScriptContext->GetOrCreateJSBindingFromMap(pObject.Get()));
    }
    return resultValues;
  }

  *bAttribute = true;
  if (resolveNodeRS.script_attribute.callback &&
      resolveNodeRS.script_attribute.eValueType == XFA_ScriptType::Object) {
    for (auto& pObject : resolveNodeRS.objects) {
      v8::Local<v8::Value> pValue;
      CJX_Object* jsObject = pObject->JSObject();
      (*resolveNodeRS.script_attribute.callback)(
          pIsolate, jsObject, &pValue, false,
          resolveNodeRS.script_attribute.attribute);
      resultValues.push_back(pValue);
      *bAttribute = false;
    }
  }
  if (*bAttribute && fxv8::IsObject(pParentValue)) {
    resultValues.push_back(pParentValue);
  }

  return resultValues;
}

// Returns 0 if the provided `arg` is an invalid payment period count.
int GetValidatedPaymentPeriods(v8::Isolate* isolate, v8::Local<v8::Value> arg) {
  double periods = ValueToDouble(isolate, arg);
  if (periods < 1 ||
      periods > static_cast<double>(std::numeric_limits<int32_t>::max())) {
    return 0;
  }

  return static_cast<int>(periods);
}

}  // namespace

const FXJSE_CLASS_DESCRIPTOR kFormCalcDescriptor = {
    kClassTag,            // tag
    "XFA_FormCalcClass",  // name
    kFormCalcFunctions,   // methods
    nullptr,              // dynamic prop type
    nullptr,              // dynamic prop getter
    nullptr,              // dynamic prop setter
    nullptr,              // dynamic prop method call
};

// static
void CFXJSE_FormCalcContext::Abs(
    CFXJSE_HostObject* pThis,
    const v8::FunctionCallbackInfo<v8::Value>& info) {
  if (info.Length() != 1) {
    ToFormCalcContext(pThis)->ThrowParamCountMismatchException("Abs");
    return;
  }

  if (ValueIsNull(info.GetIsolate(), info[0])) {
    info.GetReturnValue().SetNull();
    return;
  }
  double dValue = ValueToDouble(info.GetIsolate(), info[0]);
  if (dValue < 0) {
    dValue = -dValue;
  }

  info.GetReturnValue().Set(dValue);
}

// static
void CFXJSE_FormCalcContext::Avg(
    CFXJSE_HostObject* pThis,
    const v8::FunctionCallbackInfo<v8::Value>& info) {
  uint32_t uCount = 0;
  double dSum = 0.0;
  auto fn = [&uCount, &dSum](v8::Isolate* pIsolate,
                             v8::Local<v8::Value> pValue) {
    dSum += ValueToDouble(pIsolate, pValue);
    uCount++;
  };
  if (!ToFormCalcContext(pThis)->ApplyToExpansion(fn, info,
                                                  /*bStrict=*/false)) {
    return;
  }

  if (uCount == 0) {
    info.GetReturnValue().SetNull();
    return;
  }
  info.GetReturnValue().Set(dSum / uCount);
}

// static
void CFXJSE_FormCalcContext::Ceil(
    CFXJSE_HostObject* pThis,
    const v8::FunctionCallbackInfo<v8::Value>& info) {
  if (info.Length() != 1) {
    ToFormCalcContext(pThis)->ThrowParamCountMismatchException("Ceil");
    return;
  }

  v8::Local<v8::Value> argValue = GetSimpleValue(info, 0);
  if (ValueIsNull(info.GetIsolate(), argValue)) {
    info.GetReturnValue().SetNull();
    return;
  }

  info.GetReturnValue().Set(ceil(ValueToFloat(info.GetIsolate(), argValue)));
}

// static
void CFXJSE_FormCalcContext::Count(
    CFXJSE_HostObject* pThis,
    const v8::FunctionCallbackInfo<v8::Value>& info) {
  uint32_t iCount = 0;
  auto fn = [&iCount](v8::Isolate* pIsolate, v8::Local<v8::Value> pvalue) {
    ++iCount;
  };
  if (!ToFormCalcContext(pThis)->ApplyToExpansion(fn, info, /*bStrict=*/true)) {
    return;
  }

  info.GetReturnValue().Set(iCount);
}

// static
void CFXJSE_FormCalcContext::Floor(
    CFXJSE_HostObject* pThis,
    const v8::FunctionCallbackInfo<v8::Value>& info) {
  if (info.Length() != 1) {
    ToFormCalcContext(pThis)->ThrowParamCountMismatchException("Floor");
    return;
  }

  v8::Local<v8::Value> argValue = GetSimpleValue(info, 0);
  if (ValueIsNull(info.GetIsolate(), argValue)) {
    info.GetReturnValue().SetNull();
    return;
  }

  info.GetReturnValue().Set(floor(ValueToFloat(info.GetIsolate(), argValue)));
}

// static
void CFXJSE_FormCalcContext::Max(
    CFXJSE_HostObject* pThis,
    const v8::FunctionCallbackInfo<v8::Value>& info) {
  uint32_t uCount = 0;
  double dMaxValue = 0.0;
  auto fn = [&uCount, &dMaxValue](v8::Isolate* pIsolate,
                                  v8::Local<v8::Value> pValue) {
    ++uCount;
    double dValue = ValueToDouble(pIsolate, pValue);
    dMaxValue = uCount == 1 ? dValue : std::max(dMaxValue, dValue);
  };
  if (!ToFormCalcContext(pThis)->ApplyToExpansion(fn, info, /*bStrict=*/true)) {
    return;
  }

  if (uCount == 0) {
    info.GetReturnValue().SetNull();
    return;
  }
  info.GetReturnValue().Set(dMaxValue);
}

// static
void CFXJSE_FormCalcContext::Min(
    CFXJSE_HostObject* pThis,
    const v8::FunctionCallbackInfo<v8::Value>& info) {
  uint32_t uCount = 0;
  double dMinValue = 0.0;
  auto fn = [&uCount, &dMinValue](v8::Isolate* pIsolate,
                                  v8::Local<v8::Value> pValue) {
    ++uCount;
    double dValue = ValueToDouble(pIsolate, pValue);
    dMinValue = uCount == 1 ? dValue : std::min(dMinValue, dValue);
  };
  if (!ToFormCalcContext(pThis)->ApplyToExpansion(fn, info, /*bStrict=*/true)) {
    return;
  }

  if (uCount == 0) {
    info.GetReturnValue().SetNull();
    return;
  }
  info.GetReturnValue().Set(dMinValue);
}

// static
void CFXJSE_FormCalcContext::Mod(
    CFXJSE_HostObject* pThis,
    const v8::FunctionCallbackInfo<v8::Value>& info) {
  CFXJSE_FormCalcContext* context = ToFormCalcContext(pThis);
  if (info.Length() != 2) {
    context->ThrowParamCountMismatchException("Mod");
    return;
  }

  if (fxv8::IsNull(info[0]) || fxv8::IsNull(info[1])) {
    info.GetReturnValue().SetNull();
    return;
  }

  std::optional<double> maybe_dividend =
      ExtractDouble(info.GetIsolate(), info[0]);
  std::optional<double> maybe_divisor =
      ExtractDouble(info.GetIsolate(), info[1]);
  if (!maybe_dividend.has_value() || !maybe_divisor.has_value()) {
    context->ThrowArgumentMismatchException();
    return;
  }

  double dividend = maybe_dividend.value();
  double divisor = maybe_divisor.value();
  if (divisor == 0.0) {
    context->ThrowDivideByZeroException();
    return;
  }

  info.GetReturnValue().Set(dividend -
                            divisor * static_cast<int32_t>(dividend / divisor));
}

// static
void CFXJSE_FormCalcContext::Round(
    CFXJSE_HostObject* pThis,
    const v8::FunctionCallbackInfo<v8::Value>& info) {
  CFXJSE_FormCalcContext* context = ToFormCalcContext(pThis);
  int32_t argc = info.Length();
  if (argc < 1 || argc > 2) {
    context->ThrowParamCountMismatchException("Round");
    return;
  }

  if (fxv8::IsNull(info[0])) {
    info.GetReturnValue().SetNull();
    return;
  }

  std::optional<double> maybe_value = ExtractDouble(info.GetIsolate(), info[0]);
  if (!maybe_value.has_value()) {
    context->ThrowArgumentMismatchException();
    return;
  }

  double dValue = maybe_value.value();
  uint8_t uPrecision = 0;
  if (argc > 1) {
    if (fxv8::IsNull(info[1])) {
      info.GetReturnValue().SetNull();
      return;
    }
    std::optional<double> maybe_precision =
        ExtractDouble(info.GetIsolate(), info[1]);
    if (!maybe_precision.has_value()) {
      context->ThrowArgumentMismatchException();
      return;
    }
    double dPrecision = maybe_precision.value();
    uPrecision = static_cast<uint8_t>(std::clamp(dPrecision, 0.0, 12.0));
  }

  CFGAS_Decimal decimalValue(static_cast<float>(dValue), uPrecision);
  info.GetReturnValue().Set(decimalValue.ToDouble());
}

// static
void CFXJSE_FormCalcContext::Sum(
    CFXJSE_HostObject* pThis,
    const v8::FunctionCallbackInfo<v8::Value>& info) {
  uint32_t uCount = 0;
  double dSum = 0.0;
  auto fn = [&uCount, &dSum](v8::Isolate* pIsolate,
                             v8::Local<v8::Value> pValue) {
    ++uCount;
    dSum += ValueToDouble(pIsolate, pValue);
  };
  if (!ToFormCalcContext(pThis)->ApplyToExpansion(fn, info, /*bStrict=*/true)) {
    return;
  }

  if (uCount == 0) {
    info.GetReturnValue().SetNull();
    return;
  }
  info.GetReturnValue().Set(dSum);
}

// static
void CFXJSE_FormCalcContext::Date(
    CFXJSE_HostObject* pThis,
    const v8::FunctionCallbackInfo<v8::Value>& info) {
  if (info.Length() != 0) {
    ToFormCalcContext(pThis)->ThrowParamCountMismatchException("Date");
    return;
  }

  time_t currentTime;
  FXSYS_time(&currentTime);
  struct tm* pTmStruct = gmtime(&currentTime);

  info.GetReturnValue().Set(DateString2Num(
      ByteString::Format("%d%02d%02d", pTmStruct->tm_year + 1900,
                         pTmStruct->tm_mon + 1, pTmStruct->tm_mday)
          .AsStringView()));
}

// static
void CFXJSE_FormCalcContext::Date2Num(
    CFXJSE_HostObject* pThis,
    const v8::FunctionCallbackInfo<v8::Value>& info) {
  int32_t argc = info.Length();
  if (argc < 1 || argc > 3) {
    ToFormCalcContext(pThis)->ThrowParamCountMismatchException("Date2Num");
    return;
  }

  v8::Local<v8::Value> dateValue = GetSimpleValue(info, 0);
  if (ValueIsNull(info.GetIsolate(), dateValue)) {
    info.GetReturnValue().SetNull();
    return;
  }

  ByteString bsDate = ValueToUTF8String(info.GetIsolate(), dateValue);
  ByteString bsFormat;
  if (argc > 1) {
    v8::Local<v8::Value> formatValue = GetSimpleValue(info, 1);
    if (ValueIsNull(info.GetIsolate(), formatValue)) {
      info.GetReturnValue().SetNull();
      return;
    }
    bsFormat = ValueToUTF8String(info.GetIsolate(), formatValue);
  }

  ByteString bsLocale;
  if (argc > 2) {
    v8::Local<v8::Value> localeValue = GetSimpleValue(info, 2);
    if (ValueIsNull(info.GetIsolate(), localeValue)) {
      info.GetReturnValue().SetNull();
      return;
    }
    bsLocale = ValueToUTF8String(info.GetIsolate(), localeValue);
  }

  ByteString bsIsoDate =
      Local2IsoDate(pThis, bsDate.AsStringView(), bsFormat.AsStringView(),
                    bsLocale.AsStringView());
  info.GetReturnValue().Set(DateString2Num(bsIsoDate.AsStringView()));
}

// static
void CFXJSE_FormCalcContext::DateFmt(
    CFXJSE_HostObject* pThis,
    const v8::FunctionCallbackInfo<v8::Value>& info) {
  int32_t argc = info.Length();
  if (argc > 2) {
    ToFormCalcContext(pThis)->ThrowParamCountMismatchException("Date2Num");
    return;
  }

  int32_t iStyle = 0;
  if (argc > 0) {
    v8::Local<v8::Value> infotyle = GetSimpleValue(info, 0);
    if (fxv8::IsNull(infotyle)) {
      info.GetReturnValue().SetNull();
      return;
    }

    iStyle = static_cast<int32_t>(ValueToFloat(info.GetIsolate(), infotyle));
    if (iStyle < 0 || iStyle > 4) {
      iStyle = 0;
    }
  }

  ByteString bsLocale;
  if (argc > 1) {
    v8::Local<v8::Value> argLocale = GetSimpleValue(info, 1);
    if (fxv8::IsNull(argLocale)) {
      info.GetReturnValue().SetNull();
      return;
    }
    bsLocale = ValueToUTF8String(info.GetIsolate(), argLocale);
  }

  ByteString bsFormat =
      GetStandardDateFormat(pThis, iStyle, bsLocale.AsStringView());
  info.GetReturnValue().Set(
      fxv8::NewStringHelper(info.GetIsolate(), bsFormat.AsStringView()));
}

// static
void CFXJSE_FormCalcContext::IsoDate2Num(
    CFXJSE_HostObject* pThis,
    const v8::FunctionCallbackInfo<v8::Value>& info) {
  if (info.Length() != 1) {
    ToFormCalcContext(pThis)->ThrowParamCountMismatchException("IsoDate2Num");
    return;
  }
  v8::Local<v8::Value> argOne = GetSimpleValue(info, 0);
  if (fxv8::IsNull(argOne)) {
    info.GetReturnValue().SetNull();
    return;
  }
  ByteString bsArg = ValueToUTF8String(info.GetIsolate(), argOne);
  info.GetReturnValue().Set(DateString2Num(bsArg.AsStringView()));
}

// static
void CFXJSE_FormCalcContext::IsoTime2Num(
    CFXJSE_HostObject* pThis,
    const v8::FunctionCallbackInfo<v8::Value>& info) {
  CFXJSE_FormCalcContext* context = ToFormCalcContext(pThis);
  if (info.Length() != 1) {
    context->ThrowParamCountMismatchException("IsoTime2Num");
    return;
  }

  v8::Local<v8::Value> argOne = GetSimpleValue(info, 0);
  if (ValueIsNull(info.GetIsolate(), argOne)) {
    info.GetReturnValue().SetNull();
    return;
  }

  CXFA_Document* doc = context->GetDocument();
  CXFA_LocaleMgr* pMgr = doc->GetLocaleMgr();
  ByteString bsArg = ValueToUTF8String(info.GetIsolate(), argOne);
  auto pos = bsArg.Find('T', 0);
  if (!pos.has_value() || pos.value() == bsArg.GetLength() - 1) {
    info.GetReturnValue().Set(0);
    return;
  }
  bsArg = bsArg.Substr(pos.value() + 1);

  CXFA_LocaleValue timeValue(CXFA_LocaleValue::ValueType::kTime,
                             WideString::FromUTF8(bsArg.AsStringView()), pMgr);
  if (!timeValue.IsValid()) {
    info.GetReturnValue().Set(0);
    return;
  }

  CFX_DateTime uniTime = timeValue.GetTime();
  int32_t hour = uniTime.GetHour();
  int32_t min = uniTime.GetMinute();
  int32_t second = uniTime.GetSecond();
  int32_t milSecond = uniTime.GetMillisecond();

  // TODO(dsinclair): See if there is other time conversion code in pdfium and
  //   consolidate.
  int32_t mins = hour * 60 + min;
  mins -= pMgr->GetDefLocale()->GetTimeZoneInMinutes();
  while (mins > 1440) {
    mins -= 1440;
  }
  while (mins < 0) {
    mins += 1440;
  }
  hour = mins / 60;
  min = mins % 60;

  info.GetReturnValue().Set(hour * 3600000 + min * 60000 + second * 1000 +
                            milSecond + 1);
}

// static
void CFXJSE_FormCalcContext::LocalDateFmt(
    CFXJSE_HostObject* pThis,
    const v8::FunctionCallbackInfo<v8::Value>& info) {
  int32_t argc = info.Length();
  if (argc > 2) {
    ToFormCalcContext(pThis)->ThrowParamCountMismatchException("LocalDateFmt");
    return;
  }

  int32_t iStyle = 0;
  if (argc > 0) {
    v8::Local<v8::Value> infotyle = GetSimpleValue(info, 0);
    if (fxv8::IsNull(infotyle)) {
      info.GetReturnValue().SetNull();
      return;
    }
    iStyle = static_cast<int32_t>(ValueToFloat(info.GetIsolate(), infotyle));
    if (iStyle > 4 || iStyle < 0) {
      iStyle = 0;
    }
  }

  ByteString bsLocale;
  if (argc > 1) {
    v8::Local<v8::Value> argLocale = GetSimpleValue(info, 1);
    if (fxv8::IsNull(argLocale)) {
      info.GetReturnValue().SetNull();
      return;
    }
    bsLocale = ValueToUTF8String(info.GetIsolate(), argLocale);
  }

  ByteString bsFormat =
      GetLocalDateFormat(pThis, iStyle, bsLocale.AsStringView(), false);
  info.GetReturnValue().Set(
      fxv8::NewStringHelper(info.GetIsolate(), bsFormat.AsStringView()));
}

// static
void CFXJSE_FormCalcContext::LocalTimeFmt(
    CFXJSE_HostObject* pThis,
    const v8::FunctionCallbackInfo<v8::Value>& info) {
  int32_t argc = info.Length();
  if (argc > 2) {
    ToFormCalcContext(pThis)->ThrowParamCountMismatchException("LocalTimeFmt");
    return;
  }

  int32_t iStyle = 0;
  if (argc > 0) {
    v8::Local<v8::Value> infotyle = GetSimpleValue(info, 0);
    if (fxv8::IsNull(infotyle)) {
      info.GetReturnValue().SetNull();
      return;
    }
    iStyle = static_cast<int32_t>(ValueToFloat(info.GetIsolate(), infotyle));
    if (iStyle > 4 || iStyle < 0) {
      iStyle = 0;
    }
  }

  ByteString bsLocale;
  if (argc > 1) {
    v8::Local<v8::Value> argLocale = GetSimpleValue(info, 1);
    if (fxv8::IsNull(argLocale)) {
      info.GetReturnValue().SetNull();
      return;
    }
    bsLocale = ValueToUTF8String(info.GetIsolate(), argLocale);
  }

  ByteString bsFormat =
      GetLocalTimeFormat(pThis, iStyle, bsLocale.AsStringView(), false);
  info.GetReturnValue().Set(
      fxv8::NewStringHelper(info.GetIsolate(), bsFormat.AsStringView()));
}

// static
void CFXJSE_FormCalcContext::Num2Date(
    CFXJSE_HostObject* pThis,
    const v8::FunctionCallbackInfo<v8::Value>& info) {
  int32_t argc = info.Length();
  if (argc < 1 || argc > 3) {
    ToFormCalcContext(pThis)->ThrowParamCountMismatchException("Num2Date");
    return;
  }

  v8::Local<v8::Value> dateValue = GetSimpleValue(info, 0);
  if (ValueIsNull(info.GetIsolate(), dateValue)) {
    info.GetReturnValue().SetNull();
    return;
  }
  int32_t dDate =
      static_cast<int32_t>(ValueToFloat(info.GetIsolate(), dateValue));
  if (dDate < 1) {
    info.GetReturnValue().SetNull();
    return;
  }

  ByteString bsFormat;
  if (argc > 1) {
    v8::Local<v8::Value> formatValue = GetSimpleValue(info, 1);
    if (ValueIsNull(info.GetIsolate(), formatValue)) {
      info.GetReturnValue().SetNull();
      return;
    }
    bsFormat = ValueToUTF8String(info.GetIsolate(), formatValue);
  }

  ByteString bsLocale;
  if (argc > 2) {
    v8::Local<v8::Value> localeValue = GetSimpleValue(info, 2);
    if (ValueIsNull(info.GetIsolate(), localeValue)) {
      info.GetReturnValue().SetNull();
      return;
    }
    bsLocale = ValueToUTF8String(info.GetIsolate(), localeValue);
  }

  int32_t iYear = 1900;
  int32_t iMonth = 1;
  int32_t iDay = 1;
  int32_t i = 0;
  while (dDate > 0) {
    if (iMonth == 2) {
      if ((!((iYear + i) % 4) && ((iYear + i) % 100)) || !((iYear + i) % 400)) {
        if (dDate > 29) {
          ++iMonth;
          if (iMonth > 12) {
            iMonth = 1;
            ++i;
          }
          iDay = 1;
          dDate -= 29;
        } else {
          iDay += static_cast<int32_t>(dDate) - 1;
          dDate = 0;
        }
      } else {
        if (dDate > 28) {
          ++iMonth;
          if (iMonth > 12) {
            iMonth = 1;
            ++i;
          }
          iDay = 1;
          dDate -= 28;
        } else {
          iDay += static_cast<int32_t>(dDate) - 1;
          dDate = 0;
        }
      }
    } else if (iMonth < 8) {
      if ((iMonth % 2 == 0)) {
        if (dDate > 30) {
          ++iMonth;
          if (iMonth > 12) {
            iMonth = 1;
            ++i;
          }
          iDay = 1;
          dDate -= 30;
        } else {
          iDay += static_cast<int32_t>(dDate) - 1;
          dDate = 0;
        }
      } else {
        if (dDate > 31) {
          ++iMonth;
          if (iMonth > 12) {
            iMonth = 1;
            ++i;
          }
          iDay = 1;
          dDate -= 31;
        } else {
          iDay += static_cast<int32_t>(dDate) - 1;
          dDate = 0;
        }
      }
    } else {
      if (iMonth % 2 != 0) {
        if (dDate > 30) {
          ++iMonth;
          if (iMonth > 12) {
            iMonth = 1;
            ++i;
          }
          iDay = 1;
          dDate -= 30;
        } else {
          iDay += static_cast<int32_t>(dDate) - 1;
          dDate = 0;
        }
      } else {
        if (dDate > 31) {
          ++iMonth;
          if (iMonth > 12) {
            iMonth = 1;
            ++i;
          }
          iDay = 1;
          dDate -= 31;
        } else {
          iDay += static_cast<int32_t>(dDate) - 1;
          dDate = 0;
        }
      }
    }
  }

  ByteString bsLocalDate = IsoDate2Local(
      pThis,
      ByteString::Format("%d%02d%02d", iYear + i, iMonth, iDay).AsStringView(),
      bsFormat.AsStringView(), bsLocale.AsStringView());
  info.GetReturnValue().Set(
      fxv8::NewStringHelper(info.GetIsolate(), bsLocalDate.AsStringView()));
}

// static
void CFXJSE_FormCalcContext::Num2GMTime(
    CFXJSE_HostObject* pThis,
    const v8::FunctionCallbackInfo<v8::Value>& info) {
  int32_t argc = info.Length();
  if (argc < 1 || argc > 3) {
    ToFormCalcContext(pThis)->ThrowParamCountMismatchException("Num2GMTime");
    return;
  }

  v8::Local<v8::Value> timeValue = GetSimpleValue(info, 0);
  if (fxv8::IsNull(timeValue)) {
    info.GetReturnValue().SetNull();
    return;
  }
  int32_t iTime =
      static_cast<int32_t>(ValueToFloat(info.GetIsolate(), timeValue));
  if (abs(iTime) < 1.0) {
    info.GetReturnValue().SetNull();
    return;
  }

  ByteString bsFormat;
  if (argc > 1) {
    v8::Local<v8::Value> formatValue = GetSimpleValue(info, 1);
    if (fxv8::IsNull(formatValue)) {
      info.GetReturnValue().SetNull();
      return;
    }
    bsFormat = ValueToUTF8String(info.GetIsolate(), formatValue);
  }

  ByteString bsLocale;
  if (argc > 2) {
    v8::Local<v8::Value> localeValue = GetSimpleValue(info, 2);
    if (fxv8::IsNull(localeValue)) {
      info.GetReturnValue().SetNull();
      return;
    }
    bsLocale = ValueToUTF8String(info.GetIsolate(), localeValue);
  }

  ByteString bsGMTTime = Num2AllTime(pThis, iTime, bsFormat.AsStringView(),
                                     bsLocale.AsStringView(), true);
  info.GetReturnValue().Set(
      fxv8::NewStringHelper(info.GetIsolate(), bsGMTTime.AsStringView()));
}

// static
void CFXJSE_FormCalcContext::Num2Time(
    CFXJSE_HostObject* pThis,
    const v8::FunctionCallbackInfo<v8::Value>& info) {
  int32_t argc = info.Length();
  if (argc < 1 || argc > 3) {
    ToFormCalcContext(pThis)->ThrowParamCountMismatchException("Num2Time");
    return;
  }

  v8::Local<v8::Value> timeValue = GetSimpleValue(info, 0);
  if (fxv8::IsNull(timeValue)) {
    info.GetReturnValue().SetNull();
    return;
  }
  float fTime = ValueToFloat(info.GetIsolate(), timeValue);
  if (fabs(fTime) < 1.0) {
    info.GetReturnValue().SetNull();
    return;
  }

  ByteString bsFormat;
  if (argc > 1) {
    v8::Local<v8::Value> formatValue = GetSimpleValue(info, 1);
    if (fxv8::IsNull(formatValue)) {
      info.GetReturnValue().SetNull();
      return;
    }
    bsFormat = ValueToUTF8String(info.GetIsolate(), formatValue);
  }

  ByteString bsLocale;
  if (argc > 2) {
    v8::Local<v8::Value> localeValue = GetSimpleValue(info, 2);
    if (fxv8::IsNull(localeValue)) {
      info.GetReturnValue().SetNull();
      return;
    }
    bsLocale = ValueToUTF8String(info.GetIsolate(), localeValue);
  }

  ByteString bsLocalTime =
      Num2AllTime(pThis, static_cast<int32_t>(fTime), bsFormat.AsStringView(),
                  bsLocale.AsStringView(), false);
  info.GetReturnValue().Set(
      fxv8::NewStringHelper(info.GetIsolate(), bsLocalTime.AsStringView()));
}

// static
void CFXJSE_FormCalcContext::Time(
    CFXJSE_HostObject* pThis,
    const v8::FunctionCallbackInfo<v8::Value>& info) {
  if (info.Length() != 0) {
    ToFormCalcContext(pThis)->ThrowParamCountMismatchException("Time");
    return;
  }

  time_t now;
  FXSYS_time(&now);
  struct tm* pGmt = gmtime(&now);
  info.GetReturnValue().Set(
      (pGmt->tm_hour * 3600 + pGmt->tm_min * 60 + pGmt->tm_sec) * 1000);
}

// static
void CFXJSE_FormCalcContext::Time2Num(
    CFXJSE_HostObject* pThis,
    const v8::FunctionCallbackInfo<v8::Value>& info) {
  int32_t argc = info.Length();
  if (argc < 1 || argc > 3) {
    ToFormCalcContext(pThis)->ThrowParamCountMismatchException("Time2Num");
    return;
  }

  ByteString bsTime;
  v8::Local<v8::Value> timeValue = GetSimpleValue(info, 0);
  if (ValueIsNull(info.GetIsolate(), timeValue)) {
    info.GetReturnValue().SetNull();
    return;
  }
  bsTime = ValueToUTF8String(info.GetIsolate(), timeValue);

  ByteString bsFormat;
  if (argc > 1) {
    v8::Local<v8::Value> formatValue = GetSimpleValue(info, 1);
    if (ValueIsNull(info.GetIsolate(), formatValue)) {
      info.GetReturnValue().SetNull();
      return;
    }
    bsFormat = ValueToUTF8String(info.GetIsolate(), formatValue);
  }

  ByteString bsLocale;
  if (argc > 2) {
    v8::Local<v8::Value> localeValue = GetSimpleValue(info, 2);
    if (ValueIsNull(info.GetIsolate(), localeValue)) {
      info.GetReturnValue().SetNull();
      return;
    }
    bsLocale = ValueToUTF8String(info.GetIsolate(), localeValue);
  }

  CXFA_Document* doc = ToFormCalcContext(pThis)->GetDocument();
  CXFA_LocaleMgr* pMgr = doc->GetLocaleMgr();
  GCedLocaleIface* pLocale = nullptr;
  if (!bsLocale.IsEmpty()) {
    pLocale =
        pMgr->GetLocaleByName(WideString::FromUTF8(bsLocale.AsStringView()));
  }
  if (!pLocale) {
    CXFA_Node* pThisNode = ToNode(doc->GetScriptContext()->GetThisObject());
    pLocale = pThisNode->GetLocale();
  }

  WideString wsFormat;
  if (bsFormat.IsEmpty()) {
    if (pLocale) {
      wsFormat =
          pLocale->GetTimePattern(LocaleIface::DateTimeSubcategory::kDefault);
    }
  } else {
    wsFormat = WideString::FromUTF8(bsFormat.AsStringView());
  }
  wsFormat = L"time{" + wsFormat + L"}";
  CXFA_LocaleValue localeValue(CXFA_LocaleValue::ValueType::kTime,
                               WideString::FromUTF8(bsTime.AsStringView()),
                               wsFormat, pLocale, pMgr);
  if (!localeValue.IsValid()) {
    info.GetReturnValue().Set(0);
    return;
  }

  CFX_DateTime uniTime = localeValue.GetTime();
  int32_t hour = uniTime.GetHour();
  int32_t minute = uniTime.GetMinute();
  const int32_t second = uniTime.GetSecond();
  const int32_t millisecond = uniTime.GetMillisecond();

  static constexpr int kMinutesInDay = 24 * 60;
  int32_t minutes_with_tz =
      hour * 60 + minute - CXFA_TimeZoneProvider().GetTimeZoneInMinutes();
  minutes_with_tz %= kMinutesInDay;
  if (minutes_with_tz < 0) {
    minutes_with_tz += kMinutesInDay;
  }

  hour = minutes_with_tz / 60;
  minute = minutes_with_tz % 60;
  info.GetReturnValue().Set(hour * 3600000 + minute * 60000 + second * 1000 +
                            millisecond + 1);
}

// static
void CFXJSE_FormCalcContext::TimeFmt(
    CFXJSE_HostObject* pThis,
    const v8::FunctionCallbackInfo<v8::Value>& info) {
  int32_t argc = info.Length();
  if (argc > 2) {
    ToFormCalcContext(pThis)->ThrowParamCountMismatchException("TimeFmt");
    return;
  }

  int32_t iStyle = 0;
  if (argc > 0) {
    v8::Local<v8::Value> infotyle = GetSimpleValue(info, 0);
    if (fxv8::IsNull(infotyle)) {
      info.GetReturnValue().SetNull();
      return;
    }
    iStyle = static_cast<int32_t>(ValueToFloat(info.GetIsolate(), infotyle));
    if (iStyle > 4 || iStyle < 0) {
      iStyle = 0;
    }
  }

  ByteString bsLocale;
  if (argc > 1) {
    v8::Local<v8::Value> argLocale = GetSimpleValue(info, 1);
    if (fxv8::IsNull(argLocale)) {
      info.GetReturnValue().SetNull();
      return;
    }
    bsLocale = ValueToUTF8String(info.GetIsolate(), argLocale);
  }

  ByteString bsFormat =
      GetStandardTimeFormat(pThis, iStyle, bsLocale.AsStringView());
  info.GetReturnValue().Set(
      fxv8::NewStringHelper(info.GetIsolate(), bsFormat.AsStringView()));
}

// static
ByteString CFXJSE_FormCalcContext::Local2IsoDate(CFXJSE_HostObject* pThis,
                                                 ByteStringView bsDate,
                                                 ByteStringView bsFormat,
                                                 ByteStringView bsLocale) {
  CXFA_Document* doc = ToFormCalcContext(pThis)->GetDocument();
  if (!doc) {
    return ByteString();
  }

  CXFA_LocaleMgr* pMgr = doc->GetLocaleMgr();
  GCedLocaleIface* pLocale = LocaleFromString(doc, pMgr, bsLocale);
  if (!pLocale) {
    return ByteString();
  }

  WideString wsFormat = FormatFromString(pLocale, bsFormat);
  CFX_DateTime dt =
      CXFA_LocaleValue(CXFA_LocaleValue::ValueType::kDate,
                       WideString::FromUTF8(bsDate), wsFormat, pLocale, pMgr)
          .GetDate();

  return ByteString::Format("%4d-%02d-%02d", dt.GetYear(), dt.GetMonth(),
                            dt.GetDay());
}

// static
ByteString CFXJSE_FormCalcContext::IsoDate2Local(CFXJSE_HostObject* pThis,
                                                 ByteStringView bsDate,
                                                 ByteStringView bsFormat,
                                                 ByteStringView bsLocale) {
  CXFA_Document* doc = ToFormCalcContext(pThis)->GetDocument();
  if (!doc) {
    return ByteString();
  }

  CXFA_LocaleMgr* pMgr = doc->GetLocaleMgr();
  GCedLocaleIface* pLocale = LocaleFromString(doc, pMgr, bsLocale);
  if (!pLocale) {
    return ByteString();
  }

  WideString wsFormat = FormatFromString(pLocale, bsFormat);
  WideString wsRet;
  CXFA_LocaleValue(CXFA_LocaleValue::ValueType::kDate,
                   WideString::FromUTF8(bsDate), pMgr)
      .FormatPatterns(wsRet, wsFormat, pLocale, XFA_ValuePicture::kDisplay);
  return wsRet.ToUTF8();
}

// static
ByteString CFXJSE_FormCalcContext::IsoTime2Local(CFXJSE_HostObject* pThis,
                                                 ByteStringView bsTime,
                                                 ByteStringView bsFormat,
                                                 ByteStringView bsLocale) {
  CXFA_Document* doc = ToFormCalcContext(pThis)->GetDocument();
  if (!doc) {
    return ByteString();
  }

  CXFA_LocaleMgr* pMgr = doc->GetLocaleMgr();
  GCedLocaleIface* pLocale = LocaleFromString(doc, pMgr, bsLocale);
  if (!pLocale) {
    return ByteString();
  }

  WideString wsFormat = {
      L"time{", FormatFromString(pLocale, bsFormat).AsStringView(), L"}"};
  CXFA_LocaleValue widgetValue(CXFA_LocaleValue::ValueType::kTime,
                               WideString::FromUTF8(bsTime), pMgr);
  WideString wsRet;
  widgetValue.FormatPatterns(wsRet, wsFormat, pLocale,
                             XFA_ValuePicture::kDisplay);
  return wsRet.ToUTF8();
}

// static
ByteString CFXJSE_FormCalcContext::GetLocalDateFormat(CFXJSE_HostObject* pThis,
                                                      int32_t iStyle,
                                                      ByteStringView bsLocale,
                                                      bool bStandard) {
  CXFA_Document* doc = ToFormCalcContext(pThis)->GetDocument();
  if (!doc) {
    return ByteString();
  }

  return GetLocalDateTimeFormat(doc, iStyle, bsLocale, bStandard,
                                /*bIsDate=*/true);
}

// static
ByteString CFXJSE_FormCalcContext::GetLocalTimeFormat(CFXJSE_HostObject* pThis,
                                                      int32_t iStyle,
                                                      ByteStringView bsLocale,
                                                      bool bStandard) {
  CXFA_Document* doc = ToFormCalcContext(pThis)->GetDocument();
  if (!doc) {
    return ByteString();
  }

  return GetLocalDateTimeFormat(doc, iStyle, bsLocale, bStandard,
                                /*bIsDate=*/false);
}

// static
ByteString CFXJSE_FormCalcContext::GetStandardDateFormat(
    CFXJSE_HostObject* pThis,
    int32_t iStyle,
    ByteStringView bsLocale) {
  return GetLocalDateFormat(pThis, iStyle, bsLocale, true);
}

// static
ByteString CFXJSE_FormCalcContext::GetStandardTimeFormat(
    CFXJSE_HostObject* pThis,
    int32_t iStyle,
    ByteStringView bsLocale) {
  return GetLocalTimeFormat(pThis, iStyle, bsLocale, true);
}

// static
ByteString CFXJSE_FormCalcContext::Num2AllTime(CFXJSE_HostObject* pThis,
                                               int32_t iTime,
                                               ByteStringView bsFormat,
                                               ByteStringView bsLocale,
                                               bool bGM) {
  int32_t iHour = 0;
  int32_t iMin = 0;
  int32_t iSec = 0;
  iHour = static_cast<int>(iTime) / 3600000;
  iMin = (static_cast<int>(iTime) - iHour * 3600000) / 60000;
  iSec = (static_cast<int>(iTime) - iHour * 3600000 - iMin * 60000) / 1000;

  if (!bGM) {
    int32_t iZoneHour;
    int32_t iZoneMin;
    int32_t iZoneSec;
    GetLocalTimeZone(&iZoneHour, &iZoneMin, &iZoneSec);
    iHour += iZoneHour;
    iMin += iZoneMin;
    iSec += iZoneSec;
  }

  return IsoTime2Local(
      pThis,
      ByteString::Format("%02d:%02d:%02d", iHour, iMin, iSec).AsStringView(),
      bsFormat, bsLocale);
}

// static
void CFXJSE_FormCalcContext::Apr(
    CFXJSE_HostObject* pThis,
    const v8::FunctionCallbackInfo<v8::Value>& info) {
  CFXJSE_FormCalcContext* context = ToFormCalcContext(pThis);
  if (info.Length() != 3) {
    context->ThrowParamCountMismatchException("Apr");
    return;
  }

  v8::Local<v8::Value> argOne = GetSimpleValue(info, 0);
  v8::Local<v8::Value> argTwo = GetSimpleValue(info, 1);
  v8::Local<v8::Value> argThree = GetSimpleValue(info, 2);
  if (ValueIsNull(info.GetIsolate(), argOne) ||
      ValueIsNull(info.GetIsolate(), argTwo) ||
      ValueIsNull(info.GetIsolate(), argThree)) {
    info.GetReturnValue().SetNull();
    return;
  }

  double nPrincipal = ValueToDouble(info.GetIsolate(), argOne);
  double nPayment = ValueToDouble(info.GetIsolate(), argTwo);
  int nPeriods = GetValidatedPaymentPeriods(info.GetIsolate(), argThree);
  if (nPrincipal <= 0 || nPayment <= 0 || nPeriods == 0) {
    context->ThrowArgumentMismatchException();
    return;
  }

  double r = 2 * (nPeriods * nPayment - nPrincipal) / (nPeriods * nPrincipal);
  double nTemp = 1;
  for (int32_t i = 0; i < nPeriods; ++i) {
    nTemp *= (1 + r);
  }

  double nRet = r * nTemp / (nTemp - 1) - nPayment / nPrincipal;
  while (fabs(nRet) > kFinancialPrecision) {
    double nDerivative =
        ((nTemp + r * nPeriods * (nTemp / (1 + r))) * (nTemp - 1) -
         (r * nTemp * nPeriods * (nTemp / (1 + r)))) /
        ((nTemp - 1) * (nTemp - 1));
    if (nDerivative == 0) {
      info.GetReturnValue().SetNull();
      return;
    }

    r = r - nRet / nDerivative;
    nTemp = 1;
    for (int32_t i = 0; i < nPeriods; ++i) {
      nTemp *= (1 + r);
    }
    nRet = r * nTemp / (nTemp - 1) - nPayment / nPrincipal;
  }
  info.GetReturnValue().Set(r * 12);
}

// static
void CFXJSE_FormCalcContext::CTerm(
    CFXJSE_HostObject* pThis,
    const v8::FunctionCallbackInfo<v8::Value>& info) {
  CFXJSE_FormCalcContext* context = ToFormCalcContext(pThis);
  if (info.Length() != 3) {
    context->ThrowParamCountMismatchException("CTerm");
    return;
  }

  v8::Local<v8::Value> argOne = GetSimpleValue(info, 0);
  v8::Local<v8::Value> argTwo = GetSimpleValue(info, 1);
  v8::Local<v8::Value> argThree = GetSimpleValue(info, 2);
  if (ValueIsNull(info.GetIsolate(), argOne) ||
      ValueIsNull(info.GetIsolate(), argTwo) ||
      ValueIsNull(info.GetIsolate(), argThree)) {
    info.GetReturnValue().SetNull();
    return;
  }

  float nRate = ValueToFloat(info.GetIsolate(), argOne);
  float nFutureValue = ValueToFloat(info.GetIsolate(), argTwo);
  float nInitAmount = ValueToFloat(info.GetIsolate(), argThree);
  if ((nRate <= 0) || (nFutureValue <= 0) || (nInitAmount <= 0)) {
    context->ThrowArgumentMismatchException();
    return;
  }

  info.GetReturnValue().Set(log((float)(nFutureValue / nInitAmount)) /
                            log((float)(1 + nRate)));
}

// static
void CFXJSE_FormCalcContext::FV(
    CFXJSE_HostObject* pThis,
    const v8::FunctionCallbackInfo<v8::Value>& info) {
  CFXJSE_FormCalcContext* context = ToFormCalcContext(pThis);
  if (info.Length() != 3) {
    context->ThrowParamCountMismatchException("FV");
    return;
  }

  v8::Local<v8::Value> argOne = GetSimpleValue(info, 0);
  v8::Local<v8::Value> argTwo = GetSimpleValue(info, 1);
  v8::Local<v8::Value> argThree = GetSimpleValue(info, 2);
  if (ValueIsNull(info.GetIsolate(), argOne) ||
      ValueIsNull(info.GetIsolate(), argTwo) ||
      ValueIsNull(info.GetIsolate(), argThree)) {
    info.GetReturnValue().SetNull();
    return;
  }

  double nAmount = ValueToDouble(info.GetIsolate(), argOne);
  double nRate = ValueToDouble(info.GetIsolate(), argTwo);
  int nPeriods = GetValidatedPaymentPeriods(info.GetIsolate(), argThree);
  if (nAmount <= 0 || nRate < 0 || nPeriods == 0) {
    context->ThrowArgumentMismatchException();
    return;
  }

  double dResult = 0;
  if (nRate) {
    double nTemp = 1;
    for (int i = 0; i < nPeriods; ++i) {
      nTemp *= 1 + nRate;
    }
    dResult = nAmount * (nTemp - 1) / nRate;
  } else {
    dResult = nAmount * nPeriods;
  }

  info.GetReturnValue().Set(dResult);
}

// static
void CFXJSE_FormCalcContext::IPmt(
    CFXJSE_HostObject* pThis,
    const v8::FunctionCallbackInfo<v8::Value>& info) {
  CFXJSE_FormCalcContext* context = ToFormCalcContext(pThis);
  if (info.Length() != 5) {
    context->ThrowParamCountMismatchException("IPmt");
    return;
  }

  v8::Local<v8::Value> argOne = GetSimpleValue(info, 0);
  v8::Local<v8::Value> argTwo = GetSimpleValue(info, 1);
  v8::Local<v8::Value> argThree = GetSimpleValue(info, 2);
  v8::Local<v8::Value> argFour = GetSimpleValue(info, 3);
  v8::Local<v8::Value> argFive = GetSimpleValue(info, 4);
  if (ValueIsNull(info.GetIsolate(), argOne) ||
      ValueIsNull(info.GetIsolate(), argTwo) ||
      ValueIsNull(info.GetIsolate(), argThree) ||
      ValueIsNull(info.GetIsolate(), argFour) ||
      ValueIsNull(info.GetIsolate(), argFive)) {
    info.GetReturnValue().SetNull();
    return;
  }

  float nPrincipalAmount = ValueToFloat(info.GetIsolate(), argOne);
  float nRate = ValueToFloat(info.GetIsolate(), argTwo);
  float nPayment = ValueToFloat(info.GetIsolate(), argThree);
  float nFirstMonth = ValueToFloat(info.GetIsolate(), argFour);
  float nNumberOfMonths = ValueToFloat(info.GetIsolate(), argFive);
  if ((nPrincipalAmount <= 0) || (nRate <= 0) || (nPayment <= 0) ||
      (nFirstMonth < 0) || (nNumberOfMonths < 0)) {
    context->ThrowArgumentMismatchException();
    return;
  }

  float nRateOfMonth = nRate / 12;
  int32_t iNums = static_cast<int32_t>(
      (log10((float)(nPayment / nPrincipalAmount)) -
       log10((float)(nPayment / nPrincipalAmount - nRateOfMonth))) /
      log10((float)(1 + nRateOfMonth)));
  int32_t iEnd =
      std::min(static_cast<int32_t>(nFirstMonth + nNumberOfMonths - 1), iNums);

  if (nPayment < nPrincipalAmount * nRateOfMonth) {
    info.GetReturnValue().Set(0);
    return;
  }

  int32_t i = 0;
  for (i = 0; i < nFirstMonth - 1; ++i) {
    nPrincipalAmount -= nPayment - nPrincipalAmount * nRateOfMonth;
  }

  float nSum = 0;
  for (; i < iEnd; ++i) {
    nSum += nPrincipalAmount * nRateOfMonth;
    nPrincipalAmount -= nPayment - nPrincipalAmount * nRateOfMonth;
  }
  info.GetReturnValue().Set(nSum);
}

// static
void CFXJSE_FormCalcContext::NPV(
    CFXJSE_HostObject* pThis,
    const v8::FunctionCallbackInfo<v8::Value>& info) {
  CFXJSE_FormCalcContext* context = ToFormCalcContext(pThis);
  int32_t argc = info.Length();
  if (argc < 2) {
    context->ThrowParamCountMismatchException("NPV");
    return;
  }

  v8::Local<v8::Value> argValue = GetSimpleValue(info, 0);
  if (ValueIsNull(info.GetIsolate(), argValue)) {
    info.GetReturnValue().SetNull();
    return;
  }

  double nRate = ValueToDouble(info.GetIsolate(), argValue);
  if (nRate <= 0) {
    context->ThrowArgumentMismatchException();
    return;
  }

  std::vector<double> data;
  for (int32_t i = 1; i < argc; i++) {
    argValue = GetSimpleValue(info, i);
    if (ValueIsNull(info.GetIsolate(), argValue)) {
      info.GetReturnValue().SetNull();
      return;
    }
    data.push_back(ValueToDouble(info.GetIsolate(), argValue));
  }

  double nSum = 0;
  double nDivisor = 1.0 + nRate;
  while (!data.empty()) {
    nSum += data.back();
    nSum /= nDivisor;
    data.pop_back();
  }
  info.GetReturnValue().Set(nSum);
}

// static
void CFXJSE_FormCalcContext::Pmt(
    CFXJSE_HostObject* pThis,
    const v8::FunctionCallbackInfo<v8::Value>& info) {
  CFXJSE_FormCalcContext* context = ToFormCalcContext(pThis);
  if (info.Length() != 3) {
    context->ThrowParamCountMismatchException("Pmt");
    return;
  }

  v8::Local<v8::Value> argOne = GetSimpleValue(info, 0);
  v8::Local<v8::Value> argTwo = GetSimpleValue(info, 1);
  v8::Local<v8::Value> argThree = GetSimpleValue(info, 2);
  if (ValueIsNull(info.GetIsolate(), argOne) ||
      ValueIsNull(info.GetIsolate(), argTwo) ||
      ValueIsNull(info.GetIsolate(), argThree)) {
    info.GetReturnValue().SetNull();
    return;
  }

  double nPrincipal = ValueToDouble(info.GetIsolate(), argOne);
  double nRate = ValueToDouble(info.GetIsolate(), argTwo);
  int nPeriods = GetValidatedPaymentPeriods(info.GetIsolate(), argThree);
  if (nPrincipal <= 0 || nRate <= 0 || nPeriods == 0) {
    context->ThrowArgumentMismatchException();
    return;
  }

  double nSum = pow(1.0 + nRate, nPeriods);
  info.GetReturnValue().Set((nPrincipal * nRate * nSum) / (nSum - 1));
}

// static
void CFXJSE_FormCalcContext::PPmt(
    CFXJSE_HostObject* pThis,
    const v8::FunctionCallbackInfo<v8::Value>& info) {
  CFXJSE_FormCalcContext* context = ToFormCalcContext(pThis);
  if (info.Length() != 5) {
    context->ThrowParamCountMismatchException("PPmt");
    return;
  }

  v8::Local<v8::Value> argOne = GetSimpleValue(info, 0);
  v8::Local<v8::Value> argTwo = GetSimpleValue(info, 1);
  v8::Local<v8::Value> argThree = GetSimpleValue(info, 2);
  v8::Local<v8::Value> argFour = GetSimpleValue(info, 3);
  v8::Local<v8::Value> argFive = GetSimpleValue(info, 4);
  if (ValueIsNull(info.GetIsolate(), argOne) ||
      ValueIsNull(info.GetIsolate(), argTwo) ||
      ValueIsNull(info.GetIsolate(), argThree) ||
      ValueIsNull(info.GetIsolate(), argFour) ||
      ValueIsNull(info.GetIsolate(), argFive)) {
    info.GetReturnValue().SetNull();
    return;
  }

  float nPrincipalAmount = ValueToFloat(info.GetIsolate(), argOne);
  float nRate = ValueToFloat(info.GetIsolate(), argTwo);
  float nPayment = ValueToFloat(info.GetIsolate(), argThree);
  float nFirstMonth = ValueToFloat(info.GetIsolate(), argFour);
  float nNumberOfMonths = ValueToFloat(info.GetIsolate(), argFive);
  if ((nPrincipalAmount <= 0) || (nRate <= 0) || (nPayment <= 0) ||
      (nFirstMonth < 0) || (nNumberOfMonths < 0)) {
    context->ThrowArgumentMismatchException();
    return;
  }

  float nRateOfMonth = nRate / 12;
  int32_t iNums = static_cast<int32_t>(
      (log10((float)(nPayment / nPrincipalAmount)) -
       log10((float)(nPayment / nPrincipalAmount - nRateOfMonth))) /
      log10((float)(1 + nRateOfMonth)));
  int32_t iEnd =
      std::min(static_cast<int32_t>(nFirstMonth + nNumberOfMonths - 1), iNums);
  if (nPayment < nPrincipalAmount * nRateOfMonth) {
    context->ThrowArgumentMismatchException();
    return;
  }

  int32_t i = 0;
  for (i = 0; i < nFirstMonth - 1; ++i) {
    nPrincipalAmount -= nPayment - nPrincipalAmount * nRateOfMonth;
  }

  float nTemp = 0;
  float nSum = 0;
  for (; i < iEnd; ++i) {
    nTemp = nPayment - nPrincipalAmount * nRateOfMonth;
    nSum += nTemp;
    nPrincipalAmount -= nTemp;
  }
  info.GetReturnValue().Set(nSum);
}

// static
void CFXJSE_FormCalcContext::PV(
    CFXJSE_HostObject* pThis,
    const v8::FunctionCallbackInfo<v8::Value>& info) {
  CFXJSE_FormCalcContext* context = ToFormCalcContext(pThis);
  if (info.Length() != 3) {
    context->ThrowParamCountMismatchException("PV");
    return;
  }

  v8::Local<v8::Value> argOne = GetSimpleValue(info, 0);
  v8::Local<v8::Value> argTwo = GetSimpleValue(info, 1);
  v8::Local<v8::Value> argThree = GetSimpleValue(info, 2);
  if (ValueIsNull(info.GetIsolate(), argOne) ||
      ValueIsNull(info.GetIsolate(), argTwo) ||
      ValueIsNull(info.GetIsolate(), argThree)) {
    info.GetReturnValue().SetNull();
    return;
  }

  double nAmount = ValueToDouble(info.GetIsolate(), argOne);
  double nRate = ValueToDouble(info.GetIsolate(), argTwo);
  int nPeriods = GetValidatedPaymentPeriods(info.GetIsolate(), argThree);
  if (nAmount <= 0 || nRate < 0 || nPeriods == 0) {
    context->ThrowArgumentMismatchException();
    return;
  }

  double nTemp = 1 / pow(1.0 + nRate, nPeriods);
  info.GetReturnValue().Set(nAmount * ((1.0 - nTemp) / nRate));
}

// static
void CFXJSE_FormCalcContext::Rate(
    CFXJSE_HostObject* pThis,
    const v8::FunctionCallbackInfo<v8::Value>& info) {
  CFXJSE_FormCalcContext* context = ToFormCalcContext(pThis);
  if (info.Length() != 3) {
    context->ThrowParamCountMismatchException("Rate");
    return;
  }

  v8::Local<v8::Value> argOne = GetSimpleValue(info, 0);
  v8::Local<v8::Value> argTwo = GetSimpleValue(info, 1);
  v8::Local<v8::Value> argThree = GetSimpleValue(info, 2);
  if (ValueIsNull(info.GetIsolate(), argOne) ||
      ValueIsNull(info.GetIsolate(), argTwo) ||
      ValueIsNull(info.GetIsolate(), argThree)) {
    info.GetReturnValue().SetNull();
    return;
  }

  float nFuture = ValueToFloat(info.GetIsolate(), argOne);
  float nPresent = ValueToFloat(info.GetIsolate(), argTwo);
  int nPeriods = GetValidatedPaymentPeriods(info.GetIsolate(), argThree);
  if (nFuture <= 0 || nPresent < 0 || nPeriods == 0) {
    context->ThrowArgumentMismatchException();
    return;
  }

  info.GetReturnValue().Set(powf(nFuture / nPresent, 1.0f / nPeriods) - 1.0f);
}

// static
void CFXJSE_FormCalcContext::Term(
    CFXJSE_HostObject* pThis,
    const v8::FunctionCallbackInfo<v8::Value>& info) {
  CFXJSE_FormCalcContext* context = ToFormCalcContext(pThis);
  if (info.Length() != 3) {
    context->ThrowParamCountMismatchException("Term");
    return;
  }

  v8::Local<v8::Value> argOne = GetSimpleValue(info, 0);
  v8::Local<v8::Value> argTwo = GetSimpleValue(info, 1);
  v8::Local<v8::Value> argThree = GetSimpleValue(info, 2);
  if (ValueIsNull(info.GetIsolate(), argOne) ||
      ValueIsNull(info.GetIsolate(), argTwo) ||
      ValueIsNull(info.GetIsolate(), argThree)) {
    info.GetReturnValue().SetNull();
    return;
  }

  float nMount = ValueToFloat(info.GetIsolate(), argOne);
  float nRate = ValueToFloat(info.GetIsolate(), argTwo);
  float nFuture = ValueToFloat(info.GetIsolate(), argThree);
  if ((nMount <= 0) || (nRate <= 0) || (nFuture <= 0)) {
    context->ThrowArgumentMismatchException();
    return;
  }

  info.GetReturnValue().Set(log((float)(nFuture / nMount * nRate) + 1) /
                            log((float)(1 + nRate)));
}

// static
void CFXJSE_FormCalcContext::Choose(
    CFXJSE_HostObject* pThis,
    const v8::FunctionCallbackInfo<v8::Value>& info) {
  CFXJSE_FormCalcContext* context = ToFormCalcContext(pThis);
  int32_t argc = info.Length();
  if (argc < 2) {
    context->ThrowParamCountMismatchException("Choose");
    return;
  }

  if (ValueIsNull(info.GetIsolate(), info[0])) {
    info.GetReturnValue().SetNull();
    return;
  }

  int32_t index =
      static_cast<int32_t>(ValueToFloat(info.GetIsolate(), info[0]));
  if (index < 1) {
    info.GetReturnValue().SetEmptyString();
    return;
  }

  bool bFound = false;
  bool bStopCounterFlags = false;
  int32_t iArgIndex = 1;
  int32_t iValueIndex = 0;
  while (!bFound && !bStopCounterFlags && (iArgIndex < argc)) {
    v8::Local<v8::Value> argIndexValue = info[iArgIndex];
    if (fxv8::IsArray(argIndexValue)) {
      v8::Local<v8::Array> arr = argIndexValue.As<v8::Array>();
      uint32_t iLength = fxv8::GetArrayLengthHelper(arr);
      if (iLength > 3) {
        bStopCounterFlags = true;
      }

      iValueIndex += (iLength - 2);
      if (iValueIndex >= index) {
        v8::Local<v8::Value> propertyValue =
            fxv8::ReentrantGetArrayElementHelper(info.GetIsolate(), arr, 1);
        v8::Local<v8::Value> jsValue = fxv8::ReentrantGetArrayElementHelper(
            info.GetIsolate(), arr, (iLength - 1) - (iValueIndex - index));
        v8::Local<v8::Value> newPropertyValue;
        if (fxv8::IsObject(jsValue)) {
          v8::Local<v8::Object> jsObjectValue = jsValue.As<v8::Object>();
          if (fxv8::IsNull(propertyValue)) {
            newPropertyValue =
                GetObjectDefaultValue(info.GetIsolate(), jsObjectValue);
          } else {
            ByteString bsName = fxv8::ReentrantToByteStringHelper(
                info.GetIsolate(), propertyValue);
            newPropertyValue = fxv8::ReentrantGetObjectPropertyHelper(
                info.GetIsolate(), jsObjectValue, bsName.AsStringView());
          }
        }
        ByteString bsChosen =
            ValueToUTF8String(info.GetIsolate(), newPropertyValue);
        info.GetReturnValue().Set(
            fxv8::NewStringHelper(info.GetIsolate(), bsChosen.AsStringView()));
        bFound = true;
      }
    } else {
      iValueIndex++;
      if (iValueIndex == index) {
        ByteString bsChosen =
            ValueToUTF8String(info.GetIsolate(), argIndexValue);
        info.GetReturnValue().Set(
            fxv8::NewStringHelper(info.GetIsolate(), bsChosen.AsStringView()));
        bFound = true;
      }
    }
    iArgIndex++;
  }
  if (!bFound) {
    info.GetReturnValue().SetEmptyString();
  }
}

// static
void CFXJSE_FormCalcContext::Exists(
    CFXJSE_HostObject* pThis,
    const v8::FunctionCallbackInfo<v8::Value>& info) {
  if (info.Length() != 1) {
    ToFormCalcContext(pThis)->ThrowParamCountMismatchException("Exists");
    return;
  }
  info.GetReturnValue().Set(fxv8::IsObject(info[0]));
}

// static
void CFXJSE_FormCalcContext::HasValue(
    CFXJSE_HostObject* pThis,
    const v8::FunctionCallbackInfo<v8::Value>& info) {
  if (info.Length() != 1) {
    ToFormCalcContext(pThis)->ThrowParamCountMismatchException("HasValue");
    return;
  }

  v8::Local<v8::Value> argOne = GetSimpleValue(info, 0);
  if (!fxv8::IsString(argOne)) {
    info.GetReturnValue().Set(
        static_cast<int>(fxv8::IsNumber(argOne) || fxv8::IsBoolean(argOne)));
    return;
  }

  ByteString bsValue =
      fxv8::ReentrantToByteStringHelper(info.GetIsolate(), argOne);
  bsValue.TrimWhitespaceFront();
  info.GetReturnValue().Set(static_cast<int>(!bsValue.IsEmpty()));
}

// static
void CFXJSE_FormCalcContext::Oneof(
    CFXJSE_HostObject* pThis,
    const v8::FunctionCallbackInfo<v8::Value>& info) {
  if (info.Length() < 2) {
    ToFormCalcContext(pThis)->ThrowParamCountMismatchException("Oneof");
    return;
  }

  v8::Local<v8::Value> argOne = GetSimpleValue(info, 0);
  for (const auto& value : UnfoldArgs(info)) {
    if (SimpleValueCompare(info.GetIsolate(), argOne, value)) {
      info.GetReturnValue().Set(1);
      return;
    }
  }
  info.GetReturnValue().Set(0);
}

// static
void CFXJSE_FormCalcContext::Within(
    CFXJSE_HostObject* pThis,
    const v8::FunctionCallbackInfo<v8::Value>& info) {
  if (info.Length() != 3) {
    ToFormCalcContext(pThis)->ThrowParamCountMismatchException("Within");
    return;
  }

  v8::Local<v8::Value> argOne = GetSimpleValue(info, 0);
  if (fxv8::IsNull(argOne)) {
    info.GetReturnValue().SetUndefined();
    return;
  }

  v8::Local<v8::Value> argLow = GetSimpleValue(info, 1);
  v8::Local<v8::Value> argHigh = GetSimpleValue(info, 2);
  if (fxv8::IsNumber(argOne)) {
    float oneNumber = ValueToFloat(info.GetIsolate(), argOne);
    float lowNumber = ValueToFloat(info.GetIsolate(), argLow);
    float heightNumber = ValueToFloat(info.GetIsolate(), argHigh);
    info.GetReturnValue().Set(static_cast<int>((oneNumber >= lowNumber) &&
                                               (oneNumber <= heightNumber)));
    return;
  }

  ByteString bsOne = ValueToUTF8String(info.GetIsolate(), argOne);
  ByteString bsLow = ValueToUTF8String(info.GetIsolate(), argLow);
  ByteString bsHeight = ValueToUTF8String(info.GetIsolate(), argHigh);
  info.GetReturnValue().Set(
      static_cast<int>((bsOne.Compare(bsLow.AsStringView()) >= 0) &&
                       (bsOne.Compare(bsHeight.AsStringView()) <= 0)));
}

// static
void CFXJSE_FormCalcContext::If(
    CFXJSE_HostObject* pThis,
    const v8::FunctionCallbackInfo<v8::Value>& info) {
  if (info.Length() != 3) {
    ToFormCalcContext(pThis)->ThrowParamCountMismatchException("If");
    return;
  }

  const bool condition = fxv8::ReentrantToBooleanHelper(
      info.GetIsolate(), GetSimpleValue(info, 0));

  info.GetReturnValue().Set(GetSimpleValue(info, condition ? 1 : 2));
}

// static
void CFXJSE_FormCalcContext::Eval(
    CFXJSE_HostObject* pThis,
    const v8::FunctionCallbackInfo<v8::Value>& info) {
  CFXJSE_FormCalcContext* context = ToFormCalcContext(pThis);
  if (info.Length() != 1) {
    context->ThrowParamCountMismatchException("Eval");
    return;
  }

  v8::Isolate* pIsolate = context->GetIsolate();
  v8::Local<v8::Value> scriptValue = GetSimpleValue(info, 0);
  ByteString bsUtf8Script = ValueToUTF8String(info.GetIsolate(), scriptValue);
  if (bsUtf8Script.IsEmpty()) {
    info.GetReturnValue().SetNull();
    return;
  }

  WideString wsCalcScript = WideString::FromUTF8(bsUtf8Script.AsStringView());
  std::optional<WideTextBuffer> wsJavaScriptBuf =
      CFXJSE_FormCalcContext::Translate(context->GetDocument()->GetHeap(),
                                        wsCalcScript.AsStringView());
  if (!wsJavaScriptBuf.has_value()) {
    context->ThrowCompilerErrorException();
    return;
  }
  std::unique_ptr<CFXJSE_Context> pNewContext =
      CFXJSE_Context::Create(pIsolate, nullptr, nullptr, nullptr);

  ByteString bsScript = FX_UTF8Encode(wsJavaScriptBuf.value().AsStringView());
  CFXJSE_Context::ExecutionResult result = pNewContext->ExecuteScript(
      bsScript.AsStringView(), v8::Local<v8::Object>());
  info.GetReturnValue().Set(v8::Local<v8::Value>::New(pIsolate, result.value));
}

// static
void CFXJSE_FormCalcContext::Ref(
    CFXJSE_HostObject* pThis,
    const v8::FunctionCallbackInfo<v8::Value>& info) {
  CFXJSE_FormCalcContext* context = ToFormCalcContext(pThis);
  if (info.Length() != 1) {
    context->ThrowParamCountMismatchException("Ref");
    return;
  }

  v8::Local<v8::Value> argOne = info[0];
  if (fxv8::IsBoolean(argOne) || fxv8::IsString(argOne) ||
      fxv8::IsNumber(argOne)) {
    info.GetReturnValue().Set(argOne);
    return;
  }

  v8::LocalVector<v8::Value> values(info.GetIsolate(), 3);
  int intVal = 3;
  if (fxv8::IsNull(argOne)) {
    // TODO(dsinclair): Why is this 4 when the others are all 3?
    intVal = 4;
    values[2] = fxv8::NewNullHelper(info.GetIsolate());
  } else if (fxv8::IsArray(argOne)) {
    v8::Local<v8::Array> arr = argOne.As<v8::Array>();
    v8::Local<v8::Value> propertyValue =
        fxv8::ReentrantGetArrayElementHelper(info.GetIsolate(), arr, 1);
    v8::Local<v8::Value> jsObjectValue =
        fxv8::ReentrantGetArrayElementHelper(info.GetIsolate(), arr, 2);
    if (!fxv8::IsNull(propertyValue) || fxv8::IsNull(jsObjectValue)) {
      context->ThrowArgumentMismatchException();
      return;
    }
    values[2] = jsObjectValue;
  } else if (fxv8::IsObject(argOne)) {
    values[2] = argOne;
  } else {
    context->ThrowArgumentMismatchException();
    return;
  }

  values[0] = fxv8::NewNumberHelper(info.GetIsolate(), intVal);
  values[1] = fxv8::NewNullHelper(info.GetIsolate());
  info.GetReturnValue().Set(fxv8::NewArrayHelper(info.GetIsolate(), values));
}

// static
void CFXJSE_FormCalcContext::UnitType(
    CFXJSE_HostObject* pThis,
    const v8::FunctionCallbackInfo<v8::Value>& info) {
  if (info.Length() != 1) {
    ToFormCalcContext(pThis)->ThrowParamCountMismatchException("UnitType");
    return;
  }

  v8::Local<v8::Value> unitspanValue = GetSimpleValue(info, 0);
  if (fxv8::IsNull(unitspanValue)) {
    info.GetReturnValue().SetNull();
    return;
  }

  ByteString bsUnitspan = ValueToUTF8String(info.GetIsolate(), unitspanValue);
  if (bsUnitspan.IsEmpty()) {
    info.GetReturnValue().SetEmptyString();
    return;
  }

  enum XFA_FormCalc_VALUETYPE_ParserStatus {
    VALUETYPE_START,
    VALUETYPE_HAVEINVALIDCHAR,
    VALUETYPE_HAVEDIGIT,
    VALUETYPE_HAVEDIGITWHITE,
    VALUETYPE_ISCM,
    VALUETYPE_ISMM,
    VALUETYPE_ISPT,
    VALUETYPE_ISMP,
    VALUETYPE_ISIN,
  };
  bsUnitspan.MakeLower();
  WideString wsType = WideString::FromUTF8(bsUnitspan.AsStringView());
  const wchar_t* pData = wsType.c_str();
  int32_t u = 0;
  int32_t uLen = wsType.GetLength();
  UNSAFE_TODO({
    while (IsWhitespace(pData[u])) {
      u++;
    }

    XFA_FormCalc_VALUETYPE_ParserStatus eParserStatus = VALUETYPE_START;
    wchar_t typeChar;
    // TODO(dsinclair): Cleanup this parser, figure out what the various checks
    //    are for.
    while (u < uLen) {
      typeChar = pData[u];
      if (IsWhitespace(typeChar)) {
        if (eParserStatus != VALUETYPE_HAVEDIGIT &&
            eParserStatus != VALUETYPE_HAVEDIGITWHITE) {
          eParserStatus = VALUETYPE_ISIN;
          break;
        }
        eParserStatus = VALUETYPE_HAVEDIGITWHITE;
      } else if (IsPartOfNumberW(typeChar)) {
        if (eParserStatus == VALUETYPE_HAVEDIGITWHITE) {
          eParserStatus = VALUETYPE_ISIN;
          break;
        }
        eParserStatus = VALUETYPE_HAVEDIGIT;
      } else if ((typeChar == 'c' || typeChar == 'p') && (u + 1 < uLen)) {
        wchar_t nextChar = pData[u + 1];
        if ((eParserStatus == VALUETYPE_START ||
             eParserStatus == VALUETYPE_HAVEDIGIT ||
             eParserStatus == VALUETYPE_HAVEDIGITWHITE) &&
            !IsPartOfNumberW(nextChar)) {
          eParserStatus = (typeChar == 'c') ? VALUETYPE_ISCM : VALUETYPE_ISPT;
          break;
        }
        eParserStatus = VALUETYPE_HAVEINVALIDCHAR;
      } else if (typeChar == 'm' && (u + 1 < uLen)) {
        wchar_t nextChar = pData[u + 1];
        if ((eParserStatus == VALUETYPE_START ||
             eParserStatus == VALUETYPE_HAVEDIGIT ||
             eParserStatus == VALUETYPE_HAVEDIGITWHITE) &&
            !IsPartOfNumberW(nextChar)) {
          eParserStatus = VALUETYPE_ISMM;
          if (nextChar == 'p' || ((u + 5 < uLen) && pData[u + 1] == 'i' &&
                                  pData[u + 2] == 'l' && pData[u + 3] == 'l' &&
                                  pData[u + 4] == 'i' && pData[u + 5] == 'p')) {
            eParserStatus = VALUETYPE_ISMP;
          }
          break;
        }
      } else {
        eParserStatus = VALUETYPE_HAVEINVALIDCHAR;
      }
      u++;
    }
    switch (eParserStatus) {
      case VALUETYPE_ISCM:
        info.GetReturnValue().Set(
            fxv8::NewStringHelper(info.GetIsolate(), "cm"));
        break;
      case VALUETYPE_ISMM:
        info.GetReturnValue().Set(
            fxv8::NewStringHelper(info.GetIsolate(), "mm"));
        break;
      case VALUETYPE_ISPT:
        info.GetReturnValue().Set(
            fxv8::NewStringHelper(info.GetIsolate(), "pt"));
        break;
      case VALUETYPE_ISMP:
        info.GetReturnValue().Set(
            fxv8::NewStringHelper(info.GetIsolate(), "mp"));
        break;
      default:
        info.GetReturnValue().Set(
            fxv8::NewStringHelper(info.GetIsolate(), "in"));
        break;
    }
  });
}

// static
void CFXJSE_FormCalcContext::UnitValue(
    CFXJSE_HostObject* pThis,
    const v8::FunctionCallbackInfo<v8::Value>& info) {
  int32_t argc = info.Length();
  if (argc < 1 || argc > 2) {
    ToFormCalcContext(pThis)->ThrowParamCountMismatchException("UnitValue");
    return;
  }

  v8::Local<v8::Value> unitspanValue = GetSimpleValue(info, 0);
  if (fxv8::IsNull(unitspanValue)) {
    info.GetReturnValue().SetNull();
    return;
  }

  ByteString bsUnitspan = ValueToUTF8String(info.GetIsolate(), unitspanValue);
  const char* pData = bsUnitspan.c_str();
  if (!pData) {
    info.GetReturnValue().Set(0);
    return;
  }

  UNSAFE_TODO({
    size_t u = 0;
    while (IsWhitespace(pData[u])) {
      ++u;
    }

    while (u < bsUnitspan.GetLength()) {
      if (!IsPartOfNumber(pData[u])) {
        break;
      }
      ++u;
    }

    char* pTemp = nullptr;
    double dFirstNumber = strtod(pData, &pTemp);
    while (IsWhitespace(pData[u])) {
      ++u;
    }

    size_t uLen = bsUnitspan.GetLength();
    ByteString bsFirstUnit;
    while (u < uLen) {
      if (pData[u] == ' ') {
        break;
      }

      bsFirstUnit += pData[u];
      ++u;
    }
    bsFirstUnit.MakeLower();

    ByteString bsUnit;
    if (argc > 1) {
      v8::Local<v8::Value> unitValue = GetSimpleValue(info, 1);
      ByteString bsUnitTemp = ValueToUTF8String(info.GetIsolate(), unitValue);
      const char* pChar = bsUnitTemp.c_str();
      size_t uVal = 0;
      while (IsWhitespace(pChar[uVal])) {
        ++uVal;
      }

      while (uVal < bsUnitTemp.GetLength()) {
        if (!FXSYS_IsDecimalDigit(pChar[uVal]) && pChar[uVal] != '.') {
          break;
        }
        ++uVal;
      }
      while (IsWhitespace(pChar[uVal])) {
        ++uVal;
      }

      size_t uValLen = bsUnitTemp.GetLength();
      while (uVal < uValLen) {
        if (pChar[uVal] == ' ') {
          break;
        }

        bsUnit += pChar[uVal];
        ++uVal;
      }
      bsUnit.MakeLower();
    } else {
      bsUnit = bsFirstUnit;
    }

    double dResult = 0;
    if (bsFirstUnit == "in" || bsFirstUnit == "inches") {
      if (bsUnit == "mm" || bsUnit == "millimeters") {
        dResult = dFirstNumber * 25.4;
      } else if (bsUnit == "cm" || bsUnit == "centimeters") {
        dResult = dFirstNumber * 2.54;
      } else if (bsUnit == "pt" || bsUnit == "points") {
        dResult = dFirstNumber / 72;
      } else if (bsUnit == "mp" || bsUnit == "millipoints") {
        dResult = dFirstNumber / 72000;
      } else {
        dResult = dFirstNumber;
      }
    } else if (bsFirstUnit == "mm" || bsFirstUnit == "millimeters") {
      if (bsUnit == "mm" || bsUnit == "millimeters") {
        dResult = dFirstNumber;
      } else if (bsUnit == "cm" || bsUnit == "centimeters") {
        dResult = dFirstNumber / 10;
      } else if (bsUnit == "pt" || bsUnit == "points") {
        dResult = dFirstNumber / 25.4 / 72;
      } else if (bsUnit == "mp" || bsUnit == "millipoints") {
        dResult = dFirstNumber / 25.4 / 72000;
      } else {
        dResult = dFirstNumber / 25.4;
      }
    } else if (bsFirstUnit == "cm" || bsFirstUnit == "centimeters") {
      if (bsUnit == "mm" || bsUnit == "millimeters") {
        dResult = dFirstNumber * 10;
      } else if (bsUnit == "cm" || bsUnit == "centimeters") {
        dResult = dFirstNumber;
      } else if (bsUnit == "pt" || bsUnit == "points") {
        dResult = dFirstNumber / 2.54 / 72;
      } else if (bsUnit == "mp" || bsUnit == "millipoints") {
        dResult = dFirstNumber / 2.54 / 72000;
      } else {
        dResult = dFirstNumber / 2.54;
      }
    } else if (bsFirstUnit == "pt" || bsFirstUnit == "points") {
      if (bsUnit == "mm" || bsUnit == "millimeters") {
        dResult = dFirstNumber / 72 * 25.4;
      } else if (bsUnit == "cm" || bsUnit == "centimeters") {
        dResult = dFirstNumber / 72 * 2.54;
      } else if (bsUnit == "pt" || bsUnit == "points") {
        dResult = dFirstNumber;
      } else if (bsUnit == "mp" || bsUnit == "millipoints") {
        dResult = dFirstNumber * 1000;
      } else {
        dResult = dFirstNumber / 72;
      }
    } else if (bsFirstUnit == "mp" || bsFirstUnit == "millipoints") {
      if (bsUnit == "mm" || bsUnit == "millimeters") {
        dResult = dFirstNumber / 72000 * 25.4;
      } else if (bsUnit == "cm" || bsUnit == "centimeters") {
        dResult = dFirstNumber / 72000 * 2.54;
      } else if (bsUnit == "pt" || bsUnit == "points") {
        dResult = dFirstNumber / 1000;
      } else if (bsUnit == "mp" || bsUnit == "millipoints") {
        dResult = dFirstNumber;
      } else {
        dResult = dFirstNumber / 72000;
      }
    }
    info.GetReturnValue().Set(dResult);
  });
}

// static
void CFXJSE_FormCalcContext::At(
    CFXJSE_HostObject* pThis,
    const v8::FunctionCallbackInfo<v8::Value>& info) {
  if (info.Length() != 2) {
    ToFormCalcContext(pThis)->ThrowParamCountMismatchException("At");
    return;
  }

  v8::Local<v8::Value> argOne = GetSimpleValue(info, 0);
  v8::Local<v8::Value> argTwo = GetSimpleValue(info, 1);
  if (ValueIsNull(info.GetIsolate(), argOne) ||
      ValueIsNull(info.GetIsolate(), argTwo)) {
    info.GetReturnValue().SetNull();
    return;
  }

  ByteString stringTwo = ValueToUTF8String(info.GetIsolate(), argTwo);
  if (stringTwo.IsEmpty()) {
    info.GetReturnValue().Set(1);
    return;
  }

  ByteString stringOne = ValueToUTF8String(info.GetIsolate(), argOne);
  auto pos = stringOne.Find(stringTwo.AsStringView());
  info.GetReturnValue().Set(
      static_cast<int>(pos.has_value() ? pos.value() + 1 : 0));
}

// static
void CFXJSE_FormCalcContext::Concat(
    CFXJSE_HostObject* pThis,
    const v8::FunctionCallbackInfo<v8::Value>& info) {
  int32_t argc = info.Length();
  if (argc < 1) {
    ToFormCalcContext(pThis)->ThrowParamCountMismatchException("Concat");
    return;
  }

  ByteString bsResult;
  bool bAllNull = true;
  for (int32_t i = 0; i < argc; i++) {
    v8::Local<v8::Value> value = GetSimpleValue(info, i);
    if (ValueIsNull(info.GetIsolate(), value)) {
      continue;
    }

    bAllNull = false;
    bsResult += ValueToUTF8String(info.GetIsolate(), value);
  }

  if (bAllNull) {
    info.GetReturnValue().SetNull();
    return;
  }
  info.GetReturnValue().Set(
      fxv8::NewStringHelper(info.GetIsolate(), bsResult.AsStringView()));
}

// static
void CFXJSE_FormCalcContext::Decode(
    CFXJSE_HostObject* pThis,
    const v8::FunctionCallbackInfo<v8::Value>& info) {
  int32_t argc = info.Length();
  if (argc < 1 || argc > 2) {
    ToFormCalcContext(pThis)->ThrowParamCountMismatchException("Decode");
    return;
  }

  if (argc == 1) {
    v8::Local<v8::Value> argOne = GetSimpleValue(info, 0);
    if (ValueIsNull(info.GetIsolate(), argOne)) {
      info.GetReturnValue().SetNull();
      return;
    }

    WideString decoded = DecodeURL(WideString::FromUTF8(
        ValueToUTF8String(info.GetIsolate(), argOne).AsStringView()));
    auto result = FX_UTF8Encode(decoded.AsStringView());
    info.GetReturnValue().Set(
        fxv8::NewStringHelper(info.GetIsolate(), result.AsStringView()));
    return;
  }

  v8::Local<v8::Value> argOne = GetSimpleValue(info, 0);
  v8::Local<v8::Value> argTwo = GetSimpleValue(info, 1);
  if (ValueIsNull(info.GetIsolate(), argOne) ||
      ValueIsNull(info.GetIsolate(), argTwo)) {
    info.GetReturnValue().SetNull();
    return;
  }

  ByteString bsToDecode = ValueToUTF8String(info.GetIsolate(), argOne);
  ByteString bsIdentify = ValueToUTF8String(info.GetIsolate(), argTwo);
  WideString decoded;

  WideString wsToDecode = WideString::FromUTF8(bsToDecode.AsStringView());

  if (bsIdentify.EqualNoCase("html")) {
    decoded = DecodeHTML(wsToDecode);
  } else if (bsIdentify.EqualNoCase("xml")) {
    decoded = DecodeXML(wsToDecode);
  } else {
    decoded = DecodeURL(wsToDecode);
  }

  auto result = FX_UTF8Encode(decoded.AsStringView());
  info.GetReturnValue().Set(
      fxv8::NewStringHelper(info.GetIsolate(), result.AsStringView()));
}

// static
void CFXJSE_FormCalcContext::Encode(
    CFXJSE_HostObject* pThis,
    const v8::FunctionCallbackInfo<v8::Value>& info) {
  int32_t argc = info.Length();
  if (argc < 1 || argc > 2) {
    ToFormCalcContext(pThis)->ThrowParamCountMismatchException("Encode");
    return;
  }

  if (argc == 1) {
    v8::Local<v8::Value> argOne = GetSimpleValue(info, 0);
    if (ValueIsNull(info.GetIsolate(), argOne)) {
      info.GetReturnValue().SetNull();
      return;
    }
    WideString encoded =
        EncodeURL(ValueToUTF8String(info.GetIsolate(), argOne));
    auto result = FX_UTF8Encode(encoded.AsStringView());
    info.GetReturnValue().Set(
        fxv8::NewStringHelper(info.GetIsolate(), result.AsStringView()));
    return;
  }

  v8::Local<v8::Value> argOne = GetSimpleValue(info, 0);
  v8::Local<v8::Value> argTwo = GetSimpleValue(info, 1);
  if (ValueIsNull(info.GetIsolate(), argOne) ||
      ValueIsNull(info.GetIsolate(), argTwo)) {
    info.GetReturnValue().SetNull();
    return;
  }

  ByteString bsToEncode = ValueToUTF8String(info.GetIsolate(), argOne);
  ByteString bsIdentify = ValueToUTF8String(info.GetIsolate(), argTwo);
  WideString encoded;
  if (bsIdentify.EqualNoCase("html")) {
    encoded = EncodeHTML(bsToEncode);
  } else if (bsIdentify.EqualNoCase("xml")) {
    encoded = EncodeXML(bsToEncode);
  } else {
    encoded = EncodeURL(bsToEncode);
  }

  auto result = FX_UTF8Encode(encoded.AsStringView());
  info.GetReturnValue().Set(
      fxv8::NewStringHelper(info.GetIsolate(), result.AsStringView()));
}

// static
void CFXJSE_FormCalcContext::Format(
    CFXJSE_HostObject* pThis,
    const v8::FunctionCallbackInfo<v8::Value>& info) {
  CFXJSE_FormCalcContext* context = ToFormCalcContext(pThis);
  if (info.Length() < 2) {
    context->ThrowParamCountMismatchException("Format");
    return;
  }

  v8::Local<v8::Value> argOne = GetSimpleValue(info, 0);
  ByteString bsPattern = ValueToUTF8String(info.GetIsolate(), argOne);

  v8::Local<v8::Value> argTwo = GetSimpleValue(info, 1);
  ByteString bsValue = ValueToUTF8String(info.GetIsolate(), argTwo);

  CXFA_Document* doc = context->GetDocument();
  CXFA_LocaleMgr* pMgr = doc->GetLocaleMgr();
  CXFA_Node* pThisNode = ToNode(doc->GetScriptContext()->GetThisObject());
  GCedLocaleIface* pLocale = pThisNode->GetLocale();
  WideString wsPattern = WideString::FromUTF8(bsPattern.AsStringView());
  WideString wsValue = WideString::FromUTF8(bsValue.AsStringView());
  auto [bPatternIsString, dwPatternType] =
      PatternStringType(bsPattern.AsStringView());
  if (!bPatternIsString) {
    switch (dwPatternType) {
      case CXFA_LocaleValue::ValueType::kDateTime: {
        auto iTChar = wsPattern.Find(L'T');
        if (!iTChar.has_value()) {
          info.GetReturnValue().SetEmptyString();
          return;
        }
        auto wsDatePattern = WideString::FromASCII("date{");
        wsDatePattern += wsPattern.First(iTChar.value()) + L"} ";

        auto wsTimePattern = WideString::FromASCII("time{");
        wsTimePattern += wsPattern.Substr(iTChar.value() + 1) + L"}";
        wsPattern = wsDatePattern + wsTimePattern;
      } break;
      case CXFA_LocaleValue::ValueType::kDate: {
        wsPattern = L"date{" + wsPattern + L"}";
      } break;
      case CXFA_LocaleValue::ValueType::kTime: {
        wsPattern = L"time{" + wsPattern + L"}";
      } break;
      case CXFA_LocaleValue::ValueType::kText: {
        wsPattern = L"text{" + wsPattern + L"}";
      } break;
      case CXFA_LocaleValue::ValueType::kFloat: {
        wsPattern = L"num{" + wsPattern + L"}";
      } break;
      default: {
        WideString wsTestPattern = L"num{" + wsPattern + L"}";
        CXFA_LocaleValue tempLocaleValue(CXFA_LocaleValue::ValueType::kFloat,
                                         wsValue, wsTestPattern, pLocale, pMgr);
        if (tempLocaleValue.IsValid()) {
          wsPattern = std::move(wsTestPattern);
          dwPatternType = CXFA_LocaleValue::ValueType::kFloat;
        } else {
          wsPattern = L"text{" + wsPattern + L"}";
          dwPatternType = CXFA_LocaleValue::ValueType::kText;
        }
      } break;
    }
  }
  CXFA_LocaleValue localeValue(dwPatternType, wsValue, wsPattern, pLocale,
                               pMgr);
  WideString wsRet;
  if (!localeValue.FormatPatterns(wsRet, wsPattern, pLocale,
                                  XFA_ValuePicture::kDisplay)) {
    info.GetReturnValue().SetEmptyString();
    return;
  }
  info.GetReturnValue().Set(
      fxv8::NewStringHelper(info.GetIsolate(), wsRet.ToUTF8().AsStringView()));
}

// static
void CFXJSE_FormCalcContext::Left(
    CFXJSE_HostObject* pThis,
    const v8::FunctionCallbackInfo<v8::Value>& info) {
  if (info.Length() != 2) {
    ToFormCalcContext(pThis)->ThrowParamCountMismatchException("Left");
    return;
  }

  v8::Local<v8::Value> argOne = GetSimpleValue(info, 0);
  v8::Local<v8::Value> argTwo = GetSimpleValue(info, 1);
  if ((ValueIsNull(info.GetIsolate(), argOne)) ||
      (ValueIsNull(info.GetIsolate(), argTwo))) {
    info.GetReturnValue().SetNull();
    return;
  }

  ByteString bsSource = ValueToUTF8String(info.GetIsolate(), argOne);
  int32_t count = std::max(0, ValueToInteger(info.GetIsolate(), argTwo));
  info.GetReturnValue().Set(fxv8::NewStringHelper(
      info.GetIsolate(), bsSource.First(count).AsStringView()));
}

// static
void CFXJSE_FormCalcContext::Len(
    CFXJSE_HostObject* pThis,
    const v8::FunctionCallbackInfo<v8::Value>& info) {
  if (info.Length() != 1) {
    ToFormCalcContext(pThis)->ThrowParamCountMismatchException("Len");
    return;
  }

  v8::Local<v8::Value> argOne = GetSimpleValue(info, 0);
  if (ValueIsNull(info.GetIsolate(), argOne)) {
    info.GetReturnValue().SetNull();
    return;
  }

  ByteString bsSource = ValueToUTF8String(info.GetIsolate(), argOne);
  info.GetReturnValue().Set(static_cast<int>(bsSource.GetLength()));
}

// static
void CFXJSE_FormCalcContext::Lower(
    CFXJSE_HostObject* pThis,
    const v8::FunctionCallbackInfo<v8::Value>& info) {
  int32_t argc = info.Length();
  if (argc < 1 || argc > 2) {
    ToFormCalcContext(pThis)->ThrowParamCountMismatchException("Lower");
    return;
  }

  v8::Local<v8::Value> argOne = GetSimpleValue(info, 0);
  if (ValueIsNull(info.GetIsolate(), argOne)) {
    info.GetReturnValue().SetNull();
    return;
  }

  WideTextBuffer szLowBuf;
  ByteString bsArg = ValueToUTF8String(info.GetIsolate(), argOne);
  WideString wsArg = WideString::FromUTF8(bsArg.AsStringView());
  for (wchar_t ch : wsArg) {
    if ((ch >= 0x41 && ch <= 0x5A) || (ch >= 0xC0 && ch <= 0xDE)) {
      ch += 32;
    } else if (ch == 0x100 || ch == 0x102 || ch == 0x104) {
      ch += 1;
    }
    szLowBuf.AppendChar(ch);
  }
  auto result = FX_UTF8Encode(szLowBuf.AsStringView());
  info.GetReturnValue().Set(
      fxv8::NewStringHelper(info.GetIsolate(), result.AsStringView()));
}

// static
void CFXJSE_FormCalcContext::Ltrim(
    CFXJSE_HostObject* pThis,
    const v8::FunctionCallbackInfo<v8::Value>& info) {
  if (info.Length() != 1) {
    ToFormCalcContext(pThis)->ThrowParamCountMismatchException("Ltrim");
    return;
  }

  v8::Local<v8::Value> argOne = GetSimpleValue(info, 0);
  if (ValueIsNull(info.GetIsolate(), argOne)) {
    info.GetReturnValue().SetNull();
    return;
  }

  ByteString bsSource = ValueToUTF8String(info.GetIsolate(), argOne);
  bsSource.TrimWhitespaceFront();
  info.GetReturnValue().Set(
      fxv8::NewStringHelper(info.GetIsolate(), bsSource.AsStringView()));
}

// static
void CFXJSE_FormCalcContext::Parse(
    CFXJSE_HostObject* pThis,
    const v8::FunctionCallbackInfo<v8::Value>& info) {
  CFXJSE_FormCalcContext* context = ToFormCalcContext(pThis);
  if (info.Length() != 2) {
    context->ThrowParamCountMismatchException("Parse");
    return;
  }

  v8::Local<v8::Value> argOne = GetSimpleValue(info, 0);
  v8::Local<v8::Value> argTwo = GetSimpleValue(info, 1);
  if (ValueIsNull(info.GetIsolate(), argTwo)) {
    info.GetReturnValue().SetNull();
    return;
  }

  ByteString bsPattern = ValueToUTF8String(info.GetIsolate(), argOne);
  ByteString bsValue = ValueToUTF8String(info.GetIsolate(), argTwo);
  CXFA_Document* doc = context->GetDocument();
  CXFA_LocaleMgr* pMgr = doc->GetLocaleMgr();
  CXFA_Node* pThisNode = ToNode(doc->GetScriptContext()->GetThisObject());
  GCedLocaleIface* pLocale = pThisNode->GetLocale();
  WideString wsPattern = WideString::FromUTF8(bsPattern.AsStringView());
  WideString wsValue = WideString::FromUTF8(bsValue.AsStringView());
  auto [bPatternIsString, dwPatternType] =
      PatternStringType(bsPattern.AsStringView());
  if (bPatternIsString) {
    CXFA_LocaleValue localeValue(dwPatternType, wsValue, wsPattern, pLocale,
                                 pMgr);
    if (!localeValue.IsValid()) {
      info.GetReturnValue().SetEmptyString();
      return;
    }
    auto result = localeValue.GetValue().ToUTF8();
    info.GetReturnValue().Set(
        fxv8::NewStringHelper(info.GetIsolate(), result.AsStringView()));
    return;
  }

  switch (dwPatternType) {
    case CXFA_LocaleValue::ValueType::kDateTime: {
      auto iTChar = wsPattern.Find(L'T');
      if (!iTChar.has_value()) {
        info.GetReturnValue().SetEmptyString();
        return;
      }
      WideString wsDatePattern(L"date{" + wsPattern.First(iTChar.value()) +
                               L"} ");
      WideString wsTimePattern(L"time{" + wsPattern.Substr(iTChar.value() + 1) +
                               L"}");
      wsPattern = wsDatePattern + wsTimePattern;
      CXFA_LocaleValue localeValue(dwPatternType, wsValue, wsPattern, pLocale,
                                   pMgr);
      if (!localeValue.IsValid()) {
        info.GetReturnValue().SetEmptyString();
        return;
      }
      auto result = localeValue.GetValue().ToUTF8();
      info.GetReturnValue().Set(
          fxv8::NewStringHelper(info.GetIsolate(), result.AsStringView()));
      return;
    }
    case CXFA_LocaleValue::ValueType::kDate: {
      wsPattern = L"date{" + wsPattern + L"}";
      CXFA_LocaleValue localeValue(dwPatternType, wsValue, wsPattern, pLocale,
                                   pMgr);
      if (!localeValue.IsValid()) {
        info.GetReturnValue().SetEmptyString();
        return;
      }
      auto result = localeValue.GetValue().ToUTF8();
      info.GetReturnValue().Set(
          fxv8::NewStringHelper(info.GetIsolate(), result.AsStringView()));
      return;
    }
    case CXFA_LocaleValue::ValueType::kTime: {
      wsPattern = L"time{" + wsPattern + L"}";
      CXFA_LocaleValue localeValue(dwPatternType, wsValue, wsPattern, pLocale,
                                   pMgr);
      if (!localeValue.IsValid()) {
        info.GetReturnValue().SetEmptyString();
        return;
      }
      auto result = localeValue.GetValue().ToUTF8();
      info.GetReturnValue().Set(
          fxv8::NewStringHelper(info.GetIsolate(), result.AsStringView()));
      return;
    }
    case CXFA_LocaleValue::ValueType::kText: {
      wsPattern = L"text{" + wsPattern + L"}";
      CXFA_LocaleValue localeValue(CXFA_LocaleValue::ValueType::kText, wsValue,
                                   wsPattern, pLocale, pMgr);
      if (!localeValue.IsValid()) {
        info.GetReturnValue().SetEmptyString();
        return;
      }
      auto result = localeValue.GetValue().ToUTF8();
      info.GetReturnValue().Set(
          fxv8::NewStringHelper(info.GetIsolate(), result.AsStringView()));
      return;
    }
    case CXFA_LocaleValue::ValueType::kFloat: {
      wsPattern = L"num{" + wsPattern + L"}";
      CXFA_LocaleValue localeValue(CXFA_LocaleValue::ValueType::kFloat, wsValue,
                                   wsPattern, pLocale, pMgr);
      if (!localeValue.IsValid()) {
        info.GetReturnValue().SetEmptyString();
        return;
      }
      info.GetReturnValue().Set(localeValue.GetDoubleNum());
      return;
    }
    default: {
      {
        WideString wsTestPattern = L"num{" + wsPattern + L"}";
        CXFA_LocaleValue localeValue(CXFA_LocaleValue::ValueType::kFloat,
                                     wsValue, wsTestPattern, pLocale, pMgr);
        if (localeValue.IsValid()) {
          info.GetReturnValue().Set(localeValue.GetDoubleNum());
          return;
        }
      }

      {
        WideString wsTestPattern = L"text{" + wsPattern + L"}";
        CXFA_LocaleValue localeValue(CXFA_LocaleValue::ValueType::kText,
                                     wsValue, wsTestPattern, pLocale, pMgr);
        if (localeValue.IsValid()) {
          auto result = localeValue.GetValue().ToUTF8();
          info.GetReturnValue().Set(
              fxv8::NewStringHelper(info.GetIsolate(), result.AsStringView()));
          return;
        }
      }
      info.GetReturnValue().SetEmptyString();
      return;
    }
  }
}

// static
void CFXJSE_FormCalcContext::Replace(
    CFXJSE_HostObject* pThis,
    const v8::FunctionCallbackInfo<v8::Value>& info) {
  int32_t argc = info.Length();
  if (argc < 2 || argc > 3) {
    ToFormCalcContext(pThis)->ThrowParamCountMismatchException("Replace");
    return;
  }

  v8::Local<v8::Value> argOne = GetSimpleValue(info, 0);
  v8::Local<v8::Value> argTwo = GetSimpleValue(info, 1);
  ByteString bsOne;
  ByteString bsTwo;
  if (!ValueIsNull(info.GetIsolate(), argOne) &&
      !ValueIsNull(info.GetIsolate(), argTwo)) {
    bsOne = ValueToUTF8String(info.GetIsolate(), argOne);
    bsTwo = ValueToUTF8String(info.GetIsolate(), argTwo);
  }

  ByteString bsThree;
  if (argc > 2) {
    v8::Local<v8::Value> argThree = GetSimpleValue(info, 2);
    bsThree = ValueToUTF8String(info.GetIsolate(), argThree);
  }

  bsOne.Replace(bsTwo.AsStringView(), bsThree.AsStringView());
  info.GetReturnValue().Set(
      fxv8::NewStringHelper(info.GetIsolate(), bsOne.AsStringView()));
}

// static
void CFXJSE_FormCalcContext::Right(
    CFXJSE_HostObject* pThis,
    const v8::FunctionCallbackInfo<v8::Value>& info) {
  if (info.Length() != 2) {
    ToFormCalcContext(pThis)->ThrowParamCountMismatchException("Right");
    return;
  }

  v8::Local<v8::Value> argOne = GetSimpleValue(info, 0);
  v8::Local<v8::Value> argTwo = GetSimpleValue(info, 1);
  if ((ValueIsNull(info.GetIsolate(), argOne)) ||
      (ValueIsNull(info.GetIsolate(), argTwo))) {
    info.GetReturnValue().SetNull();
    return;
  }

  ByteString bsSource = ValueToUTF8String(info.GetIsolate(), argOne);
  int32_t count = std::max(0, ValueToInteger(info.GetIsolate(), argTwo));
  info.GetReturnValue().Set(fxv8::NewStringHelper(
      info.GetIsolate(), bsSource.Last(count).AsStringView()));
}

// static
void CFXJSE_FormCalcContext::Rtrim(
    CFXJSE_HostObject* pThis,
    const v8::FunctionCallbackInfo<v8::Value>& info) {
  if (info.Length() != 1) {
    ToFormCalcContext(pThis)->ThrowParamCountMismatchException("Rtrim");
    return;
  }

  v8::Local<v8::Value> argOne = GetSimpleValue(info, 0);
  if (ValueIsNull(info.GetIsolate(), argOne)) {
    info.GetReturnValue().SetNull();
    return;
  }

  ByteString bsSource = ValueToUTF8String(info.GetIsolate(), argOne);
  bsSource.TrimWhitespaceBack();
  info.GetReturnValue().Set(
      fxv8::NewStringHelper(info.GetIsolate(), bsSource.AsStringView()));
}

// static
void CFXJSE_FormCalcContext::Space(
    CFXJSE_HostObject* pThis,
    const v8::FunctionCallbackInfo<v8::Value>& info) {
  if (info.Length() != 1) {
    ToFormCalcContext(pThis)->ThrowParamCountMismatchException("Space");
    return;
  }

  v8::Local<v8::Value> argOne = GetSimpleValue(info, 0);
  if (fxv8::IsNull(argOne)) {
    info.GetReturnValue().SetNull();
    return;
  }

  int count = std::max(0, ValueToInteger(info.GetIsolate(), argOne));
  if (count > kMaxCharCount) {
    ToFormCalcContext(pThis)->ThrowException("String too long.");
    return;
  }
  DataVector<char> space_string(count, ' ');
  info.GetReturnValue().Set(
      fxv8::NewStringHelper(info.GetIsolate(), ByteStringView(space_string)));
}

// static
void CFXJSE_FormCalcContext::Str(
    CFXJSE_HostObject* pThis,
    const v8::FunctionCallbackInfo<v8::Value>& info) {
  int32_t argc = info.Length();
  if (argc < 1 || argc > 3) {
    ToFormCalcContext(pThis)->ThrowParamCountMismatchException("Str");
    return;
  }

  v8::Local<v8::Value> numberValue = GetSimpleValue(info, 0);
  if (fxv8::IsNull(numberValue)) {
    info.GetReturnValue().SetNull();
    return;
  }
  float fNumber = ValueToFloat(info.GetIsolate(), numberValue);

  static constexpr int32_t kDefaultWidth = 10;
  int32_t iWidth = kDefaultWidth;
  if (argc > 1) {
    v8::Local<v8::Value> widthValue = GetSimpleValue(info, 1);
    iWidth = static_cast<int32_t>(ValueToFloat(info.GetIsolate(), widthValue));
    if (iWidth > kMaxCharCount) {
      ToFormCalcContext(pThis)->ThrowException("String too long.");
      return;
    }
  }

  static constexpr int32_t kDefaultPrecision = 0;
  int32_t iPrecision = kDefaultPrecision;
  if (argc > 2) {
    static constexpr int32_t kMaxPrecision = 15;
    v8::Local<v8::Value> precision_value = GetSimpleValue(info, 2);
    iPrecision = std::max(0, static_cast<int32_t>(ValueToFloat(
                                 info.GetIsolate(), precision_value)));
    iPrecision = std::min(iPrecision, kMaxPrecision);
  }

  ByteString bsFormat = "%";
  if (iPrecision) {
    bsFormat += ".";
    bsFormat += ByteString::FormatInteger(iPrecision);
  }
  bsFormat += "f";
  ByteString bsNumber = ByteString::Format(bsFormat.c_str(), fNumber);

  const char* pData = bsNumber.c_str();
  int32_t iLength = bsNumber.GetLength();
  int32_t u = 0;
  UNSAFE_TODO({
    while (u < iLength) {
      if (pData[u] == '.') {
        break;
      }

      ++u;
    }

    if (u > iWidth || (iPrecision + u) >= iWidth) {
      DataVector<char> stars(std::max(iWidth, 0), '*');
      info.GetReturnValue().Set(
          fxv8::NewStringHelper(info.GetIsolate(), ByteStringView(stars)));
      return;
    }

    ByteString resultBuf;
    if (u == iLength) {
      if (iLength > iWidth) {
        int32_t i = 0;
        while (i < iWidth) {
          resultBuf += '*';
          ++i;
        }
      } else {
        int32_t i = 0;
        while (i < iWidth - iLength) {
          resultBuf += ' ';
          ++i;
        }
        resultBuf += pData;
      }
      info.GetReturnValue().Set(
          fxv8::NewStringHelper(info.GetIsolate(), resultBuf.AsStringView()));
      return;
    }

    int32_t iLeavingSpace = iWidth - u - iPrecision;
    if (iPrecision != 0) {
      iLeavingSpace--;
    }

    int32_t i = 0;
    while (i < iLeavingSpace) {
      resultBuf += ' ';
      ++i;
    }
    i = 0;
    while (i < u) {
      resultBuf += pData[i];
      ++i;
    }
    if (iPrecision != 0) {
      resultBuf += '.';
    }

    u++;
    i = 0;
    while (u < iLength) {
      if (i >= iPrecision) {
        break;
      }

      resultBuf += pData[u];
      ++i;
      ++u;
    }
    while (i < iPrecision) {
      resultBuf += '0';
      ++i;
    }
    info.GetReturnValue().Set(
        fxv8::NewStringHelper(info.GetIsolate(), resultBuf.AsStringView()));
  });
}

// static
void CFXJSE_FormCalcContext::Stuff(
    CFXJSE_HostObject* pThis,
    const v8::FunctionCallbackInfo<v8::Value>& info) {
  int32_t argc = info.Length();
  if (argc < 3 || argc > 4) {
    ToFormCalcContext(pThis)->ThrowParamCountMismatchException("Stuff");
    return;
  }

  v8::Local<v8::Value> sourceValue = GetSimpleValue(info, 0);
  v8::Local<v8::Value> startValue = GetSimpleValue(info, 1);
  v8::Local<v8::Value> deleteValue = GetSimpleValue(info, 2);
  if (fxv8::IsNull(sourceValue) || fxv8::IsNull(startValue) ||
      fxv8::IsNull(deleteValue)) {
    info.GetReturnValue().SetNull();
    return;
  }

  int32_t iStart = 1;  // one-based character indexing.
  int32_t iDelete = 0;
  ByteString bsSource = ValueToUTF8String(info.GetIsolate(), sourceValue);
  int32_t iLength = pdfium::checked_cast<int32_t>(bsSource.GetLength());
  if (iLength) {
    iStart = std::clamp(
        static_cast<int32_t>(ValueToFloat(info.GetIsolate(), startValue)), 1,
        iLength);
    iDelete = std::clamp(
        static_cast<int32_t>(ValueToFloat(info.GetIsolate(), deleteValue)), 0,
        iLength - iStart + 1);
  }

  ByteString bsInsert;
  if (argc > 3) {
    v8::Local<v8::Value> insertValue = GetSimpleValue(info, 3);
    bsInsert = ValueToUTF8String(info.GetIsolate(), insertValue);
  }

  --iStart;  // now zero-based.
  ByteString bsResult = {bsSource.AsStringView().First(iStart),
                         bsInsert.AsStringView(),
                         bsSource.AsStringView().Substr(iStart + iDelete)};
  info.GetReturnValue().Set(
      fxv8::NewStringHelper(info.GetIsolate(), bsResult.AsStringView()));
}

// static
void CFXJSE_FormCalcContext::Substr(
    CFXJSE_HostObject* pThis,
    const v8::FunctionCallbackInfo<v8::Value>& info) {
  if (info.Length() != 3) {
    ToFormCalcContext(pThis)->ThrowParamCountMismatchException("Substr");
    return;
  }

  v8::Local<v8::Value> string_value = GetSimpleValue(info, 0);
  v8::Local<v8::Value> start_value = GetSimpleValue(info, 1);
  v8::Local<v8::Value> end_value = GetSimpleValue(info, 2);
  if (ValueIsNull(info.GetIsolate(), string_value) ||
      ValueIsNull(info.GetIsolate(), start_value) ||
      ValueIsNull(info.GetIsolate(), end_value)) {
    info.GetReturnValue().SetNull();
    return;
  }

  ByteString bsSource = ValueToUTF8String(info.GetIsolate(), string_value);
  size_t iLength = bsSource.GetLength();
  if (iLength == 0) {
    info.GetReturnValue().SetEmptyString();
    return;
  }

  // |start_value| is 1-based. Assume first character if |start_value| is less
  // than 1, per spec. Subtract 1 since |iStart| is 0-based.
  size_t iStart =
      std::max(ValueToInteger(info.GetIsolate(), start_value), 1) - 1;
  if (iStart >= iLength) {
    info.GetReturnValue().SetEmptyString();
    return;
  }

  // Negative values are treated as 0. Can't clamp() due to sign mismatches.
  size_t iCount = std::max(ValueToInteger(info.GetIsolate(), end_value), 0);
  iCount = std::min(iCount, iLength - iStart);
  info.GetReturnValue().Set(fxv8::NewStringHelper(
      info.GetIsolate(), bsSource.Substr(iStart, iCount).AsStringView()));
}

// static
void CFXJSE_FormCalcContext::Uuid(
    CFXJSE_HostObject* pThis,
    const v8::FunctionCallbackInfo<v8::Value>& info) {
  int32_t argc = info.Length();
  if (argc < 0 || argc > 1) {
    ToFormCalcContext(pThis)->ThrowParamCountMismatchException("Uuid");
    return;
  }

  int32_t iNum = 0;
  if (argc > 0) {
    v8::Local<v8::Value> argOne = GetSimpleValue(info, 0);
    iNum = static_cast<int32_t>(ValueToFloat(info.GetIsolate(), argOne));
  }
  info.GetReturnValue().Set(fxv8::NewStringHelper(
      info.GetIsolate(), GUIDString(!!iNum).AsStringView()));
}

// static
void CFXJSE_FormCalcContext::Upper(
    CFXJSE_HostObject* pThis,
    const v8::FunctionCallbackInfo<v8::Value>& info) {
  int32_t argc = info.Length();
  if (argc < 1 || argc > 2) {
    ToFormCalcContext(pThis)->ThrowParamCountMismatchException("Upper");
    return;
  }

  v8::Local<v8::Value> argOne = GetSimpleValue(info, 0);
  if (ValueIsNull(info.GetIsolate(), argOne)) {
    info.GetReturnValue().SetNull();
    return;
  }

  ByteString bsArg = ValueToUTF8String(info.GetIsolate(), argOne);
  WideString wsArg = WideString::FromUTF8(bsArg.AsStringView());
  WideString upperStringBuf;
  upperStringBuf.Reserve(wsArg.GetLength());
  for (wchar_t ch : wsArg) {
    if ((ch >= 0x61 && ch <= 0x7A) || (ch >= 0xE0 && ch <= 0xFE)) {
      ch -= 32;
    } else if (ch == 0x101 || ch == 0x103 || ch == 0x105) {
      ch -= 1;
    }

    upperStringBuf += ch;
  }
  info.GetReturnValue().Set(fxv8::NewStringHelper(
      info.GetIsolate(),
      FX_UTF8Encode(upperStringBuf.AsStringView()).AsStringView()));
}

// static
void CFXJSE_FormCalcContext::WordNum(
    CFXJSE_HostObject* pThis,
    const v8::FunctionCallbackInfo<v8::Value>& info) {
  int32_t argc = info.Length();
  if (argc < 1 || argc > 3) {
    ToFormCalcContext(pThis)->ThrowParamCountMismatchException("WordNum");
    return;
  }

  v8::Local<v8::Value> numberValue = GetSimpleValue(info, 0);
  if (fxv8::IsNull(numberValue)) {
    info.GetReturnValue().SetNull();
    return;
  }
  float fNumber = ValueToFloat(info.GetIsolate(), numberValue);

  int32_t iIdentifier = 0;
  if (argc > 1) {
    v8::Local<v8::Value> identifierValue = GetSimpleValue(info, 1);
    if (fxv8::IsNull(identifierValue)) {
      info.GetReturnValue().SetNull();
      return;
    }
    iIdentifier =
        static_cast<int32_t>(ValueToFloat(info.GetIsolate(), identifierValue));
  }

  ByteString bsLocale;
  if (argc > 2) {
    v8::Local<v8::Value> localeValue = GetSimpleValue(info, 2);
    if (fxv8::IsNull(localeValue)) {
      info.GetReturnValue().SetNull();
      return;
    }
    bsLocale = ValueToUTF8String(info.GetIsolate(), localeValue);
  }

  if (isnan(fNumber) || fNumber < 0.0f || fNumber > 922337203685477550.0f) {
    info.GetReturnValue().Set(fxv8::NewStringHelper(info.GetIsolate(), "*"));
    return;
  }
  ByteString bsFormatted = ByteString::Format("%.2f", fNumber);
  ByteString bsWorded = WordUS(bsFormatted.AsStringView(), iIdentifier);
  info.GetReturnValue().Set(
      fxv8::NewStringHelper(info.GetIsolate(), bsWorded.AsStringView()));
}

// static
void CFXJSE_FormCalcContext::Get(
    CFXJSE_HostObject* pThis,
    const v8::FunctionCallbackInfo<v8::Value>& info) {
  CFXJSE_FormCalcContext* context = ToFormCalcContext(pThis);
  if (info.Length() != 1) {
    context->ThrowParamCountMismatchException("Get");
    return;
  }

  CXFA_Document* doc = context->GetDocument();
  if (!doc) {
    return;
  }

  CXFA_FFApp::CallbackIface* pAppProvider = doc->GetNotify()->GetAppProvider();
  if (!pAppProvider) {
    return;
  }

  v8::Local<v8::Value> argOne = GetSimpleValue(info, 0);
  ByteString bsUrl = ValueToUTF8String(info.GetIsolate(), argOne);
  RetainPtr<IFX_SeekableReadStream> pFile =
      pAppProvider->DownloadURL(WideString::FromUTF8(bsUrl.AsStringView()));
  if (!pFile) {
    return;
  }

  FX_FILESIZE size = pFile->GetSize();
  DataVector<uint8_t> data_buf(size);

  // TODO(tsepez): check return value?
  (void)pFile->ReadBlockAtOffset(data_buf, 0);
  info.GetReturnValue().Set(
      fxv8::NewStringHelper(info.GetIsolate(), ByteStringView(data_buf)));
}

// static
void CFXJSE_FormCalcContext::Post(
    CFXJSE_HostObject* pThis,
    const v8::FunctionCallbackInfo<v8::Value>& info) {
  CFXJSE_FormCalcContext* context = ToFormCalcContext(pThis);
  int32_t argc = info.Length();
  if (argc < 2 || argc > 5) {
    context->ThrowParamCountMismatchException("Post");
    return;
  }

  CXFA_Document* doc = context->GetDocument();
  if (!doc) {
    return;
  }

  CXFA_FFApp::CallbackIface* pAppProvider = doc->GetNotify()->GetAppProvider();
  if (!pAppProvider) {
    return;
  }

  v8::Local<v8::Value> argOne = GetSimpleValue(info, 0);
  ByteString bsURL = ValueToUTF8String(info.GetIsolate(), argOne);

  v8::Local<v8::Value> argTwo = GetSimpleValue(info, 1);
  ByteString bsData = ValueToUTF8String(info.GetIsolate(), argTwo);

  ByteString bsContentType;
  if (argc > 2) {
    v8::Local<v8::Value> argThree = GetSimpleValue(info, 2);
    bsContentType = ValueToUTF8String(info.GetIsolate(), argThree);
  }

  ByteString bsEncode;
  if (argc > 3) {
    v8::Local<v8::Value> argFour = GetSimpleValue(info, 3);
    bsEncode = ValueToUTF8String(info.GetIsolate(), argFour);
  }

  ByteString bsHeader;
  if (argc > 4) {
    v8::Local<v8::Value> argFive = GetSimpleValue(info, 4);
    bsHeader = ValueToUTF8String(info.GetIsolate(), argFive);
  }

  WideString decodedResponse;
  if (!pAppProvider->PostRequestURL(
          WideString::FromUTF8(bsURL.AsStringView()),
          WideString::FromUTF8(bsData.AsStringView()),
          WideString::FromUTF8(bsContentType.AsStringView()),
          WideString::FromUTF8(bsEncode.AsStringView()),
          WideString::FromUTF8(bsHeader.AsStringView()), decodedResponse)) {
    context->ThrowServerDeniedException();
    return;
  }
  info.GetReturnValue().Set(fxv8::NewStringHelper(
      info.GetIsolate(), decodedResponse.ToUTF8().AsStringView()));
}

// static
void CFXJSE_FormCalcContext::Put(
    CFXJSE_HostObject* pThis,
    const v8::FunctionCallbackInfo<v8::Value>& info) {
  CFXJSE_FormCalcContext* context = ToFormCalcContext(pThis);
  int32_t argc = info.Length();
  if (argc < 2 || argc > 3) {
    context->ThrowParamCountMismatchException("Put");
    return;
  }

  CXFA_Document* doc = context->GetDocument();
  if (!doc) {
    return;
  }

  CXFA_FFApp::CallbackIface* pAppProvider = doc->GetNotify()->GetAppProvider();
  if (!pAppProvider) {
    return;
  }

  v8::Local<v8::Value> argOne = GetSimpleValue(info, 0);
  ByteString bsURL = ValueToUTF8String(info.GetIsolate(), argOne);

  v8::Local<v8::Value> argTwo = GetSimpleValue(info, 1);
  ByteString bsData = ValueToUTF8String(info.GetIsolate(), argTwo);

  ByteString bsEncode;
  if (argc > 2) {
    v8::Local<v8::Value> argThree = GetSimpleValue(info, 2);
    bsEncode = ValueToUTF8String(info.GetIsolate(), argThree);
  }
  if (!pAppProvider->PutRequestURL(
          WideString::FromUTF8(bsURL.AsStringView()),
          WideString::FromUTF8(bsData.AsStringView()),
          WideString::FromUTF8(bsEncode.AsStringView()))) {
    context->ThrowServerDeniedException();
    return;
  }
  info.GetReturnValue().SetEmptyString();
}

// static
void CFXJSE_FormCalcContext::assign_value_operator(
    CFXJSE_HostObject* pThis,
    const v8::FunctionCallbackInfo<v8::Value>& info) {
  v8::Isolate* pIsolate = info.GetIsolate();
  CFXJSE_FormCalcContext* context = ToFormCalcContext(pThis);
  if (info.Length() != 2) {
    context->ThrowCompilerErrorException();
    return;
  }
  ByteStringView bsFuncName("asgn_val_op");
  v8::Local<v8::Value> lValue = info[0];
  v8::Local<v8::Value> rValue = GetSimpleValue(info, 1);
  if (fxv8::IsArray(lValue)) {
    v8::Local<v8::Array> arr = lValue.As<v8::Array>();
    uint32_t iLeftLength = fxv8::GetArrayLengthHelper(arr);
    v8::Local<v8::Value> propertyValue =
        fxv8::ReentrantGetArrayElementHelper(pIsolate, arr, 1);
    for (uint32_t i = 2; i < iLeftLength; i++) {
      v8::Local<v8::Value> jsValue =
          fxv8::ReentrantGetArrayElementHelper(pIsolate, arr, i);
      if (!fxv8::IsObject(jsValue)) {
        context->ThrowNoDefaultPropertyException(bsFuncName);
        return;
      }
      v8::Local<v8::Object> jsObjectValue = jsValue.As<v8::Object>();
      if (fxv8::IsNull(propertyValue)) {
        if (!SetObjectDefaultValue(pIsolate, jsObjectValue, rValue)) {
          context->ThrowNoDefaultPropertyException(bsFuncName);
          return;
        }
      } else {
        fxv8::ReentrantPutObjectPropertyHelper(
            pIsolate, jsObjectValue,
            fxv8::ReentrantToByteStringHelper(pIsolate, propertyValue)
                .AsStringView(),
            rValue);
      }
    }
  } else if (fxv8::IsObject(lValue)) {
    if (!SetObjectDefaultValue(pIsolate, lValue.As<v8::Object>(), rValue)) {
      context->ThrowNoDefaultPropertyException(bsFuncName);
      return;
    }
  }
  info.GetReturnValue().Set(rValue);
}

// static
void CFXJSE_FormCalcContext::logical_or_operator(
    CFXJSE_HostObject* pThis,
    const v8::FunctionCallbackInfo<v8::Value>& info) {
  if (info.Length() != 2) {
    ToFormCalcContext(pThis)->ThrowCompilerErrorException();
    return;
  }

  v8::Local<v8::Value> argFirst = GetSimpleValue(info, 0);
  v8::Local<v8::Value> argSecond = GetSimpleValue(info, 1);
  if (fxv8::IsNull(argFirst) && fxv8::IsNull(argSecond)) {
    info.GetReturnValue().SetNull();
    return;
  }

  float first = ValueToFloat(info.GetIsolate(), argFirst);
  float second = ValueToFloat(info.GetIsolate(), argSecond);
  info.GetReturnValue().Set(static_cast<int>(first || second));
}

// static
void CFXJSE_FormCalcContext::logical_and_operator(
    CFXJSE_HostObject* pThis,
    const v8::FunctionCallbackInfo<v8::Value>& info) {
  if (info.Length() != 2) {
    ToFormCalcContext(pThis)->ThrowCompilerErrorException();
    return;
  }

  v8::Local<v8::Value> argFirst = GetSimpleValue(info, 0);
  v8::Local<v8::Value> argSecond = GetSimpleValue(info, 1);
  if (fxv8::IsNull(argFirst) && fxv8::IsNull(argSecond)) {
    info.GetReturnValue().SetNull();
    return;
  }

  float first = ValueToFloat(info.GetIsolate(), argFirst);
  float second = ValueToFloat(info.GetIsolate(), argSecond);
  info.GetReturnValue().Set(static_cast<int>(first && second));
}

// static
void CFXJSE_FormCalcContext::equality_operator(
    CFXJSE_HostObject* pThis,
    const v8::FunctionCallbackInfo<v8::Value>& info) {
  if (info.Length() != 2) {
    ToFormCalcContext(pThis)->ThrowCompilerErrorException();
    return;
  }

  if (fm_ref_equal(pThis, info)) {
    info.GetReturnValue().Set(1);
    return;
  }

  v8::Local<v8::Value> argFirst = GetSimpleValue(info, 0);
  v8::Local<v8::Value> argSecond = GetSimpleValue(info, 1);
  if (fxv8::IsNull(argFirst) || fxv8::IsNull(argSecond)) {
    info.GetReturnValue().Set(
        static_cast<int>(fxv8::IsNull(argFirst) && fxv8::IsNull(argSecond)));
    return;
  }

  if (fxv8::IsString(argFirst) && fxv8::IsString(argSecond)) {
    info.GetReturnValue().Set(static_cast<int>(
        fxv8::ReentrantToByteStringHelper(info.GetIsolate(), argFirst) ==
        fxv8::ReentrantToByteStringHelper(info.GetIsolate(), argSecond)));
    return;
  }

  double first = ValueToDouble(info.GetIsolate(), argFirst);
  double second = ValueToDouble(info.GetIsolate(), argSecond);
  info.GetReturnValue().Set(static_cast<int>(first == second));
}

// static
void CFXJSE_FormCalcContext::notequality_operator(
    CFXJSE_HostObject* pThis,
    const v8::FunctionCallbackInfo<v8::Value>& info) {
  if (info.Length() != 2) {
    ToFormCalcContext(pThis)->ThrowCompilerErrorException();
    return;
  }

  if (fm_ref_equal(pThis, info)) {
    info.GetReturnValue().Set(0);
    return;
  }

  v8::Local<v8::Value> argFirst = GetSimpleValue(info, 0);
  v8::Local<v8::Value> argSecond = GetSimpleValue(info, 1);
  if (fxv8::IsNull(argFirst) || fxv8::IsNull(argSecond)) {
    info.GetReturnValue().Set(
        static_cast<int>(!fxv8::IsNull(argFirst) || !fxv8::IsNull(argSecond)));
    return;
  }

  if (fxv8::IsString(argFirst) && fxv8::IsString(argSecond)) {
    info.GetReturnValue().Set(static_cast<int>(
        fxv8::ReentrantToByteStringHelper(info.GetIsolate(), argFirst) !=
        fxv8::ReentrantToByteStringHelper(info.GetIsolate(), argSecond)));
    return;
  }

  double first = ValueToDouble(info.GetIsolate(), argFirst);
  double second = ValueToDouble(info.GetIsolate(), argSecond);
  info.GetReturnValue().Set(static_cast<int>(first != second));
}

// static
bool CFXJSE_FormCalcContext::fm_ref_equal(
    CFXJSE_HostObject* pThis,
    const v8::FunctionCallbackInfo<v8::Value>& info) {
  v8::Local<v8::Value> argFirst = info[0];
  v8::Local<v8::Value> argSecond = info[1];
  if (!fxv8::IsArray(argFirst) || !fxv8::IsArray(argSecond)) {
    return false;
  }

  v8::Local<v8::Array> firstArr = argFirst.As<v8::Array>();
  v8::Local<v8::Array> secondArr = argSecond.As<v8::Array>();
  v8::Local<v8::Value> firstFlag =
      fxv8::ReentrantGetArrayElementHelper(info.GetIsolate(), firstArr, 0);
  v8::Local<v8::Value> secondFlag =
      fxv8::ReentrantGetArrayElementHelper(info.GetIsolate(), secondArr, 0);
  if (fxv8::ReentrantToInt32Helper(info.GetIsolate(), firstFlag) != 3 ||
      fxv8::ReentrantToInt32Helper(info.GetIsolate(), secondFlag) != 3) {
    return false;
  }

  v8::Local<v8::Value> firstValue =
      fxv8::ReentrantGetArrayElementHelper(info.GetIsolate(), firstArr, 2);
  v8::Local<v8::Value> secondValue =
      fxv8::ReentrantGetArrayElementHelper(info.GetIsolate(), secondArr, 2);

  if (fxv8::IsNull(firstValue) || fxv8::IsNull(secondValue)) {
    return false;
  }

  return FXJSE_RetrieveObjectBinding(firstValue) ==
         FXJSE_RetrieveObjectBinding(secondValue);
}

// static
void CFXJSE_FormCalcContext::less_operator(
    CFXJSE_HostObject* pThis,
    const v8::FunctionCallbackInfo<v8::Value>& info) {
  if (info.Length() != 2) {
    ToFormCalcContext(pThis)->ThrowCompilerErrorException();
    return;
  }

  v8::Local<v8::Value> argFirst = GetSimpleValue(info, 0);
  v8::Local<v8::Value> argSecond = GetSimpleValue(info, 1);
  if (fxv8::IsNull(argFirst) || fxv8::IsNull(argSecond)) {
    info.GetReturnValue().Set(0);
    return;
  }

  if (fxv8::IsString(argFirst) && fxv8::IsString(argSecond)) {
    ByteString bs1 =
        fxv8::ReentrantToByteStringHelper(info.GetIsolate(), argFirst);
    ByteString bs2 =
        fxv8::ReentrantToByteStringHelper(info.GetIsolate(), argSecond);
    info.GetReturnValue().Set(bs1.Compare(bs2.AsStringView()) < 0);
    return;
  }

  double first = ValueToDouble(info.GetIsolate(), argFirst);
  double second = ValueToDouble(info.GetIsolate(), argSecond);
  info.GetReturnValue().Set(static_cast<int>(first < second));
}

// static
void CFXJSE_FormCalcContext::lessequal_operator(
    CFXJSE_HostObject* pThis,
    const v8::FunctionCallbackInfo<v8::Value>& info) {
  if (info.Length() != 2) {
    ToFormCalcContext(pThis)->ThrowCompilerErrorException();
    return;
  }

  v8::Local<v8::Value> argFirst = GetSimpleValue(info, 0);
  v8::Local<v8::Value> argSecond = GetSimpleValue(info, 1);
  if (fxv8::IsNull(argFirst) || fxv8::IsNull(argSecond)) {
    info.GetReturnValue().Set(
        static_cast<int>(fxv8::IsNull(argFirst) && fxv8::IsNull(argSecond)));
    return;
  }

  if (fxv8::IsString(argFirst) && fxv8::IsString(argSecond)) {
    auto bs1 = fxv8::ReentrantToByteStringHelper(info.GetIsolate(), argFirst);
    auto bs2 = fxv8::ReentrantToByteStringHelper(info.GetIsolate(), argSecond);
    info.GetReturnValue().Set(bs1.Compare(bs2.AsStringView()) <= 0);
    return;
  }

  double first = ValueToDouble(info.GetIsolate(), argFirst);
  double second = ValueToDouble(info.GetIsolate(), argSecond);
  info.GetReturnValue().Set(static_cast<int>(first <= second));
}

// static
void CFXJSE_FormCalcContext::greater_operator(
    CFXJSE_HostObject* pThis,
    const v8::FunctionCallbackInfo<v8::Value>& info) {
  if (info.Length() != 2) {
    ToFormCalcContext(pThis)->ThrowCompilerErrorException();
    return;
  }

  v8::Local<v8::Value> argFirst = GetSimpleValue(info, 0);
  v8::Local<v8::Value> argSecond = GetSimpleValue(info, 1);
  if (fxv8::IsNull(argFirst) || fxv8::IsNull(argSecond)) {
    info.GetReturnValue().Set(0);
    return;
  }

  if (fxv8::IsString(argFirst) && fxv8::IsString(argSecond)) {
    auto bs1 = fxv8::ReentrantToByteStringHelper(info.GetIsolate(), argFirst);
    auto bs2 = fxv8::ReentrantToByteStringHelper(info.GetIsolate(), argSecond);
    info.GetReturnValue().Set(bs1.Compare(bs2.AsStringView()) > 0);
    return;
  }

  double first = ValueToDouble(info.GetIsolate(), argFirst);
  double second = ValueToDouble(info.GetIsolate(), argSecond);
  info.GetReturnValue().Set(static_cast<int>(first > second));
}

// static
void CFXJSE_FormCalcContext::greaterequal_operator(
    CFXJSE_HostObject* pThis,
    const v8::FunctionCallbackInfo<v8::Value>& info) {
  if (info.Length() != 2) {
    ToFormCalcContext(pThis)->ThrowCompilerErrorException();
    return;
  }

  v8::Local<v8::Value> argFirst = GetSimpleValue(info, 0);
  v8::Local<v8::Value> argSecond = GetSimpleValue(info, 1);
  if (fxv8::IsNull(argFirst) || fxv8::IsNull(argSecond)) {
    info.GetReturnValue().Set(
        static_cast<int>(fxv8::IsNull(argFirst) && fxv8::IsNull(argSecond)));
    return;
  }

  if (fxv8::IsString(argFirst) && fxv8::IsString(argSecond)) {
    auto bs1 = fxv8::ReentrantToByteStringHelper(info.GetIsolate(), argFirst);
    auto bs2 = fxv8::ReentrantToByteStringHelper(info.GetIsolate(), argSecond);
    info.GetReturnValue().Set(bs1.Compare(bs2.AsStringView()) >= 0);
    return;
  }

  double first = ValueToDouble(info.GetIsolate(), argFirst);
  double second = ValueToDouble(info.GetIsolate(), argSecond);
  info.GetReturnValue().Set(static_cast<int>(first >= second));
}

// static
void CFXJSE_FormCalcContext::plus_operator(
    CFXJSE_HostObject* pThis,
    const v8::FunctionCallbackInfo<v8::Value>& info) {
  if (info.Length() != 2) {
    ToFormCalcContext(pThis)->ThrowCompilerErrorException();
    return;
  }

  if (ValueIsNull(info.GetIsolate(), info[0]) &&
      ValueIsNull(info.GetIsolate(), info[1])) {
    info.GetReturnValue().SetNull();
    return;
  }

  const double first = ValueToDouble(info.GetIsolate(), info[0]);
  const double second = ValueToDouble(info.GetIsolate(), info[1]);
  info.GetReturnValue().Set(first + second);
}

// static
void CFXJSE_FormCalcContext::minus_operator(
    CFXJSE_HostObject* pThis,
    const v8::FunctionCallbackInfo<v8::Value>& info) {
  if (info.Length() != 2) {
    ToFormCalcContext(pThis)->ThrowCompilerErrorException();
    return;
  }

  v8::Local<v8::Value> argFirst = GetSimpleValue(info, 0);
  v8::Local<v8::Value> argSecond = GetSimpleValue(info, 1);
  if (fxv8::IsNull(argFirst) && fxv8::IsNull(argSecond)) {
    info.GetReturnValue().SetNull();
    return;
  }

  double first = ValueToDouble(info.GetIsolate(), argFirst);
  double second = ValueToDouble(info.GetIsolate(), argSecond);
  info.GetReturnValue().Set(first - second);
}

// static
void CFXJSE_FormCalcContext::multiple_operator(
    CFXJSE_HostObject* pThis,
    const v8::FunctionCallbackInfo<v8::Value>& info) {
  if (info.Length() != 2) {
    ToFormCalcContext(pThis)->ThrowCompilerErrorException();
    return;
  }

  v8::Local<v8::Value> argFirst = GetSimpleValue(info, 0);
  v8::Local<v8::Value> argSecond = GetSimpleValue(info, 1);
  if (fxv8::IsNull(argFirst) && fxv8::IsNull(argSecond)) {
    info.GetReturnValue().SetNull();
    return;
  }

  double first = ValueToDouble(info.GetIsolate(), argFirst);
  double second = ValueToDouble(info.GetIsolate(), argSecond);
  info.GetReturnValue().Set(first * second);
}

// static
void CFXJSE_FormCalcContext::divide_operator(
    CFXJSE_HostObject* pThis,
    const v8::FunctionCallbackInfo<v8::Value>& info) {
  CFXJSE_FormCalcContext* context = ToFormCalcContext(pThis);
  if (info.Length() != 2) {
    context->ThrowCompilerErrorException();
    return;
  }

  v8::Local<v8::Value> argFirst = GetSimpleValue(info, 0);
  v8::Local<v8::Value> argSecond = GetSimpleValue(info, 1);
  if (fxv8::IsNull(argFirst) && fxv8::IsNull(argSecond)) {
    info.GetReturnValue().SetNull();
    return;
  }

  double second = ValueToDouble(info.GetIsolate(), argSecond);
  if (second == 0.0) {
    context->ThrowDivideByZeroException();
    return;
  }

  double first = ValueToDouble(info.GetIsolate(), argFirst);
  info.GetReturnValue().Set(first / second);
}

// static
void CFXJSE_FormCalcContext::positive_operator(
    CFXJSE_HostObject* pThis,
    const v8::FunctionCallbackInfo<v8::Value>& info) {
  if (info.Length() != 1) {
    ToFormCalcContext(pThis)->ThrowCompilerErrorException();
    return;
  }

  v8::Local<v8::Value> argOne = GetSimpleValue(info, 0);
  if (fxv8::IsNull(argOne)) {
    info.GetReturnValue().SetNull();
    return;
  }
  info.GetReturnValue().Set(0.0 + ValueToDouble(info.GetIsolate(), argOne));
}

// static
void CFXJSE_FormCalcContext::negative_operator(
    CFXJSE_HostObject* pThis,
    const v8::FunctionCallbackInfo<v8::Value>& info) {
  if (info.Length() != 1) {
    ToFormCalcContext(pThis)->ThrowCompilerErrorException();
    return;
  }

  v8::Local<v8::Value> argOne = GetSimpleValue(info, 0);
  if (fxv8::IsNull(argOne)) {
    info.GetReturnValue().SetNull();
    return;
  }
  info.GetReturnValue().Set(0.0 - ValueToDouble(info.GetIsolate(), argOne));
}

// static
void CFXJSE_FormCalcContext::logical_not_operator(
    CFXJSE_HostObject* pThis,
    const v8::FunctionCallbackInfo<v8::Value>& info) {
  if (info.Length() != 1) {
    ToFormCalcContext(pThis)->ThrowCompilerErrorException();
    return;
  }

  v8::Local<v8::Value> argOne = GetSimpleValue(info, 0);
  if (fxv8::IsNull(argOne)) {
    info.GetReturnValue().SetNull();
    return;
  }

  double first = ValueToDouble(info.GetIsolate(), argOne);
  info.GetReturnValue().Set((first == 0.0) ? 1 : 0);
}

// static
void CFXJSE_FormCalcContext::dot_accessor(
    CFXJSE_HostObject* pThis,
    const v8::FunctionCallbackInfo<v8::Value>& info) {
  DotAccessorCommon(pThis, info, /*bDotAccessor=*/true);
}

// static
void CFXJSE_FormCalcContext::dotdot_accessor(
    CFXJSE_HostObject* pThis,
    const v8::FunctionCallbackInfo<v8::Value>& info) {
  DotAccessorCommon(pThis, info, /*bDotAccessor=*/false);
}

// static
void CFXJSE_FormCalcContext::eval_translation(
    CFXJSE_HostObject* pThis,
    const v8::FunctionCallbackInfo<v8::Value>& info) {
  CFXJSE_FormCalcContext* context = ToFormCalcContext(pThis);
  if (info.Length() != 1) {
    context->ThrowParamCountMismatchException("Eval");
    return;
  }

  v8::Local<v8::Value> argOne = GetSimpleValue(info, 0);
  ByteString bsArg = ValueToUTF8String(info.GetIsolate(), argOne);
  if (bsArg.IsEmpty()) {
    context->ThrowArgumentMismatchException();
    return;
  }

  WideString wsCalcScript = WideString::FromUTF8(bsArg.AsStringView());
  std::optional<WideTextBuffer> wsJavaScriptBuf =
      CFXJSE_FormCalcContext::Translate(context->GetDocument()->GetHeap(),
                                        wsCalcScript.AsStringView());
  if (!wsJavaScriptBuf.has_value()) {
    context->ThrowCompilerErrorException();
    return;
  }
  info.GetReturnValue().Set(fxv8::NewStringHelper(
      info.GetIsolate(),
      FX_UTF8Encode(wsJavaScriptBuf.value().AsStringView()).AsStringView()));
}

// static
void CFXJSE_FormCalcContext::is_fm_object(
    CFXJSE_HostObject* pThis,
    const v8::FunctionCallbackInfo<v8::Value>& info) {
  const bool result = info.Length() == 1 && fxv8::IsObject(info[0]);
  info.GetReturnValue().Set(result);
}

// static
void CFXJSE_FormCalcContext::is_fm_array(
    CFXJSE_HostObject* pThis,
    const v8::FunctionCallbackInfo<v8::Value>& info) {
  const bool result = info.Length() == 1 && fxv8::IsArray(info[0]);
  info.GetReturnValue().Set(result);
}

// static
void CFXJSE_FormCalcContext::get_fm_value(
    CFXJSE_HostObject* pThis,
    const v8::FunctionCallbackInfo<v8::Value>& info) {
  CFXJSE_FormCalcContext* context = ToFormCalcContext(pThis);
  if (info.Length() != 1) {
    context->ThrowCompilerErrorException();
    return;
  }

  v8::Local<v8::Value> argOne = info[0];
  if (fxv8::IsArray(argOne)) {
    v8::Local<v8::Array> arr = argOne.As<v8::Array>();
    v8::Local<v8::Value> propertyValue =
        fxv8::ReentrantGetArrayElementHelper(info.GetIsolate(), arr, 1);
    v8::Local<v8::Value> jsValue =
        fxv8::ReentrantGetArrayElementHelper(info.GetIsolate(), arr, 2);
    if (!fxv8::IsObject(jsValue)) {
      info.GetReturnValue().Set(fxv8::NewUndefinedHelper(info.GetIsolate()));
      return;
    }
    v8::Local<v8::Object> jsObjectValue = jsValue.As<v8::Object>();
    if (fxv8::IsNull(propertyValue)) {
      info.GetReturnValue().Set(
          GetObjectDefaultValue(info.GetIsolate(), jsObjectValue));
      return;
    }
    ByteString bsName =
        fxv8::ReentrantToByteStringHelper(info.GetIsolate(), propertyValue);
    info.GetReturnValue().Set(fxv8::ReentrantGetObjectPropertyHelper(
        info.GetIsolate(), jsObjectValue, bsName.AsStringView()));
    return;
  }

  if (fxv8::IsObject(argOne)) {
    v8::Local<v8::Object> obj = argOne.As<v8::Object>();
    info.GetReturnValue().Set(GetObjectDefaultValue(info.GetIsolate(), obj));
    return;
  }

  info.GetReturnValue().Set(argOne);
}

// static
void CFXJSE_FormCalcContext::get_fm_jsobj(
    CFXJSE_HostObject* pThis,
    const v8::FunctionCallbackInfo<v8::Value>& info) {
  if (info.Length() != 1) {
    ToFormCalcContext(pThis)->ThrowCompilerErrorException();
    return;
  }

  v8::Local<v8::Value> argOne = info[0];
  if (!fxv8::IsArray(argOne)) {
    info.GetReturnValue().Set(argOne);
    return;
  }

  v8::Local<v8::Array> arr = argOne.As<v8::Array>();
  info.GetReturnValue().Set(
      fxv8::ReentrantGetArrayElementHelper(info.GetIsolate(), arr, 2));
}

// static
void CFXJSE_FormCalcContext::fm_var_filter(
    CFXJSE_HostObject* pThis,
    const v8::FunctionCallbackInfo<v8::Value>& info) {
  CFXJSE_FormCalcContext* context = ToFormCalcContext(pThis);
  if (info.Length() != 1) {
    context->ThrowCompilerErrorException();
    return;
  }

  v8::Local<v8::Value> argOne = info[0];
  if (!fxv8::IsArray(argOne)) {
    info.GetReturnValue().Set(GetSimpleValue(info, 0));
    return;
  }

  v8::Local<v8::Array> arr = argOne.As<v8::Array>();
  v8::Local<v8::Value> flagsValue =
      fxv8::ReentrantGetArrayElementHelper(info.GetIsolate(), arr, 0);
  int32_t iFlags = fxv8::ReentrantToInt32Helper(info.GetIsolate(), flagsValue);
  if (iFlags != 3 && iFlags != 4) {
    info.GetReturnValue().Set(GetSimpleValue(info, 0));
    return;
  }

  if (iFlags == 4) {
    v8::LocalVector<v8::Value> values(info.GetIsolate(), 3);
    values[0] = fxv8::NewNumberHelper(info.GetIsolate(), 3);
    values[1] = fxv8::NewNullHelper(info.GetIsolate());
    values[2] = fxv8::NewNullHelper(info.GetIsolate());
    info.GetReturnValue().Set(fxv8::NewArrayHelper(info.GetIsolate(), values));
    return;
  }

  v8::Local<v8::Value> objectValue =
      fxv8::ReentrantGetArrayElementHelper(info.GetIsolate(), arr, 2);
  if (fxv8::IsNull(objectValue)) {
    context->ThrowCompilerErrorException();
    return;
  }
  info.GetReturnValue().Set(argOne);
}

// static
void CFXJSE_FormCalcContext::concat_fm_object(
    CFXJSE_HostObject* pThis,
    const v8::FunctionCallbackInfo<v8::Value>& info) {
  v8::Isolate* pIsolate = ToFormCalcContext(pThis)->GetIsolate();
  v8::LocalVector<v8::Value> returnValues(pIsolate);
  for (int i = 0; i < info.Length(); ++i) {
    if (fxv8::IsArray(info[i])) {
      v8::Local<v8::Array> arr = info[i].As<v8::Array>();
      uint32_t length = fxv8::GetArrayLengthHelper(arr);
      for (uint32_t j = 2; j < length; j++) {
        returnValues.push_back(
            fxv8::ReentrantGetArrayElementHelper(info.GetIsolate(), arr, j));
      }
    }
    returnValues.push_back(info[i]);
  }
  info.GetReturnValue().Set(fxv8::NewArrayHelper(pIsolate, returnValues));
}

// static
ByteString CFXJSE_FormCalcContext::GenerateSomExpression(ByteStringView bsName,
                                                         int32_t index_flags,
                                                         int32_t index_value,
                                                         bool bIsStar) {
  if (bIsStar) {
    return ByteString(bsName, "[*]");
  }

  // `index_flags` values are the same as enum class
  // `CXFA_FMIndexExpression::AccessorIndex` values.
  if (index_flags == 0) {
    return ByteString(bsName);
  }

  if (index_flags == 1 || index_value == 0) {
    return ByteString(bsName, "[") + ByteString::FormatInteger(index_value) +
           "]";
  }

  const bool bNegative = index_value < 0;
  ByteString bsSomExp(bsName);
  if (index_flags == 2) {
    bsSomExp += bNegative ? "[-" : "[+";
  } else {
    DCHECK_EQ(index_flags, 3);
    bsSomExp += bNegative ? "[" : "[-";
  }

  FX_SAFE_INT32 safe_index = index_value;
  if (bNegative) {
    safe_index = -safe_index;
  }
  bsSomExp += ByteString::FormatInteger(safe_index.ValueOrDefault(0));
  bsSomExp += "]";
  return bsSomExp;
}

std::optional<WideTextBuffer> CFXJSE_FormCalcContext::Translate(
    cppgc::Heap* pHeap,
    WideStringView wsFormcalc) {
  if (wsFormcalc.IsEmpty()) {
    return WideTextBuffer();
  }

  CXFA_FMLexer lexer(wsFormcalc);
  CXFA_FMParser parser(pHeap, &lexer);
  CXFA_FMAST* ast = parser.Parse();
  if (!ast || parser.HasError()) {
    return std::nullopt;
  }

  CXFA_FMToJavaScriptDepth::Reset();
  std::optional<WideTextBuffer> wsJavaScript = ast->ToJavaScript();
  if (!wsJavaScript.has_value()) {
    return std::nullopt;
  }

  if (CXFA_IsTooBig(wsJavaScript.value())) {
    return std::nullopt;
  }

  return wsJavaScript;
}

CFXJSE_FormCalcContext::CFXJSE_FormCalcContext(v8::Isolate* pIsolate,
                                               CFXJSE_Context* pScriptContext,
                                               CXFA_Document* doc)
    : isolate_(pIsolate), document_(doc) {
  value_.Reset(isolate_,
               NewBoundV8Object(
                   isolate_, CFXJSE_Class::Create(pScriptContext,
                                                  &kFormCalcDescriptor, false)
                                 ->GetTemplate(isolate_)));
}

CFXJSE_FormCalcContext::~CFXJSE_FormCalcContext() = default;

CFXJSE_FormCalcContext* CFXJSE_FormCalcContext::AsFormCalcContext() {
  return this;
}

v8::Local<v8::Value> CFXJSE_FormCalcContext::GlobalPropertyGetter() {
  return v8::Local<v8::Value>::New(isolate_, value_);
}

// static
void CFXJSE_FormCalcContext::DotAccessorCommon(
    CFXJSE_HostObject* pThis,
    const v8::FunctionCallbackInfo<v8::Value>& info,
    bool bDotAccessor) {
  CFXJSE_FormCalcContext* context = ToFormCalcContext(pThis);
  v8::Isolate* pIsolate = context->GetIsolate();
  int32_t argc = info.Length();
  if (argc < 4 || argc > 5) {
    context->ThrowCompilerErrorException();
    return;
  }

  bool bIsStar = true;
  int32_t index_value = 0;
  if (argc > 4) {
    bIsStar = false;
    index_value = ValueToInteger(info.GetIsolate(), info[4]);
  }

  const ByteString bsName =
      fxv8::ReentrantToByteStringHelper(info.GetIsolate(), info[2]);
  const bool bHasNoResolveName = bDotAccessor && bsName.IsEmpty();
  ByteString bsSomExp = GenerateSomExpression(
      bsName.AsStringView(),
      fxv8::ReentrantToInt32Helper(info.GetIsolate(), info[3]), index_value,
      bIsStar);

  v8::Local<v8::Value> argAccessor = info[0];
  if (fxv8::IsArray(argAccessor)) {
    v8::Local<v8::Array> arr = argAccessor.As<v8::Array>();
    uint32_t iLength = fxv8::GetArrayLengthHelper(arr);
    if (iLength < 3) {
      context->ThrowArgumentMismatchException();
      return;
    }

    // TODO(crbug.com/42271105) - doublecheck use of std::vector
    std::vector<v8::LocalVector<v8::Value>> resolveValues(
        iLength - 2, v8::LocalVector<v8::Value>(info.GetIsolate()));

    bool bAttribute = false;
    bool bAllEmpty = true;
    for (uint32_t i = 2; i < iLength; i++) {
      v8::Local<v8::Value> hJSObjValue =
          fxv8::ReentrantGetArrayElementHelper(info.GetIsolate(), arr, i);
      std::optional<CFXJSE_Engine::ResolveResult> maybeResult =
          ResolveObjects(pThis, hJSObjValue, bsSomExp.AsStringView(),
                         bDotAccessor, bHasNoResolveName);
      if (maybeResult.has_value()) {
        resolveValues[i - 2] = ParseResolveResult(pThis, maybeResult.value(),
                                                  hJSObjValue, &bAttribute);
        bAllEmpty = bAllEmpty && resolveValues[i - 2].empty();
      }
    }
    if (bAllEmpty) {
      context->ThrowPropertyNotInObjectException(bsName.AsStringView(),
                                                 bsSomExp.AsStringView());
      return;
    }

    v8::LocalVector<v8::Value> values(pIsolate);
    values.push_back(fxv8::NewNumberHelper(pIsolate, 1));
    values.push_back(
        bAttribute ? fxv8::NewStringHelper(pIsolate, bsName.AsStringView())
                         .As<v8::Value>()
                   : fxv8::NewNullHelper(pIsolate).As<v8::Value>());
    for (uint32_t i = 0; i < iLength - 2; i++) {
      for (size_t j = 0; j < resolveValues[i].size(); j++) {
        values.push_back(resolveValues[i][j]);
      }
    }
    info.GetReturnValue().Set(fxv8::NewArrayHelper(pIsolate, values));
    return;
  }

  std::optional<CFXJSE_Engine::ResolveResult> maybeResult;
  ByteString bsAccessorName =
      fxv8::ReentrantToByteStringHelper(info.GetIsolate(), info[1]);
  if (fxv8::IsObject(argAccessor) ||
      (fxv8::IsNull(argAccessor) && bsAccessorName.IsEmpty())) {
    maybeResult = ResolveObjects(pThis, argAccessor, bsSomExp.AsStringView(),
                                 bDotAccessor, bHasNoResolveName);
  } else if (!fxv8::IsObject(argAccessor) && !bsAccessorName.IsEmpty()) {
    v8::Local<v8::Value> obj =
        GetObjectForName(pThis, bsAccessorName.AsStringView());
    if (!obj.IsEmpty()) {
      argAccessor = obj;
      maybeResult = ResolveObjects(pThis, argAccessor, bsSomExp.AsStringView(),
                                   bDotAccessor, bHasNoResolveName);
    }
  }
  if (!maybeResult.has_value()) {
    context->ThrowPropertyNotInObjectException(bsName.AsStringView(),
                                               bsSomExp.AsStringView());
    return;
  }

  bool bAttribute = false;
  v8::LocalVector<v8::Value> resolveValues =
      ParseResolveResult(pThis, maybeResult.value(), argAccessor, &bAttribute);

  v8::LocalVector<v8::Value> values(pIsolate, resolveValues.size() + 2);
  values[0] = fxv8::NewNumberHelper(pIsolate, 1);
  values[1] = bAttribute
                  ? fxv8::NewStringHelper(pIsolate, bsName.AsStringView())
                        .As<v8::Value>()
                  : fxv8::NewNullHelper(pIsolate).As<v8::Value>();

  for (size_t i = 0; i < resolveValues.size(); i++) {
    values[i + 2] = resolveValues[i];
  }

  info.GetReturnValue().Set(fxv8::NewArrayHelper(pIsolate, values));
}

// static
bool CFXJSE_FormCalcContext::IsIsoDateFormat(ByteStringView bsData,
                                             int32_t* pYear,
                                             int32_t* pMonth,
                                             int32_t* pDay) {
  pdfium::span<const char> pData = bsData.span();

  int32_t& iYear = *pYear;
  int32_t& iMonth = *pMonth;
  int32_t& iDay = *pDay;

  iYear = 0;
  iMonth = 1;
  iDay = 1;

  if (pData.size() < 4) {
    return false;
  }

  std::array<char, 5> szYear = {};
  for (int32_t i = 0; i < 4; ++i) {
    if (!FXSYS_IsDecimalDigit(pData[i])) {
      return false;
    }
    szYear[i] = pData[i];
  }
  iYear = FXSYS_atoi(szYear.data());
  if (pData.size() == 4) {
    return true;
  }

  int32_t iStyle = pData[4] == '-' ? 1 : 0;
  size_t iPosOff = iStyle == 0 ? 4 : 5;
  if (!FXSYS_IsDecimalDigit(pData[iPosOff]) ||
      !FXSYS_IsDecimalDigit(pData[iPosOff + 1])) {
    return false;
  }

  char szBuffer[3] = {};
  szBuffer[0] = pData[iPosOff];
  szBuffer[1] = pData[iPosOff + 1];
  iMonth = FXSYS_atoi(szBuffer);
  if (iMonth > 12 || iMonth < 1) {
    return false;
  }

  if (iStyle == 0) {
    iPosOff += 2;
    if (pData.size() == 6) {
      return true;
    }
  } else {
    iPosOff += 3;
    if (pData.size() == 7) {
      return true;
    }
  }
  if (!FXSYS_IsDecimalDigit(pData[iPosOff]) ||
      !FXSYS_IsDecimalDigit(pData[iPosOff + 1])) {
    return false;
  }

  szBuffer[0] = pData[iPosOff];
  szBuffer[1] = pData[iPosOff + 1];
  iDay = FXSYS_atoi(szBuffer);
  if (iPosOff + 2 < pData.size()) {
    return false;
  }

  if (iMonth == 2) {
    bool bIsLeap = (!(iYear % 4) && (iYear % 100)) || !(iYear % 400);
    return iDay <= (bIsLeap ? 29 : 28);
  }

  if (iMonth < 8) {
    return iDay <= (iMonth % 2 == 0 ? 30 : 31);
  }
  return iDay <= (iMonth % 2 == 0 ? 31 : 30);
}

// static
bool CFXJSE_FormCalcContext::IsIsoTimeFormat(ByteStringView bsData) {
  enum State { kHour, kMinute, kSecond, kZoneHour, kZoneMinute, kFinished };

  pdfium::span<const char> pData = bsData.span();
  if (pData.empty()) {
    return false;
  }

  size_t iZone = 0;
  size_t i = 0;
  while (i < pData.size()) {
    if (!FXSYS_IsDecimalDigit(pData[i]) && pData[i] != ':') {
      iZone = i;
      break;
    }
    ++i;
  }
  if (i == pData.size()) {
    iZone = pData.size();
  }

  char szBuffer[3] = {};  // Last char always stays NUL for termination.
  State state = kHour;
  size_t index = 0;
  while (index + 1 < iZone) {
    szBuffer[0] = pData[index];
    szBuffer[1] = pData[index + 1];
    if (!FXSYS_IsDecimalDigit(szBuffer[0]) ||
        !FXSYS_IsDecimalDigit(szBuffer[1])) {
      return false;
    }
    int32_t value = FXSYS_atoi(szBuffer);
    if (state == kHour) {
      if (value >= 24) {
        return false;
      }
      state = kMinute;
    } else if (state == kMinute) {
      if (value >= 60) {
        return false;
      }
      state = kSecond;
    } else if (state == kSecond) {
      // Allow leap second.
      if (value > 60) {
        return false;
      }
      state = kFinished;
    } else {
      return false;
    }
    index += 2;
    if (index < iZone && pData[index] == ':') {
      ++index;
    }
  }

  if (index < pData.size() && pData[index] == '.') {
    static constexpr int kSubSecondLength = 3;
    if (index + kSubSecondLength >= pData.size()) {
      return false;
    }

    ++index;
    std::array<char, kSubSecondLength + 1> szMilliSeconds = {};
    for (int j = 0; j < kSubSecondLength; ++j) {
      char c = pData[index + j];
      if (!FXSYS_IsDecimalDigit(c)) {
        return false;
      }
      szMilliSeconds[j] = c;
    }
    if (FXSYS_atoi(szMilliSeconds.data()) >= 1000) {
      return false;
    }
    index += kSubSecondLength;
  }

  if (index < pData.size() && FXSYS_towlower(pData[index]) == 'z') {
    return true;
  }

  if (index < pData.size()) {
    if (pData[index] == '+') {
      ++index;
    } else if (pData[index] == '-') {
      ++index;
    }
  }
  state = kZoneHour;
  while (index + 1 < pData.size()) {
    szBuffer[0] = pData[index];
    szBuffer[1] = pData[index + 1];
    if (!FXSYS_IsDecimalDigit(szBuffer[0]) ||
        !FXSYS_IsDecimalDigit(szBuffer[1])) {
      return false;
    }
    int32_t value = FXSYS_atoi(szBuffer);
    if (state == kZoneHour) {
      if (value >= 24) {
        return false;
      }
      state = kZoneMinute;
    } else if (state == kZoneMinute) {
      if (value >= 60) {
        return false;
      }
      state = kFinished;
    } else {
      return false;
    }
    index += 2;
    if (index < pData.size() && pData[index] == ':') {
      ++index;
    }
  }

  // Success if all input was processed.
  return index == pData.size();
}

bool CFXJSE_FormCalcContext::IsIsoDateTimeFormat(ByteStringView bsData,
                                                 int32_t* pYear,
                                                 int32_t* pMonth,
                                                 int32_t* pDay) {
  *pYear = 0;
  *pMonth = 0;
  *pDay = 0;

  size_t index = 0;
  while (index < bsData.GetLength()) {
    if (bsData[index] == 'T' || bsData[index] == 't') {
      break;
    }
    ++index;
  }
  if (index == bsData.GetLength() || (index != 8 && index != 10)) {
    return false;
  }

  ByteStringView date_part = bsData.First(index);
  ByteStringView time_part = bsData.Substr(index + 1);
  return IsIsoDateFormat(date_part, pYear, pMonth, pDay) &&
         IsIsoTimeFormat(time_part);
}

// static
int32_t CFXJSE_FormCalcContext::DateString2Num(ByteStringView bsDate) {
  int32_t iYear = 0;
  int32_t iMonth = 0;
  int32_t iDay = 0;
  if (bsDate.GetLength() <= 10) {
    if (!IsIsoDateFormat(bsDate, &iYear, &iMonth, &iDay)) {
      return 0;
    }
  } else {
    if (!IsIsoDateTimeFormat(bsDate, &iYear, &iMonth, &iDay)) {
      return 0;
    }
  }

  float dDays = 0;
  int32_t i = 1;
  if (iYear < 1900) {
    return 0;
  }

  while (iYear - i >= 1900) {
    dDays +=
        ((!((iYear - i) % 4) && ((iYear - i) % 100)) || !((iYear - i) % 400))
            ? 366
            : 365;
    ++i;
  }
  i = 1;
  while (i < iMonth) {
    if (i == 2) {
      dDays += ((!(iYear % 4) && (iYear % 100)) || !(iYear % 400)) ? 29 : 28;
    } else if (i <= 7) {
      dDays += (i % 2 == 0) ? 30 : 31;
    } else {
      dDays += (i % 2 == 0) ? 31 : 30;
    }

    ++i;
  }
  i = 0;
  while (iDay - i > 0) {
    ++dDays;
    ++i;
  }
  return static_cast<int32_t>(dDays);
}

bool CFXJSE_FormCalcContext::ApplyToExpansion(
    std::function<void(v8::Isolate*, v8::Local<v8::Value>)> fn,
    const v8::FunctionCallbackInfo<v8::Value>& info,
    bool bStrict) {
  v8::Isolate* pIsolate = info.GetIsolate();
  for (int32_t i = 0; i < info.Length(); i++) {
    v8::Local<v8::Value> argValue = info[i];
    if (fxv8::IsArray(argValue)) {
      if (!ApplyToArray(pIsolate, fn, argValue.As<v8::Array>()) && bStrict) {
        ThrowArgumentMismatchException();
        return false;
      }
    } else if (fxv8::IsObject(argValue)) {
      ApplyToObject(pIsolate, fn, argValue.As<v8::Object>());
    } else if (!fxv8::IsNull(argValue)) {
      fn(pIsolate, argValue);
    }
  }
  return true;
}

bool CFXJSE_FormCalcContext::ApplyToArray(
    v8::Isolate* pIsolate,
    std::function<void(v8::Isolate*, v8::Local<v8::Value>)> fn,
    v8::Local<v8::Array> pArray) {
  uint32_t iLength = fxv8::GetArrayLengthHelper(pArray);
  if (iLength < 3) {
    return false;
  }

  v8::Local<v8::Value> propertyValue =
      fxv8::ReentrantGetArrayElementHelper(pIsolate, pArray, 1);

  ByteString bsName;
  const bool nullprop = fxv8::IsNull(propertyValue);
  if (!nullprop) {
    bsName = fxv8::ReentrantToByteStringHelper(pIsolate, propertyValue);
  }

  for (uint32_t j = 2; j < iLength; j++) {
    v8::Local<v8::Value> jsValue =
        fxv8::ReentrantGetArrayElementHelper(pIsolate, pArray, j);
    if (!fxv8::IsObject(jsValue)) {
      continue;
    }

    v8::Local<v8::Object> jsObjectValue = jsValue.As<v8::Object>();
    v8::Local<v8::Value> newPropertyValue =
        nullprop ? GetObjectDefaultValue(pIsolate, jsObjectValue)
                 : fxv8::ReentrantGetObjectPropertyHelper(
                       pIsolate, jsObjectValue, bsName.AsStringView());
    if (!fxv8::IsNull(newPropertyValue)) {
      fn(pIsolate, newPropertyValue);
    }
  }
  return true;
}

void CFXJSE_FormCalcContext::ApplyToObject(
    v8::Isolate* pIsolate,
    std::function<void(v8::Isolate*, v8::Local<v8::Value>)> fn,
    v8::Local<v8::Object> pObject) {
  v8::Local<v8::Value> newPropertyValue =
      GetObjectDefaultValue(pIsolate, pObject);
  if (!fxv8::IsNull(newPropertyValue)) {
    fn(pIsolate, newPropertyValue);
  }
}

void CFXJSE_FormCalcContext::ThrowNoDefaultPropertyException(
    ByteStringView name) const {
  ByteString msg(name);
  msg += " doesn't have a default property.";
  ThrowException(msg.AsStringView());
}

void CFXJSE_FormCalcContext::ThrowCompilerErrorException() const {
  ThrowException("Compiler error.");
}

void CFXJSE_FormCalcContext::ThrowDivideByZeroException() const {
  ThrowException("Divide by zero.");
}

void CFXJSE_FormCalcContext::ThrowServerDeniedException() const {
  ThrowException("Server does not permit operation.");
}

void CFXJSE_FormCalcContext::ThrowPropertyNotInObjectException(
    ByteStringView name,
    ByteStringView exp) const {
  ByteString msg("An attempt was made to reference property '");
  msg += name;
  msg += "' of a non-object in SOM expression ";
  msg += exp;
  msg += ".";
  ThrowException(msg.AsStringView());
}

void CFXJSE_FormCalcContext::ThrowParamCountMismatchException(
    ByteStringView method) const {
  ByteString msg("Incorrect number of parameters calling method '");
  msg += method;
  msg += "'.";
  ThrowException(msg.AsStringView());
}

void CFXJSE_FormCalcContext::ThrowArgumentMismatchException() const {
  ThrowException("Argument mismatch in property or function argument.");
}

void CFXJSE_FormCalcContext::ThrowException(ByteStringView str) const {
  DCHECK(!str.IsEmpty());
  FXJSE_ThrowMessage(GetIsolate(), str);
}
