package flow

import (
	"context"
	"fmt"
	"path"
	"regexp"
	"strings"

	"go.starlark.net/starlark"
)

var (
	moduleNameRegex = regexp.MustCompile(`^(?:@(\w+)/|)?(.+)$`)
)

type ModuleRef struct {
	// SourceName is the name of the source to use to fetch this module.
	// Empty string means the entrypoint project.
	SourceName string
	File       string
}

func (r ModuleRef) String() string {
	if r.SourceName == "" {
		return r.File
	}
	return "@" + r.SourceName + "/" + r.File
}

type Source interface {
	// SetupThreadLocal configures thread local variables for this source.
	// projectPath is GitLab project path of the entrypoint project.
	SetupThreadLocal(thread *starlark.Thread, projectPath string)
	// RecordSource
	// fullGitRef is the full git reference:
	// - refs/heads/<name> for branches
	// - refs/tags/<name> for tags
	// - <commit id> for commits
	// - empty string or HEAD to use the default branch.
	RecordSource(thread *starlark.Thread, name, projectPath, fullGitRef string) error
	LoadFile(ctx context.Context, thread *starlark.Thread, ref ModuleRef) ([]byte, error)
}

func parseModuleRef(rootPath, module string) (ModuleRef, error) {
	m := moduleNameRegex.FindStringSubmatch(module)
	if m == nil {
		return ModuleRef{}, fmt.Errorf("invalid module: %s", module)
	}
	src := m[1]
	file := m[2]
	if src == "" {
		if path.IsAbs(file) {
			// path.Join() calls Clean, so we call it here too to ensure module is in the canonical form.
			// If we don't, the cache lookup below might fail.
			// Remove / prefix to ensure module names always have the relative format. This is how code
			// fetching them from Gitaly wants them.
			file = strings.TrimPrefix(path.Clean(file), "/")
		} else {
			file = path.Join(rootPath, file)
		}
	} else {
		file = path.Clean(file)
	}
	return ModuleRef{
		SourceName: src,
		File:       file,
	}, nil
}
