/*
 * 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.
 */

#include "paimon/data/variant.h"

#include <utility>

#include "arrow/api.h"
#include "arrow/c/bridge.h"
#include "paimon/common/data/variant/generic_variant.h"
#include "paimon/common/data/variant/variant_access_utils.h"
#include "paimon/common/data/variant/variant_defs.h"
#include "paimon/common/data/variant/variant_get.h"
#include "paimon/common/data/variant/variant_path_segment.h"
#include "paimon/common/data/variant/variant_type_utils.h"
#include "paimon/common/types/data_field.h"
#include "paimon/common/utils/arrow/mem_utils.h"
#include "paimon/common/utils/arrow/status_utils.h"

namespace paimon {

class Variant::Impl {
 public:
    Impl(std::shared_ptr<GenericVariant> variant, std::shared_ptr<MemoryPool> pool)
        : variant_(std::move(variant)), pool_(std::move(pool)), arrow_pool_(GetArrowPool(pool_)) {}

    const std::shared_ptr<GenericVariant>& GetVariant() const {
        return variant_;
    }

    const std::shared_ptr<MemoryPool>& GetPool() const {
        return pool_;
    }

    const std::shared_ptr<arrow::MemoryPool>& GetArrowMemoryPool() const {
        return arrow_pool_;
    }

 private:
    std::shared_ptr<GenericVariant> variant_;
    std::shared_ptr<MemoryPool> pool_;
    std::shared_ptr<arrow::MemoryPool> arrow_pool_;
};

Variant::Variant(std::unique_ptr<Impl>&& impl) : impl_(std::move(impl)) {}
Variant::~Variant() = default;

Result<std::unique_ptr<Variant>> Variant::FromJson(const std::string& json,
                                                   const std::shared_ptr<MemoryPool>& pool) {
    PAIMON_ASSIGN_OR_RAISE(std::shared_ptr<GenericVariant> variant,
                           GenericVariant::FromJson(json, pool));
    auto impl = std::make_unique<Impl>(std::move(variant), pool);
    return std::unique_ptr<Variant>(new Variant(std::move(impl)));
}

Result<std::unique_ptr<Variant>> Variant::Create(const char* value, uint64_t value_length,
                                                 const char* metadata, uint64_t metadata_length,
                                                 const std::shared_ptr<MemoryPool>& pool) {
    PAIMON_ASSIGN_OR_RAISE(
        std::shared_ptr<GenericVariant> variant,
        GenericVariant::Create(std::string_view(value, value_length),
                               std::string_view(metadata, metadata_length), pool));
    auto impl = std::make_unique<Impl>(std::move(variant), pool);
    return std::unique_ptr<Variant>(new Variant(std::move(impl)));
}

std::string_view Variant::Value() const {
    return impl_->GetVariant()->RawValue();
}

std::string_view Variant::Metadata() const {
    return impl_->GetVariant()->Metadata();
}

int64_t Variant::SizeInBytes() const {
    return impl_->GetVariant()->SizeInBytes();
}

Result<std::string> Variant::ToJson(const std::string& zone_id) const {
    return impl_->GetVariant()->ToJson(zone_id);
}

Result<std::optional<Literal>> Variant::VariantGet(const std::string& path,
                                                   struct ArrowSchema* target_type,
                                                   const VariantCastArgs& cast_args) const {
    PAIMON_ASSIGN_OR_RAISE_FROM_ARROW(std::shared_ptr<arrow::Field> target_field,
                                      arrow::ImportField(target_type));
    return VariantGetExecutor::Get(impl_->GetVariant(), path, target_field->type(), cast_args);
}

Result<std::unique_ptr<struct ArrowArray>> Variant::VariantGetArrow(
    const std::string& path, struct ArrowSchema* target_field,
    const VariantCastArgs& cast_args) const {
    PAIMON_ASSIGN_OR_RAISE_FROM_ARROW(std::shared_ptr<arrow::Field> field,
                                      arrow::ImportField(target_field));
    PAIMON_ASSIGN_OR_RAISE(
        std::shared_ptr<arrow::Array> array,
        VariantGetExecutor::GetAsArrow(impl_->GetVariant(), path, field, cast_args,
                                       impl_->GetPool(), impl_->GetArrowMemoryPool()));
    auto result = std::make_unique<struct ArrowArray>();
    PAIMON_RETURN_NOT_OK_FROM_ARROW(arrow::ExportArray(*array, result.get()));
    return result;
}

Result<std::optional<std::string>> Variant::VariantGetJson(const std::string& path,
                                                           const std::string& zone_id) const {
    PAIMON_ASSIGN_OR_RAISE(std::shared_ptr<GenericVariant> extracted,
                           VariantGetExecutor::ExtractByPath(impl_->GetVariant(), path));
    if (extracted == nullptr) {
        return std::optional<std::string>(std::nullopt);
    }
    PAIMON_ASSIGN_OR_RAISE(std::string json, extracted->ToJson(zone_id));
    return std::optional<std::string>(std::move(json));
}

Result<std::unique_ptr<struct ArrowSchema>> Variant::ArrowField(
    const std::string& field_name, bool nullable,
    std::unordered_map<std::string, std::string> metadata) {
    auto variant_field = VariantTypeUtils::ToArrowField(field_name, nullable, std::move(metadata));
    auto field = std::make_unique<struct ArrowSchema>();
    PAIMON_RETURN_NOT_OK_FROM_ARROW(arrow::ExportField(*variant_field, field.get()));
    return field;
}

class VariantAccessBuilder::Impl {
 public:
    arrow::FieldVector fields;
};

VariantAccessBuilder::VariantAccessBuilder() : impl_(std::make_unique<Impl>()) {}
VariantAccessBuilder::~VariantAccessBuilder() = default;

Status VariantAccessBuilder::AddField(struct ArrowSchema* target_type, const std::string& path,
                                      bool fail_on_error, const std::string& zone_id) {
    PAIMON_ASSIGN_OR_RAISE_FROM_ARROW(std::shared_ptr<arrow::Field> target,
                                      arrow::ImportField(target_type));
    // Validate the path eagerly so mistakes fail at build time, not at read time.
    PAIMON_RETURN_NOT_OK(VariantPathSegment::Parse(path));
    // Keep the target field's own metadata (e.g. the variant extension marker of a
    // `Variant::ArrowField` target, which drives the deep re-encode cast) and add the access
    // description to it.
    std::vector<std::string> keys = {DataField::DESCRIPTION};
    std::vector<std::string> values = {
        VariantAccessUtils::BuildVariantMetadata(path, fail_on_error, zone_id)};
    if (target->metadata() != nullptr) {
        for (int64_t i = 0; i < target->metadata()->size(); ++i) {
            if (target->metadata()->key(i) == DataField::DESCRIPTION) {
                continue;
            }
            keys.push_back(target->metadata()->key(i));
            values.push_back(target->metadata()->value(i));
        }
    }
    impl_->fields.push_back(arrow::field(std::to_string(impl_->fields.size()), target->type(),
                                         /*nullable=*/true,
                                         arrow::KeyValueMetadata::Make(keys, values)));
    return Status::OK();
}

Result<std::unique_ptr<struct ArrowSchema>> VariantAccessBuilder::Build(
    const std::string& field_name) const {
    if (impl_->fields.empty()) {
        return Status::Invalid("a variant-access projection needs at least one field");
    }
    auto access_field =
        arrow::field(field_name, arrow::struct_(impl_->fields), /*nullable=*/true,
                     arrow::KeyValueMetadata::Make({VariantDefs::kExtensionTypeKey},
                                                   {VariantDefs::kExtensionTypeValue}));
    auto field = std::make_unique<struct ArrowSchema>();
    PAIMON_RETURN_NOT_OK_FROM_ARROW(arrow::ExportField(*access_field, field.get()));
    return field;
}

}  // namespace paimon
