#include <algorithm>
#include <cstdint>
#include <iomanip>
#include <iostream>
#include <limits>
#include <stdexcept>
#include <string>
#include <string_view>
#include <vector>

struct NodeId {
    std::uint32_t value{};

    auto operator<=>(const NodeId&) const = default;
};

struct EdgeId {
    std::uint32_t value{};

    auto operator<=>(const EdgeId&) const = default;
};

enum class NodeKind : std::uint8_t {
    person,
    agent,
    entity,
    message,
    episode,
    claim,
    action,
    outcome
};

enum class EdgeKind : std::uint8_t {
    participant,
    subject,
    object,
    asserted_by,
    supported_by,
    recommends,
    accepts,
    produces,
    qualifies,
    supersedes
};

struct Node {
    NodeId id;
    NodeKind kind;
    std::string label;
    std::string predicate;
};

struct Edge {
    EdgeId id;
    NodeId from;
    NodeId to;
    EdgeKind kind;
    int event_day;
    double confidence;
};

[[nodiscard]] constexpr std::string_view name(const NodeKind kind) {
    switch (kind) {
        case NodeKind::person: return "person";
        case NodeKind::agent: return "agent";
        case NodeKind::entity: return "entity";
        case NodeKind::message: return "message";
        case NodeKind::episode: return "episode";
        case NodeKind::claim: return "claim";
        case NodeKind::action: return "action";
        case NodeKind::outcome: return "outcome";
    }
    return "unknown";
}

[[nodiscard]] constexpr std::string_view name(const EdgeKind kind) {
    switch (kind) {
        case EdgeKind::participant: return "participant";
        case EdgeKind::subject: return "subject";
        case EdgeKind::object: return "object";
        case EdgeKind::asserted_by: return "asserted_by";
        case EdgeKind::supported_by: return "supported_by";
        case EdgeKind::recommends: return "recommends";
        case EdgeKind::accepts: return "accepts";
        case EdgeKind::produces: return "produces";
        case EdgeKind::qualifies: return "qualifies";
        case EdgeKind::supersedes: return "supersedes";
    }
    return "unknown";
}

class MemoryMultigraph {
public:
    [[nodiscard]] NodeId add_node(
        const NodeKind kind,
        std::string label,
        std::string predicate = {}) {
        if (label.empty()) {
            throw std::invalid_argument{"node label cannot be empty"};
        }
        if (kind == NodeKind::claim && predicate.empty()) {
            throw std::invalid_argument{"claim predicate cannot be empty"};
        }
        if (kind != NodeKind::claim && !predicate.empty()) {
            throw std::invalid_argument{"only claim nodes accept a predicate"};
        }
        if (nodes_.size() >=
            static_cast<std::size_t>(std::numeric_limits<std::uint32_t>::max())) {
            throw std::overflow_error{"node identifier space exhausted"};
        }

        const auto id = NodeId{static_cast<std::uint32_t>(nodes_.size())};
        nodes_.push_back(
            Node{id, kind, std::move(label), std::move(predicate)});
        outgoing_.emplace_back();
        return id;
    }

    [[nodiscard]] EdgeId add_edge(
        const NodeId from,
        const NodeId to,
        const EdgeKind kind,
        const int event_day,
        const double confidence) {
        require_node(from);
        require_node(to);
        if (event_day < 0) {
            throw std::invalid_argument{"event day cannot be negative"};
        }
        if (!(confidence >= 0.0 && confidence <= 1.0)) {
            throw std::invalid_argument{"confidence must be in [0, 1]"};
        }
        if (edges_.size() >=
            static_cast<std::size_t>(std::numeric_limits<std::uint32_t>::max())) {
            throw std::overflow_error{"edge identifier space exhausted"};
        }

        // Endpoints do not identify an edge. Every occurrence receives an EdgeId
        // so parallel observations can keep independent time and confidence.
        const auto id = EdgeId{static_cast<std::uint32_t>(edges_.size())};
        edges_.push_back(Edge{id, from, to, kind, event_day, confidence});
        outgoing_[from.value].push_back(id);
        return id;
    }

    [[nodiscard]] const Node& node(const NodeId id) const {
        require_node(id);
        return nodes_[id.value];
    }

    [[nodiscard]] const Edge& edge(const EdgeId id) const {
        if (id.value >= edges_.size()) {
            throw std::out_of_range{"unknown edge identifier"};
        }
        return edges_[id.value];
    }

    [[nodiscard]] std::vector<EdgeId> outgoing(
        const NodeId from,
        const EdgeKind kind) const {
        require_node(from);
        std::vector<EdgeId> result;
        for (const auto id : outgoing_[from.value]) {
            if (edge(id).kind == kind) {
                result.push_back(id);
            }
        }
        return result;
    }

    [[nodiscard]] std::vector<EdgeId> incident_to_claim(
        const NodeId claim) const {
        require_node(claim);
        if (node(claim).kind != NodeKind::claim) {
            throw std::invalid_argument{"requested node is not a claim"};
        }

        std::vector<EdgeId> result;
        for (const auto& candidate : edges_) {
            if (candidate.from == claim || candidate.to == claim) {
                result.push_back(candidate.id);
            }
        }

        // Stable identifiers provide deterministic explanations and test output.
        std::ranges::sort(result, {}, &EdgeId::value);
        return result;
    }

    [[nodiscard]] std::size_t node_count() const noexcept {
        return nodes_.size();
    }

    [[nodiscard]] std::size_t edge_count() const noexcept {
        return edges_.size();
    }

private:
    void require_node(const NodeId id) const {
        if (id.value >= nodes_.size()) {
            throw std::out_of_range{"unknown node identifier"};
        }
    }

    std::vector<Node> nodes_;
    std::vector<Edge> edges_;
    std::vector<std::vector<EdgeId>> outgoing_;
};

int main() {
    MemoryMultigraph memory;

    const auto lia = memory.add_node(NodeKind::person, "Lia");
    const auto aurora = memory.add_node(NodeKind::agent, "Aurora");
    const auto tea = memory.add_node(NodeKind::entity, "tea without sugar");
    const auto message0 = memory.add_node(
        NodeKind::message, "Lia: I prefer tea without sugar");
    const auto episode1 = memory.add_node(
        NodeKind::episode, "evening recommendation on day 2");
    const auto claim0 = memory.add_node(
        NodeKind::claim, "Lia prefers tea without sugar", "prefers");
    const auto recommendation =
        memory.add_node(NodeKind::action, "recommend tea");
    const auto accepted =
        memory.add_node(NodeKind::outcome, "recommendation accepted");

    static_cast<void>(
        memory.add_edge(claim0, lia, EdgeKind::subject, 0, 1.00));
    static_cast<void>(
        memory.add_edge(claim0, tea, EdgeKind::object, 0, 1.00));
    static_cast<void>(
        memory.add_edge(claim0, message0, EdgeKind::asserted_by, 0, 0.99));
    static_cast<void>(
        memory.add_edge(claim0, episode1, EdgeKind::supported_by, 2, 0.90));
    static_cast<void>(
        memory.add_edge(episode1, lia, EdgeKind::participant, 2, 1.00));
    static_cast<void>(
        memory.add_edge(episode1, aurora, EdgeKind::participant, 2, 1.00));
    static_cast<void>(
        memory.add_edge(aurora, tea, EdgeKind::recommends, 2, 0.96));
    static_cast<void>(
        memory.add_edge(aurora, tea, EdgeKind::recommends, 52, 0.72));
    static_cast<void>(
        memory.add_edge(lia, tea, EdgeKind::accepts, 2, 1.00));
    static_cast<void>(
        memory.add_edge(recommendation, accepted, EdgeKind::produces, 2, 1.00));
    static_cast<void>(
        memory.add_edge(episode1, recommendation, EdgeKind::produces, 2, 1.00));

    const auto recommendations =
        memory.outgoing(aurora, EdgeKind::recommends);
    std::cout << "nodes=" << memory.node_count()
              << " edges=" << memory.edge_count() << '\n';
    std::cout << "parallel_recommendations=" << recommendations.size() << '\n';
    for (const auto id : recommendations) {
        const auto& relation = memory.edge(id);
        std::cout << "  E" << relation.id.value
                  << " day=" << relation.event_day
                  << " confidence=" << std::fixed << std::setprecision(2)
                  << relation.confidence << '\n';
    }

    std::cout << "claim=C" << claim0.value
              << " predicate=" << memory.node(claim0).predicate << " | "
              << memory.node(claim0).label << '\n';
    for (const auto id : memory.incident_to_claim(claim0)) {
        const auto& relation = memory.edge(id);
        const auto& target = memory.node(relation.to);
        std::cout << "  E" << relation.id.value << ' '
                  << name(relation.kind) << " -> "
                  << name(target.kind) << ':' << target.label << '\n';
    }
}
