// Copyright 2018 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// Pseudo-versions
//
// Code authors are expected to tag the revisions they want users to use,
// including prereleases. However, not all authors tag versions at all,
// and not all commits a user might want to try will have tags.
// A pseudo-version is a version with a special form that allows us to
// address an untagged commit and order that version with respect to
// other versions we might encounter.
//
// A pseudo-version takes one of the general forms:
//
//	(1) vX.0.0-yyyymmddhhmmss-abcdef123456
//	(2) vX.Y.(Z+1)-0.yyyymmddhhmmss-abcdef123456
//	(3) vX.Y.(Z+1)-0.yyyymmddhhmmss-abcdef123456+incompatible
//	(4) vX.Y.Z-pre.0.yyyymmddhhmmss-abcdef123456
//	(5) vX.Y.Z-pre.0.yyyymmddhhmmss-abcdef123456+incompatible
//
// If there is no recently tagged version with the right major version vX,
// then form (1) is used, creating a space of pseudo-versions at the bottom
// of the vX version range, less than any tagged version, including the unlikely v0.0.0.
//
// If the most recent tagged version before the target commit is vX.Y.Z or vX.Y.Z+incompatible,
// then the pseudo-version uses form (2) or (3), making it a prerelease for the next
// possible semantic version after vX.Y.Z. The leading 0 segment in the prerelease string
// ensures that the pseudo-version compares less than possible future explicit prereleases
// like vX.Y.(Z+1)-rc1 or vX.Y.(Z+1)-1.
//
// If the most recent tagged version before the target commit is vX.Y.Z-pre or vX.Y.Z-pre+incompatible,
// then the pseudo-version uses form (4) or (5), making it a slightly later prerelease.

package module

import (
	
	
	
	

	
	
)

var pseudoVersionRE = lazyregexp.New(`^v[0-9]+\.(0\.0-|\d+\.\d+-([^+]*\.)?0\.)\d{14}-[A-Za-z0-9]+(\+[0-9A-Za-z-]+(\.[0-9A-Za-z-]+)*)?$`)

const PseudoVersionTimestampFormat = "20060102150405"

// PseudoVersion returns a pseudo-version for the given major version ("v1")
// preexisting older tagged version ("" or "v1.2.3" or "v1.2.3-pre"), revision time,
// and revision identifier (usually a 12-byte commit hash prefix).
func (,  string,  time.Time,  string) string {
	if  == "" {
		 = "v0"
	}
	 := fmt.Sprintf("%s-%s", .UTC().Format(PseudoVersionTimestampFormat), )
	 := semver.Build()
	 = semver.Canonical()
	if  == "" {
		return  + ".0.0-" +  // form (1)
	}
	if semver.Prerelease() != "" {
		return  + ".0." +  +  // form (4), (5)
	}

	// Form (2), (3).
	// Extract patch from vMAJOR.MINOR.PATCH
	 := strings.LastIndex(, ".") + 1
	,  := [:], [:]

	// Reassemble.
	return  + incDecimal() + "-0." +  + 
}

// ZeroPseudoVersion returns a pseudo-version with a zero timestamp and
// revision, which may be used as a placeholder.
func ( string) string {
	return PseudoVersion(, "", time.Time{}, "000000000000")
}

// incDecimal returns the decimal string incremented by 1.
func ( string) string {
	// Scan right to left turning 9s to 0s until you find a digit to increment.
	 := []byte()
	 := len() - 1
	for ;  >= 0 && [] == '9'; -- {
		[] = '0'
	}
	if  >= 0 {
		[]++
	} else {
		// digits is all zeros
		[0] = '1'
		 = append(, '0')
	}
	return string()
}

// decDecimal returns the decimal string decremented by 1, or the empty string
// if the decimal is all zeroes.
func ( string) string {
	// Scan right to left turning 0s to 9s until you find a digit to decrement.
	 := []byte()
	 := len() - 1
	for ;  >= 0 && [] == '0'; -- {
		[] = '9'
	}
	if  < 0 {
		// decimal is all zeros
		return ""
	}
	if  == 0 && [] == '1' && len() > 1 {
		 = [1:]
	} else {
		[]--
	}
	return string()
}

// IsPseudoVersion reports whether v is a pseudo-version.
func ( string) bool {
	return strings.Count(, "-") >= 2 && semver.IsValid() && pseudoVersionRE.MatchString()
}

// IsZeroPseudoVersion returns whether v is a pseudo-version with a zero base,
// timestamp, and revision, as returned by [ZeroPseudoVersion].
func ( string) bool {
	return  == ZeroPseudoVersion(semver.Major())
}

// PseudoVersionTime returns the time stamp of the pseudo-version v.
// It returns an error if v is not a pseudo-version or if the time stamp
// embedded in the pseudo-version is not a valid time.
func ( string) (time.Time, error) {
	, , , ,  := parsePseudoVersion()
	if  != nil {
		return time.Time{}, 
	}
	,  := time.Parse("20060102150405", )
	if  != nil {
		return time.Time{}, &InvalidVersionError{
			Version: ,
			Pseudo:  true,
			Err:     fmt.Errorf("malformed time %q", ),
		}
	}
	return , nil
}

// PseudoVersionRev returns the revision identifier of the pseudo-version v.
// It returns an error if v is not a pseudo-version.
func ( string) ( string,  error) {
	_, _, , _,  = parsePseudoVersion()
	return
}

// PseudoVersionBase returns the canonical parent version, if any, upon which
// the pseudo-version v is based.
//
// If v has no parent version (that is, if it is "vX.0.0-[…]"),
// PseudoVersionBase returns the empty string and a nil error.
func ( string) (string, error) {
	, , , ,  := parsePseudoVersion()
	if  != nil {
		return "", 
	}

	switch  := semver.Prerelease();  {
	case "":
		// vX.0.0-yyyymmddhhmmss-abcdef123456 → ""
		if  != "" {
			// Pseudo-versions of the form vX.0.0-yyyymmddhhmmss-abcdef123456+incompatible
			// are nonsensical: the "vX.0.0-" prefix implies that there is no parent tag,
			// but the "+incompatible" suffix implies that the major version of
			// the parent tag is not compatible with the module's import path.
			//
			// There are a few such entries in the index generated by proxy.golang.org,
			// but we believe those entries were generated by the proxy itself.
			return "", &InvalidVersionError{
				Version: ,
				Pseudo:  true,
				Err:     fmt.Errorf("lacks base version, but has build metadata %q", ),
			}
		}
		return "", nil

	case "-0":
		// vX.Y.(Z+1)-0.yyyymmddhhmmss-abcdef123456 → vX.Y.Z
		// vX.Y.(Z+1)-0.yyyymmddhhmmss-abcdef123456+incompatible → vX.Y.Z+incompatible
		 = strings.TrimSuffix(, )
		 := strings.LastIndexByte(, '.')
		if  < 0 {
			panic("base from parsePseudoVersion missing patch number: " + )
		}
		 := decDecimal([+1:])
		if  == "" {
			// vX.0.0-0 is invalid, but has been observed in the wild in the index
			// generated by requests to proxy.golang.org.
			//
			// NOTE(bcmills): I cannot find a historical bug that accounts for
			// pseudo-versions of this form, nor have I seen such versions in any
			// actual go.mod files. If we find actual examples of this form and a
			// reasonable theory of how they came into existence, it seems fine to
			// treat them as equivalent to vX.0.0 (especially since the invalid
			// pseudo-versions have lower precedence than the real ones). For now, we
			// reject them.
			return "", &InvalidVersionError{
				Version: ,
				Pseudo:  true,
				Err:     fmt.Errorf("version before %s would have negative patch number", ),
			}
		}
		return [:+1] +  + , nil

	default:
		// vX.Y.Z-pre.0.yyyymmddhhmmss-abcdef123456 → vX.Y.Z-pre
		// vX.Y.Z-pre.0.yyyymmddhhmmss-abcdef123456+incompatible → vX.Y.Z-pre+incompatible
		if !strings.HasSuffix(, ".0") {
			panic(`base from parsePseudoVersion missing ".0" before date: ` + )
		}
		return strings.TrimSuffix(, ".0") + , nil
	}
}

var errPseudoSyntax = errors.New("syntax error")

func ( string) (, , ,  string,  error) {
	if !IsPseudoVersion() {
		return "", "", "", "", &InvalidVersionError{
			Version: ,
			Pseudo:  true,
			Err:     errPseudoSyntax,
		}
	}
	 = semver.Build()
	 = strings.TrimSuffix(, )
	 := strings.LastIndex(, "-")
	,  = [:], [+1:]
	 := strings.LastIndex(, "-")
	if  := strings.LastIndex(, ".");  >  {
		 = [:] // "vX.Y.Z-pre.0" or "vX.Y.(Z+1)-0"
		 = [+1:]
	} else {
		 = [:] // "vX.0.0"
		 = [+1:]
	}
	return , , , , nil
}