/*
 * 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 "iceberg/name_mapping.h"

#include <format>
#include <sstream>

#include "iceberg/util/formatter_internal.h"
#include "iceberg/util/macros.h"
#include "iceberg/util/visit_type.h"

namespace iceberg {

namespace {

// Helper function to join a list of field names with a dot
std::string JoinByDot(std::span<const std::string> parts) {
  std::stringstream ss;
  for (size_t i = 0; i < parts.size(); ++i) {
    if (i > 0) {
      ss << ".";
    }
    ss << parts[i];
  }
  return ss.str();
}

// Helper class to recursively index MappedField by field id
struct IndexByIdVisitor {
  std::unordered_map<int32_t, MappedFieldConstRef> field_by_id;

  void Visit(const MappedField& field) {
    if (field.field_id.has_value()) {
      field_by_id.emplace(field.field_id.value(), std::cref(field));
    }
    if (field.nested_mapping != nullptr) {
      Visit(*field.nested_mapping);
    }
  }

  void Visit(const MappedFields& fields) {
    for (const auto& field : fields.fields()) {
      Visit(field);
    }
  }

  void Visit(const NameMapping& name_mapping) { Visit(name_mapping.AsMappedFields()); }
};

// Helper class to recursively index MappedField by field name
struct IndexByNameVisitor {
  std::unordered_map<std::string, MappedFieldConstRef> field_by_name;

  void Visit(const MappedField& field) {
    for (const auto& name : field.names) {
      field_by_name.emplace(name, std::cref(field));
    }

    if (field.nested_mapping != nullptr) {
      IndexByNameVisitor nested_visitor;
      nested_visitor.Visit(*field.nested_mapping);

      for (const auto& [name, mapped_field] : nested_visitor.field_by_name) {
        for (const auto& prefix : field.names) {
          std::vector<std::string> parts = {prefix, name};
          field_by_name.emplace(JoinByDot(parts), std::cref(mapped_field));
        }
      }
    }
  }

  void Visit(const MappedFields& fields) {
    for (const auto& field : fields.fields()) {
      Visit(field);
    }
  }

  void Visit(const NameMapping& name_mapping) { Visit(name_mapping.AsMappedFields()); }
};

}  // namespace

MappedFields::MappedFields(std::vector<MappedField> fields)
    : fields_(std::move(fields)) {}

std::unique_ptr<MappedFields> MappedFields::Make(std::vector<MappedField> fields) {
  return std::unique_ptr<MappedFields>(new MappedFields(std::move(fields)));
}

std::optional<MappedFieldConstRef> MappedFields::Field(int32_t id) const {
  const auto& id_to_field = LazyIdToField();
  if (auto it = id_to_field.find(id); it != id_to_field.cend()) {
    return it->second;
  }
  return std::nullopt;
}

std::optional<int32_t> MappedFields::Id(std::string_view name) const {
  const auto& name_to_id = LazyNameToId();
  if (auto it = name_to_id.find(name); it != name_to_id.cend()) {
    return it->second;
  }
  return std::nullopt;
}

size_t MappedFields::Size() const { return fields_.size(); }

std::span<const MappedField> MappedFields::fields() const { return fields_; }

const std::unordered_map<std::string_view, int32_t>& MappedFields::LazyNameToId() const {
  if (name_to_id_.empty() && !fields_.empty()) {
    for (const auto& field : fields_) {
      for (const auto& name : field.names) {
        if (field.field_id.has_value()) {
          name_to_id_.emplace(name, field.field_id.value());
        }
      }
    }
  }
  return name_to_id_;
}

const std::unordered_map<int32_t, MappedFieldConstRef>& MappedFields::LazyIdToField()
    const {
  if (id_to_field_.empty() && !fields_.empty()) {
    for (const auto& field : fields_) {
      if (field.field_id.has_value()) {
        id_to_field_.emplace(field.field_id.value(), std::cref(field));
      }
    }
  }
  return id_to_field_;
}

NameMapping::NameMapping(std::unique_ptr<MappedFields> mapping)
    : mapping_(std::move(mapping)) {}

std::optional<MappedFieldConstRef> NameMapping::Find(int32_t id) const {
  const auto& fields_by_id = LazyFieldsById();
  if (auto iter = fields_by_id.find(id); iter != fields_by_id.cend()) {
    return iter->second;
  }
  return std::nullopt;
}

std::optional<MappedFieldConstRef> NameMapping::Find(
    std::span<const std::string> names) const {
  if (names.empty()) {
    return std::nullopt;
  }
  return Find(JoinByDot(names));
}

std::optional<MappedFieldConstRef> NameMapping::Find(const std::string& name) const {
  const auto& fields_by_name = LazyFieldsByName();
  if (auto iter = fields_by_name.find(name); iter != fields_by_name.cend()) {
    return iter->second;
  }
  return std::nullopt;
}

const MappedFields& NameMapping::AsMappedFields() const {
  if (mapping_ == nullptr) {
    const static std::unique_ptr<MappedFields> kEmptyFields = MappedFields::Make({});
    return *kEmptyFields;
  }
  return *mapping_;
}

const std::unordered_map<int32_t, MappedFieldConstRef>& NameMapping::LazyFieldsById()
    const {
  if (fields_by_id_.empty()) {
    IndexByIdVisitor visitor;
    visitor.Visit(AsMappedFields());
    fields_by_id_ = std::move(visitor.field_by_id);
  }
  return fields_by_id_;
}

const std::unordered_map<std::string, MappedFieldConstRef>&
NameMapping::LazyFieldsByName() const {
  if (fields_by_name_.empty()) {
    IndexByNameVisitor visitor;
    visitor.Visit(AsMappedFields());
    fields_by_name_ = std::move(visitor.field_by_name);
  }
  return fields_by_name_;
}

std::unique_ptr<NameMapping> NameMapping::MakeEmpty() {
  return std::unique_ptr<NameMapping>(new NameMapping(MappedFields::Make({})));
}

std::unique_ptr<NameMapping> NameMapping::Make(std::unique_ptr<MappedFields> fields) {
  return std::unique_ptr<NameMapping>(new NameMapping(std::move(fields)));
}

std::unique_ptr<NameMapping> NameMapping::Make(std::vector<MappedField> fields) {
  return Make(MappedFields::Make(std::move(fields)));
}

bool operator==(const MappedField& lhs, const MappedField& rhs) {
  if (lhs.field_id != rhs.field_id) {
    return false;
  }
  if (lhs.names != rhs.names) {
    return false;
  }
  if (lhs.nested_mapping == nullptr && rhs.nested_mapping == nullptr) {
    return true;
  }
  if (lhs.nested_mapping == nullptr || rhs.nested_mapping == nullptr) {
    return false;
  }
  return *lhs.nested_mapping == *rhs.nested_mapping;
}

bool operator==(const MappedFields& lhs, const MappedFields& rhs) {
  if (lhs.Size() != rhs.Size()) {
    return false;
  }
  auto lhs_fields = lhs.fields();
  auto rhs_fields = rhs.fields();
  for (size_t i = 0; i < lhs.Size(); ++i) {
    if (lhs_fields[i] != rhs_fields[i]) {
      return false;
    }
  }
  return true;
}

bool operator==(const NameMapping& lhs, const NameMapping& rhs) {
  return lhs.AsMappedFields() == rhs.AsMappedFields();
}

std::string ToString(const MappedField& field) {
  return std::format(
      "({} -> {}{})", field.names,
      field.field_id.has_value() ? std::to_string(field.field_id.value()) : "null",
      field.nested_mapping ? std::format(", {}", ToString(*field.nested_mapping)) : "");
}

std::string ToString(const MappedFields& fields) {
  return std::format("{}", fields.fields());
}

std::string ToString(const NameMapping& name_mapping) {
  const auto& fields = name_mapping.AsMappedFields();
  if (fields.Size() == 0) {
    return "[]";
  }
  std::string repr = "[\n";
  for (const auto& field : fields.fields()) {
    std::format_to(std::back_inserter(repr), "  {}\n", ToString(field));
  }
  repr += "]";
  return repr;
}

namespace {

// Visitor class for creating name mappings from schema types
class CreateMappingVisitor {
 public:
  Result<std::unique_ptr<MappedFields>> Visit(const StructType& type) const {
    std::vector<MappedField> fields;
    fields.reserve(type.fields().size());
    for (const auto& field : type.fields()) {
      ICEBERG_RETURN_UNEXPECTED(AddMappedField(fields, std::string(field.name()), field));
    }
    return MappedFields::Make(std::move(fields));
  }

  Result<std::unique_ptr<MappedFields>> Visit(const ListType& type) const {
    std::vector<MappedField> fields;
    ICEBERG_RETURN_UNEXPECTED(AddMappedField(fields, "element", type.fields().back()));
    return MappedFields::Make(std::move(fields));
  }

  Result<std::unique_ptr<MappedFields>> Visit(const MapType& type) const {
    std::vector<MappedField> fields;
    fields.reserve(2);
    ICEBERG_RETURN_UNEXPECTED(AddMappedField(fields, "key", type.key()));
    ICEBERG_RETURN_UNEXPECTED(AddMappedField(fields, "value", type.value()));
    return MappedFields::Make(std::move(fields));
  }

  template <typename T>
  Result<std::unique_ptr<MappedFields>> Visit(const T& type) const {
    return nullptr;
  }

 private:
  Status AddMappedField(std::vector<MappedField>& fields, const std::string& name,
                        const SchemaField& field) const {
    ICEBERG_ASSIGN_OR_RAISE(
        auto visit_result,
        VisitType(*field.type(), [this](const auto& type) { return this->Visit(type); }));

    fields.emplace_back(MappedField{
        .names = {name},
        .field_id = field.field_id(),
        .nested_mapping = std::move(visit_result),
    });
    return {};
  }
};

// Visitor class for updating name mappings with schema changes
class UpdateMappingVisitor {
 public:
  UpdateMappingVisitor(
      const std::unordered_map<int32_t, std::shared_ptr<SchemaField>>& updates,
      const std::multimap<int32_t, int32_t>& adds)
      : updates_(updates), adds_(adds) {}

  Result<std::unique_ptr<MappedFields>> VisitMapping(const NameMapping& mapping) {
    ICEBERG_ASSIGN_OR_RAISE(auto fields_result, VisitFields(mapping.AsMappedFields()));
    return AddNewFields(std::move(fields_result), kRootId);
  }

 private:
  static constexpr int32_t kRootId = -1;

  Result<std::unique_ptr<MappedFields>> VisitFields(const MappedFields& fields) {
    // Recursively visit all fields
    std::vector<MappedField> field_results;
    field_results.reserve(fields.Size());

    for (const auto& field : fields.fields()) {
      ICEBERG_ASSIGN_OR_RAISE(auto field_result, VisitField(field));
      field_results.push_back(std::move(field_result));
    }

    // Build update assignments map for removing reassigned names
    std::unordered_map<std::string, int32_t> update_assignments;
    for (const auto& field : field_results) {
      if (field.field_id.has_value()) {
        auto update_it = updates_.find(field.field_id.value());
        if (update_it != updates_.end()) {
          update_assignments.emplace(std::string(update_it->second->name()),
                                     field.field_id.value());
        }
      }
    }

    // Remove reassigned names from all fields
    for (auto& field : field_results) {
      field = RemoveReassignedNames(field, update_assignments);
    }

    return MappedFields::Make(std::move(field_results));
  }

  Result<MappedField> VisitField(const MappedField& field) {
    // Update this field's names
    std::unordered_set<std::string> field_names = field.names;
    if (field.field_id.has_value()) {
      auto update_it = updates_.find(field.field_id.value());
      if (update_it != updates_.end()) {
        field_names.insert(std::string(update_it->second->name()));
      }
    }

    std::unique_ptr<MappedFields> nested_mapping = nullptr;
    if (field.nested_mapping != nullptr) {
      ICEBERG_ASSIGN_OR_RAISE(nested_mapping, VisitFields(*field.nested_mapping));
    }

    // Add a new mapping for any new nested fields
    if (field.field_id.has_value()) {
      ICEBERG_ASSIGN_OR_RAISE(nested_mapping, AddNewFields(std::move(nested_mapping),
                                                           field.field_id.value()));
    }

    return MappedField{
        .names = std::move(field_names),
        .field_id = field.field_id,
        .nested_mapping = std::move(nested_mapping),
    };
  }

  Result<std::unique_ptr<MappedFields>> AddNewFields(
      std::unique_ptr<MappedFields> mapping, int32_t parent_id) {
    auto range = adds_.equal_range(parent_id);
    std::vector<const SchemaField*> fields_to_add;
    for (auto it = range.first; it != range.second; ++it) {
      auto update_it = updates_.find(it->second);
      if (update_it != updates_.end()) {
        fields_to_add.push_back(update_it->second.get());
      }
    }

    if (fields_to_add.empty()) {
      return std::move(mapping);
    }

    std::vector<MappedField> new_fields;
    CreateMappingVisitor create_visitor;
    for (const auto* field_to_add : fields_to_add) {
      ICEBERG_ASSIGN_OR_RAISE(
          auto nested_result,
          VisitType(*field_to_add->type(), [&create_visitor](const auto& type) {
            return create_visitor.Visit(type);
          }));

      new_fields.emplace_back(MappedField{
          .names = {std::string(field_to_add->name())},
          .field_id = field_to_add->field_id(),
          .nested_mapping = std::move(nested_result),
      });
    }

    if (mapping == nullptr || mapping->Size() == 0) {
      return MappedFields::Make(std::move(new_fields));
    }

    // Build assignments map for removing reassigned names
    std::unordered_map<std::string, int32_t> assignments;
    for (const auto* field_to_add : fields_to_add) {
      assignments.emplace(std::string(field_to_add->name()), field_to_add->field_id());
    }

    // create a copy of fields that can be updated (append new fields, replace existing
    // for reassignment)
    std::vector<MappedField> fields;
    fields.reserve(mapping->Size() + new_fields.size());
    for (const auto& field : mapping->fields()) {
      fields.push_back(RemoveReassignedNames(field, assignments));
    }

    fields.insert(fields.end(), std::make_move_iterator(new_fields.begin()),
                  std::make_move_iterator(new_fields.end()));

    return MappedFields::Make(std::move(fields));
  }

  static MappedField RemoveReassignedNames(
      const MappedField& field,
      const std::unordered_map<std::string, int32_t>& assignments) {
    std::unordered_set<std::string> updated_names = field.names;
    std::erase_if(updated_names, [&](const std::string& name) {
      auto assign_it = assignments.find(name);
      return assign_it != assignments.end() &&
             (!field.field_id.has_value() || assign_it->second != field.field_id.value());
    });
    return MappedField{
        .names = std::move(updated_names),
        .field_id = field.field_id,
        .nested_mapping = field.nested_mapping,
    };
  }

  const std::unordered_map<int32_t, std::shared_ptr<SchemaField>>& updates_;
  const std::multimap<int32_t, int32_t>& adds_;
};

}  // namespace

Result<std::unique_ptr<NameMapping>> CreateMapping(const Schema& schema) {
  CreateMappingVisitor visitor;
  ICEBERG_ASSIGN_OR_RAISE(
      auto result,
      VisitType(schema,
                [&visitor](const auto& type) -> Result<std::unique_ptr<MappedFields>> {
                  return visitor.Visit(type);
                }));
  return NameMapping::Make(std::move(result));
}

Result<std::unique_ptr<NameMapping>> UpdateMapping(
    const NameMapping& mapping,
    const std::unordered_map<int32_t, std::shared_ptr<SchemaField>>& updates,
    const std::multimap<int32_t, int32_t>& adds) {
  UpdateMappingVisitor visitor(updates, adds);
  ICEBERG_ASSIGN_OR_RAISE(auto result, visitor.VisitMapping(mapping));
  return NameMapping::Make(std::move(result));
}

}  // namespace iceberg
