#+build js wasm32, js wasm64p32
package wasm_js_interface

foreign import dom_lib "odin_dom"

Event_Kind :: enum u32 {
	Invalid,

	Load,
	Unload,
	Error,
	Resize,
	Visibility_Change,
	Fullscreen_Change,
	Fullscreen_Error,

	Click,
	Double_Click,
	Mouse_Move,
	Mouse_Over,
	Mouse_Out,
	Mouse_Up,
	Mouse_Down,

	Key_Up,
	Key_Down,
	Key_Press,

	Scroll,
	Wheel,

	Focus,
	Focus_In,
	Focus_Out,
	Submit,
	Blur,
	Change,
	Hash_Change,
	Select,

	Animation_Start,
	Animation_End,
	Animation_Iteration,
	Animation_Cancel,

	Copy,
	Cut,
	Paste,

	// Drag,
	// Drag_Start,
	// Drag_End,
	// Drag_Enter,
	// Drag_Leave,
	// Drag_Over,
	// Drop,

	Pointer_Cancel,
	Pointer_Down,
	Pointer_Enter,
	Pointer_Leave,
	Pointer_Move,
	Pointer_Over,
	Pointer_Up,
	Got_Pointer_Capture,
	Lost_Pointer_Capture,
	Pointer_Lock_Change,
	Pointer_Lock_Error,

	Selection_Change,
	Selection_Start,

	Touch_Cancel,
	Touch_End,
	Touch_Move,
	Touch_Start,

	Transition_Start,
	Transition_End,
	Transition_Run,
	Transition_Cancel,

	Context_Menu,

	Gamepad_Connected,
	Gamepad_Disconnected,

	Custom,

}
event_kind_string := [Event_Kind]string{
	.Invalid = "",

	.Load         = "load",
	.Unload       = "unload",
	.Error        = "error",
	.Resize       = "resize",
	.Visibility_Change = "visibilitychange",
	.Fullscreen_Change = "fullscreenchange",
	.Fullscreen_Error  = "fullscreenerror",

	.Click        = "click",
	.Double_Click = "dblclick",
	.Mouse_Move   = "mousemove",
	.Mouse_Over   = "mouseover",
	.Mouse_Out    = "mouseout",
	.Mouse_Up     = "mouseup",
	.Mouse_Down   = "mousedown",

	.Key_Up       = "keyup",
	.Key_Down     = "keydown",
	.Key_Press    = "keypress",

	.Scroll = "scroll",
	.Wheel = "wheel",

	.Focus        = "focus",
	.Focus_In     = "focusin",
	.Focus_Out    = "focusout",
	.Submit       = "submit",
	.Blur         = "blur",
	.Change       = "change",
	.Hash_Change  = "hashchange",
	.Select       = "select",

	.Animation_Start     = "animationstart",
	.Animation_End       = "animationend",
	.Animation_Iteration = "animationiteration",
	.Animation_Cancel    = "animationcancel",

	.Copy   = "copy",
	.Cut    = "cut",
	.Paste  = "paste",

	// .Drag,       = "drag",
	// .Drag_Start, = "dragstart",
	// .Drag_End,   = "dragend",
	// .Drag_Enter, = "dragenter",
	// .Drag_Leave, = "dragleave",
	// .Drag_Over,  = "dragover",
	// .Drop,       = "drop",

	.Pointer_Cancel       = "pointercancel",
	.Pointer_Down         = "pointerdown",
	.Pointer_Enter        = "pointerenter",
	.Pointer_Leave        = "pointerleave",
	.Pointer_Move         = "pointermove",
	.Pointer_Over         = "pointerover",
	.Pointer_Up           = "pointerup",
	.Got_Pointer_Capture  = "gotpointercapture",
	.Lost_Pointer_Capture = "lostpointercapture",
	.Pointer_Lock_Change  = "pointerlockchange",
	.Pointer_Lock_Error   = "pointerlockerror",

	.Selection_Change = "selectionchange",
	.Selection_Start  = "selectionstart",

	.Transition_Start  = "transitionstart",
	.Transition_End    = "transitionend",
	.Transition_Run    = "transitionrun",
	.Transition_Cancel = "transitioncancel",

	.Touch_Cancel = "touchcancel",
	.Touch_End    = "touchend",
	.Touch_Move   = "touchmove",
	.Touch_Start  = "touchstart",

	.Context_Menu = "contextmenu",

	.Gamepad_Connected    = "gamepadconnected",
	.Gamepad_Disconnected = "gamepaddisconnected",

	.Custom = "?custom?",
}

Delta_Mode :: enum u32 {
	Pixel = 0,
	Line  = 1,
	Page  = 2,
}

Key_Location :: enum u8 {
	Standard = 0,
	Left     = 1,
	Right    = 2,
	Numpad   = 3,
}

KEYBOARD_MAX_KEY_SIZE  :: 32
KEYBOARD_MAX_CODE_SIZE :: 32

GAMEPAD_MAX_ID_SIZE      :: 96
GAMEPAD_MAX_MAPPING_SIZE :: 64

GAMEPAD_MAX_BUTTONS :: 64
GAMEPAD_MAX_AXES    :: 16

Event_Target_Kind :: enum u32 {
	Element  = 0,
	Document = 1,
	Window   = 2,
}

Event_Phase :: enum u8 {
	None            = 0,
	Capturing_Phase = 1,
	At_Target       = 2,
	Bubbling_Phase  = 3,
}

Event_Option :: enum u8 {
	Bubbles    = 0,
	Cancelable = 1,
	Composed   = 2,
}
Event_Options :: distinct bit_set[Event_Option; u8]

Gamepad_Button :: struct {
	value:   f64,
	pressed: bool,
	touched: bool,
}

Gamepad_State :: struct {
	id:           string,
	mapping:      string,
	index:        int,
	connected:    bool,
	timestamp:    f64,

	button_count: int,
	axis_count:   int,
	buttons: [GAMEPAD_MAX_BUTTONS]Gamepad_Button `fmt:"v,button_count"`,
	axes:    [GAMEPAD_MAX_AXES]f64               `fmt:"v,axes_count"`,

	_id_len:      int `fmt:"-"`,
	_mapping_len: int `fmt:"-"`,
	_id_buf:      [GAMEPAD_MAX_ID_SIZE]byte      `fmt:"-"`,
	_mapping_buf: [GAMEPAD_MAX_MAPPING_SIZE]byte `fmt:"-"`,
}

Pointer_Type :: enum u8 {
	Mouse,
	Pen,
	Touch,
}

Event :: struct {
	kind:                 Event_Kind,
	target_kind:          Event_Target_Kind,
	current_target_kind:  Event_Target_Kind,
	id:           string,
	timestamp:    f64,

	phase:        Event_Phase,
	options:      Event_Options,
	is_composing: bool,
	is_trusted:   bool,

	using data: struct #raw_union #align(8) {
		scroll: struct {
			delta: [2]f64,
		},
		visibility_change: struct {
			is_visible: bool,
		},
		wheel: struct {
			delta: [3]f64,
			delta_mode: Delta_Mode,
		},

		key: struct {
			key:  string,
			code: string,
			location: Key_Location,

			ctrl:   bool,
			shift:  bool,
			alt:    bool,
			meta:   bool,

			repeat: bool,

			char:   rune,

			_key_len:  int                         `fmt:"-"`,
			_code_len: int                         `fmt:"-"`,
			_key_buf:  [KEYBOARD_MAX_KEY_SIZE]byte `fmt:"-"`,
			_code_buf: [KEYBOARD_MAX_KEY_SIZE]byte `fmt:"-"`,
		},

		mouse: struct {
			screen:    [2]i64,
			client:    [2]i64,
			offset:    [2]i64,
			page:      [2]i64,
			movement:  [2]i64,

			ctrl:   bool,
			shift:  bool,
			alt:    bool,
			meta:   bool,

			button:  i16,
			buttons: bit_set[0..<16; u16],

			pointer: struct {
				altitude_angle:       f64,
				azimuth_angle:        f64,
				persistent_device_id: int,
				pointer_id:           int,
				width:                int,
				height:               int,
				pressure:             f64,
				tangential_pressure:  f64,
				tilt:                 [2]f64,
				twist:                f64,
				pointer_type:         Pointer_Type,
				is_primary:           bool,
			},
		},

		gamepad: Gamepad_State,
	},


	user_data: rawptr,
	callback:  proc(e: Event),
}

@(default_calling_convention="contextless")
foreign dom_lib {
	event_stop_propagation           :: proc() ---
	event_stop_immediate_propagation :: proc() ---
	event_prevent_default            :: proc() ---
	dispatch_custom_event            :: proc(id: string, name: string, options := Event_Options{}) -> bool ---
}

add_event_listener :: proc(id: string, kind: Event_Kind, user_data: rawptr, callback: proc(e: Event), use_capture := false) -> bool {
	@(default_calling_convention="contextless")
	foreign dom_lib {
		@(link_name="add_event_listener")
		_add_event_listener :: proc(id: string, name: string, name_code: Event_Kind, user_data: rawptr, callback: proc "odin" (Event), use_capture: bool) -> bool ---
	}
	// TODO: Pointer_Lock_Change etc related stuff for all different browsers
	return _add_event_listener(id, event_kind_string[kind], kind, user_data, callback, use_capture)
}

remove_event_listener :: proc(id: string, kind: Event_Kind, user_data: rawptr, callback: proc(e: Event), use_capture := false) -> bool {
	@(default_calling_convention="contextless")
	foreign dom_lib {
		@(link_name="remove_event_listener")
		_remove_event_listener :: proc(id: string, name: string, user_data: rawptr, callback: proc "odin" (Event), use_capture: bool) -> bool ---
	}
	return _remove_event_listener(id, event_kind_string[kind], user_data, callback, use_capture)
}

add_window_event_listener :: proc(kind: Event_Kind, user_data: rawptr, callback: proc(e: Event), use_capture := false) -> bool {
	@(default_calling_convention="contextless")
	foreign dom_lib {
		@(link_name="add_window_event_listener")
		_add_window_event_listener :: proc(name: string, name_code: Event_Kind, user_data: rawptr, callback: proc "odin" (Event), use_capture: bool) -> bool ---
	}
	return _add_window_event_listener(event_kind_string[kind], kind, user_data, callback, use_capture)
}

remove_window_event_listener :: proc(kind: Event_Kind, user_data: rawptr, callback: proc(e: Event), use_capture := false) -> bool {
	@(default_calling_convention="contextless")
	foreign dom_lib {
		@(link_name="remove_window_event_listener")
		_remove_window_event_listener :: proc(name: string, user_data: rawptr, callback: proc "odin" (Event), use_capture: bool) -> bool ---
	}
	return _remove_window_event_listener(event_kind_string[kind], user_data, callback, use_capture)
}

add_document_event_listener :: proc(kind: Event_Kind, user_data: rawptr, callback: proc(e: Event), use_capture := false) -> bool {
	@(default_calling_convention="contextless")
	foreign dom_lib {
		@(link_name="add_document_event_listener")
		_add_document_event_listener :: proc(name: string, name_code: Event_Kind, user_data: rawptr, callback: proc "odin" (Event), use_capture: bool) -> bool ---
	}
	return _add_document_event_listener(event_kind_string[kind], kind, user_data, callback, use_capture)
}

remove_document_event_listener :: proc(kind: Event_Kind, user_data: rawptr, callback: proc(e: Event), use_capture := false) -> bool {
	@(default_calling_convention="contextless")
	foreign dom_lib {
		@(link_name="remove_document_event_listener")
		_remove_document_event_listener :: proc(name: string, user_data: rawptr, callback: proc "odin" (Event), use_capture: bool) -> bool ---
	}
	return _remove_document_event_listener(event_kind_string[kind], user_data, callback, use_capture)
}

remove_event_listener_from_event :: proc(e: Event) -> bool {
	from_use_capture_false: bool
	from_use_capture_true: bool
	if e.id == "" {
		from_use_capture_false = remove_window_event_listener(e.kind, e.user_data, e.callback, false)
		from_use_capture_true = remove_window_event_listener(e.kind, e.user_data, e.callback, true)
		from_use_capture_false |= remove_document_event_listener(e.kind, e.user_data, e.callback, false)
		from_use_capture_true |= remove_document_event_listener(e.kind, e.user_data, e.callback, true)
	} else {
		from_use_capture_false = remove_event_listener(e.id, e.kind, e.user_data, e.callback, false)
		from_use_capture_true = remove_event_listener(e.id, e.kind, e.user_data, e.callback, true)
	}
	return from_use_capture_false || from_use_capture_true
}

add_custom_event_listener :: proc(id: string, name: string, user_data: rawptr, callback: proc(e: Event), use_capture := false) -> bool {
	@(default_calling_convention="contextless")
	foreign dom_lib {
		@(link_name="add_event_listener")
		_add_event_listener :: proc(id: string, name: string, name_code: Event_Kind, user_data: rawptr, callback: proc "odin" (Event), use_capture: bool) -> bool ---
	}
	return _add_event_listener(id, name, .Custom, user_data, callback, use_capture)
}
remove_custom_event_listener :: proc(id: string, name: string, user_data: rawptr, callback: proc(e: Event), use_capture := false) -> bool {
	@(default_calling_convention="contextless")
	foreign dom_lib {
		@(link_name="remove_event_listener")
		_remove_event_listener :: proc(id: string, name: string, user_data: rawptr, callback: proc "odin" (Event), use_capture: bool) -> bool ---
	}
	return _remove_event_listener(id, name, user_data, callback, use_capture)
}

get_gamepad_state :: proc "contextless" (index: int, s: ^Gamepad_State) -> bool {
	@(default_calling_convention="contextless")
	foreign dom_lib {
		@(link_name="get_gamepad_state")
		_get_gamepad_state :: proc(index: int, s: ^Gamepad_State) -> bool ---
	}

	if s == nil {
		return false
	}

	if !_get_gamepad_state(index, s) {
		return false
	}

	s.id = string(s._id_buf[:s._id_len])
	s.mapping = string(s._mapping_buf[:s._mapping_len])
	return true
}


@(export, link_name="odin_dom_do_event_callback")
do_event_callback :: proc(user_data: rawptr, callback: proc(e: Event)) {
	@(default_calling_convention="contextless")
	foreign dom_lib {
		init_event_raw :: proc(e: ^Event) ---
	}

	if callback != nil {
		event := Event{
			user_data = user_data,
			callback  = callback,
		}


		init_event_raw(&event)

		#partial switch event.kind {
		case .Key_Up, .Key_Down, .Key_Press:
			event.key.key = string(event.key._key_buf[:event.key._key_len]) 
			event.key.code = string(event.key._code_buf[:event.key._code_len]) 
		case .Gamepad_Connected, .Gamepad_Disconnected:
			event.gamepad.id = string(event.gamepad._id_buf[:event.gamepad._id_len])
			event.gamepad.mapping = string(event.gamepad._mapping_buf[:event.gamepad._mapping_len])
		}

		callback(event)
	}
}
