package generator

import (
	
	
	
	
	
	

	
	
	
	
	
	
	

	am 
	
	
)

func ( cli.GrafanaParams) (*dashboard.Builder, error) {
	var  []dashboard.Option
	 := telemetry.NormalizeId(.Source)

	for ,  := range strings.Split(.Ids, ",") {
		 := telemetry.NormalizeId()

		 = append(, dashboard.Row("Mach: "+,

			row.WithTimeSeries(
				"Transitions",
				timeseries.Span(12),
				timeseries.DataSource("Prometheus"),
				timeseries.WithPrometheusTarget(
					`am_transitions_`++`{job="`++`"}`,
					prometheus.Legend("Number of transitions"),
				),
			),

			row.WithHeatmap(
				"Transition errors",
				heatmap.Span(12),
				heatmap.Height("150px"),
				heatmap.DataSource("Prometheus"),
				heatmap.WithPrometheusTarget(
					`am_exceptions_`++`{job="`++`"}`,
					prometheus.Legend("Exception"),
				),
			),
		), dashboard.Row(
			"Details: "+,
			row.Collapse(),

			row.WithTimeSeries(
				"Transition Mutations",
				timeseries.Span(12),
				timeseries.DataSource("Prometheus"),
				timeseries.FillOpacity(0),
				timeseries.WithPrometheusTarget(
					`am_queue_size_`++`{job="`++`"}`,
					prometheus.Legend("Avg queue size"),
				),
				timeseries.WithPrometheusTarget(
					`am_handlers_`++`{job="`++`"}`,
					prometheus.Legend("Avg handlers ran"),
				),
				timeseries.WithPrometheusTarget(
					`am_states_added_`++`{job="`++`"}`,
					prometheus.Legend("Avg states added"),
				),
				timeseries.WithPrometheusTarget(
					`am_states_removed_`++`{job="`++`"}`,
					prometheus.Legend("Avg states removed"),
				),
			),

			row.WithTimeSeries(
				"Transition Details",
				timeseries.Span(12),
				timeseries.DataSource("Prometheus"),
				timeseries.FillOpacity(0),
				timeseries.WithPrometheusTarget(
					`am_tx_ticks_`++`{job="`++`"}`,
					prometheus.Legend("Avg machine time taken (ticks)"),
				),
				timeseries.WithPrometheusTarget(
					`am_steps_`++`{job="`++`"}`,
					prometheus.Legend("Avg number of steps"),
				),
				timeseries.WithPrometheusTarget(
					`am_states_touched_`++`{job="`++`"}`,
					prometheus.Legend("Avg states touched"),
				),
			),

			row.WithTimeSeries(
				"States and Relations",
				timeseries.Span(12),
				timeseries.DataSource("Prometheus"),
				timeseries.FillOpacity(0),
				timeseries.WithPrometheusTarget(
					`am_ref_states_`++`{job="`++`"}`,
					prometheus.Legend("States referenced in relations"),
				),
				timeseries.WithPrometheusTarget(
					`am_relations_`++
						`{job="`++`"} / am_states_`++`{job="`++`"}`,
					prometheus.Legend("Avg number of relations per state"),
				),
				timeseries.WithPrometheusTarget(
					`am_relations_`++`{job="`++`"}`,
					prometheus.Legend("Number of relations"),
				),
				timeseries.WithPrometheusTarget(
					`am_states_active_`++
						`{job="`++`"}`,
					prometheus.Legend("Avg active states"),
				),
				timeseries.WithPrometheusTarget(
					`am_states_inactive_`++
						`{job="`++`"}`,
					prometheus.Legend("Avg inactive states"),
				),
			),

			row.WithHeatmap(
				"Average transition time",
				heatmap.Span(12),
				heatmap.Height("150px"),
				heatmap.DataSource("Prometheus"),
				heatmap.WithPrometheusTarget(
					`am_tx_time_`++`{job="`++`"}`,
					prometheus.Legend("Human time (μs)"),
				),
			),
		), dashboard.Row(
			"Logs: "+,
			row.Collapse(),

			row.WithLogs(
				"Logs",
				logs.Span(12),
				logs.Height("800px"),
				logs.DataSource("Loki"),
				logs.WithLokiTarget(
					`{service_name="`++`", asyncmachine_id="`++`"}`),
			),
		))
	}

	 = append(,
		dashboard.AutoRefresh("5s"),
		dashboard.Time("now-5m", "now"),
		dashboard.Tags([]string{"generated"}))

	,  := dashboard.New(.Name, ...)
	if  != nil {
		return nil, 
	}

	.Internal()

	return &, nil
}

func (
	 context.Context,  cli.GrafanaParams,  *dashboard.Builder,
) error {
	if  == nil {
		return fmt.Errorf("missing builder")
	}
	if .Token == "" {
		return fmt.Errorf("missing token")
	}
	if .GrafanaUrl == "" {
		return fmt.Errorf("missing host")
	}
	.GrafanaUrl = strings.TrimRight(.GrafanaUrl, "/")

	// host
	 := grabana.NewClient(&http.Client{}, .GrafanaUrl,
		grabana.WithAPIToken(.Token))

	var  *grabana.Folder
	var  error
	if .Folder != "" {
		// create the folder holding the dashboard for the service
		,  = .FindOrCreateFolder(, .Folder)
		if  != nil {
			return 
		}
	} else {
		,  = .FindOrCreateFolder(, "asyncmachine")
		if  != nil {
			return 
		}
	}

	if ,  := .UpsertDashboard(, , *);  != nil {
		return 
	}

	return nil
}

// MachDashboardEnv binds a Grafana dashboard generator to the [mach], based on
// environment variables:
// - AM_GRAFANA_URL: the Grafana URL
// - AM_GRAFANA_TOKEN: the Grafana API token
// - AM_SERVICE: the service name
//
// This tracer is inherited by submachines, and this function applies only to
// top-level machines.
func ( *am.Machine) error {
	if .ParentId() != "" {
		return nil
	}

	 := cli.GrafanaParams{
		Ids:        .Id(),
		Name:       .Id(),
		Folder:     "asyncmachine",
		GrafanaUrl: os.Getenv("AM_GRAFANA_URL"),
		Token:      os.Getenv("AM_GRAFANA_TOKEN"),
		Source:     os.Getenv("AM_SERVICE"),
	}

	if .GrafanaUrl == "" || .Token == "" || .Source == "" {
		return nil
	}

	 := &SyncTracer{
		p: ,
	}

	.Log("[bind] grafana dashboard")
	return .BindTracer()
}

// SyncTracer is [am.Tracer] for tracing new submachines and syncing the Grafana
// dashboard.
type SyncTracer struct {
	*am.NoOpTracer

	p  cli.GrafanaParams
	mx sync.Mutex
}

func ( *SyncTracer) ( am.Api) context.Context {
	.updateDashboard()
	return nil
}

func ( *SyncTracer) (,  am.Api) {
	// skip RPC machines
	 := os.Getenv("AM_RPC_DBG") != ""
	for ,  := range .Tags() {
		if strings.HasPrefix(, "rpc-") && ! {
			return
		}
	}

	.updateDashboard()
}

func ( *SyncTracer) ( am.Api) {
	.mx.Lock()
	defer .mx.Unlock()

	.p.Ids = strings.TrimLeft(.p.Ids+","+.Id(), ",")

	// TODO refresh on schema change
	,  := GenDashboard(.p)
	if  != nil {
		.AddErr(, nil)
		return
	}

	if  := SyncDashboard(.Ctx(), .p, );  != nil {
		.AddErr(, nil)
	}
}