import type {
  WebCanvasWorkflow,
  WebCanvasWorkflowAssetRef,
  WebCanvasWorkflowEdge,
  WebCanvasWorkflowNode,
  WebCanvasWorkflowPort,
  WebCanvasWorkflowTaskRef,
} from "../../types";

export const CANVAS_WORKFLOW_SCHEMA_VERSION = 2;

type LegacyNodeKind = WebCanvasWorkflowNode["kind"];

export type CanvasWorkflowNodeKind = "text" | "image" | "video";

export interface CanvasTaskResultInput {
  taskId: string;
  status: WebCanvasWorkflowTaskRef["status"];
  resultUrl?: string | null;
  ossKey?: string | null;
  mediaType?: string | null;
  originalUrl?: string | null;
  expiresAt?: string | null;
}

function isRecord(value: unknown): value is Record<string, unknown> {
  return Boolean(value && typeof value === "object" && !Array.isArray(value));
}

function toStringValue(value: unknown, fallback = ""): string {
  return typeof value === "string" ? value : fallback;
}

function normalizeNodeKind(kind: LegacyNodeKind): CanvasWorkflowNodeKind {
  if (kind === "image") return "image";
  if (kind === "video") return "video";
  return "text";
}

export function getCanvasWorkflowNodeOutputType(kind: CanvasWorkflowNodeKind): WebCanvasWorkflowPort["type"] {
  if (kind === "image") return "image";
  if (kind === "video") return "video";
  return "text";
}

export function getCanvasWorkflowNodeInputType(kind: CanvasWorkflowNodeKind): WebCanvasWorkflowPort["type"] {
  if (kind === "image" || kind === "video") return "image";
  return "text";
}

export function createCanvasWorkflowPort(
  direction: "input" | "output",
  kind: CanvasWorkflowNodeKind,
): WebCanvasWorkflowPort {
  return {
    id: direction === "output" ? "out" : "in",
    type: direction === "output" ? getCanvasWorkflowNodeOutputType(kind) : getCanvasWorkflowNodeInputType(kind),
  };
}

function inferMediaTypeFromNode(kind: CanvasWorkflowNodeKind): string {
  if (kind === "image") return "image";
  if (kind === "video") return "video";
  return "text";
}

function normalizeAssetRef(node: WebCanvasWorkflowNode, normalizedKind: CanvasWorkflowNodeKind): WebCanvasWorkflowAssetRef | null {
  const metadata = isRecord(node.metadata) ? node.metadata : {};
  const candidate = isRecord(node.assetRef)
    ? node.assetRef
    : isRecord(metadata.assetRef)
      ? metadata.assetRef
      : null;
  const url = toStringValue(candidate?.url, node.previewUrl || "");
  if (!url) return null;

  return {
    url,
    ossKey: toStringValue(candidate?.ossKey) || null,
    mediaType: toStringValue(candidate?.mediaType, inferMediaTypeFromNode(normalizedKind)),
    sourceTaskId: toStringValue(candidate?.sourceTaskId, toStringValue(metadata.sourceTaskId)) || null,
    originalUrl: toStringValue(candidate?.originalUrl) || null,
    expiresAt: toStringValue(candidate?.expiresAt) || null,
  };
}

function normalizeTaskRef(node: WebCanvasWorkflowNode, assetRef: WebCanvasWorkflowAssetRef | null): WebCanvasWorkflowTaskRef | null {
  const metadata = isRecord(node.metadata) ? node.metadata : {};
  const candidate = isRecord(node.taskRef)
    ? node.taskRef
    : isRecord(metadata.taskRef)
      ? metadata.taskRef
      : null;
  const taskId = toStringValue(candidate?.taskId, assetRef?.sourceTaskId || toStringValue(metadata.sourceTaskId));
  if (!taskId) return null;
  const status = toStringValue(candidate?.status, "completed") as WebCanvasWorkflowTaskRef["status"];
  return {
    taskId,
    status,
    resultUrl: toStringValue(candidate?.resultUrl, assetRef?.url || node.previewUrl || "") || null,
    updatedAt: toStringValue(candidate?.updatedAt) || null,
  };
}

function normalizeNodeParams(node: WebCanvasWorkflowNode): Record<string, unknown> {
  const metadata = isRecord(node.metadata) ? node.metadata : {};
  const params = isRecord(node.params) ? node.params : {};
  const reserved = new Set(["assetRef", "taskRef", "sourceTaskId"]);
  const metadataParams = Object.fromEntries(Object.entries(metadata).filter(([key]) => !reserved.has(key)));
  return {
    ...metadataParams,
    ...params,
  };
}

export function normalizeCanvasWorkflowNode(node: WebCanvasWorkflowNode): WebCanvasWorkflowNode {
  const kind = normalizeNodeKind(node.kind);
  const assetRef = normalizeAssetRef(node, kind);
  const taskRef = normalizeTaskRef(node, assetRef);
  const inputs = Array.isArray(node.inputs) && node.inputs.length ? node.inputs : [createCanvasWorkflowPort("input", kind)];
  const outputs = Array.isArray(node.outputs) && node.outputs.length ? node.outputs : [createCanvasWorkflowPort("output", kind)];

  return {
    ...node,
    kind,
    inputs,
    outputs,
    assetRef,
    taskRef,
    params: normalizeNodeParams(node),
    previewUrl: assetRef?.url || node.previewUrl,
  };
}

export function normalizeCanvasWorkflowEdge(
  edge: WebCanvasWorkflowEdge,
  nodeById: Map<string, WebCanvasWorkflowNode>,
): WebCanvasWorkflowEdge {
  const sourceNode = nodeById.get(edge.source);
  const targetNode = nodeById.get(edge.target);
  const sourcePort = edge.sourcePort || sourceNode?.outputs?.[0] || createCanvasWorkflowPort("output", "text");
  const targetPort = edge.targetPort || targetNode?.inputs?.[0] || createCanvasWorkflowPort("input", "text");
  return {
    ...edge,
    sourcePort,
    targetPort,
  };
}

export function normalizeCanvasWorkflowSchema(workflow: WebCanvasWorkflow): WebCanvasWorkflow {
  const nodes = workflow.nodes.map(normalizeCanvasWorkflowNode);
  const nodeById = new Map(nodes.map((node) => [node.id, node]));
  return {
    ...workflow,
    schemaVersion: CANVAS_WORKFLOW_SCHEMA_VERSION,
    nodes,
    edges: workflow.edges.map((edge) => normalizeCanvasWorkflowEdge(edge, nodeById)),
  };
}

export function createCanvasAssetRefFromTaskResult(
  kind: CanvasWorkflowNodeKind,
  result: CanvasTaskResultInput,
): WebCanvasWorkflowAssetRef | null {
  const url = String(result.resultUrl || "").trim();
  if (!url) return null;
  return {
    url,
    ossKey: result.ossKey || null,
    mediaType: result.mediaType || inferMediaTypeFromNode(kind),
    sourceTaskId: result.taskId,
    originalUrl: result.originalUrl || null,
    expiresAt: result.expiresAt || null,
  };
}

export function applyCanvasNodeTaskResult(
  workflow: WebCanvasWorkflow,
  nodeId: string,
  result: CanvasTaskResultInput,
): WebCanvasWorkflow {
  const normalized = normalizeCanvasWorkflowSchema(workflow);
  return {
    ...normalized,
    nodes: normalized.nodes.map((node) => {
      if (node.id !== nodeId) return node;
      const kind = normalizeNodeKind(node.kind);
      const assetRef = createCanvasAssetRefFromTaskResult(kind, result);
      return {
        ...node,
        assetRef: assetRef || node.assetRef || null,
        taskRef: {
          taskId: result.taskId,
          status: result.status,
          resultUrl: result.resultUrl || assetRef?.url || node.taskRef?.resultUrl || null,
          updatedAt: new Date().toISOString(),
        },
        previewUrl: assetRef?.url || result.resultUrl || node.previewUrl,
      };
    }),
  };
}
