/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/**
 * PluralRules internal properties.
 *
 * 9.1 Internal slots of Service Constructors
 * 16.2.3 Properties of the Intl.PluralRules Constructor, Internal slots
 *
 * ES2024 Intl draft rev 74ca7099f103d143431b2ea422ae640c6f43e3e6
 */
var pluralRulesInternalProperties = {
  localeData: pluralRulesLocaleData,
  relevantExtensionKeys: [],
};

function pluralRulesLocaleData() {
  // PluralRules don't support any extension keys.
  return {};
}

/**
 * 16.1.2 InitializePluralRules ( pluralRules, locales, options )
 *
 * Compute an internal properties object from |lazyPluralRulesData|.
 *
 * ES2024 Intl draft rev 74ca7099f103d143431b2ea422ae640c6f43e3e6
 */
function resolvePluralRulesInternals(lazyPluralRulesData) {
  assert(IsObject(lazyPluralRulesData), "lazy data not an object?");

  var internalProps = std_Object_create(null);

  var PluralRules = pluralRulesInternalProperties;

  // Compute effective locale.

  // Step 9.
  var localeData = PluralRules.localeData;

  // Step 10.
  const r = ResolveLocale(
    "PluralRules",
    lazyPluralRulesData.requestedLocales,
    lazyPluralRulesData.opt,
    PluralRules.relevantExtensionKeys,
    localeData
  );

  // Step 11.
  internalProps.locale = r.locale;

  // Step 7.
  internalProps.type = lazyPluralRulesData.type;

  // Step 8. SetNumberFormatDigitOptions, step 6.
  internalProps.minimumIntegerDigits = lazyPluralRulesData.minimumIntegerDigits;

  // Step 8. SetNumberFormatDigitOptions, step 14.
  internalProps.roundingIncrement = lazyPluralRulesData.roundingIncrement;

  // Step 8. SetNumberFormatDigitOptions, step 15.
  internalProps.roundingMode = lazyPluralRulesData.roundingMode;

  // Step 8. SetNumberFormatDigitOptions, step 16.
  internalProps.trailingZeroDisplay = lazyPluralRulesData.trailingZeroDisplay;

  // Step 8. SetNumberFormatDigitOptions, steps 25-26.
  if ("minimumFractionDigits" in lazyPluralRulesData) {
    assert(
      "maximumFractionDigits" in lazyPluralRulesData,
      "min/max frac digits mismatch"
    );
    internalProps.minimumFractionDigits =
      lazyPluralRulesData.minimumFractionDigits;
    internalProps.maximumFractionDigits =
      lazyPluralRulesData.maximumFractionDigits;
  }

  // Step 8. SetNumberFormatDigitOptions, steps 24 and 26.
  if ("minimumSignificantDigits" in lazyPluralRulesData) {
    assert(
      "maximumSignificantDigits" in lazyPluralRulesData,
      "min/max sig digits mismatch"
    );
    internalProps.minimumSignificantDigits =
      lazyPluralRulesData.minimumSignificantDigits;
    internalProps.maximumSignificantDigits =
      lazyPluralRulesData.maximumSignificantDigits;
  }

  // Step 8. SetNumberFormatDigitOptions, steps 26-30.
  internalProps.roundingPriority = lazyPluralRulesData.roundingPriority;

  // `pluralCategories` is lazily computed on first access.
  internalProps.pluralCategories = null;

  return internalProps;
}

/**
 * Returns an object containing the PluralRules internal properties of |obj|.
 */
function getPluralRulesInternals(obj) {
  assert(IsObject(obj), "getPluralRulesInternals called with non-object");
  assert(
    intl_GuardToPluralRules(obj) !== null,
    "getPluralRulesInternals called with non-PluralRules"
  );

  var internals = getIntlObjectInternals(obj);
  assert(
    internals.type === "PluralRules",
    "bad type escaped getIntlObjectInternals"
  );

  var internalProps = maybeInternalProperties(internals);
  if (internalProps) {
    return internalProps;
  }

  internalProps = resolvePluralRulesInternals(internals.lazyData);
  setInternalProperties(internals, internalProps);
  return internalProps;
}

/**
 * 16.1.2 InitializePluralRules ( pluralRules, locales, options )
 *
 * Initializes an object as a PluralRules.
 *
 * This method is complicated a moderate bit by its implementing initialization
 * as a *lazy* concept.  Everything that must happen now, does -- but we defer
 * all the work we can until the object is actually used as a PluralRules.
 * This later work occurs in |resolvePluralRulesInternals|; steps not noted
 * here occur there.
 *
 * ES2024 Intl draft rev 74ca7099f103d143431b2ea422ae640c6f43e3e6
 */
function InitializePluralRules(pluralRules, locales, options) {
  assert(IsObject(pluralRules), "InitializePluralRules called with non-object");
  assert(
    intl_GuardToPluralRules(pluralRules) !== null,
    "InitializePluralRules called with non-PluralRules"
  );

  // Lazy PluralRules data has the following structure:
  //
  //   {
  //     requestedLocales: List of locales,
  //     type: "cardinal" / "ordinal",
  //
  //     opt: // opt object computer in InitializePluralRules
  //       {
  //         localeMatcher: "lookup" / "best fit",
  //       }
  //
  //     minimumIntegerDigits: integer ∈ [1, 21],
  //
  //     // optional, mutually exclusive with the significant-digits option
  //     minimumFractionDigits: integer ∈ [0, 20],
  //     maximumFractionDigits: integer ∈ [0, 20],
  //
  //     // optional, mutually exclusive with the fraction-digits option
  //     minimumSignificantDigits: integer ∈ [1, 21],
  //     maximumSignificantDigits: integer ∈ [1, 21],
  //
  //     roundingPriority: "auto" / "lessPrecision" / "morePrecision",
  //
  //     trailingZeroDisplay: "auto" / "stripIfInteger",
  //
  //     roundingIncrement: integer ∈ (1, 2, 5,
  //                                   10, 20, 25, 50,
  //                                   100, 200, 250, 500,
  //                                   1000, 2000, 2500, 5000),
  //
  //     roundingMode: "ceil" / "floor" / "expand" / "trunc" /
  //                   "halfCeil" / "halfFloor" / "halfExpand" / "halfTrunc" / "halfEven",
  //   }
  //
  // Note that lazy data is only installed as a final step of initialization,
  // so every PluralRules lazy data object has *all* these properties, never a
  // subset of them.
  const lazyPluralRulesData = std_Object_create(null);

  // Step 1.
  let requestedLocales = CanonicalizeLocaleList(locales);
  lazyPluralRulesData.requestedLocales = requestedLocales;

  // Step 2. (Inlined call to CoerceOptionsToObject.)
  if (options === undefined) {
    options = std_Object_create(null);
  } else {
    options = ToObject(options);
  }

  // Step 3.
  let opt = new_Record();
  lazyPluralRulesData.opt = opt;

  // Steps 4-5.
  let matcher = GetOption(
    options,
    "localeMatcher",
    "string",
    ["lookup", "best fit"],
    "best fit"
  );
  opt.localeMatcher = matcher;

  // Steps 6-7.
  const type = GetOption(
    options,
    "type",
    "string",
    ["cardinal", "ordinal"],
    "cardinal"
  );
  lazyPluralRulesData.type = type;

  // Step 8.
  SetNumberFormatDigitOptions(lazyPluralRulesData, options, 0, 3, "standard");

  // Step 12.
  //
  // We've done everything that must be done now: mark the lazy data as fully
  // computed and install it.
  initializeIntlObject(pluralRules, "PluralRules", lazyPluralRulesData);
}

/**
 * 16.2.2 Intl.PluralRules.supportedLocalesOf ( locales [ , options ] )
 *
 * Returns the subset of the given locale list for which this locale list has a
 * matching (possibly fallback) locale. Locales appear in the same order in the
 * returned list as in the input list.
 *
 * ES2024 Intl draft rev 74ca7099f103d143431b2ea422ae640c6f43e3e6
 */
function Intl_PluralRules_supportedLocalesOf(locales /*, options*/) {
  var options = ArgumentsLength() > 1 ? GetArgument(1) : undefined;

  // Step 1.
  var availableLocales = "PluralRules";

  // Step 2.
  let requestedLocales = CanonicalizeLocaleList(locales);

  // Step 3.
  return SupportedLocales(availableLocales, requestedLocales, options);
}

/**
 * 16.3.3 Intl.PluralRules.prototype.select ( value )
 *
 * Returns a String value representing the plural category matching
 * the number passed as value according to the
 * effective locale and the formatting options of this PluralRules.
 *
 * ES2024 Intl draft rev 74ca7099f103d143431b2ea422ae640c6f43e3e6
 */
function Intl_PluralRules_select(value) {
  // Step 1.
  let pluralRules = this;

  // Step 2.
  if (
    !IsObject(pluralRules) ||
    (pluralRules = intl_GuardToPluralRules(pluralRules)) === null
  ) {
    return callFunction(
      intl_CallPluralRulesMethodIfWrapped,
      this,
      value,
      "Intl_PluralRules_select"
    );
  }

  // Step 3.
  let n = ToNumber(value);

  // Ensure the PluralRules internals are resolved.
  getPluralRulesInternals(pluralRules);

  // Step 4.
  return intl_SelectPluralRule(pluralRules, n);
}

/**
 * 16.3.4 Intl.PluralRules.prototype.selectRange ( start, end )
 *
 * Returns a String value representing the plural category matching the input
 * number range according to the effective locale and the formatting options
 * of this PluralRules.
 *
 * ES2024 Intl draft rev 74ca7099f103d143431b2ea422ae640c6f43e3e6
 */
function Intl_PluralRules_selectRange(start, end) {
  // Step 1.
  var pluralRules = this;

  // Step 2.
  if (
    !IsObject(pluralRules) ||
    (pluralRules = intl_GuardToPluralRules(pluralRules)) === null
  ) {
    return callFunction(
      intl_CallPluralRulesMethodIfWrapped,
      this,
      start,
      end,
      "Intl_PluralRules_selectRange"
    );
  }

  // Step 3.
  if (start === undefined || end === undefined) {
    ThrowTypeError(
      JSMSG_UNDEFINED_NUMBER,
      start === undefined ? "start" : "end",
      "PluralRules",
      "selectRange"
    );
  }

  // Step 4.
  var x = ToNumber(start);

  // Step 5.
  var y = ToNumber(end);

  // Step 6.
  return intl_SelectPluralRuleRange(pluralRules, x, y);
}

/**
 * 16.3.5 Intl.PluralRules.prototype.resolvedOptions ( )
 *
 * Returns the resolved options for a PluralRules object.
 *
 * ES2024 Intl draft rev 74ca7099f103d143431b2ea422ae640c6f43e3e6
 */
function Intl_PluralRules_resolvedOptions() {
  // Step 1.
  var pluralRules = this;

  // Step 2.
  if (
    !IsObject(pluralRules) ||
    (pluralRules = intl_GuardToPluralRules(pluralRules)) === null
  ) {
    return callFunction(
      intl_CallPluralRulesMethodIfWrapped,
      this,
      "Intl_PluralRules_resolvedOptions"
    );
  }

  var internals = getPluralRulesInternals(pluralRules);

  // Steps 3-4.
  var result = {
    locale: internals.locale,
    type: internals.type,
    minimumIntegerDigits: internals.minimumIntegerDigits,
  };

  // Min/Max fraction digits are either both present or not present at all.
  assert(
    hasOwn("minimumFractionDigits", internals) ===
      hasOwn("maximumFractionDigits", internals),
    "minimumFractionDigits is present iff maximumFractionDigits is present"
  );

  if (hasOwn("minimumFractionDigits", internals)) {
    DefineDataProperty(
      result,
      "minimumFractionDigits",
      internals.minimumFractionDigits
    );
    DefineDataProperty(
      result,
      "maximumFractionDigits",
      internals.maximumFractionDigits
    );
  }

  // Min/Max significant digits are either both present or not present at all.
  assert(
    hasOwn("minimumSignificantDigits", internals) ===
      hasOwn("maximumSignificantDigits", internals),
    "minimumSignificantDigits is present iff maximumSignificantDigits is present"
  );

  if (hasOwn("minimumSignificantDigits", internals)) {
    DefineDataProperty(
      result,
      "minimumSignificantDigits",
      internals.minimumSignificantDigits
    );
    DefineDataProperty(
      result,
      "maximumSignificantDigits",
      internals.maximumSignificantDigits
    );
  }

  DefineDataProperty(result, "roundingMode", internals.roundingMode);
  DefineDataProperty(result, "roundingIncrement", internals.roundingIncrement);
  DefineDataProperty(
    result,
    "trailingZeroDisplay",
    internals.trailingZeroDisplay
  );

  // Step 5.
  var internalsPluralCategories = internals.pluralCategories;
  if (internalsPluralCategories === null) {
    internalsPluralCategories = intl_GetPluralCategories(pluralRules);
    internals.pluralCategories = internalsPluralCategories;
  }

  var pluralCategories = [];
  for (var i = 0; i < internalsPluralCategories.length; i++) {
    DefineDataProperty(pluralCategories, i, internalsPluralCategories[i]);
  }

  // Step 6.
  DefineDataProperty(result, "pluralCategories", pluralCategories);

  // Steps 7-9.
  //
  // Our implementation doesn't use [[RoundingType]], but instead directly
  // stores the computed `roundingPriority` value.
  DefineDataProperty(result, "roundingPriority", internals.roundingPriority);

  // Step 10.
  return result;
}
