#import "modules/std.sx";
#import "modules/allocators.sx";
#import "modules/compiler.sx";
#import "modules/opengl.sx";
#import "modules/sdl3.sx";
#import "modules/wasm.sx";
#import "modules/ui/types.sx";
#import "modules/ui/events.sx";
#import "modules/platform/types.sx";
#import "modules/platform/api.sx";

g_sdl_plat : *SdlPlatform = null;

chdir :: (path: [*]u8) -> s32 #foreign;
SDL_GetBasePath :: () -> [*]u8 #foreign;

// A macOS `.app` launched via Finder / `open` starts with CWD=`/`, so a
// game's CWD-relative asset loads (`read_file_bytes("assets/...")`) miss and
// the app crashes loading fonts/textures. SDL's base path is the directory
// holding the executable — inside the `.app`, where the bundler copied
// `assets/`. chdir there, but ONLY when actually running from within a `.app`
// bundle, so the `sx run` dev flow (binary not bundled, assets in the project
// CWD) is left alone. Mirrors uikit.sx's iOS `chdir_to_bundle`.
sdl_chdir_to_bundle :: () {
    inline if OS != .macos { return; }
    bp := SDL_GetBasePath();
    if bp == null { return; }
    // Reorient only when the base path lives inside a `.app` bundle.
    i : s64 = 0;
    found := false;
    while bp[i] != 0 {
        if bp[i] == 46 and bp[i + 1] == 97 and bp[i + 2] == 112 and bp[i + 3] == 112 {
            found = true;
            break;
        }
        i = i + 1;
    }
    if found { chdir(bp); }
}

SdlPlatform :: struct {
    window: *void = null;
    gl_ctx: *void = null;
    running: bool = true;

    width: s32 = 0;
    height: s32 = 0;
    pixel_w: s32 = 0;
    pixel_h: s32 = 0;
    dpi_scale: f32 = 1.0;

    delta_time: f32 = 0.008;
    last_perf: u64 = 0;

    frame_closure: Closure() = ---;
    has_frame_closure: bool = false;

    events: List(Event) = .{};
}

impl Platform for SdlPlatform {
    init :: (self: *SdlPlatform, title: [:0]u8, w: s32, h: s32) -> bool {
        self.running = true;
        self.has_frame_closure = false;
        self.delta_time = 0.008;
        self.dpi_scale = 1.0;
        SDL_Init(SDL_INIT_VIDEO);
        // Find bundled assets when launched as a `.app` (CWD=/ under Finder).
        sdl_chdir_to_bundle();

        inline if OS == {
            case .wasm: {
                SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_ES);
                SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3);
                SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 0);
            }
            case .ios: {
                SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_ES);
                SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3);
                SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 0);
            }
            else: {
                SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE);
                SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3);
                SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 3);
            }
        }
        SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
        SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 24);

        init_w := w;
        init_h := h;
        inline if OS == .wasm {
            init_w = emscripten_run_script_int("window.innerWidth");
            init_h = emscripten_run_script_int("window.innerHeight");
        } else {
            display_id := SDL_GetPrimaryDisplay();
            bounds : SDL_Rect = ---;
            if SDL_GetDisplayUsableBounds(display_id, @bounds) {
                init_w = bounds.w * 3 / 4;
                init_h = bounds.h * 3 / 4;
            }
        }

        self.window = SDL_CreateWindow(title, init_w, init_h,
            SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE | SDL_WINDOW_HIGH_PIXEL_DENSITY);
        if self.window == null { return false; }

        self.gl_ctx = SDL_GL_CreateContext(self.window);
        SDL_GL_MakeCurrent(self.window, self.gl_ctx);
        SDL_GL_SetSwapInterval(0);

        load_gl(@SDL_GL_GetProcAddress);

        SDL_GetWindowSize(self.window, @self.width, @self.height);
        SDL_GetWindowSizeInPixels(self.window, @self.pixel_w, @self.pixel_h);
        wf : f32 = xx self.width;
        pw : f32 = xx self.pixel_w;
        self.dpi_scale = if wf > 0.0 then pw / wf else 1.0;

        glViewport(0, 0, self.pixel_w, self.pixel_h);

        g_sdl_plat = self;
        self.last_perf = SDL_GetPerformanceCounter();
        true;
    }

    run_frame_loop :: (self: *SdlPlatform, frame_fn: Closure()) {
        self.frame_closure = frame_fn;
        self.has_frame_closure = true;
        g_sdl_plat = self;

        inline if OS != .wasm {
            SDL_AddEventWatch(@sdl_event_watch, xx self);
        }

        inline if OS == .wasm {
            emscripten_set_main_loop(@sdl_wasm_tick, 0, 1);
        } else {
            while self.running {
                frame_fn();
            }
        }
    }

    poll_events :: (self: *SdlPlatform) -> []Event {
        self.events.len = 0;
        sdl_event : SDL_Event = .none;
        while SDL_PollEvent(@sdl_event) {
            if sdl_event == {
                case .quit: { self.running = false; }
                case .window_resized: (data) {
                    self.width = data.data1;
                    self.height = data.data2;
                    SDL_GetWindowSizeInPixels(self.window, @self.pixel_w, @self.pixel_h);
                }
            }
            ui_event := translate_sdl_event(@sdl_event);
            if ui_event != .none {
                self.events.append(ui_event);
            }
        }

        result : []Event = ---;
        result.ptr = self.events.items;
        result.len = self.events.len;
        result;
    }

    begin_frame :: (self: *SdlPlatform) -> FrameContext {
        current := SDL_GetPerformanceCounter();
        freq := SDL_GetPerformanceFrequency();
        if freq > 0 and self.last_perf > 0 {
            diff : f32 = xx (current - self.last_perf);
            fr : f32 = xx freq;
            self.delta_time = diff / fr;
        }
        self.last_perf = current;

        inline if OS == .wasm {
            new_w : s32 = 0;
            new_h : s32 = 0;
            SDL_GetWindowSize(self.window, @new_w, @new_h);
            if new_w != self.width or new_h != self.height {
                self.width = new_w;
                self.height = new_h;
                SDL_GetWindowSizeInPixels(self.window, @self.pixel_w, @self.pixel_h);
            }
        }

        FrameContext.{
            viewport_w = xx self.width,
            viewport_h = xx self.height,
            pixel_w = self.pixel_w,
            pixel_h = self.pixel_h,
            dpi_scale = self.dpi_scale,
            delta_time = self.delta_time,
        };
    }

    end_frame :: (self: *SdlPlatform) {
        SDL_GL_SwapWindow(self.window);
    }

    safe_insets :: (self: *SdlPlatform) -> EdgeInsets {
        EdgeInsets.zero();
    }

    keyboard :: (self: *SdlPlatform) -> KeyboardState {
        KeyboardState.zero();
    }

    show_keyboard :: (self: *SdlPlatform) { }
    hide_keyboard :: (self: *SdlPlatform) { }

    stop :: (self: *SdlPlatform) {
        self.running = false;
    }

    shutdown :: (self: *SdlPlatform) {
        inline if OS != .wasm {
            SDL_GL_DestroyContext(self.gl_ctx);
            SDL_DestroyWindow(self.window);
            SDL_Quit();
        }
    }
}

// SDL fires the watch synchronously when events are added — including during
// macOS's modal resize-drag, when SDL_PollEvent can't run. Re-invoking the
// frame closure here keeps content rendering at the new size during the drag.
sdl_event_watch :: (userdata: *void, event: *SDL_Event) -> bool callconv(.c) {
    plat : *SdlPlatform = xx userdata;
    if event.* == {
        case .window_resized: (data) {
            plat.width = data.data1;
            plat.height = data.data2;
            SDL_GetWindowSizeInPixels(plat.window, @plat.pixel_w, @plat.pixel_h);
            if plat.has_frame_closure {
                fn := plat.frame_closure;
                fn();
            }
        }
    }
    true;
}

sdl_wasm_tick :: () callconv(.c) {
    if g_sdl_plat == null { return; }
    if !g_sdl_plat.has_frame_closure { return; }
    fn := g_sdl_plat.frame_closure;
    fn();
}
