package remotebrowser

import (
	"context"
	"encoding/base64"
	"time"
)

// RunEvent is an event emitted during script execution.
type RunEvent struct {
	Type    string `json:"type"`              // "event", "log", "error", "done", "capture", "screenshot", "dom_dump", "info", "submit"
	Key     string `json:"key,omitempty"`     // for type=event/screenshot (label)
	Value   any    `json:"value,omitempty"`   // for type=event/capture/screenshot/submit (base64 data URI or arbitrary data)
	URL     string `json:"url,omitempty"`     // for type=screenshot (page URL at capture time)
	Message string `json:"message,omitempty"` // for type=log/error/info
	Data    any    `json:"data,omitempty"`    // for type=log: optional second arg from log(msg, data)
	Time    string `json:"time"`
}

// channelEmitter sends events to a buffered channel. All methods are safe to
// call from multiple goroutines; channel sends are already goroutine-safe.
type channelEmitter struct {
	events chan RunEvent
}

func newChannelEmitter(events chan RunEvent) *channelEmitter {
	return &channelEmitter{events: events}
}

func (e *channelEmitter) emit(key string, value any) {
	e.send(RunEvent{
		Type:  "event",
		Key:   key,
		Value: value,
		Time:  time.Now().UTC().Format(time.RFC3339Nano),
	})
}

func (e *channelEmitter) log(msg string, data ...any) {
	evt := RunEvent{
		Type:    "log",
		Message: msg,
		Time:    time.Now().UTC().Format(time.RFC3339Nano),
	}
	if len(data) > 0 {
		evt.Data = data[0]
	}
	e.send(evt)
}

func (e *channelEmitter) errorf(msg string) {
	e.send(RunEvent{
		Type:    "error",
		Message: msg,
		Time:    time.Now().UTC().Format(time.RFC3339Nano),
	})
}

func (e *channelEmitter) screenshot(label string, buf []byte, pageURL string) {
	e.send(RunEvent{
		Type:  "screenshot",
		Key:   label,
		Value: "data:image/png;base64," + base64.StdEncoding.EncodeToString(buf),
		URL:   pageURL,
		Time:  time.Now().UTC().Format(time.RFC3339Nano),
	})
}

func (e *channelEmitter) capture(data interface{}) {
	e.send(RunEvent{
		Type:  "capture",
		Value: data,
		Time:  time.Now().UTC().Format(time.RFC3339Nano),
	})
}

func (e *channelEmitter) domDump(label string, html string, pageURL string) {
	e.send(RunEvent{
		Type:  "dom_dump",
		Key:   label,
		Value: html,
		URL:   pageURL,
		Time:  time.Now().UTC().Format(time.RFC3339Nano),
	})
}

func (e *channelEmitter) info(msg string) {
	e.send(RunEvent{
		Type:    "info",
		Message: msg,
		Time:    time.Now().UTC().Format(time.RFC3339Nano),
	})
}

func (e *channelEmitter) submitData(data interface{}) {
	e.send(RunEvent{
		Type:  "submit",
		Value: data,
		Time:  time.Now().UTC().Format(time.RFC3339Nano),
	})
}

func (e *channelEmitter) done() {
	e.send(RunEvent{
		Type: "done",
		Time: time.Now().UTC().Format(time.RFC3339Nano),
	})
}

// send delivers evt to the channel, dropping silently if the buffer is full.
func (e *channelEmitter) send(evt RunEvent) {
	select {
	case e.events <- evt:
	default:
	}
}

// sendMust delivers evt to the channel, blocking until space is available or
// ctx is cancelled. Use only for events where a silent drop would corrupt
// session state (e.g. keep_alive).
func (e *channelEmitter) sendMust(ctx context.Context, evt RunEvent) {
	select {
	case e.events <- evt:
	case <-ctx.Done():
	}
}
