Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
//! Core framework reports for entity traversal, logging, and event history.
//!
//! These reports provide framework-level functionality that operates across
//! all entity types rather than specific domain entities.

use std::sync::Arc;
#[cfg(not(target_arch = "wasm32"))]
use std::sync::atomic::Ordering;
#[cfg(not(target_arch = "wasm32"))]
use std::time::Duration;

#[cfg(not(target_arch = "wasm32"))]
use hyphae::MapExt;
#[cfg(not(target_arch = "wasm32"))]
use hyphae::SwitchMapExt;
use hyphae::{Cell, MaterializeDefinite};
#[cfg(not(target_arch = "wasm32"))]
use hyphae::{DedupedExt, interval};
use serde::{Deserialize, Serialize};
use serde_json::Value;

use crate::{
    TS,
    report::{ReportContext, ReportHandler},
    wire::{MEvent, WrappedItem},
};

// ─────────────────────────────────────────────────────────────────────────────
// Entity Stub Types
// ─────────────────────────────────────────────────────────────────────────────

/// Stub representation of an entity for tree traversal.
/// Contains minimal identifying information without full entity data.
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export)]
pub struct ItemStub {
    pub id: Arc<str>,
    pub item_type: String,
    pub name: Option<String>,
}

/// Data returned by EntitySnapshotDifference report.
#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export)]
pub struct EntitySnapshotDifferenceData {
    pub changed: Vec<ItemStub>,
    pub added: Vec<ItemStub>,
    pub removed: Vec<ItemStub>,
}

// ─────────────────────────────────────────────────────────────────────────────
// Entity Traversal Reports
// ─────────────────────────────────────────────────────────────────────────────

/// Report that fetches all items by type and IDs.
#[myko_macros::myko_report(Vec<Value>)]
pub struct GetItemsByTypeAndIds {
    /// The entity type name (e.g., "Scene", "Target")
    #[serde(rename = "type")]
    pub item_type: String,
    /// List of entity IDs to fetch
    pub ids: Vec<Arc<str>>,
}

impl ReportHandler for GetItemsByTypeAndIds {
    type Output = Vec<serde_json::Value>;

    fn compute(&self, _ctx: ReportContext) -> impl MaterializeDefinite<Arc<Self::Output>> {
        // TODO(ts): Implement dynamic type lookup once we have entity registry
        // For now, return empty - this requires runtime type resolution
        Cell::new(Arc::new(Vec::new())).lock()
    }
}

/// Report that fetches immediate child entities of a parent.
#[myko_macros::myko_report(Vec<ItemStub>)]
pub struct ChildEntities {
    pub parent_type: String,
    pub parent_id: Arc<str>,
}

impl ReportHandler for ChildEntities {
    type Output = Vec<ItemStub>;

    fn compute(&self, _ctx: ReportContext) -> impl MaterializeDefinite<Arc<Self::Output>> {
        // TODO(ts): Implement using relationship manager
        // This requires querying the relationship graph for direct children
        Cell::new(Arc::new(Vec::new())).lock()
    }
}

/// Report that recursively fetches all child entities of a parent.
#[myko_macros::myko_report(Vec<ItemStub>)]
pub struct FullChildEntities {
    pub parent_type: String,
    pub parent_id: Arc<str>,
}

impl ReportHandler for FullChildEntities {
    type Output = Vec<ItemStub>;

    fn compute(&self, _ctx: ReportContext) -> impl MaterializeDefinite<Arc<Self::Output>> {
        // TODO(ts): Implement recursive traversal using relationship manager
        Cell::new(Arc::new(Vec::new())).lock()
    }
}

/// Report that fetches all-time child entities (including deleted).
#[myko_macros::myko_report(Vec<ItemStub>)]
pub struct ChildEntitiesAllTime {
    pub parent_type: String,
    pub parent_id: Arc<str>,
}

impl ReportHandler for ChildEntitiesAllTime {
    type Output = Vec<ItemStub>;

    fn compute(&self, _ctx: ReportContext) -> impl MaterializeDefinite<Arc<Self::Output>> {
        // TODO(ts): Implement with historical event store query
        Cell::new(Arc::new(Vec::new())).lock()
    }
}

/// Report that computes the difference between entity snapshots.
#[myko_macros::myko_report(EntitySnapshotDifferenceData)]
pub struct EntitySnapshotDifference {
    pub parent_type: String,
    pub parent_id: Arc<str>,
}

impl ReportHandler for EntitySnapshotDifference {
    type Output = EntitySnapshotDifferenceData;

    fn compute(&self, _ctx: ReportContext) -> impl MaterializeDefinite<Arc<Self::Output>> {
        // TODO(ts): Implement snapshot comparison
        Cell::new(Arc::new(EntitySnapshotDifferenceData::default())).lock()
    }
}

// ─────────────────────────────────────────────────────────────────────────────
// Logging Support
// ─────────────────────────────────────────────────────────────────────────────

/// Log level enum for controlling logging verbosity.
#[derive(Clone, Debug, Default, Serialize, Deserialize, TS, PartialEq, Eq)]
#[serde(rename_all = "UPPERCASE")]
#[ts(export)]
pub enum LogLevel {
    Error,
    Warn,
    #[default]
    Info,
    Debug,
    Verbose,
}

/// Report that returns the list of available logger names.
#[myko_macros::myko_report(Vec<String>)]
pub struct Loggers {}

impl ReportHandler for Loggers {
    type Output = Vec<String>;

    fn compute(&self, _ctx: ReportContext) -> impl MaterializeDefinite<Arc<Self::Output>> {
        // TODO(ts): Integrate with tracing subscriber to list available targets
        Cell::new(Arc::new(vec![
            "myko".to_string(),
            "myko::server".to_string(),
            "myko::query".to_string(),
            "myko::command".to_string(),
            "myko::report".to_string(),
        ]))
        .lock()
    }
}

/// Report that returns the current log level for a server.
#[myko_macros::myko_report(LogLevel)]
pub struct ServerLogLevel {
    pub server_id: Arc<str>,
}

impl ReportHandler for ServerLogLevel {
    type Output = LogLevel;

    fn compute(&self, _ctx: ReportContext) -> impl MaterializeDefinite<Arc<Self::Output>> {
        // TODO(ts): Query actual log level from tracing config
        Cell::new(Arc::new(LogLevel::Info)).lock()
    }
}

/// Command to set the log level for a server.
#[myko_macros::myko_command(bool)]
pub struct SetLogLevel {
    pub server_id: Arc<str>,
    pub level: LogLevel,
}

impl crate::command::CommandHandler for SetLogLevel {
    fn execute(
        self,
        _ctx: crate::command::CommandContext,
    ) -> Result<bool, crate::command::CommandError> {
        // TODO(ts): Implement dynamic log level adjustment
        // This requires integration with tracing-subscriber's reload layer
        Ok(true)
    }
}

/// Report that checks whether a peer server client is currently connected.
/// Returns ping in milliseconds when available, otherwise `-1`.
#[myko_macros::myko_report(i64)]
pub struct PeerAlive {
    pub peer_id: Arc<str>,
}

#[cfg(not(target_arch = "wasm32"))]
impl ReportHandler for PeerAlive {
    type Output = i64;

    fn compute(&self, ctx: ReportContext) -> impl MaterializeDefinite<Arc<Self::Output>> {
        let peer_id = self.peer_id.clone();
        let report_ctx = ctx.clone();
        ctx.peer_clients_tick().switch_map(move |_| {
            let Some(peer_client) = report_ctx.peer_client(peer_id.as_ref()) else {
                return Cell::new(Arc::new(-1)).lock();
            };

            peer_client
                .ping_ms()
                .clone()
                .map(|ping_ms| {
                    Arc::new(
                        ping_ms
                            .map(|ms| ms.min(i64::MAX as u64) as i64)
                            .unwrap_or(-1),
                    )
                })
                .materialize()
        })
    }
}

#[cfg(target_arch = "wasm32")]
impl ReportHandler for PeerAlive {
    type Output = i64;

    fn compute(&self, _ctx: ReportContext) -> impl MaterializeDefinite<Arc<Self::Output>> {
        Cell::new(Arc::new(-1)).lock()
    }
}

// ─────────────────────────────────────────────────────────────────────────────
// Event History Support
// ─────────────────────────────────────────────────────────────────────────────

/// Container for an event with associated metadata.
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export)]
pub struct EventContainer {
    pub id: Arc<str>,
    pub event: crate::wire::MEvent,
}

/// Report that returns events for a specific transaction.
#[myko_macros::myko_report(Vec<MEvent>)]
pub struct EventsForTransaction {
    pub transaction_id: String,
}

impl ReportHandler for EventsForTransaction {
    type Output = Vec<crate::wire::MEvent>;

    fn compute(&self, _ctx: ReportContext) -> impl MaterializeDefinite<Arc<Self::Output>> {
        // TODO(ts): Query event store by transaction ID
        Cell::new(Arc::new(Vec::new())).lock()
    }
}

// ─────────────────────────────────────────────────────────────────────────────
// Import/Export Commands
// ─────────────────────────────────────────────────────────────────────────────

/// Command to import items into the system as SET events, and optionally
/// delete items as DEL events. Returns the total number of events applied.
///
/// Used by the entity tree import/export system to apply diffs.
#[myko_macros::myko_command(usize)]
pub struct ImportItems {
    /// Items to SET (add or update).
    pub items: Vec<WrappedItem>,
    /// Items to DEL (remove). Only the shallowest removed entities should be
    /// included — the relationship manager cascades deletions to children.
    #[serde(default)]
    pub delete_items: Vec<WrappedItem>,
}

impl crate::command::CommandHandler for ImportItems {
    fn execute(
        self,
        ctx: crate::command::CommandContext,
    ) -> Result<usize, crate::command::CommandError> {
        use crate::wire::{MEvent, MEventType};

        let tx = ctx.tx().to_string();
        let source_id = Some(ctx.host_id().to_string());
        let created_at = ctx.created_at().to_string();

        let mut events = Vec::with_capacity(self.items.len() + self.delete_items.len());

        for wrapped in &self.items {
            events.push(MEvent {
                item: wrapped.item.clone(),
                change_type: MEventType::SET,
                item_type: wrapped.item_type.to_string(),
                created_at: created_at.clone(),
                tx: tx.clone(),
                source_id: source_id.clone(),
                options: None,
            });
        }

        for wrapped in &self.delete_items {
            events.push(MEvent {
                item: wrapped.item.clone(),
                change_type: MEventType::DEL,
                item_type: wrapped.item_type.to_string(),
                created_at: created_at.clone(),
                tx: tx.clone(),
                source_id: source_id.clone(),
                options: None,
            });
        }

        ctx.emit_event_batch(events)
    }
}

// ─────────────────────────────────────────────────────────────────────────────
// Persist Health
// ─────────────────────────────────────────────────────────────────────────────

/// Health snapshot for the persist subsystem.
#[myko_macros::myko_report_output]
pub struct PersistHealthStatus {
    /// Events queued but not yet written.
    pub queued: u64,
    /// Lifetime successful writes.
    pub total_persisted: u64,
    /// Lifetime failed writes.
    pub total_errors: u64,
    /// Consecutive failures since last success.
    pub consecutive_errors: u64,
    /// Most recent error message, if any.
    #[serde(skip_serializing_if = "Option::is_none")]
    #[ts(optional = nullable)]
    pub last_error: Option<String>,
    /// Whether the persister is currently healthy (no consecutive errors).
    pub healthy: bool,
    /// Writes per second over a sliding window.
    pub writes_per_second: f64,
}

/// Report that returns the current health of the persist subsystem.
/// Polls every 500ms and deduplicates, so subscribers only receive updates
/// when values actually change.
#[myko_macros::myko_report(PersistHealthStatus)]
pub struct GetPersistHealth {}

#[cfg(not(target_arch = "wasm32"))]
impl ReportHandler for GetPersistHealth {
    type Output = PersistHealthStatus;

    fn compute(&self, ctx: ReportContext) -> impl MaterializeDefinite<Arc<Self::Output>> {
        let health = ctx.persist_health();
        interval(Duration::from_millis(500))
            .map(move |_tick| {
                let queued = health.queued.load(Ordering::Relaxed);
                let total_persisted = health.total_persisted.load(Ordering::Relaxed);
                let total_errors = health.total_errors.load(Ordering::Relaxed);
                let consecutive_errors = health.consecutive_errors.load(Ordering::Relaxed);
                let last_error = health.last_error.read().unwrap().clone();
                let writes_per_second = health.writes_per_second();
                Arc::new(PersistHealthStatus {
                    queued,
                    total_persisted,
                    total_errors,
                    consecutive_errors,
                    last_error,
                    healthy: consecutive_errors == 0,
                    writes_per_second,
                })
            })
            .materialize()
            .deduped()
    }
}

#[cfg(target_arch = "wasm32")]
impl ReportHandler for GetPersistHealth {
    type Output = PersistHealthStatus;

    fn compute(&self, _ctx: ReportContext) -> impl MaterializeDefinite<Arc<Self::Output>> {
        Cell::new(Arc::new(PersistHealthStatus {
            queued: 0,
            total_persisted: 0,
            total_errors: 0,
            consecutive_errors: 0,
            last_error: None,
            healthy: true,
            writes_per_second: 0.0,
        }))
        .lock()
    }
}

// ─────────────────────────────────────────────────────────────────────────────
// ts-rs Export Registrations
// ─────────────────────────────────────────────────────────────────────────────

// Register output types for ts-rs export
crate::register_ts_export!(
    ItemStub,
    EntitySnapshotDifferenceData,
    LogLevel,
    EventContainer,
    PersistHealthStatus
);