import { useCallback, useEffect, useMemo, useState } from 'react';
import {
  Background,
  Controls,
  MarkerType,
  MiniMap,
  ReactFlow,
  type Edge,
  type EdgeMouseHandler,
  type Node,
  type NodeMouseHandler,
} from '@xyflow/react';

import { manifest, type FlowEdge, type FlowNode } from './manifest';
import { stageColor } from './components/nodeShapes';
import { NodeListSidebar } from './components/NodeListSidebar';
import { NodeDetailPanel } from './components/NodeDetailPanel';
import { FilterBar, type FilterState } from './components/FilterBar';

// ─── 검색 매칭 ──────────────────────────────────

function matchesSearch(node: FlowNode, query: string): boolean {
  if (!query) return true;
  const q = query.replace(/\s+/g, '').toLowerCase();
  const haystack = [
    node.id,
    node.label,
    node.shortDescription,
    node.stage,
    node.menu ?? '',
    node.kind,
    node.file ?? '',
    node.symbol ?? '',
    ...(node.tags ?? []),
    ...(node.inputs ?? []),
    ...(node.outputs ?? []),
    node.notes ?? '',
  ]
    .join(' ')
    .replace(/\s+/g, '')
    .toLowerCase();
  return haystack.includes(q);
}

// ─── 레이아웃 계산 (stage/menu 그리드) ──────────────────

const COL_WIDTH = 440;
const ROW_HEIGHT = 170;
const COL_PADDING_X = 80;
const COL_PADDING_Y = 80;

interface LayoutResult {
  nodes: Node[];
  edges: Edge[];
}

function buildLayout(
  flowNodes: FlowNode[],
  flowEdges: FlowEdge[],
  groupBy: 'stage' | 'menu',
  selectedNodeId: string | null,
  selectedEdgeId: string | null,
  focusedNodeIds: Set<string> | null,
): LayoutResult {
  // 1. 그룹 키별로 노드 분류 (stage 모드는 정해진 순서, menu 모드는 정렬)
  const STAGE_ORDER = [
    '수집',
    '캐시',
    '파이프라인',
    '분석',
    '선단',
    '출력',
    '저장소',
    'API',
    'UI',
    '의사결정',
    '외부',
  ];

  const groups: Record<string, FlowNode[]> = {};
  flowNodes.forEach((n) => {
    const key = (groupBy === 'menu' ? n.menu : n.stage) ?? '기타';
    if (!groups[key]) groups[key] = [];
    groups[key].push(n);
  });

  const groupKeys =
    groupBy === 'stage'
      ? STAGE_ORDER.filter((s) => groups[s] && groups[s].length > 0).concat(
          Object.keys(groups).filter((k) => !STAGE_ORDER.includes(k)),
        )
      : Object.keys(groups).sort();

  // 2. 노드 위치 계산
  const positionedNodes: Node[] = [];
  groupKeys.forEach((key, colIdx) => {
    const list = groups[key];
    list.forEach((node, rowIdx) => {
      const x = COL_PADDING_X + colIdx * COL_WIDTH;
      const y = COL_PADDING_Y + rowIdx * ROW_HEIGHT;
      const accent = stageColor(node.stage);
      const isSelected = selectedNodeId === node.id;
      const isDimmed = focusedNodeIds !== null && !focusedNodeIds.has(node.id);

      positionedNodes.push({
        id: node.id,
        position: { x, y },
        data: { label: node.label, node },
        type: 'default',
        style: {
          padding: 0,
          background: 'transparent',
          border: 'none',
          width: 240,
          opacity: isDimmed ? 0.18 : 1,
          transition: 'opacity 0.2s',
          pointerEvents: isDimmed ? 'none' : 'auto',
        },
      } as Node);
      const last = positionedNodes[positionedNodes.length - 1];
      last.data = {
        label: (
          <div
            className="sf-node-body"
            data-status={node.status}
            data-trigger={node.trigger}
            data-selected={isSelected}
            data-dimmed={isDimmed}
            style={{ ['--accent' as never]: accent }}
          >
            <div className="sf-node-stage">{node.stage} · {node.kind}</div>
            <div className="sf-node-label">{node.label}</div>
            <div className="sf-node-id">{node.id}</div>
          </div>
        ),
        node,
      } as Record<string, unknown>;
    });
  });

  // 3. 엣지 변환 — source별 offset으로 겹침 완화
  const sourceCounter: Record<string, number> = {};
  const positionedEdges: Edge[] = flowEdges.map((edge) => {
    const isSelected = selectedEdgeId === edge.id;
    const color =
      edge.kind === 'feedback'
        ? '#fbbf24'
        : edge.kind === 'trigger'
          ? '#86efac'
          : '#94a3b8';

    // 같은 source에서 나가는 N번째 엣지에 offset 증가 → 경로 분산
    const idxFromSource = sourceCounter[edge.source] ?? 0;
    sourceCounter[edge.source] = idxFromSource + 1;
    // 20~80px 사이에서 분산 (노드 너비 240 기준)
    const pathOffset = 20 + idxFromSource * 14;

    // 포커스 모드: 선택 노드와 연결된 엣지만 불투명
    const isFocused =
      focusedNodeIds === null ||
      (focusedNodeIds.has(edge.source) && focusedNodeIds.has(edge.target));
    const isHighlighted =
      isSelected ||
      (selectedNodeId !== null &&
        (edge.source === selectedNodeId || edge.target === selectedNodeId));

    const strokeColor = isHighlighted ? '#f8fafc' : color;
    const opacity = isFocused ? 1 : 0.08;

    return {
      id: edge.id,
      source: edge.source,
      target: edge.target,
      label: edge.label,
      type: 'smoothstep',
      pathOptions: { offset: pathOffset, borderRadius: 14 },
      animated: isHighlighted || (isFocused && edge.kind === 'feedback'),
      markerEnd: {
        type: MarkerType.ArrowClosed,
        color: strokeColor,
        width: 18,
        height: 18,
      },
      style: {
        stroke: strokeColor,
        strokeWidth: isHighlighted ? 2.8 : 1.6,
        strokeDasharray: edge.kind === 'feedback' ? '6 4' : undefined,
        opacity,
        transition: 'opacity 0.2s, stroke 0.2s, stroke-width 0.2s',
      },
      labelStyle: { fill: isFocused ? '#cbd5e1' : '#475569', fontSize: 10 },
      labelBgStyle: { fill: '#0f172a', fillOpacity: isFocused ? 0.9 : 0.3 },
      zIndex: isHighlighted ? 1000 : isFocused ? 10 : 0,
    };
  });

  return { nodes: positionedNodes, edges: positionedEdges };
}

// ─── 메인 뷰어 ──────────────────────────────────

export function SystemFlowViewer() {
  const [filter, setFilter] = useState<FilterState>({
    search: '',
    stage: '전체',
    menu: '전체',
    kinds: new Set(),
    triggers: new Set(),
    statuses: new Set(),
  });
  const [groupBy, setGroupBy] = useState<'stage' | 'menu'>('stage');
  const [selectedNodeId, setSelectedNodeId] = useState<string | null>(null);
  const [selectedEdgeId, setSelectedEdgeId] = useState<string | null>(null);

  // URL hash deep link
  useEffect(() => {
    const hash = window.location.hash;
    const m = hash.match(/^#node=(.+)$/);
    if (m) {
      const id = decodeURIComponent(m[1]);
      if (manifest.nodes.find((n) => n.id === id)) {
        setSelectedNodeId(id);
      }
    }
  }, []);

  // 선택된 노드 변경 시 hash 업데이트
  useEffect(() => {
    if (selectedNodeId) {
      window.history.replaceState(null, '', `#node=${selectedNodeId}`);
    }
  }, [selectedNodeId]);

  // 필터링된 노드
  const filteredNodes = useMemo(() => {
    return manifest.nodes.filter((n) => {
      if (filter.stage !== '전체' && n.stage !== filter.stage) return false;
      if (filter.menu !== '전체' && n.menu !== filter.menu) return false;
      if (filter.kinds.size > 0 && !filter.kinds.has(n.kind)) return false;
      if (filter.triggers.size > 0 && !filter.triggers.has(n.trigger)) return false;
      if (filter.statuses.size > 0 && !filter.statuses.has(n.status)) return false;
      if (!matchesSearch(n, filter.search)) return false;
      return true;
    });
  }, [filter]);

  // 필터링된 노드 ID 집합
  const filteredNodeIds = useMemo(
    () => new Set(filteredNodes.map((n) => n.id)),
    [filteredNodes],
  );

  // 필터링된 엣지 (양 끝이 필터된 노드일 때만)
  const filteredEdges = useMemo(() => {
    return manifest.edges.filter(
      (e) => filteredNodeIds.has(e.source) && filteredNodeIds.has(e.target),
    );
  }, [filteredNodeIds]);

  // 포커스 모드: 선택된 노드가 있으면 1-hop 연결 노드 집합 계산
  // 선택이 없거나 엣지가 선택되면 null (전체 활성)
  const focusedNodeIds = useMemo<Set<string> | null>(() => {
    if (!selectedNodeId) return null;
    const connected = new Set<string>([selectedNodeId]);
    filteredEdges.forEach((e) => {
      if (e.source === selectedNodeId) connected.add(e.target);
      if (e.target === selectedNodeId) connected.add(e.source);
    });
    return connected;
  }, [selectedNodeId, filteredEdges]);

  // React Flow 노드/엣지
  const { nodes: rfNodes, edges: rfEdges } = useMemo(
    () =>
      buildLayout(
        filteredNodes,
        filteredEdges,
        groupBy,
        selectedNodeId,
        selectedEdgeId,
        focusedNodeIds,
      ),
    [filteredNodes, filteredEdges, groupBy, selectedNodeId, selectedEdgeId, focusedNodeIds],
  );

  const selectedNode = useMemo(
    () => manifest.nodes.find((n) => n.id === selectedNodeId) ?? null,
    [selectedNodeId],
  );
  const selectedEdge = useMemo(
    () => manifest.edges.find((e) => e.id === selectedEdgeId) ?? null,
    [selectedEdgeId],
  );

  const onNodeClick: NodeMouseHandler = useCallback((_e, node) => {
    setSelectedNodeId(node.id);
    setSelectedEdgeId(null);
  }, []);

  const onEdgeClick: EdgeMouseHandler = useCallback((_e, edge) => {
    setSelectedEdgeId(edge.id);
    setSelectedNodeId(null);
  }, []);

  const onPaneClick = useCallback(() => {
    setSelectedNodeId(null);
    setSelectedEdgeId(null);
  }, []);

  const handleSelectNode = useCallback((id: string) => {
    setSelectedNodeId(id);
    setSelectedEdgeId(null);
  }, []);

  return (
    <div className="system-flow-shell">
      <FilterBar
        filter={filter}
        onChange={setFilter}
        groupBy={groupBy}
        onGroupByChange={setGroupBy}
        meta={manifest.meta}
      />
      <NodeListSidebar
        nodes={filteredNodes}
        selectedNodeId={selectedNodeId}
        groupBy={groupBy}
        onSelectNode={handleSelectNode}
      />
      <div className="sf-canvas">
        {focusedNodeIds && selectedNode && (
          <div className="sf-focus-badge">
            <span>
              <strong>{selectedNode.label}</strong> 연결 노드 {focusedNodeIds.size - 1}개만 표시 중
            </span>
            <button type="button" onClick={onPaneClick}>전체 보기 ✕</button>
          </div>
        )}
        <ReactFlow
          nodes={rfNodes}
          edges={rfEdges}
          onNodeClick={onNodeClick}
          onEdgeClick={onEdgeClick}
          onPaneClick={onPaneClick}
          fitView
          fitViewOptions={{ padding: 0.2 }}
          minZoom={0.1}
          maxZoom={1.5}
          proOptions={{ hideAttribution: true }}
        >
          <Background color="#1e293b" gap={24} size={1.2} />
          <Controls showInteractive={false} />
          <MiniMap
            pannable
            zoomable
            nodeColor={(node) => {
              const meta = manifest.nodes.find((n) => n.id === node.id);
              return meta ? `${stageColor(meta.stage)}aa` : '#334155';
            }}
            nodeStrokeColor={(node) => {
              const meta = manifest.nodes.find((n) => n.id === node.id);
              return meta ? stageColor(meta.stage) : '#94a3b8';
            }}
            maskColor="rgba(7, 17, 31, 0.7)"
          />
        </ReactFlow>
      </div>
      <NodeDetailPanel
        selectedNode={selectedNode}
        selectedEdge={selectedEdge}
        allNodes={manifest.nodes}
        allEdges={manifest.edges}
        onSelectNode={handleSelectNode}
      />
    </div>
  );
}
