package d2fonts

import (
	
	
	
	

	
	
)

type sceneCache = fontfallback.SceneCache

// BundledFallbackLimits bound all requests and returned bytes over the
// lifetime of one composite resolver. MaxBundledBytes reserves an explicit
// sub-budget for owned copies of D2's trusted font; MaxResolvedBytes bounds
// those copies plus downstream results. The downstream resolver remains
// responsible for its own discovery and coverage-work limits.
type BundledFallbackLimits struct {
	MaxRequestedRunes int
	// MaxBundledBytes defaults to MaxResolvedBytes when zero.
	MaxBundledBytes  int64
	MaxResolvedBytes int64
}

func ( BundledFallbackLimits) () (BundledFallbackLimits, error) {
	if .MaxBundledBytes == 0 {
		.MaxBundledBytes = .MaxResolvedBytes
	}
	if .MaxRequestedRunes <= 0 || .MaxBundledBytes <= 0 || .MaxResolvedBytes <= 0 {
		return BundledFallbackLimits{}, fmt.Errorf("d2fonts: every bundled fallback limit must be positive")
	}
	if .MaxBundledBytes > .MaxResolvedBytes {
		return BundledFallbackLimits{}, fmt.Errorf("d2fonts: bundled fallback byte limit %d exceeds aggregate resolved byte limit %d", .MaxBundledBytes, .MaxResolvedBytes)
	}
	return , nil
}

// BundledFallbackResolver resolves supported symbols and emoji from D2's
// pinned Noto Color Emoji face before consulting the host. This makes those
// glyphs deterministic across machines while leaving other scripts to a
// bounded downstream resolver. On js/wasm the bundled face is absent and all
// requests pass through to downstream.
type BundledFallbackResolver struct {
	*sceneCache

	next   FallbackResolver
	limits BundledFallbackLimits

	resolveOnce   sync.Once
	resolveGate   chan struct{}
	work          bundledFallbackWork
	bundledSource *fontface.BundledNotoColorEmojiSource
	loadBundled   func() ([]byte, error)
}

type bundledFallbackWork struct {
	requestedRunes int64
	bundledBytes   int64
	resolvedBytes  int64
}

func ( FallbackResolver,  BundledFallbackLimits) (*BundledFallbackResolver, error) {
	,  := .normalized()
	if  != nil {
		return nil, 
	}
	 := &BundledFallbackResolver{
		next: , limits: , loadBundled: bundledNotoColorEmoji,
	}
	.sceneCache = fontfallback.NewSceneCache(.resolveForScene)
	return , nil
}

func ( *BundledFallbackResolver) ( context.Context,  FallbackRequest) ([]FallbackFont, error) {
	,  := .resolve(, , false)
	if  != nil {
		return nil, 
	}
	 := make([]FallbackFont, len())
	for  := range  {
		[] = fallbackFont([].Font)
	}
	return , nil
}

func ( *BundledFallbackResolver) ( context.Context,  fontfallback.Request) ([]fontfallback.SceneFont, error) {
	return .resolve(, FallbackRequest{
		Runes: .Runes, Family: .Family, Style: .Style, Weight: .Weight,
	}, true)
}

func ( *BundledFallbackResolver) ( context.Context,  FallbackRequest,  bool) ([]fontfallback.SceneFont, error) {
	if  == nil {
		return nil, fmt.Errorf("d2fonts: nil bundled fallback resolver")
	}
	if  == nil {
		 = context.Background()
	}
	if  := .Err();  != nil {
		return nil, 
	}
	,  := .acquireResolve()
	if  != nil {
		return nil, 
	}
	defer ()
	if  := .Err();  != nil {
		return nil, 
	}
	if len(.Family)+len(.Style) > 1_024 {
		return nil, fmt.Errorf("d2fonts: fallback request font metadata exceeds 1024 bytes")
	}
	if len(.Runes) > .limits.MaxRequestedRunes {
		return nil, fmt.Errorf("d2fonts: requested fallback rune count %d exceeds limit %d", len(.Runes), .limits.MaxRequestedRunes)
	}
	if int64(len(.Runes)) > int64(.limits.MaxRequestedRunes)-.work.requestedRunes {
		return nil, fmt.Errorf("d2fonts: requested fallback rune count exceeds cumulative resolver limit %d", .limits.MaxRequestedRunes)
	}
	.work.requestedRunes += int64(len(.Runes))
	,  := uniqueFallbackRunes(, .Runes)
	if  != nil {
		return nil, 
	}
	if len() == 0 {
		return nil, nil
	}

	var  []fontfallback.SceneFont
	if shouldTryBundledEmoji() {
		 := .loadBundled
		if  == nil {
			 = bundledNotoColorEmoji
		}
		,  := ()
		if  != nil {
			return nil, 
		}
		if len() != 0 && .bundledSource == nil {
			, ,  := fontface.RegisteredBundledNotoColorEmoji(, 0)
			if  != nil {
				return nil, fmt.Errorf("d2fonts: parse bundled Noto Color Emoji face: %w", )
			}
			if ! {
				return nil, fmt.Errorf("d2fonts: bundled Noto Color Emoji face is not authenticated")
			}
			.bundledSource = 
		}
		var  []rune
		if .bundledSource != nil {
			,  = coveredBundledRunes(, .bundledSource, )
			if  != nil {
				return nil, 
			}
		}
		if len() != 0 && len() != 0 {
			 := fontfallback.Font{Name: "NotoColorEmoji-COLRv1-v2.051.ttf", MIMEType: "font/ttf", Data: }
			if  {
				if .sceneCache == nil {
					return nil, fmt.Errorf("d2fonts: bundled fallback scene cache is unavailable")
				}
				,  := .bundledSource.CloneReadOnly()
				if  != nil {
					return nil, fmt.Errorf("d2fonts: clone bundled Noto Color Emoji face: %w", )
				}
				if !.sceneCache.HasTrusted() {
					if  := .admitBundledBytes(int64(len()));  != nil {
						return nil, 
					}
				}
				, ,  := .sceneCache.PrepareTrusted(, )
				if  != nil {
					return nil, 
				}
				if  {
					.work.bundledBytes += int64(len(.Data))
					.work.resolvedBytes += int64(len(.Data))
				}
				.Face = 
				 = append(, )
			} else {
				if  := .admitBundledBytes(int64(len()));  != nil {
					return nil, 
				}
				,  := cloneFallbackBytes(, )
				if  != nil {
					return nil, 
				}
				.Data = 
				 = append(, fontfallback.SceneFont{Font: })
				.work.bundledBytes += int64(len())
				.work.resolvedBytes += int64(len())
			}
			for ,  := range  {
				delete(, )
			}
		}
	}
	if len() == 0 {
		return , nil
	}
	if .next == nil {
		return , nil
	}

	 := 
	.Runes = sortedFallbackRunes()
	,  := .next.ResolveFallbacks(, )
	if  != nil {
		return nil, 
	}
	for ,  := range  {
		if int64(len(.Data)) > .limits.MaxResolvedBytes-.work.resolvedBytes {
			return nil, fmt.Errorf("d2fonts: resolved fallback font bytes exceed limit %d at downstream resource %d", .limits.MaxResolvedBytes, )
		}
		.Data,  = cloneFallbackBytes(, .Data)
		if  != nil {
			return nil, 
		}
		.work.resolvedBytes += int64(len(.Data))
		 = append(, fontfallback.SceneFont{Font: sceneFont()})
	}
	return , nil
}

func sceneFont( FallbackFont) fontfallback.Font {
	return fontfallback.Font{
		Name: .Name, MIMEType: .MIMEType, Data: .Data, FaceIndex: .FaceIndex,
	}
}

func fallbackFont( fontfallback.Font) FallbackFont {
	return FallbackFont{
		Name: .Name, MIMEType: .MIMEType, Data: .Data, FaceIndex: .FaceIndex,
	}
}

func ( *BundledFallbackResolver) ( int64) error {
	if  > .limits.MaxBundledBytes-.work.bundledBytes {
		return fmt.Errorf("d2fonts: bundled fallback font bytes exceed limit %d while retaining Noto Color Emoji", .limits.MaxBundledBytes)
	}
	if  > .limits.MaxResolvedBytes-.work.resolvedBytes {
		return fmt.Errorf("d2fonts: resolved fallback font bytes exceed limit %d while retaining bundled Noto Color Emoji", .limits.MaxResolvedBytes)
	}
	return nil
}

func shouldTryBundledEmoji( map[rune]struct{}) bool {
	for  := range  {
		if fontface.BundledNotoColorEmojiCoversRune() {
			return true
		}
	}
	return false
}

func coveredBundledRunes( context.Context,  *fontface.BundledNotoColorEmojiSource,  map[rune]struct{}) ([]rune, error) {
	 := make([]rune, 0)
	 := 0
	for  := range  {
		if &255 == 0 {
			if  := .Err();  != nil {
				return nil, 
			}
		}
		,  := .SupportsRenderableRune()
		if  != nil {
			return nil, 
		}
		if  {
			 = append(, )
		}
		++
	}
	sort.Slice(, func(,  int) bool { return [] < [] })
	if  := .Err();  != nil {
		return nil, 
	}
	return , nil
}

// acquireResolve serializes the resolver-local shaping face and cumulative
// budget accounting while allowing a waiting caller to cancel promptly.
func ( *BundledFallbackResolver) ( context.Context) (func(), error) {
	.resolveOnce.Do(func() { .resolveGate = make(chan struct{}, 1) })
	select {
	case .resolveGate <- struct{}{}:
		return func() { <-.resolveGate }, nil
	case <-.Done():
		return nil, .Err()
	}
}

func sortedFallbackRunes( map[rune]struct{}) []rune {
	 := make([]rune, 0, len())
	for  := range  {
		 = append(, )
	}
	sort.Slice(, func(,  int) bool { return [] < [] })
	return 
}

func cloneFallbackBytes( context.Context,  []byte) ([]byte, error) {
	 := make([]byte, len())
	const  = 64 * 1024
	for  := 0;  < len();  +=  {
		if  := .Err();  != nil {
			return nil, 
		}
		 := min(+, len())
		copy([:], [:])
	}
	if  := .Err();  != nil {
		return nil, 
	}
	return , nil
}