#include <algorithm>
#include <cmath>
#include <cstdio>
#include <cstdlib>
#include <filesystem>
#include <limits>
#include <string>

#include <imgui.h>
#include <regex>

#include "ImGuiRenderer.h"
#include "Highlight.h"
#include "GUITheme.h"
#include "Buffer.h"
#include "Command.h"
#include "Editor.h"


// Version string expected to be provided by build system as KTE_VERSION_STR
#ifndef KTE_VERSION_STR
#  define KTE_VERSION_STR "dev"
#endif

// ImGui compatibility: some bundled ImGui versions (or builds without docking)
// don't define ImGuiWindowFlags_NoDocking. Treat it as 0 in that case.
#ifndef ImGuiWindowFlags_NoDocking
#  define ImGuiWindowFlags_NoDocking 0
#endif


void
ImGuiRenderer::Draw(Editor &ed)
{
	// Make the editor window occupy the entire GUI container/viewport
	ImGuiViewport *vp = ImGui::GetMainViewport();
	// On HiDPI/Retina, snap to integer pixels to prevent any draw vs hit-test
	// mismatches that can appear on the very first maximized frame.
	ImVec2 main_pos = vp->Pos;
	ImVec2 main_sz  = vp->Size;
	main_pos.x      = std::floor(main_pos.x + 0.5f);
	main_pos.y      = std::floor(main_pos.y + 0.5f);
	main_sz.x       = std::floor(main_sz.x + 0.5f);
	main_sz.y       = std::floor(main_sz.y + 0.5f);
	ImGui::SetNextWindowPos(main_pos);
	ImGui::SetNextWindowSize(main_sz);

	ImGuiWindowFlags flags = ImGuiWindowFlags_NoTitleBar
	                         | ImGuiWindowFlags_NoScrollbar
	                         | ImGuiWindowFlags_NoScrollWithMouse
	                         | ImGuiWindowFlags_NoResize
	                         | ImGuiWindowFlags_NoMove
	                         | ImGuiWindowFlags_NoCollapse
	                         | ImGuiWindowFlags_NoSavedSettings
	                         | ImGuiWindowFlags_NoBringToFrontOnFocus
	                         | ImGuiWindowFlags_NoNavFocus;

	// Reduce padding so the buffer content uses the whole area
	ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 0.0f);
	ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 0.0f);
	ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(6.f, 6.f));

	ImGui::Begin("kte", nullptr, flags);

	const Buffer *buf = ed.CurrentBuffer();
	if (!buf) {
		ImGui::TextUnformatted("[no buffer]");
	} else {
		const auto &lines   = buf->Rows();
		std::size_t cy      = buf->Cury();
		std::size_t cx      = buf->Curx();
		const float line_h  = ImGui::GetTextLineHeight();
		const float row_h   = ImGui::GetTextLineHeightWithSpacing();
		const float space_w = ImGui::CalcTextSize(" ").x;

		// Two-way sync between Buffer::Rowoffs and ImGui scroll position:
		// - If command layer changed Buffer::Rowoffs since last frame, drive ImGui scroll from it.
		// - Otherwise, propagate ImGui scroll to Buffer::Rowoffs so command layer has an up-to-date view.
		static long prev_buf_rowoffs = -1; // previous frame's Buffer::Rowoffs
		static long prev_buf_coloffs = -1; // previous frame's Buffer::Coloffs

		const long buf_rowoffs = static_cast<long>(buf->Rowoffs());
		const long buf_coloffs = static_cast<long>(buf->Coloffs());

		// Detect programmatic change (e.g., page_down command changed rowoffs)
		// Use SetNextWindowScroll BEFORE BeginChild to set initial scroll position
		if (prev_buf_rowoffs >= 0 && buf_rowoffs != prev_buf_rowoffs) {
			float target_y = static_cast<float>(buf_rowoffs) * row_h;
			ImGui::SetNextWindowScroll(ImVec2(-1.0f, target_y));
		}
		if (prev_buf_coloffs >= 0 && buf_coloffs != prev_buf_coloffs) {
			float target_x = static_cast<float>(buf_coloffs) * space_w;
			float target_y = static_cast<float>(buf_rowoffs) * row_h;
			ImGui::SetNextWindowScroll(ImVec2(target_x, target_y));
		}

		// Reserve space for status bar at bottom.
		// We calculate a height that is an exact multiple of the line height
		// to avoid partial lines and "scroll past end" jitter.
		float total_avail_h = ImGui::GetContentRegionAvail().y;
		float wanted_bar_h  = ImGui::GetFrameHeight();
		float child_h_plan  = std::max(0.0f, std::floor((total_avail_h - wanted_bar_h) / row_h) * row_h);
		float real_bar_h    = total_avail_h - child_h_plan;

		ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0, 0));
		ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0, 0));
		ImGui::BeginChild("scroll", ImVec2(0, child_h_plan), false,
		                  ImGuiWindowFlags_HorizontalScrollbar | ImGuiWindowFlags_NoScrollWithMouse);

		// Get child window position and scroll for click handling
		ImVec2 child_window_pos = ImGui::GetWindowPos();
		float scroll_y          = ImGui::GetScrollY();
		float scroll_x          = ImGui::GetScrollX();
		std::size_t rowoffs     = 0; // we render from the first line; scrolling is handled by ImGui

		// Synchronize buffer offsets from ImGui scroll if user scrolled manually
		bool forced_scroll = false;
		{
			static float prev_scroll_y = -1.0f; // previous frame's ImGui scroll Y in pixels
			static float prev_scroll_x = -1.0f; // previous frame's ImGui scroll X in pixels

			const long scroll_top  = static_cast<long>(scroll_y / row_h);
			const long scroll_left = static_cast<long>(scroll_x / space_w);

			// Check if rowoffs was programmatically changed this frame
			if (prev_buf_rowoffs >= 0 && buf_rowoffs != prev_buf_rowoffs) {
				forced_scroll = true;
			}

			// If user scrolled (not programmatic), update buffer offsets accordingly
			if (prev_scroll_y >= 0.0f && scroll_y != prev_scroll_y && !forced_scroll) {
				if (Buffer *mbuf = const_cast<Buffer *>(buf)) {
					mbuf->SetOffsets(static_cast<std::size_t>(std::max(0L, scroll_top)),
					                 mbuf->Coloffs());
				}
			}
			if (prev_scroll_x >= 0.0f && scroll_x != prev_scroll_x && !forced_scroll) {
				if (Buffer *mbuf = const_cast<Buffer *>(buf)) {
					mbuf->SetOffsets(mbuf->Rowoffs(),
					                 static_cast<std::size_t>(std::max(0L, scroll_left)));
				}
			}

			// Update trackers for next frame
			prev_scroll_y = scroll_y;
			prev_scroll_x = scroll_x;
		}
		prev_buf_rowoffs = buf_rowoffs;
		prev_buf_coloffs = buf_coloffs;
		// Cache current horizontal offset in rendered columns for click handling
		const std::size_t coloffs_now = buf->Coloffs();

		// Mark selection state (mark -> cursor), in source coordinates
		bool sel_active    = false;
		std::size_t sel_sy = 0, sel_sx = 0, sel_ey = 0, sel_ex = 0;
		if (buf->MarkSet()) {
			sel_sy = buf->MarkCury();
			sel_sx = buf->MarkCurx();
			sel_ey = buf->Cury();
			sel_ex = buf->Curx();
			if (sel_sy > sel_ey || (sel_sy == sel_ey && sel_sx > sel_ex)) {
				std::swap(sel_sy, sel_ey);
				std::swap(sel_sx, sel_ex);
			}
			sel_active = !(sel_sy == sel_ey && sel_sx == sel_ex);
		}
		// Visual-line selection: full-line highlight range
		const bool vsel_active    = buf->VisualLineActive();
		const std::size_t vsel_sy = vsel_active ? buf->VisualLineStartY() : 0;
		const std::size_t vsel_ey = vsel_active ? buf->VisualLineEndY() : 0;

		static bool mouse_selecting = false;
		auto mouse_pos_to_buf       = [&]() -> std::pair<std::size_t, std::size_t> {
			ImVec2 mp = ImGui::GetIO().MousePos;
			// Convert mouse pos to buffer row
			float content_y = (mp.y - child_window_pos.y) + scroll_y;
			long by_l       = static_cast<long>(content_y / row_h);
			if (by_l < 0)
				by_l = 0;
			std::size_t by = static_cast<std::size_t>(by_l);
			if (by >= lines.size())
				by = lines.empty() ? 0 : (lines.size() - 1);

			// Convert mouse pos to rendered x
			float visual_x = mp.x - child_window_pos.x;
			if (visual_x < 0.0f)
				visual_x = 0.0f;
			std::size_t clicked_rx = static_cast<std::size_t>(visual_x / space_w) + coloffs_now;

			// Convert rendered column to source column
			if (lines.empty())
				return {0, 0};
			std::string line_clicked = static_cast<std::string>(lines[by]);
			const std::size_t tabw   = 8;
			std::size_t rx           = 0;
			std::size_t best_col     = 0;
			float best_dist          = std::numeric_limits<float>::infinity();
			float clicked_rx_f       = static_cast<float>(clicked_rx);
			for (std::size_t i = 0; i <= line_clicked.size(); ++i) {
				float dist = std::fabs(clicked_rx_f - static_cast<float>(rx));
				if (dist < best_dist) {
					best_dist = dist;
					best_col  = i;
				}
				if (i < line_clicked.size()) {
					rx += (line_clicked[i] == '\t') ? (tabw - (rx % tabw)) : 1;
				}
			}
			return {by, best_col};
		};

		// Mouse-driven selection: set mark on press, update cursor on drag
		if (ImGui::IsWindowHovered() && ImGui::IsMouseClicked(ImGuiMouseButton_Left)) {
			mouse_selecting = true;
			auto [by, bx]   = mouse_pos_to_buf();
			char tmp[64];
			std::snprintf(tmp, sizeof(tmp), "%zu:%zu", by, bx);
			Execute(ed, CommandId::MoveCursorTo, std::string(tmp));
			if (Buffer *mbuf = const_cast<Buffer *>(buf)) {
				mbuf->SetMark(bx, by);
			}
		}
		if (mouse_selecting && ImGui::IsWindowHovered() && ImGui::IsMouseDown(ImGuiMouseButton_Left)) {
			auto [by, bx] = mouse_pos_to_buf();
			char tmp[64];
			std::snprintf(tmp, sizeof(tmp), "%zu:%zu", by, bx);
			Execute(ed, CommandId::MoveCursorTo, std::string(tmp));
		}
		if (mouse_selecting && ImGui::IsMouseReleased(ImGuiMouseButton_Left)) {
			mouse_selecting = false;
		}
		for (std::size_t i = rowoffs; i < lines.size(); ++i) {
			// Capture the screen position before drawing the line
			ImVec2 line_pos  = ImGui::GetCursorScreenPos();
			std::string line = static_cast<std::string>(lines[i]);

			// Expand tabs to spaces with width=8 and apply horizontal scroll offset
			const std::size_t tabw = 8;
			std::string expanded;
			expanded.reserve(line.size() + 16);
			std::size_t rx_abs_draw = 0; // rendered column for drawing
			// Compute search highlight ranges for this line in source indices
			bool search_mode = ed.SearchActive() && !ed.SearchQuery().empty();
			std::vector<std::pair<std::size_t, std::size_t> > hl_src_ranges;
			if (search_mode) {
				// If we're in RegexSearch or RegexReplaceFind mode, compute ranges using regex; otherwise plain substring
				if (ed.PromptActive() && (
					    ed.CurrentPromptKind() == Editor::PromptKind::RegexSearch || ed.
					    CurrentPromptKind() == Editor::PromptKind::RegexReplaceFind)) {
					try {
						std::regex rx(ed.SearchQuery());
						for (auto it = std::sregex_iterator(line.begin(), line.end(), rx);
						     it != std::sregex_iterator(); ++it) {
							const auto &m  = *it;
							std::size_t sx = static_cast<std::size_t>(m.position());
							std::size_t ex = sx + static_cast<std::size_t>(m.length());
							hl_src_ranges.emplace_back(sx, ex);
						}
					} catch (const std::regex_error &) {
						// ignore invalid patterns here; status line already shows the error
					}
				} else {
					const std::string &q = ed.SearchQuery();
					std::size_t pos      = 0;
					while (!q.empty() && (pos = line.find(q, pos)) != std::string::npos) {
						hl_src_ranges.emplace_back(pos, pos + q.size());
						pos += q.size();
					}
				}
			}
			auto src_to_rx = [&](std::size_t upto_src_exclusive) -> std::size_t {
				std::size_t rx = 0;
				std::size_t s  = 0;
				while (s < upto_src_exclusive && s < line.size()) {
					if (line[s] == '\t')
						rx += (tabw - (rx % tabw));
					else
						rx += 1;
					++s;
				}
				return rx;
			};
			// Draw background highlights (under text)
			if (search_mode && !hl_src_ranges.empty()) {
				// Current match emphasis
				bool has_current    = ed.SearchMatchLen() > 0 && ed.SearchMatchY() == i;
				std::size_t cur_x   = has_current ? ed.SearchMatchX() : 0;
				std::size_t cur_end = has_current ? (ed.SearchMatchX() + ed.SearchMatchLen()) : 0;
				for (const auto &rg: hl_src_ranges) {
					std::size_t sx       = rg.first, ex = rg.second;
					std::size_t rx_start = src_to_rx(sx);
					std::size_t rx_end   = src_to_rx(ex);
					// Apply horizontal scroll offset
					if (rx_end <= coloffs_now)
						continue; // fully left of view
					std::size_t vx0 = (rx_start > coloffs_now) ? (rx_start - coloffs_now) : 0;
					std::size_t vx1 = rx_end - coloffs_now;
					ImVec2 p0 = ImVec2(line_pos.x + static_cast<float>(vx0) * space_w, line_pos.y);
					ImVec2 p1 = ImVec2(line_pos.x + static_cast<float>(vx1) * space_w,
					                   line_pos.y + line_h);
					// Choose color: current match stronger
					bool is_current = has_current && sx == cur_x && ex == cur_end;
					ImU32 col       = is_current
						                  ? IM_COL32(255, 220, 120, 140)
						                  : IM_COL32(200, 200, 0, 90);
					ImGui::GetWindowDrawList()->AddRectFilled(p0, p1, col);
				}
			}

			// Draw selection background (over search highlight; under text)
			if (sel_active || vsel_active) {
				bool line_has  = false;
				std::size_t sx = 0, ex = 0;
				if (vsel_active && i >= vsel_sy && i <= vsel_ey) {
					sx       = 0;
					ex       = line.size();
					line_has = ex > sx;
				} else if (i < sel_sy || i > sel_ey) {
					line_has = false;
				} else if (sel_sy == sel_ey) {
					sx       = sel_sx;
					ex       = sel_ex;
					line_has = ex > sx;
				} else if (i == sel_sy) {
					sx       = sel_sx;
					ex       = line.size();
					line_has = ex > sx;
				} else if (i == sel_ey) {
					sx       = 0;
					ex       = std::min(sel_ex, line.size());
					line_has = ex > sx;
				} else {
					sx       = 0;
					ex       = line.size();
					line_has = ex > sx;
				}
				if (line_has) {
					std::size_t rx_start = src_to_rx(sx);
					std::size_t rx_end   = src_to_rx(ex);
					if (rx_end > coloffs_now) {
						std::size_t vx0 = (rx_start > coloffs_now)
							                  ? (rx_start - coloffs_now)
							                  : 0;
						std::size_t vx1 = rx_end - coloffs_now;
						ImVec2 p0       = ImVec2(line_pos.x + static_cast<float>(vx0) * space_w,
						                         line_pos.y);
						ImVec2 p1 = ImVec2(line_pos.x + static_cast<float>(vx1) * space_w,
						                   line_pos.y + line_h);
						ImU32 col = ImGui::GetColorU32(ImGuiCol_TextSelectedBg);
						ImGui::GetWindowDrawList()->AddRectFilled(p0, p1, col);
					}
				}
			}
			// Emit entire line to an expanded buffer (tabs -> spaces)
			for (std::size_t src = 0; src < line.size(); ++src) {
				char c = line[src];
				if (c == '\t') {
					std::size_t adv = (tabw - (rx_abs_draw % tabw));
					expanded.append(adv, ' ');
					rx_abs_draw += adv;
				} else {
					expanded.push_back(c);
					rx_abs_draw += 1;
				}
			}

			// Draw syntax-colored runs (text above background highlights)
			if (buf->SyntaxEnabled() && buf->Highlighter() && buf->Highlighter()->HasHighlighter()) {
				kte::LineHighlight lh = buf->Highlighter()->GetLine(
					*buf, static_cast<int>(i), buf->Version());
				// Sanitize spans defensively: clamp to [0, line.size()], ensure end>=start, drop empties
				struct SSpan {
					std::size_t s;
					std::size_t e;
					kte::TokenKind k;
				};
				std::vector<SSpan> spans;
				spans.reserve(lh.spans.size());
				const std::size_t line_len = line.size();
				for (const auto &sp: lh.spans) {
					int s_raw = sp.col_start;
					int e_raw = sp.col_end;
					if (e_raw < s_raw)
						std::swap(e_raw, s_raw);
					std::size_t s = static_cast<std::size_t>(std::max(
						0, std::min(s_raw, static_cast<int>(line_len))));
					std::size_t e = static_cast<std::size_t>(std::max(
						static_cast<int>(s), std::min(e_raw, static_cast<int>(line_len))));
					if (e <= s)
						continue;
					spans.push_back(SSpan{s, e, sp.kind});
				}
				std::sort(spans.begin(), spans.end(), [](const SSpan &a, const SSpan &b) {
					return a.s < b.s;
				});

				// Helper to convert a src column to expanded rx position
				auto src_to_rx_full = [&](std::size_t sidx) -> std::size_t {
					std::size_t rx = 0;
					for (std::size_t k = 0; k < sidx && k < line.size(); ++k) {
						rx += (line[k] == '\t') ? (tabw - (rx % tabw)) : 1;
					}
					return rx;
				};

				for (const auto &sp: spans) {
					std::size_t rx_s = src_to_rx_full(sp.s);
					std::size_t rx_e = src_to_rx_full(sp.e);
					if (rx_e <= coloffs_now)
						continue; // fully left of viewport
					// Clamp to visible portion and expanded length
					std::size_t draw_start = (rx_s > coloffs_now) ? rx_s : coloffs_now;
					if (draw_start >= expanded.size())
						continue; // fully right of expanded text
					std::size_t draw_end = std::min<std::size_t>(rx_e, expanded.size());
					if (draw_end <= draw_start)
						continue;
					// Screen position is relative to coloffs_now
					std::size_t screen_x = draw_start - coloffs_now;
					ImU32 col = ImGui::GetColorU32(kte::SyntaxInk(sp.k));
					ImVec2 p = ImVec2(line_pos.x + static_cast<float>(screen_x) * space_w,
					                  line_pos.y);
					ImGui::GetWindowDrawList()->AddText(
						p, col, expanded.c_str() + draw_start, expanded.c_str() + draw_end);
				}
				// We drew text via draw list (no layout advance). Manually advance the cursor to the next line.
				// Use row_h (with spacing) to match click calculation and ensure consistent line positions.
				ImGui::SetCursorScreenPos(ImVec2(line_pos.x, line_pos.y + row_h));
			} else {
				// No syntax: draw as one run, accounting for horizontal scroll offset
				if (coloffs_now < expanded.size()) {
					ImVec2 p = ImVec2(line_pos.x, line_pos.y);
					ImGui::GetWindowDrawList()->AddText(
						p, ImGui::GetColorU32(ImGuiCol_Text),
						expanded.c_str() + coloffs_now);
					ImGui::SetCursorScreenPos(ImVec2(line_pos.x, line_pos.y + row_h));
				} else {
					// Line is fully scrolled out of view horizontally
					ImGui::SetCursorScreenPos(ImVec2(line_pos.x, line_pos.y + row_h));
				}
			}

			// Draw a visible cursor indicator on the current line
			if (i == cy) {
				// Compute rendered X (rx) from source column with tab expansion
				std::size_t rx_abs = 0;
				for (std::size_t k = 0; k < std::min(cx, line.size()); ++k) {
					if (line[k] == '\t')
						rx_abs += (tabw - (rx_abs % tabw));
					else
						rx_abs += 1;
				}
				// Convert to viewport x by subtracting horizontal col offset
				std::size_t rx_viewport = (rx_abs > coloffs_now) ? (rx_abs - coloffs_now) : 0;
				// For proportional fonts (Linux GUI), avoid accumulating drift by computing
				// the exact pixel width of the expanded substring up to the cursor.
				// expanded contains the line with tabs expanded to spaces and is what we draw.
				float cursor_px = 0.0f;
				if (rx_viewport > 0 && coloffs_now < expanded.size()) {
					std::size_t start = coloffs_now;
					std::size_t end   = std::min(expanded.size(), start + rx_viewport);
					// Measure substring width in pixels
					ImVec2 sz = ImGui::CalcTextSize(expanded.c_str() + start,
					                                expanded.c_str() + end);
					cursor_px = sz.x;
				}
				ImVec2 p0 = ImVec2(line_pos.x + cursor_px, line_pos.y);
				ImVec2 p1 = ImVec2(p0.x + space_w, p0.y + line_h);
				ImU32 col = IM_COL32(200, 200, 255, 128); // soft highlight
				ImGui::GetWindowDrawList()->AddRectFilled(p0, p1, col);
			}
		}
		// Synchronize cursor and scrolling after rendering all lines so content size is known.
		{
			float child_h_actual = ImGui::GetWindowHeight();
			float child_w_actual = ImGui::GetWindowWidth();
			float scroll_y_now   = ImGui::GetScrollY();
			float scroll_x_now   = ImGui::GetScrollX();

			long first_row = static_cast<long>(scroll_y_now / row_h);
			long vis_rows  = static_cast<long>(std::round(child_h_actual / row_h));
			if (vis_rows < 1)
				vis_rows = 1;
			long last_row = first_row + vis_rows - 1;

			long cyr = static_cast<long>(cy);
			if (cyr < first_row) {
				float target = static_cast<float>(cyr) * row_h;
				if (target < 0.f)
					target = 0.f;
				float max_y = ImGui::GetScrollMaxY();
				if (max_y >= 0.f && target > max_y)
					target = max_y;
				ImGui::SetScrollY(target);
				first_row = static_cast<long>(target / row_h);
				last_row  = first_row + vis_rows - 1;
			} else if (cyr > last_row) {
				long new_first = cyr - vis_rows + 1;
				if (new_first < 0)
					new_first = 0;
				float target = static_cast<float>(new_first) * row_h;
				float max_y  = ImGui::GetScrollMaxY();
				if (target < 0.f)
					target = 0.f;
				if (max_y >= 0.f && target > max_y)
					target = max_y;
				ImGui::SetScrollY(target);
				first_row = static_cast<long>(target / row_h);
				last_row  = first_row + vis_rows - 1;
			}

			// Horizontal scroll: ensure cursor column is visible
			long vis_cols = static_cast<long>(std::round(child_w_actual / space_w));
			if (vis_cols < 1)
				vis_cols = 1;
			long first_col = static_cast<long>(scroll_x_now / space_w);
			long last_col  = first_col + vis_cols - 1;

			std::size_t cursor_rx = 0;
			if (cy < lines.size()) {
				std::string cur_line   = static_cast<std::string>(lines[cy]);
				const std::size_t tabw = 8;
				for (std::size_t i = 0; i < cx && i < cur_line.size(); ++i) {
					if (cur_line[i] == '\t') {
						cursor_rx += tabw - (cursor_rx % tabw);
					} else {
						cursor_rx += 1;
					}
				}
			}
			long cxr = static_cast<long>(cursor_rx);
			if (cxr < first_col || cxr > last_col) {
				float target_x = static_cast<float>(cxr) * space_w;
				target_x       -= (child_w_actual / 2.0f);
				if (target_x < 0.f)
					target_x = 0.f;
				float max_x = ImGui::GetScrollMaxX();
				if (max_x >= 0.f && target_x > max_x)
					target_x = max_x;
				ImGui::SetScrollX(target_x);
			}

			if (buf->SyntaxEnabled() && buf->Highlighter() && buf->Highlighter()->HasHighlighter()) {
				int fr = static_cast<int>(std::max(0L, first_row));
				int rc = static_cast<int>(std::max(1L, vis_rows));
				buf->Highlighter()->PrefetchViewport(*buf, fr, rc, buf->Version());
			}
		}
		ImGui::EndChild();
		ImGui::PopStyleVar(2); // WindowPadding, ItemSpacing

		// Status bar area starting right after the scroll child
		ImVec2 win_pos = ImGui::GetWindowPos();
		ImVec2 win_sz  = ImGui::GetWindowSize();
		float x0       = win_pos.x;
		float x1       = win_pos.x + win_sz.x;
		float y0       = ImGui::GetCursorScreenPos().y;
		float bar_h    = real_bar_h;

		ImVec2 p0(x0, y0);
		ImVec2 p1(x1, y0 + bar_h);
		ImU32 bg_col = ImGui::GetColorU32(ImGuiCol_HeaderActive);
		ImGui::GetWindowDrawList()->AddRectFilled(p0, p1, bg_col);

		// If a prompt is active, replace the entire status bar with the prompt text
		if (ed.PromptActive()) {
			std::string label = ed.PromptLabel();
			std::string ptext = ed.PromptText();
			auto kind         = ed.CurrentPromptKind();
			if (kind == Editor::PromptKind::OpenFile || kind == Editor::PromptKind::SaveAs ||
			    kind == Editor::PromptKind::Chdir) {
				const char *home_c = std::getenv("HOME");
				if (home_c && *home_c) {
					std::string home(home_c);
					if (ptext.rfind(home, 0) == 0) {
						std::string rest = ptext.substr(home.size());
						if (rest.empty())
							ptext = "~";
						else if (!rest.empty() && (rest[0] == '/' || rest[0] == '\\'))
							ptext = std::string("~") + rest;
					}
				}
			}

			float pad     = 6.f;
			float left_x  = p0.x + pad;
			float right_x = p1.x - pad;
			float max_px  = std::max(0.0f, right_x - left_x);

			std::string prefix;
			if (kind == Editor::PromptKind::Command) {
				prefix = ": ";
			} else if (!label.empty()) {
				prefix = label + ": ";
			}

			// Compose showing right-end of filename portion when too long for space
			std::string final_msg;
			ImVec2 prefix_sz = ImGui::CalcTextSize(prefix.c_str());
			float avail_px   = std::max(0.0f, max_px - prefix_sz.x);
			if ((kind == Editor::PromptKind::OpenFile || kind == Editor::PromptKind::SaveAs || kind ==
			     Editor::PromptKind::Chdir) && avail_px > 0.0f) {
				// Trim from left until it fits by pixel width
				std::string tail = ptext;
				ImVec2 tail_sz   = ImGui::CalcTextSize(tail.c_str());
				if (tail_sz.x > avail_px) {
					// Remove leading chars until it fits
					// Use a simple loop; text lengths are small here
					size_t start = 0;
					// To avoid O(n^2) worst-case, remove chunks
					while (start < tail.size()) {
						// Estimate how many chars to skip based on ratio
						float ratio = tail_sz.x / avail_px;
						size_t skip = ratio > 1.5f
							              ? std::min(tail.size() - start,
								              (size_t) std::max<size_t>(
									              1, (size_t) (tail.size() / 4)))
							              : 1;
						start                 += skip;
						std::string candidate = tail.substr(start);
						ImVec2 cand_sz        = ImGui::CalcTextSize(candidate.c_str());
						if (cand_sz.x <= avail_px) {
							tail    = candidate;
							tail_sz = cand_sz;
							break;
						}
					}
					if (ImGui::CalcTextSize(tail.c_str()).x > avail_px && !tail.empty()) {
						// As a last resort, ensure fit by chopping exactly
						// binary reduce
						size_t lo = 0, hi = tail.size();
						while (lo < hi) {
							size_t mid       = (lo + hi) / 2;
							std::string cand = tail.substr(mid);
							if (ImGui::CalcTextSize(cand.c_str()).x <= avail_px)
								hi = mid;
							else
								lo = mid + 1;
						}
						tail = tail.substr(lo);
					}
				}
				final_msg = prefix + tail;
			} else {
				final_msg = prefix + ptext;
			}

			ImVec2 msg_sz = ImGui::CalcTextSize(final_msg.c_str());
			ImGui::PushClipRect(ImVec2(p0.x, p0.y), ImVec2(p1.x, p1.y), true);
			ImGui::SetCursorScreenPos(ImVec2(left_x, y0 + (bar_h - msg_sz.y) * 0.5f));
			ImGui::TextUnformatted(final_msg.c_str());
			ImGui::PopClipRect();
		} else {
			// Build left text
			std::string left;
			left.reserve(256);
			left += "kge"; // GUI app name
			left += " ";
			left += KTE_VERSION_STR;
			std::string fname;
			try {
				fname = ed.DisplayNameFor(*buf);
			} catch (...) {
				fname = buf->Filename();
				try {
					fname = std::filesystem::path(fname).filename().string();
				} catch (...) {}
			}
			left += "  ";
			// Insert buffer position prefix "[x/N] " before filename
			{
				std::size_t total = ed.BufferCount();
				if (total > 0) {
					std::size_t idx1 = ed.CurrentBufferIndex() + 1; // 1-based for display
					left             += "[";
					left             += std::to_string(static_cast<unsigned long long>(idx1));
					left             += "/";
					left             += std::to_string(static_cast<unsigned long long>(total));
					left             += "] ";
				}
			}
			left += fname;
			if (buf->Dirty())
				left += " *";
			// Append total line count as "<n>L"
			{
				unsigned long lcount = static_cast<unsigned long>(buf->Rows().size());
				left                 += " ";
				left                 += std::to_string(lcount);
				left                 += "L";
			}

			// Build right text (cursor/mark)
			int row1       = static_cast<int>(buf->Cury()) + 1;
			int col1       = static_cast<int>(buf->Curx()) + 1;
			bool have_mark = buf->MarkSet();
			int mrow1      = have_mark ? static_cast<int>(buf->MarkCury()) + 1 : 0;
			int mcol1      = have_mark ? static_cast<int>(buf->MarkCurx()) + 1 : 0;
			char rbuf[128];
			if (have_mark)
				std::snprintf(rbuf, sizeof(rbuf), "%d,%d | M: %d,%d", row1, col1, mrow1, mcol1);
			else
				std::snprintf(rbuf, sizeof(rbuf), "%d,%d | M: not set", row1, col1);
			std::string right = rbuf;

			// Middle message: if a prompt is active, show "Label: text"; otherwise show status
			std::string msg;
			if (ed.PromptActive()) {
				msg = ed.PromptLabel();
				if (!msg.empty())
					msg += ": ";
				msg += ed.PromptText();
			} else {
				msg = ed.Status();
			}

			// Measurements
			ImVec2 left_sz  = ImGui::CalcTextSize(left.c_str());
			ImVec2 right_sz = ImGui::CalcTextSize(right.c_str());
			float pad       = 6.f;
			float left_x    = p0.x + pad;
			float right_x   = p1.x - pad - right_sz.x;
			if (right_x < left_x + left_sz.x + pad) {
				// Not enough room; clip left to fit
				float max_left = std::max(0.0f, right_x - left_x - pad);
				if (max_left < left_sz.x && max_left > 10.0f) {
					// Render a clipped left using a child region
					ImGui::SetCursorScreenPos(ImVec2(left_x, y0 + (bar_h - left_sz.y) * 0.5f));
					ImGui::PushClipRect(ImVec2(left_x, y0), ImVec2(right_x - pad, y0 + bar_h),
					                    true);
					ImGui::TextUnformatted(left.c_str());
					ImGui::PopClipRect();
				}
			} else {
				// Draw left normally
				ImGui::SetCursorScreenPos(ImVec2(left_x, y0 + (bar_h - left_sz.y) * 0.5f));
				ImGui::TextUnformatted(left.c_str());
			}

			// Draw right
			ImGui::SetCursorScreenPos(ImVec2(std::max(right_x, left_x),
			                                 y0 + (bar_h - right_sz.y) * 0.5f));
			ImGui::TextUnformatted(right.c_str());

			// Draw middle message centered in remaining space
			if (!msg.empty()) {
				float mid_left  = left_x + left_sz.x + pad;
				float mid_right = std::max(right_x - pad, mid_left);
				float mid_w     = std::max(0.0f, mid_right - mid_left);
				if (mid_w > 1.0f) {
					ImVec2 msg_sz = ImGui::CalcTextSize(msg.c_str());
					float msg_x   = mid_left + std::max(0.0f, (mid_w - msg_sz.x) * 0.5f);
					// Clip to middle region
					ImGui::PushClipRect(ImVec2(mid_left, y0), ImVec2(mid_right, y0 + bar_h), true);
					ImGui::SetCursorScreenPos(ImVec2(msg_x, y0 + (bar_h - msg_sz.y) * 0.5f));
					ImGui::TextUnformatted(msg.c_str());
					ImGui::PopClipRect();
				}
			}
		}
	}

	ImGui::End();
	ImGui::PopStyleVar(3);

	// --- Visual File Picker overlay (GUI only) ---
	if (ed.FilePickerVisible()) {
		// Centered popup-style window that always fits within the current viewport
		ImGuiViewport *vp2 = ImGui::GetMainViewport();

		// Desired size, min size, and margins
		const ImVec2 want(800.0f, 500.0f);
		const ImVec2 min_sz(240.0f, 160.0f);
		const float margin = 20.0f; // space from viewport edges

		// Compute the maximum allowed size (viewport minus margins) and make sure it's not negative
		ImVec2 max_sz(std::max(32.0f, vp2->Size.x - 2.0f * margin),
		              std::max(32.0f, vp2->Size.y - 2.0f * margin));

		// Clamp desired size to [min_sz, max_sz]
		ImVec2 size(std::min(want.x, max_sz.x), std::min(want.y, max_sz.y));
		size.x = std::max(size.x, std::min(min_sz.x, max_sz.x));
		size.y = std::max(size.y, std::min(min_sz.y, max_sz.y));

		// Center within the viewport using the final size
		ImVec2 pos(vp2->Pos.x + std::max(margin, (vp2->Size.x - size.x) * 0.5f),
		           vp2->Pos.y + std::max(margin, (vp2->Size.y - size.y) * 0.5f));

		// On HiDPI displays (macOS Retina), ensure integer pixel alignment to avoid
		// potential hit-test vs draw mismatches from sub-pixel positions.
		pos.x  = std::floor(pos.x + 0.5f);
		pos.y  = std::floor(pos.y + 0.5f);
		size.x = std::floor(size.x + 0.5f);
		size.y = std::floor(size.y + 0.5f);

		ImGui::SetNextWindowPos(pos, ImGuiCond_Always);
		ImGui::SetNextWindowSize(size, ImGuiCond_Always);
		ImGuiWindowFlags wflags = ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse |
		                          ImGuiWindowFlags_NoDocking;
		bool open = true;
		if (ImGui::Begin("File Picker", &open, wflags)) {
			// Current directory
			std::string curdir = ed.FilePickerDir();
			if (curdir.empty()) {
				try {
					curdir = std::filesystem::current_path().string();
				} catch (...) {
					curdir = ".";
				}
				ed.SetFilePickerDir(curdir);
			}
			ImGui::TextUnformatted(curdir.c_str());
			ImGui::SameLine();
			if (ImGui::Button("Up")) {
				try {
					std::filesystem::path p(curdir);
					if (p.has_parent_path()) {
						ed.SetFilePickerDir(p.parent_path().string());
					}
				} catch (...) {}
			}
			ImGui::SameLine();
			if (ImGui::Button("Close")) {
				ed.SetFilePickerVisible(false);
			}

			ImGui::Separator();

			// Header
			ImGui::TextUnformatted("Name");
			ImGui::Separator();

			// Scrollable list
			ImGui::BeginChild("picker-list", ImVec2(0, 0), true);

			// Build entries: directories first then files, alphabetical
			struct Entry {
				std::string name;
				std::filesystem::path path;
				bool is_dir;
			};
			std::vector<Entry> entries;
			entries.reserve(256);
			// Optional parent entry
			try {
				std::filesystem::path base(curdir);
				std::error_code ec;
				for (auto it = std::filesystem::directory_iterator(base, ec);
				     !ec && it != std::filesystem::directory_iterator(); it.increment(ec)) {
					const auto &p = it->path();
					std::string nm;
					try {
						nm = p.filename().string();
					} catch (...) {
						continue;
					}
					if (nm == "." || nm == "..")
						continue;
					bool is_dir = false;
					std::error_code ec2;
					is_dir = it->is_directory(ec2);
					entries.push_back({nm, p, is_dir});
				}
			} catch (...) {
				// ignore listing errors; show empty
			}
			std::sort(entries.begin(), entries.end(), [](const Entry &a, const Entry &b) {
				if (a.is_dir != b.is_dir)
					return a.is_dir && !b.is_dir;
				return a.name < b.name;
			});

			// Draw rows
			int idx = 0;
			for (const auto &e: entries) {
				ImGui::PushID(idx++); // ensure unique/stable IDs even if names repeat
				std::string label;
				label.reserve(e.name.size() + 4);
				if (e.is_dir)
					label += "[";
				label += e.name;
				if (e.is_dir)
					label += "]";

				// Render selectable row
				ImGui::Selectable(label.c_str(), false, ImGuiSelectableFlags_AllowDoubleClick);

				// Activate based strictly on hover + mouse, to avoid any off-by-one due to click routing
				if (ImGui::IsItemHovered()) {
					if (e.is_dir && ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left)) {
						// Enter directory on double-click
						ed.SetFilePickerDir(e.path.string());
					} else if (!e.is_dir && ImGui::IsMouseClicked(ImGuiMouseButton_Left)) {
						// Open file on single click
						std::string err;
						if (!ed.OpenFile(e.path.string(), err)) {
							ed.SetStatus(std::string("open: ") + err);
						} else {
							ed.SetStatus(std::string("Opened: ") + e.name);
						}
						ed.SetFilePickerVisible(false);
					}
				}
				ImGui::PopID();
			}

			ImGui::EndChild();
		}
		ImGui::End();
		if (!open) {
			ed.SetFilePickerVisible(false);
		}
	}
}