#include <clocale>
#include <cctype>
#include <cerrno>
#include <cstdio>
#include <cstring>
#include <getopt.h>
#include <iostream>
#include <limits>
#include <memory>
#include <algorithm>
#include <chrono>
#include <random>
#include <thread>
#include <signal.h>
#include <filesystem>
#include <string>
#include <unistd.h>
#include <sys/stat.h>

#include "Command.h"
#include "Editor.h"
#include "Frontend.h"
#include "TerminalFrontend.h"
#include "ErrorHandler.h"

#if defined(KTE_BUILD_GUI)
#if defined(KTE_USE_QT)
#include "QtFrontend.h"
#else
#include "ImGuiFrontend.h"
#endif
#endif


#ifndef KTE_VERSION_STR
#  define KTE_VERSION_STR "devel"
#endif


static void
PrintUsage(const char *prog)
{
	std::cerr << "Usage: " << prog << " [OPTIONS] [files]\n"
		<< "Options:\n"
		<< "  -g, --gui        Use GUI frontend (if built)\n"
		<< "  -t, --term       Use terminal (ncurses) frontend [default]\n"
		<< "  -h, --help       Show this help and exit\n"
		<< "  -V, --version    Show version and exit\n"
		<< "      --stress-highlighter[=SECONDS]  Run a short highlighter stress harness (debug aid)\n";
}


static int
RunStressHighlighter(unsigned seconds)
{
	// Build a synthetic buffer with code-like content
	Buffer buf;
	buf.SetFiletype("cpp");
	buf.SetSyntaxEnabled(true);
	buf.EnsureHighlighter();
	// Seed with many lines
	const int N = 1200;
	for (int i = 0; i < N; ++i) {
		std::string line = "int v" + std::to_string(i) + " = " + std::to_string(i) + "; // line\n";
		buf.insert_row(i, line);
	}
	// Remove the extra last empty row if any artifacts
	// Simulate a viewport of ~60 rows
	const int viewport_rows = 60;
	const auto start_ts     = std::chrono::steady_clock::now();
	std::mt19937 rng{1234567u};
	std::uniform_int_distribution<int> row_d(0, N - 1);
	std::uniform_int_distribution<int> op_d(0, 2);
	std::uniform_int_distribution<int> sleep_d(0, 2);

	// Loop performing edits and highlighter queries while background worker runs
	while (std::chrono::duration_cast<std::chrono::seconds>(std::chrono::steady_clock::now() - start_ts).count() <
	       seconds) {
		int fr = row_d(rng);
		if (fr + viewport_rows >= N)
			fr = std::max(0, N - viewport_rows - 1);
		buf.SetOffsets(static_cast<std::size_t>(fr), 0);
		if (buf.Highlighter()) {
			buf.Highlighter()->PrefetchViewport(buf, fr, viewport_rows, buf.Version());
		}
		// Do a few direct GetLine calls over the viewport to shake the caches
		if (buf.Highlighter()) {
			for (int r = 0; r < viewport_rows; r += 7) {
				(void) buf.Highlighter()->GetLine(buf, fr + r, buf.Version());
			}
		}
		// Random simple edit
		int op = op_d(rng);
		int r  = row_d(rng);
		if (op == 0) {
			buf.insert_text(r, 0, "/*X*/");
			buf.SetDirty(true);
		} else if (op == 1) {
			buf.delete_text(r, 0, 1);
			buf.SetDirty(true);
		} else {
			// split and join occasionally
			buf.split_line(r, 0);
			buf.join_lines(std::min(r + 1, N - 1));
			buf.SetDirty(true);
		}
		// tiny sleep to allow background thread to interleave
		if (sleep_d(rng) == 0) {
			std::this_thread::sleep_for(std::chrono::milliseconds(1));
		}
	}
	return 0;
}


int
main(int argc, char *argv[])
{
	std::setlocale(LC_ALL, "");

	// Ensure the error handler (and its log file) is initialised early.
	kte::ErrorHandler::Instance();

	Editor editor;

	// CLI parsing using getopt_long
	bool req_gui                   = false;
	[[maybe_unused]] bool req_term = false;
	bool show_help                 = false;
	bool show_version              = false;

	static struct option long_opts[] = {
		{"gui", no_argument, nullptr, 'g'},
		{"term", no_argument, nullptr, 't'},
		{"help", no_argument, nullptr, 'h'},
		{"version", no_argument, nullptr, 'V'},
		{"stress-highlighter", optional_argument, nullptr, 1000},
		{nullptr, 0, nullptr, 0}
	};

	int opt;
	int long_index          = 0;
	unsigned stress_seconds = 0;
	while ((opt = getopt_long(argc, argv, "gthV", long_opts, &long_index)) != -1) {
		switch (opt) {
		case 'g':
			req_gui = true;
			break;
		case 't':
			req_term = true;
			break;
		case 'h':
			show_help = true;
			break;
		case 'V':
			show_version = true;
			break;
		case 1000: {
			stress_seconds = 5; // default
			if (optarg && *optarg) {
				try {
					unsigned v = static_cast<unsigned>(std::stoul(optarg));
					if (v > 0 && v < 36000)
						stress_seconds = v;
				} catch (...) {}
			}
			break;
		}
		case '?':
		default:
			PrintUsage(argv[0]);
			return 2;
		}
	}

	if (show_help) {
		PrintUsage(argv[0]);
		return 0;
	}
	if (show_version) {
		std::cout << "kte " << KTE_VERSION_STR << "\n";
		return 0;
	}

	if (stress_seconds > 0) {
		return RunStressHighlighter(stress_seconds);
	}

	// Top-level exception handler to prevent data loss and ensure cleanup
	try {
		// Determine frontend
#if !defined(KTE_BUILD_GUI)
		if (req_gui) {
			std::cerr << "kte: GUI not built. Reconfigure with -DBUILD_GUI=ON and required deps installed."
				<<
				std::endl;
			return 2;
		}
#else
		bool use_gui = false;
		if (req_gui) {
			use_gui = true;
		} else if (req_term) {
			use_gui = false;
		} else {



		// Default depends on build target: kge defaults to GUI, kte to terminal
#if defined(KTE_DEFAULT_GUI)
		use_gui = true;
#else
		use_gui = false;
#endif
		}
#endif

		// Open files passed on the CLI; support +N to jump to line N in the next file.
		// If no files are provided, create an empty buffer.
		if (optind < argc) {
			// Seed a scratch buffer so the UI has something to show while deferred opens
			// (and potential swap recovery prompts) are processed.
			editor.AddBuffer(Buffer());
			std::size_t pending_line = 0; // 0 = no pending line
			for (int i = optind; i < argc; ++i) {
				const char *arg = argv[i];
				if (arg && arg[0] == '+') {
					// Parse +<digits>
					const char *p = arg + 1;
					if (*p != '\0') {
						bool all_digits = true;
						for (const char *q = p; *q; ++q) {
							if (!std::isdigit(static_cast<unsigned char>(*q))) {
								all_digits = false;
								break;
							}
						}
						if (all_digits) {
							// Clamp to >=1 later; 0 disables.
							try {
								unsigned long v = std::stoul(p);
								if (v > std::numeric_limits<std::size_t>::max()) {
									std::cerr <<
										"kte: Warning: Line number too large, ignoring\n";
									pending_line = 0;
								} else {
									pending_line = static_cast<std::size_t>(v);
								}
							} catch (...) {
								// Ignore malformed huge numbers
								pending_line = 0;
							}
							continue; // look for the next file arg
						}
					}
					// Fall through: not a +number, treat as filename starting with '+'
				}

				// Resolve to absolute path now, before any
				// chdir (macOS GUI changes CWD to HOME before
				// deferred opens are processed).
				std::string path = arg;
				try {
					std::filesystem::path p(path);
					if (p.is_relative()) {
						path = std::filesystem::absolute(p).string();
					}
				} catch (...) {
					// Fall through with original path
				}
				editor.RequestOpenFile(path, pending_line);
				pending_line = 0; // consumed (if set)
			}
			// If we ended with a pending +N but no subsequent file, ignore it.
		} else {
			// Create a single empty buffer
			editor.AddBuffer(Buffer());
			editor.SetStatus("new: empty buffer");
		}

		// Install built-in commands
		InstallDefaultCommands();

		// Select frontend
		std::unique_ptr<Frontend> fe;
#if defined(KTE_BUILD_GUI)
		if (use_gui) {
			fe = std::make_unique<GUIFrontend>();
		} else
#endif
		{
			fe = std::make_unique<TerminalFrontend>();
		}

#if defined(KTE_BUILD_GUI) && defined(__APPLE__)
		if (use_gui) {
			/* likely using the .app, so need to cd */
			const char *home = getenv("HOME");
			if (!home) {
				std::cerr << "kge.app: HOME environment variable not set" << std::endl;
				return 1;
			}
			if (chdir(home) != 0) {
				std::cerr << "kge.app: failed to chdir to " << home << ": "
					<< std::strerror(errno) << std::endl;
				return 1;
			}
		}
#endif

		if (!fe->Init(argc, argv, editor)) {
			std::cerr << "kte: failed to initialize frontend" << std::endl;
			return 1;
		}

		Execute(editor, CommandId::CenterOnCursor);

		bool running = true;
		while (running) {
			fe->Step(editor, running);
		}

		fe->Shutdown();

		return 0;
	} catch (const std::exception &e) {
		std::string msg = std::string("Unhandled exception: ") + e.what();
		kte::ErrorHandler::Instance().Critical("main", msg);
		std::cerr << "\n*** FATAL ERROR ***\n"
			<< "kte encountered an unhandled exception: " << e.what() << "\n"
			<< "The editor will now exit. Any unsaved changes may be recovered from swap files.\n";
		return 1;
	} catch (...) {
		kte::ErrorHandler::Instance().Critical("main", "Unknown exception");
		std::cerr << "\n*** FATAL ERROR ***\n"
			<< "kte encountered an unknown exception.\n"
			<< "The editor will now exit. Any unsaved changes may be recovered from swap files.\n";
		return 1;
	}
}