// Package sse provides Server-Sent Events infrastructure for real-time updates.
// Includes event types, a central broadcaster, and an HTTP handler.
package sse

import (
	"encoding/json"
	"time"
)

// EventType identifies the category of an SSE event.
type EventType string

const (
	EventPrinterStatus  EventType = "printer.status"
	EventJobStarted     EventType = "job.started"
	EventJobCompleted   EventType = "job.completed"
	EventFilamentLow    EventType = "filament.low"
)

// Event is a JSON-serializable SSE event pushed through the broadcaster.
type Event struct {
	Type      EventType       `json:"type"`
	Payload   json.RawMessage `json:"payload"`
	Timestamp time.Time       `json:"timestamp"`
}

// PrinterStatusPayload carries printer online/offline/printing state.
type PrinterStatusPayload struct {
	PrinterID   int    `json:"printer_id"`
	PrinterName string `json:"printer_name"`
	Status      string `json:"status"` // "online", "offline", "printing"
}

// JobStartedPayload carries initial print job info.
type JobStartedPayload struct {
	JobID      int    `json:"job_id"`
	JobName    string `json:"job_name"`
	PrinterID  int    `json:"printer_id"`
	SpoolID    *int   `json:"spool_id,omitempty"`
}

// JobCompletedPayload carries final print job data including usage.
type JobCompletedPayload struct {
	JobID           int     `json:"job_id"`
	JobName         string  `json:"job_name"`
	PrinterID       int     `json:"printer_id"`
	DurationSeconds *int    `json:"duration_seconds,omitempty"`
	TotalGramsUsed  *float64 `json:"total_grams_used,omitempty"`
	TotalCostUSD    *float64 `json:"total_cost_usd,omitempty"`
}

// FilamentLowPayload alerts that a spool is below its threshold.
type FilamentLowPayload struct {
	SpoolID       int    `json:"spool_id"`
	SpoolName     string `json:"spool_name"`
	RemainingGrams int   `json:"remaining_grams"`
	ThresholdGrams int   `json:"threshold_grams"`
}

// NewEvent creates an Event with the current timestamp from a typed payload.
func NewEvent(eventType EventType, payload any) (Event, error) {
	raw, err := json.Marshal(payload)
	if err != nil {
		return Event{}, err
	}
	return Event{
		Type:      eventType,
		Payload:   raw,
		Timestamp: time.Now().UTC(),
	}, nil
}

// MustEvent creates an Event and panics on marshal failure (for use with
// known-good payloads in tests and internal wiring).
func MustEvent(eventType EventType, payload any) Event {
	ev, err := NewEvent(eventType, payload)
	if err != nil {
		panic("sse.MustEvent: failed to marshal payload: " + err.Error())
	}
	return ev
}

// toSSE formats this Event as a standard SSE message string ready to be
// written to a response writer. The format is:
//
//	event: <type>
//	data: <json>
//
func (e Event) toSSE() string {
	data, _ := json.Marshal(e)
	return "event: " + string(e.Type) + "\n" + "data: " + string(data) + "\n\n"
}
