package processing

import (
	"slices"

	"github.com/imgproxy/imgproxy/v3/imagetype"
	"github.com/imgproxy/imgproxy/v3/options"
	"github.com/imgproxy/imgproxy/v3/options/keys"
	"github.com/imgproxy/imgproxy/v3/security"
	"github.com/imgproxy/imgproxy/v3/vips/color"
)

// ProcessingOptions is a thin wrapper around options.Options that provides
// helpers for image processing options.
type ProcessingOptions struct {
	*options.Options

	// Config that contains default values for some options.
	config *Config

	// Security checker to obtain security-related limits.
	securityChecker *security.Checker
}

func (p *Processor) NewProcessingOptions(o *options.Options) ProcessingOptions {
	return ProcessingOptions{
		Options:         o,
		config:          p.config,
		securityChecker: p.securityChecker,
	}
}

func (po ProcessingOptions) Width() int {
	return po.GetInt(keys.Width, 0)
}

func (po ProcessingOptions) Height() int {
	return po.GetInt(keys.Height, 0)
}

func (po ProcessingOptions) MinWidth() int {
	return po.GetInt(keys.MinWidth, 0)
}

func (po ProcessingOptions) MinHeight() int {
	return po.GetInt(keys.MinHeight, 0)
}

func (po ProcessingOptions) ResizingType() ResizeType {
	return options.Get(po.Options, keys.ResizingType, ResizeFit)
}

func (po ProcessingOptions) ZoomWidth() float64 {
	return po.GetFloat(keys.ZoomWidth, 1.0)
}

func (po ProcessingOptions) ZoomHeight() float64 {
	return po.GetFloat(keys.ZoomHeight, 1.0)
}

func (po ProcessingOptions) DPR() float64 {
	return po.GetFloat(keys.Dpr, 1.0)
}

func (po ProcessingOptions) EnforceThumbnail() bool {
	return po.Main().GetBool(keys.EnforceThumbnail, po.config.EnforceThumbnail)
}

func (po ProcessingOptions) Enlarge() bool {
	return po.GetBool(keys.Enlarge, false)
}

func (po ProcessingOptions) Gravity() GravityOptions {
	return NewGravityOptions(po.Options, keys.Gravity, GravityCenter)
}

func (po ProcessingOptions) ExtendEnabled() bool {
	return po.GetBool(keys.ExtendEnabled, false)
}

func (po ProcessingOptions) ExtendGravity() GravityOptions {
	return NewGravityOptions(po.Options, keys.ExtendGravity, GravityCenter)
}

func (po ProcessingOptions) ExtendAspectRatioEnabled() bool {
	return po.GetBool(keys.ExtendAspectRatioEnabled, false)
}

func (po ProcessingOptions) ExtendAspectRatioGravity() GravityOptions {
	return NewGravityOptions(po.Options, keys.ExtendAspectRatioGravity, GravityCenter)
}

func (po ProcessingOptions) Rotate() int {
	return po.GetInt(keys.Rotate, 0)
}

func (po ProcessingOptions) FlipHorizontal() bool {
	return po.GetBool(keys.FlipHorizontal, false)
}

func (po ProcessingOptions) FlipVertical() bool {
	return po.GetBool(keys.FlipVertical, false)
}

func (po ProcessingOptions) AutoRotate() bool {
	return po.GetBool(keys.AutoRotate, po.config.AutoRotate)
}

func (po ProcessingOptions) CropWidth() float64 {
	return po.GetFloat(keys.CropWidth, 0.0)
}

func (po ProcessingOptions) CropHeight() float64 {
	return po.GetFloat(keys.CropHeight, 0.0)
}

func (po ProcessingOptions) CropGravity() GravityOptions {
	return NewGravityOptions(po.Options, keys.CropGravity, GravityUnknown)
}

func (po ProcessingOptions) Format() imagetype.Type {
	return options.Get(po.Main(), keys.Format, imagetype.Unknown)
}

func (po ProcessingOptions) SetFormat(format imagetype.Type) {
	po.Set(keys.Format, format)
}

func (po ProcessingOptions) ShouldSkipFormatProcessing(inFormat imagetype.Type) bool {
	return slices.Contains(po.config.SkipProcessingFormats, inFormat) ||
		options.SliceContains(po.Main(), keys.SkipProcessing, inFormat)
}

func (po ProcessingOptions) ShouldFlatten() bool {
	return po.Has(keys.Background)
}

func (po ProcessingOptions) Background() color.RGB {
	return options.Get(po.Options, keys.Background, color.White)
}

func (po ProcessingOptions) PaddingEnabled() bool {
	return po.PaddingTop() != 0 ||
		po.PaddingRight() != 0 ||
		po.PaddingBottom() != 0 ||
		po.PaddingLeft() != 0
}

func (po ProcessingOptions) PaddingTop() int {
	return po.GetInt(keys.PaddingTop, 0)
}

func (po ProcessingOptions) PaddingRight() int {
	return po.GetInt(keys.PaddingRight, 0)
}

func (po ProcessingOptions) PaddingBottom() int {
	return po.GetInt(keys.PaddingBottom, 0)
}

func (po ProcessingOptions) PaddingLeft() int {
	return po.GetInt(keys.PaddingLeft, 0)
}

func (po ProcessingOptions) Blur() float64 {
	return po.GetFloat(keys.Blur, 0.0)
}

func (po ProcessingOptions) Sharpen() float64 {
	return po.GetFloat(keys.Sharpen, 0.0)
}

func (po ProcessingOptions) Pixelate() int {
	return po.GetInt(keys.Pixelate, 1)
}

func (po ProcessingOptions) PreferWebP() bool {
	return po.GetBool(keys.PreferWebP, false)
}

func (po ProcessingOptions) PreferAvif() bool {
	return po.GetBool(keys.PreferAvif, false)
}

func (po ProcessingOptions) PreferJxl() bool {
	return po.GetBool(keys.PreferJxl, false)
}

func (po ProcessingOptions) EnforceWebP() bool {
	return po.GetBool(keys.EnforceWebP, false)
}

func (po ProcessingOptions) EnforceAvif() bool {
	return po.GetBool(keys.EnforceAvif, false)
}

func (po ProcessingOptions) EnforceJxl() bool {
	return po.GetBool(keys.EnforceJxl, false)
}

func (po ProcessingOptions) TrimEnabled() bool {
	return po.Has(keys.TrimThreshold)
}

func (po ProcessingOptions) DisableTrim() {
	po.Delete(keys.TrimThreshold)
}

func (po ProcessingOptions) TrimThreshold() float64 {
	return po.GetFloat(keys.TrimThreshold, 10.0)
}

func (po ProcessingOptions) TrimSmart() bool {
	return !po.Has(keys.TrimColor)
}

func (po ProcessingOptions) TrimColor() color.RGB {
	return options.Get(po.Options, keys.TrimColor, color.Black)
}

func (po ProcessingOptions) TrimEqualHor() bool {
	return po.GetBool(keys.TrimEqualHor, false)
}

func (po ProcessingOptions) TrimEqualVer() bool {
	return po.GetBool(keys.TrimEqualVer, false)
}

func (po ProcessingOptions) WatermarkOpacity() float64 {
	return po.GetFloat(keys.WatermarkOpacity, 0.0)
}

func (po ProcessingOptions) SetWatermarkOpacity(opacity float64) {
	po.Set(keys.WatermarkOpacity, opacity)
}

func (po ProcessingOptions) DeleteWatermarkOpacity() {
	po.Delete(keys.WatermarkOpacity)
}

func (po ProcessingOptions) WatermarkPosition() GravityType {
	return options.Get(po.Options, keys.WatermarkPosition, GravityCenter)
}

func (po ProcessingOptions) WatermarkXOffset() float64 {
	return po.GetFloat(keys.WatermarkXOffset, 0.0)
}

func (po ProcessingOptions) WatermarkYOffset() float64 {
	return po.GetFloat(keys.WatermarkYOffset, 0.0)
}

func (po ProcessingOptions) WatermarkScale() float64 {
	return po.GetFloat(keys.WatermarkScale, 0.0)
}

func (po ProcessingOptions) PreserveHDR() bool {
	return po.Main().GetBool(keys.PreserveHDR, po.config.PreserveHDR)
}

// Quality retrieves the quality setting for a given image format.
// It first checks for a general quality setting, then for a format-specific setting,
// and finally falls back to the configured default quality.
func (po ProcessingOptions) Quality(format imagetype.Type) int {
	// First, check if quality is explicitly set in options.
	if q := po.Main().GetInt(keys.Quality, 0); q > 0 {
		return q
	}

	// Then, check if format-specific quality is set in options.
	if q := po.Main().GetInt(keys.FormatQuality(format), 0); q > 0 {
		return q
	}

	// Then, check if format-specific quality is set in config.
	if q := po.config.FormatQuality[format]; q > 0 {
		return q
	}

	// Finally, return the general quality setting from config.
	return po.config.Quality
}

func (po ProcessingOptions) MaxBytes() int {
	return po.Main().GetInt(keys.MaxBytes, 0)
}

func (po ProcessingOptions) StripMetadata() bool {
	return po.Main().GetBool(keys.StripMetadata, po.config.StripMetadata)
}

func (po ProcessingOptions) KeepCopyright() bool {
	return po.Main().GetBool(keys.KeepCopyright, po.config.KeepCopyright)
}

func (po ProcessingOptions) StripColorProfile() bool {
	return po.Main().GetBool(keys.StripColorProfile, po.config.StripColorProfile)
}

func (po ProcessingOptions) MaxSrcResolution() int {
	return po.securityChecker.MaxSrcResolution(po.Main())
}

func (po ProcessingOptions) MaxAnimationFrames() int {
	return po.securityChecker.MaxAnimationFrames(po.Main())
}

func (po ProcessingOptions) MaxResultDimension() int {
	return po.securityChecker.MaxResultDimension(po.Main())
}
