#include <algorithm>
#include <cmath>
#include <cstddef>
#include <cstdint>
#include <iomanip>
#include <iostream>
#include <limits>
#include <optional>
#include <stdexcept>
#include <string>
#include <string_view>
#include <utility>
#include <vector>

struct MemoryId {
    std::uint32_t value{};

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

enum class MemoryKind : std::uint8_t {
    Episode,
    Semantic,
    Procedure,
    Reflection
};

struct Memory {
    MemoryId id;
    MemoryKind kind;
    std::string text;
    std::vector<std::string> tags;
    double importance;
    double confidence;
    int created_day;
    std::optional<int> valid_until;
    bool active{true};
};

struct Relation {
    std::uint32_t id;
    MemoryId from;
    MemoryId to;
    std::string type;
    double weight;
};

struct ScoreBreakdown {
    MemoryId id;
    double relevance;
    double importance;
    double confidence;
    double recency;
    double total;
};

class MemoryStore {
public:
    [[nodiscard]] MemoryId add(
        MemoryKind kind,
        std::string text,
        std::vector<std::string> tags,
        double importance,
        double confidence,
        int created_day
    ) {
        validate_unit_interval(importance, "importance");
        validate_unit_interval(confidence, "confidence");
        if (created_day < 0) {
            throw std::invalid_argument("created_day must be non-negative");
        }
        if (memories_.size() >= std::numeric_limits<std::uint32_t>::max()) {
            throw std::overflow_error("memory identifier space exhausted");
        }

        const MemoryId id{static_cast<std::uint32_t>(memories_.size())};
        memories_.push_back(Memory{
            id,
            kind,
            std::move(text),
            std::move(tags),
            importance,
            confidence,
            created_day,
            std::nullopt,
            true
        });
        return id;
    }

    void relate(MemoryId from, MemoryId to, std::string type, double weight) {
        validate_id(from);
        validate_id(to);
        validate_unit_interval(weight, "relation weight");
        if (relations_.size() >= std::numeric_limits<std::uint32_t>::max()) {
            throw std::overflow_error("relation identifier space exhausted");
        }

        // Relation identity is independent of its endpoints. Two memories may
        // therefore carry several evidence, temporal, or causal relations.
        relations_.push_back(Relation{
            static_cast<std::uint32_t>(relations_.size()),
            from,
            to,
            std::move(type),
            weight
        });
    }

    void supersede(MemoryId old_id, MemoryId new_id, int day) {
        validate_id(old_id);
        validate_id(new_id);
        if (old_id == new_id) {
            throw std::invalid_argument("a memory cannot supersede itself");
        }

        Memory& old_memory = memories_[old_id.value];
        const Memory& new_memory = memories_[new_id.value];
        if (day < old_memory.created_day || day < new_memory.created_day) {
            throw std::invalid_argument("supersession cannot precede a memory");
        }

        // Closing validity preserves history. Erasing the old item would make
        // later temporal questions impossible to answer faithfully.
        old_memory.active = false;
        old_memory.valid_until = day;
        relate(old_id, new_id, "superseded_by", 1.0);
    }

    [[nodiscard]] std::vector<ScoreBreakdown> recall(
        const std::vector<std::string>& query_tags,
        int current_day,
        std::size_t limit
    ) const {
        if (query_tags.empty() || limit == 0U) {
            return {};
        }

        std::vector<ScoreBreakdown> ranked;
        ranked.reserve(memories_.size());

        for (const Memory& memory : memories_) {
            if (!memory.active || memory.created_day > current_day) {
                continue;
            }

            const double relevance = tag_relevance(query_tags, memory.tags);
            if (relevance == 0.0) {
                continue;
            }

            const int age_days = current_day - memory.created_day;
            const double recency = 1.0 / (1.0 + static_cast<double>(age_days) / 30.0);
            const double total =
                0.45 * relevance +
                0.20 * memory.importance +
                0.20 * memory.confidence +
                0.15 * recency;

            ranked.push_back(ScoreBreakdown{
                memory.id,
                relevance,
                memory.importance,
                memory.confidence,
                recency,
                total
            });
        }

        // A deterministic tie-break makes tests and explanations reproducible.
        std::ranges::sort(ranked, [](const auto& left, const auto& right) {
            if (std::abs(left.total - right.total) > 1.0e-12) {
                return left.total > right.total;
            }
            return left.id.value < right.id.value;
        });

        if (ranked.size() > limit) {
            ranked.resize(limit);
        }
        return ranked;
    }

    [[nodiscard]] const Memory& memory(MemoryId id) const {
        validate_id(id);
        return memories_[id.value];
    }

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

private:
    static void validate_unit_interval(double value, std::string_view field) {
        if (!std::isfinite(value) || value < 0.0 || value > 1.0) {
            throw std::invalid_argument(std::string(field) + " must be in [0, 1]");
        }
    }

    void validate_id(MemoryId id) const {
        if (static_cast<std::size_t>(id.value) >= memories_.size()) {
            throw std::out_of_range("unknown memory identifier");
        }
    }

    [[nodiscard]] static double tag_relevance(
        const std::vector<std::string>& query,
        const std::vector<std::string>& memory_tags
    ) {
        std::size_t matches = 0U;
        for (const std::string& query_tag : query) {
            const bool found = std::ranges::find(memory_tags, query_tag) != memory_tags.end();
            matches += found ? 1U : 0U;
        }
        return static_cast<double>(matches) / static_cast<double>(query.size());
    }

    std::vector<Memory> memories_;
    std::vector<Relation> relations_;
};

int main() {
    MemoryStore store;

    const MemoryId tea = store.add(
        MemoryKind::Semantic,
        "Lia prefers tea without sugar",
        {"lia", "tea", "preference"},
        0.72,
        0.96,
        0
    );
    const MemoryId accepted = store.add(
        MemoryKind::Episode,
        "Lia accepted Aurora's tea recommendation",
        {"lia", "tea", "recommendation", "outcome"},
        0.65,
        1.00,
        2
    );
    const MemoryId avoid_caffeine = store.add(
        MemoryKind::Semantic,
        "Lia avoids caffeine at night",
        {"lia", "caffeine", "night", "constraint"},
        0.93,
        0.99,
        52
    );
    const MemoryId morning_coffee = store.add(
        MemoryKind::Episode,
        "Lia resumed drinking coffee in the morning",
        {"lia", "coffee", "morning", "episode"},
        0.70,
        1.00,
        86
    );

    store.relate(accepted, tea, "supports", 0.80);
    store.relate(avoid_caffeine, tea, "compatible_in_context", 0.75);
    store.relate(morning_coffee, avoid_caffeine, "qualifies", 0.90);

    const auto results = store.recall({"lia", "night", "drink"}, 90, 3U);

    std::cout << std::fixed << std::setprecision(3);
    for (const ScoreBreakdown& result : results) {
        const Memory& item = store.memory(result.id);
        std::cout
            << "M" << result.id.value << " score=" << result.total
            << " relevance=" << result.relevance
            << " recency=" << result.recency
            << " | " << item.text << '\n';
    }
    std::cout << "relations=" << store.relation_count() << '\n';
}
