#include <algorithm>
#include <cassert>
#include <cstddef>
#include <expected>
#include <format>
#include <iostream>
#include <ranges>
#include <span>
#include <string>
#include <string_view>
#include <utility>
#include <variant>
#include <vector>

namespace memory {

struct SourceId {
    std::size_t value{};
    auto operator<=>(const SourceId&) const = default;
};

struct CandidateId {
    std::size_t value{};
    auto operator<=>(const CandidateId&) const = default;
};

struct EntityId {
    std::size_t value{};
    auto operator<=>(const EntityId&) const = default;
};

struct EpisodeId {
    std::size_t value{};
    auto operator<=>(const EpisodeId&) const = default;
};

struct EvidenceSpan {
    std::size_t begin{};
    std::size_t end{};
};

struct SourceEnvelope {
    SourceId id;
    std::string delivery_key;
    std::string author;
    int event_day{};
    int ingestion_day{};
    std::string payload_hash;
    std::string content;
    std::string authorization_scope;
};

[[nodiscard]] auto make_source(
    SourceId id,
    std::string delivery_key,
    std::string author,
    int event_day,
    int ingestion_day,
    std::string payload_hash,
    std::string content,
    std::string authorization_scope)
    -> std::expected<SourceEnvelope, std::string> {

    if (delivery_key.empty() || payload_hash.empty()) {
        return std::unexpected{"A source requires delivery and payload identities."};
    }
    if (author.empty() || content.empty()) {
        return std::unexpected{"A source requires an author and immutable content."};
    }
    if (event_day > ingestion_day) {
        return std::unexpected{"Event day cannot follow ingestion day in this example."};
    }
    if (authorization_scope.empty()) {
        return std::unexpected{"A source requires an authorization scope."};
    }

    return SourceEnvelope{
        id,
        std::move(delivery_key),
        std::move(author),
        event_day,
        ingestion_day,
        std::move(payload_hash),
        std::move(content),
        std::move(authorization_scope)};
}

struct ScopedConstraint {
    EntityId subject;
    std::string predicate;
    std::string object;
    std::string context;
};

struct OutcomeClaim {
    EntityId subject;
    std::string intervention;
    std::string outcome;
    EpisodeId episode;
};

struct PreferenceHypothesis {
    EntityId subject;
    std::string object;
};

using CandidatePayload =
    std::variant<ScopedConstraint, OutcomeClaim, PreferenceHypothesis>;

enum class Decision {
    commit,
    link,
    quarantine,
    ignore,
    reject
};

[[nodiscard]] constexpr auto decision_name(Decision decision) -> std::string_view {
    switch (decision) {
    case Decision::commit:
        return "COMMIT";
    case Decision::link:
        return "LINK";
    case Decision::quarantine:
        return "QUARANTINE";
    case Decision::ignore:
        return "IGNORE";
    case Decision::reject:
        return "REJECT";
    }
    std::unreachable();
}

struct Candidate {
    CandidateId id;
    SourceId source;
    EvidenceSpan evidence;
    CandidatePayload payload;
    double extraction_confidence{};
    double identity_confidence{};
    double expected_utility{};
    double privacy_risk{};
    bool schema_valid{};
    bool provenance_complete{};
    bool temporal_valid{};
    bool authorized{};
    bool semantic_duplicate{};
};

struct Admission {
    CandidateId candidate;
    Decision decision;
    std::string reason;
};

[[nodiscard]] auto validate_candidate(
    const Candidate& candidate,
    std::string_view source_text)
    -> std::expected<void, std::string> {

    if (candidate.evidence.begin >= candidate.evidence.end ||
        candidate.evidence.end > source_text.size()) {
        return std::unexpected{"Evidence span is outside the immutable source."};
    }
    if (candidate.extraction_confidence < 0.0 ||
        candidate.extraction_confidence > 1.0 ||
        candidate.identity_confidence < 0.0 ||
        candidate.identity_confidence > 1.0) {
        return std::unexpected{"Confidence values must remain in the unit interval."};
    }
    if (candidate.expected_utility < 0.0 || candidate.privacy_risk < 0.0) {
        return std::unexpected{"Utility and risk cannot be negative."};
    }
    return {};
}

[[nodiscard]] auto admit(const Candidate& candidate) -> Admission {
    if (!candidate.schema_valid || !candidate.temporal_valid) {
        return {candidate.id, Decision::reject,
                "Schema or temporal validation failed."};
    }
    if (!candidate.authorized) {
        return {candidate.id, Decision::reject,
                "The source scope does not authorize this durable write."};
    }
    if (candidate.semantic_duplicate) {
        return {candidate.id, Decision::link,
                "Equivalent content already exists, so provenance is linked."};
    }
    if (!candidate.provenance_complete ||
        candidate.identity_confidence < 0.80 ||
        candidate.extraction_confidence < 0.75) {
        return {candidate.id, Decision::quarantine,
                "Evidence, identity, or extraction certainty needs review."};
    }
    if (candidate.privacy_risk > candidate.expected_utility) {
        return {candidate.id, Decision::ignore,
                "Expected memory value does not justify the privacy risk."};
    }
    return {candidate.id, Decision::commit,
            "All gates pass and expected utility exceeds retention risk."};
}

struct IngestionResult {
    bool replay{};
    std::vector<Candidate> candidates;
    std::vector<Admission> admissions;
};

class WritePipeline {
public:
    [[nodiscard]] auto ingest(const SourceEnvelope& source)
        -> std::expected<IngestionResult, std::string> {

        if (const auto found = std::ranges::find(
                deliveries_, source.delivery_key, &DeliveryRecord::key);
            found != deliveries_.end()) {
            if (found->payload_hash != source.payload_hash) {
                return std::unexpected{
                    "A delivery key cannot identify two different payloads."};
            }
            return IngestionResult{true, {}, {}};
        }

        deliveries_.push_back(
            DeliveryRecord{source.delivery_key, source.payload_hash});
        auto candidates = extract_canonical_candidates(source);

        for (const auto& candidate : candidates) {
            if (auto checked = validate_candidate(candidate, source.content);
                !checked) {
                return std::unexpected{checked.error()};
            }
        }

        std::vector<Admission> admissions;
        admissions.reserve(candidates.size());
        std::ranges::transform(
            candidates,
            std::back_inserter(admissions),
            [](const Candidate& candidate) { return admit(candidate); });

        return IngestionResult{
            false, std::move(candidates), std::move(admissions)};
    }

private:
    struct DeliveryRecord {
        std::string key;
        std::string payload_hash;
    };

    // A small contiguous ledger keeps iteration deterministic and avoids the
    // allocation overhead of a hash table in this correctness baseline.
    std::vector<DeliveryRecord> deliveries_;

    [[nodiscard]] static auto find_span(
        std::string_view text,
        std::string_view fragment)
        -> std::expected<EvidenceSpan, std::string> {

        const auto begin = text.find(fragment);
        if (begin == std::string_view::npos) {
            return std::unexpected{
                std::format("Evidence fragment '{}' was not found.", fragment)};
        }
        return EvidenceSpan{begin, begin + fragment.size()};
    }

    [[nodiscard]] static auto extract_canonical_candidates(
        const SourceEnvelope& source) -> std::vector<Candidate> {

        const auto constraint_span =
            find_span(source.content, "evening, so please suggest something without caffeine")
                .value();
        const auto outcome_span =
            find_span(source.content, "Last time the mint tea worked well")
                .value();
        const auto preference_span =
            find_span(source.content, "mint tea")
                .value();

        // The extractor emits typed hypotheses while evidence spans keep every
        // result accountable to exact source text.
        return {
            Candidate{
                CandidateId{1},
                source.id,
                constraint_span,
                ScopedConstraint{
                    EntityId{101},
                    "AVOIDS",
                    "caffeine",
                    "evening request"},
                0.98,
                0.99,
                0.92,
                0.12,
                true,
                true,
                true,
                true,
                false},
            Candidate{
                CandidateId{2},
                source.id,
                outcome_span,
                OutcomeClaim{
                    EntityId{101},
                    "mint tea",
                    "worked well",
                    EpisodeId{44}},
                0.91,
                0.94,
                0.84,
                0.16,
                true,
                true,
                true,
                true,
                false},
            Candidate{
                CandidateId{3},
                source.id,
                preference_span,
                PreferenceHypothesis{
                    EntityId{101},
                    "mint tea"},
                0.61,
                0.94,
                0.58,
                0.10,
                true,
                true,
                true,
                true,
                false}};
    }
};

[[nodiscard]] auto payload_name(const CandidatePayload& payload)
    -> std::string_view {
    return std::visit(
        [](const auto& value) -> std::string_view {
            using T = std::remove_cvref_t<decltype(value)>;
            if constexpr (std::same_as<T, ScopedConstraint>) {
                return "scoped constraint";
            } else if constexpr (std::same_as<T, OutcomeClaim>) {
                return "episode outcome";
            } else {
                return "preference hypothesis";
            }
        },
        payload);
}

void print_result(
    const SourceEnvelope& source,
    std::span<const Candidate> candidates,
    std::span<const Admission> admissions) {

    std::cout << std::format(
        "Source {} by {} at event day {}, ingested day {}\n",
        source.id.value,
        source.author,
        source.event_day,
        source.ingestion_day);

    for (const auto& candidate : candidates) {
        const auto evidence = std::string_view{source.content}.substr(
            candidate.evidence.begin,
            candidate.evidence.end - candidate.evidence.begin);
        const auto admission = std::ranges::find(
            admissions, candidate.id, &Admission::candidate);
        assert(admission != admissions.end());

        std::cout << std::format(
            "Candidate {}: {} | evidence: \"{}\" | {} | {}\n",
            candidate.id.value,
            payload_name(candidate.payload),
            evidence,
            decision_name(admission->decision),
            admission->reason);
    }
}

} // namespace memory

int main() {
    using namespace memory;

    const auto source = make_source(
        SourceId{90},
        "chat:lia:90:delivery:1",
        "Lia",
        90,
        90,
        "sha256:61c9d7",
        "It is evening, so please suggest something without caffeine. "
        "Last time the mint tea worked well.",
        "personalization:beverages");
    assert(source);

    WritePipeline pipeline;
    const auto first = pipeline.ingest(*source);
    assert(first);
    assert(!first->replay);
    assert(first->candidates.size() == 3);

    const auto committed = std::ranges::count(
        first->admissions, Decision::commit, &Admission::decision);
    const auto quarantined = std::ranges::count(
        first->admissions, Decision::quarantine, &Admission::decision);
    assert(committed == 2);
    assert(quarantined == 1);

    print_result(*source, first->candidates, first->admissions);

    // Delivery identity suppresses transport retries while the payload hash
    // prevents one key from silently naming different evidence.
    const auto replay = pipeline.ingest(*source);
    assert(replay);
    assert(replay->replay);
    assert(replay->candidates.empty());
    std::cout << "Replay: LINK to source 90, no new evidence created.\n";

    const auto malformed = make_source(
        SourceId{91},
        "",
        "Lia",
        91,
        91,
        "",
        "",
        "personalization:beverages");
    assert(!malformed);
    std::cout << std::format("Malformed source: REJECT | {}\n", malformed.error());
}
