"use client";

import { useCallback, useEffect, useRef, useState } from "react";
import { CloudUploadOutlined } from "@ant-design/icons";
import { Position, NodeProps, Node } from "@xyflow/react";
import { Spin } from "antd";
import { BaseNode } from "./BaseNode";
import { NodeHandle } from "./NodeHandle";
import { useWorkflowStore } from "@/store/workflowStore";
import { useNodeInlinePanelStore } from "@/store/nodeInlinePanelStore";
import { ImageInputNodeData } from "@/types";
import { useAdaptiveImageSrc } from "@/hooks/useAdaptiveImageSrc";
import { downloadMedia } from "@/utils/downloadMedia";
import { useI18n } from "@/i18n";
import { MediaPreviewModal } from "@/components/MediaPreviewModal";
import { SINGLE_INPUT_HANDLE_ID, SINGLE_OUTPUT_HANDLE_ID } from "@/utils/nodeHandles";
import { AssetPickerModal, PopiAssetItem } from "@/components/AssetPickerModal";
import { defaultNodeDimensions } from "@/constants/nodeDimensions";
import { uploadFileToGatewayMedia } from "@/utils/gatewayMediaUpload";
import { getPopiImageAssetPreview, resolvePopiImageAsset } from "@/lib/popiAssetApi";
import { useSelectedNodeCount } from "@/hooks/useSelectedNodeCount";
import { NodeActionCapsule, type NodeActionCapsuleAction } from "./NodeActionCapsule";
import { SaveToAssetLibraryModal } from "@/components/SaveToAssetLibraryModal";
import { MediaCropOverlay, type MediaCropAspectRatio, type MediaCropConfirmPayload } from "@/components/media/MediaCropOverlay";
import { CropRatioSelect, MEDIA_EDIT_TOOL, MediaEditToolbar, type MediaEditTool, type MediaEditToolOption } from "@/components/media/MediaEditToolbar";
import { SplitGridCapsuleMenu, type SplitGridSelection } from "@/components/media/SplitGridCapsuleMenu";
import { createPendingDerivedImageNode } from "@/utils/derivedImageNodes";
import { createSplitGridImageNodes } from "@/utils/splitGridNodes";
import { SplitGridSelectionOverlay } from "@/components/media/SplitGridSelectionOverlay";
import { SplitGridSelectionToolbar } from "@/components/media/SplitGridSelectionToolbar";
import { getAutoMediaElementClassName, getAutoMediaFrameClassName } from "./mediaAutoSize";

type ImageInputNodeType = Node<ImageInputNodeData, "imageInput">;
const IMAGE_INPUT_DIMENSIONS = defaultNodeDimensions.imageInput;
const IMAGE_DIMENSION_TIMEOUT_MS = 10_000;
const IMAGE_LOAD_RETRY_DELAYS_MS = [600, 1600];
const IMAGE_MEDIA_EDIT_TOOLS: MediaEditToolOption[] = [
  { key: MEDIA_EDIT_TOOL.crop, label: "裁剪" },
];

export interface ImageInputNodeViewProps {
  id: string;
  data: ImageInputNodeData;
  selected?: boolean;
  renderInputHandle?: boolean;
}

function EmptyImageIcon() {
  return (
    <svg className="w-8 h-8 text-[var(--text-disabled)]" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={1.5} aria-hidden="true">
      <rect x="3" y="5" width="18" height="14" rx="2" />
      <path strokeLinecap="round" strokeLinejoin="round" d="m3 16 5-5 4.5 4.5L15 13l6 6" />
      <circle cx="15.5" cy="9.5" r="1.5" />
    </svg>
  );
}

type ImageToolbarIconKind =
  | "download"
  | "focus"
  | "delete"
  | "upload"
  | "assets"
  | "crop"
  | "grid"
  | "multiAngle";

function ImageToolbarIcon({ kind }: { kind: ImageToolbarIconKind }) {
  const commonProps = {
    className: "h-6 w-6",
    fill: "none",
    viewBox: "0 0 24 24",
    stroke: "currentColor",
    strokeWidth: 1.8,
  };

  switch (kind) {
    case "download":
      return <svg {...commonProps}><path strokeLinecap="round" strokeLinejoin="round" d="M12 4v11m0 0 4-4m-4 4-4-4M4 17v2h16v-2" /></svg>;
    case "focus":
      return <svg {...commonProps}><path strokeLinecap="round" strokeLinejoin="round" d="M8 4H4v4M16 4h4v4M8 20H4v-4M20 16v4h-4" /></svg>;
    case "delete":
      return <svg {...commonProps}><path strokeLinecap="round" strokeLinejoin="round" d="M6 7h12M9 7V5h6v2m-8 0 1 13h8l1-13" /></svg>;
    case "upload":
      return <svg {...commonProps}><path strokeLinecap="round" strokeLinejoin="round" d="M12 4v11m0-11 4 4m-4-4-4 4M4 17v2h16v-2" /></svg>;
    case "assets":
      return <svg {...commonProps}><path strokeLinecap="round" strokeLinejoin="round" d="M4 6.5A2.5 2.5 0 0 1 6.5 4h11A2.5 2.5 0 0 1 20 6.5v11a2.5 2.5 0 0 1-2.5 2.5h-11A2.5 2.5 0 0 1 4 17.5z" /><path strokeLinecap="round" strokeLinejoin="round" d="M8 8h8M8 12h8M8 16h5" /></svg>;
    case "crop":
      return <svg {...commonProps}><path strokeLinecap="round" strokeLinejoin="round" d="M6 3v12a3 3 0 0 0 3 3h12" /><path strokeLinecap="round" strokeLinejoin="round" d="M3 6h12a3 3 0 0 1 3 3v12" /></svg>;
    case "grid":
      return <svg {...commonProps}><rect x="4" y="4" width="16" height="16" rx="2" /><path strokeLinecap="round" d="M9.33 4v16M14.67 4v16M4 9.33h16M4 14.67h16" /></svg>;
    case "multiAngle":
      return <svg {...commonProps}><path strokeLinecap="round" strokeLinejoin="round" d="M4 12a8 8 0 0 1 16 0" /><path strokeLinecap="round" strokeLinejoin="round" d="M8 12a4 4 0 0 1 8 0" /><path strokeLinecap="round" strokeLinejoin="round" d="M12 12l5-5M12 12l-5 5" /><circle cx="12" cy="12" r="1.5" /></svg>;
  }
}

export function ImageInputNodeView({ id, data, selected, renderInputHandle = false }: ImageInputNodeViewProps) {
  const { t } = useI18n();
  const nodeData = data;
  const adaptiveImage = useAdaptiveImageSrc(nodeData.image, id);
  const preferredDisplayImage = nodeData.previewImage || adaptiveImage || nodeData.image;
  const updateNodeData = useWorkflowStore((state) => state.updateNodeData);
  const updateMediaNodeData = useWorkflowStore((state) => state.updateMediaNodeData);
  const addNode = useWorkflowStore((state) => state.addNode);
  const onConnect = useWorkflowStore((state) => state.onConnect);
  const getNodeById = useWorkflowStore((state) => state.getNodeById);
  const regenerateNode = useWorkflowStore((state) => state.regenerateNode);
  const selectSingleNode = useWorkflowStore((state) => state.selectSingleNode);
  const openInlinePanel = useNodeInlinePanelStore((state) => state.openPanel);
  const fileInputRef = useRef<HTMLInputElement>(null);
  const imageRef = useRef<HTMLImageElement>(null);
  const [isPreviewOpen, setIsPreviewOpen] = useState(false);
  const [isAssetPickerOpen, setIsAssetPickerOpen] = useState(false);
  const [isSaveToAssetLibraryOpen, setIsSaveToAssetLibraryOpen] = useState(false);
  const [isCropping, setIsCropping] = useState(false);
  const [activeMediaTool, setActiveMediaTool] = useState<MediaEditTool>(MEDIA_EDIT_TOOL.crop);
  const [cropAspectRatio, setCropAspectRatio] = useState<MediaCropAspectRatio>("original");
  const [cropPayload, setCropPayload] = useState<MediaCropConfirmPayload | null>(null);
  const [isSplitGridRunning, setIsSplitGridRunning] = useState(false);
  const [gridSelection, setGridSelection] = useState<SplitGridSelection | null>(null);
  const [selectedGridCellKeys, setSelectedGridCellKeys] = useState<Set<string>>(() => new Set());
  const [imageRetryNonce, setImageRetryNonce] = useState(0);
  const [displayFallbackImage, setDisplayFallbackImage] = useState<string | null>(null);
  const [imageLoadFailed, setImageLoadFailed] = useState(false);
  const isUploading = nodeData.uploadStatus === "loading";
  const displayImage = displayFallbackImage || preferredDisplayImage;
  const imageSrc = isCropping ? nodeData.image || displayImage : displayImage;

  useEffect(() => {
    setDisplayFallbackImage(null);
    setImageLoadFailed(false);
    setImageRetryNonce(0);
  }, [preferredDisplayImage]);

  const handleFileChange = useCallback(
    (e: React.ChangeEvent<HTMLInputElement>) => {
      const file = e.target.files?.[0];
      e.target.value = "";
      if (!file) return;

      if (!file.type.match(/^image\/(png|jpeg|webp)$/)) {
        alert(t("upload.imageUnsupported"));
        return;
      }

      // if (file.size > 10 * 1024 * 1024) {
      //   alert(t("upload.imageTooLarge"));
      //   return;
      // }

      updateNodeData(id, { uploadStatus: "loading", uploadError: null });

      const uploadImage = (dimensions: { width: number; height: number } | null) => {
        uploadFileToGatewayMedia(file, "image")
          .then((uploaded) => {
            updateMediaNodeData(id, {
              image: uploaded.url,
              imageRef: undefined,
              assetId: undefined,
              previewImage: uploaded.previewUrl,
              assetDetailLoading: false,
              assetDetailError: null,
              uploadStatus: "idle",
              uploadError: null,
              filename: uploaded.filename,
              dimensions,
            }, dimensions);
          })
          .catch((error) => {
            const message = error instanceof Error ? error.message : "Media upload failed";
            updateNodeData(id, { uploadStatus: "idle", uploadError: message });
            alert(message);
          });
      };

      const objectUrl = URL.createObjectURL(file);
      const img = new Image();
      let settled = false;
      let timeout: number;
      const finishDimensionRead = (dimensions: { width: number; height: number } | null) => {
        if (settled) return;
        settled = true;
        clearTimeout(timeout);
        URL.revokeObjectURL(objectUrl);
        uploadImage(dimensions);
      };
      timeout = window.setTimeout(() => {
        finishDimensionRead(null);
      }, IMAGE_DIMENSION_TIMEOUT_MS);
      img.onload = () => {
        finishDimensionRead({ width: img.width, height: img.height });
      };
      img.onerror = () => {
        finishDimensionRead(null);
      };
      img.src = objectUrl;
    },
    [id, t, updateMediaNodeData, updateNodeData]
  );

  const handleDrop = useCallback(
    (e: React.DragEvent) => {
      e.preventDefault();
      e.stopPropagation();

      const file = e.dataTransfer.files?.[0];
      if (!file) return;

      const dt = new DataTransfer();
      dt.items.add(file);
      if (fileInputRef.current) {
        fileInputRef.current.files = dt.files;
        fileInputRef.current.dispatchEvent(new Event("change", { bubbles: true }));
      }
    },
    []
  );

  const handleDragOver = useCallback((e: React.DragEvent) => {
    e.preventDefault();
    e.stopPropagation();
  }, []);

  const handleOpenFileDialog = useCallback(() => {
    if (isUploading) return;
    fileInputRef.current?.click();
  }, [isUploading]);

  const handleSelect = useCallback(() => {
    selectSingleNode(id);
  }, [id, selectSingleNode]);

  const handleRemove = useCallback(() => {
    updateNodeData(id, {
      image: null,
      imageRef: undefined,
      assetId: undefined,
      previewImage: undefined,
      assetDetailLoading: false,
      assetDetailError: null,
      filename: null,
      dimensions: null,
      derivedImage: undefined,
      status: "idle",
      error: null,
    });
  }, [id, updateNodeData]);

  const handleConfirmCrop = useCallback(async (payload: MediaCropConfirmPayload) => {
    const currentNode = getNodeById(id);
    if (!currentNode) return;

    const currentPosition = currentNode.position ?? { x: 0, y: 0 };
    const currentWidth = typeof currentNode.width === "number"
      ? currentNode.width
      : typeof currentNode.style?.width === "number"
        ? currentNode.style.width
        : IMAGE_INPUT_DIMENSIONS.width;
    const filename = `crop-${Date.now()}.png`;
    const nodeId = createPendingDerivedImageNode({
      sourceNodeId: id,
      position: {
        x: currentPosition.x + currentWidth + 220,
        y: currentPosition.y,
      },
      filename,
      customTitle: t("imageInput.crop"),
      operation: "crop",
      task: {
        cropRect: payload.cropRect,
        outputWidth: payload.outputWidth,
        outputHeight: payload.outputHeight,
        aspectRatio: payload.aspectRatio,
      },
      addNode,
      onConnect,
      selectSingleNode,
    });
    setIsCropping(false);
    setCropPayload(null);
    void regenerateNode(nodeId);
  }, [addNode, getNodeById, id, onConnect, regenerateNode, selectSingleNode, t]);

  const handleStartGridSelection = useCallback((selection: SplitGridSelection) => {
    setIsCropping(false);
    setCropPayload(null);
    setGridSelection(selection);
    setSelectedGridCellKeys(new Set());
  }, []);

  const handleToggleGridCell = useCallback((cellKey: string) => {
    setSelectedGridCellKeys((current) => {
      const next = new Set(current);
      if (next.has(cellKey)) {
        next.delete(cellKey);
      } else {
        next.add(cellKey);
      }
      return next;
    });
  }, []);

  const handleCancelGridSelection = useCallback(() => {
    setGridSelection(null);
    setSelectedGridCellKeys(new Set());
  }, []);

  const handleConfirmSplitGrid = useCallback(async () => {
    const sourceNode = getNodeById(id);
    if (!sourceNode || !gridSelection || selectedGridCellKeys.size === 0) return;

    setIsSplitGridRunning(true);
    try {
      const createdNodeIds = createSplitGridImageNodes({
        sourceNode,
        rows: gridSelection.rows,
        cols: gridSelection.cols,
        selectedCellKeys: selectedGridCellKeys,
        addNode,
        onConnect,
        selectSingleNode,
      });
      handleCancelGridSelection();
      createdNodeIds.forEach((nodeId) => void regenerateNode(nodeId));
    } catch (error) {
      alert(t("workflow.splitGrid.failed", { error: error instanceof Error ? error.message : "Unknown error" }));
    } finally {
      setIsSplitGridRunning(false);
    }
  }, [addNode, getNodeById, gridSelection, handleCancelGridSelection, id, onConnect, regenerateNode, selectSingleNode, selectedGridCellKeys, t]);

  const handleSelectAsset = useCallback((asset: PopiAssetItem) => {
    const preview = getPopiImageAssetPreview(asset);
    if (!preview) return;

    updateMediaNodeData(id, {
      image: preview.imageUrl,
      imageRef: undefined,
      assetId: preview.assetId,
      previewImage: preview.previewImageUrl,
      assetDetailLoading: true,
      assetDetailError: null,
      filename: preview.filename,
      dimensions: preview.dimensions,
    }, preview.dimensions);
    setIsAssetPickerOpen(false);

    const applyResolvedAsset = (
      resolved: NonNullable<Awaited<ReturnType<typeof resolvePopiImageAsset>>>,
      dimensions: { width: number; height: number } | null
    ) => {
      const currentNode = useWorkflowStore.getState().getNodeById(id);
      const currentData = currentNode?.data as ImageInputNodeData | undefined;
      if (currentData?.assetId !== preview.assetId) return;

      updateMediaNodeData(id, {
        image: resolved.imageUrl,
        imageRef: undefined,
        assetId: resolved.assetId,
        previewImage: resolved.previewImageUrl,
        assetDetailLoading: false,
        assetDetailError: null,
        filename: resolved.filename,
        dimensions,
      }, dimensions);
    };

    void resolvePopiImageAsset(asset)
      .then((resolved) => {
        if (!resolved?.imageUrl) return;
        if (resolved.dimensions) {
          applyResolvedAsset(resolved, resolved.dimensions);
          return;
        }

        const img = new Image();
        img.onload = () => applyResolvedAsset(resolved, { width: img.width, height: img.height });
        img.onerror = () => applyResolvedAsset(resolved, preview.dimensions);
        img.src = resolved.imageUrl;
      })
      .catch((error) => {
        const currentNode = useWorkflowStore.getState().getNodeById(id);
        const currentData = currentNode?.data as ImageInputNodeData | undefined;
        if (currentData?.assetId !== preview.assetId) return;

        updateNodeData(id, {
          assetDetailLoading: false,
          assetDetailError: error instanceof Error ? error.message : "Failed to load asset detail",
        });
      });
  }, [id, updateMediaNodeData, updateNodeData]);

  const hasImage = Boolean(displayImage);
  const selectedNodeCount = useSelectedNodeCount();
  const showSelectedActions = Boolean(selected && selectedNodeCount === 1);
  const selectedActions: NodeActionCapsuleAction[] = hasImage
    ? ([
        {
          key: "crop",
          icon: <span className="[&_svg]:h-5 [&_svg]:w-5"><ImageToolbarIcon kind="crop" /></span>,
          label: t("imageInput.crop"),
          text: t("imageInput.crop"),
          section: "feature",
          onClick: (e) => {
            e.preventDefault();
            e.stopPropagation();
            setActiveMediaTool(MEDIA_EDIT_TOOL.crop);
            setCropAspectRatio("original");
            setCropPayload(null);
            setIsCropping(true);
          },
        },
        {
          key: "split-grid",
          icon: <span className="[&_svg]:h-5 [&_svg]:w-5"><ImageToolbarIcon kind="grid" /></span>,
          label: t("imageInput.splitGrid"),
          text: t("imageInput.splitGrid"),
          section: "feature",
          panel: ({ close }) => (
            <SplitGridCapsuleMenu
              busy={isSplitGridRunning}
              onSelect={(selection) => {
                close();
                handleStartGridSelection(selection);
              }}
            />
          ),
        },
        {
          key: "multi-angle",
          icon: <span className="[&_svg]:h-5 [&_svg]:w-5"><ImageToolbarIcon kind="multiAngle" /></span>,
          label: t("imageInput.multiAngle"),
          text: t("imageInput.multiAngle"),
          section: "feature",
          onClick: (e) => {
            e.preventDefault();
            e.stopPropagation();
            openInlinePanel(id, "multiAngle");
          },
        },
        {
          key: "save-to-assets",
          icon: <CloudUploadOutlined className="text-[24px]" />,
          label: t("assetLibrary.saveToLibrary"),
          section: "basic",
          onClick: (e) => {
            e.preventDefault();
            e.stopPropagation();
            if (nodeData.image) setIsSaveToAssetLibraryOpen(true);
          },
        },
        {
          key: "download",
          icon: <ImageToolbarIcon kind="download" />,
          label: t("imageInput.download"),
          section: "basic",
          onClick: (e) => {
            e.preventDefault();
            e.stopPropagation();
            downloadMedia(nodeData.image!, "image");
          },
        },
        {
          key: "preview",
          icon: <ImageToolbarIcon kind="focus" />,
          label: t("imageInput.focusPreview"),
          section: "basic",
          onClick: (e) => {
            e.preventDefault();
            e.stopPropagation();
            setIsPreviewOpen(true);
          },
        },
        {
          key: "remove",
          icon: <ImageToolbarIcon kind="delete" />,
          label: t("imageInput.remove"),
          section: "basic",
          tone: "danger",
          onClick: (e) => {
            e.preventDefault();
            e.stopPropagation();
            handleRemove();
          },
        },
      ] satisfies NodeActionCapsuleAction[])
    : [
        {
          key: "upload",
          icon: <ImageToolbarIcon kind="upload" />,
          label: t("imageInput.localUpload"),
          text: t("imageInput.localUpload"),
          onClick: (e) => {
            e.preventDefault();
            e.stopPropagation();
            selectSingleNode(id);
            handleOpenFileDialog();
          },
        },
        {
          key: "assets",
          icon: <ImageToolbarIcon kind="assets" />,
          label: t("imageInput.fromAssets"),
          text: t("imageInput.fromAssets"),
          onClick: (e) => {
            e.preventDefault();
            e.stopPropagation();
            selectSingleNode(id);
            setIsAssetPickerOpen(true);
          },
        },
      ];

  return (
    <BaseNode
      id={id}
      selected={selected}
      minWidth={IMAGE_INPUT_DIMENSIONS.width}
      minHeight={IMAGE_INPUT_DIMENSIONS.height}
      autoSizeContent={hasImage}
      contentClassName={hasImage ? "relative h-full w-full" : "flex-1 min-h-0"}
      fullBleed
      className={selected ? "!border-[var(--node-selected-border)] !ring-0" : ""}
    >
      <input
        ref={fileInputRef}
        type="file"
        accept="image/png,image/jpeg,image/webp"
        onChange={handleFileChange}
        className="hidden"
      />

      {showSelectedActions && (
        !isCropping && !gridSelection && <NodeActionCapsule actions={selectedActions} />
      )}

      {gridSelection && (
        <SplitGridSelectionToolbar
          selectedCount={selectedGridCellKeys.size}
          busy={isSplitGridRunning}
          onCancel={handleCancelGridSelection}
          onConfirm={() => void handleConfirmSplitGrid()}
        />
      )}

      {isCropping && (
        <MediaEditToolbar
          activeTool={activeMediaTool}
          tools={IMAGE_MEDIA_EDIT_TOOLS}
          busy={false}
          onCancel={() => setIsCropping(false)}
          context={{
            value: cropAspectRatio,
            onChange: setCropAspectRatio,
            onConfirm: () => {
              if (cropPayload) void handleConfirmCrop(cropPayload);
            },
          }}
          toolComponents={{
            [MEDIA_EDIT_TOOL.crop]: CropRatioSelect,
          }}
        />
      )}

      {nodeData.image ? (
        <div
          aria-label={`${nodeData.filename || t("imageInput.uploadedAlt")} preview`}
          onClick={handleSelect}
          className={getAutoMediaFrameClassName()}
        >
          <img
            ref={imageRef}
            key={`${displayImage ?? "empty"}-${imageRetryNonce}`}
            src={imageSrc ?? undefined}
            alt={nodeData.filename || t("imageInput.uploadedAlt")}
            onLoad={(event) => {
              setImageLoadFailed(false);
              const image = event.currentTarget;
              if (imageSrc === nodeData.image && image.naturalWidth > 0 && image.naturalHeight > 0) {
                const dimensions = { width: image.naturalWidth, height: image.naturalHeight };
                if (nodeData.dimensions?.width !== dimensions.width || nodeData.dimensions?.height !== dimensions.height) {
                  updateMediaNodeData(id, { dimensions }, dimensions);
                }
              }
            }}
            onError={() => {
              if (nodeData.previewImage && displayImage === nodeData.previewImage && nodeData.image && nodeData.image !== nodeData.previewImage) {
                setDisplayFallbackImage(nodeData.image);
                setImageLoadFailed(false);
                setImageRetryNonce(0);
                return;
              }

              const retryIndex = imageRetryNonce;
              const retryDelay = IMAGE_LOAD_RETRY_DELAYS_MS[retryIndex];
              if (retryDelay !== undefined && displayImage && /^https?:\/\//i.test(displayImage)) {
                window.setTimeout(() => {
                  setImageRetryNonce((current) => current === retryIndex ? current + 1 : current);
                }, retryDelay);
                return;
              }

              setImageLoadFailed(true);
            }}
            className={getAutoMediaElementClassName(nodeData.dimensions)}
          />
          {imageLoadFailed && (
            <div className="absolute inset-0 flex flex-col items-center justify-center gap-2 rounded-lg bg-neutral-900/80 p-4 text-center">
              <EmptyImageIcon />
              <span className="text-xs font-medium text-neutral-200">{t("imageInput.loadFailed")}</span>
              <span className="max-w-full truncate text-[10px] text-neutral-500">{nodeData.filename || nodeData.image}</span>
            </div>
          )}
          {nodeData.isOptional && (
            <span className="absolute bottom-2 left-2 text-[9px] font-medium text-neutral-300 bg-black/50 px-1.5 py-0.5 rounded">
              {t("common.optional")}
            </span>
          )}
          {isCropping && (
            <MediaCropOverlay
              kind="image"
              mediaElementRef={imageRef}
              naturalWidth={nodeData.dimensions?.width}
              naturalHeight={nodeData.dimensions?.height}
              aspectRatio={cropAspectRatio}
              busy={false}
              onCropChange={setCropPayload}
            />
          )}
          {gridSelection && (
            <SplitGridSelectionOverlay
              mediaElementRef={imageRef}
              naturalWidth={nodeData.dimensions?.width}
              naturalHeight={nodeData.dimensions?.height}
              rows={gridSelection.rows}
              cols={gridSelection.cols}
              selectedCellKeys={selectedGridCellKeys}
              busy={isSplitGridRunning}
              onToggleCell={handleToggleGridCell}
            />
          )}
        </div>
      ) : (
        <div className="flex h-full w-full items-center justify-center bg-[var(--surface-1)] p-8">
          <div
            role="button"
            tabIndex={0}
            aria-label={t("imageInput.upload")}
            onClick={(e) => {
              e.preventDefault();
              e.stopPropagation();
              selectSingleNode(id);
              handleOpenFileDialog();
            }}
            onKeyDown={(e) => {
              if (e.key === "Enter" || e.key === " ") {
                e.preventDefault();
                selectSingleNode(id);
                handleOpenFileDialog();
              }
            }}
            onDrop={handleDrop}
            onDragOver={handleDragOver}
            className="flex h-full min-h-28 w-full cursor-pointer flex-col items-center justify-center rounded-lg border-2 border-dashed border-[var(--border-subtle)] bg-[var(--surface-2)] text-center transition-colors hover:border-[var(--border-strong)] hover:bg-[var(--surface-hover)]"
          >
            <EmptyImageIcon />
            <span className="text-xs text-[var(--text-muted)] mt-2">{t("imageInput.dropImage")}</span>
            {nodeData.isOptional && <span className="mt-4 text-xs text-[var(--text-muted)]">{t("common.optional")}</span>}
          </div>
        </div>
      )}

      {isUploading && (
        <div className="nodrag nopan absolute inset-0 z-[10003] flex items-center justify-center rounded-lg bg-black/45 backdrop-blur-[1px]">
          <Spin />
        </div>
      )}

      {/* Handles rendered after visual content so they paint on top */}
      {renderInputHandle && (
        <NodeHandle
          type="target"
          position={Position.Left}
          id={SINGLE_INPUT_HANDLE_ID}
          handleType="generic-input"
          style={{ opacity: 0, zIndex: 10 }}
        />
      )}
      <NodeHandle
        type="source"
        position={Position.Right}
        id={SINGLE_OUTPUT_HANDLE_ID}
        handleType="image"
        data-tutorial="node-output-handle"
        style={{ opacity: 0, zIndex: 10 }}
      />

      {isPreviewOpen && (nodeData.image || displayImage) && (
        <MediaPreviewModal
          type="image"
          src={nodeData.image || displayImage || ""}
          downloadSrc={nodeData.image || displayImage || ""}
          alt={`${nodeData.filename || t("imageInput.uploadedAlt")} preview`}
          onClose={() => setIsPreviewOpen(false)}
        />
      )}
      {isAssetPickerOpen && (
        <AssetPickerModal
          kind="image"
          onClose={() => setIsAssetPickerOpen(false)}
          onSelect={handleSelectAsset}
        />
      )}
      <SaveToAssetLibraryModal
        open={isSaveToAssetLibraryOpen}
        mediaUrl={nodeData.image || null}
        onClose={() => setIsSaveToAssetLibraryOpen(false)}
      />
    </BaseNode>
  );
}

export function ImageInputNode({ id, data, selected }: NodeProps<ImageInputNodeType>) {
  return <ImageInputNodeView id={id} data={data} selected={selected} />;
}
