#pragma once
// agentty::app::Deps — type-erased handle to the runtime's seams.
//
// AgenttyApp's static methods need access to the Provider, Store, and
// credentials that main() wired up. Rather than templating AgenttyApp on three
// type parameters (which forces every translation unit to know the concrete
// types), we use a tiny vtable-style struct that the per-domain update code
// calls into.
//
// The concrete deps are stored once at startup via install_deps().  Anything
// satisfying the relevant concept can be installed; the concrete type stays
// hidden behind std::function-style erasure.

#include <functional>
#include <optional>
#include <string>
#include <utility>
#include <vector>

#include "agentty/auth/auth.hpp"
#include "agentty/domain/conversation.hpp"
#include "agentty/runtime/msg.hpp"
#include "agentty/provider/provider.hpp"
#include "agentty/store/store.hpp"

namespace agentty::app {

struct Deps {
    // ── Provider seam ────────────────────────────────────────────────────
    std::function<void(provider::Request, provider::EventSink)> stream;

    // ── Store seam (just the calls update.cpp actually makes) ────────────
    std::function<void(const Thread&)>          save_thread;
    // Returns thread *metadata* (empty messages) for the picker. Full
    // bodies are fetched on demand via load_thread.
    std::function<std::vector<Thread>()>        load_threads;
    std::function<std::optional<Thread>(const ThreadId&)> load_thread;
    std::function<store::Settings()>            load_settings;
    std::function<void(const store::Settings&)> save_settings;
    std::function<ThreadId()>                    new_thread_id;
    std::function<std::string(std::string_view)> title_from;

    // ── Auth context (immutable for the session) ─────────────────────────
    auth::AuthHeader auth;
};

[[nodiscard]] const Deps& deps();
void install_deps(Deps d);

// Live-replace just the auth context after install. Used by the in-app
// login modal: when the user finishes signing in, the reducer dispatches
// a Cmd that calls this so the next stream pick up the new bearer
// without restarting the process. Safe to call from the UI thread —
// streams in flight cache the header at request-build time.
void update_auth(auth::AuthHeader auth);

// Convenience: bind a Provider + Store satisfying the concepts.
template <provider::Provider P, store::Store S>
void install(P& p, S& s, auth::AuthHeader auth) {
    install_deps(Deps{
        .stream = [&p](provider::Request req, provider::EventSink sink) {
            p.stream(std::move(req), std::move(sink));
        },
        .save_thread     = [&s](const Thread& t) { s.save_thread(t); },
        .load_threads    = [&s] { return s.load_threads(); },
        .load_thread     = [&s](const ThreadId& id) { return s.load_thread(id); },
        .load_settings   = [&s] { return s.load_settings(); },
        .save_settings   = [&s](const store::Settings& x) { s.save_settings(x); },
        .new_thread_id   = [&s] { return s.new_id(); },
        .title_from      = [&s](std::string_view t) { return s.title_from(t); },
        .auth            = std::move(auth),
    });
}

} // namespace agentty::app
