package runner

import (
	"fmt"
	"math"
	"strconv"
	"strings"

	"github.com/easyai/easyai-ai-gateway/apps/api/internal/store"
)

type resolutionNormalizeProcessor struct{}

func (resolutionNormalizeProcessor) Name() string { return "ResolutionNormalizeProcessor" }

func (resolutionNormalizeProcessor) ShouldProcess(params map[string]any, modelType string, context *paramProcessContext) bool {
	if normalizeMediaResolution(modelType, stringFromAny(params["resolution"])) != "" {
		return true
	}
	return normalizeMediaResolution(modelType, stringFromAny(params["size"])) != ""
}

func (resolutionNormalizeProcessor) Process(params map[string]any, modelType string, context *paramProcessContext) bool {
	if resolution := normalizeMediaResolution(modelType, stringFromAny(params["resolution"])); resolution != "" {
		before := params["resolution"]
		params["resolution"] = resolution
		context.resolution = resolution
		if stringFromAny(before) != resolution {
			_, capabilityValue := capabilityEvidence(context.modelCapability, modelType, "output_resolutions")
			context.recordChange(
				"ResolutionNormalizeProcessor",
				"adjust",
				"resolution",
				before,
				resolution,
				"resolution 使用了可识别的媒体分辨率格式，已归一为标准大小写和值。",
				capabilityPath(modelType, "output_resolutions"),
				capabilityValue,
			)
		}
		return true
	}
	size := stringFromAny(params["size"])
	if resolution := normalizeMediaResolution(modelType, size); resolution != "" {
		_, capabilityValue := capabilityEvidence(context.modelCapability, modelType, "output_resolutions")
		params["resolution"] = resolution
		context.resolution = resolution
		context.recordChange(
			"ResolutionNormalizeProcessor",
			"set",
			"resolution",
			nil,
			resolution,
			"size 使用分辨率格式，归一到 resolution 供后续能力校验和计费使用。",
			capabilityPath(modelType, "output_resolutions"),
			capabilityValue,
		)
	}
	return true
}

type aspectRatioProcessor struct{}

func (aspectRatioProcessor) Name() string { return "AspectRatioProcessor" }

func (aspectRatioProcessor) ShouldProcess(params map[string]any, modelType string, context *paramProcessContext) bool {
	if modelType == "text_generate" {
		return false
	}
	for _, key := range []string{"aspect_ratio", "aspectRatio", "ratio"} {
		if _, ok := params[key]; ok {
			return true
		}
	}
	return stringFromAny(params["size"]) != ""
}

func (aspectRatioProcessor) Process(params map[string]any, modelType string, context *paramProcessContext) bool {
	aspectRatio, ratioSource, ratioPresent := requestAspectRatio(params)
	if ratioPresent {
		originalAspectRatio := aspectRatio
		normalized, valid := normalizedAspectRatio(aspectRatio)
		if !valid {
			before := map[string]any{}
			for _, key := range []string{"aspect_ratio", "aspectRatio", "ratio"} {
				if value, ok := params[key]; ok {
					before[key] = cloneAny(value)
					delete(params, key)
				}
			}
			aspectRatio = ""
			context.aspectRatio = ""
			context.recordChange(
				"AspectRatioProcessor",
				"remove",
				ratioSource,
				before,
				nil,
				"比例不是有效的正数 x:x 格式，按未指定比例处理。",
				"",
				nil,
			)
			return true
		} else {
			aspectRatio = normalized
			params["aspect_ratio"] = normalized
			for _, key := range []string{"aspectRatio", "ratio"} {
				delete(params, key)
			}
			if ratioSource != "aspect_ratio" || normalized != originalAspectRatio {
				context.recordChange(
					"AspectRatioProcessor",
					"adjust",
					"aspect_ratio",
					map[string]any{ratioSource: originalAspectRatio},
					normalized,
					"比例已归一为标准 x:x 格式。",
					"",
					nil,
				)
			}
		}
	}
	if !ratioPresent {
		return true
	}

	capability := capabilityForType(context.modelCapability, modelType)
	if capability == nil {
		return true
	}

	resolution := firstNonEmptyString(stringFromAny(params["resolution"]), context.resolution)
	if resolution == "" {
		if values := stringListFromAny(capability["output_resolutions"]); len(values) > 0 {
			resolution = values[0]
		} else if size := stringFromAny(params["size"]); strings.HasSuffix(size, "K") || strings.HasSuffix(size, "p") {
			resolution = size
		}
	}

	allowed := aspectRatioAllowed(capability["aspect_ratio_allowed"], resolution)
	if allowed != nil && len(allowed) == 1 && allowed[0] == "adaptive" {
		before := params["aspect_ratio"]
		params["aspect_ratio"] = "adaptive"
		context.aspectRatio = "adaptive"
		if before != "adaptive" {
			context.recordChange(
				"AspectRatioProcessor",
				"adjust",
				"aspect_ratio",
				before,
				"adaptive",
				"模型当前分辨率只允许 adaptive 宽高比。",
				capabilityPath(modelType, "aspect_ratio_allowed"),
				capability["aspect_ratio_allowed"],
			)
		}
		return true
	}
	if allowed != nil && len(allowed) == 0 {
		before := params["aspect_ratio"]
		delete(params, "aspect_ratio")
		context.aspectRatio = ""
		context.recordChange(
			"AspectRatioProcessor",
			"remove",
			"aspect_ratio",
			before,
			nil,
			"模型能力配置不允许传入任何 aspect_ratio。",
			capabilityPath(modelType, "aspect_ratio_allowed"),
			capability["aspect_ratio_allowed"],
		)
		return true
	}
	if aspectRatio == "" {
		return true
	}
	if allowed == nil && validAspectRatio(aspectRatio) {
		params["aspect_ratio"] = aspectRatio
		context.aspectRatio = aspectRatio
		return true
	}

	processed, ok := validateExplicitAspectRatio(aspectRatio, capability, allowed, modelType)
	if !ok {
		before := params["aspect_ratio"]
		delete(params, "aspect_ratio")
		context.aspectRatio = ""
		context.recordChange(
			"AspectRatioProcessor",
			"remove",
			"aspect_ratio",
			before,
			nil,
			"传入的 aspect_ratio 不在模型允许范围内，且没有可用替代值。",
			capabilityPath(modelType, "aspect_ratio_allowed"),
			capability["aspect_ratio_allowed"],
		)
		return true
	}
	if processed != "" {
		before := params["aspect_ratio"]
		params["aspect_ratio"] = processed
		context.aspectRatio = processed
		if before != processed {
			path := capabilityPath(modelType, "aspect_ratio_allowed")
			value := capability["aspect_ratio_allowed"]
			if ratioRange, ok := numberPair(capability["aspect_ratio_range"]); ok {
				ratio, valid := aspectRatioNumber(aspectRatio)
				if !valid || ratio < ratioRange[0] || ratio > ratioRange[1] {
					path = capabilityPath(modelType, "aspect_ratio_range")
					value = capability["aspect_ratio_range"]
				}
			}
			context.recordChange(
				"AspectRatioProcessor",
				"adjust",
				"aspect_ratio",
				before,
				processed,
				"传入的 aspect_ratio 不符合模型能力配置，已调整为允许值。",
				path,
				value,
			)
		}
	}
	return true
}

func requestAspectRatio(params map[string]any) (string, string, bool) {
	for _, key := range []string{"aspect_ratio", "aspectRatio", "ratio"} {
		if value, ok := params[key]; ok {
			return stringFromAny(value), key, true
		}
	}
	return "", "aspect_ratio", false
}

func validateExplicitAspectRatio(aspectRatio string, capability map[string]any, allowed []string, modelType string) (string, bool) {
	if isVideoModelType(modelType) && len(allowed) > 0 && !containsString(allowed, aspectRatio) {
		if aspectRatio == "adaptive" || aspectRatio == "keep_ratio" {
			return "", false
		}
		if _, ok := numberPair(capability["aspect_ratio_range"]); !ok {
			return allowed[0], true
		}
	}
	return validateAndAdjustAspectRatio(aspectRatio, capability, allowed)
}

type imageSizeProcessor struct{}

func (imageSizeProcessor) Name() string { return "ImageSizeProcessor" }

func (imageSizeProcessor) ShouldProcess(params map[string]any, modelType string, context *paramProcessContext) bool {
	if modelType != "image_generate" && modelType != "image_edit" {
		return false
	}
	_, _, ok := imageDimensionsFromParams(params)
	return ok
}

func (imageSizeProcessor) Process(params map[string]any, modelType string, context *paramProcessContext) bool {
	capability := capabilityForType(context.modelCapability, modelType)
	if capability == nil {
		capability = map[string]any{}
	}
	width, height, ok := imageDimensionsFromParams(params)
	if !ok {
		return true
	}

	before := map[string]any{}
	for _, key := range []string{"width", "height", "size", "resolution"} {
		if value, exists := params[key]; exists {
			before[key] = cloneAny(value)
		}
	}

	width, height = constrainImageDimensions(width, height, capability)
	params["width"] = width
	params["height"] = height
	_, _, sizeHasPixelDimensions := parsePixelSizeString(stringFromAny(params["size"]))
	_, _, resolutionHasPixelDimensions := parsePixelSizeString(stringFromAny(params["resolution"]))
	explicitWidthHeight := positiveIntegerFromAny(params["width"]) > 0 && positiveIntegerFromAny(params["height"]) > 0 &&
		normalizeImageResolution(stringFromAny(params["size"])) == "" &&
		normalizeImageResolution(stringFromAny(params["resolution"])) == ""
	if sizeHasPixelDimensions || resolutionHasPixelDimensions || (stringFromAny(params["aspect_ratio"]) == "" && explicitWidthHeight) {
		aspectRatio := aspectRatioFromDimensions(width, height)
		allowed := aspectRatioAllowed(capability["aspect_ratio_allowed"], firstNonEmptyString(stringFromAny(params["resolution"]), context.resolution))
		if processed, ok := validateAndAdjustAspectRatio(aspectRatio, capability, allowed); ok && processed != "" {
			params["aspect_ratio"] = processed
			context.aspectRatio = processed
		}
	}
	resolution := normalizeImageResolutionForCapability(firstNonEmptyString(stringFromAny(params["resolution"]), context.resolution), width, height, capability)
	if resolution != "" {
		params["resolution"] = resolution
		context.resolution = resolution
	}
	if stringFromAny(capability["size_param_format"]) == "resolution" && resolution != "" {
		params["size"] = resolution
	} else {
		params["size"] = fmt.Sprintf("%dx%d", width, height)
	}

	after := map[string]any{
		"width":  params["width"],
		"height": params["height"],
		"size":   params["size"],
	}
	if value := stringFromAny(params["resolution"]); value != "" {
		after["resolution"] = value
	}
	if !imageSizeMapsEqual(before, after) {
		path, value := imageSizeConstraintEvidence(modelType, capability)
		context.recordChange(
			"ImageSizeProcessor",
			"adjust",
			"size",
			before,
			after,
			"图像宽高不符合模型尺寸限制，已按模型能力调整 width、height 和 size。",
			path,
			value,
		)
	}
	return true
}

func imageDimensionsFromParams(params map[string]any) (int, int, bool) {
	if width, height, ok := parsePixelSizeString(stringFromAny(params["size"])); ok {
		return width, height, true
	}
	width := positiveIntegerFromAny(params["width"])
	height := positiveIntegerFromAny(params["height"])
	if width > 0 && height > 0 {
		return width, height, true
	}
	if width, height, ok := parsePixelSizeString(stringFromAny(params["resolution"])); ok {
		return width, height, true
	}
	resolution := firstNonEmptyString(
		normalizeImageResolution(stringFromAny(params["resolution"])),
		normalizeImageResolution(stringFromAny(params["size"])),
	)
	if resolution == "" {
		return 0, 0, false
	}
	return imageDimensionsForResolution(resolution, stringFromAny(params["aspect_ratio"]))
}

func imageDimensionsForResolution(resolution string, aspectRatio string) (int, int, bool) {
	resolution = normalizeImageResolution(resolution)
	if resolution == "" {
		return 0, 0, false
	}
	magnitude, err := strconv.Atoi(strings.TrimSuffix(resolution, "K"))
	if err != nil || magnitude <= 0 {
		return 0, 0, false
	}
	baseSide := magnitude * 1024
	ratio, validRatio := aspectRatioNumber(aspectRatio)
	if !validRatio {
		return baseSide, baseSide, true
	}
	if ratio >= 1 {
		return baseSide, max(1, int(math.Round(float64(baseSide)/ratio))), true
	}
	return max(1, int(math.Round(float64(baseSide)*ratio))), baseSide, true
}

func imageSizeCapabilityConfigured(capability map[string]any) bool {
	if capability == nil {
		return false
	}
	if _, _, ok := imageOutputSizeRange(capability); ok {
		return true
	}
	if _, _, ok := numberRangeFromAny(capability["width_height_range"]); ok {
		return true
	}
	if _, _, ok := numberRangeFromAny(capability["aspect_ratio_range"]); ok {
		return true
	}
	return positiveIntegerFromAny(capability["width_height_multiple"]) > 1
}

func constrainImageDimensions(width int, height int, capability map[string]any) (int, int) {
	if width <= 0 || height <= 0 {
		return width, height
	}
	result := imageDimensions{Width: width, Height: height}
	for i := 0; i < 4; i++ {
		before := result
		applyImageOutputSizeRange(&result, capability)
		applyImageWidthHeightRange(&result, capability)
		applyImageAspectRatioRange(&result, capability)
		applyImageWidthHeightMultiple(&result, capability)
		if result == before {
			break
		}
	}
	return result.Width, result.Height
}

type imageDimensions struct {
	Width  int
	Height int
}

func applyImageOutputSizeRange(result *imageDimensions, capability map[string]any) {
	minValue, maxValue, ok := imageOutputSizeRange(capability)
	if !ok || result.Width <= 0 || result.Height <= 0 {
		return
	}
	pixels := float64(result.Width * result.Height)
	if minValue > 0 && pixels < minValue {
		scaleImageDimensions(result, math.Sqrt(minValue/pixels), true)
		return
	}
	if maxValue > 0 && pixels > maxValue {
		scaleImageDimensions(result, math.Sqrt(maxValue/pixels), false)
	}
}

func applyImageWidthHeightRange(result *imageDimensions, capability map[string]any) {
	minValue, maxValue, ok := numberRangeFromAny(capability["width_height_range"])
	if !ok || result.Width <= 0 || result.Height <= 0 {
		return
	}
	maxSide := float64(max(result.Width, result.Height))
	minSide := float64(min(result.Width, result.Height))
	if maxValue > 0 && maxSide > maxValue {
		scaleImageDimensions(result, maxValue/maxSide, false)
		return
	}
	if minValue > 0 && minSide < minValue {
		scaleImageDimensions(result, minValue/minSide, true)
	}
}

func applyImageAspectRatioRange(result *imageDimensions, capability map[string]any) {
	minValue, maxValue, ok := numberRangeFromAny(capability["aspect_ratio_range"])
	if !ok || result.Width <= 0 || result.Height <= 0 {
		return
	}
	ratio := float64(result.Width) / float64(result.Height)
	if maxValue > 0 && ratio > maxValue {
		result.Height = max(1, int(math.Ceil(float64(result.Width)/maxValue)))
		return
	}
	if minValue > 0 && ratio < minValue {
		result.Width = max(1, int(math.Ceil(float64(result.Height)*minValue)))
	}
}

func applyImageWidthHeightMultiple(result *imageDimensions, capability map[string]any) {
	multiple := positiveIntegerFromAny(capability["width_height_multiple"])
	if multiple <= 1 || result.Width <= 0 || result.Height <= 0 {
		return
	}
	result.Width = roundImageDimensionToMultiple(result.Width, multiple)
	result.Height = roundImageDimensionToMultiple(result.Height, multiple)
}

func imageOutputSizeRange(capability map[string]any) (float64, float64, bool) {
	for _, key := range []string{"output_size_range", "outputSizeRange", "size_range", "sizeRange"} {
		if minValue, maxValue, ok := numberRangeFromAny(capability[key]); ok {
			return minValue, maxValue, true
		}
	}
	minValue := firstPositiveNumber(capability, []string{"output_min_size", "outputMinSize", "min_output_size", "minOutputSize", "min_size", "minSize", "output_min_pixels", "outputMinPixels", "min_pixels", "minPixels"})
	maxValue := firstPositiveNumber(capability, []string{"output_max_size", "outputMaxSize", "max_output_size", "maxOutputSize", "max_size", "maxSize", "output_max_pixels", "outputMaxPixels", "max_pixels", "maxPixels"})
	return minValue, maxValue, minValue > 0 || maxValue > 0
}

func firstPositiveNumber(values map[string]any, keys []string) float64 {
	for _, key := range keys {
		if value := positiveFloatFromAny(values[key]); value > 0 {
			return value
		}
	}
	return 0
}

func numberRangeFromAny(value any) (float64, float64, bool) {
	pair, ok := numberPair(value)
	if !ok {
		return 0, 0, false
	}
	minValue := pair[0]
	maxValue := pair[1]
	if minValue < 0 || maxValue < 0 {
		return 0, 0, false
	}
	if minValue > maxValue && maxValue > 0 {
		minValue, maxValue = maxValue, minValue
	}
	return minValue, maxValue, minValue > 0 || maxValue > 0
}

func scaleImageDimensions(result *imageDimensions, scale float64, roundUp bool) {
	if scale <= 0 || math.IsNaN(scale) || math.IsInf(scale, 0) {
		return
	}
	round := math.Floor
	if roundUp {
		round = math.Ceil
	}
	result.Width = max(1, int(round(float64(result.Width)*scale)))
	result.Height = max(1, int(round(float64(result.Height)*scale)))
}

func roundImageDimensionToMultiple(value int, multiple int) int {
	if multiple <= 1 {
		return value
	}
	return max(multiple, int(math.Round(float64(value)/float64(multiple)))*multiple)
}

func normalizeImageResolutionForCapability(current string, width int, height int, capability map[string]any) string {
	allowed := stringListFromAny(capability["output_resolutions"])
	if containsString(allowed, current) {
		return current
	}
	resolution := imageResolutionFromDimensions(width, height)
	if len(allowed) == 0 {
		return firstNonEmptyString(current, resolution)
	}
	if containsString(allowed, resolution) {
		return resolution
	}
	return closestImageResolution(resolution, allowed)
}

func imageResolutionFromDimensions(width int, height int) string {
	maxSide := max(width, height)
	switch {
	case maxSide <= 1920:
		return "1K"
	case maxSide <= 2560:
		return "2K"
	case maxSide <= 3328:
		return "3K"
	case maxSide <= 3840:
		return "4K"
	default:
		return "8K"
	}
}

func aspectRatioFromDimensions(width int, height int) string {
	if width <= 0 || height <= 0 {
		return ""
	}
	divisor := gcd(width, height)
	return fmt.Sprintf("%d:%d", width/divisor, height/divisor)
}

func gcd(a int, b int) int {
	if a < 0 {
		a = -a
	}
	if b < 0 {
		b = -b
	}
	for b != 0 {
		a, b = b, a%b
	}
	if a == 0 {
		return 1
	}
	return a
}

func closestImageResolution(target string, allowed []string) string {
	order := []string{"1K", "2K", "3K", "4K", "8K"}
	targetIndex := indexOfString(order, target)
	valid := make([]string, 0, len(allowed))
	for _, value := range allowed {
		if indexOfString(order, value) >= 0 {
			valid = append(valid, value)
		}
	}
	if len(valid) == 0 {
		if len(allowed) > 0 {
			return allowed[0]
		}
		return target
	}
	if targetIndex < 0 {
		return valid[0]
	}
	for index := targetIndex; index >= 0; index-- {
		if containsString(valid, order[index]) {
			return order[index]
		}
	}
	for _, value := range order[targetIndex+1:] {
		if containsString(valid, value) {
			return value
		}
	}
	return valid[0]
}

func indexOfString(values []string, target string) int {
	for index, value := range values {
		if value == target {
			return index
		}
	}
	return -1
}

func imageSizeConstraintEvidence(modelType string, capability map[string]any) (string, any) {
	for _, key := range []string{"output_size_range", "outputSizeRange", "output_min_size", "outputMinSize", "min_pixels", "minPixels", "output_max_size", "outputMaxSize"} {
		if value, ok := capability[key]; ok {
			return capabilityPath(modelType, key), cloneAny(value)
		}
	}
	for _, key := range []string{"width_height_range", "aspect_ratio_range", "width_height_multiple"} {
		if value, ok := capability[key]; ok {
			return capabilityPath(modelType, key), cloneAny(value)
		}
	}
	return capabilityPath(modelType, ""), cloneMap(capability)
}

func imageSizeMapsEqual(before map[string]any, after map[string]any) bool {
	for key, value := range after {
		if before[key] != value {
			return false
		}
	}
	for key := range before {
		if _, ok := after[key]; !ok {
			return false
		}
	}
	return true
}

type inputAudioProcessor struct{}

func (inputAudioProcessor) Name() string { return "InputAudioProcessor" }

func (inputAudioProcessor) ShouldProcess(params map[string]any, modelType string, context *paramProcessContext) bool {
	if !isVideoModelType(modelType) {
		return false
	}
	content := contentItems(params["content"])
	for _, item := range content {
		if isAudioContent(item) {
			return true
		}
	}
	return false
}

func (inputAudioProcessor) Process(params map[string]any, modelType string, context *paramProcessContext) bool {
	content := contentItems(params["content"])
	if len(content) == 0 {
		return true
	}
	supportsInputAudio := false
	if len(context.modelCapability) > 0 {
		if isOmniVideoLike(context) {
			supportsInputAudio = supportsOmniAudioReference(context)
		} else if capability := capabilityForType(context.modelCapability, modelType); capability != nil {
			supportsInputAudio = boolFromAny(capability["input_audio"])
		}
	}
	if supportsInputAudio {
		return true
	}
	next := make([]map[string]any, 0, len(content))
	for index, item := range content {
		if isAudioContent(item) {
			path, value := audioInputCapabilityEvidence(context, modelType)
			context.recordChange(
				"InputAudioProcessor",
				"remove",
				fmt.Sprintf("content[%d]", index),
				item,
				nil,
				"模型能力未开启输入音频，已移除 audio_url。",
				path,
				value,
			)
			continue
		}
		next = append(next, item)
	}
	params["content"] = mapsToAnySlice(next)
	path, value := audioInputCapabilityEvidence(context, modelType)
	deleteFieldsWithLog(params, context, "InputAudioProcessor", []string{"audio_url", "audioUrl", "reference_audio", "referenceAudio"}, "模型能力未开启输入音频，已移除音频参考快捷字段。", path, value)
	return true
}

type durationProcessor struct{}

func (durationProcessor) Name() string { return "DurationProcessor" }

func (durationProcessor) ShouldProcess(params map[string]any, modelType string, context *paramProcessContext) bool {
	return isVideoModelType(modelType) && params["duration"] != nil
}

func (durationProcessor) Process(params map[string]any, modelType string, context *paramProcessContext) bool {
	capability := capabilityForType(context.modelCapability, modelType)
	if capability == nil {
		return true
	}
	duration := floatFromAny(params["duration"])
	if duration <= 0 {
		return true
	}
	resolution := firstNonEmptyString(stringFromAny(params["resolution"]), context.resolution)
	modeKey := videoModeKey(params)
	if options := scopedNumberList(capability["duration_options"], resolution, modeKey); len(options) > 0 {
		normalized := nextAllowedNumber(duration, options)
		params["duration"] = normalized
		syncDurationSeconds(params)
		if normalized != duration {
			context.recordChange(
				"DurationProcessor",
				"adjust",
				"duration",
				duration,
				normalized,
				"duration 不在模型固定时长选项内，已向上调整为允许值。",
				capabilityPath(modelType, "duration_options"),
				capability["duration_options"],
			)
		}
		return true
	}
	if minValue, maxValue, ok := scopedRange(capability["duration_range"], resolution, modeKey); ok {
		step := durationStep(capability["duration_step"], resolution, modeKey)
		normalized := normalizeDurationByRange(duration, minValue, maxValue, step)
		params["duration"] = normalized
		syncDurationSeconds(params)
		if normalized != duration {
			context.recordChange(
				"DurationProcessor",
				"adjust",
				"duration",
				duration,
				normalized,
				"duration 超出模型时长范围或步进配置，已按能力配置归一。",
				capabilityPath(modelType, "duration_range"),
				map[string]any{
					"duration_range": capability["duration_range"],
					"duration_step":  capability["duration_step"],
				},
			)
		}
		return true
	}
	step := durationStep(capability["duration_step"], resolution, modeKey)
	normalized := normalizeDurationByStep(duration, step)
	params["duration"] = normalized
	syncDurationSeconds(params)
	if normalized != duration {
		context.recordChange(
			"DurationProcessor",
			"adjust",
			"duration",
			duration,
			normalized,
			"duration 不符合模型时长步进，已按步进向上归一。",
			capabilityPath(modelType, "duration_step"),
			capability["duration_step"],
		)
	}
	return true
}

type audioProcessor struct{}

func (audioProcessor) Name() string { return "AudioProcessor" }

func (audioProcessor) ShouldProcess(params map[string]any, modelType string, context *paramProcessContext) bool {
	return isVideoModelType(modelType) && (params["audio"] != nil || params["output_audio"] != nil)
}

func (audioProcessor) Process(params map[string]any, modelType string, context *paramProcessContext) bool {
	if context != nil && kelingO1GeneratedAudioRequested(params, context.candidate) {
		return context.reject(
			"AudioProcessor",
			"audio",
			params["audio"],
			"kling-video-o1 does not support generated audio",
			capabilityPath(modelType, "output_audio"),
			capabilityValue(context.modelCapability, modelType, "output_audio"),
		)
	}
	capability := capabilityForType(context.modelCapability, modelType)
	if capability == nil || !boolFromAny(capability["output_audio"]) {
		for _, key := range []string{"audio", "output_audio"} {
			if before, ok := params[key]; ok {
				delete(params, key)
				context.recordChange(
					"AudioProcessor",
					"remove",
					key,
					before,
					nil,
					"模型能力未开启输出音频，已移除音频输出参数。",
					capabilityPath(modelType, "output_audio"),
					capabilityValue(context.modelCapability, modelType, "output_audio"),
				)
			}
		}
	}
	return true
}

func kelingO1GeneratedAudioRequested(params map[string]any, candidate store.RuntimeModelCandidate) bool {
	if !strings.EqualFold(strings.TrimSpace(candidate.Provider), "keling") {
		return false
	}
	model := strings.ToLower(strings.TrimSpace(candidate.ProviderModelName))
	if model != "kling-o1" && model != "kling-video-o1" {
		return false
	}
	return boolFromAny(params["audio"]) || boolFromAny(params["output_audio"])
}

type imageCountProcessor struct{}

func (imageCountProcessor) Name() string { return "ImageCountProcessor" }

func (imageCountProcessor) ShouldProcess(params map[string]any, modelType string, context *paramProcessContext) bool {
	return modelType == "image_generate" || modelType == "image_edit"
}

func (imageCountProcessor) Process(params map[string]any, modelType string, context *paramProcessContext) bool {
	capability := capabilityForType(context.modelCapability, modelType)
	if capability == nil {
		return true
	}
	maxCount := int(math.Round(floatFromAny(capability["output_max_images_count"])))
	if !boolFromAny(capability["output_multiple_images"]) {
		maxCount = 1
	}
	if maxCount <= 0 {
		return true
	}
	count := int(math.Round(floatFromAny(params["n"])))
	if count <= 0 {
		count = int(math.Round(floatFromAny(params["batch_size"])))
	}
	if count <= 0 {
		count = 1
	}
	if count > maxCount {
		before := count
		count = maxCount
		context.recordChange(
			"ImageCountProcessor",
			"adjust",
			"n",
			before,
			count,
			"请求图片数量超过模型输出上限，已按 output_max_images_count 截断。",
			capabilityPath(modelType, "output_max_images_count"),
			capability["output_max_images_count"],
		)
	}
	params["n"] = count
	return true
}

type imageQualityProcessor struct{}

func (imageQualityProcessor) Name() string { return "ImageQualityProcessor" }

var openAICompatibleImageQualities = map[string]struct{}{
	"low":    {},
	"medium": {},
	"high":   {},
	"auto":   {},
}

func (imageQualityProcessor) ShouldProcess(params map[string]any, modelType string, context *paramProcessContext) bool {
	if modelType != "image_generate" && modelType != "image_edit" {
		return false
	}
	_, ok := params["quality"]
	return ok
}

func (imageQualityProcessor) Process(params map[string]any, modelType string, context *paramProcessContext) bool {
	capability := capabilityForType(context.modelCapability, modelType)
	quality := stringFromAny(params["quality"])
	if supportsImageQualityControl(capability) && isOpenAICompatibleImageQuality(quality) {
		return true
	}

	before := params["quality"]
	delete(params, "quality")
	context.recordChange(
		"ImageQualityProcessor",
		"remove",
		"quality",
		before,
		nil,
		"模型能力未开启生成质量控制，已移除 quality 参数。",
		capabilityPath(modelType, "support_quality_control"),
		capabilityValue(context.modelCapability, modelType, "support_quality_control"),
	)
	return true
}

func supportsImageQualityControl(capability map[string]any) bool {
	if capability == nil {
		return false
	}
	for _, key := range []string{"support_quality_control", "supportQualityControl", "quality_control", "qualityControl", "quality"} {
		if boolFromAny(capability[key]) {
			return true
		}
	}
	return false
}

func isOpenAICompatibleImageQuality(value string) bool {
	if value == "" {
		return false
	}
	_, ok := openAICompatibleImageQualities[value]
	return ok
}
