pub fn benchmark_host_environment() -> &'static LashlangHostEnvironment {
    static SURFACE: OnceLock<LashlangHostEnvironment> = OnceLock::new();
    SURFACE.get_or_init(build_benchmark_host_environment)
}

fn build_benchmark_host_environment() -> LashlangHostEnvironment {
    let mut resources = LashlangHostCatalog::tool_default(["echo", "boom", "missing_tool"]);
    lashlang::add_trigger_resource_operations(&mut resources);
    resources
        .add_trigger_source_constructor(
            ["cron", "Schedule"],
            TypeExpr::Object(vec![
                TypeField {
                    name: "expr".into(),
                    ty: TypeExpr::Str,
                    optional: false,
                },
                TypeField {
                    name: "tz".into(),
                    ty: TypeExpr::Str,
                    optional: true,
                },
            ]),
            lashlang::NamedDataType::object(
                "cron.Tick",
                vec![TypeField {
                    name: "fired_at".into(),
                    ty: TypeExpr::Str,
                    optional: false,
                }],
            )
            .expect("valid cron tick type"),
        )
        .expect("valid cron trigger source");
    resources
        .add_trigger_source_constructor(
            ["ui", "button", "pressed"],
            TypeExpr::Object(Vec::new()),
            lashlang::NamedDataType::object(
                "ui.button.Pressed",
                vec![
                    TypeField {
                        name: "button".into(),
                        ty: TypeExpr::Union(vec![
                            TypeExpr::Enum(vec!["Red".into()]),
                            TypeExpr::Enum(vec!["Blue".into()]),
                        ]),
                        optional: false,
                    },
                    TypeField {
                        name: "message".into(),
                        ty: TypeExpr::Str,
                        optional: false,
                    },
                    TypeField {
                        name: "pressed_at".into(),
                        ty: TypeExpr::Str,
                        optional: false,
                    },
                ],
            )
            .expect("valid button event type"),
        )
        .expect("valid button trigger source");
    resources.add_module_operation(
        ["shell"],
        "Shell",
        "exec",
        "exec_command",
        TypeExpr::Any,
        TypeExpr::Any,
    );
    resources.add_module_operation(
        ["llm"],
        "Llm",
        "query",
        "llm_query",
        TypeExpr::Any,
        TypeExpr::Any,
    );
    resources.add_module_operation(
        ["agents"],
        "Agents",
        "spawn",
        "spawn_agent",
        TypeExpr::Any,
        TypeExpr::Any,
    );
    resources.add_module_operation(
        ["processes"],
        "Processes",
        "list",
        "list_process_handles",
        TypeExpr::Any,
        TypeExpr::Any,
    );
    resources.add_module_operation(
        ["control"],
        "Control",
        "continue_as",
        "continue_as",
        TypeExpr::Any,
        TypeExpr::Any,
    );
    LashlangHostEnvironment::new(resources, LashlangAbilities::all())
}

pub fn linked_benchmark_program(source: &str) -> LinkedModule {
    LinkedModule::link(
        lashlang::parse(source).expect("benchmark program should parse"),
        benchmark_host_environment(),
    )
    .expect("benchmark program should link")
}

pub fn projected_bindings(scenario: Scenario) -> ProjectedBindings {
    let mut bindings = ProjectedBindings::new();
    if !matches!(
        scenario,
        Scenario::ProjectedValues
            | Scenario::ProjectedOperations
            | Scenario::ContinueAsSeedHostEnvironment
    ) {
        return bindings;
    }
    match scenario {
        Scenario::ProjectedValues => {
            bindings.insert(
                "history",
                ProjectedValue::custom("history", Arc::new(ProjectedList::history())),
            );
            bindings.insert(
                "docs",
                ProjectedValue::scalar("docs", projected_docs_record()),
            );
        }
        Scenario::ProjectedOperations | Scenario::ContinueAsSeedHostEnvironment => {
            bindings.insert(
                "proj",
                ProjectedValue::scalar("proj", projected_operations_record()),
            );
        }
        _ => {}
    }
    bindings
}
