"use client";

import {
  useState,
  useCallback,
  useEffect,
  useRef,
  type ChangeEvent,
  type DragEvent,
} from "react";
import { Handle, Position, type NodeProps, type Node } from "@xyflow/react";
import { api } from "@/convex/_generated/api";
import type { Id } from "@/convex/_generated/dataModel";
import BaseNodeWrapper from "./base-node-wrapper";
import { toast } from "@/lib/toast";
import { msg } from "@/lib/toast-messages";
import { computeMediaNodeSize } from "@/lib/canvas-utils";
import { useCanvasSync } from "@/components/canvas/canvas-sync-context";
import { useMutation } from "convex/react";

const ALLOWED_IMAGE_TYPES = new Set([
  "image/png",
  "image/jpeg",
  "image/webp",
]);
const MAX_IMAGE_BYTES = 10 * 1024 * 1024;
const OPTIMISTIC_NODE_PREFIX = "optimistic_";

type ImageNodeData = {
  storageId?: string;
  url?: string;
  filename?: string;
  mimeType?: string;
  width?: number;
  height?: number;
  _status?: string;
  _statusMessage?: string;
};

export type ImageNode = Node<ImageNodeData, "image">;

async function getImageDimensions(file: File): Promise<{ width: number; height: number }> {
  return await new Promise((resolve, reject) => {
    const objectUrl = URL.createObjectURL(file);
    const image = new window.Image();

    image.onload = () => {
      const width = image.naturalWidth;
      const height = image.naturalHeight;
      URL.revokeObjectURL(objectUrl);

      if (!width || !height) {
        reject(new Error("Could not read image dimensions"));
        return;
      }

      resolve({ width, height });
    };

    image.onerror = () => {
      URL.revokeObjectURL(objectUrl);
      reject(new Error("Could not decode image"));
    };

    image.src = objectUrl;
  });
}

export default function ImageNode({
  id,
  data,
  selected,
  width,
  height,
}: NodeProps<ImageNode>) {
  const generateUploadUrl = useMutation(api.storage.generateUploadUrl);
  const { queueNodeDataUpdate, queueNodeResize, status } = useCanvasSync();
  const fileInputRef = useRef<HTMLInputElement>(null);
  const [isUploading, setIsUploading] = useState(false);
  const [isDragOver, setIsDragOver] = useState(false);
  const hasAutoSizedRef = useRef(false);

  useEffect(() => {
    if (typeof id === "string" && id.startsWith(OPTIMISTIC_NODE_PREFIX)) {
      return;
    }

    if (typeof data.width !== "number" || typeof data.height !== "number") {
      return;
    }

    if (hasAutoSizedRef.current) return;
    const targetSize = computeMediaNodeSize("image", {
      intrinsicWidth: data.width,
      intrinsicHeight: data.height,
    });
    const currentWidth = typeof width === "number" ? width : 0;
    const currentHeight = typeof height === "number" ? height : 0;
    const hasMeasuredSize = currentWidth > 0 && currentHeight > 0;
    if (!hasMeasuredSize) {
      return;
    }

    const isAtTargetSize = currentWidth === targetSize.width && currentHeight === targetSize.height;
    const isAtDefaultSeedSize = currentWidth === 280 && currentHeight === 200;
    const shouldRunInitialAutoSize = isAtDefaultSeedSize && !isAtTargetSize;

    if (!shouldRunInitialAutoSize) {
      hasAutoSizedRef.current = true;
      return;
    }

    hasAutoSizedRef.current = true;
    void queueNodeResize({
      nodeId: id as Id<"nodes">,
      width: targetSize.width,
      height: targetSize.height,
    });
  }, [data.height, data.width, height, id, queueNodeResize, width]);

  const uploadFile = useCallback(
    async (file: File) => {
      if (!ALLOWED_IMAGE_TYPES.has(file.type)) {
        const { title, desc } = msg.canvas.uploadFormatError(
          file.type || file.name.split(".").pop() || "—",
        );
        toast.error(title, desc);
        return;
      }
      if (file.size > MAX_IMAGE_BYTES) {
        const { title, desc } = msg.canvas.uploadSizeError(
          Math.round(MAX_IMAGE_BYTES / (1024 * 1024)),
        );
        toast.error(title, desc);
        return;
      }
      if (status.isOffline) {
        toast.warning(
          "Offline aktuell nicht unterstützt",
          "Bild-Uploads benötigen eine aktive Verbindung.",
        );
        return;
      }

      setIsUploading(true);

      try {
        let dimensions: { width: number; height: number } | undefined;
        try {
          dimensions = await getImageDimensions(file);
        } catch {
          dimensions = undefined;
        }

        const uploadUrl = await generateUploadUrl();
        const result = await fetch(uploadUrl, {
          method: "POST",
          headers: { "Content-Type": file.type },
          body: file,
        });

        if (!result.ok) {
          throw new Error("Upload failed");
        }

        const { storageId } = (await result.json()) as { storageId: string };

        await queueNodeDataUpdate({
          nodeId: id as Id<"nodes">,
          data: {
            storageId,
            filename: file.name,
            mimeType: file.type,
            ...(dimensions ? { width: dimensions.width, height: dimensions.height } : {}),
          },
        });

        if (dimensions) {
          const targetSize = computeMediaNodeSize("image", {
            intrinsicWidth: dimensions.width,
            intrinsicHeight: dimensions.height,
          });

          await queueNodeResize({
            nodeId: id as Id<"nodes">,
            width: targetSize.width,
            height: targetSize.height,
          });
        }

        toast.success(msg.canvas.imageUploaded.title);
      } catch (err) {
        console.error("Upload failed:", err);
        toast.error(
          msg.canvas.uploadFailed.title,
          err instanceof Error ? err.message : undefined,
        );
      } finally {
        setIsUploading(false);
      }
    },
    [generateUploadUrl, id, queueNodeDataUpdate, queueNodeResize, status.isOffline]
  );

  const handleClick = useCallback(() => {
    if (!data.url && !isUploading) {
      fileInputRef.current?.click();
    }
  }, [data.url, isUploading]);

  const handleFileChange = useCallback(
    (e: ChangeEvent<HTMLInputElement>) => {
      const file = e.target.files?.[0];
      if (file) uploadFile(file);
    },
    [uploadFile]
  );

  const handleDragOver = useCallback((e: DragEvent) => {
    e.preventDefault();
    e.stopPropagation();
    if (e.dataTransfer.types.includes("Files")) {
      setIsDragOver(true);
      e.dataTransfer.dropEffect = "copy";
    }
  }, []);

  const handleDragLeave = useCallback((e: DragEvent) => {
    e.preventDefault();
    e.stopPropagation();
    setIsDragOver(false);
  }, []);

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

      const file = e.dataTransfer.files?.[0];
      if (file && file.type.startsWith("image/")) {
        uploadFile(file);
      }
    },
    [uploadFile]
  );

  const handleReplace = useCallback(() => {
    fileInputRef.current?.click();
  }, []);

  const showFilename = Boolean(data.filename && data.url);

  return (
    <BaseNodeWrapper
      nodeType="image"
      selected={selected}
      status={data._status}
    >
      <Handle
        type="target"
        position={Position.Left}
        className="h-3! w-3! bg-primary! border-2! border-background!"
      />

      <div
        className={`grid h-full min-h-0 w-full grid-cols-1 gap-y-1 p-2 ${
          showFilename
            ? "grid-rows-[auto_minmax(0,1fr)_auto]"
            : "grid-rows-[auto_minmax(0,1fr)]"
        }`}
      >
        <div className="flex items-center justify-between">
          <div className="text-xs font-medium text-muted-foreground">🖼️ Bild</div>
          {data.url && (
            <button
              onClick={handleReplace}
              className="nodrag text-xs text-muted-foreground transition-colors hover:text-foreground"
            >
              Ersetzen
            </button>
          )}
        </div>

        <div className="relative min-h-0 overflow-hidden rounded-lg bg-muted/30">
          {isUploading ? (
            <div className="flex h-full w-full items-center justify-center bg-muted">
              <div className="flex flex-col items-center gap-2">
                <div className="h-5 w-5 animate-spin rounded-full border-2 border-primary border-t-transparent" />
                <span className="text-xs text-muted-foreground">Wird hochgeladen...</span>
              </div>
            </div>
          ) : data.url ? (
            // eslint-disable-next-line @next/next/no-img-element -- Convex storage URL, volle Auflösung wie Asset-Node
            <img
              src={data.url}
              alt={data.filename ?? "Bild"}
              className="h-full w-full object-cover object-center"
              draggable={false}
            />
          ) : (
            <div
              onClick={handleClick}
              onDragOver={handleDragOver}
              onDragLeave={handleDragLeave}
              onDrop={handleDrop}
              className={`
              nodrag flex h-full w-full cursor-pointer flex-col items-center justify-center
              border-2 border-dashed text-sm transition-colors
              ${
                isDragOver
                  ? "border-primary bg-primary/5 text-primary"
                  : "text-muted-foreground hover:border-primary/50 hover:text-foreground"
              }
            `}
            >
              <span className="mb-1 text-lg">📁</span>
              <span>Klicken oder hierhin ziehen</span>
              <span className="mt-0.5 text-xs">PNG, JPG, WebP</span>
            </div>
          )}
        </div>

        {showFilename ? (
          <p className="min-h-0 truncate text-xs text-muted-foreground">{data.filename}</p>
        ) : null}
      </div>

      <input
        ref={fileInputRef}
        type="file"
        accept="image/png,image/jpeg,image/webp"
        onChange={handleFileChange}
        className="hidden"
      />

      <Handle
        type="source"
        position={Position.Right}
        className="h-3! w-3! bg-primary! border-2! border-background!"
      />
    </BaseNodeWrapper>
  );
}
