/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you 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.
 */

/* This file is based on source code from the Spark Project (http://spark.apache.org/), licensed
 * by the Apache Software Foundation (ASF) under the Apache License, Version 2.0. See the NOTICE
 * file distributed with this work for additional information regarding copyright ownership. */

#pragma once

#include <memory>
#include <optional>
#include <string>
#include <string_view>

#include "paimon/common/data/variant/variant_binary_util.h"
#include "paimon/memory/bytes.h"
#include "paimon/memory/memory_pool.h"
#include "paimon/result.h"

namespace paimon {

/// A Variant represents a type that contains one of: 1) Primitive: A type and corresponding value
/// (e.g. INT, STRING); 2) Array: An ordered list of Variant values; 3) Object: An unordered
/// collection of string/Variant pairs (i.e. key/value pairs). An object may not contain duplicate
/// keys.
///
/// A Variant is encoded with 2 binaries: the value and the metadata. The Variant Binary Encoding
/// allows representation of semi-structured data (e.g. JSON) in a form that can be efficiently
/// queried by path. The design is intended to allow efficient access to nested data even in the
/// presence of very wide or deep structures.
class GenericVariant {
 public:
    /// A field of a variant object.
    struct ObjectField {
        std::string key;
        std::shared_ptr<GenericVariant> value;
    };

    /// Creates a variant taking ownership of the given buffers. Fails with `MALFORMED_VARIANT`
    /// if the metadata version is unsupported, or `VARIANT_CONSTRUCTOR_SIZE_LIMIT` if either
    /// buffer exceeds the 128MiB size limit.
    static Result<std::shared_ptr<GenericVariant>> Create(std::shared_ptr<Bytes> value,
                                                          std::shared_ptr<Bytes> metadata);

    /// Creates a variant by copying the given buffers into `pool`.
    static Result<std::shared_ptr<GenericVariant>> Create(std::string_view value,
                                                          std::string_view metadata,
                                                          const std::shared_ptr<MemoryPool>& pool);

    /// Parses a JSON string as a variant (duplicate object keys are rejected).
    static Result<std::shared_ptr<GenericVariant>> FromJson(
        std::string_view json, const std::shared_ptr<MemoryPool>& pool);

    /// The variant value binary. For a sub-variant (`Pos() != 0`), the view covers only the
    /// sub-variant slice of the underlying buffer.
    Result<std::string_view> Value() const;

    /// The whole underlying value buffer, regardless of `Pos()`.
    std::string_view RawValue() const;

    /// The variant metadata binary.
    std::string_view Metadata() const;

    /// The variant value doesn't use the whole value binary, but starts from the `Pos()` index
    /// and spans a size of `VariantBinaryUtil::ValueSize`. This design avoids frequent copies of
    /// the value binary when reading a sub-variant in an array/object element.
    int32_t Pos() const {
        return pos_;
    }

    /// The size of the variant in bytes (value size + metadata size).
    int64_t SizeInBytes() const;

    /// Stringifies the variant in JSON format. `zone_id` controls the rendering of TIMESTAMP
    /// values; supported forms are "UTC"/"Z"/"GMT" and fixed offsets such as "+08:00".
    Result<std::string> ToJson(const std::string& zone_id = "UTC") const;

    /// The value type of the variant.
    Result<VariantValueType> GetType() const;

    /// The type info bits of the variant value header.
    Result<int32_t> GetTypeInfo() const;

    Result<bool> GetBoolean() const;
    Result<int64_t> GetLong() const;
    Result<double> GetDouble() const;
    Result<VariantDecimal> GetDecimal() const;
    Result<float> GetFloat() const;
    Result<std::string_view> GetBinary() const;
    Result<std::string_view> GetString() const;
    /// The 16-byte big-endian UUID value.
    Result<std::string_view> GetUuid() const;

    /// The number of object fields in the variant. It is only legal to call it when `GetType()`
    /// is `kObject`.
    Result<int32_t> ObjectSize() const;

    /// Finds the field value whose key is equal to `key`. Returns nullptr if the key is not
    /// found. It is only legal to call it when `GetType()` is `kObject`. The returned sub-variant
    /// shares the underlying buffers with this variant.
    Result<std::shared_ptr<GenericVariant>> GetFieldByKey(std::string_view key) const;

    /// Gets the object field at the `index` slot. Returns nullopt if `index` is out of the bound
    /// of `[0, ObjectSize())`. It is only legal to call it when `GetType()` is `kObject`.
    Result<std::optional<ObjectField>> GetFieldAtIndex(int32_t index) const;

    /// Gets the metadata dictionary id for the object field at the `index` slot. It is only
    /// legal to call it when `GetType()` is `kObject`.
    Result<int32_t> GetDictionaryIdAtIndex(int32_t index) const;

    /// The number of array elements in the variant. It is only legal to call it when `GetType()`
    /// is `kArray`.
    Result<int32_t> ArraySize() const;

    /// Gets the array element at the `index` slot. Returns nullptr if `index` is out of the
    /// bound of `[0, ArraySize())`. It is only legal to call it when `GetType()` is `kArray`.
    Result<std::shared_ptr<GenericVariant>> GetElementAtIndex(int32_t index) const;

    bool operator==(const GenericVariant& other) const;

 private:
    GenericVariant(std::shared_ptr<Bytes> value, std::shared_ptr<Bytes> metadata, int32_t pos);

    std::shared_ptr<GenericVariant> SubVariant(int32_t pos) const;

    std::shared_ptr<Bytes> value_;
    std::shared_ptr<Bytes> metadata_;
    int32_t pos_;
};

}  // namespace paimon
