Module Notify
	EnableExplicit
	
	;- Toast constants
	#Toast_W      = 300
	#Toast_H      = 60
	#Toast_Margin = 10
	#Accent_W     = 4
	#Toast_Timer  = 0
	#MaxToasts    = 5
	
	;- Globals
	Global _Font = LoadFont(#PB_Any, "sans-serif", 12)
	
	; Toast state
	Global Dim _Wins.i(#MaxToasts)
	Global Dim _Canvases.i(#MaxToasts)
	Global Dim _Types.i(#MaxToasts)
	Global Dim _Messages.s(#MaxToasts)
	Global _Count
	
	; Confirm state
	Global _ConfirmWin
	Global _ConfirmOkBtn
	Global _ConfirmCancelBtn
	Global *_ConfirmCb
	
	;- Private declarations
	Declare _TypeColor(Type)
	Declare _DrawToast(Slot)
	Declare _Dismiss(Slot)
	Declare _Reposition()
	Declare _FindByWindow(Win)
	Declare _FindByCanvas(Canvas)
	Declare _CloseConfirm()
	Declare _FireConfirm(Result)
	Declare Handler_ToastTimer()
	Declare Handler_ToastClick()
	Declare Handler_ConfirmOK()
	Declare Handler_ConfirmCancel()
	Declare Handler_ConfirmClose()
	
	;- Public procedures
	
	; Show a toast in the bottom-right corner. Dismisses after Duration ms or on click.
	Procedure Toast(Message.s, Type = #Info, Duration = 4000)
		Protected Slot, DW, DH, X, Y, Win, Canvas
		
		If _Count >= #MaxToasts : ProcedureReturn : EndIf
		
		Slot   = _Count
		DW     = DesktopWidth(0)
		DH     = DesktopHeight(0)
		X      = DW - #Toast_W - #Toast_Margin
		Y      = DH - (Slot + 1) * (#Toast_H + #Toast_Margin)
		Win    = OpenWindow(#PB_Any, X, Y, #Toast_W, #Toast_H, "", #PB_Window_BorderLess)
		StickyWindow(Win, #True)
		Canvas = CanvasGadget(#PB_Any, 0, 0, #Toast_W, #Toast_H)
		
		_Wins(Slot)     = Win
		_Canvases(Slot) = Canvas
		_Types(Slot)    = Type
		_Messages(Slot) = Message
		_Count + 1
		
		_DrawToast(Slot)
		
		BindGadgetEvent(Canvas, @Handler_ToastClick())
		AddWindowTimer(Win, #Toast_Timer, Duration)
		BindEvent(#PB_Event_Timer, @Handler_ToastTimer(), Win)
	EndProcedure
	
	; Show a modal confirm dialog. *Callback(Result) is called with 1 for OK, 0 for Cancel.
	; Only one confirm can be open at a time — subsequent calls are ignored.
	Procedure Confirm(Title.s, Message.s, *Callback)
		Protected W, H, DW, DH
		
		If IsWindow(_ConfirmWin) : ProcedureReturn : EndIf
		
		W  = 340
		H  = 130
		DW = DesktopWidth(0)
		DH = DesktopHeight(0)
		
		_ConfirmWin = OpenWindow(#PB_Any, (DW - W) / 2, (DH - H) / 2, W, H, Title, #PB_Window_TitleBar | #PB_Window_SystemMenu)
		StickyWindow(_ConfirmWin, #True)
		*_ConfirmCb = *Callback
		
		TextGadget(#PB_Any, 20, 20, W - 40, 50, Message)
		_ConfirmOkBtn     = ButtonGadget(#PB_Any, W - 190, H - 45, 80, 30, "OK")
		_ConfirmCancelBtn = ButtonGadget(#PB_Any, W - 100, H - 45, 80, 30, "Cancel")
		
		BindGadgetEvent(_ConfirmOkBtn,     @Handler_ConfirmOK())
		BindGadgetEvent(_ConfirmCancelBtn, @Handler_ConfirmCancel())
		BindEvent(#PB_Event_CloseWindow,   @Handler_ConfirmClose(), _ConfirmWin)
	EndProcedure
	
	;- Private procedures
	
	Procedure _TypeColor(Type)
		Select Type
			Case #Success : ProcedureReturn RGB( 60, 180, 100)
			Case #Warning : ProcedureReturn RGB(220, 160,  40)
			Case #Error   : ProcedureReturn RGB(210,  65,  65)
			Default       : ProcedureReturn RGB( 50, 130, 220) ; #Info
		EndSelect
	EndProcedure
	
	Procedure _DrawToast(Slot)
		Protected W, H, MaxW
		Protected Msg.s
		
		If Not IsGadget(_Canvases(Slot))                 : ProcedureReturn : EndIf
		If Not StartDrawing(CanvasOutput(_Canvases(Slot))) : ProcedureReturn : EndIf
		
		W = OutputWidth()
		H = OutputHeight()
		
		; Background
		Box(0, 0, W, H, RGB(38, 38, 54))
		
		; Accent bar
		Box(0, 0, #Accent_W, H, _TypeColor(_Types(Slot)))
		
		; Message — truncate with ellipsis if too wide
		DrawingMode(#PB_2DDrawing_Transparent)
		If IsFont(_Font) : DrawingFont(FontID(_Font)) : EndIf
		
		Msg  = _Messages(Slot)
		MaxW = W - #Accent_W - 24
		While Len(Msg) > 3 And TextWidth(Msg) > MaxW
			Msg = Left(Msg, Len(Msg) - 4) + "..."
		Wend
		
		DrawText(#Accent_W + 14, (H - TextHeight("A")) / 2, Msg, RGB(220, 220, 220))
		StopDrawing()
	EndProcedure
	
	Procedure _Dismiss(Slot)
		Protected i
		
		If Not IsWindow(_Wins(Slot)) : ProcedureReturn : EndIf
		
		UnbindEvent(#PB_Event_Timer, @Handler_ToastTimer(), _Wins(Slot))
		UnbindGadgetEvent(_Canvases(Slot), @Handler_ToastClick())
		CloseWindow(_Wins(Slot))
		
		For i = Slot To _Count - 2
			_Wins(i)     = _Wins(i + 1)
			_Canvases(i) = _Canvases(i + 1)
			_Types(i)    = _Types(i + 1)
			_Messages(i) = _Messages(i + 1)
		Next
		_Wins(_Count - 1)     = 0
		_Canvases(_Count - 1) = 0
		_Types(_Count - 1)    = 0
		_Messages(_Count - 1) = ""
		_Count - 1
		
		_Reposition()
	EndProcedure
	
	Procedure _Reposition()
		Protected DW, DH, i
		
		DW = DesktopWidth(0)
		DH = DesktopHeight(0)
		For i = 0 To _Count - 1
			ResizeWindow(_Wins(i), DW - #Toast_W - #Toast_Margin, DH - (i + 1) * (#Toast_H + #Toast_Margin), #PB_Ignore, #PB_Ignore)
		Next
	EndProcedure
	
	Procedure _FindByWindow(Win)
		Protected i
		For i = 0 To _Count - 1
			If _Wins(i) = Win : ProcedureReturn i : EndIf
		Next
		ProcedureReturn -1
	EndProcedure
	
	Procedure _FindByCanvas(Canvas)
		Protected i
		For i = 0 To _Count - 1
			If _Canvases(i) = Canvas : ProcedureReturn i : EndIf
		Next
		ProcedureReturn -1
	EndProcedure
	
	Procedure _CloseConfirm()
		UnbindGadgetEvent(_ConfirmOkBtn,     @Handler_ConfirmOK())
		UnbindGadgetEvent(_ConfirmCancelBtn, @Handler_ConfirmCancel())
		UnbindEvent(#PB_Event_CloseWindow,   @Handler_ConfirmClose(), _ConfirmWin)
		CloseWindow(_ConfirmWin)
		_ConfirmWin = 0
	EndProcedure
	
	Procedure _FireConfirm(Result)
		_CloseConfirm()
		If *_ConfirmCb
			!notify$g__confirmcb(v_result);
			*_ConfirmCb = 0
		EndIf
	EndProcedure
	
	;- Event handlers
	
	Procedure Handler_ToastTimer()
		Protected Slot = _FindByWindow(EventWindow())
		If Slot >= 0 : _Dismiss(Slot) : EndIf
	EndProcedure
	
	Procedure Handler_ToastClick()
		Protected Slot
		If EventType() <> #PB_EventType_LeftButtonUp : ProcedureReturn : EndIf
		Slot = _FindByCanvas(EventGadget())
		If Slot >= 0 : _Dismiss(Slot) : EndIf
	EndProcedure
	
	Procedure Handler_ConfirmOK()
		_FireConfirm(1)
	EndProcedure
	
	Procedure Handler_ConfirmCancel()
		_FireConfirm(0)
	EndProcedure
	
	Procedure Handler_ConfirmClose()
		_FireConfirm(0)
	EndProcedure
	
EndModule

; IDE Options = SpiderBasic 3.20 (Windows - x86)
; CursorPosition = 13
; Folding = BAw
; iOSAppOrientation = 0
; AndroidAppCode = 0
; AndroidAppOrientation = 0
; EnableXP
; DPIAware
; CompileSourceDirectory