package search

// This file will not be regenerated automatically.
//
// It serves as dependency injection for your app, add any dependencies you require here.

import (
	"context"
	"errors"
	"fmt"
	"sort"
	"strings"

	godigest "github.com/opencontainers/go-digest"
	ispec "github.com/opencontainers/image-spec/specs-go/v1"
	"github.com/vektah/gqlparser/v2/gqlerror"

	zerr "zotregistry.dev/zot/errors"
	zcommon "zotregistry.dev/zot/pkg/common"
	"zotregistry.dev/zot/pkg/extensions/search/convert"
	cveinfo "zotregistry.dev/zot/pkg/extensions/search/cve"
	cvemodel "zotregistry.dev/zot/pkg/extensions/search/cve/model"
	"zotregistry.dev/zot/pkg/extensions/search/gql_generated"
	"zotregistry.dev/zot/pkg/extensions/search/pagination"
	"zotregistry.dev/zot/pkg/log"
	mTypes "zotregistry.dev/zot/pkg/meta/types"
	reqCtx "zotregistry.dev/zot/pkg/requestcontext"
	"zotregistry.dev/zot/pkg/storage"
)

// THIS CODE IS A STARTING POINT ONLY. IT WILL NOT BE UPDATED WITH SCHEMA CHANGES.

const (
	querySizeLimit = 256
)

// Resolver ...
type Resolver struct {
	cveInfo         cveinfo.CveInfo
	metaDB          mTypes.MetaDB
	storeController storage.StoreController
	log             log.Logger
}

// GetResolverConfig ...
func GetResolverConfig(log log.Logger, storeController storage.StoreController,
	metaDB mTypes.MetaDB, cveInfo cveinfo.CveInfo,
) gql_generated.Config {
	resConfig := &Resolver{
		cveInfo:         cveInfo,
		metaDB:          metaDB,
		storeController: storeController,
		log:             log,
	}

	return gql_generated.Config{
		Resolvers: resConfig, Directives: gql_generated.DirectiveRoot{},
		Complexity: gql_generated.ComplexityRoot{},
	}
}

func NewResolver(log log.Logger, storeController storage.StoreController,
	metaDB mTypes.MetaDB, cveInfo cveinfo.CveInfo,
) *Resolver {
	resolver := &Resolver{
		cveInfo:         cveInfo,
		metaDB:          metaDB,
		storeController: storeController,
		log:             log,
	}

	return resolver
}

func FilterByDigest(digest string) mTypes.FilterFunc {
	// imageMeta will always contain 1 manifest
	return func(repoMeta mTypes.RepoMeta, imageMeta mTypes.ImageMeta) bool {
		lookupDigest := digest

		// Check in case of an index if the index digest matches the search digest
		// For Manifests, this is equivalent to imageMeta.Manifests[0]
		if imageMeta.Digest.String() == lookupDigest {
			return true
		}

		manifest := imageMeta.Manifests[0]

		manifestDigest := manifest.Digest.String()

		// Check the image manifest in index.json matches the search digest
		// This is a blob with mediaType application/vnd.oci.image.manifest.v1+json
		if strings.Contains(manifestDigest, lookupDigest) {
			return true
		}

		// Check the image config matches the search digest
		// This is a blob with mediaType application/vnd.oci.image.config.v1+json
		if strings.Contains(manifest.Manifest.Config.Digest.String(), lookupDigest) {
			return true
		}

		// Check to see if the individual layers in the oci image manifest match the digest
		// These are blobs with mediaType application/vnd.oci.image.layer.v1.tar+gzip
		for _, layer := range manifest.Manifest.Layers {
			if strings.Contains(layer.Digest.String(), lookupDigest) {
				return true
			}
		}

		return false
	}
}

func getImageListForDigest(ctx context.Context, digest string, metaDB mTypes.MetaDB, cveInfo cveinfo.CveInfo,
	requestedPage *gql_generated.PageInput,
) (*gql_generated.PaginatedImagesResult, error) {
	if requestedPage == nil {
		requestedPage = &gql_generated.PageInput{}
	}

	skip := convert.SkipQGLField{
		Vulnerabilities: canSkipField(convert.GetPreloads(ctx), "Images.Vulnerabilities"),
	}

	pageInput := pagination.PageInput{
		Limit:  deref(requestedPage.Limit, 0),
		Offset: deref(requestedPage.Offset, 0),
		SortBy: pagination.SortCriteria(
			deref(requestedPage.SortBy, gql_generated.SortCriteriaRelevance),
		),
	}

	fullImageMetaList, err := metaDB.FilterTags(ctx, mTypes.AcceptAllRepoTag, FilterByDigest(digest))
	if err != nil {
		return &gql_generated.PaginatedImagesResult{}, err
	}

	imageSummaries, pageInfo, err := convert.PaginatedFullImageMeta2ImageSummaries(ctx, fullImageMetaList, skip,
		cveInfo, mTypes.Filter{}, pageInput)
	if err != nil {
		return &gql_generated.PaginatedImagesResult{}, err
	}

	return &gql_generated.PaginatedImagesResult{
		Results: imageSummaries,
		Page: &gql_generated.PageInfo{
			TotalCount: pageInfo.TotalCount,
			ItemCount:  pageInfo.ItemCount,
		},
	}, nil
}

func getImageSummary(ctx context.Context, repo, tag string, digest *string, skipCVE convert.SkipQGLField,
	metaDB mTypes.MetaDB, cveInfo cveinfo.CveInfo, log log.Logger, //nolint:unparam
) (
	*gql_generated.ImageSummary, error,
) {
	repoMeta, err := metaDB.GetRepoMeta(ctx, repo)
	if err != nil {
		return nil, err
	}

	manifestDescriptor, ok := repoMeta.Tags[tag]
	if !ok {
		return nil, gqlerror.Errorf("can't find image: %s:%s", repo, tag)
	}

	repoMeta.Tags = map[mTypes.Tag]mTypes.Descriptor{tag: manifestDescriptor}

	imageDigest := manifestDescriptor.Digest
	if digest != nil {
		imageDigest = *digest
		repoMeta.Tags[tag] = mTypes.Descriptor{
			Digest:    imageDigest,
			MediaType: ispec.MediaTypeImageManifest,
		}
	}

	imageMetaMap, err := metaDB.FilterImageMeta(ctx, []string{imageDigest})
	if err != nil {
		return &gql_generated.ImageSummary{}, err
	}

	imageSummaries := convert.RepoMeta2ImageSummaries(ctx, repoMeta, imageMetaMap, skipCVE, cveInfo)

	if len(imageSummaries) == 0 {
		return &gql_generated.ImageSummary{}, nil
	}

	return imageSummaries[0], nil
}

func getCVEListForImage(
	ctx context.Context, //nolint:unparam // may be used in the future to filter by permissions
	image string,
	cveInfo cveinfo.CveInfo,
	requestedPage *gql_generated.PageInput,
	searchedCVE string,
	excludedCVE string,
	severity string,
	log log.Logger, //nolint:unparam // may be used by devs for debugging
) (*gql_generated.CVEResultForImage, error) {
	if requestedPage == nil {
		requestedPage = &gql_generated.PageInput{}
	}

	pageInput := cvemodel.PageInput{
		Limit:  deref(requestedPage.Limit, 0),
		Offset: deref(requestedPage.Offset, 0),
		SortBy: cvemodel.SortCriteria(
			deref(requestedPage.SortBy, gql_generated.SortCriteriaSeverity),
		),
	}

	repo, ref, _ := zcommon.GetImageDirAndReference(image)

	if ref == "" {
		return &gql_generated.CVEResultForImage{}, gqlerror.Errorf("no reference provided")
	}

	cveList, imageCveSummary, pageInfo, err := cveInfo.GetCVEListForImage(ctx, repo, ref,
		searchedCVE, excludedCVE, severity, pageInput)
	if err != nil {
		return &gql_generated.CVEResultForImage{}, err
	}

	cveids := []*gql_generated.Cve{}

	for _, cveDetail := range cveList {
		vulID := cveDetail.ID
		desc := cveDetail.Description
		title := cveDetail.Title
		severity := cveDetail.Severity
		referenceURL := cveDetail.Reference

		pkgList := make([]*gql_generated.PackageInfo, 0)

		for _, pkg := range cveDetail.PackageList {
			pkg := pkg

			pkgList = append(pkgList,
				&gql_generated.PackageInfo{
					Name:             &pkg.Name,
					PackagePath:      &pkg.PackagePath,
					InstalledVersion: &pkg.InstalledVersion,
					FixedVersion:     &pkg.FixedVersion,
				},
			)
		}

		cveids = append(cveids,
			&gql_generated.Cve{
				ID:          &vulID,
				Title:       &title,
				Description: &desc,
				Severity:    &severity,
				Reference:   &referenceURL,
				PackageList: pkgList,
			},
		)
	}

	return &gql_generated.CVEResultForImage{
		Tag:     &ref,
		CVEList: cveids,
		Summary: &gql_generated.ImageVulnerabilitySummary{
			MaxSeverity:   &imageCveSummary.MaxSeverity,
			UnknownCount:  &imageCveSummary.UnknownCount,
			LowCount:      &imageCveSummary.LowCount,
			MediumCount:   &imageCveSummary.MediumCount,
			HighCount:     &imageCveSummary.HighCount,
			CriticalCount: &imageCveSummary.CriticalCount,
			Count:         &imageCveSummary.Count,
		},
		Page: &gql_generated.PageInfo{
			TotalCount: pageInfo.TotalCount,
			ItemCount:  pageInfo.ItemCount,
		},
	}, nil
}

func getCVEDiffListForImages(
	ctx context.Context, //nolint:unparam // may be used in the future to filter by permissions
	minuend gql_generated.ImageInput,
	subtrahend gql_generated.ImageInput,
	metaDB mTypes.MetaDB,
	cveInfo cveinfo.CveInfo,
	requestedPage *gql_generated.PageInput,
	searchedCVE string,
	excludedCVE string,
	log log.Logger, //nolint:unparam // may be used by devs for debugging
) (*gql_generated.CVEDiffResult, error) {
	minuend, err := resolveImageData(ctx, minuend, metaDB)
	if err != nil {
		return nil, err
	}

	resultMinuend := getImageIdentifier(minuend)
	resultSubtrahend := gql_generated.ImageIdentifier{}

	if subtrahend.Repo != "" {
		subtrahend, err = resolveImageData(ctx, subtrahend, metaDB)
		if err != nil {
			return nil, err
		}

		resultSubtrahend = getImageIdentifier(subtrahend)
	} else {
		// search for base images
		// get minuend image meta
		minuendSummary, err := metaDB.GetImageMeta(godigest.Digest(deref(minuend.Digest, "")))
		if err != nil {
			return &gql_generated.CVEDiffResult{}, err
		}

		// get the base images for the minuend
		minuendBaseImages, err := metaDB.FilterTags(ctx, mTypes.AcceptOnlyRepo(minuend.Repo),
			filterBaseImagesForMeta(minuendSummary))
		if err != nil {
			return &gql_generated.CVEDiffResult{}, err
		}

		// get the best base image as subtrahend
		// get the one with most layers in common
		imgLayers := map[string]struct{}{}

		for _, layer := range minuendSummary.Manifests[0].Manifest.Layers {
			imgLayers[layer.Digest.String()] = struct{}{}
		}

		bestMatchingScore := 0

		for _, baseImage := range minuendBaseImages {
			for _, baseManifest := range baseImage.Manifests {
				currentMatchingScore := 0

				for _, layer := range baseManifest.Manifest.Layers {
					if _, ok := imgLayers[layer.Digest.String()]; ok {
						currentMatchingScore++
					}
				}

				if currentMatchingScore > bestMatchingScore {
					bestMatchingScore = currentMatchingScore

					resultSubtrahend = gql_generated.ImageIdentifier{
						Repo:   baseImage.Repo,
						Tag:    baseImage.Tag,
						Digest: ref(baseImage.Manifests[0].Digest.String()),
						Platform: &gql_generated.Platform{
							Os:   ref(baseImage.Manifests[0].Config.OS),
							Arch: ref(getArch(baseImage.Manifests[0].Config.Platform)),
						},
					}
					subtrahend.Repo = baseImage.Repo
					subtrahend.Tag = baseImage.Tag
					subtrahend.Digest = ref(baseImage.Manifests[0].Digest.String())
				}
			}
		}
	}

	minuendRepoRef := minuend.Repo + "@" + deref(minuend.Digest, "")
	subtrahendRepoRef := subtrahend.Repo + "@" + deref(subtrahend.Digest, "")
	page := dderef(requestedPage)

	diffCVEs, diffSummary, _, err := cveInfo.GetCVEDiffListForImages(ctx, minuendRepoRef, subtrahendRepoRef, searchedCVE,
		excludedCVE, cvemodel.PageInput{
			Limit:  deref(page.Limit, 0),
			Offset: deref(page.Offset, 0),
			SortBy: cvemodel.SortCriteria(deref(page.SortBy, gql_generated.SortCriteriaSeverity)),
		})
	if err != nil {
		return nil, err
	}

	cveids := []*gql_generated.Cve{}

	for _, cveDetail := range diffCVEs {
		vulID := cveDetail.ID
		desc := cveDetail.Description
		title := cveDetail.Title
		severity := cveDetail.Severity
		referenceURL := cveDetail.Reference

		pkgList := make([]*gql_generated.PackageInfo, 0)

		for _, pkg := range cveDetail.PackageList {
			pkg := pkg

			pkgList = append(pkgList,
				&gql_generated.PackageInfo{
					Name:             &pkg.Name,
					InstalledVersion: &pkg.InstalledVersion,
					FixedVersion:     &pkg.FixedVersion,
				},
			)
		}

		cveids = append(cveids,
			&gql_generated.Cve{
				ID:          &vulID,
				Title:       &title,
				Description: &desc,
				Severity:    &severity,
				Reference:   &referenceURL,
				PackageList: pkgList,
			},
		)
	}

	return &gql_generated.CVEDiffResult{
		Minuend:    &resultMinuend,
		Subtrahend: &resultSubtrahend,
		Summary: &gql_generated.ImageVulnerabilitySummary{
			Count:         &diffSummary.Count,
			UnknownCount:  &diffSummary.UnknownCount,
			LowCount:      &diffSummary.LowCount,
			MediumCount:   &diffSummary.MediumCount,
			HighCount:     &diffSummary.HighCount,
			CriticalCount: &diffSummary.CriticalCount,
			MaxSeverity:   &diffSummary.MaxSeverity,
		},
		CVEList: cveids,
		Page:    &gql_generated.PageInfo{},
	}, nil
}

func getImageIdentifier(img gql_generated.ImageInput) gql_generated.ImageIdentifier {
	return gql_generated.ImageIdentifier{
		Repo:     img.Repo,
		Tag:      img.Tag,
		Digest:   img.Digest,
		Platform: getIdentifierPlatform(img.Platform),
	}
}

func getIdentifierPlatform(platform *gql_generated.PlatformInput) *gql_generated.Platform {
	if platform == nil {
		return nil
	}

	return &gql_generated.Platform{
		Os:   platform.Os,
		Arch: platform.Arch,
	}
}

// rename idea: identify image from input.
func resolveImageData(ctx context.Context, imageInput gql_generated.ImageInput, metaDB mTypes.MetaDB,
) (gql_generated.ImageInput, error) {
	if imageInput.Repo == "" {
		return gql_generated.ImageInput{}, zerr.ErrEmptyRepoName
	}

	if imageInput.Tag == "" {
		return gql_generated.ImageInput{}, zerr.ErrEmptyTag
	}

	// try checking if the tag is a simple image first
	repoMeta, err := metaDB.GetRepoMeta(ctx, imageInput.Repo)
	if err != nil {
		return gql_generated.ImageInput{}, err
	}

	descriptor, ok := repoMeta.Tags[imageInput.Tag]
	if !ok {
		return gql_generated.ImageInput{}, zerr.ErrImageNotFound
	}

	switch descriptor.MediaType {
	case ispec.MediaTypeImageManifest:
		imageInput.Digest = ref(descriptor.Digest)

		return imageInput, nil
	case ispec.MediaTypeImageIndex:
		if dderef(imageInput.Digest) == "" && !isPlatformSpecified(imageInput.Platform) {
			return gql_generated.ImageInput{},
				fmt.Errorf("%w: platform or specific manifest digest needed", zerr.ErrAmbiguousInput)
		}

		imageMeta, err := metaDB.GetImageMeta(godigest.Digest(descriptor.Digest))
		if err != nil {
			return gql_generated.ImageInput{}, err
		}

		for _, manifest := range imageMeta.Manifests {
			if manifest.Digest.String() == dderef(imageInput.Digest) ||
				isMatchingPlatform(manifest.Config.Platform, dderef(imageInput.Platform)) {
				imageInput.Digest = ref(manifest.Digest.String())
				imageInput.Platform = &gql_generated.PlatformInput{
					Os:   ref(manifest.Config.OS),
					Arch: ref(getArch(manifest.Config.Platform)),
				}

				return imageInput, nil
			}
		}

		return imageInput, zerr.ErrImageNotFound
	}

	return imageInput, nil
}

func isPlatformSpecified(platformInput *gql_generated.PlatformInput) bool {
	if platformInput == nil {
		return false
	}

	if dderef(platformInput.Os) == "" || dderef(platformInput.Arch) == "" {
		return false
	}

	return true
}

func isMatchingPlatform(platform ispec.Platform, platformInput gql_generated.PlatformInput) bool {
	if platform.OS != deref(platformInput.Os, "") {
		return false
	}

	arch := getArch(platform)

	return arch == deref(platformInput.Arch, "")
}

func getArch(platform ispec.Platform) string {
	arch := platform.Architecture
	if arch != "" && platform.Variant != "" {
		arch += "/" + platform.Variant
	}

	return arch
}

func FilterByTagInfo(tagsInfo []cvemodel.TagInfo) mTypes.FilterFunc {
	return func(repoMeta mTypes.RepoMeta, imageMeta mTypes.ImageMeta) bool {
		manifestDigest := imageMeta.Manifests[0].Digest.String()

		for _, tagInfo := range tagsInfo {
			switch tagInfo.Descriptor.MediaType {
			case ispec.MediaTypeImageManifest:
				if tagInfo.Descriptor.Digest.String() == manifestDigest {
					return true
				}
			case ispec.MediaTypeImageIndex:
				for _, manifestDesc := range tagInfo.Manifests {
					if manifestDesc.Digest.String() == manifestDigest {
						return true
					}
				}
			}
		}

		return false
	}
}

func FilterByRepoAndTagInfo(repo string, tagsInfo []cvemodel.TagInfo) mTypes.FilterFunc {
	return func(repoMeta mTypes.RepoMeta, imageMeta mTypes.ImageMeta) bool {
		if repoMeta.Name != repo {
			return false
		}

		manifestDigest := imageMeta.Manifests[0].Digest.String()

		for _, tagInfo := range tagsInfo {
			switch tagInfo.Descriptor.MediaType {
			case ispec.MediaTypeImageManifest:
				if tagInfo.Descriptor.Digest.String() == manifestDigest {
					return true
				}
			case ispec.MediaTypeImageIndex:
				for _, manifestDesc := range tagInfo.Manifests {
					if manifestDesc.Digest.String() == manifestDigest {
						return true
					}
				}
			}
		}

		return false
	}
}

func getImageListForCVE(
	ctx context.Context,
	cveID string,
	cveInfo cveinfo.CveInfo,
	filter *gql_generated.Filter,
	requestedPage *gql_generated.PageInput,
	metaDB mTypes.MetaDB,
	log log.Logger,
) (*gql_generated.PaginatedImagesResult, error) {
	// Obtain all repos and tags
	// Infinite page to make sure we scan all repos in advance, before filtering results
	// The CVE scan logic is called from here, not in the actual filter,
	// this is because we shouldn't keep the DB locked while we wait on scan results
	reposMeta, err := metaDB.GetMultipleRepoMeta(ctx, func(repoMeta mTypes.RepoMeta) bool { return true })
	if err != nil {
		return &gql_generated.PaginatedImagesResult{}, err
	}

	affectedImages := []cvemodel.TagInfo{}

	for _, repoMeta := range reposMeta {
		repo := repoMeta.Name

		log.Info().Str("repository", repo).Str("CVE", cveID).Msg("extracting list of tags affected by this cve")

		tagsInfo, err := cveInfo.GetImageListForCVE(ctx, repo, cveID)
		if err != nil {
			log.Error().Str("repository", repo).Str("CVE", cveID).Err(err).
				Msg("failed to get image list for this cve from repository")

			return &gql_generated.PaginatedImagesResult{}, err
		}

		affectedImages = append(affectedImages, tagsInfo...)
	}

	// We're not interested in other vulnerabilities
	skip := convert.SkipQGLField{Vulnerabilities: true}

	if requestedPage == nil {
		requestedPage = &gql_generated.PageInput{}
	}

	localFilter := mTypes.Filter{}
	if filter != nil {
		localFilter = mTypes.Filter{
			Os:            filter.Os,
			Arch:          filter.Arch,
			HasToBeSigned: filter.HasToBeSigned,
			IsBookmarked:  filter.IsBookmarked,
			IsStarred:     filter.IsStarred,
		}
	}

	// Actual page requested by user
	pageInput := pagination.PageInput{
		Limit:  deref(requestedPage.Limit, 0),
		Offset: deref(requestedPage.Offset, 0),
		SortBy: pagination.SortCriteria(
			deref(requestedPage.SortBy, gql_generated.SortCriteriaUpdateTime),
		),
	}

	// get all repos
	fullImageMetaList, err := metaDB.FilterTags(ctx, mTypes.AcceptAllRepoTag, FilterByTagInfo(affectedImages))
	if err != nil {
		return &gql_generated.PaginatedImagesResult{}, err
	}

	imageSummaries, pageInfo, err := convert.PaginatedFullImageMeta2ImageSummaries(ctx, fullImageMetaList,
		skip, cveInfo, localFilter, pageInput)
	if err != nil {
		return &gql_generated.PaginatedImagesResult{}, err
	}

	return &gql_generated.PaginatedImagesResult{
		Results: imageSummaries,
		Page: &gql_generated.PageInfo{
			TotalCount: pageInfo.TotalCount,
			ItemCount:  pageInfo.ItemCount,
		},
	}, nil
}

func getImageListWithCVEFixed(
	ctx context.Context,
	cveID string,
	repo string,
	cveInfo cveinfo.CveInfo,
	filter *gql_generated.Filter,
	requestedPage *gql_generated.PageInput,
	metaDB mTypes.MetaDB,
	log log.Logger,
) (*gql_generated.PaginatedImagesResult, error) {
	imageList := make([]*gql_generated.ImageSummary, 0)

	log.Info().Str("repository", repo).Str("CVE", cveID).Msg("extracting list of tags where this cve is fixed")

	tagsInfo, err := cveInfo.GetImageListWithCVEFixed(ctx, repo, cveID)
	if err != nil {
		log.Error().Str("repository", repo).Str("CVE", cveID).Err(err).
			Msg("failed to get image list with this cve fixed from repository")

		return &gql_generated.PaginatedImagesResult{
			Page:    &gql_generated.PageInfo{},
			Results: imageList,
		}, err
	}

	// We're not interested in other vulnerabilities
	skip := convert.SkipQGLField{Vulnerabilities: true}

	if requestedPage == nil {
		requestedPage = &gql_generated.PageInput{}
	}

	localFilter := mTypes.Filter{}
	if filter != nil {
		localFilter = mTypes.Filter{
			Os:            filter.Os,
			Arch:          filter.Arch,
			HasToBeSigned: filter.HasToBeSigned,
			IsBookmarked:  filter.IsBookmarked,
			IsStarred:     filter.IsStarred,
		}
	}

	// Actual page requested by user
	pageInput := pagination.PageInput{
		Limit:  deref(requestedPage.Limit, 0),
		Offset: deref(requestedPage.Offset, 0),
		SortBy: pagination.SortCriteria(
			deref(requestedPage.SortBy, gql_generated.SortCriteriaUpdateTime),
		),
	}

	// get all repos
	fullImageMetaList, err := metaDB.FilterTags(ctx, mTypes.AcceptAllRepoTag, FilterByRepoAndTagInfo(repo, tagsInfo))
	if err != nil {
		return &gql_generated.PaginatedImagesResult{}, err
	}

	imageSummaries, pageInfo, err := convert.PaginatedFullImageMeta2ImageSummaries(ctx, fullImageMetaList,
		skip, cveInfo, localFilter, pageInput)
	if err != nil {
		return &gql_generated.PaginatedImagesResult{}, err
	}

	return &gql_generated.PaginatedImagesResult{
		Results: imageSummaries,
		Page: &gql_generated.PageInfo{
			TotalCount: pageInfo.TotalCount,
			ItemCount:  pageInfo.ItemCount,
		},
	}, nil
}

func repoListWithNewestImage(
	ctx context.Context,
	cveInfo cveinfo.CveInfo,
	log log.Logger, //nolint:unparam // may be used by devs for debugging
	requestedPage *gql_generated.PageInput,
	metaDB mTypes.MetaDB,
) (*gql_generated.PaginatedReposResult, error) {
	paginatedRepos := &gql_generated.PaginatedReposResult{}

	if requestedPage == nil {
		requestedPage = &gql_generated.PageInput{}
	}

	skip := convert.SkipQGLField{
		Vulnerabilities: canSkipField(convert.GetPreloads(ctx), "Results.NewestImage.Vulnerabilities"),
	}

	pageInput := pagination.PageInput{
		Limit:  deref(requestedPage.Limit, 0),
		Offset: deref(requestedPage.Offset, 0),
		SortBy: pagination.SortCriteria(
			deref(requestedPage.SortBy, gql_generated.SortCriteriaUpdateTime),
		),
	}

	repoMetaList, err := metaDB.SearchRepos(ctx, "")
	if err != nil {
		return &gql_generated.PaginatedReposResult{}, err
	}

	imageMetaMap, err := metaDB.FilterImageMeta(ctx, mTypes.GetLatestImageDigests(repoMetaList))
	if err != nil {
		return &gql_generated.PaginatedReposResult{}, err
	}

	repos, pageInfo, err := convert.PaginatedRepoMeta2RepoSummaries(ctx, repoMetaList, imageMetaMap,
		mTypes.Filter{}, pageInput, cveInfo, skip)
	if err != nil {
		return &gql_generated.PaginatedReposResult{}, err
	}

	paginatedRepos.Page = &gql_generated.PageInfo{
		TotalCount: pageInfo.TotalCount,
		ItemCount:  pageInfo.ItemCount,
	}

	paginatedRepos.Results = repos

	return paginatedRepos, nil
}

func getBookmarkedRepos(
	ctx context.Context,
	cveInfo cveinfo.CveInfo,
	log log.Logger, //nolint:unparam // may be used by devs for debugging
	requestedPage *gql_generated.PageInput,
	metaDB mTypes.MetaDB,
) (*gql_generated.PaginatedReposResult, error) {
	bookmarkedRepos, err := metaDB.GetBookmarkedRepos(ctx)
	if err != nil {
		return &gql_generated.PaginatedReposResult{}, err
	}

	filterByName := func(repo string) bool {
		return zcommon.Contains(bookmarkedRepos, repo)
	}

	return getFilteredPaginatedRepos(ctx, cveInfo, filterByName, log, requestedPage, metaDB)
}

func getStarredRepos(
	ctx context.Context,
	cveInfo cveinfo.CveInfo,
	log log.Logger, //nolint:unparam // may be used by devs for debugging
	requestedPage *gql_generated.PageInput,
	metaDB mTypes.MetaDB,
) (*gql_generated.PaginatedReposResult, error) {
	starredRepos, err := metaDB.GetStarredRepos(ctx)
	if err != nil {
		return &gql_generated.PaginatedReposResult{}, err
	}

	filterFn := func(repo string) bool {
		return zcommon.Contains(starredRepos, repo)
	}

	return getFilteredPaginatedRepos(ctx, cveInfo, filterFn, log, requestedPage, metaDB)
}

func getFilteredPaginatedRepos(
	ctx context.Context,
	cveInfo cveinfo.CveInfo,
	filterFn mTypes.FilterRepoNameFunc,
	log log.Logger, //nolint:unparam // may be used by devs for debugging
	requestedPage *gql_generated.PageInput,
	metaDB mTypes.MetaDB,
) (*gql_generated.PaginatedReposResult, error) {
	if requestedPage == nil {
		requestedPage = &gql_generated.PageInput{}
	}

	skip := convert.SkipQGLField{
		Vulnerabilities: canSkipField(convert.GetPreloads(ctx), "Results.NewestImage.Vulnerabilities"),
	}

	pageInput := pagination.PageInput{
		Limit:  deref(requestedPage.Limit, 0),
		Offset: deref(requestedPage.Offset, 0),
		SortBy: pagination.SortCriteria(
			deref(requestedPage.SortBy, gql_generated.SortCriteriaUpdateTime),
		),
	}

	repoMetaList, err := metaDB.FilterRepos(ctx, filterFn, mTypes.AcceptAllRepoMeta)
	if err != nil {
		return &gql_generated.PaginatedReposResult{}, err
	}

	latestImageMeta, err := metaDB.FilterImageMeta(ctx, mTypes.GetLatestImageDigests(repoMetaList))
	if err != nil {
		return &gql_generated.PaginatedReposResult{}, err
	}

	repos, pageInfo, err := convert.PaginatedRepoMeta2RepoSummaries(ctx, repoMetaList, latestImageMeta,
		mTypes.Filter{}, pageInput, cveInfo, skip)
	if err != nil {
		return &gql_generated.PaginatedReposResult{}, err
	}

	return &gql_generated.PaginatedReposResult{
		Results: repos,
		Page: &gql_generated.PageInfo{
			TotalCount: pageInfo.TotalCount,
			ItemCount:  pageInfo.ItemCount,
		},
	}, nil
}

func globalSearch(ctx context.Context, query string, metaDB mTypes.MetaDB, filter *gql_generated.Filter,
	requestedPage *gql_generated.PageInput, cveInfo cveinfo.CveInfo, log log.Logger, //nolint:unparam
) (*gql_generated.PaginatedReposResult, []*gql_generated.ImageSummary, []*gql_generated.LayerSummary, error,
) {
	preloads := convert.GetPreloads(ctx)
	paginatedRepos := gql_generated.PaginatedReposResult{}
	images := []*gql_generated.ImageSummary{}
	layers := []*gql_generated.LayerSummary{}

	if requestedPage == nil {
		requestedPage = &gql_generated.PageInput{}
	}

	localFilter := mTypes.Filter{}
	if filter != nil {
		localFilter = mTypes.Filter{
			Os:            filter.Os,
			Arch:          filter.Arch,
			HasToBeSigned: filter.HasToBeSigned,
			IsBookmarked:  filter.IsBookmarked,
			IsStarred:     filter.IsStarred,
		}
	}

	switch getSearchTarget(query) {
	case RepoTarget:
		skip := convert.SkipQGLField{Vulnerabilities: canSkipField(preloads, "Repos.NewestImage.Vulnerabilities")}
		pageInput := getPageInput(requestedPage)

		repoMetaList, err := metaDB.SearchRepos(ctx, query)
		if err != nil {
			return &gql_generated.PaginatedReposResult{}, []*gql_generated.ImageSummary{},
				[]*gql_generated.LayerSummary{}, err
		}

		imageMetaMap, err := metaDB.FilterImageMeta(ctx, mTypes.GetLatestImageDigests(repoMetaList))
		if err != nil {
			return &gql_generated.PaginatedReposResult{}, []*gql_generated.ImageSummary{},
				[]*gql_generated.LayerSummary{}, err
		}

		repos, pageInfo, err := convert.PaginatedRepoMeta2RepoSummaries(ctx, repoMetaList, imageMetaMap, localFilter,
			pageInput, cveInfo,
			skip)
		if err != nil {
			return &gql_generated.PaginatedReposResult{}, []*gql_generated.ImageSummary{},
				[]*gql_generated.LayerSummary{}, err
		}

		paginatedRepos.Page = &gql_generated.PageInfo{
			TotalCount: pageInfo.TotalCount,
			ItemCount:  pageInfo.ItemCount,
		}

		paginatedRepos.Results = repos
	case ImageTarget:
		skip := convert.SkipQGLField{Vulnerabilities: canSkipField(preloads, "Images.Vulnerabilities")}
		pageInput := getPageInput(requestedPage)

		fullImageMetaList, err := metaDB.SearchTags(ctx, query)
		if err != nil {
			return &gql_generated.PaginatedReposResult{}, []*gql_generated.ImageSummary{}, []*gql_generated.LayerSummary{}, err
		}

		imageSummaries, pageInfo, err := convert.PaginatedFullImageMeta2ImageSummaries(ctx, fullImageMetaList, skip, cveInfo,
			localFilter, pageInput)
		if err != nil {
			return &gql_generated.PaginatedReposResult{}, []*gql_generated.ImageSummary{}, []*gql_generated.LayerSummary{}, err
		}

		images = imageSummaries

		paginatedRepos.Page = &gql_generated.PageInfo{
			TotalCount: pageInfo.TotalCount,
			ItemCount:  pageInfo.ItemCount,
		}
	case TagTarget:
		skip := convert.SkipQGLField{Vulnerabilities: canSkipField(preloads, "Images.Vulnerabilities")}
		pageInput := getPageInput(requestedPage)

		expectedTag := strings.TrimPrefix(query, `:`)
		matchTagName := func(repoName, actualTag string) bool {
			return strings.Contains(strings.ToLower(actualTag), expectedTag)
		}

		fullImageMetaList, err := metaDB.FilterTags(ctx, matchTagName, mTypes.AcceptAllImageMeta)
		if err != nil {
			return &gql_generated.PaginatedReposResult{}, []*gql_generated.ImageSummary{}, []*gql_generated.LayerSummary{}, err
		}

		imageSummaries, pageInfo, err := convert.PaginatedFullImageMeta2ImageSummaries(ctx, fullImageMetaList, skip, cveInfo,
			localFilter, pageInput)
		if err != nil {
			return &gql_generated.PaginatedReposResult{}, []*gql_generated.ImageSummary{}, []*gql_generated.LayerSummary{}, err
		}

		images = imageSummaries

		paginatedRepos.Page = &gql_generated.PageInfo{
			TotalCount: pageInfo.TotalCount,
			ItemCount:  pageInfo.ItemCount,
		}
	case DigestTarget:
		skip := convert.SkipQGLField{Vulnerabilities: canSkipField(preloads, "Images.Vulnerabilities")}
		pageInput := getPageInput(requestedPage)

		searchedDigest := query

		fullImageMetaList, err := metaDB.FilterTags(ctx, mTypes.AcceptAllRepoTag, FilterByDigest(searchedDigest))
		if err != nil {
			return &gql_generated.PaginatedReposResult{}, []*gql_generated.ImageSummary{}, []*gql_generated.LayerSummary{}, err
		}

		imageSummaries, pageInfo, err := convert.PaginatedFullImageMeta2ImageSummaries(ctx, fullImageMetaList, skip, cveInfo,
			localFilter, pageInput)
		if err != nil {
			return &gql_generated.PaginatedReposResult{}, []*gql_generated.ImageSummary{}, []*gql_generated.LayerSummary{}, err
		}

		images = imageSummaries

		paginatedRepos.Page = &gql_generated.PageInfo{
			TotalCount: pageInfo.TotalCount,
			ItemCount:  pageInfo.ItemCount,
		}
	default:
		return &paginatedRepos, images, layers, zerr.ErrInvalidSearchQuery
	}

	return &paginatedRepos, images, layers, nil
}

func getPageInput(requestedPage *gql_generated.PageInput) pagination.PageInput {
	return pagination.PageInput{
		Limit:  deref(requestedPage.Limit, 0),
		Offset: deref(requestedPage.Offset, 0),
		SortBy: pagination.SortCriteria(
			deref(requestedPage.SortBy, gql_generated.SortCriteriaRelevance),
		),
	}
}

type SearchTarget int

const (
	RepoTarget = iota
	ImageTarget
	DigestTarget
	InvalidTarget
	TagTarget
)

func getSearchTarget(query string) SearchTarget {
	if !strings.ContainsAny(query, ":@") {
		return RepoTarget
	}

	if strings.HasPrefix(query, string(godigest.SHA256)+":") {
		return DigestTarget
	}

	if before, after, found := strings.Cut(query, ":"); found {
		if before != "" {
			return ImageTarget
		} else if after != "" {
			return TagTarget
		}
	}

	return InvalidTarget
}

func canSkipField(preloads map[string]bool, s string) bool {
	fieldIsPresent := preloads[s]

	return !fieldIsPresent
}

func derivedImageList(ctx context.Context, image string, digest *string, metaDB mTypes.MetaDB,
	requestedPage *gql_generated.PageInput,
	cveInfo cveinfo.CveInfo, log log.Logger,
) (*gql_generated.PaginatedImagesResult, error) {
	if requestedPage == nil {
		requestedPage = &gql_generated.PageInput{}
	}

	pageInput := pagination.PageInput{
		Limit:  deref(requestedPage.Limit, 0),
		Offset: deref(requestedPage.Offset, 0),
		SortBy: pagination.SortCriteria(
			deref(requestedPage.SortBy, gql_generated.SortCriteriaUpdateTime),
		),
	}

	skip := convert.SkipQGLField{
		Vulnerabilities: canSkipField(convert.GetPreloads(ctx), "Vulnerabilities"),
	}

	imageRepo, imageTag := zcommon.GetImageDirAndTag(image)
	if imageTag == "" {
		return &gql_generated.PaginatedImagesResult{}, gqlerror.Errorf("no reference provided")
	}

	skipReferenceImage := convert.SkipQGLField{
		Vulnerabilities: true,
	}

	searchedImage, err := getImageSummary(ctx, imageRepo, imageTag, digest, skipReferenceImage, metaDB, cveInfo, log)
	if err != nil {
		if errors.Is(err, zerr.ErrRepoMetaNotFound) {
			return &gql_generated.PaginatedImagesResult{}, gqlerror.Errorf("repository not found")
		}

		return &gql_generated.PaginatedImagesResult{}, err
	}

	// we need all available tags
	fullImageMetaList, err := metaDB.FilterTags(ctx, mTypes.AcceptAllRepoTag, filterDerivedImages(searchedImage))
	if err != nil {
		return &gql_generated.PaginatedImagesResult{}, err
	}

	derivedList, pageInfo, err := convert.PaginatedFullImageMeta2ImageSummaries(ctx, fullImageMetaList, skip, cveInfo,
		mTypes.Filter{}, pageInput)
	if err != nil {
		return &gql_generated.PaginatedImagesResult{}, err
	}

	return &gql_generated.PaginatedImagesResult{
		Results: derivedList,
		Page: &gql_generated.PageInfo{
			TotalCount: pageInfo.TotalCount,
			ItemCount:  pageInfo.ItemCount,
		},
	}, nil
}

func filterDerivedImages(image *gql_generated.ImageSummary) mTypes.FilterFunc {
	return func(repoMeta mTypes.RepoMeta, imageMeta mTypes.ImageMeta) bool {
		var addImageToList bool

		imageManifest := imageMeta.Manifests[0]

		for i := range image.Manifests {
			manifestDigest := imageManifest.Digest.String()
			if manifestDigest == *image.Manifests[i].Digest {
				return false
			}

			imageLayers := image.Manifests[i].Layers

			addImageToList = false
			layers := imageManifest.Manifest.Layers

			sameLayer := 0

			for _, l := range imageLayers {
				for _, k := range layers {
					if k.Digest.String() == *l.Digest {
						sameLayer++
					}
				}
			}

			// if all layers are the same
			if sameLayer == len(imageLayers) {
				// it's a derived image
				addImageToList = true
			}

			if addImageToList {
				return true
			}
		}

		return false
	}
}

func baseImageList(ctx context.Context, image string, digest *string, metaDB mTypes.MetaDB,
	requestedPage *gql_generated.PageInput,
	cveInfo cveinfo.CveInfo, log log.Logger,
) (*gql_generated.PaginatedImagesResult, error) {
	if requestedPage == nil {
		requestedPage = &gql_generated.PageInput{}
	}

	pageInput := pagination.PageInput{
		Limit:  deref(requestedPage.Limit, 0),
		Offset: deref(requestedPage.Offset, 0),
		SortBy: pagination.SortCriteria(
			deref(requestedPage.SortBy, gql_generated.SortCriteriaUpdateTime),
		),
	}

	skip := convert.SkipQGLField{
		Vulnerabilities: canSkipField(convert.GetPreloads(ctx), "Vulnerabilities"),
	}

	imageRepo, imageTag := zcommon.GetImageDirAndTag(image)

	if imageTag == "" {
		return &gql_generated.PaginatedImagesResult{}, gqlerror.Errorf("no reference provided")
	}

	skipReferenceImage := convert.SkipQGLField{
		Vulnerabilities: true,
	}

	searchedImage, err := getImageSummary(ctx, imageRepo, imageTag, digest, skipReferenceImage, metaDB, cveInfo, log)
	if err != nil {
		if errors.Is(err, zerr.ErrRepoMetaNotFound) {
			return &gql_generated.PaginatedImagesResult{}, gqlerror.Errorf("repository not found")
		}

		return &gql_generated.PaginatedImagesResult{}, err
	}

	// we need all available tags
	fullImageMetaList, err := metaDB.FilterTags(ctx, mTypes.AcceptAllRepoTag, filterBaseImages(searchedImage))
	if err != nil {
		return &gql_generated.PaginatedImagesResult{}, err
	}

	baseList, pageInfo, err := convert.PaginatedFullImageMeta2ImageSummaries(ctx, fullImageMetaList,
		skip, cveInfo, mTypes.Filter{}, pageInput)
	if err != nil {
		return &gql_generated.PaginatedImagesResult{}, err
	}

	return &gql_generated.PaginatedImagesResult{
		Page: &gql_generated.PageInfo{
			TotalCount: pageInfo.TotalCount,
			ItemCount:  pageInfo.ItemCount,
		},
		Results: baseList,
	}, nil
}

func filterBaseImages(image *gql_generated.ImageSummary) mTypes.FilterFunc {
	return func(repoMeta mTypes.RepoMeta, imageMeta mTypes.ImageMeta) bool {
		var addImageToList bool

		manifest := imageMeta.Manifests[0]

		for i := range image.Manifests {
			manifestDigest := manifest.Digest.String()
			if manifestDigest == *image.Manifests[i].Digest {
				return false
			}

			addImageToList = true

			for _, l := range manifest.Manifest.Layers {
				foundLayer := false

				for _, k := range image.Manifests[i].Layers {
					if l.Digest.String() == *k.Digest {
						foundLayer = true

						break
					}
				}

				if !foundLayer {
					addImageToList = false

					break
				}
			}

			if addImageToList {
				return true
			}
		}

		return false
	}
}

func filterBaseImagesForMeta(image mTypes.ImageMeta) mTypes.FilterFunc {
	return func(repoMeta mTypes.RepoMeta, imageMeta mTypes.ImageMeta) bool {
		var addImageToList bool

		manifest := imageMeta.Manifests[0]

		for i := range image.Manifests {
			manifestDigest := manifest.Digest.String()
			if manifestDigest == image.Manifests[i].Digest.String() {
				return false
			}

			addImageToList = true

			for _, l := range manifest.Manifest.Layers {
				foundLayer := false

				for _, k := range image.Manifests[i].Manifest.Layers {
					if l.Digest.String() == k.Digest.String() {
						foundLayer = true

						break
					}
				}

				if !foundLayer {
					addImageToList = false

					break
				}
			}

			if addImageToList {
				return true
			}
		}

		return false
	}
}

func validateGlobalSearchInput(query string, filter *gql_generated.Filter,
	requestedPage *gql_generated.PageInput,
) error {
	if len(query) > querySizeLimit {
		return fmt.Errorf("max string size limit exceeded for query parameter. max=%d current=%d %w",
			querySizeLimit, len(query), zerr.ErrInvalidRequestParams)
	}

	err := checkFilter(filter)
	if err != nil {
		return err
	}

	err = checkRequestedPage(requestedPage)
	if err != nil {
		return err
	}

	return nil
}

func checkFilter(filter *gql_generated.Filter) error {
	if filter == nil {
		return nil
	}

	for _, arch := range filter.Arch {
		if len(*arch) > querySizeLimit {
			return fmt.Errorf("max string size limit exceeded for arch parameter. max=%d current=%d %w",
				querySizeLimit, len(*arch), zerr.ErrInvalidRequestParams)
		}
	}

	for _, osSys := range filter.Os {
		if len(*osSys) > querySizeLimit {
			return fmt.Errorf("max string size limit exceeded for os parameter. max=%d current=%d %w",
				querySizeLimit, len(*osSys), zerr.ErrInvalidRequestParams)
		}
	}

	return nil
}

func checkRequestedPage(requestedPage *gql_generated.PageInput) error {
	if requestedPage == nil {
		return nil
	}

	if requestedPage.Limit != nil && *requestedPage.Limit < 0 {
		return fmt.Errorf("requested page limit parameter can't be negative %w",
			zerr.ErrInvalidRequestParams)
	}

	if requestedPage.Offset != nil && *requestedPage.Offset < 0 {
		return fmt.Errorf("requested page offset parameter can't be negative %w",
			zerr.ErrInvalidRequestParams)
	}

	return nil
}

func cleanQuery(query string) string {
	query = strings.TrimSpace(query)
	query = strings.Trim(query, "/")
	query = strings.ToLower(query)

	return query
}

func cleanFilter(filter *gql_generated.Filter) *gql_generated.Filter {
	if filter == nil {
		return nil
	}

	if filter.Arch != nil {
		for i := range filter.Arch {
			*filter.Arch[i] = strings.ToLower(*filter.Arch[i])
			*filter.Arch[i] = strings.TrimSpace(*filter.Arch[i])
		}

		filter.Arch = deleteEmptyElements(filter.Arch)
	}

	if filter.Os != nil {
		for i := range filter.Os {
			*filter.Os[i] = strings.ToLower(*filter.Os[i])
			*filter.Os[i] = strings.TrimSpace(*filter.Os[i])
		}

		filter.Os = deleteEmptyElements(filter.Os)
	}

	return filter
}

func deleteEmptyElements(slice []*string) []*string {
	i := 0
	for i < len(slice) {
		if elementIsEmpty(*slice[i]) {
			slice = deleteElementAt(slice, i)
		} else {
			i++
		}
	}

	return slice
}

func elementIsEmpty(s string) bool {
	return s == ""
}

func deleteElementAt(slice []*string, i int) []*string {
	slice[i] = slice[len(slice)-1]
	slice = slice[:len(slice)-1]

	return slice
}

func expandedRepoInfo(ctx context.Context, repo string, metaDB mTypes.MetaDB, cveInfo cveinfo.CveInfo, log log.Logger,
) (*gql_generated.RepoInfo, error) {
	if ok, err := reqCtx.RepoIsUserAvailable(ctx, repo); !ok || err != nil {
		log.Info().Err(err).Str("repository", repo).Bool("availability", ok).Str("component", "graphql").
			Msg("repo user availability")

		return &gql_generated.RepoInfo{}, nil //nolint:nilerr // don't give details to a potential attacker
	}

	repoMeta, err := metaDB.GetRepoMeta(ctx, repo)
	if err != nil {
		log.Error().Err(err).Str("repository", repo).Str("component", "graphql").
			Msg("can't retrieve repoMeta for repository")

		return &gql_generated.RepoInfo{}, err
	}

	tagsDigests := []string{}

	for i := range repoMeta.Tags {
		if i == "" {
			continue
		}

		digest := repoMeta.Tags[i].Digest

		if !zcommon.Contains(tagsDigests, digest) {
			tagsDigests = append(tagsDigests, digest)
		}
	}

	imageMetaMap, err := metaDB.FilterImageMeta(ctx, tagsDigests)
	if err != nil {
		log.Error().Err(err).Str("repository", repo).Str("component", "graphql").
			Msg("can't retrieve imageMeta for repo")

		return &gql_generated.RepoInfo{}, err
	}

	skip := convert.SkipQGLField{
		Vulnerabilities: canSkipField(convert.GetPreloads(ctx), "Summary.NewestImage.Vulnerabilities") &&
			canSkipField(convert.GetPreloads(ctx), "Images.Vulnerabilities"),
	}

	repoSummary, imageSummaries := convert.RepoMeta2ExpandedRepoInfo(ctx, repoMeta, imageMetaMap,
		skip, cveInfo, log)

	dateSortedImages := make(timeSlice, 0, len(imageSummaries))
	for _, imgSummary := range imageSummaries {
		dateSortedImages = append(dateSortedImages, imgSummary)
	}

	sort.Sort(dateSortedImages)

	return &gql_generated.RepoInfo{Summary: repoSummary, Images: dateSortedImages}, nil
}

type timeSlice []*gql_generated.ImageSummary

func (p timeSlice) Len() int {
	return len(p)
}

func (p timeSlice) Less(i, j int) bool {
	return p[i].LastUpdated.After(*p[j].LastUpdated)
}

func (p timeSlice) Swap(i, j int) {
	p[i], p[j] = p[j], p[i]
}

// dderef is a default deref.
func dderef[T any](pointer *T) T {
	var defValue T

	if pointer != nil {
		return *pointer
	}

	return defValue
}

func deref[T any](pointer *T, defaultVal T) T {
	if pointer != nil {
		return *pointer
	}

	return defaultVal
}

func ref[T any](input T) *T {
	ref := input

	return &ref
}

func getImageList(ctx context.Context, repo string, metaDB mTypes.MetaDB, cveInfo cveinfo.CveInfo,
	requestedPage *gql_generated.PageInput, log log.Logger, //nolint:unparam
) (*gql_generated.PaginatedImagesResult, error) {
	if requestedPage == nil {
		requestedPage = &gql_generated.PageInput{}
	}

	skip := convert.SkipQGLField{
		Vulnerabilities: canSkipField(convert.GetPreloads(ctx), "Images.Vulnerabilities"),
	}

	pageInput := pagination.PageInput{
		Limit:  deref(requestedPage.Limit, 0),
		Offset: deref(requestedPage.Offset, 0),
		SortBy: pagination.SortCriteria(
			deref(requestedPage.SortBy, gql_generated.SortCriteriaRelevance),
		),
	}

	var matchRepoName mTypes.FilterRepoTagFunc

	if repo == "" {
		matchRepoName = mTypes.AcceptAllRepoTag
	} else {
		matchRepoName = func(repoName, tag string) bool { return repoName == repo }
	}

	imageMeta, err := metaDB.FilterTags(ctx, matchRepoName, mTypes.AcceptAllImageMeta)
	if err != nil {
		return &gql_generated.PaginatedImagesResult{}, err
	}

	imageList, pageInfo, err := convert.PaginatedFullImageMeta2ImageSummaries(ctx, imageMeta, skip,
		cveInfo, mTypes.Filter{}, pageInput)
	if err != nil {
		return &gql_generated.PaginatedImagesResult{}, err
	}

	return &gql_generated.PaginatedImagesResult{
		Results: imageList,
		Page: &gql_generated.PageInfo{
			TotalCount: pageInfo.TotalCount,
			ItemCount:  pageInfo.ItemCount,
		},
	}, nil
}

func getReferrers(metaDB mTypes.MetaDB, repo string, referredDigest string, artifactTypes []string,
	log log.Logger,
) ([]*gql_generated.Referrer, error) {
	refDigest := godigest.Digest(referredDigest)
	if err := refDigest.Validate(); err != nil {
		log.Error().Err(err).Str("digest", referredDigest).Str("component", "graphql").
			Msg("bad referenced digest string from request")

		return []*gql_generated.Referrer{}, fmt.Errorf("bad digest string from request '%s' %w",
			referredDigest, err)
	}

	referrers, err := metaDB.GetReferrersInfo(repo, refDigest, artifactTypes)
	if err != nil {
		return nil, err
	}

	results := make([]*gql_generated.Referrer, 0, len(referrers))

	for _, referrer := range referrers {
		referrer := referrer

		results = append(results, &gql_generated.Referrer{
			MediaType:    &referrer.MediaType,
			ArtifactType: &referrer.ArtifactType,
			Digest:       &referrer.Digest,
			Size:         &referrer.Size,
			Annotations:  convert.StringMap2Annotations(referrer.Annotations),
		})
	}

	return results, nil
}
