#pragma once

#include <cstddef>
#include <cstdint>
#include <map>
#include <span>
#include <string>
#include <unordered_map>
#include <utility>
#include <vector>

#include <fesa/model/beam_element.hpp>
#include <fesa/model/beam_section.hpp>
#include <fesa/model/entity_set.hpp>
#include <fesa/model/material.hpp>
#include <fesa/model/node.hpp>
#include <fesa/model/step_definition.hpp>

namespace fesa {

class DomainBuilder;

class Domain final {
public:
    Domain(const Domain&) = default;
    Domain(Domain&&) noexcept = default;
    Domain& operator=(const Domain&) = default;
    Domain& operator=(Domain&&) noexcept = default;

    [[nodiscard]] std::span<const Node> nodes() const noexcept {
        return nodes_;
    }

    [[nodiscard]] std::span<const BeamElement> beam_elements() const noexcept {
        return beam_elements_;
    }

    [[nodiscard]] std::span<const IsotropicElastic> materials() const noexcept {
        return materials_;
    }

    [[nodiscard]] std::span<const BeamSection> sections() const noexcept {
        return sections_;
    }

    [[nodiscard]] std::span<const NodeSet> node_sets() const noexcept {
        return node_sets_;
    }

    [[nodiscard]] std::span<const ElementSet> element_sets() const noexcept {
        return element_sets_;
    }

    [[nodiscard]] const StepDefinition& step() const noexcept {
        return step_;
    }

    [[nodiscard]] const Node& node(NodeId id) const;
    [[nodiscard]] const Node& node(const EntityOrigin& origin) const;
    [[nodiscard]] const IsotropicElastic& material(MaterialId id) const;
    [[nodiscard]] const BeamSection& section(SectionId id) const;

private:
    friend class DomainBuilder;

    using OriginKey = std::pair<std::string, std::int64_t>;

    Domain(
        std::vector<Node> nodes,
        std::vector<BeamElement> beam_elements,
        std::vector<IsotropicElastic> materials,
        std::vector<BeamSection> sections,
        std::vector<NodeSet> node_sets,
        std::vector<ElementSet> element_sets,
        StepDefinition step);

    [[nodiscard]] static OriginKey origin_key(const EntityOrigin& origin);

    std::vector<Node> nodes_;
    std::vector<BeamElement> beam_elements_;
    std::vector<IsotropicElastic> materials_;
    std::vector<BeamSection> sections_;
    std::vector<NodeSet> node_sets_;
    std::vector<ElementSet> element_sets_;
    StepDefinition step_;
    std::unordered_map<std::int64_t, std::size_t> node_indices_;
    std::map<OriginKey, std::size_t> node_origin_indices_;
    std::unordered_map<std::int64_t, std::size_t> beam_element_indices_;
    std::map<OriginKey, std::size_t> beam_element_origin_indices_;
    std::unordered_map<std::int64_t, std::size_t> material_indices_;
    std::unordered_map<std::int64_t, std::size_t> section_indices_;
};

}  // namespace fesa
