/// Classification metadata for portfolio analysis.
///
/// Each entry maps a symbol to one or more asset class / sector / geographic allocations.
/// For individual stocks, there's typically one entry at 100%.
/// For blended funds (e.g., target date), there can be multiple entries that sum to ~100%.
///
/// Loaded from a metadata SRF file like `metadata.srf`:
///   symbol::AMZN,sector::Technology,geo::US,asset_class::US Large Cap
///   symbol::02315N600,asset_class::US Large Cap,pct:num:55
///   symbol::02315N600,asset_class::International Developed,pct:num:20
///   symbol::02315N600,asset_class::Bonds,pct:num:15
const std = @import("std");
const srf = @import("srf");

/// A single classification entry for a symbol.
pub const ClassificationEntry = struct {
    symbol: []const u8,
    /// Sector (e.g., "Technology", "Healthcare", "Financials")
    sector: ?[]const u8 = null,
    /// Geographic region (e.g., "US", "International Developed", "Emerging Markets")
    geo: ?[]const u8 = null,
    /// Asset class (e.g., "US Large Cap", "Bonds", "Cash")
    asset_class: ?[]const u8 = null,
    /// Percentage weight for this entry (0-100). Default 100 for single-class assets.
    pct: f64 = 100.0,
};

/// Parsed classification data for the entire portfolio.
pub const ClassificationMap = struct {
    entries: []ClassificationEntry,
    allocator: std.mem.Allocator,

    pub fn deinit(self: *ClassificationMap) void {
        for (self.entries) |e| {
            self.allocator.free(e.symbol);
            if (e.sector) |s| self.allocator.free(s);
            if (e.geo) |g| self.allocator.free(g);
            if (e.asset_class) |a| self.allocator.free(a);
        }
        self.allocator.free(self.entries);
    }
};

/// Parse a metadata SRF file into a ClassificationMap.
/// Each record has: symbol::<SYM>,sector::<S>,geo::<G>,asset_class::<A>,pct:num:<P>
/// All fields except symbol are optional. pct defaults to 100.
pub fn parseClassificationFile(allocator: std.mem.Allocator, data: []const u8) !ClassificationMap {
    var entries = std.ArrayList(ClassificationEntry).empty;
    errdefer {
        for (entries.items) |e| {
            allocator.free(e.symbol);
            if (e.sector) |s| allocator.free(s);
            if (e.geo) |g| allocator.free(g);
            if (e.asset_class) |a| allocator.free(a);
        }
        entries.deinit(allocator);
    }

    var reader = std.Io.Reader.fixed(data);
    var it = srf.iterator(&reader, allocator, .{ .alloc_strings = false }) catch return error.InvalidData;
    defer it.deinit();

    while (try it.next()) |fields| {
        const entry = fields.to(ClassificationEntry) catch continue;
        try entries.append(allocator, .{
            .symbol = try allocator.dupe(u8, entry.symbol),
            .sector = if (entry.sector) |s| try allocator.dupe(u8, s) else null,
            .geo = if (entry.geo) |g| try allocator.dupe(u8, g) else null,
            .asset_class = if (entry.asset_class) |a| try allocator.dupe(u8, a) else null,
            .pct = entry.pct,
        });
    }

    return .{
        .entries = try entries.toOwnedSlice(allocator),
        .allocator = allocator,
    };
}

test "parse classification file" {
    const data =
        \\#!srfv1
        \\# Stock: single sector
        \\symbol::AMZN,sector::Technology,geo::US,asset_class::US Large Cap
        \\
        \\# Target date fund: blended
        \\symbol::TGT2035,asset_class::US Large Cap,pct:num:55
        \\symbol::TGT2035,asset_class::Bonds,pct:num:15
        \\symbol::TGT2035,asset_class::International Developed,pct:num:20
    ;
    const allocator = std.testing.allocator;
    var cm = try parseClassificationFile(allocator, data);
    defer cm.deinit();

    try std.testing.expectEqual(@as(usize, 4), cm.entries.len);
    try std.testing.expectEqualStrings("AMZN", cm.entries[0].symbol);
    try std.testing.expectEqualStrings("Technology", cm.entries[0].sector.?);
    try std.testing.expectEqualStrings("US", cm.entries[0].geo.?);
    try std.testing.expectApproxEqAbs(@as(f64, 100.0), cm.entries[0].pct, 0.01);

    try std.testing.expectEqualStrings("TGT2035", cm.entries[1].symbol);
    try std.testing.expectEqualStrings("US Large Cap", cm.entries[1].asset_class.?);
    try std.testing.expectApproxEqAbs(@as(f64, 55.0), cm.entries[1].pct, 0.01);
}
