// Package version provides build-time and Perforce-stamped version information
// for p4lf.
//
// This file is stored in Perforce as type 'text+k', which causes Perforce to
// expand the $Id: //p4lf/dev/internal/version/version.go#3 $, $Change: 32824 $, and $DateTime: 2026/06/25 13:26:01 $ keywords on each submit/sync.
// The expanded strings are compiled directly into the binary, so:
//
//	strings p4lf | grep '\$'    # reveals all three stamps without running the binary
//	p4lf -version               # parses and displays them cleanly
//
// SemVer follows Semantic Versioning (Major.Minor.Patch). Increment it manually
// before submitting a notable release point. It is declared as a var so it can
// also be overridden at build time via -ldflags for CI/release pipelines:
//
//	go build -ldflags "-X workshop.perforce.com/p4lf/internal/version.SemVer=1.0.0"
package version

import (
	"fmt"
	"regexp"
	"strings"
)

// SemVer is the hand-maintained semantic version (Major.Minor.Patch).
// Increment this manually before submitting a release point.
var SemVer = "0.1.0"

// P4ID is expanded by Perforce to the depot path and file revision.
// Example after submit: "$Id: //p4lf/dev/internal/version/version.go#3 $"
const P4ID = "$Id: //p4lf/dev/internal/version/version.go#3 $"

// P4Change is expanded by Perforce to the changelist number that last
// submitted this file.
// Example after submit: "$Change: 32824 $"
const P4Change = "$Change: 32824 $"

// P4DateTime is expanded by Perforce to the submit date and time.
// Example after submit: "$DateTime: 2026/06/25 13:26:01 $"
const P4DateTime = "$DateTime: 2026/06/25 13:26:01 $"

// idRe, changeRe, and dateTimeRe are built via string concatenation using
// dollar ("\x24") to prevent Perforce keyword expansion in the pattern text.
// dollar is "$" written in hex so P4 does not interpret it as a keyword prefix.
const dollar = "\x24"

var idRe = regexp.MustCompile(`\` + dollar + `Id:\s*(//[^\s#]+)(#\d+)\s*\` + dollar)
var changeRe = regexp.MustCompile(`\` + dollar + `Change:\s*(\d+)\s*\` + dollar)
var dateTimeRe = regexp.MustCompile(`\` + dollar + `DateTime:\s*(\d{4}/\d{2}/\d{2})`)

// Info holds parsed version fields.
type Info struct {
	SemVer   string // e.g. "0.1.0"
	Stream   string // e.g. "//p4lf/dev"
	Revision string // e.g. "#3"
	Change   string // e.g. "32819"
	Date     string // e.g. "2026/06/25"
}

// Get returns the parsed version information.
func Get() Info {
	info := Info{SemVer: SemVer}

	if m := idRe.FindStringSubmatch(P4ID); m != nil {
		depotPath := m[1] // e.g. //p4lf/dev/internal/version/version.go
		info.Revision = m[2]
		// Extract stream: first two path components (//stream-depot/stream-name).
		parts := strings.SplitN(strings.TrimPrefix(depotPath, "//"), "/", 3)
		if len(parts) >= 2 {
			info.Stream = "//" + parts[0] + "/" + parts[1]
		}
	}

	if m := changeRe.FindStringSubmatch(P4Change); m != nil {
		info.Change = m[1]
	}

	if m := dateTimeRe.FindStringSubmatch(P4DateTime); m != nil {
		info.Date = m[1]
	}

	return info
}

// String returns the single-line version string used in -version output and
// startup log messages.
//
// With P4 keywords expanded:
//
//	p4lf 0.1.0 (CL 32819, //p4lf/dev#3, 2026/06/25)
//
// Without expansion (unexpanded keywords, e.g. before first submit):
//
//	p4lf 0.1.0 (P4 keywords not expanded)
func String() string {
	info := Get()
	if info.Change == "" && info.Stream == "" {
		return fmt.Sprintf("p4lf %s (P4 keywords not expanded)", info.SemVer)
	}
	parts := []string{}
	if info.Change != "" {
		parts = append(parts, "CL "+info.Change)
	}
	if info.Stream != "" {
		s := info.Stream
		if info.Revision != "" {
			s += info.Revision
		}
		parts = append(parts, s)
	}
	if info.Date != "" {
		parts = append(parts, info.Date)
	}
	return fmt.Sprintf("p4lf %s (%s)", info.SemVer, strings.Join(parts, ", "))
}
