import { useEffect, useMemo, useRef, useState, type CSSProperties, type PointerEvent as ReactPointerEvent } from "react";
import { useTranslation } from "react-i18next";
import { Message, Modal } from "@arco-design/web-react";
import { createGenTask, getAiModelList } from "@/api/api";
import { MY_CREATIONS_REFRESH_QUERY, ROUTES } from "@/constants";
import { ensureSufficientPoints } from "@/layouts/globalPromptPanel/composerCommon";
import type { AiModelItem } from "@/layouts/globalPromptPanel/types";
import { buildGenTaskModelPayload, normalizeAiModelItem } from "@/layouts/globalPromptPanel/utils";
import { useGenerateCost } from "@/layouts/globalPromptPanel/useGenerateCost";
import { useUserStore } from "@/store/useUserStore";
import "./index.scss";

const EXPAND_IMAGE_SCALE_OPTIONS = ["1.5x", "2x", "3x"] as const;
type ExpandImageScale = (typeof EXPAND_IMAGE_SCALE_OPTIONS)[number];

const EXPAND_IMAGE_RATIO_OPTIONS = [
  { value: "original", iconClassName: "assetDetailsExpandImageModal__ratioIcon_original" },
  { value: "1:1", iconClassName: "assetDetailsExpandImageModal__ratioIcon_1x1" },
  { value: "3:4", iconClassName: "assetDetailsExpandImageModal__ratioIcon_3x4" },
  { value: "4:3", iconClassName: "assetDetailsExpandImageModal__ratioIcon_4x3" },
  { value: "16:9", iconClassName: "assetDetailsExpandImageModal__ratioIcon_16x9" },
] as const;
type ExpandImageRatio = (typeof EXPAND_IMAGE_RATIO_OPTIONS)[number]["value"];

const EXPAND_IMAGE_TASK_TYPE = 1;
const EXPAND_IMAGE_SUB_TYPE = 112;
const EXPAND_IMAGE_RESOLUTION = "1K";
const EXPAND_IMAGE_BATCH_SIZE = 1;
const EXPAND_IMAGE_COST_PARAM_VALUES: Record<string, string | number> = { batchSize: EXPAND_IMAGE_BATCH_SIZE };
const EXPAND_IMAGE_PREVIEW_HEIGHT = 369;
const EXPAND_IMAGE_ORIGINAL_IMAGE_MAX_SIZE = { width: 140, height: 122 };
const EXPAND_IMAGE_ORIGINAL_FRAME_TOP = 66;
const EXPAND_IMAGE_FIXED_RATIO_MAX_SIZE = { width: 560, height: 315 };
const EXPAND_IMAGE_MAX_DIRECTION_RATIO = 0.4;
const EXPAND_IMAGE_MAX_AXIS_RATIO = EXPAND_IMAGE_MAX_DIRECTION_RATIO * 2;
const EXPAND_IMAGE_RATIO_EPSILON = 0.0001;
const EXPAND_IMAGE_SCALE_MULTIPLIERS: Record<ExpandImageScale, number> = {
  "1.5x": 1.5,
  "2x": 2,
  "3x": 3,
};
const EXPAND_IMAGE_RATIO_ASPECT_MAP: Record<Exclude<ExpandImageRatio, "original">, number> = {
  "1:1": 1,
  "3:4": 3 / 4,
  "4:3": 4 / 3,
  "16:9": 16 / 9,
};

interface ExpandImageSize {
  width: number;
  height: number;
}

interface ExpandImagePosition {
  x: number;
  y: number;
}

interface ExpandImageInsets {
  top: number;
  left: number;
  right: number;
  bottom: number;
}

interface ExpandImagePreviewLayout {
  frameStyle: CSSProperties;
  imageStyle: CSSProperties;
  imageSize: ExpandImageSize;
  maxOffsetX: number;
  maxOffsetY: number;
  widthMultiplier: number;
  heightMultiplier: number;
}

interface ExpandImageDragState {
  pointerId: number;
  startClientX: number;
  startClientY: number;
  startPosition: ExpandImagePosition;
  maxOffsetX: number;
  maxOffsetY: number;
}

export interface AssetExpandImageModalProps {
  visible: boolean;
  imageUrl: string;
  originalRatio?: string;
  onClose: () => void;
}

function formatExpandImageMultiplier(value: number) {
  const rounded = Math.round(value * 10) / 10;
  return Number.isInteger(rounded) ? String(rounded) : rounded.toFixed(1);
}

function clampNumber(value: number, min: number, max: number) {
  return Math.min(max, Math.max(min, value));
}

function clampExpandImageInsetRatio(value: number) {
  if (!Number.isFinite(value)) return 0;
  return clampNumber(Math.round(value * 10000) / 10000, 0, EXPAND_IMAGE_MAX_DIRECTION_RATIO);
}

function fitExpandImageSize(aspectRatio: number, maxSize: ExpandImageSize): ExpandImageSize {
  if (!Number.isFinite(aspectRatio) || aspectRatio <= 0) {
    return maxSize;
  }
  const maxAspectRatio = maxSize.width / maxSize.height;
  if (aspectRatio >= maxAspectRatio) {
    return {
      width: maxSize.width,
      height: Math.round(maxSize.width / aspectRatio),
    };
  }
  return {
    width: Math.round(maxSize.height * aspectRatio),
    height: maxSize.height,
  };
}

function getExpandImageAxisExpandRatio(maxOffset: number, imageSide: number) {
  if (maxOffset <= 0 || imageSide <= 0) {
    return 0;
  }
  return maxOffset / imageSide;
}

function isExpandImageAxisWithinLimit(maxOffset: number, imageSide: number) {
  return getExpandImageAxisExpandRatio(maxOffset, imageSide) <= EXPAND_IMAGE_MAX_AXIS_RATIO + EXPAND_IMAGE_RATIO_EPSILON;
}

function isExpandImageLayoutWithinLimit(layout: ExpandImagePreviewLayout) {
  return isExpandImageAxisWithinLimit(layout.maxOffsetX, layout.imageSize.width)
    && isExpandImageAxisWithinLimit(layout.maxOffsetY, layout.imageSize.height);
}

function getExpandImageAxisPositionBounds(maxOffset: number, imageSide: number) {
  const totalExpandRatio = getExpandImageAxisExpandRatio(maxOffset, imageSide);
  if (totalExpandRatio <= 0) {
    return { min: 0.5, max: 0.5 };
  }
  if (totalExpandRatio > EXPAND_IMAGE_MAX_AXIS_RATIO + EXPAND_IMAGE_RATIO_EPSILON) {
    return { min: 0.5, max: 0.5 };
  }

  const maxPosition = Math.min(1, EXPAND_IMAGE_MAX_DIRECTION_RATIO / totalExpandRatio);
  const minPosition = Math.max(0, 1 - maxPosition);
  return { min: minPosition, max: maxPosition };
}

function clampExpandImagePositionForLayout(position: ExpandImagePosition, layout: ExpandImagePreviewLayout): ExpandImagePosition {
  const xBounds = getExpandImageAxisPositionBounds(layout.maxOffsetX, layout.imageSize.width);
  const yBounds = getExpandImageAxisPositionBounds(layout.maxOffsetY, layout.imageSize.height);
  return {
    x: clampNumber(position.x, xBounds.min, xBounds.max),
    y: clampNumber(position.y, yBounds.min, yBounds.max),
  };
}

/**
 * 后端 extraTaskParams 要的是原图四边外扩比例。
 * 这里用“图片在目标框内的位置”反推 top/left/right/bottom，拖到边缘时对应边就是 0。
 */
function getExpandImageExtraTaskParams(layout: ExpandImagePreviewLayout, position: ExpandImagePosition): ExpandImageInsets {
  const clampedPosition = clampExpandImagePositionForLayout(position, layout);
  const left = layout.maxOffsetX * clampedPosition.x;
  const top = layout.maxOffsetY * clampedPosition.y;
  return {
    top: clampExpandImageInsetRatio(top / layout.imageSize.height),
    left: clampExpandImageInsetRatio(left / layout.imageSize.width),
    right: clampExpandImageInsetRatio((layout.maxOffsetX - left) / layout.imageSize.width),
    bottom: clampExpandImageInsetRatio((layout.maxOffsetY - top) / layout.imageSize.height),
  };
}

function getGreatestCommonDivisor(a: number, b: number): number {
  let nextA = Math.abs(Math.round(a));
  let nextB = Math.abs(Math.round(b));
  while (nextB > 0) {
    const remainder = nextA % nextB;
    nextA = nextB;
    nextB = remainder;
  }
  return nextA || 1;
}

function getExpandImageNaturalRatioLabel(imageNaturalSize: ExpandImageSize | null) {
  if (imageNaturalSize == null) return "1:1";
  const divisor = getGreatestCommonDivisor(imageNaturalSize.width, imageNaturalSize.height);
  return `${String(Math.round(imageNaturalSize.width / divisor))}:${String(Math.round(imageNaturalSize.height / divisor))}`;
}

function getExpandImageTaskRatio(ratio: ExpandImageRatio, originalRatio: string, imageNaturalSize: ExpandImageSize | null) {
  if (ratio !== "original") return ratio;
  const trimmedOriginalRatio = originalRatio.trim();
  return trimmedOriginalRatio || getExpandImageNaturalRatioLabel(imageNaturalSize);
}

function buildExpandImageModelPayload(model: AiModelItem | null): Record<string, unknown> {
  const payload = buildGenTaskModelPayload(model);
  const platform = (model?.platform ?? "GATEWAY").trim();
  if (platform) {
    payload.aiPlatform = platform;
  }
  return payload;
}

function buildExpandImageTaskPayload(params: {
  model: AiModelItem | null;
  image: string;
  ratio: string;
  chatPrompt: string;
  extraTaskParams?: ExpandImageInsets;
}): Record<string, unknown> | null {
  const image = params.image.trim();
  const code = (params.model?.code ?? "").trim();
  const modelId = params.model?.id;
  if (!image || !code || modelId == null || String(modelId).trim() === "") return null;
  const modelPayload = buildExpandImageModelPayload(params.model);
  if (Object.keys(modelPayload).length === 0) return null;

  return {
    type: EXPAND_IMAGE_TASK_TYPE,
    subType: EXPAND_IMAGE_SUB_TYPE,
    ...modelPayload,
    ratio: params.ratio,
    resolution: EXPAND_IMAGE_RESOLUTION,
    batchSize: EXPAND_IMAGE_BATCH_SIZE,
    chatPrompt: params.chatPrompt,
    images: [image],
    ...(params.extraTaskParams ? { extraTaskParams: params.extraTaskParams } : {}),
  };
}

/**
 * 预览框只负责展示“最终画布”和“原图可拖动位置”。
 * 原比例走倍率放大；固定比例走目标比例框，按短边不动来推导需要扩展的长边。
 */
function getExpandImagePreviewLayout(ratio: ExpandImageRatio, scale: ExpandImageScale, imageNaturalSize: ExpandImageSize | null): ExpandImagePreviewLayout {
  const naturalWidth = Math.max(1, imageNaturalSize?.width ?? 82);
  const naturalHeight = Math.max(1, imageNaturalSize?.height ?? 122);
  const naturalAspectRatio = naturalWidth / naturalHeight;

  if (ratio === "original") {
    const multiplier = EXPAND_IMAGE_SCALE_MULTIPLIERS[scale];
    const imageSize = fitExpandImageSize(naturalAspectRatio, EXPAND_IMAGE_ORIGINAL_IMAGE_MAX_SIZE);
    const width = Math.round(imageSize.width * multiplier);
    const height = Math.round(imageSize.height * multiplier);
    const top = Math.max(2, Math.round(EXPAND_IMAGE_ORIGINAL_FRAME_TOP - (height - EXPAND_IMAGE_ORIGINAL_IMAGE_MAX_SIZE.height * 1.5) / 2));
    return {
      frameStyle: { width, height, top },
      imageStyle: imageSize,
      imageSize,
      maxOffsetX: Math.max(0, width - imageSize.width),
      maxOffsetY: Math.max(0, height - imageSize.height),
      widthMultiplier: multiplier,
      heightMultiplier: multiplier,
    };
  }

  const targetAspectRatio = EXPAND_IMAGE_RATIO_ASPECT_MAP[ratio];
  let widthMultiplier = 1;
  let heightMultiplier = 1;
  if (targetAspectRatio > naturalAspectRatio) {
    widthMultiplier = targetAspectRatio / naturalAspectRatio;
  } else {
    heightMultiplier = naturalAspectRatio / targetAspectRatio;
  }

  const frameSize = fitExpandImageSize(targetAspectRatio, EXPAND_IMAGE_FIXED_RATIO_MAX_SIZE);
  const displayImageWidth = frameSize.width / widthMultiplier;
  const displayImageHeight = frameSize.height / heightMultiplier;

  return {
    frameStyle: {
      width: Math.round(frameSize.width),
      height: Math.round(frameSize.height),
      top: Math.round((EXPAND_IMAGE_PREVIEW_HEIGHT - frameSize.height) / 2),
    },
    imageStyle: {
      width: Math.round(displayImageWidth),
      height: Math.round(displayImageHeight),
    },
    imageSize: {
      width: Math.round(displayImageWidth),
      height: Math.round(displayImageHeight),
    },
    maxOffsetX: Math.max(0, Math.round(frameSize.width - displayImageWidth)),
    maxOffsetY: Math.max(0, Math.round(frameSize.height - displayImageHeight)),
    widthMultiplier,
    heightMultiplier,
  };
}

function isExpandImageScaleAvailable(scale: ExpandImageScale, imageNaturalSize: ExpandImageSize | null) {
  return isExpandImageLayoutWithinLimit(getExpandImagePreviewLayout("original", scale, imageNaturalSize));
}

function isExpandImageRatioAvailable(ratio: ExpandImageRatio, imageNaturalSize: ExpandImageSize | null) {
  if (ratio === "original") return true;
  if (imageNaturalSize == null) return false;
  return isExpandImageLayoutWithinLimit(getExpandImagePreviewLayout(ratio, "1.5x", imageNaturalSize));
}

export function AssetExpandImageModal({ visible, imageUrl, originalRatio = "", onClose }: AssetExpandImageModalProps) {
  const { t } = useTranslation();
  const setUser = useUserStore((s) => s.setUser);
  const createTaskInFlightRef = useRef(false);
  const dragStateRef = useRef<ExpandImageDragState | null>(null);
  const [scale, setScale] = useState<ExpandImageScale>("1.5x");
  const [ratio, setRatio] = useState<ExpandImageRatio>("original");
  const [prompt, setPrompt] = useState("");
  const [imageNaturalSize, setImageNaturalSize] = useState<ExpandImageSize | null>(null);
  const [imagePosition, setImagePosition] = useState<ExpandImagePosition>({ x: 0.5, y: 0.5 });
  const [isDraggingImage, setIsDraggingImage] = useState(false);
  const [creating, setCreating] = useState(false);
  const [activeModel, setActiveModel] = useState<AiModelItem | null>(null);
  const [modelsLoading, setModelsLoading] = useState(false);

  const isOriginalRatio = ratio === "original";
  const previewLayout = useMemo(() => getExpandImagePreviewLayout(ratio, scale, imageNaturalSize), [imageNaturalSize, ratio, scale]);
  const selectedLayoutAvailable = isExpandImageLayoutWithinLimit(previewLayout);
  const boundedImagePosition = useMemo(() => clampExpandImagePositionForLayout(imagePosition, previewLayout), [imagePosition, previewLayout]);
  const positionedImageStyle = useMemo<CSSProperties>(() => ({
    ...previewLayout.imageStyle,
    left: Math.round(previewLayout.maxOffsetX * boundedImagePosition.x),
    top: Math.round(previewLayout.maxOffsetY * boundedImagePosition.y),
  }), [boundedImagePosition, previewLayout]);
  const canDragImage = previewLayout.maxOffsetX > 0 || previewLayout.maxOffsetY > 0;
  const taskRatio = useMemo(() => getExpandImageTaskRatio(ratio, originalRatio, imageNaturalSize), [imageNaturalSize, originalRatio, ratio]);
  const resolvedPrompt = useMemo(() => prompt.trim() || t("pages.assetDetails.expandImage.defaultPrompt"), [prompt, t]);
  const costPayload = useMemo(
    () => visible ? buildExpandImageTaskPayload({
      model: activeModel,
      image: imageUrl,
      ratio: taskRatio,
      chatPrompt: resolvedPrompt,
    }) ?? undefined : undefined,
    [activeModel, imageUrl, resolvedPrompt, taskRatio, visible],
  );
  const costResult = useGenerateCost({
    activeModelItem: visible ? activeModel : null,
    selectedParamValues: EXPAND_IMAGE_COST_PARAM_VALUES,
    taskPayload: costPayload,
    activeTaskSubType: EXPAND_IMAGE_SUB_TYPE,
    chatPrompt: resolvedPrompt,
  });
  const createCost = costResult.cost;
  const modelReady = !modelsLoading && activeModel != null && costPayload != null;
  const canCreateExpandImage = modelReady && imageNaturalSize != null && selectedLayoutAvailable;
  const scaleHintText = isOriginalRatio
    ? scale
    : t("pages.assetDetails.expandImage.sizeHint", {
      width: formatExpandImageMultiplier(previewLayout.widthMultiplier),
      height: formatExpandImageMultiplier(previewLayout.heightMultiplier),
    });

  const resetImageDragState = () => {
    setImagePosition({ x: 0.5, y: 0.5 });
    dragStateRef.current = null;
    setIsDraggingImage(false);
  };

  const handleScaleChange = (nextScale: ExpandImageScale) => {
    if (nextScale === scale) return;
    if (!isExpandImageScaleAvailable(nextScale, imageNaturalSize)) return;
    resetImageDragState();
    setScale(nextScale);
  };

  const handleRatioChange = (nextRatio: ExpandImageRatio) => {
    if (nextRatio === ratio) return;
    if (!isExpandImageRatioAvailable(nextRatio, imageNaturalSize)) return;
    resetImageDragState();
    setRatio(nextRatio);
  };

  useEffect(() => {
    setScale("1.5x");
    setRatio("original");
    setPrompt("");
    setImageNaturalSize(null);
    resetImageDragState();
  }, [imageUrl]);

  useEffect(() => {
    setImagePosition({ x: 0.5, y: 0.5 });
    dragStateRef.current = null;
    setIsDraggingImage(false);
  }, [imageUrl, ratio, scale]);

  useEffect(() => {
    if (!visible) return;

    let cancelled = false;
    setActiveModel(null);
    setModelsLoading(true);
    // 模型列表属于扩图弹窗内部字段，父级不需要知道具体模型结构。
    void getAiModelList({ subType: EXPAND_IMAGE_SUB_TYPE })
      .then((res) => {
        if (cancelled) return;
        const list = ((res as { data?: unknown[] }).data ?? []).map((item) => normalizeAiModelItem(item as AiModelItem));
        setActiveModel(list[0] ?? null);
      })
      .catch(() => {
        if (!cancelled) {
          setActiveModel(null);
        }
      })
      .finally(() => {
        if (!cancelled) setModelsLoading(false);
      });

    return () => {
      cancelled = true;
    };
  }, [visible]);

  const handleImagePointerDown = (event: ReactPointerEvent<HTMLDivElement>) => {
    if (!canDragImage || creating || !selectedLayoutAvailable) return;
    event.preventDefault();
    const clampedPosition = clampExpandImagePositionForLayout(imagePosition, previewLayout);
    dragStateRef.current = {
      pointerId: event.pointerId,
      startClientX: event.clientX,
      startClientY: event.clientY,
      startPosition: clampedPosition,
      maxOffsetX: previewLayout.maxOffsetX,
      maxOffsetY: previewLayout.maxOffsetY,
    };
    setImagePosition(clampedPosition);
    setIsDraggingImage(true);
    event.currentTarget.setPointerCapture(event.pointerId);
  };

  const handleImagePointerMove = (event: ReactPointerEvent<HTMLDivElement>) => {
    const dragState = dragStateRef.current;
    if (dragState == null || dragState.pointerId !== event.pointerId) return;
    event.preventDefault();
    const nextPosition = {
      x: dragState.maxOffsetX > 0
        ? (dragState.startPosition.x * dragState.maxOffsetX + event.clientX - dragState.startClientX) / dragState.maxOffsetX
        : 0.5,
      y: dragState.maxOffsetY > 0
        ? (dragState.startPosition.y * dragState.maxOffsetY + event.clientY - dragState.startClientY) / dragState.maxOffsetY
        : 0.5,
    };
    setImagePosition(clampExpandImagePositionForLayout(nextPosition, previewLayout));
  };

  const finishImageDrag = (event: ReactPointerEvent<HTMLDivElement>) => {
    const dragState = dragStateRef.current;
    if (dragState == null || dragState.pointerId !== event.pointerId) return;
    dragStateRef.current = null;
    setIsDraggingImage(false);
    if (event.currentTarget.hasPointerCapture(event.pointerId)) {
      event.currentTarget.releasePointerCapture(event.pointerId);
    }
  };

  const handleCreate = async () => {
    if (createTaskInFlightRef.current || creating || !canCreateExpandImage) return;
    const payload = buildExpandImageTaskPayload({
      model: activeModel,
      image: imageUrl,
      ratio: taskRatio,
      chatPrompt: resolvedPrompt,
      extraTaskParams: getExpandImageExtraTaskParams(previewLayout, boundedImagePosition),
    });
    if (payload == null) {
      Message.error(t("pages.assetDetails.expandImage.modelRequired"));
      return;
    }
    createTaskInFlightRef.current = true;
    setCreating(true);
    try {
      if (!(await ensureSufficientPoints(createCost))) return;
      Message.success(t("composer.taskCreating"));
      await createGenTask(payload);
      onClose();
      void setUser();
      const href = `${ROUTES.CREATE_MY_CREATIONS}?type=1&${MY_CREATIONS_REFRESH_QUERY}=${String(Date.now())}`;
      globalThis.location.assign(href);
      return;
    } catch {
      /* request interceptor shows the error. */
    } finally {
      createTaskInFlightRef.current = false;
      setCreating(false);
    }
  };

  if (!visible) return null;

  return (
    <Modal visible={visible} title={null} footer={null} closable={false} maskClosable escToExit onCancel={onClose} alignCenter unmountOnExit className="assetDetailsExpandImageModal" style={{ width: 838, padding: 0 }}>
      <div className="assetDetailsExpandImageModal__content">
        <button type="button" className="assetDetailsExpandImageModal__close" onClick={onClose} aria-label={t("button.cancel")}>
          <img src={new URL("/src/assets/images/ic_close.png", import.meta.url).href} alt="" />
        </button>
        <h3 className="assetDetailsExpandImageModal__title">{t("pages.assetDetails.expandImage.title")}</h3>

        <div className="assetDetailsExpandImageModal__preview">
          <span className="assetDetailsExpandImageModal__scaleHint">{scaleHintText}</span>
          <div className="assetDetailsExpandImageModal__selection" style={previewLayout.frameStyle} aria-hidden="true">
            <span className="assetDetailsExpandImageModal__selectionHandle assetDetailsExpandImageModal__selectionHandle_topLeft" />
            <span className="assetDetailsExpandImageModal__selectionHandle assetDetailsExpandImageModal__selectionHandle_topCenter" />
            <span className="assetDetailsExpandImageModal__selectionHandle assetDetailsExpandImageModal__selectionHandle_topRight" />
            <span className="assetDetailsExpandImageModal__selectionHandle assetDetailsExpandImageModal__selectionHandle_rightCenter" />
            <span className="assetDetailsExpandImageModal__selectionHandle assetDetailsExpandImageModal__selectionHandle_bottomRight" />
            <span className="assetDetailsExpandImageModal__selectionHandle assetDetailsExpandImageModal__selectionHandle_bottomCenter" />
            <span className="assetDetailsExpandImageModal__selectionHandle assetDetailsExpandImageModal__selectionHandle_bottomLeft" />
            <span className="assetDetailsExpandImageModal__selectionHandle assetDetailsExpandImageModal__selectionHandle_leftCenter" />
            <div
              className={[
                "assetDetailsExpandImageModal__selectionImageWrap",
                canDragImage ? "assetDetailsExpandImageModal__selectionImageWrap_draggable" : "",
                isDraggingImage ? "assetDetailsExpandImageModal__selectionImageWrap_dragging" : "",
              ].filter(Boolean).join(" ")}
              style={positionedImageStyle}
              onPointerDown={handleImagePointerDown}
              onPointerMove={handleImagePointerMove}
              onPointerUp={finishImageDrag}
              onPointerCancel={finishImageDrag}
            >
              <img
                className="assetDetailsExpandImageModal__selectionImage"
                src={imageUrl}
                alt=""
                onLoad={(event) => {
                  const image = event.currentTarget;
                  setImageNaturalSize({
                    width: image.naturalWidth || image.width,
                    height: image.naturalHeight || image.height,
                  });
                }}
              />
            </div>
          </div>
          {isOriginalRatio ? (
            <div className="assetDetailsExpandImageModal__scaleControl" role="radiogroup" aria-label={t("pages.assetDetails.expandImage.scaleAria")}>
              {EXPAND_IMAGE_SCALE_OPTIONS.map((scaleOption) => (
                <button
                  key={scaleOption}
                  type="button"
                  className={[
                    "assetDetailsExpandImageModal__scaleButton",
                    scaleOption === scale ? "assetDetailsExpandImageModal__scaleButton_active" : "",
                  ].filter(Boolean).join(" ")}
                  role="radio"
                  aria-checked={scaleOption === scale}
                  aria-label={t("pages.assetDetails.expandImage.scaleOptionAria", { scale: scaleOption })}
                  disabled={!isExpandImageScaleAvailable(scaleOption, imageNaturalSize)}
                  onClick={() => {
                    handleScaleChange(scaleOption);
                  }}
                >
                  {scaleOption}
                </button>
              ))}
            </div>
          ) : null}
        </div>

        <div className="assetDetailsExpandImageModal__ratioBar" role="radiogroup" aria-label={t("pages.assetDetails.expandImage.ratioAria")}>
          {EXPAND_IMAGE_RATIO_OPTIONS.map((ratioOption) => {
            const ratioLabel = ratioOption.value === "original" ? t("pages.assetDetails.expandImage.ratioOriginal") : ratioOption.value;
            const ratioDisabled = !isExpandImageRatioAvailable(ratioOption.value, imageNaturalSize);
            return (
              <button
                key={ratioOption.value}
                type="button"
                className={[
                  "assetDetailsExpandImageModal__ratioButton",
                  ratioOption.value === ratio ? "assetDetailsExpandImageModal__ratioButton_active" : "",
                ].filter(Boolean).join(" ")}
                role="radio"
                aria-checked={ratioOption.value === ratio}
                aria-label={t("pages.assetDetails.expandImage.ratioOptionAria", { ratio: ratioLabel })}
                disabled={ratioDisabled}
                onClick={() => {
                  handleRatioChange(ratioOption.value);
                }}
              >
                <span className={["assetDetailsExpandImageModal__ratioIcon", ratioOption.iconClassName].join(" ")} aria-hidden="true" />
                <span className="assetDetailsExpandImageModal__ratioLabel">{ratioLabel}</span>
              </button>
            );
          })}
        </div>

        <div className="assetDetailsExpandImageModal__promptBlock">
          <input
            className="assetDetailsExpandImageModal__promptInput"
            value={prompt}
            placeholder={t("pages.assetDetails.expandImage.promptPlaceholder")}
            onChange={(event) => {
              setPrompt(event.target.value);
            }}
            aria-label={t("pages.assetDetails.expandImage.promptAria")}
          />
          <button
            type="button"
            className="assetDetailsExpandImageModal__createButton"
            disabled={creating || !canCreateExpandImage}
            aria-busy={creating}
            aria-label={t("pages.assetDetails.expandImage.createAria", { credits: createCost })}
            onClick={() => { void handleCreate(); }}
          >
            <img className="assetDetailsExpandImageModal__createButtonIcon" src={new URL("/src/assets/images/ic_stars_white.png", import.meta.url).href} alt="" />
            <span className="assetDetailsExpandImageModal__createButtonText">{createCost}</span>
          </button>
        </div>
      </div>
    </Modal>
  );
}
