/*
 * 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 <cstdint>
#include <memory>
#include <string>
#include <string_view>
#include <unordered_map>
#include <vector>

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

namespace paimon {

/// Builds variant value and metadata binaries, either by parsing JSON values or by appending
/// values directly.
class VariantBuilder {
 public:
    /// Temporarily stores the information of a field. All fields of a JSON object are collected,
    /// sorted by their keys, and then the variant object is built in sorted order.
    struct FieldEntry {
        std::string key;
        int32_t id;
        int32_t offset;

        FieldEntry(std::string key, int32_t id, int32_t offset)
            : key(std::move(key)), id(id), offset(offset) {}
    };

    explicit VariantBuilder(bool allow_duplicate_keys)
        : allow_duplicate_keys_(allow_duplicate_keys) {}

    /// Parses a JSON string as a variant value.
    ///
    /// When `allow_duplicate_keys` is true, the last occurrence of a duplicate object key wins;
    /// otherwise duplicate keys make the parse fail.
    static Result<std::shared_ptr<GenericVariant>> ParseJson(
        std::string_view json, bool allow_duplicate_keys, const std::shared_ptr<MemoryPool>& pool);

    /// Builds the variant metadata from the collected dictionary keys and returns the variant
    /// result.
    Result<std::shared_ptr<GenericVariant>> Build(const std::shared_ptr<MemoryPool>& pool);

    /// The variant value written so far, without metadata. Used in shredding to produce a final
    /// value where all shredded values refer to a common metadata.
    std::string_view ValueWithoutMetadata() const {
        return {reinterpret_cast<const char*>(write_buffer_.data()),
                static_cast<size_t>(write_pos_)};
    }

    Status AppendString(std::string_view str);
    Status AppendNull();
    Status AppendBoolean(bool b);
    /// Appends a long value. The actual used integer type depends on the value range.
    Status AppendLong(int64_t l);
    Status AppendDouble(double d);
    /// Appends a decimal value. Its precision and scale must fit into `kMaxDecimal16Precision`.
    Status AppendDecimal(const VariantDecimal& d);
    Status AppendDate(int32_t days_since_epoch);
    Status AppendTimestamp(int64_t micros_since_epoch);
    Status AppendTimestampNtz(int64_t micros_since_epoch);
    Status AppendFloat(float f);
    Status AppendBinary(std::string_view binary);
    /// Appends a UUID value (16 bytes, big-endian).
    Status AppendUuid(std::string_view uuid_bytes);

    /// Adds a key to the variant dictionary and returns its id. If the key already exists, the
    /// dictionary is not modified.
    int32_t AddKey(std::string_view key);

    /// The current write position of the variant builder. It is used together with
    /// `FinishWritingObject` or `FinishWritingArray`.
    int32_t GetWritePos() const {
        return write_pos_;
    }

    /// Finishes writing a variant object after all of its fields have already been written. The
    /// process is as follows:
    /// 1. The caller calls `GetWritePos` before writing any fields to obtain the `start`
    ///    parameter.
    /// 2. The caller appends all the object fields to the builder. In the meantime, it should
    ///    maintain the `fields` parameter. Before appending each field, it should append an entry
    ///    to `fields` to record the offset of the field, computed as `GetWritePos() - start`.
    /// 3. The caller calls `FinishWritingObject` to finish writing a variant object.
    ///
    /// This function sorts the fields by key. If there are duplicate field keys:
    /// - when `allow_duplicate_keys` is true, the field with the greatest offset value (the last
    ///   appended one) is kept;
    /// - otherwise, the call fails.
    Status FinishWritingObject(int32_t start, std::vector<FieldEntry>* fields);

    /// Finishes writing a variant array after all of its elements have already been written. The
    /// process is similar to that of `FinishWritingObject`.
    Status FinishWritingArray(int32_t start, const std::vector<int32_t>& offsets);

    /// Appends a variant value. The keys of the input variant are inserted into the current
    /// variant dictionary and the value is rebuilt with new field ids. For scalar values, the
    /// binary slice is copied directly.
    Status AppendVariant(const GenericVariant& v);

    /// Appends the variant value without rewriting or creating any metadata. This is used when
    /// building an object during shredding, where there is a fixed pre-existing metadata that all
    /// shredded values refer to.
    Status ShallowAppendVariant(std::string_view value, int32_t pos);

 private:
    Status CheckCapacity(int32_t additional);
    Status AppendVariantImpl(std::string_view value, std::string_view metadata, int32_t pos);
    static int32_t GetIntegerSize(int32_t value);

    // The write buffer in building the variant value. Its first `write_pos_` bytes have been
    // written.
    std::vector<uint8_t> write_buffer_ = std::vector<uint8_t>(128);
    int32_t write_pos_ = 0;
    // Maps keys to a monotonically increasing id.
    std::unordered_map<std::string, int32_t> dictionary_;
    // Stores all keys in `dictionary_` in the order of id.
    std::vector<std::string> dictionary_keys_;
    const bool allow_duplicate_keys_;
};

}  // namespace paimon
