package rui

import (
	"fmt"
	"net/url"
	"strconv"
	"strings"
)

type bridge interface {
	startUpdateScript(htmlID string) bool
	finishUpdateScript(htmlID string)
	callFunc(funcName string, args ...any) bool
	updateInnerHTML(htmlID, html string)
	appendToInnerHTML(htmlID, html string)
	updateCSSProperty(htmlID, property, value string)
	updateProperty(htmlID, property string, value any)
	removeProperty(htmlID, property string)
	sendResponse()
	setAnimationCSS(css string)
	appendAnimationCSS(css string)
	canvasStart(htmlID string)
	callCanvasFunc(funcName string, args ...any)
	callCanvasVarFunc(v any, funcName string, args ...any)
	callCanvasImageFunc(url string, property string, funcName string, args ...any)
	createCanvasVar(funcName string, args ...any) any
	createPath2D(arg string) any
	updateCanvasProperty(property string, value any)
	canvasFinish()
	canvasTextMetrics(htmlID, font, text string) TextMetrics
	htmlPropertyValue(htmlID, name string) string
	answerReceived(answer DataObject)
	close()
	remoteAddr() string
}

// SessionContent is the interface of a session content
type SessionContent interface {

	// CreateRootView will be called by the library to create a root view of the application
	CreateRootView(session Session) View
}

// Session provide interface to session parameters assess
type Session interface {
	// App return the current application interface
	App() Application
	// ID return the id of the session
	ID() int

	// DarkTheme returns "true" if the dark theme is used
	DarkTheme() bool
	// Mobile returns "true" if current session is displayed on a touch screen device
	TouchScreen() bool
	// PixelRatio returns the ratio of the resolution in physical pixels to the resolution
	// in logical pixels for the current display device.
	PixelRatio() float64
	// TextDirection returns the default text direction (LeftToRightDirection (1) or RightToLeftDirection (2))
	TextDirection() int
	// Constant returns the constant with "tag" name or "" if it is not exists
	Constant(tag string) (string, bool)
	// Color returns the color with "tag" name or 0 if it is not exists
	Color(tag string) (Color, bool)
	// ImageConstant returns the image constant with "tag" name or "" if it is not exists
	ImageConstant(tag string) (string, bool)
	// SetCustomTheme set the custom theme
	SetCustomTheme(name string) bool
	// UserAgent returns the "user-agent" text of the client browser
	UserAgent() string
	// RemoteAddr returns the client address.
	RemoteAddr() string
	// Language returns the current session language
	Language() string
	// SetLanguage set the current session language
	SetLanguage(lang string)
	// GetString returns the text for the current language
	GetString(tag string) (string, bool)

	// Content returns the SessionContent of session
	Content() SessionContent
	setContent(content SessionContent) bool

	// SetTitle sets the text of the browser title/tab
	SetTitle(title string)
	// SetTitleColor sets the color of the browser navigation bar. Supported only in Safari and Chrome for android
	SetTitleColor(color Color)

	// RootView returns the root view of the session
	RootView() View
	// Get returns a value of the view (with id defined by the first argument) property with name defined by the second argument.
	// The type of return value depends on the property. If the property is not set then nil is returned.
	Get(viewID string, tag PropertyName) any
	// Set sets the value (third argument) of the property (second argument) of the view with id defined by the first argument.
	// Return "true" if the value has been set, in the opposite case "false" are returned and
	// a description of the error is written to the log
	Set(viewID string, tag PropertyName, value any) bool

	// DownloadFile downloads (saves) on the client side the file located at the specified path on the server.
	DownloadFile(path string)
	//DownloadFileData downloads (saves) on the client side a file with a specified name and specified content.
	DownloadFileData(filename string, data []byte)
	// OpenURL opens the url in the new browser tab
	OpenURL(url string)

	// ClientItem reads value by key from the client-side storage
	ClientItem(key string) (string, bool)
	// SetClientItem stores a key-value pair in the client-side storage
	SetClientItem(key, value string)
	// RemoveClientItem removes a key-value pair in the client-side storage
	RemoveClientItem(key string)
	// RemoveAllClientItems removes all key-value pair from the client-side storage
	RemoveAllClientItems()

	// SetHotKey sets the function that will be called when the given hotkey is pressed.
	// Invoke SetHotKey(..., ..., nil) for remove hotkey function.
	SetHotKey(keyCode KeyCode, controlKeys ControlKeyMask, fn func(Session))

	// StartTimer starts a timer on the client side.
	// The first argument specifies the timer period in milliseconds.
	// The second argument specifies a function that will be called on each timer event.
	// The result is the id of the timer, which is used to stop the timer
	StartTimer(ms int, timerFunc func(Session)) int
	// StopTimer the timer with the given id
	StopTimer(timerID int)

	getCurrentTheme() Theme
	registerAnimation(props []AnimatedProperty) string

	resolveConstants(value string) (string, bool)
	checkboxOffImage(accentColor Color) string
	checkboxOnImage(accentColor Color) string
	radiobuttonOffImage() string
	radiobuttonOnImage(accentColor Color) string

	viewByHTMLID(id string) View
	nextViewID() string
	styleProperty(styleTag string, propertyTag PropertyName) any

	setBridge(events chan DataObject, bridge bridge)
	writeInitScript(writer *strings.Builder)
	callFunc(funcName string, args ...any)
	updateInnerHTML(htmlID, html string)
	appendToInnerHTML(htmlID, html string)
	updateCSSProperty(htmlID, property, value string)
	updateProperty(htmlID, property string, value any)
	removeProperty(htmlID, property string)
	startUpdateScript(htmlID string) bool
	finishUpdateScript(htmlID string)
	sendResponse()
	addAnimationCSS(css string)
	removeAnimation(keyframe string)
	htmlPropertyValue(htmlID, name string) string

	canvasStart(htmlID string) bool
	callCanvasFunc(funcName string, args ...any)
	createCanvasVar(funcName string, args ...any) any
	createPath(arg string) any
	callCanvasVarFunc(v any, funcName string, args ...any)
	callCanvasImageFunc(url string, property string, funcName string, args ...any)
	updateCanvasProperty(property string, value any)
	canvasFinish()
	canvasTextMetrics(htmlID, font, text string) TextMetrics

	addToEventsQueue(data DataObject)
	handleAnswer(command string, data DataObject) bool
	handleRootSize(data DataObject)
	handleResize(data DataObject)
	handleEvent(command string, data DataObject)
	close()

	onStart()
	onFinish()
	onPause()
	onResume()
	onDisconnect()
	onReconnect()

	ignoreViewUpdates() bool
	setIgnoreViewUpdates(ignore bool)

	popupManager() *popupManager
	imageManager() *imageManager
}

type sessionData struct {
	customTheme      Theme
	currentTheme     Theme
	darkTheme        bool
	touchScreen      bool
	screenWidth      int
	screenHeight     int
	textDirection    int
	pixelRatio       float64
	userAgent        string
	language         string
	languages        []string
	checkboxOff      string
	checkboxOn       string
	radiobuttonOff   string
	radiobuttonOn    string
	app              Application
	sessionID        int
	viewCounter      int
	content          SessionContent
	rootView         View
	ignoreUpdates    bool
	popups           *popupManager
	images           *imageManager
	bridge           bridge
	events           chan DataObject
	animationCounter int
	animationCSS     string
	updateScripts    map[string]*strings.Builder
	clientStorage    map[string]string
	hotkeys          map[string]func(Session)
	timers           map[int]func(Session)
	nextTimerID      int
	pauseTime        int64
}

func newSession(app Application, id int, customTheme string, params DataObject) Session {
	session := new(sessionData)
	session.app = app
	session.sessionID = id
	session.darkTheme = false
	session.touchScreen = false
	session.pixelRatio = 1
	session.textDirection = LeftToRightDirection
	session.languages = []string{}
	session.viewCounter = 0
	session.ignoreUpdates = false
	session.animationCounter = 0
	session.animationCSS = ""
	session.updateScripts = map[string]*strings.Builder{}
	session.clientStorage = map[string]string{}
	session.hotkeys = map[string]func(Session){}
	session.timers = map[int]func(Session){}
	session.nextTimerID = 1

	if customTheme != "" {
		if theme, ok := CreateThemeFromText(customTheme); ok {
			session.customTheme = theme
			session.currentTheme = nil
		}
	}

	if params != nil {
		session.handleSessionInfo(params)
	}

	return session
}

func (session *sessionData) App() Application {
	return session.app
}

func (session *sessionData) ID() int {
	return session.sessionID
}

func (session *sessionData) setBridge(events chan DataObject, bridge bridge) {
	session.events = events
	session.bridge = bridge
}

func (session *sessionData) close() {
	if session.events != nil {
		session.events <- ParseDataText(`session-close{session="` + strconv.Itoa(session.sessionID) + `"}`)
	}
}

func (session *sessionData) styleProperty(styleTag string, propertyTag PropertyName) any {
	if style := session.getCurrentTheme().style(styleTag); style != nil {
		return style.getRaw(propertyTag)
	}
	//errorLogF(`property "%v" not found`, propertyTag)
	return nil
}

func (session *sessionData) nextViewID() string {
	session.viewCounter++
	return fmt.Sprintf("id%06d", session.viewCounter)
}

func (session *sessionData) viewByHTMLID(id string) View {
	if session.rootView == nil {
		return nil
	}
	popupManager := session.popupManager()
	for _, popup := range popupManager.popups {
		if view := popup.viewByHTMLID(id); view != nil {
			return view
		}
	}
	return viewByHTMLID(id, session.rootView)
}

func (session *sessionData) Content() SessionContent {
	return session.content
}

func (session *sessionData) setContent(content SessionContent) bool {
	if content != nil {
		session.content = content
		session.rootView = content.CreateRootView(session)
		if session.rootView != nil {
			session.rootView.setParentID("ruiRootView")
			return true
		}
	}
	return false
}

func (session *sessionData) RootView() View {
	return session.rootView
}

func (session *sessionData) writeInitScript(writer *strings.Builder) {
	if css := session.getCurrentTheme().cssText(session); css != "" {
		css = strings.ReplaceAll(css, "\n", `\n`)
		css = strings.ReplaceAll(css, "\t", `\t`)
		writer.WriteString(`document.querySelector('style').textContent += "`)
		writer.WriteString(css)
		writer.WriteString("\";\n")
	}

	if session.rootView != nil {
		writer.WriteString(`document.getElementById('ruiRootView').innerHTML = '`)
		buffer := allocStringBuilder()
		defer freeStringBuilder(buffer)
		viewHTML(session.rootView, buffer, "")
		text := strings.ReplaceAll(buffer.String(), "'", `\'`)
		writer.WriteString(text)
		writer.WriteString("';\nscanElementsSize();")
	}

	session.updateTooltipConstants()
}

func (session *sessionData) updateTooltipConstants() {
	if color, ok := session.Color("ruiTooltipBackground"); ok {
		session.bridge.callFunc("setCssVar", "--tooltip-background", color.cssString())
	}
	if color, ok := session.Color("ruiTooltipTextColor"); ok {
		session.bridge.callFunc("setCssVar", "--tooltip-text-color", color.cssString())
	}
	if color, ok := session.Color("ruiTooltipShadowColor"); ok {
		session.bridge.callFunc("setCssVar", "--tooltip-shadow-color", color.cssString())
	}

}

func (session *sessionData) reload() {

	css := appStyles + session.getCurrentTheme().cssText(session) + session.animationCSS
	session.bridge.callFunc("setStyles", css)

	if session.rootView != nil {
		buffer := allocStringBuilder()
		defer freeStringBuilder(buffer)

		viewHTML(session.rootView, buffer, "")
		session.bridge.updateInnerHTML("ruiRootView", buffer.String())
		session.bridge.callFunc("scanElementsSize")
	}

	session.updateTooltipConstants()
}

func (session *sessionData) ignoreViewUpdates() bool {
	return session.bridge == nil || session.ignoreUpdates
}

func (session *sessionData) setIgnoreViewUpdates(ignore bool) {
	session.ignoreUpdates = ignore
}

func (session *sessionData) Get(viewID string, tag PropertyName) any {
	if view := ViewByID(session.RootView(), viewID); view != nil {
		return view.Get(tag)
	}
	return nil
}

func (session *sessionData) Set(viewID string, tag PropertyName, value any) bool {
	if view := ViewByID(session.RootView(), viewID); view != nil {
		return view.Set(tag, value)
	}
	return false
}

func (session *sessionData) popupManager() *popupManager {
	if session.popups == nil {
		session.popups = new(popupManager)
		session.popups.popups = []Popup{}
	}
	return session.popups
}

func (session *sessionData) imageManager() *imageManager {
	if session.images == nil {
		session.images = new(imageManager)
		session.images.images = make(map[string]*imageData)
	}
	return session.images
}

func (session *sessionData) callFunc(funcName string, args ...any) {
	if session.bridge != nil {
		session.bridge.callFunc(funcName, args...)
	} else {
		ErrorLog("No connection")
	}
}

func (session *sessionData) updateInnerHTML(htmlID, html string) {
	if !session.ignoreViewUpdates() {
		if session.bridge != nil {
			session.bridge.updateInnerHTML(htmlID, html)
		} else {
			ErrorLog("No connection")
		}
	}
}

func (session *sessionData) appendToInnerHTML(htmlID, html string) {
	if !session.ignoreViewUpdates() {
		if session.bridge != nil {
			session.bridge.appendToInnerHTML(htmlID, html)
		} else {
			ErrorLog("No connection")
		}
	}
}

func (session *sessionData) updateCSSProperty(htmlID, property, value string) {
	if !session.ignoreViewUpdates() {
		if session.bridge != nil {
			session.bridge.updateCSSProperty(htmlID, property, value)
		} else {
			ErrorLog("No connection")
		}
	}
}

func (session *sessionData) updateProperty(htmlID, property string, value any) {
	if !session.ignoreViewUpdates() {
		if session.bridge != nil {
			session.bridge.updateProperty(htmlID, property, value)
		} else {
			ErrorLog("No connection")
		}
	}
}

func (session *sessionData) removeProperty(htmlID, property string) {
	if !session.ignoreViewUpdates() {
		if session.bridge != nil {
			session.bridge.removeProperty(htmlID, property)
		} else {
			ErrorLog("No connection")
		}
	}
}

func (session *sessionData) startUpdateScript(htmlID string) bool {
	if session.bridge != nil {
		return session.bridge.startUpdateScript(htmlID)
	}
	ErrorLog("No connection")
	return false
}

func (session *sessionData) finishUpdateScript(htmlID string) {
	if session.bridge != nil {
		session.bridge.finishUpdateScript(htmlID)
	} else {
		ErrorLog("No connection")
	}
}

func (session *sessionData) sendResponse() {
	if session.bridge != nil {
		session.bridge.sendResponse()
	} else {
		ErrorLog("No connection")
	}
}

func (session *sessionData) addAnimationCSS(css string) {
	session.animationCSS += css
	if session.bridge != nil {
		session.bridge.appendAnimationCSS(css)
	} else {
		ErrorLog("No connection")
	}
}

func (session *sessionData) removeAnimation(keyframe string) {
	css := session.animationCSS
	index := strings.Index(css, "@keyframes "+keyframe)
	if index < 0 {
		return
	}

	start := strings.IndexRune(css[index:], '{')
	if start < 0 {
		return
	}

	n := 1
	end := -1
	for i := start + index + 1; i < len(css); i++ {
		if css[i] == '}' {
			n--
			if n == 0 {
				end = i + 1
				if end < len(css) && css[end] == '\n' {
					end++
				}
				break
			}
		} else if css[i] == '{' {
			n++
		}
	}

	if end > index {
		session.animationCSS = strings.Trim(css[:index]+css[end:], "\n")
		if session.bridge != nil {
			session.bridge.setAnimationCSS(session.animationCSS)
		} else {
			ErrorLog("No connection")
		}
	}
}

func (session *sessionData) canvasStart(htmlID string) bool {
	if session.bridge != nil {
		session.bridge.canvasStart(htmlID)
		return true
	}

	ErrorLog("No connection")
	return false
}

func (session *sessionData) callCanvasFunc(funcName string, args ...any) {
	if session.bridge != nil {
		session.bridge.callCanvasFunc(funcName, args...)
	}
}

func (session *sessionData) updateCanvasProperty(property string, value any) {
	if session.bridge != nil {
		session.bridge.updateCanvasProperty(property, value)
	}
}

func (session *sessionData) createCanvasVar(funcName string, args ...any) any {
	if session.bridge != nil {
		return session.bridge.createCanvasVar(funcName, args...)
	}
	return nil
}

func (session *sessionData) createPath(arg string) any {
	if session.bridge != nil {
		return session.bridge.createPath2D(arg)
	}
	return nil
}

func (session *sessionData) callCanvasVarFunc(v any, funcName string, args ...any) {
	if session.bridge != nil && v != nil {
		session.bridge.callCanvasVarFunc(v, funcName, args...)
	}
}

func (session *sessionData) callCanvasImageFunc(url string, property string, funcName string, args ...any) {
	if session.bridge != nil {
		session.bridge.callCanvasImageFunc(url, property, funcName, args...)
	}
}

func (session *sessionData) canvasFinish() {
	if session.bridge != nil {
		session.bridge.canvasFinish()
	}
}

func (session *sessionData) canvasTextMetrics(htmlID, font, text string) TextMetrics {
	if session.bridge != nil {
		return session.bridge.canvasTextMetrics(htmlID, font, text)
	}

	ErrorLog("No connection")
	return TextMetrics{Width: 0}
}

func (session *sessionData) htmlPropertyValue(htmlID, name string) string {
	if session.bridge != nil {
		return session.bridge.htmlPropertyValue(htmlID, name)
	}

	ErrorLog("No connection")
	return ""
}

func (session *sessionData) handleAnswer(command string, data DataObject) bool {
	switch command {
	case "answer":
		if session.bridge != nil {
			session.bridge.answerReceived(data)
		}

	case "imageLoaded":
		session.imageManager().imageLoaded(data)

	case "imageError":
		session.imageManager().imageLoadError(data)

	default:
		return false
	}

	if session.bridge != nil {
		session.bridge.sendResponse()
	} else {
		ErrorLog("No connection")
	}
	return true
}

func (session *sessionData) handleRootSize(data DataObject) {
	getValue := func(tag string) int {
		if value, ok := data.PropertyValue(tag); ok {
			float, err := strconv.ParseFloat(value, 64)
			if err == nil {
				return int(float)
			}
			ErrorLog(`Resize event error: ` + err.Error())
		} else {
			ErrorLogF(`Resize event error: the property "%s" not found`, tag)
		}
		return 0
	}

	if w := getValue("width"); w > 0 {
		session.screenWidth = w
	}
	if h := getValue("height"); h > 0 {
		session.screenHeight = h
	}
}

func (session *sessionData) handleResize(data DataObject) {
	if node := data.PropertyByTag("views"); node != nil && node.Type() == ArrayNode {
		for _, el := range node.ArrayElements() {
			if el.IsObject() {
				obj := el.Object()
				getFloat := func(tag string) float64 {
					if value, ok := obj.PropertyValue(tag); ok {
						f, err := strconv.ParseFloat(value, 64)
						if err == nil {
							return f
						}
						ErrorLog(`Resize event error: ` + err.Error())
					} else {
						ErrorLogF(`Resize event error: the property "%s" not found`, tag)
					}
					return 0
				}
				if viewID, ok := obj.PropertyValue("id"); ok {
					if n := strings.IndexRune(viewID, '-'); n > 0 {
						if view := session.viewByHTMLID(viewID[:n]); view != nil {
							view.onItemResize(view, viewID[n+1:], getFloat("x"), getFloat("y"), getFloat("width"), getFloat("height"))
						} else {
							DebugLogF(`View with id == %s not found`, viewID[:n])
						}
					} else if view := session.viewByHTMLID(viewID); view != nil {
						view.onResize(view, getFloat("x"), getFloat("y"), getFloat("width"), getFloat("height"))
						view.setScroll(getFloat("scroll-x"), getFloat("scroll-y"), getFloat("scroll-width"), getFloat("scroll-height"))
					} else {
						DebugLogF(`View with id == %s not found`, viewID)
					}
				} else {
					ErrorLog(`"id" property not found`)
				}
			} else {
				ErrorLog(`Resize event error: views element is not object`)
			}
		}
	} else {
		ErrorLog(`Resize event error: invalid "views" property`)
	}
}

func (session *sessionData) handleSessionInfo(params DataObject) {
	if value, ok := params.PropertyValue("touch"); ok {
		session.touchScreen = (value == "1" || value == "true")
	}

	if value, ok := params.PropertyValue("user-agent"); ok {
		session.userAgent = value
	}

	if value, ok := params.PropertyValue("direction"); ok {
		if value == "rtl" {
			session.textDirection = RightToLeftDirection
		}
	}

	if value, ok := params.PropertyValue("language"); ok {
		session.language = value
	}

	if value, ok := params.PropertyValue("languages"); ok {
		session.languages = strings.Split(value, ",")
	}

	if value, ok := params.PropertyValue("dark"); ok {
		session.darkTheme = (value == "1" || value == "true")
	}

	if value, ok := params.PropertyValue("pixel-ratio"); ok {
		if f, err := strconv.ParseFloat(value, 64); err != nil {
			ErrorLog(err.Error())
		} else {
			session.pixelRatio = f
		}
	}

	if node := params.PropertyByTag("storage"); node != nil && node.Type() == ObjectNode {
		if obj := node.Object(); obj != nil {
			for i := 0; i < obj.PropertyCount(); i++ {
				if element := obj.Property(i); element.Type() == TextNode {
					session.clientStorage[element.Tag()] = element.Text()
				}
			}
		}
	}
}

func (session *sessionData) handleEvent(command string, data DataObject) {
	switch command {
	case "session-pause":
		session.onPause()

	case "session-resume":
		session.onResume()

	case "timer":
		if text, ok := data.PropertyValue("timerID"); ok {
			timerID, err := strconv.Atoi(text)
			if err == nil {
				if fn, ok := session.timers[timerID]; ok {
					fn(session)
				} else {
					ErrorLog(`Timer (id = ` + text + `) not exists`)
				}
			} else {
				ErrorLog(err.Error())
			}
		} else {
			ErrorLog(`"timerID" property not found`)
		}

	case "root-size":
		session.handleRootSize(data)

	case "resize":
		session.handleResize(data)

	case "sessionInfo":
		session.handleSessionInfo(data)

	case "storageError":
		if text, ok := data.PropertyValue("error"); ok {
			ErrorLog(text)
		}

	default:
		if viewID, ok := data.PropertyValue("id"); ok {
			if viewID != "body" {
				if view := session.viewByHTMLID(viewID); view != nil {
					view.handleCommand(view, PropertyName(command), data)
				}
			}
			if command == string(KeyDownEvent) {
				var event KeyEvent
				event.init(data)
				session.hotKey(event)
			}
		} else {
			ErrorLog(`"id" property not found. Event: ` + command)
		}
	}

	session.bridge.sendResponse()
}

func (session *sessionData) hotKey(event KeyEvent) {
	popups := session.popupManager().popups
	if count := len(popups); count > 0 {
		if popups[count-1].keyEvent(event) {
			return
		}
	}

	var controlKeys ControlKeyMask = 0
	if event.AltKey {
		controlKeys |= AltKey
	}
	if event.CtrlKey {
		controlKeys |= CtrlKey
	}
	if event.MetaKey {
		controlKeys |= MetaKey
	}
	if event.ShiftKey {
		controlKeys |= ShiftKey
	}

	key := hotkeyCode(KeyCode(event.Code), controlKeys)
	if fn, ok := session.hotkeys[key]; ok && fn != nil {
		fn(session)
	}
}

func hotkeyCode(keyCode KeyCode, controlKeys ControlKeyMask) string {
	buffer := allocStringBuilder()
	defer freeStringBuilder(buffer)

	buffer.WriteString(strings.ToLower(string(keyCode)))
	if controlKeys != 0 {
		buffer.WriteRune('-')
		if controlKeys&AltKey != 0 {
			buffer.WriteRune('a')
		}
		if controlKeys&CtrlKey != 0 {
			buffer.WriteRune('c')
		}
		if controlKeys&MetaKey != 0 {
			buffer.WriteRune('m')
		}
		if controlKeys&ShiftKey != 0 {
			buffer.WriteRune('s')
		}
	}
	return buffer.String()
}

func (session *sessionData) SetHotKey(keyCode KeyCode, controlKeys ControlKeyMask, fn func(Session)) {
	hotkey := hotkeyCode(keyCode, controlKeys)
	if fn == nil {
		delete(session.hotkeys, hotkey)
	} else {
		session.hotkeys[hotkey] = fn
	}
}

func (session *sessionData) SetTitle(title string) {
	title, _ = session.GetString(title)
	session.callFunc("setTitle", title)
}

func (session *sessionData) SetTitleColor(color Color) {
	session.callFunc("setTitleColor", color.cssString())
}

func (session *sessionData) RemoteAddr() string {
	return session.bridge.remoteAddr()
}

func (session *sessionData) OpenURL(urlStr string) {
	if _, err := url.ParseRequestURI(urlStr); err != nil {
		ErrorLog(err.Error())
		return
	}
	session.callFunc("openURL", urlStr)
}

func (session *sessionData) ClientItem(key string) (string, bool) {
	value, ok := session.clientStorage[key]
	return value, ok
}

func (session *sessionData) SetClientItem(key, value string) {
	session.clientStorage[key] = value
	session.bridge.callFunc("localStorageSet", key, value)
}

func (session *sessionData) RemoveClientItem(key string) {
	delete(session.clientStorage, key)
	session.bridge.callFunc("localStorageRemove", key)
}

func (session *sessionData) RemoveAllClientItems() {
	session.clientStorage = map[string]string{}
	session.bridge.callFunc("localStorageClear")
}

func (session *sessionData) addToEventsQueue(data DataObject) {
	session.events <- data
}

func (session *sessionData) StartTimer(ms int, timerFunc func(Session)) int {
	timerID := 0
	if session.bridge != nil {
		timerID = session.nextTimerID
		session.nextTimerID++
		session.timers[timerID] = timerFunc
		session.bridge.callFunc("startTimer", ms, timerID)
	}
	return timerID
}

func (session *sessionData) StopTimer(timerID int) {
	if session.bridge != nil {
		session.bridge.callFunc("stopTimer", timerID)
		delete(session.timers, timerID)
	}
}