//go:build js && wasm

package hp_ipn

import (
	"errors"
	"syscall/js"
)

// Represents the options needed to initialize the TsWasmNet module.
type TsWasmNetOptions struct {
	// Tailscale control URL, e.g., "https://controlplane.tailscale.com"
	ControlURL string

	// Pre-authentication key for the Tailnet
	PreAuthKey string

	// Optional hostname, autogenerated if not provided.
	Hostname string
}

// Parses the provided JS object to validate and extract the TsWasmNetOptions.
func ParseTsWasmNetOptions(obj js.Value) (*TsWasmNetOptions, error) {
	if obj.IsUndefined() || obj.IsNull() {
		return nil, errors.New("TsWasmNetOptions cannot be undefined or null")
	}

	cUrl := safeString("ControlURL", obj)
	preAuthKey := safeString("PreAuthKey", obj)
	hostname := safeString("Hostname", obj)

	if cUrl == "" || preAuthKey == "" || hostname == "" {
		return nil, errors.New("missing required fields in TsWasmNetOptions")
	}

	return &TsWasmNetOptions{
		ControlURL: cUrl,
		PreAuthKey: preAuthKey,
		Hostname:   hostname,
	}, nil
}

// Options passed from the JS side to pass data to xterm.js.
type SSHXtermConfig struct {
	Timeout      int                  // Timeout in seconds for the PTY connection.
	Rows         int                  // Number of rows in the PTY.
	Cols         int                  // Number of columns in the PTY.
	OnStdout     func(data js.Value)  // Fires when the PTY has output.
	OnStderr     func(error js.Value) // Fires when the PTY has an error.
	OnStdin      js.Value             // Passes a function to the JS side to provide input.
	OnConnect    func()               // Fires when the PTY is opened.
	OnDisconnect func()               // Fires when the PTY is closed.
}

// Parses the provided JS object to validate and extract SSHXtermConfig.
func ParseSSHXtermConfig(obj js.Value) (*SSHXtermConfig, error) {
	if obj.IsUndefined() || obj.IsNull() {
		return nil, errors.New("SSHXtermConfig cannot be undefined or null")
	}

	timeout := safeInt("timeout", obj)
	rows := safeInt("rows", obj)
	cols := safeInt("cols", obj)

	if rows <= 0 || cols <= 0 {
		return nil, errors.New("`rows` and `cols` must be positive integers")
	}

	if timeout <= 0 {
		timeout = 30 // Default timeout to 30 seconds if not specified
	}

	config := &SSHXtermConfig{
		Timeout: timeout,
		Rows:    rows,
		Cols:    cols,
	}

	onStdout := obj.Get("onStdout")
	if onStdout.IsUndefined() || onStdout.IsNull() || (onStdout.Type() != js.TypeFunction) {
		return nil, errors.New("`onStdout` is required and must be a function")
	}

	config.OnStdout = func(data js.Value) {
		onStdout.Invoke(data)
	}

	onStderr := obj.Get("onStderr")
	if onStderr.IsUndefined() || onStderr.IsNull() || (onStderr.Type() != js.TypeFunction) {
		return nil, errors.New("`onStderr` is required and must be a function")
	}

	config.OnStderr = func(error js.Value) {
		onStderr.Invoke(error)
	}

	onStdin := obj.Get("onStdin")
	if onStdin.IsUndefined() || onStdin.IsNull() || (onStdin.Type() != js.TypeFunction) {
		return nil, errors.New("`onStdin` is required and must be a function")
	}

	config.OnStdin = onStdin

	onConnect := obj.Get("onConnect")
	if onConnect.IsUndefined() || onConnect.IsNull() || (onConnect.Type() != js.TypeFunction) {
		return nil, errors.New("`onConnect` is required and must be a function")
	}

	config.OnConnect = func() {
		onConnect.Invoke()
	}

	onDisconnect := obj.Get("onDisconnect")
	if onDisconnect.IsUndefined() || onDisconnect.IsNull() || (onDisconnect.Type() != js.TypeFunction) {
		return nil, errors.New("`onDisconnect` is required and must be a function")
	}

	config.OnDisconnect = func() {
		onDisconnect.Invoke()
	}

	return config, nil
}

// Retrieves a string value from a JS object safely.
func safeString(key string, obj js.Value) string {
	if obj.IsUndefined() || obj.IsNull() {
		return ""
	}

	val := obj.Get(key)
	if val.IsUndefined() || val.IsNull() {
		return ""
	}

	return val.String()
}

// Retrieves an integer value from a JS object safely.
func safeInt(key string, obj js.Value) int {
	if obj.IsUndefined() || obj.IsNull() {
		return 0
	}

	val := obj.Get(key)
	if val.IsUndefined() || val.IsNull() {
		return 0
	}

	return val.Int()
}
