#![cfg(feature = "inventory")]

use expect_test::expect;
use salsa::{Backtrace, Database, DatabaseImpl};
use test_log::test;

#[salsa::input(debug)]
struct Thing {
    detailed: bool,
}

#[salsa::tracked]
fn query_a(db: &dyn Database, thing: Thing) -> String {
    query_b(db, thing)
}

#[salsa::tracked]
fn query_b(db: &dyn Database, thing: Thing) -> String {
    query_c(db, thing)
}

#[salsa::tracked]
fn query_c(db: &dyn Database, thing: Thing) -> String {
    query_d(db, thing)
}

#[salsa::tracked]
fn query_d(db: &dyn Database, thing: Thing) -> String {
    query_e(db, thing)
}

#[salsa::tracked]
fn query_e(db: &dyn Database, thing: Thing) -> String {
    if thing.detailed(db) {
        format!("{:#}", Backtrace::capture().unwrap())
    } else {
        format!("{}", Backtrace::capture().unwrap())
    }
}
#[salsa::tracked]
fn query_f(db: &dyn Database, thing: Thing) -> String {
    query_cycle(db, thing)
}

#[salsa::tracked(cycle_initial=cycle_initial)]
fn query_cycle(db: &dyn Database, thing: Thing) -> String {
    let backtrace = query_cycle(db, thing);
    if backtrace.is_empty() {
        query_e(db, thing)
    } else {
        backtrace
    }
}

fn cycle_initial(_db: &dyn salsa::Database, _id: salsa::Id, _thing: Thing) -> String {
    String::new()
}

#[test]
fn backtrace_works() {
    let db = DatabaseImpl::default();

    let backtrace = query_a(&db, Thing::new(&db, false)).replace("\\", "/");
    expect![[r#"
        query stacktrace:
           0: query_e(Id(0))
                     at tests/backtrace.rs:32
           1: query_d(Id(0))
                     at tests/backtrace.rs:27
           2: query_c(Id(0))
                     at tests/backtrace.rs:22
           3: query_b(Id(0))
                     at tests/backtrace.rs:17
           4: query_a(Id(0))
                     at tests/backtrace.rs:12
    "#]]
    .assert_eq(&backtrace);

    let backtrace = query_a(&db, Thing::new(&db, true)).replace("\\", "/");
    expect![[r#"
        query stacktrace:
           0: query_e(Id(1)) -> (R1, Durability::LOW)
                     at tests/backtrace.rs:32
           1: query_d(Id(1)) -> (R1, Durability::HIGH)
                     at tests/backtrace.rs:27
           2: query_c(Id(1)) -> (R1, Durability::HIGH)
                     at tests/backtrace.rs:22
           3: query_b(Id(1)) -> (R1, Durability::HIGH)
                     at tests/backtrace.rs:17
           4: query_a(Id(1)) -> (R1, Durability::HIGH)
                     at tests/backtrace.rs:12
    "#]]
    .assert_eq(&backtrace);

    let backtrace = query_f(&db, Thing::new(&db, false)).replace("\\", "/");
    expect![[r#"
        query stacktrace:
           0: query_e(Id(2))
                     at tests/backtrace.rs:32
           1: query_cycle(Id(2))
                     at tests/backtrace.rs:45
                     cycle heads: query_cycle(Id(2)) -> iteration = 0
           2: query_f(Id(2))
                     at tests/backtrace.rs:40
    "#]]
    .assert_eq(&backtrace);

    let backtrace = query_f(&db, Thing::new(&db, true)).replace("\\", "/");
    expect![[r#"
        query stacktrace:
           0: query_e(Id(3)) -> (R1, Durability::LOW)
                     at tests/backtrace.rs:32
           1: query_cycle(Id(3)) -> (R1, Durability::HIGH, iteration = 0)
                     at tests/backtrace.rs:45
                     cycle heads: query_cycle(Id(3)) -> iteration = 0
           2: query_f(Id(3)) -> (R1, Durability::HIGH)
                     at tests/backtrace.rs:40
    "#]]
    .assert_eq(&backtrace);
}
