// Copyright 2019 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifndef V8_INTL_SUPPORT
#error Internationalization is expected to be enabled.
#endif  // V8_INTL_SUPPORT

#include <memory>
#include <vector>

#include "src/objects/js-display-names-inl.h"
#include "src/objects/js-display-names.h"

#include "src/execution/isolate.h"
#include "src/heap/factory.h"
#include "src/objects/intl-objects.h"
#include "src/objects/managed.h"
#include "src/objects/objects-inl.h"

#include "unicode/dtfmtsym.h"
#include "unicode/dtptngen.h"
#include "unicode/localebuilder.h"
#include "unicode/locdspnm.h"
#include "unicode/timezone.h"
#include "unicode/tznames.h"
#include "unicode/uloc.h"
#include "unicode/unistr.h"
#include "unicode/uscript.h"

namespace v8 {
namespace internal {

namespace {
// Type: identifying the types of the display names.
//
// ecma402/#sec-properties-of-intl-displaynames-instances
enum class Type {
  kUndefined,
  kLanguage,
  kRegion,
  kScript,
  kCurrency,
  kWeekday,
  kMonth,
  kQuarter,
  kDayPeriod,
  kDateTimeField
};

bool IsUnicodeScriptSubtag(const std::string& value) {
  UErrorCode status = U_ZERO_ERROR;
  icu::LocaleBuilder builder;
  builder.setScript(value).build(status);
  return U_SUCCESS(status);
}

bool IsUnicodeRegionSubtag(const std::string& value) {
  UErrorCode status = U_ZERO_ERROR;
  icu::LocaleBuilder builder;
  builder.setRegion(value).build(status);
  return U_SUCCESS(status);
}

UDisplayContext ToUDisplayContext(JSDisplayNames::Style style) {
  switch (style) {
    case JSDisplayNames::Style::kLong:
      return UDISPCTX_LENGTH_FULL;
    case JSDisplayNames::Style::kShort:
    case JSDisplayNames::Style::kNarrow:
      return UDISPCTX_LENGTH_SHORT;
  }
}

}  // anonymous namespace

// Abstract class for all different types.
class DisplayNamesInternal {
 public:
  DisplayNamesInternal() = default;
  virtual ~DisplayNamesInternal() = default;
  virtual const char* type() const = 0;
  virtual icu::Locale locale() const = 0;
  virtual Maybe<icu::UnicodeString> of(Isolate* isolate,
                                       const char* code) const = 0;
  virtual const char* calendar() const { return nullptr; }
};

namespace {

class LocaleDisplayNamesCommon : public DisplayNamesInternal {
 public:
  LocaleDisplayNamesCommon(const icu::Locale& locale,
                           JSDisplayNames::Style style, bool fallback)
      : style_(style) {
    UDisplayContext sub =
        fallback ? UDISPCTX_SUBSTITUTE : UDISPCTX_NO_SUBSTITUTE;
    UDisplayContext display_context[] = {ToUDisplayContext(style_),
                                         UDISPCTX_DIALECT_NAMES,
                                         UDISPCTX_CAPITALIZATION_NONE, sub};
    ldn_.reset(
        icu::LocaleDisplayNames::createInstance(locale, display_context, 4));
  }

  ~LocaleDisplayNamesCommon() override = default;

  icu::Locale locale() const override { return ldn_->getLocale(); }

 protected:
  icu::LocaleDisplayNames* locale_display_names() const { return ldn_.get(); }

 private:
  std::unique_ptr<icu::LocaleDisplayNames> ldn_;
  JSDisplayNames::Style style_;
};

class LanguageNames : public LocaleDisplayNamesCommon {
 public:
  LanguageNames(const icu::Locale& locale, JSDisplayNames::Style style,
                bool fallback)
      : LocaleDisplayNamesCommon(locale, style, fallback) {}
  ~LanguageNames() override = default;
  const char* type() const override { return "language"; }
  Maybe<icu::UnicodeString> of(Isolate* isolate,
                               const char* code) const override {
    UErrorCode status = U_ZERO_ERROR;
    icu::Locale l =
        icu::Locale(icu::Locale::forLanguageTag(code, status).getBaseName());
    std::string checked = l.toLanguageTag<std::string>(status);

    if (U_FAILURE(status)) {
      THROW_NEW_ERROR_RETURN_VALUE(
          isolate, NewRangeError(MessageTemplate::kInvalidArgument),
          Nothing<icu::UnicodeString>());
    }

    icu::UnicodeString result;
    locale_display_names()->localeDisplayName(checked.c_str(), result);

    return Just(result);
  }
};

class RegionNames : public LocaleDisplayNamesCommon {
 public:
  RegionNames(const icu::Locale& locale, JSDisplayNames::Style style,
              bool fallback)
      : LocaleDisplayNamesCommon(locale, style, fallback) {}
  ~RegionNames() override = default;
  const char* type() const override { return "region"; }
  Maybe<icu::UnicodeString> of(Isolate* isolate,
                               const char* code) const override {
    std::string code_str(code);
    if (!IsUnicodeRegionSubtag(code_str)) {
      THROW_NEW_ERROR_RETURN_VALUE(
          isolate, NewRangeError(MessageTemplate::kInvalidArgument),
          Nothing<icu::UnicodeString>());
    }

    icu::UnicodeString result;
    locale_display_names()->regionDisplayName(code_str.c_str(), result);
    return Just(result);
  }
};

class ScriptNames : public LocaleDisplayNamesCommon {
 public:
  ScriptNames(const icu::Locale& locale, JSDisplayNames::Style style,
              bool fallback)
      : LocaleDisplayNamesCommon(locale, style, fallback) {}
  ~ScriptNames() override = default;
  const char* type() const override { return "script"; }
  Maybe<icu::UnicodeString> of(Isolate* isolate,
                               const char* code) const override {
    std::string code_str(code);
    if (!IsUnicodeScriptSubtag(code_str)) {
      THROW_NEW_ERROR_RETURN_VALUE(
          isolate, NewRangeError(MessageTemplate::kInvalidArgument),
          Nothing<icu::UnicodeString>());
    }

    icu::UnicodeString result;
    locale_display_names()->scriptDisplayName(code_str.c_str(), result);
    return Just(result);
  }
};

class CurrencyNames : public LocaleDisplayNamesCommon {
 public:
  CurrencyNames(const icu::Locale& locale, JSDisplayNames::Style style,
                bool fallback)
      : LocaleDisplayNamesCommon(locale, style, fallback) {}
  ~CurrencyNames() override = default;
  const char* type() const override { return "currency"; }
  Maybe<icu::UnicodeString> of(Isolate* isolate,
                               const char* code) const override {
    std::string code_str(code);
    if (!Intl::IsWellFormedCurrency(code_str)) {
      THROW_NEW_ERROR_RETURN_VALUE(
          isolate, NewRangeError(MessageTemplate::kInvalidArgument),
          Nothing<icu::UnicodeString>());
    }

    icu::UnicodeString result;
    locale_display_names()->keyValueDisplayName("currency", code_str.c_str(),
                                                result);

    return Just(result);
  }
};

UDateTimePGDisplayWidth StyleToUDateTimePGDisplayWidth(
    JSDisplayNames::Style style) {
  switch (style) {
    case JSDisplayNames::Style::kLong:
      return UDATPG_WIDE;
    case JSDisplayNames::Style::kShort:
      return UDATPG_ABBREVIATED;
    case JSDisplayNames::Style::kNarrow:
      return UDATPG_NARROW;
  }
}

UDateTimePatternField StringToUDateTimePatternField(const char* code) {
  switch (code[0]) {
    case 'd':
      if (strcmp(code, "day") == 0) return UDATPG_DAY_FIELD;
      if (strcmp(code, "dayPeriod") == 0) return UDATPG_DAYPERIOD_FIELD;
      break;
    case 'e':
      if (strcmp(code, "era") == 0) return UDATPG_ERA_FIELD;
      break;
    case 'h':
      if (strcmp(code, "hour") == 0) return UDATPG_HOUR_FIELD;
      break;
    case 'm':
      if (strcmp(code, "minute") == 0) return UDATPG_MINUTE_FIELD;
      if (strcmp(code, "month") == 0) return UDATPG_MONTH_FIELD;
      break;
    case 'q':
      if (strcmp(code, "quarter") == 0) return UDATPG_QUARTER_FIELD;
      break;
    case 's':
      if (strcmp(code, "second") == 0) return UDATPG_SECOND_FIELD;
      break;
    case 't':
      if (strcmp(code, "timeZoneName") == 0) return UDATPG_ZONE_FIELD;
      break;
    case 'w':
      if (strcmp(code, "weekOfYear") == 0) return UDATPG_WEEK_OF_YEAR_FIELD;
      if (strcmp(code, "weekday") == 0) return UDATPG_WEEKDAY_FIELD;
      break;
    case 'y':
      if (strcmp(code, "year") == 0) return UDATPG_YEAR_FIELD;
      break;
    default:
      break;
  }
  UNREACHABLE();
}

class DateTimeFieldNames : public DisplayNamesInternal {
 public:
  DateTimeFieldNames(const icu::Locale& locale, JSDisplayNames::Style style)
      : locale_(locale), width_(StyleToUDateTimePGDisplayWidth(style)) {
    UErrorCode status = U_ZERO_ERROR;
    generator_.reset(
        icu::DateTimePatternGenerator::createInstance(locale_, status));
    DCHECK(U_SUCCESS(status));
  }
  ~DateTimeFieldNames() override = default;
  const char* type() const override { return "dateTimeField"; }
  icu::Locale locale() const override { return locale_; }
  Maybe<icu::UnicodeString> of(Isolate* isolate,
                               const char* code) const override {
    UDateTimePatternField field = StringToUDateTimePatternField(code);
    if (field == UDATPG_FIELD_COUNT) {
      THROW_NEW_ERROR_RETURN_VALUE(
          isolate, NewRangeError(MessageTemplate::kInvalidArgument),
          Nothing<icu::UnicodeString>());
    }
    return Just(generator_->getFieldDisplayName(field, width_));
  }

 private:
  icu::Locale locale_;
  UDateTimePGDisplayWidth width_;
  std::unique_ptr<icu::DateTimePatternGenerator> generator_;
};

icu::DateFormatSymbols::DtWidthType StyleToDtWidthType(
    JSDisplayNames::Style style, Type type) {
  switch (style) {
    case JSDisplayNames::Style::kLong:
      return icu::DateFormatSymbols::WIDE;
    case JSDisplayNames::Style::kShort:
      return icu::DateFormatSymbols::SHORT;
    case JSDisplayNames::Style::kNarrow:
      if (type == Type::kQuarter) {
        return icu::DateFormatSymbols::ABBREVIATED;
      } else {
        return icu::DateFormatSymbols::NARROW;
      }
  }
}

class DateFormatSymbolsNames : public DisplayNamesInternal {
 public:
  DateFormatSymbolsNames(const char* type, const icu::Locale& locale,
                         const icu::UnicodeString* array, int32_t length,
                         const char* calendar)
      : type_(type),
        locale_(locale),
        array_(array),
        length_(length),
        calendar_(calendar) {}

  ~DateFormatSymbolsNames() override = default;

  const char* type() const override { return type_; }

  icu::Locale locale() const override { return locale_; }

  const char* calendar() const override {
    if (calendar_.empty()) {
      return nullptr;
    }
    return calendar_.c_str();
  }

  virtual int32_t ComputeIndex(const char* code) const = 0;

  Maybe<icu::UnicodeString> of(Isolate* isolate,
                               const char* code) const override {
    int32_t index = ComputeIndex(code);
    if (index < 0 || index >= length_) {
      THROW_NEW_ERROR_RETURN_VALUE(
          isolate, NewRangeError(MessageTemplate::kInvalidArgument),
          Nothing<icu::UnicodeString>());
    }
    return Just(array_[index]);
  }

 private:
  const char* type_;
  icu::Locale locale_;
  const icu::UnicodeString* array_;
  int32_t length_;
  std::string calendar_;
};

class WeekdayNames : public DateFormatSymbolsNames {
 public:
  WeekdayNames(const char* type, const icu::Locale& locale,
               const icu::UnicodeString* array, int32_t length,
               const char* calendar)
      : DateFormatSymbolsNames(type, locale, array, length, calendar) {}
  ~WeekdayNames() override = default;

  int32_t ComputeIndex(const char* code) const override {
    int32_t i = atoi(code);
    if (i == 7) return 1;
    if (i > 0 && i < 7) return i + 1;
    return -1;
  }
};

class MonthNames : public DateFormatSymbolsNames {
 public:
  MonthNames(const char* type, const icu::Locale& locale,
             const icu::UnicodeString* array, int32_t length,
             const char* calendar)
      : DateFormatSymbolsNames(type, locale, array, length, calendar) {}
  ~MonthNames() override = default;

  int32_t ComputeIndex(const char* code) const override {
    return atoi(code) - 1;
  }
};

class QuarterNames : public DateFormatSymbolsNames {
 public:
  QuarterNames(const char* type, const icu::Locale& locale,
               const icu::UnicodeString* array, int32_t length,
               const char* calendar)
      : DateFormatSymbolsNames(type, locale, array, length, calendar) {}
  ~QuarterNames() override = default;

  int32_t ComputeIndex(const char* code) const override {
    return atoi(code) - 1;
  }
};

class DayPeriodNames : public DateFormatSymbolsNames {
 public:
  DayPeriodNames(const char* type, const icu::Locale& locale,
                 const icu::UnicodeString* array, int32_t length,
                 const char* calendar)
      : DateFormatSymbolsNames(type, locale, array, length, calendar) {}
  ~DayPeriodNames() override = default;

  int32_t ComputeIndex(const char* code) const override {
    if (strcmp("am", code) == 0) {
      return 0;
    } else if (strcmp("pm", code) == 0) {
      return 1;
    } else {
      return -1;
    }
  }
};

const char* gWeekday = "weekday";
const char* gMonth = "month";
const char* gQuarter = "quarter";
const char* gDayPeriod = "dayPeriod";

DateFormatSymbolsNames* CreateDateFormatSymbolsNames(
    const icu::Locale& locale, JSDisplayNames::Style style, Type type) {
  UErrorCode status = U_ZERO_ERROR;
  std::unique_ptr<icu::DateFormatSymbols> symbols(
      icu::DateFormatSymbols::createForLocale(locale, status));
  if (U_FAILURE(status)) {
    return nullptr;
  }
  icu::DateFormatSymbols::DtWidthType width_type =
      StyleToDtWidthType(style, type);
  int32_t count = 0;
  std::string calendar =
      locale.getUnicodeKeywordValue<std::string>("ca", status);

  switch (type) {
    case Type::kMonth:
      return new MonthNames(
          gMonth, locale,
          symbols->getMonths(count, icu::DateFormatSymbols::STANDALONE,
                             width_type),
          count, calendar.c_str());
    case Type::kWeekday:
      return new WeekdayNames(
          gWeekday, locale,
          symbols->getWeekdays(count, icu::DateFormatSymbols::STANDALONE,
                               width_type),
          count, calendar.c_str());
    case Type::kQuarter:
      return new QuarterNames(
          gQuarter, locale,
          symbols->getQuarters(count, icu::DateFormatSymbols::STANDALONE,
                               width_type),
          count, calendar.c_str());
    case Type::kDayPeriod:
      return new DayPeriodNames(gDayPeriod, locale,
                                symbols->getAmPmStrings(count), count,
                                calendar.c_str());
    default:
      UNREACHABLE();
  }
}

DisplayNamesInternal* CreateInternal(const icu::Locale& locale,
                                     JSDisplayNames::Style style, Type type,
                                     bool fallback) {
  switch (type) {
    case Type::kLanguage:
      return new LanguageNames(locale, style, fallback);
    case Type::kRegion:
      return new RegionNames(locale, style, fallback);
    case Type::kScript:
      return new ScriptNames(locale, style, fallback);
    case Type::kCurrency:
      return new CurrencyNames(locale, style, fallback);
    case Type::kDateTimeField:
      return new DateTimeFieldNames(locale, style);
    case Type::kMonth:
    case Type::kWeekday:
    case Type::kQuarter:
    case Type::kDayPeriod:
      return CreateDateFormatSymbolsNames(locale, style, type);
    default:
      UNREACHABLE();
  }
}

}  // anonymous namespace

// ecma402 #sec-Intl.DisplayNames
MaybeHandle<JSDisplayNames> JSDisplayNames::New(Isolate* isolate,
                                                Handle<Map> map,
                                                Handle<Object> locales,
                                                Handle<Object> input_options) {
  const char* service = "Intl.DisplayNames";
  Factory* factory = isolate->factory();

  Handle<JSReceiver> options;
  // 3. Let requestedLocales be ? CanonicalizeLocaleList(locales).
  Maybe<std::vector<std::string>> maybe_requested_locales =
      Intl::CanonicalizeLocaleList(isolate, locales);
  MAYBE_RETURN(maybe_requested_locales, Handle<JSDisplayNames>());
  std::vector<std::string> requested_locales =
      maybe_requested_locales.FromJust();

  // 4. Let options be ? ToObject(options).
  ASSIGN_RETURN_ON_EXCEPTION(isolate, options,
                             Object::ToObject(isolate, input_options),
                             JSDisplayNames);

  // Note: No need to create a record. It's not observable.
  // 5. Let opt be a new Record.

  // 6. Let localeData be %DisplayNames%.[[LocaleData]].

  // 7. Let matcher be ? GetOption(options, "localeMatcher", "string", «
  // "lookup", "best fit" », "best fit").
  Maybe<Intl::MatcherOption> maybe_locale_matcher =
      Intl::GetLocaleMatcher(isolate, options, "Intl.DisplayNames");
  MAYBE_RETURN(maybe_locale_matcher, MaybeHandle<JSDisplayNames>());

  // 8. Set opt.[[localeMatcher]] to matcher.
  Intl::MatcherOption matcher = maybe_locale_matcher.FromJust();

  std::unique_ptr<char[]> calendar_str = nullptr;
  if (FLAG_harmony_intl_displaynames_date_types) {
    const std::vector<const char*> empty_values = {};
    // Let calendar be ? GetOption(options, "calendar",
    //    "string", undefined, undefined).
    Maybe<bool> maybe_calendar = Intl::GetStringOption(
        isolate, options, "calendar", empty_values, service, &calendar_str);
    MAYBE_RETURN(maybe_calendar, MaybeHandle<JSDisplayNames>());
    // If calendar is not undefined, then
    if (maybe_calendar.FromJust() && calendar_str != nullptr) {
      // a. If calendar does not match the (3*8alphanum) *("-" (3*8alphanum))
      //    sequence, throw a RangeError exception.
      if (!Intl::IsWellFormedCalendar(calendar_str.get())) {
        THROW_NEW_ERROR(
            isolate,
            NewRangeError(
                MessageTemplate::kInvalid, factory->calendar_string(),
                factory->NewStringFromAsciiChecked(calendar_str.get())),
            JSDisplayNames);
      }
    }
  }

  // Set opt.[[ca]] to calendar.

  // ecma402/#sec-Intl.DisplayNames-internal-slots
  // The value of the [[RelevantExtensionKeys]] internal slot is
  // « "ca" ».
  std::set<std::string> relevant_extension_keys_ca = {"ca"};
  std::set<std::string> relevant_extension_keys = {};
  // 9. Let r be ResolveLocale(%DisplayNames%.[[AvailableLocales]],
  //     requestedLocales, opt, %DisplayNames%.[[RelevantExtensionKeys]]).
  Maybe<Intl::ResolvedLocale> maybe_resolve_locale = Intl::ResolveLocale(
      isolate, JSDisplayNames::GetAvailableLocales(), requested_locales,
      matcher,
      FLAG_harmony_intl_displaynames_date_types ? relevant_extension_keys_ca
                                                : relevant_extension_keys);
  if (maybe_resolve_locale.IsNothing()) {
    THROW_NEW_ERROR(isolate, NewRangeError(MessageTemplate::kIcuError),
                    JSDisplayNames);
  }
  Intl::ResolvedLocale r = maybe_resolve_locale.FromJust();

  icu::Locale icu_locale = r.icu_locale;
  UErrorCode status = U_ZERO_ERROR;
  if (calendar_str != nullptr &&
      Intl::IsValidCalendar(icu_locale, calendar_str.get())) {
    icu_locale.setUnicodeKeywordValue("ca", calendar_str.get(), status);
    DCHECK(U_SUCCESS(status));
  }

  // 10. Let s be ? GetOption(options, "style", "string",
  //                          «"long", "short", "narrow"», "long").
  Maybe<Style> maybe_style = Intl::GetStringOption<Style>(
      isolate, options, "style", "Intl.DisplayNames",
      {"long", "short", "narrow"},
      {Style::kLong, Style::kShort, Style::kNarrow}, Style::kLong);
  MAYBE_RETURN(maybe_style, MaybeHandle<JSDisplayNames>());
  Style style_enum = maybe_style.FromJust();

  // 11. Set displayNames.[[Style]] to style.

  // 12. Let type be ? GetOption(options, "type", "string", « "language",
  //     "region", "script", "currency", "weekday", "month", "quarter",
  //     "dayPeriod", "dateTimeField" », undefined).
  Maybe<Type> maybe_type =
      FLAG_harmony_intl_displaynames_date_types
          ? Intl::GetStringOption<Type>(
                isolate, options, "type", "Intl.DisplayNames",
                {"language", "region", "script", "currency", "weekday", "month",
                 "quarter", "dayPeriod", "dateTimeField"},
                {
                    Type::kLanguage,
                    Type::kRegion,
                    Type::kScript,
                    Type::kCurrency,
                    Type::kWeekday,
                    Type::kMonth,
                    Type::kQuarter,
                    Type::kDayPeriod,
                    Type::kDateTimeField,
                },
                Type::kUndefined)
          : Intl::GetStringOption<Type>(
                isolate, options, "type", "Intl.DisplayNames",
                {"language", "region", "script", "currency"},
                {
                    Type::kLanguage,
                    Type::kRegion,
                    Type::kScript,
                    Type::kCurrency,
                },
                Type::kUndefined);
  MAYBE_RETURN(maybe_type, MaybeHandle<JSDisplayNames>());
  Type type_enum = maybe_type.FromJust();

  // 13. If type is undefined, throw a TypeError exception.
  if (type_enum == Type::kUndefined) {
    THROW_NEW_ERROR(isolate, NewTypeError(MessageTemplate::kInvalidArgument),
                    JSDisplayNames);
  }

  // 14. Set displayNames.[[Type]] to type.

  // 15. Let fallback be ? GetOption(options, "fallback", "string",
  //     « "code", "none" », "code").
  Maybe<Fallback> maybe_fallback = Intl::GetStringOption<Fallback>(
      isolate, options, "fallback", "Intl.DisplayNames", {"code", "none"},
      {Fallback::kCode, Fallback::kNone}, Fallback::kCode);
  MAYBE_RETURN(maybe_fallback, MaybeHandle<JSDisplayNames>());
  Fallback fallback_enum = maybe_fallback.FromJust();

  // 16. Set displayNames.[[Fallback]] to fallback.

  // 17. Set displayNames.[[Locale]] to the value of r.[[Locale]].

  // Let calendar be r.[[ca]].

  // Set displayNames.[[Calendar]] to calendar.

  // Let dataLocale be r.[[dataLocale]].

  // Let dataLocaleData be localeData.[[<dataLocale>]].

  // Let types be dataLocaleData.[[types]].

  // Assert: types is a Record (see 1.3.3).

  // Let typeFields be types.[[<type>]].

  // Assert: typeFields is a Record (see 1.3.3).

  // Let styleFields be typeFields.[[<style>]].

  // Assert: styleFields is a Record (see 1.3.3).

  // Set displayNames.[[Fields]] to styleFields.

  DisplayNamesInternal* internal = CreateInternal(
      icu_locale, style_enum, type_enum, fallback_enum == Fallback::kCode);
  if (internal == nullptr) {
    THROW_NEW_ERROR(isolate, NewTypeError(MessageTemplate::kIcuError),
                    JSDisplayNames);
  }

  Handle<Managed<DisplayNamesInternal>> managed_internal =
      Managed<DisplayNamesInternal>::FromRawPtr(isolate, 0, internal);

  Handle<JSDisplayNames> display_names =
      Handle<JSDisplayNames>::cast(factory->NewFastOrSlowJSObjectFromMap(map));
  display_names->set_flags(0);
  display_names->set_style(style_enum);
  display_names->set_fallback(fallback_enum);

  DisallowHeapAllocation no_gc;
  display_names->set_internal(*managed_internal);

  // Return displayNames.
  return display_names;
}

// ecma402 #sec-Intl.DisplayNames.prototype.resolvedOptions
Handle<JSObject> JSDisplayNames::ResolvedOptions(
    Isolate* isolate, Handle<JSDisplayNames> display_names) {
  Factory* factory = isolate->factory();
  // 4. Let options be ! ObjectCreate(%ObjectPrototype%).
  Handle<JSObject> options = factory->NewJSObject(isolate->object_function());

  DisplayNamesInternal* internal = display_names->internal().raw();

  Maybe<std::string> maybe_locale = Intl::ToLanguageTag(internal->locale());
  DCHECK(maybe_locale.IsJust());
  Handle<String> locale = isolate->factory()->NewStringFromAsciiChecked(
      maybe_locale.FromJust().c_str());
  Handle<String> style = display_names->StyleAsString();
  Handle<String> type = factory->NewStringFromAsciiChecked(internal->type());
  Handle<String> fallback = display_names->FallbackAsString();

  Maybe<bool> maybe_create_locale = JSReceiver::CreateDataProperty(
      isolate, options, factory->locale_string(), locale, Just(kDontThrow));
  DCHECK(maybe_create_locale.FromJust());
  USE(maybe_create_locale);
  if (internal->calendar() != nullptr) {
    Maybe<bool> maybe_create_calendar = JSReceiver::CreateDataProperty(
        isolate, options, factory->calendar_string(),
        factory->NewStringFromAsciiChecked(internal->calendar()),
        Just(kDontThrow));
    DCHECK(maybe_create_calendar.FromJust());
    USE(maybe_create_calendar);
  }
  Maybe<bool> maybe_create_style = JSReceiver::CreateDataProperty(
      isolate, options, factory->style_string(), style, Just(kDontThrow));
  DCHECK(maybe_create_style.FromJust());
  USE(maybe_create_style);

  Maybe<bool> maybe_create_type = JSReceiver::CreateDataProperty(
      isolate, options, factory->type_string(), type, Just(kDontThrow));
  DCHECK(maybe_create_type.FromJust());
  USE(maybe_create_type);

  Maybe<bool> maybe_create_fallback = JSReceiver::CreateDataProperty(
      isolate, options, factory->fallback_string(), fallback, Just(kDontThrow));
  DCHECK(maybe_create_fallback.FromJust());
  USE(maybe_create_fallback);

  return options;
}

// ecma402 #sec-Intl.DisplayNames.prototype.of
MaybeHandle<Object> JSDisplayNames::Of(Isolate* isolate,
                                       Handle<JSDisplayNames> display_names,
                                       Handle<Object> code_obj) {
  Handle<String> code;
  ASSIGN_RETURN_ON_EXCEPTION(isolate, code, Object::ToString(isolate, code_obj),
                             Object);
  DisplayNamesInternal* internal = display_names->internal().raw();
  Maybe<icu::UnicodeString> maybe_result =
      internal->of(isolate, code->ToCString().get());
  MAYBE_RETURN(maybe_result, Handle<Object>());
  icu::UnicodeString result = maybe_result.FromJust();
  if (result.isBogus()) {
    return isolate->factory()->undefined_value();
  }
  return Intl::ToString(isolate, result).ToHandleChecked();
}

namespace {

struct CheckCalendar {
  static const char* key() { return "calendar"; }
  static const char* path() { return nullptr; }
};

}  // namespace

const std::set<std::string>& JSDisplayNames::GetAvailableLocales() {
  static base::LazyInstance<Intl::AvailableLocales<CheckCalendar>>::type
      available_locales = LAZY_INSTANCE_INITIALIZER;
  return available_locales.Pointer()->Get();
}

Handle<String> JSDisplayNames::StyleAsString() const {
  switch (style()) {
    case Style::kLong:
      return GetReadOnlyRoots().long_string_handle();
    case Style::kShort:
      return GetReadOnlyRoots().short_string_handle();
    case Style::kNarrow:
      return GetReadOnlyRoots().narrow_string_handle();
  }
  UNREACHABLE();
}

Handle<String> JSDisplayNames::FallbackAsString() const {
  switch (fallback()) {
    case Fallback::kCode:
      return GetReadOnlyRoots().code_string_handle();
    case Fallback::kNone:
      return GetReadOnlyRoots().none_string_handle();
  }
  UNREACHABLE();
}

}  // namespace internal
}  // namespace v8