//go:build js && wasm

package hp_ipn

import (
	"errors"
	"log"
	"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 string)  // Fires when the PTY has output.
	OnStderr     func(error string) // 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() {
		return nil, errors.New("OnStdout must be a function")
	}

	if onStdout.Type() != js.TypeFunction {
		return nil, errors.New("OnStdout must be a function")
	}

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

	onStderr := obj.Get("OnStderr")
	if onStderr.IsUndefined() || onStderr.IsNull() {
		return nil, errors.New("OnStderr must be a function")
	}

	if onStderr.Type() != js.TypeFunction {
		return nil, errors.New("OnStderr must be a function")
	}

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

	onStdin := obj.Get("OnStdin")
	if onStdin.IsUndefined() || onStdin.IsNull() {
		return nil, errors.New("OnStdin must be a function")
	}

	if onStdin.Type() != js.TypeFunction {
		return nil, errors.New("OnStdin must be a function")
	}

	config.OnStdin = onStdin

	onConnect := obj.Get("OnConnect")
	if onConnect.IsUndefined() || onConnect.IsNull() {
		return nil, errors.New("OnConnect must be a function")
	}

	if onConnect.Type() != js.TypeFunction {
		return nil, errors.New("OnConnect must be a function")
	}

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

	onDisconnect := obj.Get("OnDisconnect")
	if onDisconnect.IsUndefined() || onDisconnect.IsNull() {
		return nil, errors.New("OnDisconnect must be a function")
	}

	if onDisconnect.Type() != js.TypeFunction {
		return nil, errors.New("OnDisconnect must be a function")
	}

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

	return config, nil
}

func (t *SSHXtermConfig) PassStdinHandler(fn func(string)) {
	handler := js.FuncOf(func(this js.Value, args []js.Value) any {
		if len(args) != 1 || args[0].Type() != js.TypeString {
			return nil
		}

		fn(args[0].String())
		return nil
	})

	log.Printf("Passing stdin handler to JS: %v", handler)
	t.OnStdin.Invoke(handler)
}

// 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()
}
