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

#pragma once

#include <cstdint>
#include <map>
#include <memory>
#include <optional>
#include <string>
#include <vector>

#include "arrow/api.h"
#include "paimon/common/types/data_field.h"
#include "paimon/common/utils/jsonizable.h"
#include "paimon/result.h"
#include "paimon/schema/schema.h"
#include "rapidjson/allocators.h"
#include "rapidjson/document.h"
#include "rapidjson/rapidjson.h"

struct ArrowSchema;

namespace paimon {
/// Schema of a table, including schemaId and fieldId.
class TableSchema : public DataSchema, public Jsonizable<TableSchema> {
 public:
    static constexpr int64_t FIRST_SCHEMA_ID = 0;
    static constexpr int32_t PAIMON_07_VERSION = 1;
    static constexpr int32_t PAIMON_08_VERSION = 2;
    static constexpr int32_t CURRENT_VERSION = 3;

    static Result<std::unique_ptr<TableSchema>> Create(
        int64_t schema_id, const std::shared_ptr<arrow::Schema>& schema,
        const std::vector<std::string>& partition_keys,
        const std::vector<std::string>& primary_keys,
        const std::map<std::string, std::string>& options);

    static Result<std::unique_ptr<TableSchema>> CreateFromJson(const std::string& json_str);

    rapidjson::Value ToJson(rapidjson::Document::AllocatorType* allocator) const
        noexcept(false) override;

    void FromJson(const rapidjson::Value& obj) noexcept(false) override;

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

    Result<std::unique_ptr<::ArrowSchema>> GetArrowSchema() const override;

    Result<std::string> GetJsonSchema() const override {
        return ToJsonString();
    }

    std::vector<std::string> FieldNames() const override;

    Result<FieldType> GetFieldType(const std::string& field_name) const override;

    int64_t Id() const override {
        return id_;
    }
    const std::vector<std::string>& PrimaryKeys() const override {
        return primary_keys_;
    }
    const std::vector<std::string>& PartitionKeys() const override {
        return partition_keys_;
    }

    const std::vector<std::string>& BucketKeys() const override {
        return bucket_keys_;
    }

    int32_t NumBuckets() const override {
        return num_bucket_;
    }
    int32_t HighestFieldId() const override {
        return highest_field_id_;
    }
    const std::map<std::string, std::string>& Options() const override {
        return options_;
    }
    const std::vector<DataField>& Fields() const {
        return fields_;
    }

    Result<DataField> GetField(const std::string& field_name) const;

    Result<DataField> GetField(int32_t field_id) const;

    Result<std::vector<DataField>> GetFields(const std::vector<std::string>& field_names) const;
    Result<std::vector<std::string>> TrimmedPrimaryKeys() const;
    Result<std::vector<DataField>> TrimmedPrimaryKeyFields() const;
    Result<std::shared_ptr<arrow::Schema>> TrimmedPrimaryKeySchema() const;

    std::optional<std::string> Comment() const override {
        return comment_;
    }

    bool CrossPartitionUpdate() const;

    int64_t TimeMillis() const {
        return time_millis_;
    }

 private:
    JSONIZABLE_FRIEND_AND_DEFAULT_CTOR(TableSchema);

    static Result<std::unique_ptr<TableSchema>> InitSchema(
        int64_t schema_id, const std::vector<DataField>& fields, int32_t highest_field_id,
        const std::vector<std::string>& partition_keys,
        const std::vector<std::string>& primary_keys,
        const std::map<std::string, std::string>& options,
        const std::optional<std::string>& comment, int64_t time_millis);

    TableSchema(int32_t version, int64_t id, const std::vector<DataField>& fields,
                int32_t highest_field_id, const std::vector<std::string>& partition_keys,
                const std::vector<std::string>& primary_keys,
                const std::map<std::string, std::string>& options,
                const std::optional<std::string>& comment, int64_t time_millis);

    Result<std::vector<std::string>> OriginalBucketKeys() const;

    static Result<std::shared_ptr<arrow::Field>> AssignFieldIdsRecursively(
        const std::shared_ptr<arrow::Field>& field, bool set_field_id, int32_t* field_id);

    static Result<std::shared_ptr<arrow::KeyValueMetadata>> MakeMetaDataWithFieldId(
        const std::shared_ptr<arrow::Field>& field, int32_t field_id);

 private:
    // version of schema for paimon
    int32_t version_ = -1;
    int64_t id_ = -1;
    std::vector<DataField> fields_;
    /// Not available from fields, as some fields may have been deleted.
    int32_t highest_field_id_ = -1;
    std::vector<std::string> partition_keys_;
    std::vector<std::string> primary_keys_;
    std::vector<std::string> bucket_keys_;
    int32_t num_bucket_ = -1;
    std::map<std::string, std::string> options_;
    std::optional<std::string> comment_;
    int64_t time_millis_ = -1;
};
}  // namespace paimon
