// RecursiveLM_Photon.h
// Recursive Language Model Integration for Photon Empress Moore
// ================================================================
// Based on: "Recursive Language Models" (Zhang, Kraska, Khattab - MIT CSAIL)
// arXiv Dec 31, 2025
//
// KEY INSIGHT FROM PAPER:
//   "Reasoning and retrieval are now ORTHOGONAL capacities"
//   "Problem tractability is a function of ADDRESSABILITY, not complexity"
//
// WHAT THIS MEANS FOR PHOTON:
//   - Don't cram everything into context window
//   - Write CODE to search, chunk, and recursively analyze
//   - Can handle 10 MILLION+ tokens via recursion
//   - Model calls ITSELF on sub-problems
//
// HOW IT INTEGRATES:
//   - Kage Bunshin = Physical recursive agents
//   - RLM = Logical recursive memory access
//   - NodeGraphMemory = Addressable storage backend
//   - Bracket Protocol = Inter-recursion communication
//
// Build: Reference implementation - requires PHOTON_FULL_BUILD
// Author: Tadden Moore
// STATUS: REFERENCE ONLY - the real RLM consolidation runs in
//         ~/.agi-dtf/local-rlm-worker.js (Node.js + LanceDB)
// ================================================================

#pragma once

// Guard: This file only compiles with the full Photon build system.
#ifdef PHOTON_FULL_BUILD

#include <string>
#include <vector>
#include <functional>
#include <memory>
#include <queue>
#include <mutex>
#include <future>
#include <variant>
#include <optional>
#include <unordered_map>
#include <chrono>
#include <algorithm>
#include <fstream>
#include <cmath>
#include <climits>

// Forward declarations
class NodeGraphMemory;
class EngineBridge;

namespace RLM {

// ================================================================
// CORE RLM CONCEPT: THE RECURSIVE CALL STRUCTURE
// ================================================================

// A chunk of data that can be processed
struct DataChunk {
    std::string content;
    std::vector<float> embedding;  // For similarity search
    size_t start_idx;              // Position in original data
    size_t end_idx;
    float relevance_score;         // How relevant to current query
    std::string source;            // Where this came from

    // Metadata for recursion tracking
    int depth;                     // How deep in recursion tree
    std::string parent_id;         // Which chunk spawned this
    std::string chunk_id;          // Unique ID for this chunk
};

// Result from processing a chunk
struct ChunkResult {
    std::string chunk_id;
    std::string summary;           // Condensed result
    std::vector<float> result_embedding;
    float confidence;
    bool needs_deeper_recursion;   // Should we go deeper?
    std::vector<std::string> extracted_facts;
    std::vector<std::string> child_chunk_ids;  // If we recursed
};

// The recursive query that drives everything
struct RecursiveQuery {
    std::string original_question;
    std::string current_subquery;  // May differ during recursion
    int max_depth;                 // Prevent infinite recursion
    int current_depth;
    size_t max_tokens_per_chunk;   // Context window limit
    float relevance_threshold;     // Min score to include

    // Aggregation strategy
    enum class AggregateMode {
        CONCAT,      // Just concatenate results
        SUMMARIZE,   // LLM summarizes all results
        VOTE,        // Majority vote (for factual queries)
        FILTER,      // Keep only high-confidence results
        HIERARCHICAL // Build tree of summaries
    };
    AggregateMode aggregate_mode = AggregateMode::HIERARCHICAL;
};

// ================================================================
// THE RLM TRAJECTORY (for RL training later!)
// ================================================================

struct RLMAction {
    enum class Type {
        INSPECT,     // Look at data structure
        CHUNK,       // Split data into parts
        SEARCH,      // Query memory system
        CALL_SELF,   // Recursive call
        AGGREGATE,   // Combine results
        ANSWER       // Produce final answer
    };

    Type type;
    std::string target;           // What to act on
    std::string parameters;       // JSON params
    std::string result;           // What happened
    float reward;                 // For RL training
    std::chrono::microseconds latency;
};

struct RLMTrajectory {
    std::string query_id;
    std::string original_query;
    std::vector<RLMAction> actions;
    std::string final_answer;
    float total_reward;
    bool success;

    // For distillation training
    std::string optimal_path;     // Best action sequence found
};

// ================================================================
// THE CHUNKING STRATEGIES
// ================================================================

class ChunkingStrategy {
public:
    virtual ~ChunkingStrategy() = default;
    virtual std::vector<DataChunk> chunk(const std::string& data,
                                         size_t max_tokens,
                                         const RecursiveQuery& query) = 0;
};

// Simple fixed-size chunking
class FixedSizeChunker : public ChunkingStrategy {
public:
    std::vector<DataChunk> chunk(const std::string& data,
                                 size_t max_tokens,
                                 const RecursiveQuery& query) override {
        std::vector<DataChunk> chunks;

        // Approximate tokens (4 chars = 1 token roughly)
        size_t chars_per_chunk = max_tokens * 4;
        size_t overlap = chars_per_chunk / 10;  // 10% overlap

        size_t pos = 0;
        int chunk_num = 0;

        while (pos < data.size()) {
            size_t end = std::min(pos + chars_per_chunk, data.size());

            DataChunk c;
            c.content = data.substr(pos, end - pos);
            c.start_idx = pos;
            c.end_idx = end;
            c.depth = query.current_depth;
            c.chunk_id = "chunk_" + std::to_string(chunk_num++);
            c.relevance_score = 1.0f;  // Will be updated by search

            chunks.push_back(c);

            pos = end - overlap;
            if (pos >= data.size() - overlap) break;
        }

        return chunks;
    }
};

// Semantic chunking (split by meaning)
class SemanticChunker : public ChunkingStrategy {
    NodeGraphMemory* ngm_;

public:
    SemanticChunker(NodeGraphMemory* ngm) : ngm_(ngm) {}

    std::vector<DataChunk> chunk(const std::string& data,
                                 size_t max_tokens,
                                 const RecursiveQuery& query) override {
        std::vector<DataChunk> chunks;

        // Split by sentences/paragraphs first
        std::vector<std::string> sentences = splitSentences(data);

        // Group sentences by semantic similarity
        std::vector<std::vector<std::string>> groups;
        std::vector<std::string> current_group;
        size_t current_tokens = 0;

        for (const auto& sent : sentences) {
            size_t sent_tokens = sent.size() / 4;

            if (current_tokens + sent_tokens > max_tokens && !current_group.empty()) {
                groups.push_back(current_group);
                current_group.clear();
                current_tokens = 0;
            }

            current_group.push_back(sent);
            current_tokens += sent_tokens;
        }

        if (!current_group.empty()) {
            groups.push_back(current_group);
        }

        // Convert groups to chunks
        int chunk_num = 0;
        for (const auto& group : groups) {
            DataChunk c;
            for (const auto& s : group) {
                c.content += s + " ";
            }
            c.depth = query.current_depth;
            c.chunk_id = "semantic_" + std::to_string(chunk_num++);
            c.relevance_score = 1.0f;

            chunks.push_back(c);
        }

        return chunks;
    }

private:
    std::vector<std::string> splitSentences(const std::string& text) {
        std::vector<std::string> sentences;
        std::string current;

        for (char c : text) {
            current += c;
            if (c == '.' || c == '!' || c == '?' || c == '\n') {
                if (!current.empty()) {
                    sentences.push_back(current);
                    current.clear();
                }
            }
        }

        if (!current.empty()) {
            sentences.push_back(current);
        }

        return sentences;
    }
};

// Query-aware chunking (chunks relevant to the question)
class QueryAwareChunker : public ChunkingStrategy {
    NodeGraphMemory* ngm_;
    EngineBridge* engine_;  // For embeddings

public:
    QueryAwareChunker(NodeGraphMemory* ngm, EngineBridge* eng)
        : ngm_(ngm), engine_(eng) {}

    std::vector<DataChunk> chunk(const std::string& data,
                                 size_t max_tokens,
                                 const RecursiveQuery& query) override {
        // First do semantic chunking
        SemanticChunker semantic(ngm_);
        auto base_chunks = semantic.chunk(data, max_tokens, query);

        // Then score by relevance to query
        std::vector<float> query_embedding;
        // engine_->embed(query.current_subquery, query_embedding);

        for (auto& chunk : base_chunks) {
            std::vector<float> chunk_embedding;
            // engine_->embed(chunk.content, chunk_embedding);

            // Cosine similarity
            chunk.relevance_score = cosineSimilarity(query_embedding, chunk_embedding);
        }

        // Sort by relevance
        std::sort(base_chunks.begin(), base_chunks.end(),
            [](const DataChunk& a, const DataChunk& b) {
                return a.relevance_score > b.relevance_score;
            });

        // Filter by threshold
        std::vector<DataChunk> relevant;
        for (const auto& c : base_chunks) {
            if (c.relevance_score >= query.relevance_threshold) {
                relevant.push_back(c);
            }
        }

        return relevant;
    }

private:
    float cosineSimilarity(const std::vector<float>& a, const std::vector<float>& b) {
        if (a.size() != b.size() || a.empty()) return 0.0f;

        float dot = 0, norm_a = 0, norm_b = 0;
        for (size_t i = 0; i < a.size(); ++i) {
            dot += a[i] * b[i];
            norm_a += a[i] * a[i];
            norm_b += b[i] * b[i];
        }

        if (norm_a == 0 || norm_b == 0) return 0.0f;
        return dot / (std::sqrt(norm_a) * std::sqrt(norm_b));
    }
};

// ================================================================
// THE AGGREGATION STRATEGIES
// ================================================================

class AggregationStrategy {
public:
    virtual ~AggregationStrategy() = default;
    virtual ChunkResult aggregate(const std::vector<ChunkResult>& results,
                                  const RecursiveQuery& query) = 0;
};

// Simple concatenation
class ConcatAggregator : public AggregationStrategy {
public:
    ChunkResult aggregate(const std::vector<ChunkResult>& results,
                         const RecursiveQuery& query) override {
        ChunkResult final;
        final.chunk_id = "aggregated_concat";

        for (const auto& r : results) {
            final.summary += r.summary + "\n-\n";
            for (const auto& fact : r.extracted_facts) {
                final.extracted_facts.push_back(fact);
            }
        }

        // Average confidence
        float total_conf = 0;
        for (const auto& r : results) total_conf += r.confidence;
        final.confidence = results.empty() ? 0 : total_conf / results.size();

        return final;
    }
};

// Hierarchical summarization (LLM summarizes summaries)
class HierarchicalAggregator : public AggregationStrategy {
    EngineBridge* engine_;

public:
    HierarchicalAggregator(EngineBridge* eng) : engine_(eng) {}

    ChunkResult aggregate(const std::vector<ChunkResult>& results,
                         const RecursiveQuery& query) override {
        ChunkResult final;
        final.chunk_id = "aggregated_hierarchical";

        if (results.empty()) {
            final.summary = "No relevant information found.";
            final.confidence = 0;
            return final;
        }

        // Build prompt for LLM to summarize
        std::string prompt = "Given the following sub-results for the query: \""
                           + query.original_question + "\"\n\n";

        for (size_t i = 0; i < results.size(); ++i) {
            prompt += "Result " + std::to_string(i+1) + ":\n";
            prompt += results[i].summary + "\n\n";
        }

        prompt += "Please synthesize these results into a coherent answer:";

        // Call LLM to synthesize
        // final.summary = engine_->complete(prompt);
        final.summary = "[Synthesized from " + std::to_string(results.size()) + " sub-results]";

        // Collect all facts
        for (const auto& r : results) {
            for (const auto& fact : r.extracted_facts) {
                final.extracted_facts.push_back(fact);
            }
        }

        // Weighted confidence
        float total_conf = 0, total_weight = 0;
        for (const auto& r : results) {
            total_conf += r.confidence * r.confidence;  // Higher confidence = more weight
            total_weight += r.confidence;
        }
        final.confidence = total_weight > 0 ? total_conf / total_weight : 0;

        return final;
    }
};

// ================================================================
// THE MAIN RLM ENGINE
// ================================================================

class RecursiveLMEngine {
public:
    RecursiveLMEngine(NodeGraphMemory* ngm, EngineBridge* engine)
        : ngm_(ngm), engine_(engine) {

        // Initialize chunking strategies
        chunkers_["fixed"] = std::make_unique<FixedSizeChunker>();
        chunkers_["semantic"] = std::make_unique<SemanticChunker>(ngm);
        chunkers_["query_aware"] = std::make_unique<QueryAwareChunker>(ngm, engine);

        // Initialize aggregation strategies
        aggregators_["concat"] = std::make_unique<ConcatAggregator>();
        aggregators_["hierarchical"] = std::make_unique<HierarchicalAggregator>(engine);
    }

    // ================================================================
    // MAIN ENTRY POINT: RECURSIVE QUERY
    // ================================================================

    ChunkResult query(const std::string& question,
                     const std::string& data_source,
                     int max_depth = 5,
                     size_t context_limit = 4096) {

        // Create the recursive query
        RecursiveQuery q;
        q.original_question = question;
        q.current_subquery = question;
        q.max_depth = max_depth;
        q.current_depth = 0;
        q.max_tokens_per_chunk = context_limit;
        q.relevance_threshold = 0.3f;
        q.aggregate_mode = RecursiveQuery::AggregateMode::HIERARCHICAL;

        // Start trajectory for potential RL training
        current_trajectory_.query_id = generateId();
        current_trajectory_.original_query = question;
        current_trajectory_.actions.clear();

        // Begin recursion!
        auto result = processRecursively(data_source, q);

        // Finalize trajectory
        current_trajectory_.final_answer = result.summary;
        current_trajectory_.success = result.confidence > 0.5f;
        trajectories_.push_back(current_trajectory_);

        return result;
    }

    // ================================================================
    // INTEGRATION WITH KAGE BUNSHIN (Physical Agents)
    // ================================================================

    // Spawn a clone to handle a sub-query in parallel
    std::future<ChunkResult> spawnCloneQuery(const DataChunk& chunk,
                                             const RecursiveQuery& query) {
        return std::async(std::launch::async, [this, chunk, query]() {
            // Each clone processes its chunk independently
            // This is where Kage Bunshin meets RLM!

            RecursiveQuery sub_query = query;
            sub_query.current_depth++;
            sub_query.current_subquery = "Based on this context, answer: "
                                        + query.original_question
                                        + "\n\nContext:\n" + chunk.content;

            // Process the chunk
            return processChunk(chunk, sub_query);
        });
    }

    // Parallel recursive processing using Kage Bunshin pattern
    std::vector<ChunkResult> parallelProcess(const std::vector<DataChunk>& chunks,
                                             const RecursiveQuery& query) {
        std::vector<std::future<ChunkResult>> futures;

        // Spawn clones for each chunk
        for (const auto& chunk : chunks) {
            futures.push_back(spawnCloneQuery(chunk, query));
        }

        // Gather results (like Kage Bunshin memory merge!)
        std::vector<ChunkResult> results;
        for (auto& fut : futures) {
            results.push_back(fut.get());
        }

        return results;
    }

    // ================================================================
    // INTEGRATION WITH BRACKET PROTOCOL
    // ================================================================

    // Generate bracket command for recursive call
    std::string generateRecursiveCommand(const DataChunk& chunk,
                                         const RecursiveQuery& query) {
        // [C: RLM_RECURSE depth=2 chunk_id=semantic_3 query="What happened?"]
        return "[C: RLM_RECURSE depth=" + std::to_string(query.current_depth)
             + " chunk_id=" + chunk.chunk_id
             + " query=\"" + query.current_subquery + "\"]";
    }

    // Parse incoming RLM command
    std::optional<std::pair<DataChunk, RecursiveQuery>> parseRecursiveCommand(
            const std::string& command) {
        // Parse [C: RLM_RECURSE ...] commands
        if (command.find("RLM_RECURSE") == std::string::npos) {
            return std::nullopt;
        }

        // Extract parameters...
        // (Full parsing implementation here)

        return std::nullopt;  // Placeholder
    }

    // ================================================================
    // MEMORY INTEGRATION (NodeGraphMemory)
    // ================================================================

    // Query NodeGraphMemory as a data source
    std::string loadFromMemory(const std::string& query_text,
                               int max_results = 100) {
        // Use VSS to find relevant nodes
        // ngm_->wrapWord() for semantic search

        std::string combined_context;

        // In full implementation:
        // 1. Embed the query
        // 2. VSS search in NodeGraphMemory
        // 3. Return top-k results as string

        return combined_context;
    }

    // Store RLM results back to memory
    void storeResult(const ChunkResult& result, float importance) {
        // Store in NodeGraphMemory for future queries
        // This creates a "cache" of processed knowledge

        for (const auto& fact : result.extracted_facts) {
            // ngm_->addNode(fact, result.result_embedding, importance);
        }
    }

    // ================================================================
    // RL TRAINING DATA EXPORT
    // ================================================================

    std::vector<RLMTrajectory> getTrajectories() const {
        return trajectories_;
    }

    void exportTrajectoriesForTraining(const std::string& path) {
        // Export trajectories in format suitable for TRL/OpenEnv
        // This enables self-improvement!

        std::ofstream file(path);
        for (const auto& traj : trajectories_) {
            file << "QUERY: " << traj.original_query << "\n";
            file << "ACTIONS:\n";
            for (const auto& action : traj.actions) {
                file << "  - " << static_cast<int>(action.type)
                     << " -> " << action.result
                     << " (reward: " << action.reward << ")\n";
            }
            file << "ANSWER: " << traj.final_answer << "\n";
            file << "SUCCESS: " << (traj.success ? "YES" : "NO") << "\n";
            file << "-\n";
        }
    }

private:
    NodeGraphMemory* ngm_;
    EngineBridge* engine_;

    std::unordered_map<std::string, std::unique_ptr<ChunkingStrategy>> chunkers_;
    std::unordered_map<std::string, std::unique_ptr<AggregationStrategy>> aggregators_;

    RLMTrajectory current_trajectory_;
    std::vector<RLMTrajectory> trajectories_;

    std::mutex recursion_mutex_;

    // ================================================================
    // CORE RECURSIVE PROCESSING
    // ================================================================

    ChunkResult processRecursively(const std::string& data,
                                   const RecursiveQuery& query) {
        // Log action
        logAction(RLMAction::Type::INSPECT, data.substr(0, 100), "", "", 0);

        // Check recursion depth limit
        if (query.current_depth >= query.max_depth) {
            // Base case: just process directly
            DataChunk single;
            single.content = data;
            single.depth = query.current_depth;
            single.chunk_id = "terminal";
            return processChunk(single, query);
        }

        // Check if data fits in context window
        size_t estimated_tokens = data.size() / 4;

        if (estimated_tokens <= query.max_tokens_per_chunk) {
            // Fits! Process directly
            DataChunk single;
            single.content = data;
            single.depth = query.current_depth;
            single.chunk_id = "direct";
            return processChunk(single, query);
        }

        // RECURSIVE CASE: Need to chunk and process

        // 1. CHUNK the data
        auto chunks = chunkers_["query_aware"]->chunk(
            data, query.max_tokens_per_chunk, query);

        logAction(RLMAction::Type::CHUNK, "",
                 std::to_string(chunks.size()) + " chunks", "", 0);

        // 2. RECURSIVE CALL on each chunk (parallel with Kage Bunshin!)
        std::vector<ChunkResult> sub_results = parallelProcess(chunks, query);

        for (const auto& r : sub_results) {
            logAction(RLMAction::Type::CALL_SELF, r.chunk_id, r.summary, "", 0);
        }

        // 3. AGGREGATE results
        ChunkResult aggregated;
        switch (query.aggregate_mode) {
            case RecursiveQuery::AggregateMode::HIERARCHICAL:
                aggregated = aggregators_["hierarchical"]->aggregate(sub_results, query);
                break;
            default:
                aggregated = aggregators_["concat"]->aggregate(sub_results, query);
                break;
        }

        logAction(RLMAction::Type::AGGREGATE, "", aggregated.summary, "",
                 aggregated.confidence);

        return aggregated;
    }

    ChunkResult processChunk(const DataChunk& chunk, const RecursiveQuery& query) {
        ChunkResult result;
        result.chunk_id = chunk.chunk_id;

        // Build prompt for LLM
        std::string prompt = "Query: " + query.current_subquery + "\n\n";
        prompt += "Context:\n" + chunk.content + "\n\n";
        prompt += "Based on this context, provide:\n";
        prompt += "1. A direct answer to the query\n";
        prompt += "2. Key facts extracted (one per line, prefixed with FACT:)\n";
        prompt += "3. Confidence level (0-1)\n";

        // In full implementation: Call LLM
        // std::string response = engine_->complete(prompt);

        // Parse response...
        result.summary = "[Processed chunk: " + chunk.chunk_id + "]";
        result.confidence = chunk.relevance_score;
        result.needs_deeper_recursion = false;

        return result;
    }

    void logAction(RLMAction::Type type, const std::string& target,
                  const std::string& result, const std::string& params,
                  float reward) {
        RLMAction action;
        action.type = type;
        action.target = target;
        action.result = result;
        action.parameters = params;
        action.reward = reward;
        action.latency = std::chrono::microseconds(0);  // TODO: measure

        current_trajectory_.actions.push_back(action);
        current_trajectory_.total_reward += reward;
    }

    std::string generateId() {
        static int counter = 0;
        return "rlm_" + std::to_string(++counter) + "_"
             + std::to_string(std::chrono::system_clock::now().time_since_epoch().count());
    }
};


// ================================================================
// TIME DILATION SCOPE (TDS) / TIMEVINE
// ================================================================
// Extension to MIT CSAIL Recursive LM
// Concept: Run multiple async forward passes over memory at
//          different temporal resolutions simultaneously.
//          Collate results: RELEVANT / OUTLIER / NOISE.
//          Noise gets flagged and excluded from related passes -
//          like Photon Empress filtering intrusive thoughts.
//
// TimeVine  = The temporal memory tree (branching time-indexed nodes)
// TDS       = The zoom lens - focuses/widens time windows per pass
//
// Flow:
//   1. Define N TimeWindows (IMMEDIATE -> DEEP_ARCHIVE or custom range)
//   2. Async forward-pass over each window in parallel (Kage Bunshin!)
//   3. Collate: cross-pass scoring per chunk
//      RELEVANT  (score >= relevance_threshold)  -> keep, include in synthesis
//      OUTLIER   (outlier_threshold <= score < relevance_threshold) -> mark, surface separately
//      NOISE     (score < noise_floor)           -> exclude from related passes
// ================================================================

// ----------------------------------------------------------------------------
// TEMPORAL ENUMERATIONS & BASIC STRUCTURES
// ----------------------------------------------------------------------------

enum class TimeScale {
    IMMEDIATE   = 0,  // Seconds / current turn (working memory)
    SHORT_TERM  = 1,  // Minutes to hours (session memory)
    MEDIUM_TERM = 2,  // Hours to days (recent engrams)
    LONG_TERM   = 3,  // Weeks to months (consolidated LoRA)
    DEEP_ARCHIVE= 4,  // Months to all-time (LanceDB 28k engrams)
    CUSTOM      = 5   // User-defined range
};

inline std::string timeScaleLabel(TimeScale s) {
    switch (s) {
        case TimeScale::IMMEDIATE:    return "IMMEDIATE";
        case TimeScale::SHORT_TERM:   return "SHORT_TERM";
        case TimeScale::MEDIUM_TERM:  return "MEDIUM_TERM";
        case TimeScale::LONG_TERM:    return "LONG_TERM";
        case TimeScale::DEEP_ARCHIVE: return "DEEP_ARCHIVE";
        default:                      return "CUSTOM";
    }
}

struct TimeWindow {
    TimeScale   scale           = TimeScale::MEDIUM_TERM;
    int64_t     start_unix_ms   = 0;   // 0 = beginning of time
    int64_t     end_unix_ms     = 0;   // 0 = now
    float       zoom_factor     = 1.0f; // >1 = fine-grained, <1 = coarse sweep
    float       attention_weight= 1.0f; // Relative weight in final collation
    std::string label;                  // Human-readable tag

    // Convenience: "now minus N seconds" window
    static TimeWindow last(int64_t seconds, float zoom = 1.0f) {
        TimeWindow w;
        auto now = std::chrono::duration_cast<std::chrono::milliseconds>(
            std::chrono::system_clock::now().time_since_epoch()).count();
        w.end_unix_ms   = now;
        w.start_unix_ms = now - seconds * 1000LL;
        w.scale         = TimeScale::CUSTOM;
        w.zoom_factor   = zoom;
        w.label         = "last_" + std::to_string(seconds) + "s";
        return w;
    }

    // Convenience: predefined scales with sensible defaults
    static TimeWindow forScale(TimeScale s) {
        auto now = std::chrono::duration_cast<std::chrono::milliseconds>(
            std::chrono::system_clock::now().time_since_epoch()).count();
        TimeWindow w;
        w.scale       = s;
        w.end_unix_ms = now;
        switch (s) {
            case TimeScale::IMMEDIATE:    w.start_unix_ms = now - 60000LL;           w.zoom_factor = 4.0f; break; // 1 min
            case TimeScale::SHORT_TERM:   w.start_unix_ms = now - 3600000LL;         w.zoom_factor = 2.0f; break; // 1 hr
            case TimeScale::MEDIUM_TERM:  w.start_unix_ms = now - 86400000LL * 7;    w.zoom_factor = 1.0f; break; // 7 days
            case TimeScale::LONG_TERM:    w.start_unix_ms = now - 86400000LL * 90;   w.zoom_factor = 0.5f; break; // 90 days
            case TimeScale::DEEP_ARCHIVE: w.start_unix_ms = 0;                        w.zoom_factor = 0.2f; break; // all time
            default:                      w.start_unix_ms = 0;                        break;
        }
        w.label = timeScaleLabel(s);
        return w;
    }

    bool contains(int64_t ts_ms) const {
        if (end_unix_ms == 0) return ts_ms >= start_unix_ms;
        return ts_ms >= start_unix_ms && ts_ms <= end_unix_ms;
    }
};

// Configuration for a full TDS scan
struct TDSConfig {
    std::vector<TimeWindow> windows;          // Time windows to scan (one pass each)
    bool   async_passes           = true;     // Parallel passes (Kage Bunshin)
    float  relevance_threshold    = 0.65f;    // Cross-pass score -> RELEVANT
    float  outlier_threshold      = 0.35f;    // Cross-pass score -> OUTLIER (else NOISE)
    float  noise_floor            = 0.35f;    // Below this = NOISE, excluded from related
    size_t max_concurrent_passes  = 5;        // Cap on simultaneous async passes
    bool   exclude_noise_globally = false;    // If true, noise excluded from ALL future passes
    size_t top_k_per_window       = 20;       // Max chunks to surface per pass

    // Preset: all 5 standard scales
    static TDSConfig allScales() {
        TDSConfig cfg;
        for (auto s : { TimeScale::IMMEDIATE, TimeScale::SHORT_TERM,
                        TimeScale::MEDIUM_TERM, TimeScale::LONG_TERM,
                        TimeScale::DEEP_ARCHIVE }) {
            cfg.windows.push_back(TimeWindow::forScale(s));
        }
        return cfg;
    }

    // Preset: zoom deep on a specific scale
    static TDSConfig zoomOn(TimeScale s, float zoom_factor = 3.0f) {
        TDSConfig cfg;
        TimeWindow w = TimeWindow::forScale(s);
        w.zoom_factor = zoom_factor;
        cfg.windows.push_back(w);
        return cfg;
    }

    // Preset: custom time range
    static TDSConfig customRange(int64_t start_ms, int64_t end_ms) {
        TDSConfig cfg;
        TimeWindow w;
        w.scale         = TimeScale::CUSTOM;
        w.start_unix_ms = start_ms;
        w.end_unix_ms   = end_ms;
        w.label         = "custom_range";
        cfg.windows.push_back(w);
        return cfg;
    }
};

// Result from a single temporal pass
struct TemporalPassResult {
    TimeWindow              window;
    std::vector<ChunkResult> chunks;
    std::vector<DataChunk>  raw_chunks;  // Original chunks for cross-pass scoring
    float                   pass_confidence = 0.0f;
    std::chrono::microseconds latency{0};
    bool                    completed = false;
    std::string             error_msg;
};

// Collation classification per chunk
enum class CollationStatus {
    PENDING  = 0,
    RELEVANT = 1,   // Consistent across passes -> keep + include in synthesis
    OUTLIER  = 2,   // Appears in some passes but inconsistent -> mark, surface separately
    NOISE    = 3    // Rare, low-coherence -> exclude from related passes
};

inline std::string collationLabel(CollationStatus s) {
    switch (s) {
        case CollationStatus::RELEVANT: return "RELEVANT";
        case CollationStatus::OUTLIER:  return "OUTLIER";
        case CollationStatus::NOISE:    return "NOISE";
        default:                        return "PENDING";
    }
}

// A chunk after multi-pass collation
struct CollatedChunk {
    DataChunk         chunk;
    CollationStatus   status            = CollationStatus::PENDING;
    float             cross_pass_score  = 0.0f;  // 0-1: consistency across passes
    float             peak_relevance    = 0.0f;  // Highest score in any single pass
    std::vector<TimeScale> found_in;             // Which passes surfaced this chunk
    int               pass_count        = 0;     // How many passes found it
    std::string       exclusion_reason;          // Why flagged as noise (if applicable)
    bool              excluded_from_related = false; // TDS exclusion gate
};

// The full output of a TDS scan
struct TDSScanResult {
    std::string                  query;
    std::vector<TemporalPassResult> pass_results;     // Per-window raw results
    std::vector<CollatedChunk>   collated;            // Classified chunks
    ChunkResult                  synthesized;         // Aggregated final answer
    int                          relevant_count  = 0;
    int                          outlier_count   = 0;
    int                          noise_count     = 0;
    std::chrono::microseconds    total_latency{0};
};

// ----------------------------------------------------------------------------
// TIMEVINE - Temporal Memory Tree
// ----------------------------------------------------------------------------
// Mirrors the "fractal tree" concept: each node covers a time range
// and can be zoomed into finer sub-nodes.  TDS uses TimeVine to
// determine which memory segments to pass during each temporal scan.
// ----------------------------------------------------------------------------

struct TimeVineNode {
    std::string                id;
    TimeWindow                 window;
    std::vector<DataChunk>     chunks;
    std::vector<std::string>   child_ids;   // Finer-grained children
    std::string                parent_id;
    float                      density     = 0.0f;  // Engrams / ms
    int                        depth       = 0;
    bool                       is_leaf     = false;
};

class TimeVine {
public:
    // Build a TimeVine from a flat list of chunks with timestamps
    // (chunk.source should encode unix_ms as prefix "ts:<ms>|...")
    void build(const std::vector<DataChunk>& all_chunks, int max_depth = 4) {
        nodes_.clear();
        root_id_.clear();

        if (all_chunks.empty()) return;

        // Find global time range
        int64_t global_start = INT64_MAX, global_end = INT64_MIN;
        for (const auto& c : all_chunks) {
            int64_t ts = extractTimestamp(c);
            if (ts > 0) {
                global_start = std::min(global_start, ts);
                global_end   = std::max(global_end,   ts);
            }
        }

        if (global_start == INT64_MAX) {
            // No timestamps - put all in a single root node
            TimeWindow w;
            w.label = "root_untimed";
            buildLeaf("root", w, all_chunks, 0);
            root_id_ = "root";
            return;
        }

        // Build root covering all time
        TimeWindow root_window;
        root_window.start_unix_ms = global_start;
        root_window.end_unix_ms   = global_end;
        root_window.scale         = TimeScale::DEEP_ARCHIVE;
        root_window.label         = "root";

        root_id_ = buildNode("root", root_window, all_chunks, 0, max_depth);
    }

    // Get all chunks in a given time window
    std::vector<DataChunk> chunksInWindow(const TimeWindow& window) const {
        std::vector<DataChunk> result;
        collectChunks(root_id_, window, result);
        return result;
    }

    // Get the vine node for a window (nearest match)
    const TimeVineNode* nodeForWindow(const TimeWindow& w) const {
        return findNode(root_id_, w);
    }

    // Density map: how memory-dense is each time segment
    std::vector<std::pair<TimeWindow, float>> densityMap() const {
        std::vector<std::pair<TimeWindow, float>> out;
        for (const auto& [id, node] : nodes_) {
            if (node.is_leaf) {
                out.push_back({node.window, node.density});
            }
        }
        return out;
    }

    size_t nodeCount() const { return nodes_.size(); }

private:
    std::unordered_map<std::string, TimeVineNode> nodes_;
    std::string root_id_;

    int64_t extractTimestamp(const DataChunk& c) const {
        // Expect source = "ts:<unix_ms>|<rest>" or chunk_id prefix
        const std::string prefix = "ts:";
        if (c.source.substr(0, 3) == prefix) {
            try { return std::stoll(c.source.substr(3, c.source.find('|') - 3)); }
            catch (...) {}
        }
        // Fallback: chunk_id might encode ts
        return 0;
    }

    std::string buildNode(const std::string& id,
                          const TimeWindow& window,
                          const std::vector<DataChunk>& chunks,
                          int depth, int max_depth) {
        TimeVineNode node;
        node.id    = id;
        node.window= window;
        node.depth = depth;

        // Filter chunks that fall in this window
        for (const auto& c : chunks) {
            int64_t ts = extractTimestamp(c);
            if (ts == 0 || window.contains(ts)) {
                node.chunks.push_back(c);
            }
        }

        int64_t span_ms = window.end_unix_ms - window.start_unix_ms;
        node.density = span_ms > 0 ? (float)node.chunks.size() / (float)span_ms * 1000.0f : 0.0f;

        if (depth >= max_depth || node.chunks.size() <= 4 || span_ms <= 1000) {
            node.is_leaf = true;
        } else {
            // Split into 2 halves
            int64_t mid = window.start_unix_ms + span_ms / 2;

            TimeWindow left_w  = window; left_w.end_unix_ms   = mid; left_w.label = id + "_L";
            TimeWindow right_w = window; right_w.start_unix_ms= mid; right_w.label= id + "_R";

            std::string left_id  = id + "_L";
            std::string right_id = id + "_R";

            buildNode(left_id,  left_w,  node.chunks, depth + 1, max_depth);
            buildNode(right_id, right_w, node.chunks, depth + 1, max_depth);

            node.child_ids = {left_id, right_id};
        }

        nodes_[id] = std::move(node);
        return id;
    }

    void buildLeaf(const std::string& id, const TimeWindow& w,
                   const std::vector<DataChunk>& chunks, int depth) {
        TimeVineNode node;
        node.id = id; node.window = w; node.chunks = chunks;
        node.depth = depth; node.is_leaf = true;
        node.density = 0.0f;
        nodes_[id] = std::move(node);
    }

    void collectChunks(const std::string& id, const TimeWindow& window,
                       std::vector<DataChunk>& out) const {
        auto it = nodes_.find(id);
        if (it == nodes_.end()) return;
        const auto& node = it->second;

        // Check window overlap
        bool overlaps = (node.window.start_unix_ms <= window.end_unix_ms || window.end_unix_ms == 0)
                     && (node.window.end_unix_ms   >= window.start_unix_ms || node.window.end_unix_ms == 0);
        if (!overlaps) return;

        if (node.is_leaf) {
            for (const auto& c : node.chunks) {
                out.push_back(c);
            }
        } else {
            for (const auto& child_id : node.child_ids) {
                collectChunks(child_id, window, out);
            }
        }
    }

    const TimeVineNode* findNode(const std::string& id, const TimeWindow& w) const {
        auto it = nodes_.find(id);
        if (it == nodes_.end()) return nullptr;
        const auto& node = it->second;

        for (const auto& child_id : node.child_ids) {
            auto child_it = nodes_.find(child_id);
            if (child_it == nodes_.end()) continue;
            const auto& child = child_it->second;
            if (child.window.contains(w.start_unix_ms) &&
                child.window.contains(w.end_unix_ms)) {
                return findNode(child_id, w);
            }
        }
        return &node;
    }
};

// ----------------------------------------------------------------------------
// TIME DILATION SCOPE - Main Engine
// ----------------------------------------------------------------------------

class TimeDilationScope {
public:
    // Loader function type: given a TimeWindow + query, returns relevant chunks
    // In Photon: this calls LanceDB with timestamp range filter + VSS
    using ChunkLoader = std::function<std::vector<DataChunk>(
        const TimeWindow& window, const std::string& query)>;

    explicit TimeDilationScope(TDSConfig cfg = TDSConfig::allScales())
        : config_(std::move(cfg)) {}

    // ------------------------------------------------------------------------
    // PRIMARY ENTRY: async multi-pass temporal scan
    // ------------------------------------------------------------------------
    TDSScanResult scan(const std::string& query, ChunkLoader loader) {
        auto scan_start = std::chrono::high_resolution_clock::now();

        TDSScanResult result;
        result.query = query;

        if (config_.async_passes) {
            result.pass_results = runPassesAsync(query, loader);
        } else {
            result.pass_results = runPassesSync(query, loader);
        }

        // Collation
        result.collated = collate(result.pass_results, query);

        // Count statuses
        for (const auto& c : result.collated) {
            switch (c.status) {
                case CollationStatus::RELEVANT: result.relevant_count++; break;
                case CollationStatus::OUTLIER:  result.outlier_count++;  break;
                case CollationStatus::NOISE:    result.noise_count++;    break;
                default: break;
            }
        }

        // Synthesize final answer from RELEVANT chunks only
        result.synthesized = synthesize(result.collated, query);

        auto scan_end = std::chrono::high_resolution_clock::now();
        result.total_latency = std::chrono::duration_cast<std::chrono::microseconds>(
            scan_end - scan_start);

        return result;
    }

    // Convenience: scan using an already-built TimeVine
    TDSScanResult scanVine(const std::string& query,
                           TimeVine& vine,
                           NodeGraphMemory* ngm = nullptr) {
        // Build loader from vine
        ChunkLoader loader = [&vine](const TimeWindow& window,
                                     const std::string& /*q*/) {
            return vine.chunksInWindow(window);
        };
        return scan(query, loader);
    }

    // Get formatted report of collation results
    std::string formatReport(const TDSScanResult& result) const {
        std::string report;
        report += "=== TDS SCAN REPORT ===\n";
        report += "Query: " + result.query + "\n";
        report += "Passes: " + std::to_string(result.pass_results.size()) + "\n";
        report += "Relevant: " + std::to_string(result.relevant_count) + "\n";
        report += "Outliers: " + std::to_string(result.outlier_count) + "\n";
        report += "Noise:    " + std::to_string(result.noise_count) + "\n";
        report += "Latency:  " + std::to_string(result.total_latency.count()) + " us\n\n";

        report += "- RELEVANT CHUNKS -\n";
        for (const auto& c : result.collated) {
            if (c.status == CollationStatus::RELEVANT) {
                report += "[" + collationLabel(c.status) + " score="
                        + std::to_string(c.cross_pass_score).substr(0,4)
                        + " passes=" + std::to_string(c.pass_count) + "] "
                        + c.chunk.content.substr(0, 80) + "...\n";
            }
        }

        report += "\n- OUTLIERS -\n";
        for (const auto& c : result.collated) {
            if (c.status == CollationStatus::OUTLIER) {
                report += "[OUTLIER score=" + std::to_string(c.cross_pass_score).substr(0,4)
                        + "] " + c.chunk.content.substr(0, 60) + "...\n";
            }
        }

        report += "\n- NOISE (excluded from related passes) -\n";
        for (const auto& c : result.collated) {
            if (c.status == CollationStatus::NOISE) {
                report += "[NOISE: " + c.exclusion_reason + "] "
                        + c.chunk.content.substr(0, 40) + "...\n";
            }
        }

        return report;
    }

    // Access config
    TDSConfig& config() { return config_; }
    const TDSConfig& config() const { return config_; }

private:
    TDSConfig config_;

    // ------------------------------------------------------------------------
    // ASYNC PASSES (Kage Bunshin - parallel temporal clones)
    // ------------------------------------------------------------------------
    std::vector<TemporalPassResult> runPassesAsync(
            const std::string& query, ChunkLoader loader) {

        std::vector<std::future<TemporalPassResult>> futures;

        // Semaphore-style: batch by max_concurrent_passes
        size_t i = 0;
        while (i < config_.windows.size()) {
            size_t batch_end = std::min(i + config_.max_concurrent_passes,
                                        config_.windows.size());
            for (size_t j = i; j < batch_end; ++j) {
                const TimeWindow& w = config_.windows[j];
                futures.push_back(std::async(std::launch::async,
                    [this, &w, &query, &loader]() {
                        return runSinglePass(w, query, loader);
                    }));
            }
            i = batch_end;
        }

        std::vector<TemporalPassResult> results;
        for (auto& f : futures) {
            results.push_back(f.get());
        }
        return results;
    }

    std::vector<TemporalPassResult> runPassesSync(
            const std::string& query, ChunkLoader loader) {
        std::vector<TemporalPassResult> results;
        for (const auto& w : config_.windows) {
            results.push_back(runSinglePass(w, query, loader));
        }
        return results;
    }

    TemporalPassResult runSinglePass(const TimeWindow& window,
                                     const std::string& query,
                                     ChunkLoader loader) {
        auto t0 = std::chrono::high_resolution_clock::now();
        TemporalPassResult pass;
        pass.window = window;

        try {
            // Load chunks for this time window
            auto chunks = loader(window, query);

            // Apply zoom: if zoom_factor > 1, keep only highest relevance chunks
            if (window.zoom_factor > 1.0f && !chunks.empty()) {
                std::sort(chunks.begin(), chunks.end(),
                    [](const DataChunk& a, const DataChunk& b) {
                        return a.relevance_score > b.relevance_score;
                    });
                size_t keep = static_cast<size_t>(
                    std::ceil(chunks.size() / window.zoom_factor));
                if (keep < 1) keep = 1;
                chunks.resize(std::min(keep, config_.top_k_per_window));
            } else {
                if (chunks.size() > config_.top_k_per_window)
                    chunks.resize(config_.top_k_per_window);
            }

            pass.raw_chunks = chunks;

            // Convert to ChunkResults
            float total_conf = 0.0f;
            for (const auto& c : chunks) {
                ChunkResult cr;
                cr.chunk_id   = c.chunk_id;
                cr.summary    = c.content.substr(0, std::min(c.content.size(), size_t(256)));
                cr.confidence = c.relevance_score;
                cr.result_embedding = c.embedding;
                pass.chunks.push_back(cr);
                total_conf += c.relevance_score;
            }
            pass.pass_confidence = chunks.empty() ? 0.0f : total_conf / chunks.size();
            pass.completed = true;

        } catch (const std::exception& e) {
            pass.error_msg = e.what();
            pass.completed = false;
        }

        auto t1 = std::chrono::high_resolution_clock::now();
        pass.latency = std::chrono::duration_cast<std::chrono::microseconds>(t1 - t0);
        return pass;
    }

    // ------------------------------------------------------------------------
    // COLLATION - Cross-pass scoring and classification
    // ------------------------------------------------------------------------
    std::vector<CollatedChunk> collate(
            const std::vector<TemporalPassResult>& pass_results,
            const std::string& /*query*/) {

        // Build a map: chunk_id -> CollatedChunk
        std::unordered_map<std::string, CollatedChunk> chunk_map;

        int total_passes = 0;
        for (const auto& pass : pass_results) {
            if (!pass.completed) continue;
            total_passes++;

            for (const auto& raw : pass.raw_chunks) {
                auto& cc = chunk_map[raw.chunk_id];
                if (cc.chunk.chunk_id.empty()) {
                    cc.chunk = raw;
                    cc.status = CollationStatus::PENDING;
                }
                cc.pass_count++;
                cc.found_in.push_back(pass.window.scale);
                cc.peak_relevance = std::max(cc.peak_relevance, raw.relevance_score);
                cc.cross_pass_score += raw.relevance_score * pass.window.attention_weight;
            }
        }

        if (total_passes == 0) return {};

        // Normalise cross_pass_score by total attention weight across completed passes
        float total_weight = 0.0f;
        for (const auto& pass : pass_results) {
            if (pass.completed) total_weight += pass.window.attention_weight;
        }

        std::vector<CollatedChunk> collated;
        for (auto& [id, cc] : chunk_map) {
            // Normalise
            if (total_weight > 0.0f) {
                cc.cross_pass_score /= total_weight;
            }

            // Also factor in pass frequency: chunks that appear in more passes are stronger
            float frequency_bonus = (float)cc.pass_count / (float)std::max(total_passes, 1);
            cc.cross_pass_score = cc.cross_pass_score * 0.7f + frequency_bonus * 0.3f;
            cc.cross_pass_score = std::min(cc.cross_pass_score, 1.0f);

            // Classify
            if (cc.cross_pass_score >= config_.relevance_threshold) {
                cc.status = CollationStatus::RELEVANT;
            } else if (cc.cross_pass_score >= config_.outlier_threshold) {
                cc.status = CollationStatus::OUTLIER;
            } else {
                cc.status = CollationStatus::NOISE;
                cc.excluded_from_related = true;
                cc.exclusion_reason = "cross_pass_score="
                    + std::to_string(cc.cross_pass_score).substr(0, 4)
                    + " < noise_floor="
                    + std::to_string(config_.noise_floor).substr(0, 4);
            }

            collated.push_back(cc);
        }

        // Sort: RELEVANT first, then OUTLIER, then NOISE; within each by score desc
        std::sort(collated.begin(), collated.end(),
            [](const CollatedChunk& a, const CollatedChunk& b) {
                if (a.status != b.status) return (int)a.status < (int)b.status;
                return a.cross_pass_score > b.cross_pass_score;
            });

        return collated;
    }

    // ------------------------------------------------------------------------
    // SYNTHESIS - Build final answer from RELEVANT chunks only
    // ------------------------------------------------------------------------
    ChunkResult synthesize(const std::vector<CollatedChunk>& collated,
                           const std::string& query) {
        ChunkResult result;
        result.chunk_id = "tds_synthesized";

        std::string combined;
        float total_conf = 0.0f;
        int count = 0;

        for (const auto& cc : collated) {
            if (cc.status == CollationStatus::RELEVANT) {
                combined += cc.chunk.content + "\n";
                for (const auto& fact : cc.chunk.source.empty()
                        ? std::vector<std::string>{}
                        : std::vector<std::string>{cc.chunk.source}) {
                    result.extracted_facts.push_back(fact);
                }
                total_conf += cc.cross_pass_score;
                count++;
            }
        }

        result.summary = count > 0
            ? "[TDS: " + std::to_string(count) + " relevant chunks for: " + query + "]\n" + combined
            : "[TDS: No relevant memory found for: " + query + "]";

        result.confidence = count > 0 ? total_conf / count : 0.0f;
        return result;
    }
};

// ================================================================
// INTEGRATION: Add TDS/TimeVine query to RecursiveLMEngine
// Extend with queryWithTDS() - runs TDS scan then feeds into RLM
// ================================================================

// Forward declare so RecursiveLMEngine can hold TDS
// (Defined inline below as extension methods are added via subclass
//  or free functions to preserve the original class structure)

// Free function: run TDS scan then deep RLM recursion on RELEVANT chunks
inline ChunkResult tdsRLMQuery(
        RecursiveLMEngine& engine,
        TimeDilationScope& tds,
        const std::string& query,
        TimeDilationScope::ChunkLoader loader,
        int max_depth = 5) {

    // 1. TDS multi-pass temporal scan
    TDSScanResult scan = tds.scan(query, loader);

    // 2. Build a data string from RELEVANT chunks only
    //    (NOISE excluded per TDS collation gate)
    std::string relevant_data;
    for (const auto& cc : scan.collated) {
        if (cc.status == CollationStatus::RELEVANT ||
            cc.status == CollationStatus::OUTLIER) {
            // Include outliers with lower weight marker
            std::string prefix = (cc.status == CollationStatus::OUTLIER)
                ? "[OUTLIER] " : "";
            relevant_data += prefix + cc.chunk.content + "\n-\n";
        }
        // NOISE: excluded_from_related = true -> skip entirely
    }

    if (relevant_data.empty()) {
        ChunkResult empty;
        empty.summary    = "[TDS+RLM: No relevant memory] " + scan.synthesized.summary;
        empty.confidence = 0.0f;
        return empty;
    }

    // 3. Feed filtered, temporally-scoped data into the RLM recursive engine
    ChunkResult rlm_result = engine.query(query, relevant_data, max_depth);

    // 4. Merge TDS metadata into result
    rlm_result.summary =
        "[TDS:" + std::to_string(scan.relevant_count) + "rel/"
                + std::to_string(scan.outlier_count)  + "out/"
                + std::to_string(scan.noise_count)    + "noise"
        + "] " + rlm_result.summary;

    return rlm_result;
}


// ================================================================
// CONVENIENCE WRAPPER FOR PHOTON INTEGRATION
// ================================================================

class PhotonRLM {
public:
    PhotonRLM(NodeGraphMemory* ngm, EngineBridge* engine)
        : engine_(std::make_unique<RecursiveLMEngine>(ngm, engine))
        , tds_(TDSConfig::allScales()) {}

    // Simple query interface
    std::string ask(const std::string& question, const std::string& context = "") {
        std::string data_source = context;
        if (data_source.empty()) {
            data_source = engine_->loadFromMemory(question);
        }
        auto result = engine_->query(question, data_source);
        return result.summary;
    }

    // Query with custom depth
    std::string askDeep(const std::string& question,
                        const std::string& context,
                        int max_depth) {
        auto result = engine_->query(question, context, max_depth);
        return result.summary;
    }

    // ------------------------------------------------------------------------
    // TIMEVINE / TDS INTERFACE
    // ------------------------------------------------------------------------

    // Full temporal scan: load all chunks -> TDS multi-pass -> RLM recursion
    // loader: function that returns chunks given a TimeWindow + query string
    // (in Photon: wrap your LanceDB timestamp-range + VSS call here)
    std::string askTemporal(
            const std::string& question,
            TimeDilationScope::ChunkLoader loader,
            int max_rlm_depth = 5) {
        ChunkResult result = tdsRLMQuery(*engine_, tds_, question, loader, max_rlm_depth);
        return result.summary;
    }

    // Zoom into a single time scale with optional focus factor
    std::string askZoom(const std::string& question,
                        TimeScale scale,
                        TimeDilationScope::ChunkLoader loader,
                        float zoom_factor = 2.0f) {
        TimeDilationScope zoomed(TDSConfig::zoomOn(scale, zoom_factor));
        ChunkResult result = tdsRLMQuery(*engine_, zoomed, question, loader, 3);
        return result.summary;
    }

    // Scan using a pre-built TimeVine
    std::string askVine(const std::string& question, TimeVine& vine, int max_depth = 5) {
        TDSScanResult scan = tds_.scanVine(question, vine);
        // Feed RELEVANT chunks into RLM
        std::string data;
        for (const auto& cc : scan.collated) {
            if (cc.status == CollationStatus::RELEVANT)
                data += cc.chunk.content + "\n-\n";
        }
        if (data.empty()) return "[TimeVine: No relevant memory for: " + question + "]";
        auto result = engine_->query(question, data, max_depth);
        result.summary = "[TV:" + std::to_string(scan.relevant_count) + "R/"
                       + std::to_string(scan.outlier_count) + "O/"
                       + std::to_string(scan.noise_count) + "N] " + result.summary;
        return result.summary;
    }

    // Build a TimeVine from a flat chunk list
    void buildVine(const std::vector<DataChunk>& chunks, int max_depth = 4) {
        vine_.build(chunks, max_depth);
    }

    // Get collation report from last TDS scan (call after askTemporal)
    std::string lastTDSReport(const std::string& question,
                               TimeDilationScope::ChunkLoader loader) {
        TDSScanResult scan = tds_.scan(question, loader);
        return tds_.formatReport(scan);
    }

    // Reconfigure TDS windows
    void configureTDS(TDSConfig cfg) { tds_ = TimeDilationScope(std::move(cfg)); }
    TimeDilationScope& tds() { return tds_; }
    TimeVine& vine() { return vine_; }

    // Export training data
    void exportTrainingData(const std::string& path) {
        engine_->exportTrajectoriesForTraining(path);
    }

    // Get underlying engine for advanced use
    RecursiveLMEngine* getEngine() { return engine_.get(); }

private:
    std::unique_ptr<RecursiveLMEngine> engine_;
    TimeDilationScope                  tds_;
    TimeVine                           vine_;
};

} // namespace RLM

// ================================================================
// INTEGRATION MACROS FOR PHOTON_CORE.CPP
#endif // PHOTON_FULL_BUILD
