package tui

import (
	"context"
	"fmt"
	"os"
	"path/filepath"

	"github.com/gruntwork-io/terragrunt/internal/experiment"
	"github.com/gruntwork-io/terragrunt/internal/services/catalog/module"
	"github.com/gruntwork-io/terragrunt/internal/util"
	"github.com/gruntwork-io/terragrunt/internal/venv"
	"github.com/gruntwork-io/terragrunt/pkg/log"
	"github.com/gruntwork-io/terragrunt/pkg/options"
)

// tempDirFormat mirrors the legacy catalog service's temp-path scheme so
// repeated runs reuse the same clone on disk.
const tempDirFormat = "catalog-%s"

// CatalogTempPath returns the local cache directory for repoURL's clone. It
// resolves os.TempDir() through any symlinks so the subdirectories
// [ComponentDiscovery] derives via filepath.Rel stay inside the clone. macOS
// reports os.TempDir() as a /var/folders symlink to /private/var/folders;
// leaving it unresolved makes Rel emit a "../" traversal that the getter package
// rejects when scaffolding.
func CatalogTempPath(repoURL string) string {
	encodedRepoURL := util.EncodeBase64Sha1(repoURL)
	return filepath.Join(util.ResolvePath(os.TempDir()), fmt.Sprintf(tempDirFormat, encodedRepoURL))
}

// LoadURL clones repoURL via module.NewRepo, walks it with a
// [ComponentDiscovery], resolves the latest release tag once, and emits a
// *ComponentEntry for each discovered component on componentCh. It reuses
// module.NewRepo for the generic git/clone plumbing.
func LoadURL(
	ctx context.Context,
	l log.Logger,
	v venv.Venv,
	opts *options.TerragruntOptions,
	repoURL string,
	componentCh chan<- *ComponentEntry,
) error {
	if repoURL == "" {
		l.Warnf("Empty repository URL encountered, skipping.")
		return nil
	}

	walkWithSymlinks := opts.Experiments.Evaluate(experiment.Symlinks)
	allowCAS := !opts.NoCAS
	slowReporting := opts.Experiments.Evaluate(experiment.SlowTaskReporting)

	tempPath := CatalogTempPath(repoURL)

	l.Debugf("Processing repository %s in temporary path %s", repoURL, tempPath)

	repo, err := module.NewRepo(ctx, l, v.FS, &module.RepoOpts{
		CloneURL:         repoURL,
		Path:             tempPath,
		WalkWithSymlinks: walkWithSymlinks,
		AllowCAS:         allowCAS,
		CASCloneDepth:    opts.CASCloneDepth,
		SlowReporting:    slowReporting,
		RootWorkingDir:   opts.RootWorkingDir,
	})
	if err != nil {
		return fmt.Errorf("failed to initialize repository %s: %w", repoURL, err)
	}

	discovery := NewComponentDiscovery().WithFS(v.FS).WithExtraIgnoreFile(opts.CatalogIgnoreFile)
	if walkWithSymlinks {
		discovery = discovery.WithWalkWithSymlinks()
	}

	components, err := discovery.Discover(repo)
	if err != nil {
		return fmt.Errorf("failed to discover components in repository %s: %w", repoURL, err)
	}

	if len(components) == 0 {
		l.Debugf("No components found in repository %q", repoURL)
		return nil
	}

	l.Debugf("Found %d component(s) in repository %q", len(components), repoURL)

	// Resolve the latest release tag once per repo. All components from the
	// same repo share the Repo, so the tag is set for everyone.
	repo.ResolveLatestTag(ctx, l, v.Exec)

	source := ExtractRepoURL(repo.SourceURL())

	for _, c := range components {
		entry := NewComponentEntry(c).WithSource(source)

		if repo.LatestTag != "" {
			entry = entry.WithVersion(repo.LatestTag)
		}

		select {
		case componentCh <- entry:
		case <-ctx.Done():
			return nil
		}
	}

	return nil
}
