package flow

import (
	"fmt"

	starjson "go.starlark.net/lib/json"
	"go.starlark.net/starlark"
)

const (
	signalChannelType = "signalChannel"
)

var (
	_ starlark.HasAttrs = (*ReceiveChannelType)(nil)
)

type ReceiveChannel interface {
	// TODO: is this interface copied form temporal really the best one?
	Receive(payloadJSON *string) bool /* more */

	Unwrap() any
}

type ReceiveChannelType struct {
	c ReceiveChannel

	members starlark.StringDict
}

func (c *ReceiveChannelType) Unwrap() any {
	return c.c.Unwrap()
}

func newReceiveChannel(c ReceiveChannel) *ReceiveChannelType {
	r := &ReceiveChannelType{
		members: map[string]starlark.Value{},
		c:       c,
	}
	r.members["receive"] = starlark.NewBuiltin("receive", r.receive)
	return r
}

func (c *ReceiveChannelType) receive(thread *starlark.Thread, fn *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) {
	// TODO do we have a single type for all channels that contains a type of the value + the value? a-la protobuf.Any

	var payloadJSON string
	more := c.c.Receive(&payloadJSON) // This only works for signal channels - good enough for the demo.

	payloadValue, err := starlark.Call(thread, starjson.Module.Members["decode"], starlark.Tuple{starlark.String(payloadJSON)}, nil)
	if err != nil {
		return nil, err
	}

	return starlark.Tuple{payloadValue, starlark.Bool(more)}, nil
}

func (c *ReceiveChannelType) Attr(name string) (starlark.Value, error) {
	return c.members[name], nil
}

func (c *ReceiveChannelType) AttrNames() []string {
	return c.members.Keys()
}

func (c *ReceiveChannelType) Freeze() {
	c.members.Freeze()
}

func (c *ReceiveChannelType) Hash() (uint32, error) {
	return 0, fmt.Errorf("unhashable: %s", c.Type())
}

func (c *ReceiveChannelType) String() string {
	return signalChannelType
}

func (c *ReceiveChannelType) Truth() starlark.Bool {
	return true
}

func (c *ReceiveChannelType) Type() string {
	return signalChannelType
}
