#include <fesa/io/abaqus/active_input.hpp>

#include <algorithm>
#include <set>
#include <string>
#include <string_view>
#include <utility>

namespace fesa {
namespace {

const std::string* parameter(
    const DeckRecord& record,
    const std::string_view name) {
    const auto found = record.parameters.find(name);
    return found == record.parameters.end() ? nullptr : &found->second;
}

bool is_flat_model_record(const DeckRecord& record) {
    return record.keyword == "NODE" || record.keyword == "ELEMENT" ||
           record.keyword == "NSET" || record.keyword == "ELSET" ||
           record.keyword == "BEAM GENERAL SECTION" ||
           record.keyword == "TRANSVERSE SHEAR STIFFNESS";
}

ActiveInputResult failure(
    std::string code,
    std::string message,
    const SourceLocation& source) {
    return {
        std::nullopt,
        {{
            DiagnosticStage::semantic,
            Severity::error,
            std::move(code),
            std::move(message),
            source,
        }},
    };
}

}  // namespace

ActiveInputResult select_active_input(const ParsedDeck& deck) {
    const bool hierarchical =
        !deck.parts.empty() || deck.assembly.has_value();
    if (!hierarchical) {
        return {
            ActiveInputView{
                true,
                {},
                {},
                deck.global_records,
                {},
            },
            {},
        };
    }

    const auto flat_record =
        std::ranges::find_if(deck.global_records, is_flat_model_record);
    if (flat_record != deck.global_records.end()) {
        return failure(
            "abaqus.semantic.mixed_mesh_organization",
            "Flat model records cannot be mixed with Part/Assembly input.",
            flat_record->source);
    }

    std::set<std::string, std::less<>> part_names;
    for (const ParsedPart& part : deck.parts) {
        if (!part_names.insert(part.name).second) {
            return failure(
                "abaqus.semantic.duplicate_part",
                "Part name '" + part.name + "' is defined more than once.",
                part.source);
        }
    }

    if (!deck.assembly.has_value()) {
        const SourceLocation source =
            deck.parts.empty() ? SourceLocation{} : deck.parts.front().source;
        return failure(
            "abaqus.semantic.assembly_count",
            "Hierarchical Phase 1 input requires exactly one Assembly.",
            source);
    }

    const ParsedAssembly& assembly = *deck.assembly;
    if (assembly.instances.size() != 1U) {
        const SourceLocation& source = assembly.instances.size() > 1U
                                           ? assembly.instances[1].source
                                           : assembly.source;
        return failure(
            "abaqus.semantic.instance_count",
            "Phase 1 requires exactly one Instance.",
            source);
    }

    const ParsedInstance& instance = assembly.instances.front();
    if (!instance.transform_data.empty()) {
        const SourceLocation& source = instance.transform_sources.empty()
                                           ? instance.source
                                           : instance.transform_sources.front();
        return failure(
            "abaqus.semantic.instance_transform",
            "Instance translation and rotation data are unsupported.",
            source);
    }

    const auto part =
        std::ranges::find(deck.parts, instance.part_name, &ParsedPart::name);
    if (part == deck.parts.end()) {
        return failure(
            "abaqus.semantic.missing_part",
            "Instance '" + instance.name + "' references missing Part '" +
                instance.part_name + "'.",
            instance.source);
    }

    for (const DeckRecord& record : assembly.records) {
        if (record.keyword != "NSET" && record.keyword != "ELSET") {
            continue;
        }
        const std::string* record_instance = parameter(record, "INSTANCE");
        if (record_instance == nullptr || *record_instance != instance.name) {
            const std::string* name = parameter(
                record, record.keyword == "NSET" ? "NSET" : "ELSET");
            return failure(
                "abaqus.semantic.wrong_instance",
                "Assembly set '" +
                    (name == nullptr ? std::string{} : *name) +
                    "' must reference the active Instance.",
                record.source);
        }
    }

    return {
        ActiveInputView{
            false,
            part->name,
            instance.name,
            part->records,
            assembly.records,
        },
        {},
    };
}

}  // namespace fesa
