/* ES40 emulator.
 * Copyright (C) 2007-2008 by the ES40 Emulator Project
 * Copyright (C) 2020-2025 by gdwnldsKSC
 *
 * WWW    : https://github.com/gdwnldsKSC/es40
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
 * 02110-1301, USA.
 */

/**
 * \file
 * SDL main thread support implementation for macOS.
 */

#include "../StdAfx.h"

#if defined(HAVE_SDL)

#include "sdl_main_thread.h"
#include "../System.h"
#include "../Serial.h"
#include "../Keyboard.h"
#include "Thread.h"
#include <SDL3/SDL.h>
#include <string.h>
#include <unistd.h>  // for sleep()

// Forward declarations
extern class bx_gui_c* bx_gui;

// SDL globals from sdl.cpp
extern SDL_Window*   sdl_window;
extern SDL_Renderer* sdl_renderer;
extern SDL_Texture*  sdl_texture;

#ifdef __APPLE__
// Shared framebuffer for thread-safe rendering
extern u32* shared_framebuffer;
extern unsigned shared_fb_width;
extern unsigned shared_fb_height;
extern std::atomic<bool> shared_fb_dirty;
#endif

/**
 * Check if SDL is configured as the GUI in the configuration.
 * In the ES40 configuration, the GUI is specified at the top level as "gui = sdl { ... }".
 * The bx_gui global will be set if SDL was loaded as a plugin.
 */
bool sdl_is_configured_gui(CConfigurator* cfg)
{
	if (!cfg)
		return false;

	// The GUI plugin is loaded during configuration initialization.
	// By the time we check, if SDL is configured, the plugin will have been loaded
	// and bx_gui will point to the SDL GUI instance.
	//
	// We can check if bx_gui exists and is an SDL GUI by checking if SDL was
	// selected in the configuration. The configuration file has "gui = sdl { ... }"
	// at the top level.

	// Simple approach: check if the "gui" configurator exists and its class is SDL
	// The Configurator stores the device name in myValue.
	// However, we don't have direct access to walk the tree, so we'll rely on
	// the fact that if SDL GUI is configured, it will be loaded before we get here.

	// For now, we'll use a simple heuristic: if HAVE_SDL is defined and
	// we're on macOS, assume SDL might be in use and initialize it.
	// The actual check happens in the S3Trio64 thread via SDL_WasInit().

#ifdef __APPLE__
	// On macOS, always try to initialize SDL if HAVE_SDL is defined,
	// since the cost is low and it prevents the threading issue.
	return true;
#else
	return false;
#endif
}

/**
 * Initialize SDL video subsystem on the main thread if needed.
 * On macOS, this MUST be called from the main thread before spawning
 * worker threads that will use SDL.
 *
 * Returns true if SDL was initialized (or already initialized), false otherwise.
 */
bool sdl_init_on_main_thread_if_needed(CConfigurator* cfg)
{
#ifdef __APPLE__
	// On macOS, SDL_Init(SDL_INIT_VIDEO) must be called from the main thread.
	// We initialize it unconditionally if HAVE_SDL is defined, since:
	// 1. If SDL GUI is not configured, this is harmless (small memory cost)
	// 2. If SDL GUI is configured, this fixes the threading issue
	// 3. The alternative of checking configuration is complex and error-prone

	// Check if SDL is already initialized
	if (SDL_WasInit(SDL_INIT_VIDEO))
	{
		printf("%%SDL-I-INIT: SDL video subsystem already initialized on main thread\n");
		return true;
	}

	// Initialize SDL video subsystem on the main thread
	printf("%%SDL-I-INIT: Initializing SDL video subsystem on main thread (macOS requirement)\n");
	if (!SDL_Init(SDL_INIT_VIDEO))
	{
		fprintf(stderr, "%%SDL-W-INIT: Failed to initialize SDL3 video subsystem on main thread: %s\n", SDL_GetError());
		fprintf(stderr, "%%SDL-W-INIT: If SDL GUI is configured, the emulator may fail to start.\n");
		return false;
	}

	printf("%%SDL-I-INIT: SDL video subsystem initialized successfully\n");
	return true;
#else
	// On non-macOS platforms, SDL can be initialized from any thread
	return false;
#endif
}

/**
 * Create SDL window on the main thread (macOS only).
 * On macOS, SDL_CreateWindow() must be called from the main thread.
 * This creates a default 640x480 window that will be updated by the S3 thread later.
 */
void sdl_create_window_on_main_thread_if_needed()
{
#ifdef __APPLE__
	if (!SDL_WasInit(SDL_INIT_VIDEO))
	{
		// SDL not initialized, can't create window
		return;
	}

	// Import SDL window/renderer/texture globals from sdl.cpp
	// These are defined in sdl.cpp and used by bx_sdl_gui_c
	extern SDL_Window*   sdl_window;
	extern SDL_Renderer* sdl_renderer;
	extern SDL_Texture*  sdl_texture;

	// Only create if not already created
	if (sdl_window)
	{
		printf("%%SDL-I-WINDOW: SDL window already exists, skipping creation\n");
		return;
	}

	printf("%%SDL-I-WINDOW: Creating SDL window on main thread (macOS requirement)\n");

	// Create window at default 640x480 resolution
	// The S3 thread will call dimension_update() later to resize if needed
	const char* title = "ES40 Emulator - Ctrl+Alt+End sends Ctrl+Alt+Del - Ctrl+Alt+Home resets window";
	int width = 640;
	int height = 480;

	sdl_window = SDL_CreateWindow(title, width, height,
		SDL_WINDOW_RESIZABLE | SDL_WINDOW_HIGH_PIXEL_DENSITY);

	if (!sdl_window)
	{
		fprintf(stderr, "%%SDL-E-WINDOW: Failed to create SDL window on main thread: %s\n", SDL_GetError());
		fprintf(stderr, "%%SDL-E-WINDOW: The emulator will likely fail when S3 thread starts.\n");
		return;
	}

	printf("%%SDL-I-WINDOW: SDL window created successfully, creating renderer\n");

	// Create renderer
	sdl_renderer = SDL_CreateRenderer(sdl_window, NULL);
	if (!sdl_renderer)
	{
		fprintf(stderr, "%%SDL-E-RENDERER: Failed to create SDL renderer: %s\n", SDL_GetError());
		SDL_DestroyWindow(sdl_window);
		sdl_window = NULL;
		return;
	}

	printf("%%SDL-I-WINDOW: SDL renderer created, setting logical presentation\n");

	// Set up logical presentation - use STRETCH to avoid black borders
	SDL_SetRenderLogicalPresentation(sdl_renderer, width, height,
		SDL_LOGICAL_PRESENTATION_STRETCH);

	// Create initial texture
	sdl_texture = SDL_CreateTexture(sdl_renderer,
		SDL_PIXELFORMAT_ARGB8888,
		SDL_TEXTUREACCESS_STREAMING,
		width, height);

	if (!sdl_texture)
	{
		fprintf(stderr, "%%SDL-E-TEXTURE: Failed to create SDL texture: %s\n", SDL_GetError());
		SDL_DestroyRenderer(sdl_renderer);
		SDL_DestroyWindow(sdl_window);
		sdl_renderer = NULL;
		sdl_window = NULL;
		return;
	}

	SDL_SetTextureScaleMode(sdl_texture, SDL_SCALEMODE_LINEAR);
	SDL_RaiseWindow(sdl_window);

	printf("%%SDL-I-WINDOW: SDL window, renderer, and texture created successfully on main thread\n");

	// IMMEDIATE test: Fill window with RED briefly to verify rendering works
	if (sdl_renderer && sdl_texture)
	{
		printf("%%SDL-IMMEDIATE-TEST: Filling window with RED...\n");
		u32 test_buffer[640 * 480];

		// Show RED for 1 second
		for (int i = 0; i < 640 * 480; i++)
			test_buffer[i] = 0xFFFF0000;  // Red (ARGB format)
		SDL_UpdateTexture(sdl_texture, NULL, test_buffer, 640 * 4);
		SDL_RenderClear(sdl_renderer);
		SDL_RenderTexture(sdl_renderer, sdl_texture, NULL, NULL);
		SDL_RenderPresent(sdl_renderer);
		printf("%%SDL-IMMEDIATE-TEST: RED render complete. Window should be RED now.\n");
		sleep(1);

		// Clear to BLACK before continuing
		printf("%%SDL-IMMEDIATE-TEST: Clearing to BLACK, ready for VGA output...\n");
		for (int i = 0; i < 640 * 480; i++)
			test_buffer[i] = 0xFF000000;  // Black (ARGB format)
		SDL_UpdateTexture(sdl_texture, NULL, test_buffer, 640 * 4);
		SDL_SetRenderDrawColor(sdl_renderer, 0, 0, 0, 255);
		SDL_RenderClear(sdl_renderer);
		SDL_RenderTexture(sdl_renderer, sdl_texture, NULL, NULL);
		SDL_RenderPresent(sdl_renderer);
		printf("%%SDL-IMMEDIATE-TEST: Window cleared, waiting for VGA to output graphics...\n");
	}
#endif
}

/**
 * Cleanup SDL on main thread.
 */
void sdl_cleanup_main_thread()
{
#ifdef __APPLE__
	if (SDL_WasInit(SDL_INIT_VIDEO))
	{
		// Note: SDL_Quit() will be called by the GUI's exit() method
		// We don't call it here to avoid double-cleanup
	}
#endif
}

#ifdef __APPLE__
// Helper class to run CSystem::Run() in a background thread
class CSystemRunner : public CRunnable
{
public:
	CSystem* system;
	CSystemRunner(CSystem* sys) : system(sys) {}

	virtual void run() override
	{
		system->Run();
	}
};
#endif

/**
 * Run the emulator with proper SDL main thread handling.
 *
 * On macOS with SDL GUI:
 *   - Spawns CSystem::Run() in a background thread
 *   - Uses main thread for SDL event pumping (required by macOS)
 *   - When CSystem thread exits, main thread returns
 *
 * On other platforms or without SDL:
 *   - Just calls CSystem::Run() directly (backward compatible)
 */
void sdl_aware_run(CSystem* sys)
{
#ifdef __APPLE__
	// On macOS, if SDL is initialized, we need to keep the main thread
	// available for SDL operations. Run CSystem::Run() in a background thread.
	if (SDL_WasInit(SDL_INIT_VIDEO))
	{
		printf("%%SDL-I-THREAD: Running CSystem::Run() in background thread (macOS SDL requirement)\n");

		CSystemRunner runner(sys);
		CThread* systemThread = new CThread("system");
		systemThread->start(runner);

		// Main thread stays here and handles ALL SDL events + rendering
		// On macOS, SDL_PollEvent AND rendering must be on main thread
		printf("%%SDL-I-THREAD: Main thread now handling SDL events and rendering\n");

		// Ensure SDL is ready before starting render loop
		if (!sdl_texture || !sdl_renderer)
		{
			fprintf(stderr, "%%SDL-E-THREAD: SDL not properly initialized, cannot start render loop\n");
			delete systemThread;
			return;
		}

		// Clear screen to black before starting
		u32 black_buffer[640 * 480];
		memset(black_buffer, 0x00, sizeof(black_buffer));
		SDL_UpdateTexture(sdl_texture, NULL, black_buffer, 640 * 4);
		SDL_RenderClear(sdl_renderer);
		SDL_RenderTexture(sdl_renderer, sdl_texture, NULL, NULL);
		SDL_RenderPresent(sdl_renderer);

		// Small delay to let Metal backend fully initialize
		CThread::sleep(100);

		// Main event loop: handle SDL events + render while system thread runs
		SDL_Event event;
		unsigned last_texture_width = 0;  // Start at 0 to force initial texture creation
		unsigned last_texture_height = 0;

		// Access to keyboard controller for input forwarding
		extern CKeyboard* theKeyboard;

		// Mouse state (from sdl.cpp)
		extern int sdl_grab;
		static int sdl_mouse_button_state = 0;
		static double sdl_mouse_accum_x = 0.0;
		static double sdl_mouse_accum_y = 0.0;
		static bool sdl_swallow_keys = false;
		static bool sdl_swallow_end_release = false;
		static bool sdl_swallow_home_release = false;
		static bool sdl_swallow_pageup_release = false;
		static bool sdl_swallow_pagedown_release = false;

		// SDL scancode to BX key conversion (complete mapping from sdl.cpp)
		auto sdl_scan_to_bx_key = [](SDL_Scancode sym) -> u32 {
			#define BX_KEY_UNHANDLED 0xFFFFFFFF
			switch (sym) {
				// Basic keys
				case SDL_SCANCODE_BACKSPACE: return BX_KEY_BACKSPACE;
				case SDL_SCANCODE_TAB: return BX_KEY_TAB;
				case SDL_SCANCODE_RETURN: return BX_KEY_ENTER;
				case SDL_SCANCODE_PAUSE: return BX_KEY_PAUSE;
				case SDL_SCANCODE_ESCAPE: return BX_KEY_ESC;
				case SDL_SCANCODE_SPACE: return BX_KEY_SPACE;
				case SDL_SCANCODE_APOSTROPHE: return BX_KEY_SINGLE_QUOTE;
				case SDL_SCANCODE_COMMA: return BX_KEY_COMMA;
				case SDL_SCANCODE_MINUS: return BX_KEY_MINUS;
				case SDL_SCANCODE_PERIOD: return BX_KEY_PERIOD;
				case SDL_SCANCODE_SLASH: return BX_KEY_SLASH;

				// Numbers
				case SDL_SCANCODE_0: return BX_KEY_0;
				case SDL_SCANCODE_1: return BX_KEY_1;
				case SDL_SCANCODE_2: return BX_KEY_2;
				case SDL_SCANCODE_3: return BX_KEY_3;
				case SDL_SCANCODE_4: return BX_KEY_4;
				case SDL_SCANCODE_5: return BX_KEY_5;
				case SDL_SCANCODE_6: return BX_KEY_6;
				case SDL_SCANCODE_7: return BX_KEY_7;
				case SDL_SCANCODE_8: return BX_KEY_8;
				case SDL_SCANCODE_9: return BX_KEY_9;

				case SDL_SCANCODE_SEMICOLON: return BX_KEY_SEMICOLON;
				case SDL_SCANCODE_EQUALS: return BX_KEY_EQUALS;
				case SDL_SCANCODE_LEFTBRACKET: return BX_KEY_LEFT_BRACKET;
				// MacBook Air: Map both backslash keys to BX_KEY_BACKSLASH
				case SDL_SCANCODE_BACKSLASH: return BX_KEY_BACKSLASH;
				case SDL_SCANCODE_NONUSBACKSLASH: return BX_KEY_BACKSLASH;
				case SDL_SCANCODE_RIGHTBRACKET: return BX_KEY_RIGHT_BRACKET;
				case SDL_SCANCODE_GRAVE: return BX_KEY_GRAVE;

				// Letters
				case SDL_SCANCODE_A: return BX_KEY_A;
				case SDL_SCANCODE_B: return BX_KEY_B;
				case SDL_SCANCODE_C: return BX_KEY_C;
				case SDL_SCANCODE_D: return BX_KEY_D;
				case SDL_SCANCODE_E: return BX_KEY_E;
				case SDL_SCANCODE_F: return BX_KEY_F;
				case SDL_SCANCODE_G: return BX_KEY_G;
				case SDL_SCANCODE_H: return BX_KEY_H;
				case SDL_SCANCODE_I: return BX_KEY_I;
				case SDL_SCANCODE_J: return BX_KEY_J;
				case SDL_SCANCODE_K: return BX_KEY_K;
				case SDL_SCANCODE_L: return BX_KEY_L;
				case SDL_SCANCODE_M: return BX_KEY_M;
				case SDL_SCANCODE_N: return BX_KEY_N;
				case SDL_SCANCODE_O: return BX_KEY_O;
				case SDL_SCANCODE_P: return BX_KEY_P;
				case SDL_SCANCODE_Q: return BX_KEY_Q;
				case SDL_SCANCODE_R: return BX_KEY_R;
				case SDL_SCANCODE_S: return BX_KEY_S;
				case SDL_SCANCODE_T: return BX_KEY_T;
				case SDL_SCANCODE_U: return BX_KEY_U;
				case SDL_SCANCODE_V: return BX_KEY_V;
				case SDL_SCANCODE_W: return BX_KEY_W;
				case SDL_SCANCODE_X: return BX_KEY_X;
				case SDL_SCANCODE_Y: return BX_KEY_Y;
				case SDL_SCANCODE_Z: return BX_KEY_Z;

				case SDL_SCANCODE_DELETE: return BX_KEY_DELETE;

				// Keypad
				case SDL_SCANCODE_KP_0: return BX_KEY_KP_INSERT;
				case SDL_SCANCODE_KP_1: return BX_KEY_KP_END;
				case SDL_SCANCODE_KP_2: return BX_KEY_KP_DOWN;
				case SDL_SCANCODE_KP_3: return BX_KEY_KP_PAGE_DOWN;
				case SDL_SCANCODE_KP_4: return BX_KEY_KP_LEFT;
				case SDL_SCANCODE_KP_5: return BX_KEY_KP_5;
				case SDL_SCANCODE_KP_6: return BX_KEY_KP_RIGHT;
				case SDL_SCANCODE_KP_7: return BX_KEY_KP_HOME;
				case SDL_SCANCODE_KP_8: return BX_KEY_KP_UP;
				case SDL_SCANCODE_KP_9: return BX_KEY_KP_PAGE_UP;
				case SDL_SCANCODE_KP_PERIOD: return BX_KEY_KP_DELETE;
				case SDL_SCANCODE_KP_DIVIDE: return BX_KEY_KP_DIVIDE;
				case SDL_SCANCODE_KP_MULTIPLY: return BX_KEY_KP_MULTIPLY;
				case SDL_SCANCODE_KP_MINUS: return BX_KEY_KP_SUBTRACT;
				case SDL_SCANCODE_KP_PLUS: return BX_KEY_KP_ADD;
				case SDL_SCANCODE_KP_ENTER: return BX_KEY_KP_ENTER;

				// Arrows + Home/End pad
				case SDL_SCANCODE_UP: return BX_KEY_UP;
				case SDL_SCANCODE_DOWN: return BX_KEY_DOWN;
				case SDL_SCANCODE_RIGHT: return BX_KEY_RIGHT;
				case SDL_SCANCODE_LEFT: return BX_KEY_LEFT;
				case SDL_SCANCODE_INSERT: return BX_KEY_INSERT;
				case SDL_SCANCODE_HOME: return BX_KEY_HOME;
				case SDL_SCANCODE_END: return BX_KEY_END;
				case SDL_SCANCODE_PAGEUP: return BX_KEY_PAGE_UP;
				case SDL_SCANCODE_PAGEDOWN: return BX_KEY_PAGE_DOWN;

				// Function keys
				case SDL_SCANCODE_F1: return BX_KEY_F1;
				case SDL_SCANCODE_F2: return BX_KEY_F2;
				case SDL_SCANCODE_F3: return BX_KEY_F3;
				case SDL_SCANCODE_F4: return BX_KEY_F4;
				case SDL_SCANCODE_F5: return BX_KEY_F5;
				case SDL_SCANCODE_F6: return BX_KEY_F6;
				case SDL_SCANCODE_F7: return BX_KEY_F7;
				case SDL_SCANCODE_F8: return BX_KEY_F8;
				case SDL_SCANCODE_F9: return BX_KEY_F9;
				case SDL_SCANCODE_F10: return BX_KEY_F10;
				case SDL_SCANCODE_F11: return BX_KEY_F11;
				case SDL_SCANCODE_F12: return BX_KEY_F12;

				// Modifier keys
				case SDL_SCANCODE_NUMLOCKCLEAR: return BX_KEY_NUM_LOCK;
				case SDL_SCANCODE_CAPSLOCK: return BX_KEY_CAPS_LOCK;
				case SDL_SCANCODE_SCROLLLOCK: return BX_KEY_SCRL_LOCK;
				case SDL_SCANCODE_RSHIFT: return BX_KEY_SHIFT_R;
				case SDL_SCANCODE_LSHIFT: return BX_KEY_SHIFT_L;
				case SDL_SCANCODE_RCTRL: return BX_KEY_CTRL_R;
				case SDL_SCANCODE_LCTRL: return BX_KEY_CTRL_L;
				case SDL_SCANCODE_RALT: return BX_KEY_ALT_R;
				case SDL_SCANCODE_LALT: return BX_KEY_ALT_L;
				case SDL_SCANCODE_LGUI: return BX_KEY_WIN_L;
				case SDL_SCANCODE_RGUI: return BX_KEY_WIN_R;

				// Misc function keys
				case SDL_SCANCODE_PRINTSCREEN: return BX_KEY_PRINT;
				case SDL_SCANCODE_MENU: return BX_KEY_MENU;

				default: return BX_KEY_UNHANDLED;
			}
		};

		while (systemThread->isRunning())
		{
			// Process all pending events
			while (SDL_PollEvent(&event))
			{
				// Handle window close event
				if (event.type == SDL_EVENT_QUIT)
				{
					printf("%%SDL-I-THREAD: Quit event received on main thread\n");
					// Signal system to shutdown
					break;
				}

				// Handle window events (expose, resize, etc.)
				if (event.type == SDL_EVENT_WINDOW_EXPOSED && sdl_renderer && sdl_texture)
				{
					// Force redraw
					shared_fb_dirty.store(true, std::memory_order_release);
				}

				// Forward keyboard events to keyboard controller (for SRM OCP console)
				if (event.type == SDL_EVENT_KEY_DOWN && theKeyboard)
				{
					u32 bx_key = sdl_scan_to_bx_key(event.key.scancode);
					if (bx_key != BX_KEY_UNHANDLED)
					{
						theKeyboard->gen_scancode(bx_key);
					}
				}
				else if (event.type == SDL_EVENT_KEY_UP && theKeyboard)
				{
					u32 bx_key = sdl_scan_to_bx_key(event.key.scancode);
					if (bx_key != BX_KEY_UNHANDLED)
					{
						theKeyboard->gen_scancode(bx_key | BX_KEY_RELEASED);
					}
				}

				// Handle mouse motion
				if (event.type == SDL_EVENT_MOUSE_MOTION && theKeyboard)
				{
					if (sdl_grab)
					{
						// Get mouse speed and invert settings from sdl.cpp globals
						extern double mouse_speed;
						extern bool mouse_invert_x;
						extern bool mouse_invert_y;

						// PS/2 mouse Y is positive-up, SDL is positive-down; hence the
						// baseline Y negation. invert_x/y flip on top of that.
						double mx = (double)event.motion.xrel * mouse_speed;
						double my = -(double)event.motion.yrel * mouse_speed;
						sdl_mouse_accum_x += mouse_invert_x ? -mx : mx;
						sdl_mouse_accum_y += mouse_invert_y ? -my : my;

						int dx = (int)sdl_mouse_accum_x;
						int dy = (int)sdl_mouse_accum_y;

						if (dx != 0 || dy != 0)
						{
							sdl_mouse_accum_x -= dx;
							sdl_mouse_accum_y -= dy;
							theKeyboard->mouse_motion(dx, dy, 0, sdl_mouse_button_state);
						}
					}
				}

				// Handle mouse button clicks
				if (event.type == SDL_EVENT_MOUSE_BUTTON_DOWN || event.type == SDL_EVENT_MOUSE_BUTTON_UP)
				{
					if (!sdl_grab && theKeyboard)
					{
						if (event.type == SDL_EVENT_MOUSE_BUTTON_DOWN
							&& event.button.button == SDL_BUTTON_LEFT)
						{
							extern class bx_gui_c* bx_gui;
							if (bx_gui)
								bx_gui->mouse_enabled_changed(true);
						}
					}
					else if (theKeyboard)
					{
						int bitmask = 0;
						switch (event.button.button)
						{
						case SDL_BUTTON_LEFT:   bitmask = 0x01; break;
						case SDL_BUTTON_RIGHT:  bitmask = 0x02; break;
						case SDL_BUTTON_MIDDLE: bitmask = 0x04; break;
						default: break;
						}

						if (event.type == SDL_EVENT_MOUSE_BUTTON_DOWN)
							sdl_mouse_button_state |= bitmask;
						else
							sdl_mouse_button_state &= ~bitmask;

						theKeyboard->mouse_motion(0, 0, 0, sdl_mouse_button_state);
					}
				}

				// Handle mouse wheel
				if (event.type == SDL_EVENT_MOUSE_WHEEL && theKeyboard)
				{
					if (sdl_grab)
					{
						float wy = event.wheel.y;  // SDL3: float; +y = away from user (scroll up)
						if (event.wheel.direction == SDL_MOUSEWHEEL_FLIPPED)
							wy = -wy;
						int dz = (int)wy;
						if (dz != 0)
							theKeyboard->mouse_motion(0, 0, dz, sdl_mouse_button_state);
					}
				}

				// Handle window focus loss
				if (event.type == SDL_EVENT_WINDOW_FOCUS_LOST)
				{
					if (sdl_grab)
					{
						extern class bx_gui_c* bx_gui;
						if (bx_gui)
							bx_gui->mouse_enabled_changed(false);
					}
				}
			}

			// Check if dimensions changed and recreate texture if needed (macOS: must be on main thread)
			// Note: We start with last_texture_* = 0 to force initial creation when shared_fb is first allocated
			if (shared_framebuffer && shared_fb_width > 0 && shared_fb_height > 0 &&
			    (shared_fb_width != last_texture_width || shared_fb_height != last_texture_height))
			{
				printf("%%SDL-I-THREAD: Recreating texture on main thread: %ux%u -> %ux%u\n",
					last_texture_width, last_texture_height, shared_fb_width, shared_fb_height);

				if (sdl_texture)
				{
					SDL_DestroyTexture(sdl_texture);
					sdl_texture = NULL;
				}

				sdl_texture = SDL_CreateTexture(sdl_renderer,
					SDL_PIXELFORMAT_ARGB8888,
					SDL_TEXTUREACCESS_STREAMING,
					(int)shared_fb_width, (int)shared_fb_height);

				if (!sdl_texture)
				{
					fprintf(stderr, "%%SDL-E-TEXTURE: Failed to create texture %ux%u: %s\n",
						shared_fb_width, shared_fb_height, SDL_GetError());
				}
				else
				{
					SDL_SetTextureScaleMode(sdl_texture, SDL_SCALEMODE_LINEAR);
					SDL_SetRenderLogicalPresentation(sdl_renderer,
						(int)shared_fb_width, (int)shared_fb_height,
						SDL_LOGICAL_PRESENTATION_STRETCH);

					last_texture_width = shared_fb_width;
					last_texture_height = shared_fb_height;

					// Force redraw with new dimensions
					shared_fb_dirty.store(true, std::memory_order_release);
				}
			}

			// Check if S3 thread has updated the framebuffer
			if (shared_fb_dirty.load(std::memory_order_acquire))
			{
				// Render the shared framebuffer to the SDL window
				if (sdl_texture && sdl_renderer && shared_framebuffer)
				{
					// Clear dirty flag BEFORE rendering to avoid re-entry
					shared_fb_dirty.store(false, std::memory_order_release);

					// Update texture with new pixels
					if (SDL_UpdateTexture(sdl_texture, NULL, shared_framebuffer,
						(int)(shared_fb_width * sizeof(u32))))
					{
						// Render: clear -> draw texture -> present
						SDL_RenderClear(sdl_renderer);
						SDL_RenderTexture(sdl_renderer, sdl_texture, NULL, NULL);
						SDL_RenderPresent(sdl_renderer);
					}
				}
			}

			// Sleep a bit to not spin the CPU
			// 10ms = 100Hz render rate (matching VGA's 50Hz with some margin)
			CThread::sleep(10);
		}

		printf("%%SDL-I-THREAD: System thread exited, cleaning up\n");
		delete systemThread;
		return;
	}
#endif

	// Non-macOS or no SDL: run normally on main thread
	sys->Run();
}

#endif // HAVE_SDL
