//! Snapshot record types — the wire format for `history/<date>-portfolio.srf`.
//!
//! Each record kind below is a plain struct suitable for `srf.fmt`
//! on the write side and `srf.iterator` + `FieldIterator.to(Union)` on
//! the read side (see `src/history.zig`, which demuxes via a tagged
//! `SnapshotRecord` union whose `srf_tag_field = "kind"`). Field order
//! in the struct declaration IS the on-disk order: srf's `Record.from`
//! iterates `inline for (info.fields)`.
//!
//! Lives in `src/models/` because these types describe the data format
//! itself — they're consumed by the snapshot writer command
//! (`src/commands/snapshot.zig`), the history reader (`src/history.zig`),
//! and the timeline analytics (`src/analytics/timeline.zig`). Putting
//! them in `commands/` would force analytics to depend on a command
//! module, which would be backwards.
//!
//! IMPORTANT: `kind` uses `= ""` as the default — a sentinel that never
//! matches any real discriminator value. This satisfies two constraints
//! simultaneously:
//!   - On write, srf elides fields whose value matches the default. Since
//!     real tags are "meta"/"total"/... (never `""`), `kind` is always
//!     emitted.
//!   - On read via the `SnapshotRecord` tagged union (see `history.zig`),
//!     srf's union dispatch consumes the `kind` field itself before
//!     coercing into a variant struct, so `kind` is absent from the
//!     variant's field stream. The default value prevents a
//!     `FieldNotFoundOnFieldWithoutDefaultValue` error. The post-parse
//!     `kind` field on variant rows is `""` and should not be consulted —
//!     the union tag carries the discriminator.
//!
//! Optional fields default to `null` so they're elided on null values —
//! that's the behavior we want for `price`, `quote_date`, etc.

const std = @import("std");
const Date = @import("../Date.zig");

pub const MetaRow = struct {
    kind: []const u8 = "",
    snapshot_version: u32,
    as_of_date: Date,
    captured_at: i64,
    zfin_version: []const u8,
    quote_date_min: ?Date = null,
    quote_date_max: ?Date = null,
    stale_count: usize,
};

pub const TotalRow = struct {
    kind: []const u8 = "",
    scope: []const u8,
    value: f64,
};

pub const TaxTypeRow = struct {
    kind: []const u8 = "",
    label: []const u8,
    value: f64,
};

pub const AccountRow = struct {
    kind: []const u8 = "",
    name: []const u8,
    value: f64,
};

pub const LotRow = struct {
    kind: []const u8 = "",
    symbol: []const u8,
    lot_symbol: []const u8,
    account: []const u8,
    security_type: []const u8,
    shares: f64,
    open_price: f64,
    cost_basis: f64,
    value: f64,
    /// Null for non-stock lots (cash/CD/illiquid have no per-share price).
    price: ?f64 = null,
    /// Null for non-stock lots.
    quote_date: ?Date = null,
    /// Emitted only when true (default is false, which srf skips).
    quote_stale: bool = false,
};

/// In-memory portfolio snapshot. `meta` is a single record; the four
/// slices are per-section record collections.
///
/// When constructed by `history.parseSnapshotBytes`, all string fields
/// inside the rows (`symbol`, `label`, etc.) are slices into the
/// caller-owned `bytes` buffer, NOT independently-allocated copies.
/// The caller is responsible for keeping that buffer alive at least as
/// long as the `Snapshot` — typical pattern is `defer allocator.free(bytes)`
/// placed AFTER the `defer snap.deinit(allocator)`.
pub const Snapshot = struct {
    meta: MetaRow,
    totals: []TotalRow,
    tax_types: []TaxTypeRow,
    accounts: []AccountRow,
    lots: []LotRow,

    pub fn deinit(self: *Snapshot, allocator: std.mem.Allocator) void {
        allocator.free(self.totals);
        allocator.free(self.tax_types);
        allocator.free(self.accounts);
        allocator.free(self.lots);
    }
};
