package github

import (
	"context"
	"crypto/sha256"
	"encoding/hex"
	"encoding/json"
	"fmt"
	"io"
	"net/http"
	"net/url"
	"sort"
	"strconv"
	"strings"
	"sync"
	"time"

	reposync "github.com/pingcap/agent-git-service/internal/sync"
	"github.com/pingcap/agent-git-service/internal/sync/bulk"
	"github.com/pingcap/agent-git-service/internal/sync/canonical"
	"github.com/pingcap/agent-git-service/internal/sync/githubapp"
	"github.com/pingcap/agent-git-service/internal/sync/gittransport"
)

type Factory struct {
	Tokens         TokenSource
	HTTPClient     *http.Client
	MaxConcurrency int
	MaxAssetBytes  int64
}

func (f *Factory) ValidateRepository(ctx context.Context, connectionID, repositoryID, fullName string) error {
	_, err := f.ResolveRepository(ctx, connectionID, repositoryID, fullName)
	return err
}

func (f *Factory) ResolveRepository(ctx context.Context, connectionID, repositoryID, fullName string) (string, error) {
	resolved, _, err := f.ResolveRepositoryWithVisibility(ctx, connectionID, repositoryID, fullName)
	return resolved, err
}

func (f *Factory) ResolveRepositoryWithVisibility(ctx context.Context, connectionID, repositoryID, fullName string) (string, string, error) {
	token, err := f.Tokens.Token(ctx, connectionID)
	if err != nil {
		return "", "", err
	}
	if err := validateSyncPermissions(token.Permissions); err != nil {
		return "", "", err
	}
	if strings.TrimSpace(fullName) == "" {
		client, err := newAPIClient(f.Tokens, connectionID, f.HTTPClient)
		if err != nil {
			return "", "", err
		}
		var response struct {
			Node *struct {
				ID            string `json:"id"`
				NameWithOwner string `json:"nameWithOwner"`
			} `json:"node"`
		}
		if err := client.doGraphQL(ctx, `query RepositorySyncRepository($id: ID!) { node(id: $id) { ... on Repository { id nameWithOwner } } }`, map[string]any{"id": repositoryID}, &response); err != nil {
			return "", "", err
		}
		if response.Node == nil || response.Node.ID != repositoryID || strings.TrimSpace(response.Node.NameWithOwner) == "" {
			return "", "", fmt.Errorf("GitHub repository %q was not found or is not accessible to the installation", repositoryID)
		}
		fullName = response.Node.NameWithOwner
	}
	provider, err := f.Open(ctx, reposync.Endpoint{Provider: "github", RepositoryID: repositoryID, RepositoryFullName: fullName, ConnectionID: connectionID})
	if err != nil {
		return "", "", err
	}
	resolved := provider.Endpoint().RepositoryFullName
	visibility := provider.(*Provider).Visibility()
	return resolved, visibility, provider.Close()
}

func validateSyncPermissions(permissions map[string]string) error {
	missing := missingSyncPermissions(permissions)
	if len(missing) != 0 {
		return fmt.Errorf("GitHub App installation requires %s", strings.Join(missing, ", "))
	}
	return nil
}

func missingSyncPermissions(permissions map[string]string) []string {
	required := []struct{ name, level string }{{"contents", "write"}, {"issues", "write"}, {"pull_requests", "write"}, {"workflows", "write"}, {"metadata", "read"}}
	missing := make([]string, 0)
	for _, requirement := range required {
		name, level := requirement.name, requirement.level
		actual := strings.ToLower(strings.TrimSpace(permissions[name]))
		allowed := actual == level || actual == "admin" || (level == "read" && actual == "write")
		if !allowed {
			missing = append(missing, fmt.Sprintf("%s:%s (got %q)", name, level, actual))
		}
	}
	return missing
}

type githubConnectionDiscoverySource interface {
	ListInstallations(context.Context) ([]githubapp.Installation, error)
	ListRepositories(context.Context, string) ([]githubapp.Repository, error)
}

type githubConnectionAuthorizationSource interface {
	InstallURL(string) (string, error)
	AuthorizeURL(string) (string, error)
	VerifyInstallation(context.Context, string, int64) (githubapp.Authorization, error)
	VerifyInstallationChange(context.Context, string, int64, []int64) (githubapp.Authorization, error)
	VerifyAuthorizations(context.Context, string) (githubapp.Authorization, error)
}

func (f *Factory) GitHubInstallURL(state string) (string, error) {
	authorizer, ok := f.Tokens.(githubConnectionAuthorizationSource)
	if !ok {
		return "", fmt.Errorf("GitHub App installation onboarding is unavailable")
	}
	return authorizer.InstallURL(state)
}

func (f *Factory) VerifyGitHubInstallation(ctx context.Context, code string, installationID int64) (reposync.GitHubAuthorization, error) {
	authorizer, ok := f.Tokens.(githubConnectionAuthorizationSource)
	if !ok {
		return reposync.GitHubAuthorization{}, fmt.Errorf("GitHub App installation onboarding is unavailable")
	}
	authorization, err := authorizer.VerifyInstallation(ctx, code, installationID)
	if err != nil {
		return reposync.GitHubAuthorization{}, err
	}
	return syncGitHubAuthorization(authorization), nil
}

func (f *Factory) VerifyGitHubInstallationChange(ctx context.Context, code string, installationID int64, baseline []int64) (reposync.GitHubAuthorization, error) {
	authorizer, ok := f.Tokens.(githubConnectionAuthorizationSource)
	if !ok {
		return reposync.GitHubAuthorization{}, fmt.Errorf("GitHub App installation onboarding is unavailable")
	}
	authorization, err := authorizer.VerifyInstallationChange(ctx, code, installationID, baseline)
	if err != nil {
		return reposync.GitHubAuthorization{}, err
	}
	return syncGitHubAuthorization(authorization), nil
}

func (f *Factory) GitHubAuthorizeURL(state string) (string, error) {
	authorizer, ok := f.Tokens.(githubConnectionAuthorizationSource)
	if !ok {
		return "", fmt.Errorf("GitHub App reauthorization is unavailable")
	}
	return authorizer.AuthorizeURL(state)
}

func (f *Factory) VerifyGitHubAuthorizations(ctx context.Context, code string) (reposync.GitHubAuthorization, error) {
	authorizer, ok := f.Tokens.(githubConnectionAuthorizationSource)
	if !ok {
		return reposync.GitHubAuthorization{}, fmt.Errorf("GitHub App reauthorization is unavailable")
	}
	authorization, err := authorizer.VerifyAuthorizations(ctx, code)
	if err != nil {
		return reposync.GitHubAuthorization{}, err
	}
	return syncGitHubAuthorization(authorization), nil
}

func syncGitHubAuthorization(authorization githubapp.Authorization) reposync.GitHubAuthorization {
	result := reposync.GitHubAuthorization{
		User: reposync.GitHubUserIdentity{
			ID: authorization.User.ID, Login: authorization.User.Login, AvatarURL: authorization.User.AvatarURL,
		},
		Installations: make([]reposync.GitHubInstallation, 0, len(authorization.Installations)),
	}
	for _, item := range authorization.Installations {
		result.Installations = append(result.Installations, syncGitHubInstallation(item))
	}
	return result
}

func syncGitHubInstallation(item githubapp.Installation) reposync.GitHubInstallation {
	missing := missingSyncPermissions(item.Permissions)
	return reposync.GitHubInstallation{
		ConnectionID: "github-app-installation:" + strconv.FormatInt(item.ID, 10), InstallationID: item.ID,
		AccountLogin: item.AccountLogin, AccountType: item.AccountType, AccountAvatarURL: item.AccountAvatarURL,
		ManagementURL: item.HTMLURL, RepositorySelection: item.RepositorySelection, Permissions: item.Permissions,
		PermissionsValid: len(missing) == 0, MissingPermissions: missing,
	}
}

func (f *Factory) ListGitHubInstallations(ctx context.Context) ([]reposync.GitHubInstallation, error) {
	discovery, ok := f.Tokens.(githubConnectionDiscoverySource)
	if !ok {
		return nil, fmt.Errorf("GitHub App connection discovery is unavailable")
	}
	items, err := discovery.ListInstallations(ctx)
	if err != nil {
		return nil, err
	}
	result := make([]reposync.GitHubInstallation, 0, len(items))
	for _, item := range items {
		missing := missingSyncPermissions(item.Permissions)
		result = append(result, reposync.GitHubInstallation{
			ConnectionID: "github-app-installation:" + strconv.FormatInt(item.ID, 10), InstallationID: item.ID,
			AccountLogin: item.AccountLogin, AccountType: item.AccountType, AccountAvatarURL: item.AccountAvatarURL,
			ManagementURL: item.HTMLURL, RepositorySelection: item.RepositorySelection, Permissions: item.Permissions,
			PermissionsValid: len(missing) == 0, MissingPermissions: missing,
		})
	}
	sort.Slice(result, func(i, j int) bool {
		return strings.ToLower(result[i].AccountLogin) < strings.ToLower(result[j].AccountLogin)
	})
	return result, nil
}

func (f *Factory) ListGitHubRepositories(ctx context.Context, connectionID string) ([]reposync.GitHubRepositoryOption, error) {
	discovery, ok := f.Tokens.(githubConnectionDiscoverySource)
	if !ok {
		return nil, fmt.Errorf("GitHub App repository discovery is unavailable")
	}
	items, err := discovery.ListRepositories(ctx, connectionID)
	if err != nil {
		return nil, err
	}
	result := make([]reposync.GitHubRepositoryOption, 0, len(items))
	for _, item := range items {
		if strings.TrimSpace(item.NodeID) == "" || strings.TrimSpace(item.FullName) == "" {
			continue
		}
		result = append(result, reposync.GitHubRepositoryOption{ID: item.NodeID, FullName: item.FullName, Private: item.Private, Visibility: item.Visibility, Archived: item.Archived})
	}
	sort.Slice(result, func(i, j int) bool { return strings.ToLower(result[i].FullName) < strings.ToLower(result[j].FullName) })
	return result, nil
}

func (f *Factory) Open(ctx context.Context, endpoint reposync.Endpoint) (reposync.Provider, error) {
	if endpoint.Provider != "github" {
		return nil, fmt.Errorf("GitHub provider cannot open %q endpoint", endpoint.Provider)
	}
	if f.Tokens == nil {
		return nil, fmt.Errorf("GitHub token source is required")
	}
	token, err := f.Tokens.Token(ctx, endpoint.ConnectionID)
	if err != nil {
		return nil, err
	}
	if err := validateSyncPermissions(token.Permissions); err != nil {
		return nil, fmt.Errorf("%w: %v", reposync.ErrPermission, err)
	}
	client, err := newAPIClient(f.Tokens, endpoint.ConnectionID, f.HTTPClient)
	if err != nil {
		return nil, err
	}
	provider := &Provider{
		endpoint: endpoint, client: client, maxConcurrency: f.MaxConcurrency, maxAssetBytes: f.MaxAssetBytes,
		inventoryByIdentity: make(map[string]reposync.InventoryObject), issueIdentityByNumber: make(map[int]string),
		pullIdentityByNumber: make(map[int]string), releaseIdentityByTag: make(map[string]string),
	}
	if provider.maxConcurrency <= 0 {
		provider.maxConcurrency = 8
	}
	if provider.maxAssetBytes <= 0 {
		provider.maxAssetBytes = 2 << 30
	}
	if err := provider.validateRepository(ctx); err != nil {
		return nil, err
	}
	return provider, nil
}

type Provider struct {
	endpoint                reposync.Endpoint
	client                  *apiClient
	maxConcurrency          int
	maxAssetBytes           int64
	mu                      sync.RWMutex
	inventoryByIdentity     map[string]reposync.InventoryObject
	issueIdentityByNumber   map[int]string
	pullIdentityByNumber    map[int]string
	releaseIdentityByTag    map[string]string
	managedLinkExpectations map[uint]managedLinkExpectation
	bootstrapRefTypes       map[string]bool
	inventoryAsSource       bool
	visibility              string
}

type managedLinkExpectation struct {
	objectType string
	identity   string
	metadata   json.RawMessage
}

func (p *Provider) Endpoint() reposync.Endpoint { return p.endpoint }
func (p *Provider) Close() error                { return nil }
func (p *Provider) Visibility() string          { return p.visibility }

func (p *Provider) SetObjectLinks(links []reposync.LinkView, sourceSide bool) {
	p.mu.Lock()
	defer p.mu.Unlock()
	p.inventoryAsSource = sourceSide
	p.managedLinkExpectations = make(map[uint]managedLinkExpectation, len(links))
	for _, link := range links {
		identity := link.TargetIdentity
		if sourceSide {
			identity = link.SourceIdentity
		}
		p.managedLinkExpectations[link.ID] = managedLinkExpectation{
			objectType: link.ObjectType, identity: identity,
			metadata: append(json.RawMessage(nil), link.Metadata...),
		}
	}
}

func (p *Provider) trustBatchObjectLinks(items []reposync.PlannedItem) {
	p.mu.Lock()
	defer p.mu.Unlock()
	if p.managedLinkExpectations == nil {
		p.managedLinkExpectations = make(map[uint]managedLinkExpectation)
	}
	for _, item := range items {
		if item.ObjectLinkID != nil {
			p.managedLinkExpectations[*item.ObjectLinkID] = managedLinkExpectation{
				objectType: item.ObjectType, identity: item.TargetIdentity,
				metadata: append(json.RawMessage(nil), item.Payload...),
			}
		}
	}
}

func (p *Provider) managedExpectation(raw, identity string) (canonical.ManagedBlock, managedLinkExpectation, bool) {
	parsed := canonical.StripManagedBody(raw)
	if parsed.ObjectLinkID == 0 {
		return parsed, managedLinkExpectation{}, false
	}
	p.mu.RLock()
	expected, trusted := p.managedLinkExpectations[parsed.ObjectLinkID]
	p.mu.RUnlock()
	if !trusted || (expected.identity != "" && expected.identity != identity) {
		return canonical.ManagedBlock{Body: raw}, managedLinkExpectation{}, false
	}
	return parsed, expected, true
}

func (p *Provider) trustedManagedBody(raw, objectType, identity string) canonical.ManagedBlock {
	parsed, expected, trusted := p.managedExpectation(raw, identity)
	if !trusted || expected.objectType != objectType {
		return canonical.ManagedBlock{Body: raw}
	}
	return parsed
}

func (p *Provider) GitRemote(ctx context.Context, wiki bool) (reposync.GitRemote, error) {
	token, err := p.client.tokens.Token(ctx, p.endpoint.ConnectionID)
	if err != nil {
		return reposync.GitRemote{}, err
	}
	base, err := url.Parse(p.client.baseURL)
	if err != nil {
		return reposync.GitRemote{}, err
	}
	base.Path, base.RawQuery, base.Fragment = "", "", ""
	if strings.EqualFold(base.Hostname(), "api.github.com") {
		base.Host = "github.com"
	}
	suffix := ".git"
	if wiki {
		suffix = ".wiki.git"
	}
	return reposync.GitRemote{URL: strings.TrimRight(base.String(), "/") + "/" + strings.Trim(p.endpoint.RepositoryFullName, "/") + suffix, Token: token.Value}, nil
}

func (p *Provider) validateRepository(ctx context.Context) error {
	var repository map[string]any
	if err := p.client.doJSON(ctx, http.MethodGet, p.repoPath(), nil, nil, &repository); err != nil {
		return err
	}
	actualID := idString(repository)
	if p.endpoint.RepositoryID != "" && actualID != p.endpoint.RepositoryID {
		return fmt.Errorf("GitHub repository immutable ID changed: configured=%s actual=%s", p.endpoint.RepositoryID, actualID)
	}
	if fullName := stringValue(repository, "full_name"); fullName != "" {
		p.endpoint.RepositoryFullName = fullName
	}
	p.visibility = strings.ToLower(strings.TrimSpace(stringValue(repository, "visibility")))
	if p.visibility == "" {
		if boolValue(repository, "private") {
			p.visibility = "private"
		} else {
			p.visibility = "public"
		}
	}
	return nil
}

func (p *Provider) Inventory(ctx context.Context) ([]reposync.InventoryObject, string, error) {
	p.issueIdentityByNumber = make(map[int]string)
	p.pullIdentityByNumber = make(map[int]string)
	p.releaseIdentityByTag = make(map[string]string)
	collectors := []func(context.Context) ([]reposync.InventoryObject, error){
		p.inventoryGitRefs, p.inventoryLFSObjects, p.inventoryWikiRefs, p.inventoryLabels, p.inventoryMilestones, p.inventoryIssues,
		p.inventoryPullRequests, p.inventoryIssueComments, p.inventoryPullRequestReviews,
		p.inventoryReviewRequests, p.inventoryPullRequestReviewComments, p.inventoryReleases,
	}
	all := make([]reposync.InventoryObject, 0)
	for _, collect := range collectors {
		objects, err := collect(ctx)
		if err != nil {
			return nil, "", err
		}
		all = append(all, objects...)
	}
	sort.Slice(all, func(i, j int) bool {
		if all[i].ObjectType != all[j].ObjectType {
			return all[i].ObjectType < all[j].ObjectType
		}
		return all[i].Identity < all[j].Identity
	})
	hash := sha256.New()
	p.mu.Lock()
	p.inventoryByIdentity = make(map[string]reposync.InventoryObject, len(all))
	for _, object := range all {
		_, _ = fmt.Fprintf(hash, "%s\x00%s\x00%s\x00", object.ObjectType, object.Identity, object.ContentHash)
		p.inventoryByIdentity[identityKey(object.ObjectType, object.Identity)] = object
	}
	p.mu.Unlock()
	return all, hex.EncodeToString(hash.Sum(nil)), nil
}

func (p *Provider) InventoryIndex(ctx context.Context) ([]reposync.InventoryObject, string, error) {
	objects, digest, err := p.Inventory(ctx)
	if err != nil {
		return nil, "", err
	}
	for index := range objects {
		objects[index].Payload = nil
		objects[index].PayloadBytes = 0
	}
	return objects, digest, nil
}

func (p *Provider) IsEmpty(ctx context.Context) (bool, error) {
	for _, inventory := range []func(context.Context) ([]reposync.InventoryObject, error){p.inventoryGitRefs, p.inventoryWikiRefs} {
		objects, err := inventory(ctx)
		if err != nil {
			return false, err
		}
		if len(objects) != 0 {
			return false, nil
		}
	}
	checks := []struct {
		path  string
		query url.Values
	}{
		// GitHub creates a standard label set for new repositories. Those
		// provider-owned defaults are not AGS business data and must not disable
		// the empty-target bulk bootstrap path.
		{p.repoPath() + "/milestones", url.Values{"state": {"all"}}},
		{p.repoPath() + "/issues", url.Values{"state": {"all"}}},
		{p.repoPath() + "/issues/comments", nil},
		{p.repoPath() + "/pulls/comments", nil},
		{p.repoPath() + "/releases", nil},
	}
	for _, check := range checks {
		exists, err := p.client.hasAny(ctx, check.path, check.query)
		if err != nil {
			return false, err
		}
		if exists {
			return false, nil
		}
	}
	return true, nil
}

func (p *Provider) Read(ctx context.Context, objectType, identity string) (reposync.InventoryObject, error) {
	// A complete refresh keeps GitHub's no-CAS metadata writes honest. The
	// worker uses this immediately before apply and after uncertain results.
	objects, _, err := p.Inventory(ctx)
	if err != nil {
		return reposync.InventoryObject{}, err
	}
	for _, object := range objects {
		if object.ObjectType == objectType && object.Identity == identity {
			return object, nil
		}
	}
	return reposync.InventoryObject{}, reposync.ErrObjectNotFound
}

func (p *Provider) ReadBatch(ctx context.Context, objectType string, identities []string) (map[string]reposync.InventoryObject, error) {
	if err := p.refreshType(ctx, objectType); err != nil {
		return nil, err
	}
	result := make(map[string]reposync.InventoryObject, len(identities))
	for _, identity := range identities {
		if object, ok := p.cached(objectType, identity); ok {
			result[identity] = object
		}
	}
	return result, nil
}

func (p *Provider) Recover(ctx context.Context, item reposync.PlannedItem) (reposync.TargetState, error) {
	if item.ObjectType == canonical.TypeLFSObject && item.Action != reposync.ActionDelete {
		states, err := p.recoverLFSBatch(ctx, []reposync.PlannedItem{item})
		return states[reposync.ExecutionKey(item)], err
	}
	for attempt := 0; attempt < 4; attempt++ {
		if err := p.refreshType(ctx, item.ObjectType); err != nil {
			return reposync.TargetState{}, err
		}
		state, err := p.recoverCached(item)
		if err != nil {
			return reposync.TargetState{}, err
		}
		// GitHub list endpoints can briefly lag a successful create response.
		// Recovery is the safety boundary that prevents a blind duplicate POST,
		// so give ownership-marker/natural-key read-back a bounded visibility
		// window before declaring the result unknown.
		if item.Action != reposync.ActionCreate || (state.Exists && (item.DesiredHash == "" || state.Hash == item.DesiredHash)) || attempt == 3 {
			return state, nil
		}
		if !waitRetry(ctx, time.Duration(attempt+1)*250*time.Millisecond) {
			return reposync.TargetState{}, ctx.Err()
		}
	}
	return reposync.TargetState{Exists: false}, nil
}

func (p *Provider) RecoverBatch(ctx context.Context, items []reposync.PlannedItem) (map[string]reposync.TargetState, error) {
	result := make(map[string]reposync.TargetState, len(items))
	if len(items) == 0 {
		return result, nil
	}
	if items[0].ObjectType == canonical.TypeLFSObject {
		return p.recoverLFSBatch(ctx, items)
	}
	if err := p.refreshType(ctx, items[0].ObjectType); err != nil {
		return nil, err
	}
	for _, item := range items {
		state, err := p.recoverCached(item)
		if err != nil {
			return nil, err
		}
		result[reposync.ExecutionKey(item)] = state
	}
	return result, nil
}

func (p *Provider) recoverLFSBatch(ctx context.Context, items []reposync.PlannedItem) (map[string]reposync.TargetState, error) {
	result := make(map[string]reposync.TargetState, len(items))
	objects := make(map[string]int64)
	type lfsItem struct {
		oid  string
		size int64
	}
	decoded := make(map[string]lfsItem, len(items))
	for _, item := range items {
		if item.Action == reposync.ActionDelete {
			// LFS media is content-addressed and deliberately retained. A logical
			// delete completes after the refs that referenced it were removed.
			result[reposync.ExecutionKey(item)] = reposync.TargetState{Exists: false}
			continue
		}
		record, err := canonical.Decode(item.Payload)
		if err != nil {
			return nil, err
		}
		oid := canonical.StringField(record.Fields, "oid")
		size := int64(canonical.IntField(record.Fields, "size"))
		objects[oid] = size
		decoded[reposync.ExecutionKey(item)] = lfsItem{oid: oid, size: size}
	}
	if len(objects) == 0 {
		return result, nil
	}
	remote, err := p.GitRemote(ctx, false)
	if err != nil {
		return nil, err
	}
	verified, err := gittransport.VerifyLFSBatch(ctx, remote, objects)
	if err != nil {
		return nil, err
	}
	for _, item := range items {
		value, ok := decoded[reposync.ExecutionKey(item)]
		if !ok || !verified[value.oid] {
			if ok {
				result[reposync.ExecutionKey(item)] = reposync.TargetState{Exists: false}
			}
			continue
		}
		result[reposync.ExecutionKey(item)] = reposync.TargetState{Exists: true, Managed: true, Identity: value.oid, Revision: value.oid + ":" + strconv.FormatInt(value.size, 10), Hash: item.DesiredHash}
	}
	return result, nil
}

func (p *Provider) recoverCached(item reposync.PlannedItem) (reposync.TargetState, error) {
	if item.TargetIdentity != "" {
		if object, ok := p.cached(item.ObjectType, item.TargetIdentity); ok {
			return canonical.TargetStateFromInventory(object, true), nil
		}
	}
	source, err := canonical.Decode(item.Payload)
	if err != nil {
		return reposync.TargetState{}, err
	}
	p.mu.RLock()
	defer p.mu.RUnlock()
	var candidate *reposync.InventoryObject
	candidateManaged := false
	for _, object := range p.inventoryByIdentity {
		if object.ObjectType != item.ObjectType {
			continue
		}
		record, decodeErr := canonical.Decode(object.Payload)
		if decodeErr != nil {
			continue
		}
		matchedMarker := item.ObjectLinkID != nil && record.ManagedLinkID == *item.ObjectLinkID
		matched := matchedMarker
		if !matched && source.UniqueKey != "" {
			matched = record.UniqueKey == source.UniqueKey
		}
		if !matched && item.ObjectType == canonical.TypeReviewRequest && source.ParentIdentity != "" {
			matched = record.ParentIdentity == source.ParentIdentity
		}
		if !matched {
			continue
		}
		if candidate != nil {
			return reposync.TargetState{}, fmt.Errorf("multiple GitHub recovery candidates for %s", item.ObjectKey)
		}
		copy := object
		candidate = &copy
		candidateManaged = matchedMarker
	}
	if candidate == nil {
		return reposync.TargetState{Exists: false}, nil
	}
	return canonical.TargetStateFromInventory(*candidate, candidateManaged), nil
}

func (p *Provider) repoPath() string {
	return "/repos/" + strings.Trim(p.endpoint.RepositoryFullName, "/")
}

func (p *Provider) inventoryGitRefs(ctx context.Context) ([]reposync.InventoryObject, error) {
	remote, err := p.GitRemote(ctx, false)
	if err != nil {
		return nil, err
	}
	refs, err := gittransport.InventoryRefs(ctx, remote, canonical.TypeGitRef, false)
	if err != nil {
		return nil, err
	}
	p.mu.RLock()
	asSource := p.inventoryAsSource
	p.mu.RUnlock()
	if !asSource {
		return refs, nil
	}
	existing := make(map[string]bool, len(refs))
	for _, ref := range refs {
		existing[ref.Identity] = true
	}
	pullHeads, err := gittransport.InventoryPullHeadRefs(ctx, remote, existing)
	if err != nil {
		return nil, err
	}
	return append(refs, pullHeads...), nil
}

func (p *Provider) inventoryLFSObjects(ctx context.Context) ([]reposync.InventoryObject, error) {
	remote, err := p.GitRemote(ctx, false)
	if err != nil {
		return nil, err
	}
	return gittransport.InventoryLFS(ctx, remote)
}

func (p *Provider) inventoryWikiRefs(ctx context.Context) ([]reposync.InventoryObject, error) {
	remote, err := p.GitRemote(ctx, true)
	if err != nil {
		return nil, err
	}
	return gittransport.InventoryRefs(ctx, remote, canonical.TypeWikiRef, true)
}

func (p *Provider) inventoryLabels(ctx context.Context) ([]reposync.InventoryObject, error) {
	items, err := p.client.list(ctx, p.repoPath()+"/labels", nil)
	if err != nil {
		return nil, err
	}
	return mapRawRecords(items, func(value map[string]any) (canonical.Record, string, string, error) {
		identity := idString(value)
		name := stringValue(value, "name")
		record := canonical.NewRecord(canonical.TypeLabel, identity, "label:"+identity, map[string]any{
			"_database_id": intValue(value, "id"), "name": name, "color": stringValue(value, "color"), "description": nullableString(value["description"]),
		})
		record.UniqueKey = strings.ToLower(name)
		return record, "", "native", nil
	})
}

func (p *Provider) inventoryMilestones(ctx context.Context) ([]reposync.InventoryObject, error) {
	items, err := p.client.list(ctx, p.repoPath()+"/milestones", url.Values{"state": {"all"}})
	if err != nil {
		return nil, err
	}
	return mapRawRecords(items, func(value map[string]any) (canonical.Record, string, string, error) {
		identity := idString(value)
		title := stringValue(value, "title")
		record := canonical.NewRecord(canonical.TypeMilestone, identity, "milestone:"+identity, map[string]any{
			"_database_id": intValue(value, "id"), "_number": intValue(value, "number"), "title": title, "description": nullableString(value["description"]),
			"state": stringValue(value, "state"), "due_on": nullableString(value["due_on"]),
		})
		record.UniqueKey = strings.ToLower(title)
		setProvenance(&record, value, "creator")
		return record, stringValue(value, "updated_at"), "native", nil
	})
}

func (p *Provider) inventoryIssues(ctx context.Context) ([]reposync.InventoryObject, error) {
	items, err := p.client.list(ctx, p.repoPath()+"/issues", url.Values{"state": {"all"}, "sort": {"created"}, "direction": {"asc"}})
	if err != nil {
		return nil, err
	}
	filtered := make([]json.RawMessage, 0, len(items))
	for _, raw := range items {
		var value map[string]any
		if err := json.Unmarshal(raw, &value); err != nil {
			return nil, err
		}
		if value["pull_request"] == nil {
			filtered = append(filtered, raw)
		}
	}
	return mapRawRecords(filtered, func(value map[string]any) (canonical.Record, string, string, error) {
		identity := idString(value)
		number := intValue(value, "number")
		if managed, expected, trusted := p.managedExpectation(nullableString(value["body"]), identity); trusted && expected.objectType == canonical.TypePullRequest {
			record, err := projectedPullRequestCarrier(expected.metadata, value, managed)
			if err != nil {
				return canonical.Record{}, "", "", err
			}
			p.pullIdentityByNumber[number] = identity
			return record, stringValue(value, "updated_at"), "projected", nil
		}
		managed := p.trustedManagedBody(nullableString(value["body"]), canonical.TypeIssue, identity)
		body := managed.Body
		record := canonical.NewRecord(canonical.TypeIssue, identity, "issue:"+identity, map[string]any{
			"_database_id": intValue(value, "id"), "_number": number, "title": stringValue(value, "title"), "body": body,
			"state": stringValue(value, "state"), "state_reason": nullableString(value["state_reason"]),
			"locked": boolValue(value, "locked"), "labels": labelNames(value["labels"]),
			"assignees": userLogins(value["assignees"]), "milestone": nestedString(value, "milestone", "title"),
		})
		record.ManagedLinkID = managed.ObjectLinkID
		p.issueIdentityByNumber[number] = identity
		setProvenance(&record, value, "user")
		return record, stringValue(value, "updated_at"), "native", nil
	})
}

func projectedPullRequestCarrier(metadata json.RawMessage, value map[string]any, managed canonical.ManagedBlock) (canonical.Record, error) {
	record, err := canonical.Decode(metadata)
	if err != nil {
		return canonical.Record{}, fmt.Errorf("decode projected pull request ObjectLink metadata: %w", err)
	}
	if record.Type != canonical.TypePullRequest {
		return canonical.Record{}, fmt.Errorf("projected pull request marker points to %q metadata", record.Type)
	}
	identity := idString(value)
	record.Identity = identity
	record.Key = "pull-request:" + identity
	record.ManagedLinkID = managed.ObjectLinkID
	record.Fields["_database_id"] = intValue(value, "id")
	record.Fields["_number"] = intValue(value, "number")
	record.Fields["_carrier"] = "issue"
	record.Fields["title"] = stringValue(value, "title")
	record.Fields["body"] = managed.Body
	record.Fields["state"] = stringValue(value, "state")
	record.Fields["labels"] = labelNames(value["labels"])
	record.Fields["assignees"] = userLogins(value["assignees"])
	record.Fields["milestone"] = nestedString(value, "milestone", "title")
	record.SourceURL = stringValue(value, "html_url")
	return record, nil
}

func (p *Provider) inventoryIssueComments(ctx context.Context) ([]reposync.InventoryObject, error) {
	items, err := p.client.list(ctx, p.repoPath()+"/issues/comments", url.Values{"sort": {"created"}, "direction": {"asc"}})
	if err != nil {
		return nil, err
	}
	return mapRawRecords(items, func(value map[string]any) (canonical.Record, string, string, error) {
		identity := idString(value)
		issueNumber := numberFromURL(stringValue(value, "issue_url"))
		if managed, expected, trusted := p.managedExpectation(nullableString(value["body"]), identity); trusted && isProjectedConversationType(expected.objectType) {
			record, err := projectedConversationCarrier(expected, value, managed, issueNumber, p.pullIdentityByNumber[issueNumber])
			if err != nil {
				return canonical.Record{}, "", "", err
			}
			return record, stringValue(value, "updated_at"), "projected", nil
		}
		managed := p.trustedManagedBody(nullableString(value["body"]), canonical.TypeIssueComment, identity)
		body := managed.Body
		fields := map[string]any{"_database_id": intValue(value, "id"), "_issue_number": issueNumber, "body": body}
		record := canonical.NewRecord(canonical.TypeIssueComment, identity, "issue-comment:"+identity, fields)
		record.ParentKey = "issue-number:" + strconv.Itoa(issueNumber)
		if pullIdentity := p.pullIdentityByNumber[issueNumber]; pullIdentity != "" {
			record.ParentType, record.ParentIdentity = canonical.TypePullRequest, pullIdentity
			record.Fields["_pull_number"] = issueNumber
		} else {
			record.ParentType, record.ParentIdentity = canonical.TypeIssue, p.issueIdentityByNumber[issueNumber]
		}
		record.ManagedLinkID = managed.ObjectLinkID
		setProvenance(&record, value, "user")
		return record, stringValue(value, "updated_at"), "attributed", nil
	})
}

func isProjectedConversationType(objectType string) bool {
	switch objectType {
	case canonical.TypePullRequestReview, canonical.TypePullRequestReviewComment, canonical.TypeReviewRequest:
		return true
	default:
		return false
	}
}

func projectedConversationCarrier(expected managedLinkExpectation, value map[string]any, managed canonical.ManagedBlock, issueNumber int, parentIdentity string) (canonical.Record, error) {
	record, err := canonical.Decode(expected.metadata)
	if err != nil {
		return canonical.Record{}, fmt.Errorf("decode projected conversation ObjectLink metadata: %w", err)
	}
	if record.Type != expected.objectType || !isProjectedConversationType(record.Type) {
		return canonical.Record{}, fmt.Errorf("projected conversation marker points to %q metadata", record.Type)
	}
	identity := idString(value)
	record.Identity = identity
	switch record.Type {
	case canonical.TypePullRequestReview:
		record.Key = "pull-review:" + identity
	case canonical.TypePullRequestReviewComment:
		record.Key = "pull-review-comment:" + identity
	case canonical.TypeReviewRequest:
		record.Key = "review-request:" + identity
	}
	record.ManagedLinkID = managed.ObjectLinkID
	record.Fields["_database_id"] = intValue(value, "id")
	record.Fields["_issue_number"] = issueNumber
	record.Fields["_pull_number"] = issueNumber
	record.Fields["_carrier"] = "issue_comment"
	record.ParentType = canonical.TypePullRequest
	record.ParentIdentity = parentIdentity
	record.ParentKey = "pull-number:" + strconv.Itoa(issueNumber)
	record.SourceURL = stringValue(value, "html_url")
	record.Fields["body"] = managed.Body
	return record, nil
}

func (p *Provider) inventoryPullRequests(ctx context.Context) ([]reposync.InventoryObject, error) {
	items, err := p.client.list(ctx, p.repoPath()+"/pulls", url.Values{"state": {"all"}, "sort": {"created"}, "direction": {"asc"}})
	if err != nil {
		return nil, err
	}
	return mapRawRecords(items, func(value map[string]any) (canonical.Record, string, string, error) {
		identity := idString(value)
		number := intValue(value, "number")
		managed := p.trustedManagedBody(nullableString(value["body"]), canonical.TypePullRequest, identity)
		body := managed.Body
		record := canonical.NewRecord(canonical.TypePullRequest, identity, "pull-request:"+identity, map[string]any{
			"_database_id": intValue(value, "id"), "_number": number, "title": stringValue(value, "title"), "body": body,
			"state": stringValue(value, "state"), "draft": boolValue(value, "draft"),
			"head_ref": nestedString(value, "head", "ref"), "head_sha": nestedString(value, "head", "sha"),
			"_head_repo": nestedString(value, "head", "repo", "full_name"),
			"_base_repo": nestedString(value, "base", "repo", "full_name"),
			"base_ref":   nestedString(value, "base", "ref"), "base_sha": nestedString(value, "base", "sha"),
			"merged": nullableString(value["merged_at"]) != "", "merge_commit_sha": pullRequestMergeCommitSHA(value),
			"labels": labelNames(value["labels"]), "assignees": userLogins(value["assignees"]),
			"milestone": nestedString(value, "milestone", "title"),
		})
		record.ManagedLinkID = managed.ObjectLinkID
		p.pullIdentityByNumber[number] = identity
		setProvenance(&record, value, "user")
		return record, stringValue(value, "updated_at"), "native", nil
	})
}

func pullRequestMergeCommitSHA(value map[string]any) string {
	// For an open pull request GitHub may expose a synthetic test-merge commit
	// in merge_commit_sha. It is provider-derived and is not the merge result.
	// Only preserve the field after the pull request is actually merged.
	if nullableString(value["merged_at"]) == "" {
		return ""
	}
	return nullableString(value["merge_commit_sha"])
}

func (p *Provider) inventoryPullRequestReviews(ctx context.Context) ([]reposync.InventoryObject, error) {
	pulls, err := p.client.list(ctx, p.repoPath()+"/pulls", url.Values{"state": {"all"}})
	if err != nil {
		return nil, err
	}
	var mu sync.Mutex
	all := make([]reposync.InventoryObject, 0)
	err = bulk.ForEach(ctx, len(pulls), p.maxConcurrency, func(groupCtx context.Context, index int) error {
		raw := pulls[index]
		var pull map[string]any
		if err := json.Unmarshal(raw, &pull); err != nil {
			return err
		}
		number := intValue(pull, "number")
		items, err := p.client.list(groupCtx, fmt.Sprintf("%s/pulls/%d/reviews", p.repoPath(), number), nil)
		if err != nil {
			return err
		}
		mapped, err := mapRawRecords(items, func(value map[string]any) (canonical.Record, string, string, error) {
			identity := idString(value)
			managed := p.trustedManagedBody(nullableString(value["body"]), canonical.TypePullRequestReview, identity)
			body := managed.Body
			record := canonical.NewRecord(canonical.TypePullRequestReview, identity, "pull-review:"+identity, map[string]any{
				"_database_id": intValue(value, "id"), "_pull_number": number, "_original_state": stringValue(value, "state"), "body": body, "commit_id": stringValue(value, "commit_id"),
			})
			record.ParentKey = "pull-number:" + strconv.Itoa(number)
			record.ParentType, record.ParentIdentity = canonical.TypePullRequest, p.pullIdentityByNumber[number]
			record.ManagedLinkID = managed.ObjectLinkID
			setProvenance(&record, value, "user")
			return record, nullableString(value["submitted_at"]), "attributed", nil
		})
		if err != nil {
			return err
		}
		mu.Lock()
		all = append(all, mapped...)
		mu.Unlock()
		return nil
	})
	return all, err
}

func (p *Provider) inventoryReviewRequests(ctx context.Context) ([]reposync.InventoryObject, error) {
	pulls, err := p.client.list(ctx, p.repoPath()+"/pulls", url.Values{"state": {"open"}})
	if err != nil {
		return nil, err
	}
	var mu sync.Mutex
	all := make([]reposync.InventoryObject, 0)
	err = bulk.ForEach(ctx, len(pulls), p.maxConcurrency, func(groupCtx context.Context, index int) error {
		raw := pulls[index]
		var pull map[string]any
		if err := json.Unmarshal(raw, &pull); err != nil {
			return err
		}
		number := intValue(pull, "number")
		var response map[string]any
		if err := p.client.doJSON(groupCtx, http.MethodGet, fmt.Sprintf("%s/pulls/%d/requested_reviewers", p.repoPath(), number), nil, nil, &response); err != nil {
			return err
		}
		users := userLogins(response["users"])
		teams := teamSlugs(response["teams"])
		if len(users) == 0 && len(teams) == 0 {
			return nil
		}
		identity := "pull:" + strconv.Itoa(number)
		record := canonical.NewRecord(canonical.TypeReviewRequest, identity, "review-request:"+identity, map[string]any{"_pull_number": number, "users": users, "teams": teams})
		record.ParentKey = "pull-number:" + strconv.Itoa(number)
		record.ParentType, record.ParentIdentity = canonical.TypePullRequest, p.pullIdentityByNumber[number]
		object, err := record.Inventory("", "projected")
		if err != nil {
			return err
		}
		mu.Lock()
		all = append(all, object)
		mu.Unlock()
		return nil
	})
	if err != nil {
		return nil, err
	}
	return all, nil
}

func (p *Provider) inventoryPullRequestReviewComments(ctx context.Context) ([]reposync.InventoryObject, error) {
	items, err := p.client.list(ctx, p.repoPath()+"/pulls/comments", url.Values{"sort": {"created"}, "direction": {"asc"}})
	if err != nil {
		return nil, err
	}
	return mapRawRecords(items, func(value map[string]any) (canonical.Record, string, string, error) {
		identity := idString(value)
		pullNumber := numberFromURL(stringValue(value, "pull_request_url"))
		managed := p.trustedManagedBody(nullableString(value["body"]), canonical.TypePullRequestReviewComment, identity)
		body := managed.Body
		record := canonical.NewRecord(canonical.TypePullRequestReviewComment, identity, "pull-review-comment:"+identity, map[string]any{
			"_database_id": intValue(value, "id"), "_pull_number": pullNumber, "body": body, "commit_id": stringValue(value, "commit_id"),
			"path": stringValue(value, "path"), "line": intValue(value, "line"), "side": stringValue(value, "side"),
			"start_line": intValue(value, "start_line"), "start_side": stringValue(value, "start_side"),
			"_in_reply_to_id": intValue(value, "in_reply_to_id"),
		})
		record.ParentKey = "pull-number:" + strconv.Itoa(pullNumber)
		record.ParentType, record.ParentIdentity = canonical.TypePullRequest, p.pullIdentityByNumber[pullNumber]
		record.ManagedLinkID = managed.ObjectLinkID
		setProvenance(&record, value, "user")
		return record, stringValue(value, "updated_at"), "attributed", nil
	})
}

func (p *Provider) inventoryReleases(ctx context.Context) ([]reposync.InventoryObject, error) {
	items, err := p.client.list(ctx, p.repoPath()+"/releases", nil)
	if err != nil {
		return nil, err
	}
	all, err := mapRawRecords(items, func(value map[string]any) (canonical.Record, string, string, error) {
		identity := idString(value)
		tag := stringValue(value, "tag_name")
		managed := p.trustedManagedBody(nullableString(value["body"]), canonical.TypeRelease, identity)
		body := managed.Body
		record := canonical.NewRecord(canonical.TypeRelease, identity, "release:"+identity, map[string]any{
			"_database_id": intValue(value, "id"), "tag_name": tag, "_target_commitish": stringValue(value, "target_commitish"), "name": nullableString(value["name"]),
			"body": body, "draft": boolValue(value, "draft"), "prerelease": boolValue(value, "prerelease"),
		})
		record.UniqueKey = tag
		record.ManagedLinkID = managed.ObjectLinkID
		p.releaseIdentityByTag[tag] = identity
		setProvenance(&record, value, "author")
		return record, stringValue(value, "published_at"), "native", nil
	})
	if err != nil {
		return nil, err
	}
	for _, raw := range items {
		var release map[string]any
		if err := json.Unmarshal(raw, &release); err != nil {
			return nil, err
		}
		releaseID := idString(release)
		tag := stringValue(release, "tag_name")
		assets, _ := release["assets"].([]any)
		for _, item := range assets {
			asset, _ := item.(map[string]any)
			size := int64(intValue(asset, "size"))
			checksum := strings.TrimPrefix(stringValue(asset, "digest"), "sha256:")
			contentType := stringValue(asset, "content_type")
			if len(checksum) != 64 {
				body, detectedType, contentLength, err := p.client.openDownload(ctx, stringValue(asset, "url"), p.maxAssetBytes)
				if err != nil {
					return nil, fmt.Errorf("download release asset %s: %w", stringValue(asset, "name"), err)
				}
				hash := sha256.New()
				written, copyErr := io.Copy(hash, io.LimitReader(body, p.maxAssetBytes+1))
				closeErr := body.Close()
				if copyErr != nil || closeErr != nil || written > p.maxAssetBytes || (contentLength >= 0 && written != contentLength) || (size > 0 && written != size) {
					return nil, fmt.Errorf("release asset %s size or read failed", stringValue(asset, "name"))
				}
				size, checksum, contentType = written, hex.EncodeToString(hash.Sum(nil)), firstNonEmpty(contentType, detectedType)
			}
			identity := idString(asset)
			record := canonical.NewRecord(canonical.TypeReleaseAsset, identity, "release-asset:"+identity, map[string]any{
				"_database_id": intValue(asset, "id"), "_release_id": releaseID, "release_tag": tag, "name": stringValue(asset, "name"), "label": nullableString(asset["label"]),
				"content_type": contentType, "size": size, "sha256": checksum,
				"_download_url": stringValue(asset, "url"),
			})
			record.ParentKey = "release-tag:" + tag
			record.ParentType, record.ParentIdentity = canonical.TypeRelease, releaseID
			record.UniqueKey = tag + "\x00" + stringValue(asset, "name")
			setProvenance(&record, asset, "uploader")
			object, err := record.Inventory(stringValue(asset, "updated_at"), "native")
			if err != nil {
				return nil, err
			}
			all = append(all, object)
		}
	}
	return all, nil
}

func (p *Provider) inventoryReactions(ctx context.Context) ([]reposync.InventoryObject, error) {
	type parent struct {
		path, objectType, identity, key string
	}
	parents := make([]parent, 0)
	issues, err := p.client.list(ctx, p.repoPath()+"/issues", url.Values{"state": {"all"}})
	if err != nil {
		return nil, err
	}
	for _, raw := range issues {
		var value map[string]any
		if err := json.Unmarshal(raw, &value); err != nil {
			return nil, err
		}
		number := intValue(value, "number")
		objectType, identity := canonical.TypeIssue, p.issueIdentityByNumber[number]
		if value["pull_request"] != nil {
			objectType, identity = canonical.TypePullRequest, p.pullIdentityByNumber[number]
		}
		parents = append(parents, parent{path: fmt.Sprintf("%s/issues/%d/reactions", p.repoPath(), number), objectType: objectType, identity: identity, key: "number:" + strconv.Itoa(number)})
	}
	comments, err := p.client.list(ctx, p.repoPath()+"/issues/comments", nil)
	if err != nil {
		return nil, err
	}
	for _, raw := range comments {
		var value map[string]any
		if err := json.Unmarshal(raw, &value); err != nil {
			return nil, err
		}
		id := intValue(value, "id")
		parents = append(parents, parent{path: fmt.Sprintf("%s/issues/comments/%d/reactions", p.repoPath(), id), objectType: canonical.TypeIssueComment, identity: idString(value), key: "database-id:" + strconv.Itoa(id)})
	}
	reviewComments, err := p.client.list(ctx, p.repoPath()+"/pulls/comments", nil)
	if err != nil {
		return nil, err
	}
	for _, raw := range reviewComments {
		var value map[string]any
		if err := json.Unmarshal(raw, &value); err != nil {
			return nil, err
		}
		id := intValue(value, "id")
		parents = append(parents, parent{path: fmt.Sprintf("%s/pulls/comments/%d/reactions", p.repoPath(), id), objectType: canonical.TypePullRequestReviewComment, identity: idString(value), key: "database-id:" + strconv.Itoa(id)})
	}

	var mu sync.Mutex
	all := make([]reposync.InventoryObject, 0)
	err = bulk.ForEach(ctx, len(parents), p.maxConcurrency, func(groupCtx context.Context, index int) error {
		value := parents[index]
		items, err := p.client.list(groupCtx, value.path, nil)
		if err != nil {
			return err
		}
		mapped, err := mapRawRecords(items, func(reaction map[string]any) (canonical.Record, string, string, error) {
			identity := idString(reaction)
			record := canonical.NewRecord(canonical.TypeReaction, identity, "reaction:"+identity, map[string]any{"content": stringValue(reaction, "content")})
			record.ParentType, record.ParentIdentity, record.ParentKey = value.objectType, value.identity, value.key
			setProvenance(&record, reaction, "user")
			return record, stringValue(reaction, "created_at"), "archive_only", nil
		})
		if err != nil {
			return err
		}
		mu.Lock()
		all = append(all, mapped...)
		mu.Unlock()
		return nil
	})
	if err != nil {
		return nil, err
	}
	return all, nil
}

func mapRawRecords(items []json.RawMessage, mapper func(map[string]any) (canonical.Record, string, string, error)) ([]reposync.InventoryObject, error) {
	objects := make([]reposync.InventoryObject, 0, len(items))
	for _, raw := range items {
		var value map[string]any
		if err := json.Unmarshal(raw, &value); err != nil {
			return nil, err
		}
		record, revision, representation, err := mapper(value)
		if err != nil {
			return nil, err
		}
		object, err := record.Inventory(revision, representation)
		if err != nil {
			return nil, err
		}
		objects = append(objects, object)
	}
	return objects, nil
}

func setProvenance(record *canonical.Record, value map[string]any, actorKey string) {
	record.OriginalActor = nestedString(value, actorKey, "login")
	record.SourceURL = stringValue(value, "html_url")
	record.SourceProvider = "github"
	if raw := stringValue(value, "created_at"); raw != "" {
		if parsed, err := time.Parse(time.RFC3339, raw); err == nil {
			record.OriginalTime = &parsed
		}
	}
}

func identityKey(objectType, identity string) string { return objectType + "\x00" + identity }

func idString(value map[string]any) string {
	if node := stringValue(value, "node_id"); node != "" {
		return node
	}
	switch id := value["id"].(type) {
	case float64:
		return strconv.FormatInt(int64(id), 10)
	case string:
		return id
	default:
		return ""
	}
}

func stringValue(value map[string]any, key string) string {
	result, _ := value[key].(string)
	return result
}

func nullableString(value any) string {
	result, _ := value.(string)
	return result
}

func boolValue(value map[string]any, key string) bool {
	result, _ := value[key].(bool)
	return result
}

func intValue(value map[string]any, key string) int {
	result, _ := value[key].(float64)
	return int(result)
}

func nestedString(value map[string]any, keys ...string) string {
	var current any = value
	for _, key := range keys {
		object, ok := current.(map[string]any)
		if !ok {
			return ""
		}
		current = object[key]
	}
	result, _ := current.(string)
	return result
}

func nestedInt(value map[string]any, keys ...string) int {
	var current any = value
	for _, key := range keys {
		object, ok := current.(map[string]any)
		if !ok {
			return 0
		}
		current = object[key]
	}
	result, _ := current.(float64)
	return int(result)
}

func labelNames(value any) []string {
	items, _ := value.([]any)
	result := make([]string, 0, len(items))
	for _, item := range items {
		object, _ := item.(map[string]any)
		if name := stringValue(object, "name"); name != "" {
			result = append(result, name)
		}
	}
	sort.Strings(result)
	return result
}

func userLogins(value any) []string {
	items, _ := value.([]any)
	result := make([]string, 0, len(items))
	for _, item := range items {
		object, _ := item.(map[string]any)
		if login := stringValue(object, "login"); login != "" {
			result = append(result, login)
		}
	}
	sort.Strings(result)
	return result
}

func teamSlugs(value any) []string {
	items, _ := value.([]any)
	result := make([]string, 0, len(items))
	for _, item := range items {
		object, _ := item.(map[string]any)
		if slug := stringValue(object, "slug"); slug != "" {
			result = append(result, slug)
		}
	}
	sort.Strings(result)
	return result
}

func numberFromURL(raw string) int {
	parts := strings.Split(strings.TrimRight(raw, "/"), "/")
	if len(parts) == 0 {
		return 0
	}
	value, _ := strconv.Atoi(parts[len(parts)-1])
	return value
}

func sha256Bytes(value []byte) string {
	sum := sha256.Sum256(value)
	return hex.EncodeToString(sum[:])
}
