Pith - wedge
#include "WedgeCommands.h"
#include "WedgeCrypto.h"
#include "WedgeHeader.h"
#include "WedgeNote.h"
#include "WedgeSecurity.h"
#include "WedgeSettings.h"
#include "WedgeSync.h"
#include <algorithm>
#include <QContextMenuEvent>
#include <QCoreApplication>
#include <QDateTime>
#include <QDesktopServices>
#include <QDialog>
#include <QGraphicsOpacityEffect>
#include <QGuiApplication>
#include <QHBoxLayout>
#include <QIcon>
#include <QKeyEvent>
#include <QKeySequence>
#include <QLabel>
#include <QMenu>
#include <QPainter>
#include <QPalette>
#include <QPixmap>
#include <QProcess>
#include <QPushButton>
#include <QRegularExpression>
#include <QResizeEvent>
#include <QScrollBar>
#include <QSizeGrip>
#include <QSvgRenderer>
#include <QTextBlock>
#include <QTextCursor>
#include <QTextDocument>
#include <QTimer>
#include <QUrl>
#include <QVBoxLayout>

static constexpr int kIconPx = 18;

static QIcon tintedIcon(const QString &path, const QColor &color) {
	QSvgRenderer renderer(path);
	if (!renderer.isValid()) return QIcon();

	const qreal dpr = qGuiApp ? qGuiApp->devicePixelRatio() : 1.0;
	QPixmap pm(QSize(kIconPx, kIconPx) * dpr);
	pm.setDevicePixelRatio(dpr);
	pm.fill(Qt::transparent);

	QPainter p(&pm);
	p.setRenderHint(QPainter::Antialiasing, true);
	renderer.render(&p, QRectF(0, 0, kIconPx, kIconPx));
	p.setCompositionMode(QPainter::CompositionMode_SourceIn);
	p.fillRect(QRectF(0, 0, kIconPx, kIconPx), color);
	p.end();

	return QIcon(pm);
}

static void blockSpan(const QTextCursor &c, QTextBlock &first, QTextBlock &last) {
	const QTextDocument *doc = c.document();
	first = doc->findBlock(c.selectionStart());
	last = doc->findBlock(c.selectionEnd());
	if (c.hasSelection() && last != first && c.selectionEnd() == last.position())
		last = last.previous();
}

// re-number list
static void relist(const QTextBlock &b) {
	static const QRegularExpression re("^(\\s*)(\\d+)\\.\\s");
	const QRegularExpressionMatch head = re.match(b.text());
	if (!head.hasMatch()) return;
	const QString ws = head.captured(1);
	auto item = [&ws](const QTextBlock &x) {
		const QRegularExpressionMatch m = re.match(x.text());
		return (m.hasMatch() && m.captured(1) == ws) ? m : QRegularExpressionMatch();
	};

	QTextBlock a = b;
	while (item(a.previous()).hasMatch()) a = a.previous();
	int base = item(a).captured(2).toInt();
	QTextBlock z = a;
	for (QTextBlock x = a.next(); item(x).hasMatch(); x = x.next()) {
		base = qMin(base, item(x).captured(2).toInt());
		z = x;
	}

	int n = base;
	for (QTextBlock x = a;; x = x.next()) {
		const QRegularExpressionMatch m = item(x);
		const QString num = QString::number(n++);
		if (m.captured(2) != num) {
			QTextCursor c(x);
			c.setPosition(x.position() + m.capturedStart(2));
			c.setPosition(x.position() + m.capturedEnd(2), QTextCursor::KeepAnchor);
			c.insertText(num);
		}
		if (x == z) break;
	}
}

static QString urlAt(const QTextCursor &c) {
	static const QRegularExpression urlRegex(
		"(?:https?://|www\\.)[^\\s<>\"']*[^\\s<>\"'.,;:!?*~)\\]]");
	const int pos = c.positionInBlock();
	QRegularExpressionMatchIterator it = urlRegex.globalMatch(c.block().text());
	while (it.hasNext()) {
		const QRegularExpressionMatch m = it.next();
		if (pos >= m.capturedStart() && pos <= m.capturedEnd())
			return m.captured();
	}
	return QString();
}

static bool headerDateOf(const QString &line, const QString &fmt,
	const QRegularExpression &loose, QDate &out) {
	const QRegularExpressionMatch m = loose.match(line);
	if (!m.hasMatch()) return false;
	bool ok = false;
	const qint64 otc = m.captured("otc").toLongLong(&ok);
	if (!ok || otc < 1 || otc > 366) return false;
	const QDate base = WedgeHeader::otcStartFor(QDate::currentDate());
	for (int dy : {0, -1, 1}) {
		const QDate d = base.addYears(dy).addDays(otc - 1);
		if (WedgeHeader::dateRegex(fmt, d).match(line).hasMatch()) {
			out = d;
			return true;
		}
	}
	return false;
}

NoteWindow::NoteWindow(QWidget *parent) : QWidget(parent) {
	setWindowFlags(Qt::FramelessWindowHint);
	setWindowTitle("wedge");

	networkManager = new NetworkManager(this);
	connect(networkManager, &NetworkManager::contentReceived, this, &NoteWindow::handleIncomingContent);
	networkManager->startListener();

	auto *backupTimer = new QTimer(this);
	connect(backupTimer, &QTimer::timeout, this, []() { ConfigManager::backupNote(); });
	ConfigManager::backupNote();
backupTimer->start(3600000); // 1 hour

setupUi();
loadData();
installEventFilter(this);

resize(currentData.width, currentData.height);
move(currentData.x, currentData.y);

updateAppearance();
isInitialized = true;
}

void NoteWindow::setupUi() {
	auto *layout = new QVBoxLayout(this);
	layout->setContentsMargins(8, 8, 0, 8); 
	layout->setSpacing(5);

	auto *toolbar = new QHBoxLayout();
	toolbar->setContentsMargins(0, 0, 8, 0); 

	topBtn = new QPushButton();
	cryptoBtn = new QPushButton();
	syncBtn = new QPushButton();
	settingsBtn = new QPushButton();
	closeBtn = new QPushButton();

	for (QPushButton *b : {topBtn, cryptoBtn, syncBtn, settingsBtn, closeBtn}) {
		b->setFlat(true);
		b->setStyleSheet("QPushButton { border: none; background: transparent; }");
		b->setIconSize(QSize(kIconPx, kIconPx));
		b->setFixedSize(kIconPx, kIconPx);
		b->setCursor(Qt::PointingHandCursor);
		auto *fx = new QGraphicsOpacityEffect(b);
		fx->setOpacity(0.5);
		b->setGraphicsEffect(fx);
	}
	applyToolbarIcons();

	toolbar->addStretch();
	toolbar->addWidget(topBtn);
	toolbar->addSpacing(10);
	toolbar->addWidget(syncBtn);
	toolbar->addSpacing(10);
	toolbar->addWidget(cryptoBtn);
	toolbar->addSpacing(10);
	toolbar->addWidget(settingsBtn);
	toolbar->addSpacing(10);
	toolbar->addWidget(closeBtn);
	layout->addLayout(toolbar);

	editor = new QTextEdit();
	editor->setFrameStyle(QFrame::NoFrame);
	editor->setAcceptRichText(false);
	editor->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded);
	editor->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
	editor->setLineWrapMode(QTextEdit::WidgetWidth);

	editor->installEventFilter(this);
	editor->viewport()->installEventFilter(this);
	highlighter = new WedgeHighlighter(editor->document(), currentData.focusColor, currentData.highlightColor, currentData.textColor, currentData.headerFormat);

	editor->verticalScrollBar()->setStyleSheet(
		"QScrollBar:vertical { border: none; background: transparent; width: 8px; }"
		"QScrollBar::handle:vertical { background: rgba(0, 0, 0, 0.4); min-height: 20px; border-radius: 4px; }"
		"QScrollBar::add-line, QScrollBar::sub-line { height: 0px; }"
		);

	layout->addWidget(editor);

	auto *grip = new QSizeGrip(this);
	layout->addWidget(grip, 0, Qt::AlignBottom | Qt::AlignRight);

	connect(topBtn, &QPushButton::clicked, this, [this]() {
		if (editor && editor->verticalScrollBar()) {
			editor->verticalScrollBar()->setValue(0);
		}
	});

	connect(cryptoBtn, &QPushButton::clicked, this, &NoteWindow::toggleEncryption);
	connect(settingsBtn, &QPushButton::clicked, this, &NoteWindow::openSettings);
	connect(syncBtn, &QPushButton::clicked, this, &NoteWindow::openSyncWindow);
	connect(closeBtn, &QPushButton::clicked, this, &NoteWindow::closeApplication);

	connect(editor, &QTextEdit::textChanged, [this]() {
		QString newContent = editor->toPlainText();
		bool contentChanged = (newContent != currentData.content);
		currentData.content = newContent;
		if (isInitialized && contentChanged) {
			currentData.lastModified = QDateTime::currentMSecsSinceEpoch();
			networkManager->noteActivity();
		}
		ConfigManager::saveNote(currentData);
		updateCryptoState();
	});
	connect(networkManager, &NetworkManager::autoSyncDue, this, &NoteWindow::autoMergeTick);
}

bool NoteWindow::eventFilter(QObject *obj, QEvent *event) {
	if (event->type() == QEvent::WindowActivate) {
		QMetaObject::invokeMethod(this, [this]() { refreshDates(); }, Qt::QueuedConnection);
	}

	if (obj == editor->viewport() && event->type() == QEvent::ContextMenu) {
		auto *ce = static_cast<QContextMenuEvent*>(event);
		QMenu *menu = editor->createStandardContextMenu();
		const QColor ic = menu->palette().color(QPalette::Text);
		const QString url = urlAt(editor->cursorForPosition(ce->pos()));
		menu->addSeparator();
		QAction *urlAct = menu->addAction(tintedIcon(":/icons/browser.svg", ic), "Open in Browser");
		urlAct->setEnabled(!url.isEmpty());
		connect(urlAct, &QAction::triggered, this, [url]() {
			QDesktopServices::openUrl(QUrl::fromUserInput(url));
		});
		QAction *dayAct = menu->addAction(tintedIcon(":/icons/header.svg", ic), "Add Next Header");
		connect(dayAct, &QAction::triggered, this, &NoteWindow::insertNextDayHeader);
		QAction *sortAct = menu->addAction(tintedIcon(":/icons/sort.svg", ic), "Sort (A-Z)");
		sortAct->setEnabled(editor->textCursor().hasSelection());
		connect(sortAct, &QAction::triggered, this, &NoteWindow::sortSelection);
		menu->addSeparator();
		QTextBlock spanFirst, spanLast;
		blockSpan(editor->textCursor(), spanFirst, spanLast);
		QAction *upAct = menu->addAction(tintedIcon(":/icons/top.svg", ic), "Move Up");
		upAct->setShortcut(QKeySequence(Qt::CTRL | Qt::SHIFT | Qt::Key_Up));
		upAct->setEnabled(spanFirst.previous().isValid());
		connect(upAct, &QAction::triggered, this, [this]() { moveLines(true); });
		QAction *downAct = menu->addAction(tintedIcon(":/icons/down.svg", ic), "Move Down");
		downAct->setShortcut(QKeySequence(Qt::CTRL | Qt::SHIFT | Qt::Key_Down));
		downAct->setEnabled(spanLast.next().isValid());
		connect(downAct, &QAction::triggered, this, [this]() { moveLines(false); });
		menu->exec(ce->globalPos());
		delete menu;
		return true;
	}

	if (obj == editor && event->type() == QEvent::KeyPress) {
		QKeyEvent *keyEvent = static_cast<QKeyEvent*>(event);
		int key = keyEvent->key();
		const Qt::KeyboardModifiers mods = keyEvent->modifiers()
		& (Qt::ControlModifier | Qt::ShiftModifier | Qt::AltModifier | Qt::MetaModifier);
		if (mods == (Qt::ControlModifier | Qt::ShiftModifier)
			&& (key == Qt::Key_Up || key == Qt::Key_Down)) {
			moveLines(key == Qt::Key_Up);
		return true;
	}
	if (key == Qt::Key_Return || key == Qt::Key_Enter || key == Qt::Key_Space) {
		if (handleCalculation(key)) {
			return true;
		}
	}
}
return QWidget::eventFilter(obj, event);
}

static void notifyAlarm(const QString &time, const QString &text) {
	const QString body = QString("Wedge: %1 %2").arg(time, text.trimmed());
	QProcess::startDetached("notify-send", QStringList() << "Wedge" << body);
}

QString NoteWindow::refreshAlarms(const QString &content) {

// alarm count down

	QDateTime now = QDateTime::currentDateTime();
	int currentMinutes = now.time().hour() * 60 + now.time().minute();

	QDate currentDate = now.date();
	QDate otcStart(currentDate.year(), 3, 20);
	if (currentDate < otcStart) otcStart = otcStart.addYears(-1);
	qint64 todayOtcDay = otcStart.daysTo(currentDate) + 1;

	QStringList lines = content.split('\n');
	qint64 currentHeaderOtcDay = todayOtcDay;
	bool hasHeader = false;

	QRegularExpression headerRegex = WedgeHeader::matchRegex(currentData.headerFormat);
	QRegularExpression alarmRegex("^(\\d{2}):(\\d{2})\\s+(.*?)(?<!\\s)(?:\\((-\\d+(?:\\.\\d+)?|0)?\\)|\\[(-\\d+(?:\\.\\d+)?|0)?\\])$");

	QStringList updatedLines;
	for (const QString &line : lines) {
		QString trimmed = line.trimmed();
		auto headerMatch = headerRegex.match(trimmed);
		if (headerMatch.hasMatch()) {
			currentHeaderOtcDay = headerMatch.captured("otc").toLongLong();
			hasHeader = true;
			updatedLines.append(line);
			continue;
		}

		auto alarmMatch = alarmRegex.match(trimmed);
		if (alarmMatch.hasMatch()) {
			int h = alarmMatch.captured(1).toInt();
			int m = alarmMatch.captured(2).toInt();
			QString desc = alarmMatch.captured(3);
			bool isAlarm = trimmed.endsWith(')');
			QString prevValue = isAlarm ? alarmMatch.captured(4) : alarmMatch.captured(5);

			int alarmMinutes = h * 60 + m;
			qint64 headerDay = hasHeader ? currentHeaderOtcDay : todayOtcDay;

			QString valueInside;
			if (headerDay < todayOtcDay) {
				valueInside = "0";
			} else if (headerDay == todayOtcDay) {
				int diffMinutes = alarmMinutes - currentMinutes;
				if (diffMinutes <= 0) {
					valueInside = "0";
				} else {
					double hours = qMax(0.1, diffMinutes / 60.0);
					valueInside = QString("-%1").arg(hours, 0, 'f', 1);
				}
			} else {
				qint64 diffDays = headerDay - todayOtcDay;
				int diffMinutes = (diffDays * 24 * 60) + alarmMinutes - currentMinutes;
				double hours = qMax(0.1, diffMinutes / 60.0);
				valueInside = QString("-%1").arg(hours, 0, 'f', 1);
			}

			if (isAlarm && valueInside == "0" && !prevValue.isEmpty() && prevValue != "0") {
				notifyAlarm(QString("%1:%2")
					.arg(h, 2, 10, QChar('0'))
					.arg(m, 2, 10, QChar('0')),
					desc);
			}

			int leadingSpacesCount = 0;
			while (leadingSpacesCount < line.length() && line[leadingSpacesCount].isSpace()) {
				leadingSpacesCount++;
			}
			QString leadingSpaces = line.left(leadingSpacesCount);

			updatedLines.append(QString("%1%2:%3  %4%5%6%7")
				.arg(leadingSpaces)
				.arg(h, 2, 10, QChar('0'))
				.arg(m, 2, 10, QChar('0'))
				.arg(desc)
				.arg(QString(isAlarm ? "(" : "["))
				.arg(valueInside)
				.arg(QString(isAlarm ? ")" : "]")));
		} else {
			updatedLines.append(line);
		}
	}
	return updatedLines.join('\n');
}

void NoteWindow::refreshDates() {

// days counter

	if (!editor) return;
	QString content = editor->toPlainText();
	QString newContent = content;

	static QRegularExpression scanRegex("(?<=\\s|^)(\\d{1,2})([a-zA-Z]{3})(\\d{2})?\\((\\d*)\\)=(-?\\d*)");
	QRegularExpressionMatchIterator i = scanRegex.globalMatch(content);
	int offset = 0;
	while (i.hasNext()) {
		QRegularExpressionMatch match = i.next();
		const QString prev = match.captured(5);
		if (prev == "0") continue;
		const int year = match.captured(3).isEmpty() ? -1 : 2000 + match.captured(3).toInt();
		QString res = WedgeCommands::otcDateResult(match.captured(1).toInt(), match.captured(2), year);
		if (!res.isEmpty()) {
			if (!prev.isEmpty() && res.section('=', 1).toLongLong() > prev.toLongLong())
				res = res.section('=', 0, 0) + "=0";
			QString replacement = match.captured(1) + match.captured(2) + match.captured(3) + res;
			newContent.replace(match.capturedStart() + offset, match.capturedLength(), replacement);
			offset += (replacement.length() - match.capturedLength());
		}
	}

	newContent = refreshAlarms(newContent);

	if (newContent == content) return;

	int prefix = 0;
	int minLen = qMin(content.length(), newContent.length());
	while (prefix < minLen && content.at(prefix) == newContent.at(prefix))
		++prefix;

	int suffix = 0;
	while (suffix < (minLen - prefix) &&
		content.at(content.length() - 1 - suffix) ==
		newContent.at(newContent.length() - 1 - suffix))
		++suffix;

	const int oldChangedEnd = content.length() - suffix;
	const int newChangedEnd = newContent.length() - suffix;
	const int delta = newContent.length() - content.length();

	editor->blockSignals(true);

	QScrollBar *vbar = editor->verticalScrollBar();
	int scrollValue = vbar ? vbar->value() : 0;

	QTextCursor userCursor = editor->textCursor();
	int oldPos = userCursor.position();
	int newPos;
	if (oldPos <= prefix)
		newPos = oldPos;
	else if (oldPos >= oldChangedEnd)
		newPos = oldPos + delta;
	else
		newPos = qMin(oldPos, newChangedEnd);

	QTextCursor writeCursor(editor->document());
	writeCursor.beginEditBlock();
	writeCursor.setPosition(prefix);
	writeCursor.setPosition(oldChangedEnd, QTextCursor::KeepAnchor);
	writeCursor.insertText(newContent.mid(prefix, newChangedEnd - prefix));
	writeCursor.endEditBlock();

	userCursor.setPosition(qBound(0, newPos, newContent.length()));
	editor->setTextCursor(userCursor);

	if (vbar) vbar->setValue(scrollValue);

	editor->blockSignals(false);

	currentData.content = newContent;
	ConfigManager::saveNote(currentData);
}

void NoteWindow::insertNextDayHeader() {

// next day header insert

	if (!editor) return;

	const QString fmt = currentData.headerFormat;
	if (fmt.isEmpty()) return;

	const QRegularExpression loose = WedgeHeader::matchRegex(fmt);
	QTextDocument *doc = editor->document();

	QTextBlock lastHeader;
	QDate lastDate;
	for (QTextBlock b = doc->begin(); b.isValid(); b = b.next()) {
		QDate d;
		if (headerDateOf(b.text(), fmt, loose, d)) {
			lastHeader = b;
			lastDate = d;
		}
	}

	QString header;
	QString insert;
	int at = 0;
	int lead = 0;

	if (!lastHeader.isValid()) {
		header = WedgeHeader::render(fmt, QDate::currentDate());
		insert = header + "\n\n";
	} else {
		header = WedgeHeader::render(fmt, lastDate.addDays(1));

		QTextBlock blank = lastHeader.next();
		while (blank.isValid() && !blank.text().trimmed().isEmpty())
			blank = blank.next();

		if (blank.isValid()) {
			const QTextBlock after = blank.next();
			const bool gap = !after.isValid() || !after.text().trimmed().isEmpty();
			at = blank.position() + blank.text().length();
			lead = 1;
			insert = "\n" + header + (gap ? "\n" : "");
		} else {
			const QTextBlock tail = doc->lastBlock();
			at = tail.position() + tail.text().length();
			lead = 2;
			insert = "\n\n" + header + "\n";
		}
	}

	QTextCursor edit(doc);
	edit.beginEditBlock();
	edit.setPosition(at);
	edit.insertText(insert);
	edit.endEditBlock();

	QTextCursor caret = editor->textCursor();
	caret.setPosition(qBound(0, at + lead + static_cast<int>(header.length()), doc->characterCount() - 1));
	editor->setTextCursor(caret);
	editor->ensureCursorVisible();
}

void NoteWindow::moveLines(bool up) {

	if (!editor) return;

	QTextCursor cursor = editor->textCursor();
	QTextBlock first, last;
	blockSpan(cursor, first, last);

	const QTextBlock neighbor = up ? first.previous() : last.next();
	if (!neighbor.isValid()) return;

	QStringList moved;
	for (QTextBlock b = first; b.isValid(); b = b.next()) {
		moved << b.text();
		if (b == last) break;
	}

	const QString block = moved.join('\n');
	const QString other = neighbor.text();
	const QTextBlock tail = up ? last : neighbor;
	const int start = up ? neighbor.position() : first.position();
	const int end = tail.position() + tail.text().length();
	const int shift = (other.length() + 1) * (up ? -1 : 1);
	const int maxPos = editor->document()->characterCount() - 1;
	const int anchor = qBound(0, cursor.anchor() + shift, maxPos);
	const int pos = qBound(0, cursor.position() + shift, maxPos);

	QTextCursor edit(editor->document());
	edit.beginEditBlock();
	edit.setPosition(start);
	edit.setPosition(end, QTextCursor::KeepAnchor);
	edit.insertText(up ? block + "\n" + other : other + "\n" + block);

	cursor.setPosition(anchor);
	cursor.setPosition(pos, QTextCursor::KeepAnchor);
	QTextBlock mb = editor->document()->findBlock(up ? start : start + other.length() + 1);
	for (int k = 0; k < moved.size() && mb.isValid(); ++k, mb = mb.next())
		relist(mb);
	edit.endEditBlock();

	editor->setTextCursor(cursor);
	editor->ensureCursorVisible();
}

void NoteWindow::sortSelection() {
	if (!editor) return;
	QTextCursor cursor = editor->textCursor();
	if (!cursor.hasSelection()) return;

	const QString txt = editor->toPlainText();
	const int selMin = cursor.selectionStart();
	const int selMax = cursor.selectionEnd();

	int start = txt.lastIndexOf('\n', selMin - 1);
	start = (start == -1) ? 0 : start + 1;
	int end = txt.indexOf('\n', selMax);
	if (end == -1) end = txt.length();

	const QStringList lines = txt.mid(start, end - start).split('\n');

	auto indentLen = [](const QString &s) {
		int n = 0;
		while (n < s.length() && s[n].isSpace()) ++n;
		return n;
	};

	int baseIndent = 0;
	for (const QString &l : lines) {
		if (!l.trimmed().isEmpty()) { baseIndent = indentLen(l); break; }
	}

	QList<QStringList> groups;
	for (const QString &line : lines) {
		if (groups.isEmpty() ||
			(!line.trimmed().isEmpty() && indentLen(line) <= baseIndent)) {
			groups.append(QStringList{line});
	} else {
		groups.last().append(line);
	}
}

std::sort(groups.begin(), groups.end(),
	[](const QStringList &a, const QStringList &b) {
		return QString::compare(a.first(), b.first(),
			Qt::CaseInsensitive) < 0;
	});

QStringList flat;
for (const QStringList &g : groups) flat += g;
	const QString sorted = flat.join('\n');

editor->blockSignals(true);
QTextCursor edit(editor->document());
edit.beginEditBlock();
edit.setPosition(start);
edit.setPosition(end, QTextCursor::KeepAnchor);
edit.insertText(sorted);
edit.endEditBlock();

QTextCursor sel = editor->textCursor();
sel.setPosition(start);
sel.setPosition(start + sorted.length(), QTextCursor::KeepAnchor);
editor->setTextCursor(sel);
editor->blockSignals(false);

currentData.content = editor->toPlainText();
currentData.lastModified = QDateTime::currentMSecsSinceEpoch();
ConfigManager::saveNote(currentData);
}

bool NoteWindow::handleCalculation(int key) {
	WedgeCommands::Result r = WedgeCommands::apply(editor, key);
	if (r.changed) {
		currentData.content = editor->toPlainText();
		ConfigManager::saveNote(currentData);
	}
	return r.consumed;
}

void NoteWindow::updateAppearance() {
	Qt::WindowFlags flags = Qt::FramelessWindowHint;
	if (currentData.hideTaskbar) {
		flags |= Qt::Tool;
	} else {
		flags |= Qt::Window;
	}

	if (windowFlags() != flags) {
		setWindowFlags(flags);
		setAttribute(Qt::WA_QuitOnClose, true);
		show(); 
	}

	QPalette pal = palette();
	pal.setColor(QPalette::Window, currentData.bgColor);
	setPalette(pal);
	setAutoFillBackground(true);

	if (editor) {
		QPalette editorPal = editor->palette();
		editorPal.setColor(QPalette::Text, currentData.textColor);
		editorPal.setColor(QPalette::Base, currentData.bgColor);
		editor->setPalette(editorPal);
		editor->setAutoFillBackground(true);
	}

	highlighter->setColors(currentData.focusColor, currentData.highlightColor, currentData.textColor);
	highlighter->setHeaderFormat(currentData.headerFormat);

	applyToolbarIcons();

	updateCryptoState();

	editor->setStyleSheet(QString(
		"QTextEdit { background-color: %1; color: %2; font-family: '%3'; font-size: %4pt; border: none; }"
		).arg(currentData.bgColor.name())
	.arg(currentData.textColor.name())
	.arg(currentData.fontFamily)
	.arg(currentData.fontSize));
}

void NoteWindow::applyToolbarIcons() {
	const QColor c = currentData.textColor;
	topBtn->setIcon(tintedIcon(":/icons/top.svg", c));
	cryptoBtn->setIcon(tintedIcon(":/icons/encrypt.svg", c));
	syncBtn->setIcon(tintedIcon(":/icons/sync.svg", c));
	settingsBtn->setIcon(tintedIcon(":/icons/settings.svg", c));
	closeBtn->setIcon(tintedIcon(":/icons/close.svg", c));
}

void NoteWindow::updateCryptoState() {
	const bool active = editor && !editor->toPlainText().isEmpty();
	cryptoBtn->setEnabled(active);
	if (auto *fx = qobject_cast<QGraphicsOpacityEffect *>(cryptoBtn->graphicsEffect()))
		fx->setOpacity(active ? 0.5 : 0.15);
}

void NoteWindow::closeApplication() {
	ConfigManager::saveNote(currentData);
	QCoreApplication::quit();
}

void NoteWindow::closeEvent(QCloseEvent *event) {
	QCoreApplication::quit();
	event->accept();
}

void NoteWindow::resizeEvent(QResizeEvent *event) {
	if (isInitialized) {
		currentData.width = event->size().width();
		currentData.height = event->size().height();
		ConfigManager::saveNote(currentData);
	}
	QWidget::resizeEvent(event);
}

void NoteWindow::loadData() {
	currentData = ConfigManager::loadNote();
	if (editor) {
		editor->blockSignals(true);
		editor->setPlainText(currentData.content);
		editor->blockSignals(false);
		refreshDates();
	}
}

void NoteWindow::openSyncWindow() {
	SyncWindow dialog(currentData, this);

	connect(&dialog, &SyncWindow::dataChanged, this, [this](const NoteData &d) {
		currentData.passKey = d.passKey;
		currentData.ipAddress = d.ipAddress;
		currentData.networkRole = d.networkRole;
		currentData.networkRoute = d.networkRoute;
		currentData.autoMerge = d.autoMerge;
		networkManager->setAutoMerge(d.autoMerge);
		ConfigManager::saveNote(currentData);
	});

	connect(networkManager, &NetworkManager::statusChanged, dialog.netStatusValLabel, &QLabel::setText);

	connect(&dialog, &SyncWindow::syncRequested, this, [this]() {
		networkManager->requestSync(static_cast<NetworkManager::Route>(currentData.networkRoute));
	});

	connect(networkManager, &NetworkManager::linkReady, &dialog, [this]() {
		if (currentData.lastModified < networkManager->peerLastModified()) {
			QDialog dlg(this);
			dlg.setWindowTitle("");

			QVBoxLayout *v = new QVBoxLayout(&dlg);

			QLabel *msg = new QLabel("Local data is older than destination.", &dlg);
			msg->setAlignment(Qt::AlignCenter);
			v->addWidget(msg, 0, Qt::AlignCenter);

			v->addSpacing(7);

			QHBoxLayout *btnRow = new QHBoxLayout();
			QPushButton *cancelBtn = new QPushButton("Cancel", &dlg);
			QPushButton *approveBtn = new QPushButton("Approve", &dlg);
			btnRow->addStretch();
			btnRow->addWidget(cancelBtn);
			btnRow->addWidget(approveBtn);
			btnRow->addStretch();
			v->addLayout(btnRow);

			connect(approveBtn, &QPushButton::clicked, &dlg, &QDialog::accept);
			connect(cancelBtn, &QPushButton::clicked, &dlg, &QDialog::reject);

			if (dlg.exec() != QDialog::Accepted) return;
		}

		if (!archiveFirst()) return;
		networkManager->sendNoteContent(editor->toPlainText(), currentData.passKey, currentData.lastModified);
		currentData.lastSynced = QDateTime::currentMSecsSinceEpoch();
		ConfigManager::saveNote(currentData);
	});

	if (networkManager->isConnected()) {
		dialog.netStatusValLabel->setText("Connected");
	}

	dialog.exec();
}

void NoteWindow::setEditorContent(const QString &text) {
	editor->blockSignals(true);
	editor->setPlainText(text);
	editor->blockSignals(false);
	currentData.content = text;
	currentData.lastModified = QDateTime::currentMSecsSinceEpoch();
	ConfigManager::saveNote(currentData);
	updateCryptoState();
}

void NoteWindow::toggleEncryption() {

	const bool locked = WedgeSecurity::isEncrypted(editor->toPlainText());

	CryptoWindow dialog(locked ? CryptoWindow::Decrypt : CryptoWindow::Encrypt, this);
	connect(&dialog, &CryptoWindow::phraseSubmitted, this, [this, &dialog, locked](const QString &phrase) {
		const QString out = locked
		? WedgeSecurity::decrypt(editor->toPlainText(), phrase)
		: WedgeSecurity::encrypt(editor->toPlainText(), phrase);
		if (out.isEmpty()) return;
		setEditorContent(out);
		dialog.accept();
	});
	dialog.exec();
}

void NoteWindow::openSettings() {
	SettingsDialog dialog(currentData, this);

	connect(&dialog, &SettingsDialog::settingsChanged, this, [this](const NoteData &d) {
		currentData.autoStart = d.autoStart;
		currentData.bgColor = d.bgColor;
		currentData.focusColor = d.focusColor;
		currentData.fontFamily = d.fontFamily;
		currentData.fontSize = d.fontSize;
		currentData.headerFormat = d.headerFormat;
		currentData.hideTaskbar = d.hideTaskbar;
		currentData.highlightColor = d.highlightColor;
		currentData.textColor = d.textColor;
		updateAppearance();
		ConfigManager::saveNote(currentData);
	});

	dialog.exec();
}

void NoteWindow::mousePressEvent(QMouseEvent *event) {
	if (event->button() == Qt::LeftButton) {
		dragPosition = event->globalPosition().toPoint() - frameGeometry().topLeft();
		event->accept();
	}
}

void NoteWindow::handleIncomingContent(const QString &content) {
	if (!archiveFirst()) return;
	editor->blockSignals(true);
	editor->setPlainText(content);
	editor->blockSignals(false);
	currentData.content = content;
	currentData.lastModified = networkManager->peerLastModified();
	currentData.lastSynced = QDateTime::currentMSecsSinceEpoch();
	ConfigManager::saveNote(currentData);
	updateCryptoState();
}

void NoteWindow::autoMergeTick() {
	if (currentData.lastModified <= networkManager->peerLastModified()) return;
	if (!archiveFirst()) return;
	networkManager->sendNoteContent(editor->toPlainText(), currentData.passKey,
		currentData.lastModified);
	currentData.lastSynced = QDateTime::currentMSecsSinceEpoch();
	ConfigManager::saveNote(currentData);
}

bool NoteWindow::archiveFirst() {
	return !currentData.autoMerge || ConfigManager::backupNote();
}

void NoteWindow::mouseMoveEvent(QMouseEvent *event) {
	if (event->buttons() & Qt::LeftButton) {
		QPoint newPos = event->globalPosition().toPoint() - dragPosition;
		move(newPos);
		if (isInitialized) {
			currentData.x = newPos.x();
			currentData.y = newPos.y();
			ConfigManager::saveNote(currentData);
		}
		event->accept();
	}
}