import type { CSSProperties } from "react";
import type { AiTaskStatus } from "../../api/aiGenerationClient";
import type { ServerCommunityCase } from "../../api/communityClient";
import { waitForTask } from "../../api/taskSubscription";
import type { WebCanvasWorkflow } from "../../types";
import type { AssetLibraryCategory } from "../assets/localAssetStore";
import { communityCaseToPromptCase, getCommunityCaseCover } from "../community/communityCaseUtils";
import { toHappyHorseDisplayModel } from "../../utils/happyHorseRouting";
import { toViduDisplayModel } from "../../utils/viduRouting";
import { toPixverseDisplayModel } from "../../utils/pixverseRouting";
import type {
  CanvasImageFocusSelection,
  CanvasNodeKind,
  CanvasNodePort,
  CanvasNodeSide,
  CanvasNodeSize,
  CanvasOption,
  CanvasPoint,
  CanvasSelectedNode,
  CanvasStyleCase,
  CanvasStyleReference,
  CanvasVideoMode,
} from "./canvasTypes";
import {
  assetTypePromptLabel,
  canvasNodeDefaultSizes,
  canvasNodeMaxSizes,
  canvasNodeMinSizes,
  canvasVideoModes,
  canvasViewportMaxZoom,
  canvasViewportMinZoom,
  defaultImageModel,
  defaultVideoModel,
  imageModelOptions,
  videoModelOptions,
} from "./canvasConstants";

// ─── Utility Functions ─────────────────────────────────────────────────────────

export function getCanvasNodeSideDirection(side: CanvasNodeSide) {
  return side === "right" ? 1 : -1;
}

export function normalizeCanvasLinkPorts(first: CanvasNodePort, second: CanvasNodePort) {
  if (first.side === second.side) return null;
  if (first.kind === second.kind && first.nodeId === second.nodeId) return null;

  return first.side === "right"
    ? { from: first, to: second }
    : { from: second, to: first };
}

export function getCanvasPortIdentity(port: CanvasNodePort) {
  return `${port.kind}:${port.nodeId}:${port.side}:${port.slot}`;
}

export function getCanvasLinkIdentity(from: CanvasNodePort, to: CanvasNodePort) {
  return `${getCanvasPortIdentity(from)}->${getCanvasPortIdentity(to)}`;
}

export function moveCanvasNodesForPackageDrag<T extends { id: string; position: CanvasPoint }>(
  currentNodes: T[],
  originPositions: Record<string, CanvasPoint>,
  delta: CanvasPoint
): T[] {
  return currentNodes.map((node) => {
    const origin = originPositions[node.id];
    if (!origin) return node;
    return {
      ...node,
      position: {
        x: origin.x + delta.x,
        y: origin.y + delta.y,
      },
    };
  });
}

export function createCanvasNodeSize(kind: CanvasNodeKind) {
  return { ...canvasNodeDefaultSizes[kind] };
}

export function clampCanvasNodeSize(kind: CanvasNodeKind, width: number, height: number): CanvasNodeSize {
  const minSize = canvasNodeMinSizes[kind];
  const maxSize = canvasNodeMaxSizes[kind];
  return {
    width: Math.min(maxSize.width, Math.max(minSize.width, Math.round(width))),
    height: Math.min(maxSize.height, Math.max(minSize.height, Math.round(height))),
  };
}

export function getCanvasSelectionKey(node: CanvasSelectedNode) {
  return `${node.kind}:${node.id}`;
}

export function normalizeCanvasSelectionRect(start: CanvasPoint, current: CanvasPoint) {
  const left = Math.min(start.x, current.x);
  const top = Math.min(start.y, current.y);
  const width = Math.abs(current.x - start.x);
  const height = Math.abs(current.y - start.y);
  return { left, top, width, height };
}

export function doCanvasRectsIntersect(
  first: { left: number; top: number; width: number; height: number },
  second: { left: number; top: number; width: number; height: number }
) {
  return (
    first.left <= second.left + second.width &&
    first.left + first.width >= second.left &&
    first.top <= second.top + second.height &&
    first.top + first.height >= second.top
  );
}

export function clampCanvasViewportZoom(zoom: number) {
  return Math.min(canvasViewportMaxZoom, Math.max(canvasViewportMinZoom, zoom));
}

export function getOptionLabel(options: CanvasOption[], value: string) {
  return options.find((option) => option.value === value)?.label || value;
}

import {
  getImageQualityOptions,
  getDefaultImageQuality,
  getVideoQualityOptions,
  getDefaultVideoQuality,
  getVideoQualityLabel,
} from "../../utils/modelOptions";

export {
  getImageQualityOptions,
  getDefaultImageQuality,
  getVideoQualityOptions,
  getDefaultVideoQuality,
  getVideoQualityLabel,
};

export function hasCanvasOptionValue(options: CanvasOption[], value: string | undefined) {
  return Boolean(value && options.some((option) => option.value === value));
}

export function getWorkflowNodeMetadataString(
  node: WebCanvasWorkflow["nodes"][number],
  key: string,
  fallback = "",
) {
  const params = (node as unknown as { params?: Record<string, unknown> }).params;
  const paramValue = params?.[key];
  if (typeof paramValue === "string" && paramValue.trim()) return paramValue.trim();
  const metadata = (node as unknown as { metadata?: Record<string, unknown> }).metadata;
  const value = metadata?.[key];
  return typeof value === "string" && value.trim() ? value.trim() : fallback;
}

export function getWorkflowNodeStringField(
  node: WebCanvasWorkflow["nodes"][number],
  key: string,
): string | undefined {
  const params = (node as unknown as { params?: Record<string, unknown> }).params;
  const paramValue = params?.[key];
  if (typeof paramValue === "string") return paramValue;
  const metadata = (node as unknown as { metadata?: Record<string, unknown> }).metadata;
  const value = metadata?.[key];
  return typeof value === "string" ? value : undefined;
}

export function isLikelyImageFileName(value: string) {
  const trimmed = value.trim();
  if (!trimmed) return false;
  return /\.(png|jpe?g|webp)(\?.*)?$/i.test(trimmed);
}

export function getWorkflowImageNodePrompt(node: WebCanvasWorkflow["nodes"][number]) {
  const storedPrompt = getWorkflowNodeStringField(node, "prompt");
  if (storedPrompt !== undefined) return storedPrompt;
  const detail = node.detail || "";
  return isLikelyImageFileName(detail) ? "" : detail;
}

export function getWorkflowImageNodeFileName(node: WebCanvasWorkflow["nodes"][number], index: number) {
  const storedFileName = getWorkflowNodeStringField(node, "fileName");
  if (storedFileName?.trim()) return storedFileName.trim();
  if (isLikelyImageFileName(node.detail || "")) return node.detail.trim();
  return node.label || `图片节点 ${index + 1}`;
}

export function resolveWorkflowImageModel(node: WebCanvasWorkflow["nodes"][number], workflowModel: string) {
  const storedModel = getWorkflowNodeMetadataString(node, "model");
  if (hasCanvasOptionValue(imageModelOptions, storedModel)) return storedModel;
  if (hasCanvasOptionValue(imageModelOptions, workflowModel)) return workflowModel;
  return defaultImageModel;
}

export function resolveWorkflowVideoModel(node: WebCanvasWorkflow["nodes"][number], _workflowModel: string) {
  const raw = getWorkflowNodeMetadataString(node, "model");
  const storedModel = toPixverseDisplayModel(toViduDisplayModel(toHappyHorseDisplayModel(raw)));
  if (hasCanvasOptionValue(videoModelOptions, storedModel)) return storedModel;
  return defaultVideoModel;
}

export function resolveWorkflowRatio(
  node: WebCanvasWorkflow["nodes"][number],
  workflowRatio: string,
  options: CanvasOption[],
  fallback: string,
) {
  const storedRatio = getWorkflowNodeMetadataString(node, "aspectRatio");
  if (hasCanvasOptionValue(options, storedRatio)) return storedRatio;
  if (hasCanvasOptionValue(options, workflowRatio)) return workflowRatio;
  return fallback;
}

export function resolveWorkflowVideoMode(node: WebCanvasWorkflow["nodes"][number]) {
  const storedMode = getWorkflowNodeMetadataString(node, "videoMode");
  return canvasVideoModes.includes(storedMode as CanvasVideoMode) ? (storedMode as CanvasVideoMode) : "text2video";
}

export function resolveImageQuality(model: string, imageSize: string) {
  const options = getImageQualityOptions(model || defaultImageModel);
  return options.some((option) => option.value === imageSize)
    ? imageSize
    : getDefaultImageQuality(model || defaultImageModel);
}

export function resolveVideoQuality(model: string, resolution: string) {
  const options = getVideoQualityOptions(model || defaultVideoModel);
  return options.some((option) => option.value === resolution)
    ? resolution
    : getDefaultVideoQuality(model || defaultVideoModel);
}

export function delay(ms: number) {
  return new Promise<void>((resolve) => {
    window.setTimeout(resolve, ms);
  });
}

export function blobToDataUrl(blob: Blob) {
  return new Promise<string>((resolve, reject) => {
    const reader = new FileReader();
    reader.onload = () => {
      if (typeof reader.result === "string") {
        resolve(reader.result);
      } else {
        reject(new Error("Unable to read canvas image"));
      }
    };
    reader.onerror = () => reject(reader.error || new Error("Unable to read canvas image"));
    reader.readAsDataURL(blob);
  });
}

export async function waitForImageTaskResult(
  taskId: string,
  onStatus?: (status: AiTaskStatus) => void,
  abortRef?: { current: boolean },
) {
  const resultUrl = await waitForTask(taskId, {
    kind: "image",
    abortRef,
    onProgress: (e) => {
      if (onStatus) {
        onStatus({
          taskId,
          status: e.status,
          progress: e.progress,
          progressSource: e.progressSource,
          stage: e.stage,
          startedAt: e.startedAt,
          expectedDurationMs: e.expectedDurationMs,
          resultUrl: e.resultUrl ?? undefined,
          error: e.error ?? undefined,
        } as AiTaskStatus);
      }
    },
  });
  if (abortRef?.current) return "";
  if (!resultUrl) throw new Error("生成任务已完成，但服务器没有返回结果地址，请稍后重试");
  return resultUrl;
}

export async function waitForVideoTaskResult(
  taskId: string,
  onStatus?: (status: AiTaskStatus) => void,
  abortRef?: { current: boolean },
) {
  const resultUrl = await waitForTask(taskId, {
    kind: "video",
    abortRef,
    onProgress: (e) => {
      if (onStatus) {
        onStatus({
          taskId,
          status: e.status,
          progress: e.progress,
          progressSource: e.progressSource,
          stage: e.stage,
          startedAt: e.startedAt,
          expectedDurationMs: e.expectedDurationMs,
          resultUrl: e.resultUrl ?? undefined,
          error: e.error ?? undefined,
        } as AiTaskStatus);
      }
    },
  });
  if (abortRef?.current) return "";
  if (!resultUrl) throw new Error("视频生成任务已完成，但服务器没有返回结果地址，请稍后重试");
  return resultUrl;
}

export function normalizeCanvasGenerationProgress(state?: { status: "submitting" | "running" | "success" | "error"; progress?: number }) {
  if (!state) return 0;
  if (state.status === "success") return 100;
  if (state.status === "error") return 0;
  const serverProgress = Number(state.progress || 0);
  if (state.status === "submitting") return Math.max(6, Math.min(18, serverProgress || 8));
  return Math.max(18, Math.min(96, serverProgress || 36));
}

export function canvasGenerationProgressStyle(progress: number) {
  return {
    "--canvas-generation-progress": `${Math.max(0, Math.min(100, progress)) * 3.6}deg`,
  } as CSSProperties;
}

export function buildCopyTitle(title: string) {
  return title.includes("副本") ? title : `${title} 副本`;
}

export function buildReversePromptFromAsset(asset: { type: AssetLibraryCategory; name: string; description: string }) {
  return `AI 反推提示词：${assetTypePromptLabel[asset.type]}「${asset.name}」，${asset.description}，保留主体轮廓、材质细节、色彩氛围和镜头语言，电影感构图，高质量细节。`;
}

export function positionFloatingMenu(clientX: number, clientY: number, width: number, height: number, offset = 0) {
  const edgeGap = 8;
  const canOpenRight = clientX + offset + width + edgeGap <= window.innerWidth;
  const canOpenBelow = clientY + offset + height + edgeGap <= window.innerHeight;

  return {
    left: Math.max(edgeGap, canOpenRight ? clientX + offset : clientX - width - offset),
    top: Math.max(edgeGap, canOpenBelow ? clientY + offset : clientY - height - offset),
  };
}

export function toCanvasStyleString(value: unknown, fallback = ""): string {
  if (typeof value === "string") return value.trim() || fallback;
  if (typeof value === "number" && Number.isFinite(value)) return String(value);
  return fallback;
}

export function buildCanvasStyleKeywords(item: Omit<CanvasStyleCase, "keywords">, tags: string[] = []) {
  return [
    item.title,
    item.author,
    item.category,
    item.summary,
    item.prompt || "",
    ...tags,
  ]
    .join(" ")
    .toLowerCase();
}

export function communityCaseToCanvasStyleCase(item: ServerCommunityCase): CanvasStyleCase | null {
  const promptCase = communityCaseToPromptCase(item);
  const imageUrl = promptCase?.imageUrl || getCommunityCaseCover(item);
  if (!imageUrl) return null;

  const metadata = item.metadata || {};
  const title = promptCase?.title || item.title;
  const author = promptCase?.author || item.username || "OmniAI";
  const category =
    promptCase?.category ||
    toCanvasStyleString(metadata.category ?? metadata.styleCategory) ||
    item.tags.find(Boolean) ||
    "社区风格";
  const prompt =
    promptCase?.prompt ||
    toCanvasStyleString(metadata.prompt ?? metadata.inputPrompt ?? metadata.descriptionPrompt, item.description || "");
  const summary = promptCase?.summary || item.description || prompt || "来自社区广场的风格参考";
  const baseCase: Omit<CanvasStyleCase, "keywords"> = {
    id: promptCase?.id || `server-style-case-${item.id}`,
    title,
    author,
    category,
    imageUrl,
    prompt,
    summary,
    favoriteCount: item.favoriteCount,
    likeCount: item.likeCount,
    isFavorited: item.isFavorited,
  };

  return {
    ...baseCase,
    keywords: buildCanvasStyleKeywords(baseCase, item.tags),
  };
}

export function createStyleReferenceFromCase(item: CanvasStyleCase): CanvasStyleReference {
  return {
    id: item.id,
    title: item.title,
    author: item.author,
    category: item.category,
    imageUrl: item.imageUrl,
    prompt: item.prompt,
  };
}

export function clampCanvasPercent(value: number) {
  return Math.min(100, Math.max(0, value));
}

export function parseCanvasRatio(value: string): number | null {
  const [width, height] = value.split(":").map(Number);
  return width > 0 && height > 0 ? width / height : null;
}

export function normalizeImageFocusSelectionFromAnchor(
  start: { x: number; y: number },
  end: { x: number; y: number },
  ratio: string,
  containerRatio: number,
): CanvasImageFocusSelection {
  const targetRatio = parseCanvasRatio(ratio);
  const directionX = end.x >= start.x ? 1 : -1;
  const directionY = end.y >= start.y ? 1 : -1;
  let width = Math.abs(end.x - start.x);
  let height = Math.abs(end.y - start.y);

  if (targetRatio && Number.isFinite(containerRatio) && containerRatio > 0 && width > 0 && height > 0) {
    const percentRatio = targetRatio / containerRatio;
    if (width / height > percentRatio) {
      width = height * percentRatio;
    } else {
      height = width / percentRatio;
    }
  }

  const maxWidth = directionX > 0 ? 100 - start.x : start.x;
  const maxHeight = directionY > 0 ? 100 - start.y : start.y;
  width = Math.min(width, maxWidth);
  height = Math.min(height, maxHeight);

  if (targetRatio && Number.isFinite(containerRatio) && containerRatio > 0 && width > 0 && height > 0) {
    const percentRatio = targetRatio / containerRatio;
    if (width / height > percentRatio) {
      width = height * percentRatio;
    } else {
      height = width / percentRatio;
    }
  }

  return {
    x: clampCanvasPercent(directionX > 0 ? start.x : start.x - width),
    y: clampCanvasPercent(directionY > 0 ? start.y : start.y - height),
    width: clampCanvasPercent(width),
    height: clampCanvasPercent(height),
    ratio,
  };
}

export function applyImageFocusRatioFromTopLeft(
  selection: CanvasImageFocusSelection,
  ratio: string,
  containerRatio: number,
): CanvasImageFocusSelection {
  const targetRatio = parseCanvasRatio(ratio);
  if (!targetRatio || !Number.isFinite(containerRatio) || containerRatio <= 0) {
    return { ...selection, ratio };
  }

  const percentRatio = targetRatio / containerRatio;
  let width = Math.max(4, selection.width);
  let height = Math.max(4, selection.height);
  if (width / height > percentRatio) {
    width = height * percentRatio;
  } else {
    height = width / percentRatio;
  }

  const maxWidth = Math.max(4, 100 - selection.x);
  const maxHeight = Math.max(4, 100 - selection.y);
  if (width > maxWidth) {
    width = maxWidth;
    height = width / percentRatio;
  }
  if (height > maxHeight) {
    height = maxHeight;
    width = height * percentRatio;
  }

  return {
    ...selection,
    width: clampCanvasPercent(width),
    height: clampCanvasPercent(height),
    ratio,
  };
}

export function getWorkflowNodeStyleReference(node: WebCanvasWorkflow["nodes"][number]): CanvasStyleReference | undefined {
  const metadata = (node as unknown as { metadata?: Record<string, unknown> }).metadata;
  const rawStyle = metadata?.styleReference;
  if (!rawStyle || typeof rawStyle !== "object" || Array.isArray(rawStyle)) return undefined;
  const style = rawStyle as Record<string, unknown>;
  const imageUrl = toCanvasStyleString(style.imageUrl);
  const title = toCanvasStyleString(style.title);
  if (!imageUrl || !title) return undefined;
  return {
    id: toCanvasStyleString(style.id, `workflow-style-${node.id}`),
    title,
    author: toCanvasStyleString(style.author, "OmniAI"),
    category: toCanvasStyleString(style.category, "社区风格"),
    imageUrl,
    prompt: toCanvasStyleString(style.prompt),
  };
}

export function getWorkflowNodeFocusSelection(node: WebCanvasWorkflow["nodes"][number]): CanvasImageFocusSelection | undefined {
  const metadata = (node as unknown as { metadata?: Record<string, unknown> }).metadata;
  const rawSelection = metadata?.focusSelection;
  if (!rawSelection || typeof rawSelection !== "object" || Array.isArray(rawSelection)) return undefined;
  const selection = rawSelection as Record<string, unknown>;
  const width = Number(selection.width);
  const height = Number(selection.height);
  if (!Number.isFinite(width) || !Number.isFinite(height) || width <= 0 || height <= 0) return undefined;
  return {
    x: clampCanvasPercent(Number(selection.x) || 0),
    y: clampCanvasPercent(Number(selection.y) || 0),
    width: clampCanvasPercent(width),
    height: clampCanvasPercent(height),
    ratio: toCanvasStyleString(selection.ratio, "16:9"),
  };
}
