#include "first-run-wizard-sequence.hpp"
#include "first-run-wizard-helpers.hpp"

#include "layout-helpers.hpp"
#include "log-helper.hpp"
#include "macro-settings.hpp"
#include "selection-helpers.hpp"

#include <obs-data.h>

#include <QFrame>
#include <QHBoxLayout>
#include <QMessageBox>
#include <QScrollArea>
#include <QVBoxLayout>

namespace advss {

namespace wiz {

// Builds the obs_data blob for a "current scene == sceneName for at least
// triggerDuration" condition, matching MacroConditionScene::Save().
//
// {
//   "segmentSettings": { "enabled": true, "version": 2 },
//   "id": "scene",
//   "logic": 0,
//   "durationModifier": {
//     "time_constraint": 1,       // AT_LEAST
//     "seconds": <Duration::Save output>
//   },
//   "sceneSelection": { "type": 0, "name": "<scene>", "canvasSelection": "Main" },
//   "type": 10,                   // CURRENT_SCENE
//   "version": 1
// }
static OBSDataAutoRelease
buildSceneConditionData(const QString &scene, const Duration &triggerDuration)
{
	OBSDataAutoRelease seg = obs_data_create();
	obs_data_set_bool(seg, "enabled", true);
	obs_data_set_int(seg, "version", 2);

	OBSDataAutoRelease durMod = obs_data_create();
	obs_data_set_int(durMod, "time_constraint", 1);
	triggerDuration.Save(durMod, "seconds");

	OBSDataAutoRelease sceneSel = obs_data_create();
	obs_data_set_int(sceneSel, "type", 0);
	obs_data_set_string(sceneSel, "name", scene.toUtf8().constData());
	obs_data_set_string(sceneSel, "canvasSelection", "Main");

	OBSDataAutoRelease data = obs_data_create();
	obs_data_set_obj(data, "segmentSettings", seg);
	obs_data_set_string(data, "id", "scene");
	obs_data_set_int(data, "logic", 0);
	obs_data_set_obj(data, "durationModifier", durMod);
	obs_data_set_obj(data, "sceneSelection", sceneSel);
	obs_data_set_int(data, "type", 10);
	obs_data_set_int(data, "version", 1);

	return data;
}

// Builds the obs_data blob for a scene-switch action,
// matching MacroActionSwitchScene::Save().
//
// {
//   "segmentSettings": { "enabled": true, "version": 2 },
//   "id": "scene_switch",
//   "action": 0,
//   "sceneSelection": { "type": 0, "name": "<scene>", "canvasSelection": "Main" },
//   "transitionType": 1,           // scene's default transition
//   "blockUntilTransitionDone": true,
//   "sceneType": 0
// }
static OBSDataAutoRelease buildSceneSwitchData(const QString &scene)
{
	OBSDataAutoRelease seg = obs_data_create();
	obs_data_set_bool(seg, "enabled", true);
	obs_data_set_int(seg, "version", 2);

	OBSDataAutoRelease sceneSel = obs_data_create();
	obs_data_set_int(sceneSel, "type", 0);
	obs_data_set_string(sceneSel, "name", scene.toUtf8().constData());
	obs_data_set_string(sceneSel, "canvasSelection", "Main");

	OBSDataAutoRelease data = obs_data_create();
	obs_data_set_obj(data, "segmentSettings", seg);
	obs_data_set_string(data, "id", "scene_switch");
	obs_data_set_int(data, "action", 0);
	obs_data_set_obj(data, "sceneSelection", sceneSel);
	obs_data_set_int(data, "transitionType", 1);
	obs_data_set_bool(data, "blockUntilTransitionDone", true);
	obs_data_set_int(data, "sceneType", 0);

	return data;
}

// Builds the obs_data blob for a wait action, matching MacroActionWait::Save().
//
// {
//   "segmentSettings": { "enabled": true, "version": 2 },
//   "id": "wait",
//   "duration": <Duration::Save output>,
//   "waitType": 0,
//   "version": 1
// }
static OBSDataAutoRelease buildWaitData(const Duration &duration)
{
	OBSDataAutoRelease seg = obs_data_create();
	obs_data_set_bool(seg, "enabled", true);
	obs_data_set_int(seg, "version", 2);

	OBSDataAutoRelease data = obs_data_create();
	obs_data_set_obj(data, "segmentSettings", seg);
	obs_data_set_string(data, "id", "wait");
	duration.Save(data, "duration");
	obs_data_set_int(data, "waitType", 0);
	obs_data_set_int(data, "version", 1);

	return data;
}

// ===========================================================================
// SequenceTriggerPage
// ===========================================================================

SequenceTriggerPage::SequenceTriggerPage(QWidget *parent)
	: QWizardPage(parent),
	  _sceneCombo(new QComboBox(this)),
	  _delaySelection(new DurationSelection(this, true, 0.0))
{
	setTitle(obs_module_text("FirstRunWizard.seqTrigger.title"));
	setSubTitle(obs_module_text("FirstRunWizard.seqTrigger.subtitle"));

	_sceneCombo->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
	_delaySelection->SetDuration(Duration(5.0));

	registerField("seqTriggerScene*", _sceneCombo, "currentText",
		      SIGNAL(currentTextChanged(QString)));

	connect(_sceneCombo, &QComboBox::currentTextChanged, this,
		&QWizardPage::completeChanged);

	auto *sceneRow = new QHBoxLayout;
	PlaceWidgets(obs_module_text("FirstRunWizard.seqTrigger.scene"),
		     sceneRow, {{"{{scene}}", _sceneCombo}}, false);

	auto *delayRow = new QHBoxLayout;
	PlaceWidgets(obs_module_text("FirstRunWizard.seqTrigger.delay"),
		     delayRow, {{"{{duration}}", _delaySelection}}, false);

	auto *layout = new QVBoxLayout(this);
	layout->addLayout(sceneRow);
	layout->addLayout(delayRow);
	layout->addStretch();
}

void SequenceTriggerPage::initializePage()
{
	_sceneCombo->clear();
	for (const QString &name : GetSceneNames()) {
		_sceneCombo->addItem(name);
	}
}

bool SequenceTriggerPage::isComplete() const
{
	return _sceneCombo->count() > 0 &&
	       !_sceneCombo->currentText().isEmpty();
}

Duration SequenceTriggerPage::GetTriggerDuration() const
{
	return _delaySelection->GetDuration();
}

// ===========================================================================
// SequenceScenesPage
// ===========================================================================

SequenceScenesPage::SequenceScenesPage(QWidget *parent)
	: QWizardPage(parent),
	  _triggerInfoLabel(new QLabel(this)),
	  _stepsContainer(new QWidget(this)),
	  _stepsLayout(new QVBoxLayout(_stepsContainer))
{
	setTitle(obs_module_text("FirstRunWizard.seqScenes.title"));
	setSubTitle(obs_module_text("FirstRunWizard.seqScenes.subtitle"));

	_triggerInfoLabel->setWordWrap(true);
	_triggerInfoLabel->setFrameShape(QFrame::StyledPanel);
	_triggerInfoLabel->setContentsMargins(8, 4, 8, 4);

	_stepsLayout->setContentsMargins(0, 0, 0, 0);
	_stepsLayout->setSpacing(4);

	auto *scrollArea = new QScrollArea(this);
	scrollArea->setWidget(_stepsContainer);
	scrollArea->setWidgetResizable(true);
	scrollArea->setFrameShape(QFrame::NoFrame);

	auto *addBtn = new QPushButton(
		obs_module_text("FirstRunWizard.seqScenes.addScene"), this);
	connect(addBtn, &QPushButton::clicked, this,
		&SequenceScenesPage::onAddStepClicked);

	auto *layout = new QVBoxLayout(this);
	layout->addWidget(_triggerInfoLabel);
	layout->addWidget(scrollArea, 1);
	layout->addWidget(addBtn, 0, Qt::AlignLeft);
}

void SequenceScenesPage::initializePage()
{
	const QString triggerScene = field("seqTriggerScene").toString();
	auto *triggerPage = qobject_cast<SequenceTriggerPage *>(
		wizard()->page(PAGE_SEQ_TRIGGER));
	const Duration triggerDuration =
		triggerPage ? triggerPage->GetTriggerDuration() : Duration(5.0);
	_triggerInfoLabel->setText(
		QString(obs_module_text("FirstRunWizard.seqScenes.triggerInfo"))
			.arg(triggerScene)
			.arg(QString::fromStdString(
				triggerDuration.ToString())));

	if (_initialized) {
		return;
	}
	_initialized = true;

	// Pre-select the scenes after the trigger scene so the user doesn't
	// have to start by deselecting it manually.
	const QStringList scenes = GetSceneNames();
	const int count = static_cast<int>(scenes.size());
	const int triggerIdx = scenes.indexOf(triggerScene);
	const int firstIdx = count > 0 ? (triggerIdx + 1) % count : 0;
	const int secondIdx = count > 0 ? (triggerIdx + 2) % count : 0;
	AddStep(firstIdx);
	AddStep(secondIdx);
}

bool SequenceScenesPage::isComplete() const
{
	return _rows.size() >= 2;
}

QVector<QPair<QString, Duration>> SequenceScenesPage::GetSteps() const
{
	QVector<QPair<QString, Duration>> steps;
	steps.reserve(_rows.size());
	for (int i = 0; i < _rows.size(); ++i) {
		const Duration delay = (i < _rows.size() - 1)
					       ? _rows[i].delay->GetDuration()
					       : Duration();
		steps.append({_rows[i].scene->currentText(), delay});
	}
	return steps;
}

void SequenceScenesPage::onAddStepClicked()
{
	const QStringList scenes = GetSceneNames();
	const int count = static_cast<int>(scenes.size());
	int nextIdx = 0;
	if (!_rows.isEmpty() && count > 0) {
		const int lastIdx = _rows.last().scene->currentIndex();
		nextIdx = (lastIdx + 1) % count;
	}
	AddStep(nextIdx);
	emit completeChanged();
}

void SequenceScenesPage::AddStep(int defaultSceneIndex)
{
	auto *row = new QWidget(_stepsContainer);
	auto *rowLayout = new QHBoxLayout(row);
	rowLayout->setContentsMargins(0, 0, 0, 0);

	auto *sceneCombo = new QComboBox(row);
	sceneCombo->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
	const QStringList scenes = GetSceneNames();
	for (const QString &name : scenes) {
		sceneCombo->addItem(name);
	}
	if (defaultSceneIndex >= 0 && defaultSceneIndex < scenes.size()) {
		sceneCombo->setCurrentIndex(defaultSceneIndex);
	}
	PlaceWidgets(obs_module_text("FirstRunWizard.seqScenes.switchTo"),
		     rowLayout, {{"{{scene}}", sceneCombo}}, false);

	auto *delayWidget = new QWidget(row);
	auto *delayLayout = new QHBoxLayout(delayWidget);
	delayLayout->setContentsMargins(0, 0, 0, 0);
	auto *durationSel = new DurationSelection(delayWidget, true, 0.1);
	durationSel->SetDuration(Duration(5.0));
	PlaceWidgets(obs_module_text("FirstRunWizard.seqScenes.thenWait"),
		     delayLayout, {{"{{duration}}", durationSel}}, false);
	rowLayout->addWidget(delayWidget);

	auto *removeBtn = new QPushButton(row);
	removeBtn->setProperty("themeID",
			       QVariant(QString::fromUtf8("removeIconSmall")));
	removeBtn->setProperty("class",
			       QVariant(QString::fromUtf8("icon-trash")));
	removeBtn->setToolTip(
		obs_module_text("FirstRunWizard.seqScenes.removeTooltip"));
	removeBtn->setMaximumSize(22, 22);
	rowLayout->addWidget(removeBtn);

	_rows.append({row, sceneCombo, delayWidget, durationSel, removeBtn});
	_stepsLayout->addWidget(row);

	UpdateDelayVisibility();
	UpdateRemoveButtons();

	connect(removeBtn, &QPushButton::clicked, this,
		[this, row]() { RemoveStep(row); });
}

void SequenceScenesPage::RemoveStep(QWidget *rowWidget)
{
	int idx = -1;
	for (int i = 0; i < _rows.size(); ++i) {
		if (_rows[i].row == rowWidget) {
			idx = i;
			break;
		}
	}
	if (idx < 0) {
		return;
	}

	_stepsLayout->removeWidget(_rows[idx].row);
	_rows[idx].row->deleteLater();
	_rows.removeAt(idx);

	UpdateDelayVisibility();
	UpdateRemoveButtons();
	emit completeChanged();
}

void SequenceScenesPage::UpdateDelayVisibility()
{
	for (int i = 0; i < _rows.size(); ++i) {
		_rows[i].delayWidget->setVisible(i < _rows.size() - 1);
	}
}

void SequenceScenesPage::UpdateRemoveButtons()
{
	const bool canRemove = _rows.size() > 1;
	for (auto &step : _rows) {
		step.remove->setEnabled(canRemove);
	}
}

// ===========================================================================
// SequenceReviewPage
// ===========================================================================

SequenceReviewPage::SequenceReviewPage(QWidget *parent,
				       std::shared_ptr<Macro> &macro)
	: QWizardPage(parent),
	  _summary(new QLabel(this)),
	  _macro(macro)
{
	setTitle(obs_module_text("FirstRunWizard.seqReview.title"));
	setSubTitle(obs_module_text("FirstRunWizard.seqReview.subtitle"));

	detail::setupSummaryLabel(_summary);

	auto *layout = new QVBoxLayout(this);
	layout->addWidget(_summary);
	layout->addStretch();
}

void SequenceReviewPage::initializePage()
{
	const QString triggerScene = field("seqTriggerScene").toString();

	auto *triggerPage = qobject_cast<SequenceTriggerPage *>(
		wizard()->page(PAGE_SEQ_TRIGGER));
	const Duration triggerDuration =
		triggerPage ? triggerPage->GetTriggerDuration() : Duration(5.0);

	auto *seqPage = qobject_cast<SequenceScenesPage *>(
		wizard()->page(PAGE_SEQ_SCENES));
	const auto steps = seqPage ? seqPage->GetSteps()
				   : QVector<QPair<QString, Duration>>{};

	QString html =
		QString("<p>%1</p>")
			.arg(QString(obs_module_text(
					     "FirstRunWizard.seqReview.trigger"))
				     .arg(triggerScene.toHtmlEscaped())
				     .arg(QString::fromStdString(
					     triggerDuration.ToString())));

	html += "<ol>";
	for (int i = 0; i < steps.size(); ++i) {
		const QString scene = steps[i].first.toHtmlEscaped();
		if (i == 0) {
			html += "<li>" + scene + "</li>";
		} else {
			const QString waitStr = QString::fromStdString(
				steps[i - 1].second.ToString());
			html += "<li>" +
				QString(obs_module_text(
						"FirstRunWizard.seqReview.step"))
					.arg(waitStr)
					.arg(scene) +
				"</li>";
		}
	}
	html += "</ol>";

	_summary->setText(html);
}

bool SequenceReviewPage::validatePage()
{
	const QString triggerScene = field("seqTriggerScene").toString();
	const std::string name = ("Sequence: " + triggerScene).toStdString();

	auto *triggerPage = qobject_cast<SequenceTriggerPage *>(
		wizard()->page(PAGE_SEQ_TRIGGER));
	const Duration triggerDuration =
		triggerPage ? triggerPage->GetTriggerDuration() : Duration(5.0);

	auto *seqPage = qobject_cast<SequenceScenesPage *>(
		wizard()->page(PAGE_SEQ_SCENES));
	if (!seqPage) {
		return true;
	}
	const auto steps = seqPage->GetSteps();

	_macro = std::make_shared<Macro>(name, GetGlobalMacroSettings());
	if (!_macro) {
		blog(LOG_WARNING,
		     "FirstRunWizard: sequence macro allocation failed");
		return true;
	}
	_macro->SetRunInParallel(true);

	// --- Condition ---
	OBSDataAutoRelease condData =
		buildSceneConditionData(triggerScene, triggerDuration);
	if (!detail::addCondition(_macro.get(), "scene", condData)) {
		_macro.reset();
		QMessageBox::warning(
			this,
			obs_module_text("FirstRunWizard.seqReview.errorTitle"),
			obs_module_text("FirstRunWizard.seqReview.errorBody"));
		return true;
	}

	// --- Actions: scene_switch interleaved with wait ---
	for (int i = 0; i < steps.size(); ++i) {
		OBSDataAutoRelease switchData =
			buildSceneSwitchData(steps[i].first);
		if (!detail::addAction(_macro.get(), "scene_switch",
				       switchData)) {
			_macro.reset();
			QMessageBox::warning(
				this,
				obs_module_text(
					"FirstRunWizard.seqReview.errorTitle"),
				obs_module_text(
					"FirstRunWizard.seqReview.errorBody"));
			return true;
		}

		const bool isLastStep = (i == steps.size() - 1);
		if (!isLastStep) {
			OBSDataAutoRelease waitData =
				buildWaitData(steps[i].second);
			if (!detail::addAction(_macro.get(), "wait",
					       waitData)) {
				_macro.reset();
				QMessageBox::warning(
					this,
					obs_module_text(
						"FirstRunWizard.seqReview.errorTitle"),
					obs_module_text(
						"FirstRunWizard.seqReview.errorBody"));
				return true;
			}
		}
	}

	blog(LOG_INFO, "FirstRunWizard: created sequence macro '%s'",
	     name.c_str());
	return true;
}

} // namespace wiz

} // namespace advss
