"use client";

import { useCallback, useEffect, useMemo, useRef, useState } from "react";
import { createPortal } from "react-dom";
import {
  Background,
  BaseEdge,
  MiniMap,
  ReactFlow,
  ReactFlowProvider,
  ViewportPortal,
  getBezierPath,
  useReactFlow,
  useViewport,
  type EdgeProps,
  type Node,
  type NodeProps,
  type NodeTypes,
} from "@xyflow/react";
import "@xyflow/react/dist/style.css";
import { Alert, Button, Slider, Space, Spin, Tooltip } from "antd";
import { AppstoreOutlined, BorderOuterOutlined, CloseOutlined, CopyOutlined } from "@ant-design/icons";

import { CanvasPreviewModeProvider } from "@/components/CanvasPreviewModeContext";
import {
  NodeGenerationComposerPreview,
  buildNodeGenerationPreviewPayload,
} from "@/components/composer/NodeGenerationComposerPreview";
import { workflowNodeTypes } from "@/components/workflowTypes";
import { useI18n } from "@/i18n";
import { DEFAULT_NODE_DIMENSIONS, defaultNodeDimensions } from "@/store/utils/nodeDefaults";
import { authFetch } from "@/utils/authFetch";
import { parseCanvasWorkflowProperties } from "@/utils/canvasWorkflowProperties";
import type { WorkflowFile } from "@/store/workflowStore";
import type { GroupColor, NodeGroup, NodeType, WorkflowNode } from "@/types";

export type CanvasTemplatePreviewData = {
  id: number;
  title: string;
  subtitle: string;
  image: string;
  data: string;
};

type ParsedPreview =
  | { workflow: WorkflowFile; error: null }
  | { workflow: null; error: string };

type CanvasTemplateDetail = {
  id: number;
  coverUrl: string;
  name: string;
  desp: string;
  properties: string;
};

type CanvasTemplateDetailResponse = {
  data?: CanvasTemplateDetail;
  message?: string;
  status?: string;
};

const PREVIEW_FIT_VIEW_OPTIONS = { maxZoom: 1 };
const PREVIEW_GROUP_COLORS: Record<GroupColor, string> = {
  neutral: "rgba(31, 35, 40, 0.06)",
  blue: "rgba(59, 130, 246, 0.12)",
  green: "rgba(34, 197, 94, 0.12)",
  purple: "rgba(147, 51, 234, 0.12)",
  orange: "rgba(249, 115, 22, 0.12)",
  red: "rgba(239, 68, 68, 0.12)",
};

interface CanvasTemplatePreviewModalProps {
  open: boolean;
  template: CanvasTemplatePreviewData | null;
  copying: boolean;
  onClose: () => void;
  onCopy: (template: CanvasTemplatePreviewData) => void;
}

function parsePreviewWorkflow(template: CanvasTemplatePreviewData | null): ParsedPreview {
  if (!template) return { workflow: null, error: "" };

  try {
    const workflow = parseCanvasWorkflowProperties(template.data);
    return { workflow, error: null };
  } catch (error) {
    return {
      workflow: null,
      error: error instanceof Error ? error.message : "Workflow data is invalid.",
    };
  }
}

function getGroupNodeId(groupId: string): string {
  return `group-node-${groupId}`;
}

function createPreviewGroupNode(group: NodeGroup): WorkflowNode {
  return {
    id: getGroupNodeId(group.id),
    type: "group",
    position: group.position,
    data: { groupId: group.id, group },
    style: { width: group.size.width, height: group.size.height },
    selectable: false,
    draggable: false,
  } as WorkflowNode;
}

function buildPreviewNodes(workflow: WorkflowFile): WorkflowNode[] {
  const groups = workflow.groups;
  const groupEntries = Object.values(groups ?? {});
  if (groupEntries.length === 0) {
    return workflow.nodes
      .filter((node) => node.type !== "group")
      .map((node) => ({
        ...node,
        selected: false,
        selectable: false,
        draggable: false,
      } as WorkflowNode));
  }

  const groupNodes = groupEntries.map(createPreviewGroupNode);
  const regularNodes = workflow.nodes
    .filter((node) => node.type !== "group")
    .map((node) => {
      const groupId = node.groupId;
      if (!groupId || !groups?.[groupId]) {
        return {
          ...node,
          selected: false,
          selectable: false,
          draggable: false,
        } as WorkflowNode;
      }

      const group = groups[groupId];
      const groupNodeId = getGroupNodeId(groupId);
      const isAlreadyNativeChild = node.parentId === groupNodeId;

      return {
        ...node,
        parentId: groupNodeId,
        groupId,
        selected: false,
        position: isAlreadyNativeChild
          ? node.position
          : {
              x: node.position.x - group.position.x,
              y: node.position.y - group.position.y,
            },
        selectable: false,
        draggable: false,
      } as WorkflowNode;
    });

  return [...groupNodes, ...regularNodes];
}

function PreviewGroupNode({ data }: NodeProps) {
  const group = (data as { group?: NodeGroup }).group;
  if (!group) return null;

  return (
    <div
      className="relative h-full w-full rounded-xl border border-white/12"
      style={{ backgroundColor: PREVIEW_GROUP_COLORS[group.color] ?? PREVIEW_GROUP_COLORS.neutral }}
    >
      <div className="absolute left-0 top-0 -translate-y-[calc(100%+4px)] whitespace-nowrap text-xs font-medium text-neutral-400">
        {group.name}
      </div>
    </div>
  );
}

function PreviewEdge({
  id,
  sourceX,
  sourceY,
  targetX,
  targetY,
  sourcePosition,
  targetPosition,
  markerEnd,
}: EdgeProps) {
  const [edgePath] = getBezierPath({
    sourceX,
    sourceY,
    sourcePosition,
    targetX,
    targetY,
    targetPosition,
    curvature: 0.35,
  });

  return (
    <BaseEdge
      id={id}
      path={edgePath}
      markerEnd={markerEnd}
      style={{
        stroke: "var(--edge-default)",
        strokeWidth: 2,
        strokeLinecap: "round",
        strokeLinejoin: "round",
        opacity: 0.78,
      }}
    />
  );
}

const previewEdgeTypes = {
  editable: PreviewEdge,
  reference: PreviewEdge,
};

const previewNodeTypes = Object.fromEntries(
  Object.entries(workflowNodeTypes).map(([type, Component]) => [
    type,
    (props: NodeProps) => (
      <div className="relative h-full w-full overflow-visible">
        {type === "group" ? <PreviewGroupNode {...props} /> : <Component {...props} />}
      </div>
    ),
  ])
) as NodeTypes;

function readPreviewNodeDimension(node: Node, axis: "width" | "height"): number {
  const defaults = defaultNodeDimensions[node.type as NodeType] || DEFAULT_NODE_DIMENSIONS;
  const styleValue = node.style?.[axis];
  return typeof styleValue === "number" ? styleValue : defaults[axis];
}

function getPreviewNodeAbsolutePosition(node: Node, nodeById: Map<string, Node>): { x: number; y: number } {
  if (!node.parentId) return node.position;
  const parent = nodeById.get(node.parentId);
  if (!parent) return node.position;
  const parentPosition = getPreviewNodeAbsolutePosition(parent, nodeById);
  return {
    x: parentPosition.x + node.position.x,
    y: parentPosition.y + node.position.y,
  };
}

function CanvasTemplatePreviewNodePopover({
  selectedNodeId,
  nodes,
  edges,
}: {
  selectedNodeId: string | null;
  nodes: WorkflowNode[];
  edges: WorkflowFile["edges"];
}) {
  const { zoom } = useViewport();
  const selectedNode = useMemo(
    () => nodes.find((node) => node.id === selectedNodeId && node.type !== "group"),
    [nodes, selectedNodeId]
  );

  if (!selectedNode) return null;
  const previewPayload = buildNodeGenerationPreviewPayload(selectedNode.id, nodes, edges);
  if (!previewPayload.text && previewPayload.materials.length === 0) return null;

  const nodeById = new Map<string, Node>(nodes.map((node) => [node.id, node]));
  const position = getPreviewNodeAbsolutePosition(selectedNode, nodeById);
  const width = readPreviewNodeDimension(selectedNode, "width");
  const height = readPreviewNodeDimension(selectedNode, "height");
  const safeZoom = zoom > 0 ? zoom : 1;

  return (
    <ViewportPortal>
      <div
        className="nodrag nopan nowheel pointer-events-auto absolute z-[10080]"
        style={{
          left: position.x + width / 2,
          top: position.y + height,
          transform: `translateX(-50%) translateY(${12 / safeZoom}px) scale(${1 / safeZoom})`,
          transformOrigin: "top center",
        }}
        onPointerDown={(event) => event.stopPropagation()}
        onClick={(event) => event.stopPropagation()}
      >
        <NodeGenerationComposerPreview
          previewText={previewPayload.text}
          previewMaterials={previewPayload.materials}
        />
      </div>
    </ViewportPortal>
  );
}

function CanvasTemplatePreviewFlow({ workflow }: { workflow: WorkflowFile }) {
  const { t } = useI18n();
  const { getViewport, setViewport, fitView } = useReactFlow();
  const wrapperRef = useRef<HTMLDivElement | null>(null);
  const [isMiniMapOpen, setIsMiniMapOpen] = useState(false);
  const [zoomPercent, setZoomPercent] = useState(100);
  const [selectedNodeId, setSelectedNodeId] = useState<string | null>(null);
  const previewNodes = useMemo(() => buildPreviewNodes(workflow), [workflow]);
  const previewEdges = useMemo(
    () => workflow.edges.map((edge) => ({ ...edge, type: edge.type || "editable" })),
    [workflow.edges]
  );
  const syncZoomPercent = useCallback(() => {
    setZoomPercent(Math.round(getViewport().zoom * 100));
  }, [getViewport]);

  const handleZoomPercentChange = useCallback((value: number) => {
    const viewport = getViewport();
    const nextZoom = value / 100;
    const bounds = wrapperRef.current?.getBoundingClientRect();
    const centerX = bounds ? bounds.left + bounds.width / 2 : window.innerWidth / 2;
    const centerY = bounds ? bounds.top + bounds.height / 2 : window.innerHeight / 2;
    const flowCenterX = (centerX - viewport.x) / viewport.zoom;
    const flowCenterY = (centerY - viewport.y) / viewport.zoom;

    setZoomPercent(value);
    setViewport({
      x: centerX - flowCenterX * nextZoom,
      y: centerY - flowCenterY * nextZoom,
      zoom: nextZoom,
    });
  }, [getViewport, setViewport]);

  const handleFitViewFromToolbar = useCallback(() => {
    void fitView({ ...PREVIEW_FIT_VIEW_OPTIONS, padding: 0.2, duration: 300 }).then(syncZoomPercent);
  }, [fitView, syncZoomPercent]);

  useEffect(() => {
    syncZoomPercent();
  }, [syncZoomPercent]);

  return (
    <div ref={wrapperRef} className="canvas-template-preview relative h-full w-full">
      <ReactFlow
        nodes={previewNodes}
        edges={previewEdges}
        nodeTypes={previewNodeTypes}
        edgeTypes={previewEdgeTypes}
        fitView
        fitViewOptions={PREVIEW_FIT_VIEW_OPTIONS}
        minZoom={0.1}
        maxZoom={4}
        nodesDraggable={false}
        nodesConnectable={false}
        elementsSelectable={false}
        selectNodesOnDrag={false}
        zoomOnDoubleClick={false}
        panOnDrag
        zoomOnScroll
        zoomOnPinch
        onNodeClick={(_, node) => {
          if (node.type === "group") return;
          setSelectedNodeId(node.id);
        }}
        onPaneClick={() => setSelectedNodeId(null)}
        onMoveEnd={syncZoomPercent}
        proOptions={{ hideAttribution: true }}
        className="canvas-background"
      >
        <Background color="var(--border-subtle,#404040)" gap={20} size={1} />
        <CanvasTemplatePreviewNodePopover
          selectedNodeId={selectedNodeId}
          nodes={previewNodes}
          edges={previewEdges}
        />
        {isMiniMapOpen && (
          <MiniMap
            position="bottom-left"
            className="!rounded-none !border-0 !bg-[var(--surface-2)] !shadow-[var(--shadow-panel)] [&_.react-flow__minimap-mask]:stroke-transparent"
            style={{ width: 190, height: 112, bottom: 58, left: 22 }}
            maskColor="var(--surface-overlay)"
            pannable
            zoomable
            nodeColor="var(--text-disabled)"
          />
        )}
      </ReactFlow>
      <div className="absolute bottom-4 left-4 z-30 flex items-center gap-2">
        <div className="flex h-12 items-center gap-1 rounded-2xl border border-[var(--border-subtle)] bg-[var(--surface-2)] px-2 shadow-[var(--shadow-panel)]">
          <button
            type="button"
            aria-label={t("canvas.toolbar.toggleMinimap")}
            title={t("canvas.toolbar.toggleMinimap")}
            onClick={() => setIsMiniMapOpen((open) => !open)}
            className={`flex h-8 w-8 items-center justify-center rounded-xl transition-colors ${
              isMiniMapOpen
                ? "border border-[var(--border-strong)] bg-[var(--surface-active)] text-[var(--text-primary)] ring-1 ring-[var(--border-strong)]"
                : "border border-transparent bg-[var(--surface-2)] text-[var(--text-secondary)] hover:bg-[var(--surface-hover)] hover:text-[var(--text-primary)]"
            }`}
          >
            <AppstoreOutlined className="text-[15px]" />
          </button>
          <button
            type="button"
            aria-label={t("canvas.toolbar.fitView")}
            title={t("canvas.toolbar.fitView")}
            onClick={handleFitViewFromToolbar}
            className="flex h-8 w-8 items-center justify-center rounded-xl bg-[var(--surface-2)] text-[var(--text-secondary)] transition-colors hover:bg-[var(--surface-hover)] hover:text-[var(--text-primary)]"
          >
            <BorderOuterOutlined className="text-[15px]" />
          </button>
          <div className="ml-1 flex w-24 items-center">
            <Slider
              min={10}
              max={400}
              step={3}
              value={zoomPercent}
              onChange={handleZoomPercentChange}
              tooltip={{ formatter: (value) => `${value}%` }}
              className="m-0 w-full"
            />
          </div>
          <span className="text-right text-xs tabular-nums text-[var(--text-secondary)]">
            {zoomPercent}%
          </span>
        </div>
      </div>
    </div>
  );
}

export function CanvasTemplatePreviewModal({
  open,
  template,
  copying,
  onClose,
  onCopy,
}: CanvasTemplatePreviewModalProps) {
  const { t } = useI18n();
  const [resolvedTemplate, setResolvedTemplate] = useState<CanvasTemplatePreviewData | null>(null);
  const [isLoadingDetail, setIsLoadingDetail] = useState(false);
  const [detailError, setDetailError] = useState<string | null>(null);
  const parsed = useMemo(() => parsePreviewWorkflow(resolvedTemplate), [resolvedTemplate]);
  const canCopy = !!resolvedTemplate && !copying && !isLoadingDetail;

  useEffect(() => {
    if (!open || !template) {
      setResolvedTemplate(null);
      setDetailError(null);
      setIsLoadingDetail(false);
      return;
    }

    setResolvedTemplate(null);
    setDetailError(null);
    setIsLoadingDetail(true);

    void authFetch(`/api/canvas-templates/detail?id=${template.id}`)
      .then(async (response) => {
        const result = (await response.json().catch(() => null)) as CanvasTemplateDetailResponse | null;
        if (!response.ok || !result?.data) {
          throw new Error(result?.message || t("templatePreview.invalidWorkflowDescription"));
        }

        const detail = result.data;
        setResolvedTemplate({
          id: detail.id,
          title: detail.name,
          subtitle: detail.desp,
          image: detail.coverUrl,
          data: detail.properties,
        });
      })
      .catch((error) => {
        setDetailError(error instanceof Error ? error.message : t("templatePreview.invalidWorkflowDescription"));
      })
      .finally(() => {
        setIsLoadingDetail(false);
      });
  }, [open, template, t]);

  useEffect(() => {
    if (!open || typeof document === "undefined") return;

    const hadSliderScope = document.body.classList.contains("canvas-slider-scope");
    document.body.classList.add("canvas-slider-scope");

    return () => {
      if (!hadSliderScope) document.body.classList.remove("canvas-slider-scope");
    };
  }, [open]);

  if (!open || typeof document === "undefined") return null;
  const displayTitle = resolvedTemplate?.title || template?.title || t("home.tvShow");

  return createPortal(
    <div
      role="dialog"
      aria-modal="true"
      aria-label={displayTitle}
      className="fixed inset-0 z-[1000] flex items-center justify-center bg-[var(--surface-overlay)] p-10 backdrop-blur-sm"
      onClick={onClose}
    >
      <section
        className="relative h-[min(900px,90vh)] w-[90vw] overflow-hidden rounded-2xl border border-[var(--border-strong)] bg-[var(--surface-1)] text-[var(--text-primary)] shadow-[var(--shadow-panel)]"
        onClick={(event) => event.stopPropagation()}
      >
        <header className="absolute left-[10%] right-[10%] top-4 z-20 flex h-12 items-center justify-between gap-4 rounded-xl border border-[var(--border-subtle)] bg-[var(--surface-2)] px-4 shadow-[var(--shadow-panel)]">
          <div className="flex min-w-0 items-center gap-3">
            <h2 className="truncate text-sm font-medium">
              {displayTitle}
            </h2>
          </div>
          <Space size={8}>
            <span className="hidden text-sm text-[var(--text-secondary)] sm:inline">
              {t("templatePreview.readOnly")}
            </span>
            <Tooltip title={t("templatePreview.copyProject")}>
              <Button
                type="primary"
                icon={<CopyOutlined />}
                loading={copying}
                disabled={!canCopy}
                onClick={() => {
                  if (resolvedTemplate) onCopy(resolvedTemplate);
                }}
              >
                {t("templatePreview.copyProject")}
              </Button>
            </Tooltip>
          </Space>
        </header>

        <button
          type="button"
          aria-label={t("common.close")}
          onClick={onClose}
          className="absolute right-4 top-4 z-30 flex h-10 w-10 items-center justify-center rounded-full text-[var(--text-secondary)] transition-colors hover:bg-[var(--surface-hover)] hover:text-[var(--text-primary)]"
        >
          <CloseOutlined />
        </button>

        <div className="canvas-background absolute inset-0">
          {isLoadingDetail ? (
            <div className="flex h-full items-center justify-center">
              <Spin tip={t("home.loading")} />
            </div>
          ) : detailError ? (
            <div className="flex h-full items-center justify-center p-6">
              <Alert
                type="error"
                showIcon
                message={t("templatePreview.invalidWorkflow")}
                description={detailError}
              />
            </div>
          ) : parsed.workflow ? (
            <ReactFlowProvider>
              <CanvasPreviewModeProvider>
                <CanvasTemplatePreviewFlow workflow={parsed.workflow} />
              </CanvasPreviewModeProvider>
            </ReactFlowProvider>
          ) : (
            <div className="flex h-full items-center justify-center p-6">
              <Alert
                type="error"
                showIcon
                message={t("templatePreview.invalidWorkflow")}
                description={parsed.error || t("templatePreview.invalidWorkflowDescription")}
              />
            </div>
          )}
        </div>
      </section>
      <style>{`
        .canvas-template-preview .react-flow__node-group {
          pointer-events: none;
        }
        .canvas-template-preview .react-flow__node button,
        .canvas-template-preview .react-flow__node input,
        .canvas-template-preview .react-flow__node textarea,
        .canvas-template-preview .react-flow__node [contenteditable="true"] {
          pointer-events: none;
        }
        .canvas-template-preview .react-flow__resize-control {
          display: none;
        }
        .canvas-template-preview .react-flow__handle {
          opacity: 0;
          pointer-events: none;
        }
      `}</style>
    </div>,
    document.body
  );
}
