package version

import (
	"errors"
	"fmt"
	"regexp"
	"slices"
	"strconv"
)

var (
	// semverRegex is copied from the SemVer 2.0.0 standard
	// see https://semver.org/#is-there-a-suggested-regular-expression-regex-to-check-a-semver-string
	semverRegex = regexp.MustCompile(`^v?(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$`)
)

// Version represents a semantic version
// It's implemented according to https://semver.org
// Limitations: it currently does not expose the pre-release and build metadata extension data.
type Version struct {
	Raw   string
	Major uint64
	Minor uint64
	Patch uint64
	IsPre bool

	// Helpers
	MajorMinor string
}

func NewVersion(v string) (Version, error) {
	m := semverRegex.FindStringSubmatch(v)
	if len(m) < 4 {
		return Version{}, fmt.Errorf("unable to parse version %q as semantic version. Need at least major, minor and patch component", v)
	}

	major, err := strconv.ParseUint(m[1], 10, 64)
	if err != nil {
		return Version{}, fmt.Errorf("unable to parse version %q as semantic version. Invalid major component", v)
	}
	minor, err := strconv.ParseUint(m[2], 10, 64)
	if err != nil {
		return Version{}, fmt.Errorf("unable to parse version %q as semantic version. Invalid minor component", v)
	}
	patch, err := strconv.ParseUint(m[3], 10, 64)
	if err != nil {
		return Version{}, fmt.Errorf("unable to parse version %q as semantic version. Invalid patch component", v)
	}

	isPre := false
	if len(m) >= 4 {
		if m[4] != "" {
			isPre = true
		}
	}

	return Version{
		Raw:        v,
		Major:      major,
		Minor:      minor,
		Patch:      patch,
		IsPre:      isPre,
		MajorMinor: m[1] + "." + m[2],
	}, nil
}

func (v Version) String() string {
	return v.Raw
}

func (v Version) Previous(releasesList []string) (Version, error) {
	idx := slices.Index(releasesList, v.MajorMinor)
	if idx == -1 || idx == len(releasesList)-1 {
		return Version{}, errors.New("no previous version found")
	}

	previous, err := NewVersion(releasesList[idx+1] + ".0")
	if err != nil {
		return Version{}, err
	}
	return previous, nil
}
