// Package protoanalysis provides a toolset for analyzing proto files and packages.
package protoanalysis

import (
	"context"
	"fmt"
	"slices"
	"strings"

	"github.com/ignite/cli/v29/ignite/pkg/errors"
)

// ErrImportNotFound returned when proto file import cannot be found.
var ErrImportNotFound = errors.New("proto import not found")

const (
	protoFilePattern = "*.proto"
	internalPath     = "/internal"
)

// Parse parses proto packages by finding them with given glob pattern.
func Parse(ctx context.Context, cache *Cache, path string) (Packages, error) {
	if cache != nil {
		if packages, ok := cache.Get(path); ok {
			return packages, nil
		}
	}

	parsed, err := parse(ctx, path, protoFilePattern)
	if err != nil {
		return nil, err
	}

	var packages Packages

	for _, pp := range parsed {
		if strings.Contains(pp.dir, internalPath) { // skip internal protos (mainly testing protos, etc.)
			continue
		}

		packages = append(packages, build(*pp))
	}

	if cache != nil {
		cache.Add(path, packages)
	}

	return packages, nil
}

// ParseFile parses a proto file at path.
func ParseFile(path string) (File, error) {
	packages, err := Parse(context.Background(), nil, path)
	if err != nil {
		return File{}, err
	}
	files := packages.Files()
	if len(files) != 1 {
		return File{}, errors.New("path does not point to single file or it cannot be found")
	}
	return files[0], nil
}

// HasMessages checks if the proto package under path contains messages with given names.
func HasMessages(ctx context.Context, path string, names ...string) error {
	pkgs, err := Parse(ctx, NewCache(), path)
	if err != nil {
		return err
	}

	hasName := func(name string) error {
		for _, pkg := range pkgs {
			for _, msg := range pkg.Messages {
				if msg.Name == name {
					return nil
				}
			}
		}
		return errors.Errorf("invalid proto message name %s", name)
	}

	for _, name := range names {
		if err := hasName(name); err != nil {
			return err
		}
	}
	return nil
}

// IsImported checks if the proto package under path imports list of dependencies.
func IsImported(path string, dependencies ...string) error {
	f, err := ParseFile(path)
	if err != nil {
		return err
	}

	for _, wantDep := range dependencies {
		found := slices.Contains(f.Dependencies, wantDep)
		if !found {
			return errors.Wrap(ErrImportNotFound, fmt.Sprintf(
				"invalid proto dependency %s for file %s", wantDep, path),
			)
		}
	}
	return nil
}
