/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#pragma once

#include <fmt/format.h>

#include <folly/Utility.h>
#include <folly/lang/Exception.h>
#include <folly/lang/SafeAssert.h>
#include <folly/portability/SourceLocation.h>

namespace folly {

class rich_msg;

namespace ext { // For extension authors -- public details

/// ext::format_string_and_location
///
/// Rich error users probably want `rich_msg` instead, see below.  This is meant
/// to enable library authors to make concise APIs, in the style
/// `enrich_non_value` or `...coded_rich_error`.
///
/// Captures a source location together with a literal string -- either for
/// `fmt` formatting with arguments, or one without substitutions. Usage:
/// template <typename... As> void
/// yourFn(format_string_and_location<std::type_identity_t<As>...> = "")
///
/// The `type_identity` allows implicit construction of this type from string
/// literals (its sole ctor is consteval).  Adding that nested template makes
/// the argument non-deducible.  Without that, template deduction would attempt
/// to deduce `const char [N]` -> `format_string_and_location`, which would fail
/// since template deduction does not consider implicit conversions.
///
/// BUG ALERT: Due to https://github.com/llvm/llvm-project/issues/137907 avoid
/// writing constructors like this: T(source_location sl =
/// source_location::current()) A constructor with a defaulted
/// `source_location` should take at least one mandatory argument to avoid this
/// pitfall.
template <typename... Args>
class format_string_and_location {
 private:
  friend class ::folly::rich_msg;

  source_location loc_;
  fmt::format_string<Args...> fmt_str_;
  literal_c_str lit_str_;

 public:
  /* implicit */ consteval format_string_and_location(
      const char* str, source_location loc = source_location::current())
      : loc_{std::move(loc)}, fmt_str_{str}, lit_str_{str} {}

  constexpr exception_shared_string as_exception_shared_string(Args const&...)
    requires(sizeof...(Args) == 0)
  {
    return exception_shared_string{lit_str_};
  }

  exception_shared_string as_exception_shared_string(Args const&... args)
    requires(sizeof...(Args) > 0)
  {
    // `fmt::runtime` is safe since our ctor checked the string/args combo
    //
    // Future: If this code shows up in your benchmark, check out the test
    // `exception_shared_string_format` for a possible micro-optimization that
    // involves customizing the type given to `fmt` based on
    // `is_register_pass_v`.
    return {
        fmt::formatted_size(fmt::runtime(fmt_str_), args...),
        [&](auto buf, auto len) {
          auto res =
              fmt::format_to_n(buf, len, fmt::runtime(fmt_str_), args...);
          FOLLY_SAFE_DCHECK(len == res.size);
        }};
  }
};

} // namespace ext

/// A message that can automatically capture source locations. Usage:
///   rich_msg msg1{"error: {} at {}", code, line};
class rich_msg {
 private:
  exception_shared_string msg_;
  source_location loc_;

 public:
  // Writing `rich_error{"fmt {} str {}", 1, 2}` auto-captures the location.
  //
  // Future: Could make implicit. Would still need curly braces to construct:
  //  []() -> rich_msg { return "simple message"; } // error
  //  []() -> rich_msg { return {"simple message"}; } // ok
  template <typename... As>
  explicit rich_msg(
      ext::format_string_and_location<std::type_identity_t<As>...> snl,
      As const&... args)
      : msg_{snl.as_exception_shared_string(args...)},
        loc_{std::move(snl.loc_)} {}

  // This overload exists specifically for use in `immortal_rich_error`, which
  // passes all of the error's constructor arguments as template parameters.
  // In C++20, this imposes severe constraints.  We cannot pass:
  //   - `source_location` since it's not a structural type.
  //   - `rich_msg`, since it's not structural (and can likely never be).
  //   - String literals, or `literal_c_str` because `char*` is forbidden in
  //     template parameters.
  //   - `literal_string`, because that instance would not have static storage
  //     and thus we could use its `c_str()` in this `consteval` code.
  //   - Format args, since our `fmt` isn't `constexpr` (yet).
  //
  // Implicitly converting from a `"foo"_litv` is about as good as this can get.
  template <literal_string Str>
  /* implicit */ consteval rich_msg(vtag_t<Str>)
      : msg_{literal_c_str{Str.c_str()}}, loc_{} {}

  rich_msg(exception_shared_string msg, source_location loc)
      : msg_{std::move(msg)}, loc_{std::move(loc)} {}

  constexpr const source_location& location() const noexcept { return loc_; }
  constexpr const char* message() const noexcept { return msg_.what(); }
};

} // namespace folly
