package metadatair

import (
	"fmt"
	"regexp"
	"strconv"
)

var (
	semverPattern     = regexp.MustCompile(`^(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)(?:-((?:0|[1-9][0-9]*|[0-9]*[A-Za-z-][0-9A-Za-z-]*)(?:\.(?:0|[1-9][0-9]*|[0-9]*[A-Za-z-][0-9A-Za-z-]*))*))?(?:\+([0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*))?$`)
	prereleasePattern = regexp.MustCompile(`^(?:0|[1-9][0-9]*|[0-9]*[A-Za-z-][0-9A-Za-z-]*)(?:\.(?:0|[1-9][0-9]*|[0-9]*[A-Za-z-][0-9A-Za-z-]*))*$`)
	buildPattern      = regexp.MustCompile(`^[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*$`)
)

// Version is the semantic version attached to an IR document contract. The
// zero value is reserved as the unset sentinel and is not a valid document
// version.
type Version struct {
	Major      int
	Minor      int
	Patch      int
	Prerelease string
	Build      string
}

// ParseVersion parses a semantic version of the form MAJOR.MINOR.PATCH with
// optional prerelease and build suffixes.
func ParseVersion(value string) (Version, error) {
	matches := semverPattern.FindStringSubmatch(value)
	if matches == nil {
		return Version{}, fmt.Errorf("metadata IR version %q is not semantic version MAJOR.MINOR.PATCH", value)
	}
	major, err := strconv.Atoi(matches[1])
	if err != nil {
		return Version{}, fmt.Errorf("metadata IR version major component %q is invalid: %w", matches[1], err)
	}
	minor, err := strconv.Atoi(matches[2])
	if err != nil {
		return Version{}, fmt.Errorf("metadata IR version minor component %q is invalid: %w", matches[2], err)
	}
	patch, err := strconv.Atoi(matches[3])
	if err != nil {
		return Version{}, fmt.Errorf("metadata IR version patch component %q is invalid: %w", matches[3], err)
	}
	version := Version{
		Major:      major,
		Minor:      minor,
		Patch:      patch,
		Prerelease: matches[4],
		Build:      matches[5],
	}
	if err := version.Validate(); err != nil {
		return Version{}, fmt.Errorf("metadata IR version %q is invalid: %w", value, err)
	}
	return version, nil
}

func (v Version) IsZero() bool {
	return v.Major == 0 && v.Minor == 0 && v.Patch == 0 &&
		v.Prerelease == "" && v.Build == ""
}

func (v Version) Validate() error {
	if v.Major == 0 && v.Minor == 0 && v.Patch == 0 {
		return fmt.Errorf("metadata IR version core 0.0.0 is reserved as the unset sentinel")
	}
	if v.Major < 0 || v.Minor < 0 || v.Patch < 0 {
		return fmt.Errorf("metadata IR version components must be non-negative")
	}
	if v.Prerelease != "" && !prereleasePattern.MatchString(v.Prerelease) {
		return fmt.Errorf("metadata IR prerelease %q is not a valid semantic version suffix", v.Prerelease)
	}
	if v.Build != "" && !buildPattern.MatchString(v.Build) {
		return fmt.Errorf("metadata IR build %q is not a valid semantic version suffix", v.Build)
	}
	return nil
}

func (v Version) String() string {
	out := fmt.Sprintf("%d.%d.%d", v.Major, v.Minor, v.Patch)
	if v.Prerelease != "" {
		out += "-" + v.Prerelease
	}
	if v.Build != "" {
		out += "+" + v.Build
	}
	return out
}
