package ast

import (
	"fmt"
	"strings"

	"gopkg.in/yaml.v3"

	"github.com/go-task/task/v3/errors"
	"github.com/go-task/task/v3/internal/goext"
)

// Platform represents GOOS and GOARCH values
type Platform struct {
	OS   string
	Arch string
}

func (p *Platform) DeepCopy() *Platform {
	if p == nil {
		return nil
	}
	return &Platform{
		OS:   p.OS,
		Arch: p.Arch,
	}
}

type ErrInvalidPlatform struct {
	Platform string
}

func (err *ErrInvalidPlatform) Error() string {
	return fmt.Sprintf(`invalid platform "%s"`, err.Platform)
}

// UnmarshalYAML implements yaml.Unmarshaler interface.
func (p *Platform) UnmarshalYAML(node *yaml.Node) error {
	switch node.Kind {
	case yaml.ScalarNode:
		var platform string
		if err := node.Decode(&platform); err != nil {
			return errors.NewTaskfileDecodeError(err, node)
		}
		if err := p.parsePlatform(platform); err != nil {
			return errors.NewTaskfileDecodeError(err, node)
		}
		return nil
	}
	return errors.NewTaskfileDecodeError(nil, node).WithTypeMessage("platform")
}

// parsePlatform takes a string representing an OS/Arch combination (or either on their own)
// and parses it into the Platform struct. It returns an error if the input string is invalid.
// Valid combinations for input: OS, Arch, OS/Arch
func (p *Platform) parsePlatform(input string) error {
	splitValues := strings.Split(input, "/")
	if len(splitValues) > 2 {
		return &ErrInvalidPlatform{Platform: input}
	}
	if err := p.parseOsOrArch(splitValues[0]); err != nil {
		return &ErrInvalidPlatform{Platform: input}
	}
	if len(splitValues) == 2 {
		if err := p.parseArch(splitValues[1]); err != nil {
			return &ErrInvalidPlatform{Platform: input}
		}
	}
	return nil
}

// parseOsOrArch will check if the given input is a valid OS or Arch value.
// If so, it will store it. If not, an error is returned
func (p *Platform) parseOsOrArch(osOrArch string) error {
	if osOrArch == "" {
		return fmt.Errorf("task: Blank OS/Arch value provided")
	}
	if goext.IsKnownOS(osOrArch) {
		p.OS = osOrArch
		return nil
	}
	if goext.IsKnownArch(osOrArch) {
		p.Arch = osOrArch
		return nil
	}
	return fmt.Errorf("task: Invalid OS/Arch value provided (%s)", osOrArch)
}

func (p *Platform) parseArch(arch string) error {
	if arch == "" {
		return fmt.Errorf("task: Blank Arch value provided")
	}
	if p.Arch != "" {
		return fmt.Errorf("task: Multiple Arch values provided")
	}
	if goext.IsKnownArch(arch) {
		p.Arch = arch
		return nil
	}
	return fmt.Errorf("task: Invalid Arch value provided (%s)", arch)
}