#include "chat.hpp"

#include "config.hpp"
#include "filetool.hpp"
#include "llm.hpp"
#include "llmclient.hpp"
#include "webfetchtool.hpp"

#include <QDebug>

namespace ZShell::llm {

Chat::Chat(QObject* parent)
	: QObject(parent)
	, m_store(new ChatStore(this))
	, m_client(new LlmClient(this)) {
	if (!config::Config::instance()) new config::Config();

	m_store->setLlmClient(m_client);
	m_client->tools()->registerTool(new WebFetchTool(m_client->tools()));
	m_client->tools()->registerTool(new FileReadTool(m_client->tools()));

	const auto* llm = config::Config::instance()->llm();
	m_client->setEndpoint(llm->endpoint());
	m_client->setModel(llm->model());
	m_client->setTemperature(llm->temperature());
	m_client->setToolsEnabled(llm->tools());

	connect(llm, &config::Llm::endpointChanged, this, [this, llm]() {
		m_client->setEndpoint(llm->endpoint());
	});
	connect(llm, &config::Llm::modelChanged, this, [this, llm]() {
		m_client->setModel(llm->model());
	});
	connect(llm, &config::Llm::temperatureChanged, this, [this, llm]() {
		m_client->setTemperature(llm->temperature());
	});
	connect(llm, &config::Llm::toolsChanged, this, [this, llm]() {
		m_client->setToolsEnabled(llm->tools());
	});

	connect(m_client, &LlmClient::busyChanged, this, [this]() {
		// A fresh run supersedes the previous error.
		if (m_client->busy() && !m_lastError.isEmpty()) {
			m_lastError.clear();
			Q_EMIT lastErrorChanged();
		}
		Q_EMIT busyChanged();
	});
	connect(m_client, &LlmClient::endpointChanged, this, &Chat::endpointChanged);
	connect(m_client, &LlmClient::modelChanged, this, &Chat::modelChanged);
	connect(
		m_client,
		&LlmClient::availableModelsChanged,
		this,
		&Chat::availableModelsChanged);
	connect(
		m_client,
		&LlmClient::contextSizeChanged,
		this,
		&Chat::contextSizeChanged);
	connect(
		m_client,
		&LlmClient::streamingChatIdChanged,
		this,
		&Chat::streamingChatIdChanged);
	connect(
		m_client,
		&LlmClient::toolsEnabledChanged,
		this,
		&Chat::toolsEnabledChanged);
	connect(
		m_client,
		&LlmClient::errorOccurred,
		this,
		[this](const QString& message) {
			m_lastError = message;
			Q_EMIT lastErrorChanged();
			Q_EMIT errorOccurred(message);
		});
	connect(
		m_store,
		&ChatStore::sessionRemoved,
		this,
		[this](ChatSession* session) { m_client->sessionRemoved(session); });
	connect(
		m_client,
		&LlmClient::titleSuggested,
		this,
		[this](ChatSession* session, const QString& title) {
			qInfo() << "Chat: applying generated title" << session->id()
					<< title << "(was" << session->title() << ")";
			session->setTitle(title);
			m_store->saveMeta(session);
		});
	connect(
		m_client,
		&LlmClient::iconSuggested,
		this,
		[this](ChatSession* session, const QString& icon) {
			qInfo() << "Chat: applying generated icon" << session->id() << icon
					<< "(was" << session->icon() << ")";
			session->setIcon(icon);
			m_store->saveMeta(session);
		});
}

bool Chat::busy() const {
	return m_client->busy();
}

QString Chat::endpoint() const {
	return m_client->endpoint();
}

QString Chat::model() const {
	return m_client->model();
}

QStringList Chat::availableModels() const {
	return m_client->availableModels();
}

int Chat::contextSize() const {
	return m_client->contextSize();
}

bool Chat::toolsEnabled() const {
	return m_client->toolsEnabled();
}

void Chat::setToolsEnabled(bool value) {
	m_client->setToolsEnabled(value);
	if (auto* config = config::Config::instance())
		config->llm()->set_tools(value);
}

QString Chat::streamingChatId() const {
	return m_client->streamingChatId();
}


Chat* Chat::s_instance = nullptr;

Chat* Chat::create(QQmlEngine*, QJSEngine*) {
	if (!s_instance) s_instance = new Chat();
	return s_instance;
}

void Chat::stop() {
	m_client->stop();
}

void Chat::dismissError() {
	if (m_lastError.isEmpty()) return;
	m_lastError.clear();
	Q_EMIT lastErrorChanged();
}

void Chat::refreshModels() {
	m_client->refreshModels();
}

void Chat::selectModel(const QString& id) {
	if (id.isEmpty()) return;
	m_client->setModel(id);
	if (auto* config = config::Config::instance()) config->llm()->set_model(id);
}

} // namespace ZShell::llm
