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

/// Top holding in an ETF.
pub const Holding = struct {
    symbol: ?[]const u8 = null,
    name: []const u8,
    weight: f64,
    /// CUSIP from the NPORT-P filing, when present. The join key for
    /// look-through resolution: NPORT-P identifies holdings by CUSIP
    /// far more reliably than by ticker, so `exposure` resolves this
    /// to a ticker (via `cusip_tickers.srf` / OpenFIGI) to match a
    /// holding against a directly-held position.
    cusip: ?[]const u8 = null,
};

/// Sector allocation in an ETF.
pub const SectorWeight = struct {
    name: []const u8,
    weight: f64,
};

/// ETF profile and metadata. Assembled from public EDGAR
/// (NPORT-P holdings + sectors + AUM) plus Wikidata
/// (inception_date + name fallback). The legacy AlphaVantage
/// fields (`expense_ratio`, `dividend_yield`,
/// `portfolio_turnover`, `leveraged`) remain on the type but
/// stay null in the current pipeline — they'll fill in once a
/// prospectus parser lands.
pub const EtfProfile = struct {
    symbol: []const u8,
    /// Fund name (preferred from EDGAR series_name; fallback to
    /// Wikidata name). Owned by the caller (via `deinit`).
    name: ?[]const u8 = null,
    asset_class: ?[]const u8 = null,
    /// Expense ratio as a decimal (e.g., 0.0003 for 0.03%).
    /// Currently unset — needs a prospectus parser.
    expense_ratio: ?f64 = null,
    /// Net assets in USD (from NPORT-P).
    net_assets: ?f64 = null,
    /// Top holdings (from NPORT-P).
    holdings: ?[]const Holding = null,
    /// Number of top holdings retained from NPORT-P.
    total_holdings: ?u32 = null,
    /// Sector allocations (from NPORT-P).
    sectors: ?[]const SectorWeight = null,
    /// Dividend yield as decimal. Currently unset.
    dividend_yield: ?f64 = null,
    /// Portfolio turnover as decimal. Currently unset.
    portfolio_turnover: ?f64 = null,
    /// Fund inception date (from Wikidata).
    inception_date: ?Date = null,
    /// Whether the fund is leveraged. Currently always false
    /// pending prospectus parsing.
    leveraged: bool = false,

    /// Returns true if the profile contains meaningful ETF data.
    /// Non-ETF symbols return empty profiles.
    pub fn isEtf(self: EtfProfile) bool {
        return self.expense_ratio != null or
            self.net_assets != null or
            self.holdings != null or
            self.sectors != null or
            self.total_holdings != null;
    }

    /// Free any owned fields on this profile.
    ///
    /// Frees: `symbol`, `name`, holdings (each holding's strings +
    /// the slice), sectors (each sector's name + the slice).
    /// Other optional strings (`asset_class`) currently stay null
    /// in the EDGAR-backed pipeline; if a future code path
    /// allocates them, extend this function.
    pub fn deinit(self: EtfProfile, allocator: std.mem.Allocator) void {
        allocator.free(self.symbol);
        if (self.name) |n| allocator.free(n);
        if (self.holdings) |h| {
            for (h) |holding| {
                if (holding.symbol) |s| allocator.free(s);
                if (holding.cusip) |c| allocator.free(c);
                allocator.free(holding.name);
            }
            allocator.free(h);
        }
        if (self.sectors) |s| {
            for (s) |sec| allocator.free(sec.name);
            allocator.free(s);
        }
    }
};

const std = @import("std");

test "isEtf" {
    // Empty profile -> not an ETF
    const empty = EtfProfile{ .symbol = "AAPL" };
    try std.testing.expect(!empty.isEtf());
    // Has expense_ratio -> is ETF
    const with_er = EtfProfile{ .symbol = "VTI", .expense_ratio = 0.0003 };
    try std.testing.expect(with_er.isEtf());
    // Has net_assets -> is ETF
    const with_na = EtfProfile{ .symbol = "SPY", .net_assets = 500_000_000_000 };
    try std.testing.expect(with_na.isEtf());
    // Has total_holdings -> is ETF
    const with_th = EtfProfile{ .symbol = "QQQ", .total_holdings = 100 };
    try std.testing.expect(with_th.isEtf());
}
