#include "platform/platform.h"

#import <Cocoa/Cocoa.h>
#include <stdlib.h>
#include <string.h>

// macOS virtual key codes (avoids Carbon.h include)
enum {
    kVK_ANSI_A     = 0x00, kVK_ANSI_C     = 0x08, kVK_ANSI_V     = 0x09,
    kVK_ANSI_X     = 0x07,
    kVK_Return     = 0x24, kVK_Tab        = 0x30, kVK_Delete     = 0x33,
    kVK_Escape     = 0x35, kVK_ForwardDelete = 0x75,
    kVK_LeftArrow  = 0x7B, kVK_RightArrow = 0x7C,
    kVK_DownArrow  = 0x7D, kVK_UpArrow    = 0x7E,
    kVK_Home       = 0x73, kVK_End        = 0x77,
    kVK_Command    = 0x37, kVK_Shift      = 0x38,
    kVK_RightShift = 0x3C, kVK_RightCommand = 0x36,
    kVK_ANSI_Equal = 0x18, kVK_ANSI_Minus = 0x1B,
    kVK_ANSI_0     = 0x1D,
    kVK_ANSI_KeypadEnter = 0x4C,
};

static U8 macos_keycode_to_pkey(U16 keycode) {
    switch (keycode) {
    case kVK_ANSI_A:       return PKEY_A;
    case kVK_ANSI_C:       return PKEY_C;
    case kVK_ANSI_V:       return PKEY_V;
    case kVK_ANSI_X:       return PKEY_X;
    case kVK_Return:       return PKEY_RETURN;
    case kVK_ANSI_KeypadEnter: return PKEY_RETURN;
    case kVK_Tab:          return PKEY_TAB;
    case kVK_Delete:       return PKEY_BACKSPACE;
    case kVK_ForwardDelete:return PKEY_DELETE;
    case kVK_Escape:       return PKEY_ESCAPE;
    case kVK_LeftArrow:    return PKEY_LEFT;
    case kVK_RightArrow:   return PKEY_RIGHT;
    case kVK_UpArrow:      return PKEY_UP;
    case kVK_DownArrow:    return PKEY_DOWN;
    case kVK_Home:         return PKEY_HOME;
    case kVK_End:          return PKEY_END;
    case kVK_ANSI_Equal:   return PKEY_EQUAL;
    case kVK_ANSI_Minus:   return PKEY_MINUS;
    case kVK_ANSI_0:       return PKEY_0;
    default:               return 0;
    }
}

////////////////////////////////
// Forward declarations

// Main window receives menu commands
static PlatformWindow *g_main_window = NULL;

////////////////////////////////
// Objective-C helper classes

@interface ASmplAppDelegate : NSObject <NSApplicationDelegate>
@end

@implementation ASmplAppDelegate
- (void)applicationDidFinishLaunching:(NSNotification *)notification { (void)notification; }
- (BOOL)applicationShouldTerminateAfterLastWindowClosed:(NSApplication *)sender { (void)sender; return YES; }
@end

@interface ASmplWindowDelegate : NSObject <NSWindowDelegate> {
@public
    PlatformWindow *_platformWindow;
}
@end

@interface ASmplView : NSView <NSTextInputClient> {
@public
    PlatformWindow *_platformWindow;
}
@end

////////////////////////////////
// PlatformWindow struct

typedef struct PlatformWindow {
    NSWindow             *ns_window;
    ASmplView            *view;
    ASmplWindowDelegate  *delegate;
    B32                  should_close;
    S32               width;
    S32               height;
    S32               pending_menu_cmd;
    PlatformFrameCallback frame_callback;
    void                 *frame_callback_user_data;
    PlatformInput         input;
    B32                   prev_mouse_down;
    B32                   mouse_down_state;
    F32                   backing_scale;
} PlatformWindow;

////////////////////////////////
// C callback helpers (called from ObjC via PlatformWindow pointer)

static void platform_macos_insert_text_pw(PlatformWindow *pw, const char *utf8) {
    if (!pw || !utf8) return;
    PlatformInput *ev = &pw->input;
    while (*utf8 && ev->char_count < PLATFORM_MAX_CHARS_PER_FRAME) {
        U8 c = (U8)*utf8;
        if (c < 32) { utf8++; continue; }
        // Handle ASCII printable range (single-byte UTF-8)
        if (c < 0x80) {
            ev->chars[ev->char_count++] = (U16)c;
            utf8++;
        } else {
            // Skip multi-byte UTF-8 sequences for now (UI only handles ASCII)
            if      (c < 0xE0) utf8 += 2;
            else if (c < 0xF0) utf8 += 3;
            else               utf8 += 4;
        }
    }
}

static void platform_macos_key_down_pw(PlatformWindow *pw, U16 keycode, NSEventModifierFlags mods) {
    if (!pw) return;
    PlatformInput *ev = &pw->input;

    U8 pkey = macos_keycode_to_pkey(keycode);
    if (pkey && ev->key_count < PLATFORM_MAX_KEYS_PER_FRAME)
        ev->keys[ev->key_count++] = pkey;

    // Command = ctrl_held (macOS convention: Cmd+C, Cmd+V, etc.)
    ev->ctrl_held  = (mods & NSEventModifierFlagCommand) != 0;
    ev->shift_held = (mods & NSEventModifierFlagShift) != 0;
}

////////////////////////////////
// ObjC class implementations

@implementation ASmplWindowDelegate
- (BOOL)windowShouldClose:(id)sender {
    (void)sender;
    if (_platformWindow) {
        _platformWindow->should_close = true;
    }
    return NO; // We handle closing ourselves
}

- (void)windowDidResize:(NSNotification *)notification {
    (void)notification;
    if (!_platformWindow) return;
    PlatformWindow *pw = _platformWindow;
    NSRect frame = [pw->view bounds];
    F32 scale = pw->backing_scale;
    pw->width  = (S32)(frame.size.width * scale);
    pw->height = (S32)(frame.size.height * scale);
    if (pw->frame_callback)
        pw->frame_callback(pw->frame_callback_user_data);
}
@end

@implementation ASmplView

- (BOOL)acceptsFirstResponder { return YES; }
- (BOOL)canBecomeKeyView      { return YES; }

// Needed for NSTextInputClient
- (BOOL)hasMarkedText { return NO; }
- (NSRange)markedRange { return NSMakeRange(NSNotFound, 0); }
- (NSRange)selectedRange { return NSMakeRange(NSNotFound, 0); }
- (void)setMarkedText:(id)string selectedRange:(NSRange)selectedRange replacementRange:(NSRange)replacementRange {
    (void)string; (void)selectedRange; (void)replacementRange;
}
- (void)unmarkText {}
- (NSArray<NSAttributedStringKey> *)validAttributesForMarkedText { return @[]; }
- (NSAttributedString *)attributedSubstringForProposedRange:(NSRange)range actualRange:(NSRangePointer)actualRange {
    (void)range; (void)actualRange;
    return nil;
}
- (NSUInteger)characterIndexForPoint:(NSPoint)point { (void)point; return NSNotFound; }
- (NSRect)firstRectForCharacterRange:(NSRange)range actualRange:(NSRangePointer)actualRange {
    (void)range; (void)actualRange;
    return NSZeroRect;
}

- (void)insertText:(id)string replacementRange:(NSRange)replacementRange {
    (void)replacementRange;
    NSString *str = nil;
    if ([string isKindOfClass:[NSAttributedString class]])
        str = [string string];
    else
        str = (NSString *)string;

    platform_macos_insert_text_pw(_platformWindow, [str UTF8String]);
}

- (void)keyDown:(NSEvent *)event {
    platform_macos_key_down_pw(_platformWindow, [event keyCode], [event modifierFlags]);

    // Feed into text input system for character generation
    [self interpretKeyEvents:@[event]];
}

- (void)flagsChanged:(NSEvent *)event {
    (void)event;
    // Modifiers are read at key-down time, nothing to accumulate here
}

- (void)mouseDown:(NSEvent *)event {
    (void)event;
    if (_platformWindow) _platformWindow->mouse_down_state = 1;
}

- (void)mouseUp:(NSEvent *)event {
    (void)event;
    if (_platformWindow) _platformWindow->mouse_down_state = 0;
}

- (void)mouseMoved:(NSEvent *)event  { (void)event; }
- (void)mouseDragged:(NSEvent *)event { (void)event; }

- (void)scrollWheel:(NSEvent *)event {
    if (!_platformWindow) return;
    F32 dy = (F32)[event scrollingDeltaY];
    if ([event hasPreciseScrollingDeltas])
        dy /= 40.0f; // Normalize trackpad deltas to match discrete wheel steps
    _platformWindow->input.scroll_delta.y += dy;
}

- (BOOL)acceptsFirstMouse:(NSEvent *)event { (void)event; return YES; }

@end

////////////////////////////////
// Menu action handler

@interface ASmplMenuTarget : NSObject
- (void)menuAction:(id)sender;
@end

@implementation ASmplMenuTarget
- (void)menuAction:(id)sender {
    if (!g_main_window) return;
    NSMenuItem *item = (NSMenuItem *)sender;
    g_main_window->pending_menu_cmd = (S32)[item tag];
}
@end

static ASmplMenuTarget *g_menu_target = nil;

////////////////////////////////
// Public API

PlatformWindow *platform_create_window(PlatformWindowDesc *desc) {
    // Ensure NSApplication is initialized and properly launched
    [NSApplication sharedApplication];
    [NSApp setActivationPolicy:NSApplicationActivationPolicyRegular];

    static ASmplAppDelegate *app_delegate = nil;
    if (!app_delegate) {
        app_delegate = [[ASmplAppDelegate alloc] init];
        [NSApp setDelegate:app_delegate];
        [NSApp finishLaunching];
    }

    NSRect content_rect = NSMakeRect(0, 0, desc->width, desc->height);

    NSWindowStyleMask style_mask;
    if (desc->style == PLATFORM_WINDOW_STYLE_POPUP) {
        style_mask = NSWindowStyleMaskTitled | NSWindowStyleMaskClosable;
    } else if (desc->style == PLATFORM_WINDOW_STYLE_POPUP_RESIZABLE) {
        style_mask = NSWindowStyleMaskTitled | NSWindowStyleMaskClosable | NSWindowStyleMaskResizable;
    } else {
        style_mask = NSWindowStyleMaskTitled | NSWindowStyleMaskClosable |
                     NSWindowStyleMaskMiniaturizable | NSWindowStyleMaskResizable;
    }

    NSWindow *ns_window = [[NSWindow alloc]
        initWithContentRect:content_rect
        styleMask:style_mask
        backing:NSBackingStoreBuffered
        defer:NO];

    [ns_window setTitle:[NSString stringWithUTF8String:desc->title]];
    [ns_window center];

    ASmplView *view = [[ASmplView alloc] initWithFrame:content_rect];
    [ns_window setContentView:view];
    [ns_window makeFirstResponder:view];

    // Enable mouse moved events
    [ns_window setAcceptsMouseMovedEvents:YES];

    ASmplWindowDelegate *delegate = [[ASmplWindowDelegate alloc] init];
    [ns_window setDelegate:delegate];

    PlatformWindow *window = (PlatformWindow *)calloc(1, sizeof(PlatformWindow));
    window->ns_window    = ns_window;
    window->view         = view;
    window->delegate     = delegate;
    window->should_close = false;
    window->backing_scale = (F32)[ns_window backingScaleFactor];
    window->width        = (S32)(desc->width * window->backing_scale);
    window->height       = (S32)(desc->height * window->backing_scale);

    // Wire up per-window pointers
    view->_platformWindow = window;
    delegate->_platformWindow = window;

    // Track main window for menu commands
    if (desc->style == PLATFORM_WINDOW_STYLE_NORMAL) {
        g_main_window = window;
    }

    // If popup, add as child of parent (unless independent)
    if (!desc->independent && (desc->style == PLATFORM_WINDOW_STYLE_POPUP || desc->style == PLATFORM_WINDOW_STYLE_POPUP_RESIZABLE) && desc->parent) {
        [desc->parent->ns_window addChildWindow:ns_window ordered:NSWindowAbove];
    }

    [ns_window makeKeyAndOrderFront:nil];
    [NSApp activateIgnoringOtherApps:YES];

    return window;
}

void platform_destroy_window(PlatformWindow *window) {
    if (!window) return;

    // Remove from parent if it's a child window
    NSWindow *parent = [window->ns_window parentWindow];
    if (parent) {
        [parent removeChildWindow:window->ns_window];
    }

    [window->ns_window close];
    if (g_main_window == window)
        g_main_window = NULL;
    free(window);
}

B32 platform_poll_events(PlatformWindow *window) {
    @autoreleasepool {
        // When the app is not active (e.g. during Cmd+Tab away), block until
        // an event arrives instead of spinning.  This keeps CPU near zero while
        // backgrounded and lets macOS handle the app-switch animation smoothly.
        BOOL is_active = [NSApp isActive];
        NSDate *deadline = is_active ? nil : [NSDate distantFuture];

        NSEvent *event;
        while ((event = [NSApp nextEventMatchingMask:NSEventMaskAny
                                           untilDate:deadline
                                              inMode:NSDefaultRunLoopMode
                                             dequeue:YES])) {
            [NSApp sendEvent:event];
            // After processing one event while backgrounded, switch to
            // non-blocking drain so we handle any remaining queued events
            // (including the activation event that will flip isActive).
            deadline = nil;
        }
    }
    return !window->should_close;
}

void platform_get_size(PlatformWindow *window, S32 *w, S32 *h) {
    if (w) *w = window->width;
    if (h) *h = window->height;
}

void *platform_get_native_handle(PlatformWindow *window) {
    return (__bridge void *)window->view;
}

void platform_set_frame_callback(PlatformWindow *window, PlatformFrameCallback cb, void *user_data) {
    window->frame_callback = cb;
    window->frame_callback_user_data = user_data;
}

void platform_set_menu(PlatformWindow *window, PlatformMenu *menus, S32 menu_count) {
    (void)window;

    if (!g_menu_target)
        g_menu_target = [[ASmplMenuTarget alloc] init];

    NSMenu *menu_bar = [[NSMenu alloc] init];

    // App menu (required on macOS)
    NSMenuItem *app_menu_item = [[NSMenuItem alloc] init];
    NSMenu *app_menu = [[NSMenu alloc] init];
    [app_menu addItemWithTitle:@"Quit autosample"
                        action:@selector(terminate:)
                 keyEquivalent:@"q"];
    [app_menu_item setSubmenu:app_menu];
    [menu_bar addItem:app_menu_item];

    for (S32 i = 0; i < menu_count; i++) {
        NSMenuItem *top_item = [[NSMenuItem alloc] init];
        NSMenu *submenu = [[NSMenu alloc] initWithTitle:
            [NSString stringWithUTF8String:menus[i].label]];

        for (S32 j = 0; j < menus[i].item_count; j++) {
            PlatformMenuItem *item = &menus[i].items[j];
            if (!item->label) {
                [submenu addItem:[NSMenuItem separatorItem]];
            } else {
                NSMenuItem *ns_item = [[NSMenuItem alloc]
                    initWithTitle:[NSString stringWithUTF8String:item->label]
                    action:@selector(menuAction:)
                    keyEquivalent:@""];
                [ns_item setTag:item->id];
                [ns_item setTarget:g_menu_target];
                [submenu addItem:ns_item];
            }
        }

        [top_item setSubmenu:submenu];
        [menu_bar addItem:top_item];
    }

    [NSApp setMainMenu:menu_bar];
}

S32 platform_poll_menu_command(PlatformWindow *window) {
    S32 cmd = window->pending_menu_cmd;
    window->pending_menu_cmd = 0;
    return cmd;
}

PlatformInput platform_get_input(PlatformWindow *window) {
    PlatformInput result = window->input;

    // Poll mouse position (Cocoa uses bottom-left origin, flip Y)
    NSPoint mouse_in_window = [window->ns_window mouseLocationOutsideOfEventStream];
    NSRect view_bounds = [window->view bounds];
    F32 scale = window->backing_scale;
    result.mouse_pos = v2f32(
        (F32)mouse_in_window.x * scale,
        (F32)(view_bounds.size.height - mouse_in_window.y) * scale);

    // Mouse button state
    result.was_mouse_down = window->prev_mouse_down;
    result.mouse_down = window->mouse_down_state;
    window->prev_mouse_down = result.mouse_down;

    // Poll current modifier state (so shift/ctrl are accurate even without key events)
    NSEventModifierFlags mods = [NSEvent modifierFlags];
    result.ctrl_held  = (mods & NSEventModifierFlagCommand) != 0;
    result.shift_held = (mods & NSEventModifierFlagShift) != 0;

    // Clear accumulated events for next frame
    memset(&window->input, 0, sizeof(window->input));
    return result;
}

B32 platform_window_should_close(PlatformWindow *window) {
    return window ? window->should_close : 0;
}

void platform_focus_window(PlatformWindow *window) {
    if (!window || !window->ns_window) return;
    [window->ns_window makeKeyAndOrderFront:nil];
}

F32 platform_get_dpi_scale(PlatformWindow *window) {
    (void)window;
    return 1.0f; // macOS handles Retina via backing scale factor, not DPI
}

void platform_set_cursor(PlatformCursor cursor) {
    switch (cursor) {
    case PLATFORM_CURSOR_SIZE_WE: [[NSCursor resizeLeftRightCursor] set]; break;
    case PLATFORM_CURSOR_SIZE_NS: [[NSCursor resizeUpDownCursor] set];   break;
    default:                      [[NSCursor arrowCursor] set];           break;
    }
}

void platform_clipboard_set(const char *text) {
    if (!text) return;
    NSPasteboard *pb = [NSPasteboard generalPasteboard];
    [pb clearContents];
    [pb setString:[NSString stringWithUTF8String:text] forType:NSPasteboardTypeString];
}

const char *platform_clipboard_get() {
    static char buf[4096];
    buf[0] = '\0';

    NSPasteboard *pb = [NSPasteboard generalPasteboard];
    NSString *str = [pb stringForType:NSPasteboardTypeString];
    if (str) {
        const char *utf8 = [str UTF8String];
        if (utf8) {
            size_t len = strlen(utf8);
            if (len >= sizeof(buf)) len = sizeof(buf) - 1;
            memcpy(buf, utf8, len);
            buf[len] = '\0';
        }
    }

    return buf[0] ? buf : NULL;
}

S32 platform_message_box(PlatformWindow *parent, const char *title,
                         const char *message, PlatformMsgBoxType type) {
    @autoreleasepool {
        NSAlert *alert = [[NSAlert alloc] init];
        [alert setMessageText:[NSString stringWithUTF8String:title]];
        [alert setInformativeText:[NSString stringWithUTF8String:message]];

        switch (type) {
        case PLATFORM_MSGBOX_OK:
            [alert addButtonWithTitle:@"OK"];
            break;
        case PLATFORM_MSGBOX_OK_CANCEL:
            [alert addButtonWithTitle:@"OK"];
            [alert addButtonWithTitle:@"Cancel"];
            break;
        case PLATFORM_MSGBOX_YES_NO:
            [alert addButtonWithTitle:@"Yes"];
            [alert addButtonWithTitle:@"No"];
            break;
        }

        NSModalResponse response;
        if (parent && parent->ns_window) {
            response = [alert runModal];
        } else {
            response = [alert runModal];
        }

        // NSAlertFirstButtonReturn = 1000, Second = 1001
        if (response == NSAlertFirstButtonReturn) return 0;
        if (response == NSAlertSecondButtonReturn) return 1;
        return -1;
    }
}
