/// Embedded historical market data derived from Robert Shiller's CAPE dataset.
///
/// Source: https://shillerdata.com/ (ie_data.xls, "Data" tab saved as CSV)
///
/// To update:
///   1. Download ie_data.xls from https://shillerdata.com/
///   2. Open in LibreOffice Calc, select the "Data" tab
///   3. File → Save As → CSV (ie_data.csv)
///   4. Replace src/data/ie_data.csv with the new file
///   5. Rebuild — build/gen_shiller.zig regenerates the data automatically
///   6. Bump `ie_data_last_updated` below to today's date.
///
/// All returns are nominal, expressed as decimals (0.12 = 12%).
const std = @import("std");
const Date = @import("../Date.zig");
const generated = @import("shiller_generated");
pub const ShillerYear = @import("shiller_year").ShillerYear;

/// Last time `ie_data.csv` was refreshed. Bump this whenever you
/// replace the CSV — drives the annual staleness nag in
/// `src/data/staleness.zig` (nags on stderr from April 1 each year
/// until refreshed).
pub const ie_data_last_updated: Date = Date.fromYmd(2026, 4, 27);

/// Annual returns from the Shiller dataset, generated at build time from ie_data.csv.
pub const annual_returns: []const ShillerYear = &generated.data;

/// Number of available years of historical data.
pub const year_count: usize = annual_returns.len;

/// First year with complete data.
pub const first_year: u16 = if (annual_returns.len > 0) annual_returns[0].year else 0;

/// Last year with complete data.
pub const last_year: u16 = if (annual_returns.len > 0) annual_returns[annual_returns.len - 1].year else 0;

/// Convert a nominal return to a real (inflation-adjusted) return.
pub fn realReturn(nominal: f64, inflation: f64) f64 {
    return (1.0 + nominal) / (1.0 + inflation) - 1.0;
}

/// Return the maximum number of complete simulation cycles for a given horizon.
/// A cycle starting in year Y needs data through year Y + horizon - 1.
pub fn maxCycles(horizon: u16) usize {
    if (annual_returns.len == 0) return 0;
    const span = last_year - first_year + 1;
    if (horizon > span) return 0;
    return span - horizon + 1;
}

// --- Tests ---

test "annual returns are populated" {
    try std.testing.expect(annual_returns.len >= 150);
    try std.testing.expectEqual(@as(u16, 1871), first_year);
    try std.testing.expect(last_year >= 2024);
}

test "spot check 2008 crash" {
    for (annual_returns) |yr| {
        if (yr.year == 2008) {
            try std.testing.expect(yr.sp500_total_return < -0.30);
            try std.testing.expect(yr.sp500_total_return > -0.50);
            return;
        }
    }
    return error.YearNotFound;
}

test "spot check 1929 crash" {
    for (annual_returns) |yr| {
        if (yr.year == 1929) {
            try std.testing.expect(yr.sp500_total_return < -0.05);
            return;
        }
    }
    return error.YearNotFound;
}

test "realReturn calculation" {
    const real = realReturn(0.10, 0.03);
    try std.testing.expectApproxEqAbs(0.06796, real, 0.001);
}

test "maxCycles" {
    const cycles = maxCycles(30);
    try std.testing.expect(cycles >= 120);
    try std.testing.expect(cycles <= 130);
    try std.testing.expectEqual(@as(usize, 0), maxCycles(200));
}

test "spot check known annual total returns" {
    const checks = [_]struct { year: u16, min: f64, max: f64 }{
        .{ .year = 1931, .min = -0.55, .max = -0.25 },
        .{ .year = 1933, .min = 0.30, .max = 0.80 },
        .{ .year = 1974, .min = -0.40, .max = -0.05 },
        .{ .year = 2008, .min = -0.50, .max = -0.25 },
        .{ .year = 2009, .min = 0.15, .max = 0.50 },
        .{ .year = 2021, .min = 0.10, .max = 0.45 },
    };

    for (checks) |chk| {
        for (annual_returns) |yr| {
            if (yr.year == chk.year) {
                if (yr.sp500_total_return < chk.min or yr.sp500_total_return > chk.max) {
                    return error.TestExpectedEqual;
                }
                break;
            }
        }
    }
}

test "bond returns are reasonable" {
    for (annual_returns) |yr| {
        if (yr.bond_total_return < 0.0 or yr.bond_total_return > 0.16) return error.TestExpectedEqual;
    }
}

test "CPI inflation is reasonable" {
    for (annual_returns) |yr| {
        if (yr.cpi_inflation < -0.20 or yr.cpi_inflation > 0.25) return error.TestExpectedEqual;
    }
}
