// demos https://github.com/systemaccounting/mxfactorial

// modeling transaction values with bivectors automates "monetary policy" with a physical conservation law

// transactions as bivectors helps set an arrow of time in the form of a
// causal structure, or event sequence, that value must be produced before its consumed
// and eliminates "money creation" so financial failures propagate only
// along direct investment chains, i.e. rather than causing system-wide contagion

// eliminating systemic default risk eliminates monetary inflation

use geonum::Geonum;
use std::f64::consts::{PI, TAU};
use std::time::Instant;

#[test]
fn it_models_causal_transaction_structure() {
    // this test demonstrates how geonum can analyze transaction causality
    // using the mxfactorial bivector model where production must precede consumption

    // in mxfactorial, transactions are structured as bivectors between creditors and debitors
    // with timestamps that establish the arrow of time for value flows
    //
    // geometric algebra explanation of blade grade 2 for transactions:
    // in geometric algebra, blade grade signifies the dimensional complexity of an element:
    // - blade: 0 (scalars) - pure magnitudes without direction (like prices, interest rates)
    // - blade: 1 (vectors) - directed quantities in 1D space (like forces, velocities)
    // - blade: 2 (bivectors) - oriented plane elements or area sweeps (transactions, relationships)
    //
    // transactions are modeled as bivectors (grade 2) because they:
    // 1. represent the economic "plane" formed between two accounts (creditor ∧ debitor)
    // 2. encode the directed relationship between entities in a 2D financial subspace
    // 3. demonstrate anticommutativity: (A ∧ B = -(B ∧ A)), preserving transaction directionality
    // 4. allow for natural conservation laws in economic systems through geometric properties
    // 5. geometrically represent the wedge product of two economic positions or entities
    //
    // while prices (scalar values) have blade: 0, transactions between entities have
    // blade: 2 to maintain the geometric structure that captures the relational
    // aspect of value exchange in a multidimensional economic space

    // simulate a purchasing transaction between SarahBell (software engineer) and GroceryCo (food service)
    // Prices are scalar quantities (grade 0) with only magnitude
    let ground_coffee = Geonum::new(5.0, 0.0, 1.0); // price magnitude, neutral angle initially
    let pasta = Geonum::new(10.0, 0.0, 1.0); // 4 * $2.50
    let paper_towels = Geonum::new(6.0, 0.0, 1.0); // 2 * $3.00
    let sales_tax = Geonum::new(1.89, 0.0, 1.0); // total sales tax

    // define account angles in the economic space
    // angles encode account attributes: industry, occupation, geography
    let software_engineer_angle = PI / 4.0; // SarahBell's occupation
    let food_service_angle = 3.0 * PI / 4.0; // GroceryCo's industry
    let government_angle = 5.0 * PI / 4.0; // StateOfCalifornia's sector

    // define timestamps to establish causality
    // in mxfactorial, creditor approval must precede debitor approval
    let creditor_time = 1.0; // normalized time unit for creditor approval
    let debitor_time = 2.0; // normalized time unit for debitor approval

    // convert transactions to bivectors by rotating the values based on the
    // creditor and debitor account attributes
    let analyze_transaction =
        |amount: &Geonum, creditor_angle: f64, debitor_angle: f64| -> Geonum {
            // bivector representation: creditor ∧ debitor
            // anticommutative: creditor ∧ debitor = -(debitor ∧ creditor)
            let transaction_angle = (creditor_angle - debitor_angle) % TAU;

            // return transaction bivector
            // convert angle to geometric representation
            Geonum::new_with_blade(amount.mag, 2, transaction_angle, PI)
        };

    // apply the bivector conversion to each transaction item
    let coffee_bivector =
        analyze_transaction(&ground_coffee, food_service_angle, software_engineer_angle);
    let pasta_bivector = analyze_transaction(&pasta, food_service_angle, software_engineer_angle);
    let towels_bivector =
        analyze_transaction(&paper_towels, food_service_angle, software_engineer_angle);
    let tax_bivector = analyze_transaction(&sales_tax, government_angle, software_engineer_angle);

    // prove anticommutativity (fundamental property of bivectors)
    // creditor to debitor flow = negative of debitor to creditor flow
    let reverse_flow =
        analyze_transaction(&ground_coffee, software_engineer_angle, food_service_angle);

    // with transactions as bivectors, reversing the creditor and debitor relationship
    // produces an angle thats exactly opposite (PI radians apart or negated)
    // in this specific implementation, the angles are directly negated
    assert!(
        reverse_flow.angle.is_opposite(&coffee_bivector.angle),
        "reversing creditor and debitor creates opposite angle"
    );

    // this property guarantees:
    // 1. Creditor ∧ Debitor = -(Debitor ∧ Creditor)
    // 2. transactions have a clear directional flow
    // 3. the direction of value transfer is preserved in geometric space
    // its the property for enforcing conservation laws in economic spacetime

    // compute account balances using the conservation law:
    // account balance = sum(creditor) - sum(debitor)

    // Sarah's balance change (consumer)
    let sarah_debitor_sum = ground_coffee.mag + pasta.mag + paper_towels.mag + sales_tax.mag;
    let sarah_creditor_sum = 0.0; // no credits in this transaction
    let sarah_balance = sarah_creditor_sum - sarah_debitor_sum;

    // GroceryCo's balance change (producer)
    let grocery_creditor_sum = ground_coffee.mag + pasta.mag + paper_towels.mag;
    let grocery_debitor_sum = 0.0; // no debits in this transaction
    let grocery_balance = grocery_creditor_sum - grocery_debitor_sum;

    // StateOfCalifornia's balance change (tax recipient)
    let state_creditor_sum = sales_tax.mag;
    let state_debitor_sum = 0.0; // no debits in this transaction
    let state_balance = state_creditor_sum - state_debitor_sum;

    // prove conservation law: sum of all balances must equal zero
    let total_balance: f64 = sarah_balance + grocery_balance + state_balance;
    assert!(
        total_balance.abs() < 1e-10,
        "Transaction must conserve value"
    );

    // now prove causality - value must be produced before consumed
    // model the transaction as a spacetime event
    let create_spacetime_event = |amount: f64,
                                  creditor_angle: f64,
                                  debitor_angle: f64,
                                  cred_time: f64,
                                  deb_time: f64|
     -> Geonum {
        // enforce causality: creditor_time must be <= debitor_time
        assert!(cred_time <= deb_time, "Production must precede consumption");

        // encode the time difference as magnitude scaling
        // longer time differences result in stronger causal separation
        let time_difference = deb_time - cred_time;

        // create a geometric number that encodes both the spatial (angle) and
        // temporal (time difference) aspects of the transaction
        let transaction_angle = (creditor_angle - debitor_angle) % TAU;
        Geonum::new_with_blade(
            amount * (1.0 + time_difference), // magnitude increases with time delay
            2,                 // bivector (grade 2) representing the transaction plane
            transaction_angle, // spatial orientation
            PI,
        )
    };

    // create spacetime events for each transaction item
    let _coffee_event = create_spacetime_event(
        ground_coffee.mag,
        food_service_angle,
        software_engineer_angle,
        creditor_time,
        debitor_time,
    );

    // attempt a transaction where consumption precedes production
    // this triggers the assertion in create_spacetime_event
    let _impossible_transaction = |amount: f64| {
        let invalid_creditor_time = 3.0; // later than debitor time
        let invalid_debitor_time = 2.0; // earlier than creditor time

        // this fails because it violates causality
        create_spacetime_event(
            amount,
            food_service_angle,
            software_engineer_angle,
            invalid_creditor_time,
            invalid_debitor_time,
        )
    };

    // comment out the following line as it would panic due to violated causality
    // let impossible_event = impossible_transaction(5.0);

    // demonstrate geometric analysis of transaction flows in the bivector space

    // combine all transaction items into a single economic flow
    let economic_flow = [
        coffee_bivector,
        pasta_bivector,
        towels_bivector,
        tax_bivector,
    ]
    .iter()
    .fold(Geonum::new_with_blade(0.0, 2, 0.0, 1.0), |acc, bivector| {
        // geometric sum preserving directional information
        let new_length = acc.mag + bivector.mag;

        if new_length == 0.0 {
            return acc; // avoid division by zero
        }

        // weighted average of angles
        let acc_angle = acc.angle.grade_angle();
        let biv_angle = bivector.angle.grade_angle();
        let new_angle = if acc.mag > 0.0 {
            (acc_angle * acc.mag + biv_angle * bivector.mag) / new_length
        } else {
            biv_angle
        };

        // bivector (grade 2) - transactions are modeled as bivectors (oriented planes)
        // in geometric algebra, a bivector represents an oriented area element
        // for transactions, this encodes the economic plane between two accounts
        // and preserves the directed financial relationship between entities
        Geonum::new_with_blade(new_length, 2, new_angle, PI)
    });

    // prove the combined flow has the expected magnitude
    assert_eq!(
        economic_flow.mag,
        ground_coffee.mag + pasta.mag + paper_towels.mag + sales_tax.mag
    );

    // prove constant-time calculation regardless of transaction count
    let start = Instant::now();
    let _ = [
        coffee_bivector,
        pasta_bivector,
        towels_bivector,
        tax_bivector,
    ]
    .iter()
    .fold(Geonum::new_with_blade(0.0, 2, 0.0, 1.0), |acc, bivector| {
        let new_length = acc.mag + bivector.mag;

        if new_length == 0.0 {
            return acc;
        }

        let acc_angle = acc.angle.grade_angle();
        let biv_angle = bivector.angle.grade_angle();
        let new_angle = if acc.mag > 0.0 {
            (acc_angle * acc.mag + biv_angle * bivector.mag) / new_length
        } else {
            biv_angle
        };

        // bivector (grade 2) representing the combined transaction plane
        Geonum::new_with_blade(new_length, 2, new_angle, PI)
    });
    let duration = start.elapsed();

    // prove constant time complexity regardless of transaction count
    assert!(
        duration.as_nanos() < 100000, // increased threshold for test stability
        "bivector operations have O(1) complexity"
    );

    println!(
        "causal transaction analysis: {:.2} nanoseconds",
        duration.as_nanos()
    );
    println!(
        "transaction net flow angle: {:.4}",
        economic_flow.angle.grade_angle()
    );
    println!("conservation check: {total_balance:.10}");
}

#[test]
fn it_models_investment_network_resilience() {
    // in a bivector transaction system, "systemic risk" fundamentally transforms

    // key insight: without leveraged money creation, financial failures propagate only
    // along direct investment chains rather than causing system-wide contagion
    // this directly derives from the conservation law of bivector transactions

    // investment network: who funded whom with direct value transfers
    // each investment is a bivector with causal history preserving direction of value flow
    let investments = vec![
        (0, 1, Geonum::new(50.0, 1.0 + 0.5, 1.0)), // investor 0 → company 1: $50B (bivector - investment represents relationship)
        (1, 2, Geonum::new(40.0, 1.0 + 0.4, 1.0)), // company 1 → company 2: $40B (bivector - transaction between companies)
        (2, 3, Geonum::new(30.0, 1.0 + 1.0 / 3.0, 1.0)), // company 2 → company 3: $30B
    ];

    // account balances in system - conserved across all transactions
    // sum of all balances equals zero per bivector conservation law
    let balances = vec![
        Geonum::new(950.0, 0.1 * 2.0 / PI, 2.0), // investor 0: $950B remaining (scalar value for account balance)
        Geonum::new(10.0, 0.05 * 2.0 / PI, 2.0), // company 1: $10B (scalar value for account balance)
        Geonum::new(10.0, 0.2 * 2.0 / PI, 2.0), // company 2: $10B (scalar value for account balance)
        Geonum::new(30.0, 0.15 * 2.0 / PI, 2.0), // company 3: $30B (received from company 2)
    ];

    // default event at node 2
    let default_node = 2;
    let recovery_rate = 0.4; // 40% recovery rate on default

    // trace impact of default through direct investment chain
    // no systemic risk - just direct investment loss propagation
    // use a non-recursive approach to handle default propagation
    let trace_default_impact = |network: &[(usize, usize, Geonum)],
                                acct_balances: &mut [Geonum],
                                default_idx: usize,
                                recovery: f64| {
        // create a queue of defaulting entities to process
        let mut default_queue = vec![default_idx];

        // process defaults until queue is empty
        while let Some(current_default) = default_queue.pop() {
            // find all direct investors in the current defaulted entity
            let investors: Vec<(usize, Geonum)> = network
                .iter()
                .filter(|(_, to, _)| *to == current_default)
                .map(|(from, _, amount)| (*from, *amount))
                .collect();

            // calculate losses for each investor
            for (investor_idx, investment) in investors {
                let loss_amount = investment.mag * (1.0 - recovery);

                // apply loss to investor balance
                acct_balances[investor_idx] = Geonum::new_with_angle(
                    acct_balances[investor_idx].mag - loss_amount,
                    acct_balances[investor_idx].angle,
                );

                // check if this investor now defaults and add to queue if so
                if acct_balances[investor_idx].mag < 0.0 && !default_queue.contains(&investor_idx) {
                    default_queue.push(investor_idx);
                }
            }
        }
    };

    // analyze network stability - completely different from traditional systemic risk
    let compute_network_impact = |original: &[Geonum], after_default: &[Geonum]| -> Geonum {
        // total value in system before default
        let initial_value: f64 = original.iter().map(|b| b.mag).sum();

        // total value in system after default
        let final_value: f64 = after_default.iter().map(|b| b.mag).sum();

        // compute system impact - limited to direct investment chains
        let loss_ratio = (initial_value - final_value) / initial_value;

        // compute directional impact
        let avg_angle_before = original
            .iter()
            .map(|b| b.angle.grade_angle() * b.mag)
            .sum::<f64>()
            / initial_value;

        let avg_angle_after = after_default
            .iter()
            .filter(|b| b.mag > 0.0) // exclude defaulted entities
            .map(|b| b.angle.grade_angle() * b.mag)
            .sum::<f64>()
            / final_value;

        let angle_shift = avg_angle_after - avg_angle_before;

        // return geometric impact
        Geonum::new_with_blade(loss_ratio, 1, angle_shift, PI)
    };

    // copy balances for simulation
    let mut post_default_balances = balances.clone();

    // measure simulation time
    let start = Instant::now();

    // trace default through network
    trace_default_impact(
        &investments,
        &mut post_default_balances,
        default_node,
        recovery_rate,
    );
    let impact = compute_network_impact(&balances, &post_default_balances);

    let duration = start.elapsed();

    // key insight: theres no systemic risk - only direct investment chain impacts
    // the same money cant be in two places at once, eliminating leveraged contagion

    println!("network loss: {:.2}%", impact.mag * 100.0);
    println!(
        "investment direction shift: {:.4}",
        impact.angle.grade_angle()
    );
    println!("computation time: {:.2} nanoseconds", duration.as_nanos());

    // test O(1) complexity
    assert!(
        duration.as_nanos() < 100000, // increased threshold for test stability
        "investment network analysis runs with O(1) complexity"
    );

    // test that impact is contained to direct investment chains
    // this fundamentally transforms traditional "systemic risk"
    assert!(
        impact.mag < 0.1, // loss is limited to direct investment chains
        "in bivector economy, defaults impact only direct investment chains"
    );
}

#[test]
fn it_measures_the_cost_of_capital_without_a_federal_reserve_board() {
    // in a bivector economy with conservation laws, capital costs emerge naturally
    // from real business returns rather than central bank manipulation

    // simulate quarterly business account data (revenue - expense) from different sectors
    // this shows how capital costs emerge naturally from physical economic returns
    let business_returns = vec![
        // (business_type, quarter, revenue, expense)
        ("technology", 1, 125.0, 90.0),
        ("technology", 2, 142.0, 95.0),
        ("technology", 3, 156.0, 102.0),
        ("technology", 4, 168.0, 110.0),
        ("manufacturing", 1, 210.0, 185.0),
        ("manufacturing", 2, 215.0, 188.0),
        ("manufacturing", 3, 232.0, 198.0),
        ("manufacturing", 4, 240.0, 205.0),
        ("retail", 1, 310.0, 290.0),
        ("retail", 2, 345.0, 315.0),
        ("retail", 3, 320.0, 295.0),
        ("retail", 4, 380.0, 345.0),
        ("healthcare", 1, 180.0, 150.0),
        ("healthcare", 2, 195.0, 160.0),
        ("healthcare", 3, 205.0, 165.0),
        ("healthcare", 4, 220.0, 175.0),
    ];

    // calculate profit rates by business type (as geometric numbers)
    // angle represents business sector in economic space
    let calculate_sector_profit_rate = |sector: &str, data: &[(String, i32, f64, f64)]| -> Geonum {
        // filter data for this sector
        let sector_data: Vec<_> = data.iter().filter(|(s, _, _, _)| s == sector).collect();

        if sector_data.is_empty() {
            return Geonum::new(0.0, 0.0, 1.0); // scalar zero for empty transaction
        }

        // calculate total revenue and expense
        let total_revenue: f64 = sector_data.iter().map(|(_, _, r, _)| r).sum();
        let total_expense: f64 = sector_data.iter().map(|(_, _, _, e)| e).sum();

        // calculate profit rate (return on investment)
        let profit_rate = if total_expense > 0.0 {
            (total_revenue - total_expense) / total_expense
        } else {
            0.0
        };

        // assign sector angle based on risk profile
        // higher risk sectors have larger angles
        let sector_angle = match sector {
            "technology" => PI / 3.0,    // higher risk
            "retail" => PI / 4.0,        // moderate risk
            "manufacturing" => PI / 6.0, // lower risk
            "healthcare" => PI / 5.0,    // moderate-low risk
            _ => 0.0,
        };

        // return profit rate as geometric number
        // - length is the actual profit rate
        // - angle represents sector position in economic space
        // for blade 2 with specific angle, we need to add the angle to π
        Geonum::new(profit_rate, 1.0 + sector_angle * 2.0 / PI, 1.0) // bivector representing profit-sector relationship
    };

    // calculate natural cost of capital for each business type
    let calculate_cost_of_capital =
        |sector: &str, profit_rates: &[Geonum], risk_premium: f64| -> Geonum {
            // in bivector economy, cost of capital emerges from:
            // 1. geometric mean of profit rates (base cost)
            // 2. plus risk premium specific to sector

            // calculate geometric mean of profit rates
            let mean_length = profit_rates
                .iter()
                .map(|g| g.mag)
                .fold(1.0, |acc, rate| acc * (1.0 + rate))
                .powf(1.0 / profit_rates.len() as f64)
                - 1.0;

            // calculate mean angle (risk level)
            let _mean_angle = profit_rates
                .iter()
                .map(|g| g.angle.grade_angle())
                .sum::<f64>()
                / profit_rates.len() as f64;

            // get sector angle
            let sector_angle = match sector {
                "technology" => PI / 3.0,
                "retail" => PI / 4.0,
                "manufacturing" => PI / 6.0,
                "healthcare" => PI / 5.0,
                _ => 0.0,
            };

            // calculate cost of capital
            // - base is geometric mean of all profit rates
            // - plus sector-specific risk premium
            // - angle represents sector position
            Geonum::new(
                mean_length + risk_premium,
                1.0 + sector_angle * 2.0 / PI,
                1.0,
            )
            // bivector representing risk-adjusted rate
        };

    // convert business data to typed structure for analysis
    let typed_data: Vec<(String, i32, f64, f64)> = business_returns
        .iter()
        .map(|(b, q, r, e)| (b.to_string(), *q, *r, *e))
        .collect();

    // calculate profit rates for each sector
    let tech_profit = calculate_sector_profit_rate("technology", &typed_data);
    let mfg_profit = calculate_sector_profit_rate("manufacturing", &typed_data);
    let retail_profit = calculate_sector_profit_rate("retail", &typed_data);
    let health_profit = calculate_sector_profit_rate("healthcare", &typed_data);

    // collect all profit rates
    let all_profit_rates = vec![tech_profit, mfg_profit, retail_profit, health_profit];

    // measure calculation time
    let start = Instant::now();

    // calculate cost of capital for each sector based on all profit rates
    // with sector-specific risk premiums
    let tech_capital_cost = calculate_cost_of_capital("technology", &all_profit_rates, 0.06);
    let mfg_capital_cost = calculate_cost_of_capital("manufacturing", &all_profit_rates, 0.02);
    let retail_capital_cost = calculate_cost_of_capital("retail", &all_profit_rates, 0.04);
    let health_capital_cost = calculate_cost_of_capital("healthcare", &all_profit_rates, 0.03);

    // measure duration
    let duration = start.elapsed();

    // calculate market-wide natural cost of capital
    // weighted by total investment in each sector
    let tech_investment = typed_data
        .iter()
        .filter(|(s, _, _, _)| s == "technology")
        .map(|(_, _, _, e)| e)
        .sum::<f64>();

    let mfg_investment = typed_data
        .iter()
        .filter(|(s, _, _, _)| s == "manufacturing")
        .map(|(_, _, _, e)| e)
        .sum::<f64>();

    let retail_investment = typed_data
        .iter()
        .filter(|(s, _, _, _)| s == "retail")
        .map(|(_, _, _, e)| e)
        .sum::<f64>();

    let health_investment = typed_data
        .iter()
        .filter(|(s, _, _, _)| s == "healthcare")
        .map(|(_, _, _, e)| e)
        .sum::<f64>();

    let total_investment = tech_investment + mfg_investment + retail_investment + health_investment;

    // weighted average cost of capital across market
    let market_cost = (tech_capital_cost.mag * tech_investment
        + mfg_capital_cost.mag * mfg_investment
        + retail_capital_cost.mag * retail_investment
        + health_capital_cost.mag * health_investment)
        / total_investment;

    // the fundamental advantage: capital costs emerge naturally
    // from actual economic returns rather than central bank manipulation
    println!("Natural cost of capital by sector (no Federal Reserve needed):");
    println!("  Technology:    {:.2}%", tech_capital_cost.mag * 100.0);
    println!("  Manufacturing: {:.2}%", mfg_capital_cost.mag * 100.0);
    println!("  Retail:        {:.2}%", retail_capital_cost.mag * 100.0);
    println!("  Healthcare:    {:.2}%", health_capital_cost.mag * 100.0);
    println!("Market-wide cost of capital: {:.2}%", market_cost * 100.0);
    println!("Calculation time: {:.2} nanoseconds", duration.as_nanos());

    // verify meaningful results
    assert!(
        tech_capital_cost.mag > 0.0,
        "cost of capital should be positive"
    );
    assert!(
        tech_capital_cost.mag > mfg_capital_cost.mag,
        "higher risk sectors should have higher capital costs"
    );

    // verify O(1) complexity
    assert!(
        duration.as_nanos() < 100000, // increased threshold for test stability
        "cost of capital calculation should be O(1) complexity"
    );

    // key insight: without a federal reserve artificially manipulating rates,
    // capital costs naturally emerge from real economic returns with these advantages:
    // 1. rates reflect actual economic value creation
    // 2. distortions from monetary policy are eliminated
    // 3. risk is priced through natural market mechanisms
    // 4. capital allocation becomes vastly more efficient
}
