package main

import (
	"context"
	"encoding/json"
	"fmt"
	"log"
	"os"
	"time"

	"macvolumecontrol/logging"
	"macvolumecontrol/volume"

	"code.encyclopediaofdaniel.com/dlprows/streamdeck-sdk"
	sdcontext "code.encyclopediaofdaniel.com/dlprows/streamdeck-sdk/context"
)

var _currentSettings *volume.VolumeSettings = &volume.VolumeSettings{}

func main() {
	logging.Enable()
	log.Println("Starting plugin")

	params, err := streamdeck.ParseRegistrationParams(os.Args)
	if err != nil {
		log.Fatalf("error parsing registration params: %v", err)
	}

	sd := streamdeck.NewClient(context.Background(), params)
	defer sd.Close()

	setup(sd)

	if err := sd.Run(); err != nil {
		log.Fatalf("error running streamdeck client: %v\n", err)
	}
}

func setup(client *streamdeck.Client) {
	log.Println("Registering actions")
	action := client.Action("com.dlprows.macvolumecontrol.dialaction")

	contexts := make(map[string]struct{})

	action.RegisterHandler(streamdeck.DialRotate, func(ctx context.Context, client *streamdeck.Client, event streamdeck.Event) error {
		log.Println("dial rotate")

		p := streamdeck.DialRotatePayload[any]{}

		if err := json.Unmarshal(event.Payload, &p); err != nil {
			return err
		}

		//volume.ChangeVolumeWithKeyboard(p.Ticks)
		newSettings, err := volume.ChangeVolume(p.Ticks)

		if err != nil {
			return err
		}

		return setFeedbackIfNeeded(ctx, client, newSettings)
	})

	action.RegisterHandler(streamdeck.DialDown, func(ctx context.Context, client *streamdeck.Client, event streamdeck.Event) error {
		log.Println("dial down")

		newSettings, err := volume.ToggleMute()

		if err != nil {
			return err
		}

		return setFeedbackIfNeeded(ctx, client, newSettings)
	})

	action.RegisterHandler(streamdeck.TouchTap, func(ctx context.Context, client *streamdeck.Client, event streamdeck.Event) error {
		log.Println("touch tap")

		newSettings, err := volume.ToggleMute()

		if err != nil {
			return err
		}

		return setFeedbackIfNeeded(ctx, client, newSettings)
	})

	action.RegisterHandler(streamdeck.WillAppear, func(ctx context.Context, client *streamdeck.Client, event streamdeck.Event) error {
		log.Println("Will Appear")
		contexts[event.Context] = struct{}{}

		newSettings, err := volume.GetVolumeSettings()

		if err != nil {
			return err
		}

		return setFeedbackIfNeeded(ctx, client, newSettings)
	})

	action.RegisterHandler(streamdeck.WillDisappear, func(ctx context.Context, client *streamdeck.Client, event streamdeck.Event) error {
		log.Println("Will Disappear")
		delete(contexts, event.Context)
		return nil
	})

	//start background thread to keep the display up to date if changed outside the stream deck
	go func() {
		for range time.Tick(time.Second * 1) {
			newSettings, err := volume.GetVolumeSettings()

			if err != nil {
				log.Fatal(err)
			}

			for ctxStr := range contexts {
				//for each context
				//build a new context that can be used to perform outbound requests
				ctx := context.Background()
				ctx = sdcontext.WithContext(ctx, ctxStr)

				setFeedbackIfNeeded(ctx, client, newSettings)
			}
		}
	}()
}

func setFeedbackIfNeeded(ctx context.Context, client *streamdeck.Client, newSettings *volume.VolumeSettings) error {

	if _currentSettings.OutputVolume == newSettings.OutputVolume && _currentSettings.OutputMuted == newSettings.OutputMuted {
		return nil
	}

	payload := FeedbackPayload{}

	opacity := 1.0

	if newSettings.OutputMuted {
		opacity = 0.5
	}

	payload.Value = ValueWithOpacity[string]{
		fmt.Sprintf("%d%%", newSettings.OutputVolume),
		opacity,
	}

	payload.Indicator = ValueWithOpacity[int]{
		newSettings.OutputVolume,
		opacity,
	}

	payload.Icon = ValueWithOpacity[any]{nil, opacity}

	_currentSettings = newSettings
	return client.SetFeedback(ctx, payload)
}

type FeedbackPayload struct {
	Value     ValueWithOpacity[string] `json:"value"`
	Indicator ValueWithOpacity[int]    `json:"indicator"`
	Icon      ValueWithOpacity[any]    `json:"icon"`
}

type ValueWithOpacity[T any] struct {
	Value   T       `json:"value,omitempty"`
	Opacity float64 `json:"opacity"`
}
