// Refactor: route all key processing through command subsystem, mirroring ImGuiInputHandler

#include "QtInputHandler.h"

#include <QKeyEvent>

#include <ncurses.h>

#include "Editor.h"
#include "KKeymap.h"

// Temporary verbose logging to debug macOS Qt key translation issues
// Default to off; enable by defining QT_IH_DEBUG=1 at compile time when needed.
#ifndef QT_IH_DEBUG
#define QT_IH_DEBUG 0
#endif

#if QT_IH_DEBUG
#include <cstdio>
static const char *
mods_str(Qt::KeyboardModifiers m)
{
	static thread_local char buf[64];
	buf[0]     = '\0';
	bool first = true;
	auto add   = [&](const char *s) {
		if (!first)
			std::snprintf(buf + std::strlen(buf), sizeof(buf) - std::strlen(buf), "|");
		std::snprintf(buf + std::strlen(buf), sizeof(buf) - std::strlen(buf), "%s", s);
		first = false;
	};
	if (m & Qt::ShiftModifier)
		add("Shift");
	if (m & Qt::ControlModifier)
		add("Ctrl");
	if (m & Qt::AltModifier)
		add("Alt");
	if (m & Qt::MetaModifier)
		add("Meta");
	if (first)
		std::snprintf(buf, sizeof(buf), "none");
	return buf;
}
#define LOGF(...) std::fprintf(stderr, __VA_ARGS__)
#else
#define LOGF(...) ((void)0)
#endif

static bool
IsPrintableQt(const QKeyEvent &e)
{
	// Printable if it yields non-empty text and no Ctrl/Meta modifier
	if (e.modifiers() & (Qt::ControlModifier | Qt::MetaModifier))
		return false;
	const QString t = e.text();
	return !t.isEmpty() && !t.at(0).isNull();
}


static int
ToAsciiKey(const QKeyEvent &e)
{
	const QString t = e.text();
	if (!t.isEmpty()) {
		const QChar c = t.at(0);
		if (!c.isNull())
			return KLowerAscii(c.unicode());
	}
	// When modifiers (like Control) are held, Qt::text() can be empty on macOS.
	// Fall back to mapping common virtual keys to ASCII.
	switch (e.key()) {
	case Qt::Key_A:
		return 'a';
	case Qt::Key_B:
		return 'b';
	case Qt::Key_C:
		return 'c';
	case Qt::Key_D:
		return 'd';
	case Qt::Key_E:
		return 'e';
	case Qt::Key_F:
		return 'f';
	case Qt::Key_G:
		return 'g';
	case Qt::Key_H:
		return 'h';
	case Qt::Key_I:
		return 'i';
	case Qt::Key_J:
		return 'j';
	case Qt::Key_K:
		return 'k';
	case Qt::Key_L:
		return 'l';
	case Qt::Key_M:
		return 'm';
	case Qt::Key_N:
		return 'n';
	case Qt::Key_O:
		return 'o';
	case Qt::Key_P:
		return 'p';
	case Qt::Key_Q:
		return 'q';
	case Qt::Key_R:
		return 'r';
	case Qt::Key_S:
		return 's';
	case Qt::Key_T:
		return 't';
	case Qt::Key_U:
		return 'u';
	case Qt::Key_V:
		return 'v';
	case Qt::Key_W:
		return 'w';
	case Qt::Key_X:
		return 'x';
	case Qt::Key_Y:
		return 'y';
	case Qt::Key_Z:
		return 'z';
	case Qt::Key_0:
		return '0';
	case Qt::Key_1:
		return '1';
	case Qt::Key_2:
		return '2';
	case Qt::Key_3:
		return '3';
	case Qt::Key_4:
		return '4';
	case Qt::Key_5:
		return '5';
	case Qt::Key_6:
		return '6';
	case Qt::Key_7:
		return '7';
	case Qt::Key_8:
		return '8';
	case Qt::Key_9:
		return '9';
	case Qt::Key_Comma:
		return ',';
	case Qt::Key_Period:
		return '.';
	case Qt::Key_Semicolon:
		return ';';
	case Qt::Key_Apostrophe:
		return '\'';
	case Qt::Key_Minus:
		return '-';
	case Qt::Key_Equal:
		return '=';
	case Qt::Key_Slash:
		return '/';
	case Qt::Key_Backslash:
		return '\\';
	case Qt::Key_BracketLeft:
		return '[';
	case Qt::Key_BracketRight:
		return ']';
	case Qt::Key_QuoteLeft:
		return '`';
	case Qt::Key_Space:
		return ' ';
	default:
		break;
	}
	return 0;
}


// Case-preserving ASCII derivation for k-prefix handling where we need to
// distinguish between 'C' and 'c'. Falls back to virtual-key mapping if
// event text is unavailable (common when Control/Meta held on macOS).
static int
ToAsciiKeyPreserveCase(const QKeyEvent &e)
{
	const QString t = e.text();
	if (!t.isEmpty()) {
		const QChar c = t.at(0);
		if (!c.isNull())
			return c.unicode();
	}
	// Fall back to virtual key mapping (letters as uppercase A..Z)
	switch (e.key()) {
	case Qt::Key_A:
		return 'A';
	case Qt::Key_B:
		return 'B';
	case Qt::Key_C:
		return 'C';
	case Qt::Key_D:
		return 'D';
	case Qt::Key_E:
		return 'E';
	case Qt::Key_F:
		return 'F';
	case Qt::Key_G:
		return 'G';
	case Qt::Key_H:
		return 'H';
	case Qt::Key_I:
		return 'I';
	case Qt::Key_J:
		return 'J';
	case Qt::Key_K:
		return 'K';
	case Qt::Key_L:
		return 'L';
	case Qt::Key_M:
		return 'M';
	case Qt::Key_N:
		return 'N';
	case Qt::Key_O:
		return 'O';
	case Qt::Key_P:
		return 'P';
	case Qt::Key_Q:
		return 'Q';
	case Qt::Key_R:
		return 'R';
	case Qt::Key_S:
		return 'S';
	case Qt::Key_T:
		return 'T';
	case Qt::Key_U:
		return 'U';
	case Qt::Key_V:
		return 'V';
	case Qt::Key_W:
		return 'W';
	case Qt::Key_X:
		return 'X';
	case Qt::Key_Y:
		return 'Y';
	case Qt::Key_Z:
		return 'Z';
	case Qt::Key_Comma:
		return ',';
	case Qt::Key_Period:
		return '.';
	case Qt::Key_Semicolon:
		return ';';
	case Qt::Key_Apostrophe:
		return '\'';
	case Qt::Key_Minus:
		return '-';
	case Qt::Key_Equal:
		return '=';
	case Qt::Key_Slash:
		return '/';
	case Qt::Key_Backslash:
		return '\\';
	case Qt::Key_BracketLeft:
		return '[';
	case Qt::Key_BracketRight:
		return ']';
	case Qt::Key_QuoteLeft:
		return '`';
	case Qt::Key_Space:
		return ' ';
	default:
		break;
	}
	return 0;
}


bool
QtInputHandler::ProcessKeyEvent(const QKeyEvent &e)
{
	const Qt::KeyboardModifiers mods = e.modifiers();
	LOGF("[QtIH] keyPress key=0x%X mods=%s text='%s' k_prefix=%d k_ctrl_pending=%d esc_meta=%d\n",
	     e.key(), mods_str(mods), e.text().toUtf8().constData(), (int)k_prefix_, (int)k_ctrl_pending_,
	     (int)esc_meta_);

	// Control-chord detection: only treat the physical Control key as control-like.
	// Do NOT include Meta (Command) here so that ⌘-letter shortcuts do not fall into
	// the Ctrl map (prevents ⌘-T being mistaken for C-t).
	const bool ctrl_like = (mods & Qt::ControlModifier);

	// 1) Universal argument digits (when active), consume digits without enqueuing commands
	if (ed_ && ed_

	    ->
	    UArg() != 0
	) {
		if (!(mods & (Qt::ControlModifier | Qt::AltModifier | Qt::MetaModifier))) {
			if (e.key() >= Qt::Key_0 && e.key() <= Qt::Key_9) {
				int d = e.key() - Qt::Key_0;
				ed_->UArgDigit(d);
				// request status refresh
				std::lock_guard<std::mutex> lk(mu_);
				q_.push(MappedInput{true, CommandId::UArgStatus, std::string(), 0});
				LOGF("[QtIH] UArg digit %d -> enqueue UArgStatus\n", d);
				return true;
			}
		}
	}

	// 2) Enter k-prefix on C-k
	if (ctrl_like && (e.key() == Qt::Key_K)) {
		k_prefix_       = true;
		k_ctrl_pending_ = false;
		LOGF("[QtIH] Enter KPrefix\n");
		std::lock_guard<std::mutex> lk(mu_);
		q_.push(MappedInput{true, CommandId::KPrefix, std::string(), 0});
		return true;
	}

	// 3) If currently in k-prefix, resolve next key via KLookupKCommand
	if (k_prefix_) {
		// ESC/meta prefix should not interfere with k-suffix resolution
		esc_meta_ = false;
		// Support literal 'C' (uppercase) or '^' to indicate the next key is Ctrl-qualified.
		// Use case-preserving derivation so that 'c' (lowercase) can still be a valid suffix
		// like C-k c (BufferClose).
		int ascii_raw = ToAsciiKeyPreserveCase(e);
		if (ascii_raw == 'C' || ascii_raw == '^') {
			k_ctrl_pending_ = true;
			if (ed_)
				ed_->SetStatus("C-k C _");
			LOGF("[QtIH] KPrefix: set k_ctrl_pending via '%c'\n", (ascii_raw == 'C') ? 'C' : '^');
			return true; // consume, wait for next key
		}
		int ascii_key = (ascii_raw != 0) ? ascii_raw : ToAsciiKey(e);
		int lower     = KLowerAscii(ascii_key);
		// Only pass a control suffix for specific supported keys (d/x/q),
		// matching ImGui behavior so that holding Ctrl during the suffix
		// doesn't break other mappings like C-k c (BufferClose).
		bool ctrl_suffix_supported = (lower == 'd' || lower == 'x' || lower == 'q');
		bool pass_ctrl             = (ctrl_like || k_ctrl_pending_) && ctrl_suffix_supported;
		k_ctrl_pending_            = false; // consume pending qualifier on any suffix
		LOGF("[QtIH] KPrefix: ascii_key=%d lower=%d pass_ctrl=%d\n", ascii_key, lower, (int)pass_ctrl);
		if (ascii_key != 0) {
			CommandId id;
			if (KLookupKCommand(ascii_key, pass_ctrl, id)) {
				LOGF("[QtIH] KPrefix: mapped to command id=%d\n", (int)id);
				std::lock_guard<std::mutex> lk(mu_);
				q_.push(MappedInput{true, id, std::string(), 0});
			} else {
				// Unknown k-command: notify
				std::string a;
				a.push_back(static_cast<char>(ascii_key));
				LOGF("[QtIH] KPrefix: unknown command for '%c'\n", (char)ascii_key);
				std::lock_guard<std::mutex> lk(mu_);
				q_.push(MappedInput{true, CommandId::UnknownKCommand, a, 0});
			}
			k_prefix_ = false;
			return true;
		}
		// If not resolvable, consume and exit k-prefix
		k_prefix_ = false;
		LOGF("[QtIH] KPrefix: unresolved key; exiting prefix\n");
		return true;
	}

	// 3.5) GUI shortcut: Command/Meta + T opens the visual font picker (Qt only).
	// Require Meta present and Control NOT present so Ctrl-T never triggers this.
	if ((mods & Qt::MetaModifier) && !(mods & Qt::ControlModifier) && e.key() == Qt::Key_T) {
		LOGF("[QtIH] Meta/Super-T -> VisualFontPickerToggle\n");
		std::lock_guard<std::mutex> lk(mu_);
		q_.push(MappedInput{true, CommandId::VisualFontPickerToggle, std::string(), 0});
		return true;
	}

	// 4) ESC as Meta prefix (set state). Alt/Meta chord handled below directly.
	if (e.key() == Qt::Key_Escape) {
		esc_meta_ = true;
		LOGF("[QtIH] ESC: set esc_meta\n");
		return true; // consumed
	}

	// 5) Alt/Meta bindings (ESC f/b equivalent). Handle either Alt/Meta or pending esc_meta_
	// ESC/meta chords: on macOS, do NOT treat Meta as ESC; only Alt (Option) should trigger.
#if defined(__APPLE__)
	if (esc_meta_ || (mods & Qt::AltModifier)) {
#else
		if (esc_meta_ || (mods & (Qt::AltModifier | Qt::MetaModifier))) {

#endif
		int ascii_key = 0;
		if (e.key() == Qt::Key_Backspace) {
			ascii_key = KEY_BACKSPACE;
		} else if (e.key() >= Qt::Key_A && e.key() <= Qt::Key_Z) {
			ascii_key = 'a' + (e.key() - Qt::Key_A);
		} else if (e.key() == Qt::Key_Comma) {
			ascii_key = '<';
		} else if (e.key() == Qt::Key_Period) {
			ascii_key = '>';
		}
		// If still unknown, try deriving from text (covers digits, punctuation, locale)
		if (ascii_key == 0) {
			ascii_key = ToAsciiKey(e);
		}
		esc_meta_ = false; // one-shot regardless
		if (ascii_key != 0) {
			ascii_key = KLowerAscii(ascii_key);
			CommandId id;
			if (KLookupEscCommand(ascii_key, id)) {
				LOGF("[QtIH] ESC/Meta: mapped '%d' -> id=%d\n", ascii_key, (int)id);
				std::lock_guard<std::mutex> lk(mu_);
				q_.push(MappedInput{true, id, std::string(), 0});
				return true;
			} else {
				// Report invalid ESC sequence just like ImGui path
				LOGF("[QtIH] ESC/Meta: unknown command for ascii=%d\n", ascii_key);
				std::lock_guard<std::mutex> lk(mu_);
				q_.push(MappedInput{true, CommandId::UnknownEscCommand, std::string(), 0});
				return true;
			}
		}
		// Nothing derivable: consume (ESC prefix cleared) and do not insert text
		return true;
	}

	// 6) Control-chord direct mappings (e.g., C-n/C-p/C-f/C-b...)
	if (ctrl_like) {
		// Universal argument handling: C-u starts collection; C-g cancels
		if (e.key() == Qt::Key_U) {
			if (ed_)
				ed_->UArgStart();
			LOGF("[QtIH] Ctrl-chord: start universal argument\n");
			return true;
		}
		if (e.key() == Qt::Key_G) {
			if (ed_)
				ed_->UArgClear();
			k_ctrl_pending_ = false;
			k_prefix_       = false;
			LOGF("[QtIH] Ctrl-chord: cancel universal argument and k-prefix via C-g\n");
			// Fall through to map C-g to Refresh via ctrl map
		}
		if (e.key() >= Qt::Key_A && e.key() <= Qt::Key_Z) {
			int ascii_key = 'a' + (e.key() - Qt::Key_A);
			CommandId id;
			if (KLookupCtrlCommand(ascii_key, id)) {
				LOGF("[QtIH] Ctrl-chord: 'C-%c' -> id=%d\n", (char)ascii_key, (int)id);
				std::lock_guard<std::mutex> lk(mu_);
				q_.push(MappedInput{true, id, std::string(), 0});
				return true;
			}
		}
		// If no mapping, continue to allow other keys below
	}

	// 7) Special navigation/edit keys (match ImGui behavior)
	{
		CommandId id;
		bool has = false;
		switch (e.key()) {
		case Qt::Key_Return:
		case Qt::Key_Enter:
			id = CommandId::Newline;
			has = true;
			break;
		case Qt::Key_Backspace:
			id = CommandId::Backspace;
			has = true;
			break;
		case Qt::Key_Delete:
			id = CommandId::DeleteChar;
			has = true;
			break;
		case Qt::Key_Left:
			id = CommandId::MoveLeft;
			has = true;
			break;
		case Qt::Key_Right:
			id = CommandId::MoveRight;
			has = true;
			break;
		case Qt::Key_Up:
			id = CommandId::MoveUp;
			has = true;
			break;
		case Qt::Key_Down:
			id = CommandId::MoveDown;
			has = true;
			break;
		case Qt::Key_Home:
			id = CommandId::MoveHome;
			has = true;
			break;
		case Qt::Key_End:
			id = CommandId::MoveEnd;
			has = true;
			break;
		case Qt::Key_PageUp:
			id = CommandId::PageUp;
			has = true;
			break;
		case Qt::Key_PageDown:
			id = CommandId::PageDown;
			has = true;
			break;
		default:
			break;
		}
		if (has) {
			LOGF("[QtIH] Special key -> id=%d\n", (int)id);
			std::lock_guard<std::mutex> lk(mu_);
			q_.push(MappedInput{true, id, std::string(), 0});
			return true;
		}
	}

	// 8) Insert printable text
	if (IsPrintableQt(e)) {
		std::string s = e.text().toStdString();
		if (!s.empty()) {
			LOGF("[QtIH] InsertText '%s'\n", s.c_str());
			std::lock_guard<std::mutex> lk(mu_);
			q_.push(MappedInput{true, CommandId::InsertText, s, 0});
			return true;
		}
	}

	LOGF("[QtIH] Unhandled key\n");
	return false;
}


bool
QtInputHandler::Poll(MappedInput &out)
{
	std::lock_guard<std::mutex> lock(mu_);
	if (q_.empty())
		return false;
	out = q_.front();
	q_.pop();
	return true;
}
