import { useMemo } from 'react';
import type { Node, Edge } from '@xyflow/react';
import type { ExpertTransformationDAGResult, CategoryDefinition } from '../../types';

interface LayoutConfig {
  isDark: boolean;
  fontSize?: number;
  crossoverSource?: string; // If set, marks all attributes as crossed over from this source
}

const COLOR_PALETTE = [
  { dark: '#177ddc', light: '#1890ff' }, // blue
  { dark: '#854eca', light: '#722ed1' }, // purple
  { dark: '#13a8a8', light: '#13c2c2' }, // cyan
  { dark: '#d87a16', light: '#fa8c16' }, // orange
  { dark: '#49aa19', light: '#52c41a' }, // green
  { dark: '#1677ff', light: '#1890ff' }, // blue
  { dark: '#eb2f96', light: '#f759ab' }, // magenta
  { dark: '#faad14', light: '#ffc53d' }, // gold
];

// Estimate description card height based on text length
function estimateDescriptionHeight(description: string): number {
  const cardWidth = 400;
  const padding = 24;
  const headerHeight = 32;
  const charPerLine = Math.floor(cardWidth / 14);
  const lineHeight = 20;
  const lines = Math.ceil(description.length / charPerLine);
  return Math.min(padding + headerHeight + lines * lineHeight, 140);
}

export function useExpertTransformationLayout(
  data: ExpertTransformationDAGResult | null,
  categories: CategoryDefinition[],
  config: LayoutConfig
): { nodes: Node[]; edges: Edge[] } {
  return useMemo(() => {
    if (!data || data.results.length === 0) {
      return { nodes: [], edges: [] };
    }

    const { isDark, fontSize = 13, crossoverSource } = config;
    const nodes: Node[] = [];
    const edges: Edge[] = [];

    // Layout constants
    const colStep = 140;
    const categoryRowGap = 160; // increased gap
    const minItemGap = 12;
    const expertKeywordGap = 24; // gap between expert keywords

    const queryX = 0;
    const categoryX = colStep;
    const originalAttrX = colStep * 2;
    const keywordX = colStep * 3.2;
    const descriptionX = colStep * 4.8;

    // Build category color map
    const categoryColors: Record<string, string> = {};
    categories.forEach((cat, index) => {
      const paletteIndex = index % COLOR_PALETTE.length;
      categoryColors[cat.name] = isDark
        ? COLOR_PALETTE[paletteIndex].dark
        : COLOR_PALETTE[paletteIndex].light;
    });

    // Pre-calculate layouts for each category
    interface CategoryLayout {
      attributeGroups: Array<{
        attribute: string;
        expertKeywords: Array<{
          keyword: string;
          expertName: string;
          expertId: string;
          yOffset: number;
        }>;
        descriptionYPositions: number[];
        totalHeight: number;
      }>;
      totalHeight: number;
    }

    const categoryLayouts: CategoryLayout[] = data.results.map((result) => {
      // Group expert keywords by source_attribute
      const attributeMap = new Map<string, typeof result.expert_keywords>();
      result.expert_keywords.forEach((kw) => {
        if (!attributeMap.has(kw.source_attribute)) {
          attributeMap.set(kw.source_attribute, []);
        }
        attributeMap.get(kw.source_attribute)!.push(kw);
      });

      const attributeGroups: CategoryLayout['attributeGroups'] = [];
      let categoryTotalHeight = 0;

      // Process each source attribute group
      result.original_attributes.forEach((attr) => {
        const expertKeywords = attributeMap.get(attr) || [];
        const keywordPositions: typeof attributeGroups[0]['expertKeywords'] = [];
        const descYPositions: number[] = [];
        let currentY = 0;

        expertKeywords.forEach((kw) => {
          keywordPositions.push({
            keyword: kw.keyword,
            expertName: kw.expert_name,
            expertId: kw.expert_id,
            yOffset: currentY,
          });

          // Find matching description
          const matchingDesc = result.descriptions.find(
            (d) => d.keyword === kw.keyword && d.expert_id === kw.expert_id
          );
          const descHeight = matchingDesc
            ? estimateDescriptionHeight(matchingDesc.description)
            : 50;

          descYPositions.push(currentY);
          currentY += descHeight + expertKeywordGap;
        });

        const groupHeight = currentY > 0 ? currentY - expertKeywordGap + minItemGap : 0;

        attributeGroups.push({
          attribute: attr,
          expertKeywords: keywordPositions,
          descriptionYPositions: descYPositions,
          totalHeight: groupHeight,
        });

        categoryTotalHeight += groupHeight;
      });

      return { attributeGroups, totalHeight: categoryTotalHeight };
    });

    // Calculate total height for query centering
    const totalHeight = categoryLayouts.reduce(
      (sum, layout, i) =>
        sum + layout.totalHeight + (i < categoryLayouts.length - 1 ? categoryRowGap : 0),
      0
    );

    // Add Query node (centered vertically)
    const queryY = totalHeight / 2 - 20;
    nodes.push({
      id: 'query-node',
      type: 'query',
      position: { x: queryX, y: queryY },
      data: {
        label: data.query,
        isDark,
        fontSize,
      },
      draggable: false,
      selectable: false,
    });

    // Track current Y position
    let currentY = 0;

    // Process each category result
    data.results.forEach((result, catIndex) => {
      const categoryId = `category-${catIndex}`;
      const color = categoryColors[result.category] || '#666';
      const layout = categoryLayouts[catIndex];

      // Category Y position (centered within its group)
      const categoryY = currentY + layout.totalHeight / 2 - 20;

      // Add Group Node (Background)
      const groupPaddingX = 60;
      const groupPaddingY = 40;
      const groupHeight = layout.totalHeight + (groupPaddingY * 2);
      const groupWidth = descriptionX + 450; // Cover everything up to description end

      nodes.push({
        id: `group-${catIndex}`,
        type: 'group',
        position: {
          x: categoryX - groupPaddingX,
          y: currentY - groupPaddingY
        },
        data: {
          label: result.category, // Add label to group
          color: color,
          width: groupWidth + groupPaddingX,
          height: groupHeight,
          isDark,
        },
        draggable: false,
        selectable: false,
        zIndex: -1,
      });

      // Add category node
      nodes.push({
        id: categoryId,
        type: 'category',
        position: { x: categoryX, y: categoryY },
        data: {
          label: result.category,
          color,
          attributeCount: result.original_attributes.length,
          isDark,
        },
        draggable: false,
        selectable: false,
      });

      // Edge from query to category
      edges.push({
        id: `edge-query-${categoryId}`,
        source: 'query-node',
        target: categoryId,
        type: 'smoothstep',
        style: { stroke: color, strokeWidth: 2, opacity: 0.6 },
        animated: false,
      });

      // Process each attribute group
      layout.attributeGroups.forEach((group, attrIndex) => {
        const attrId = `orig-${catIndex}-${attrIndex}`;
        const attrY = currentY + group.totalHeight / 2 - 12;

        // Add original attribute node
        nodes.push({
          id: attrId,
          type: 'originalAttribute',
          position: { x: originalAttrX, y: attrY },
          data: {
            label: group.attribute,
            color,
            isDark,
            crossoverSource, // Mark as crossover if source is provided
          },
          draggable: false,
          selectable: false,
        });

        // Edge from category to original attribute
        edges.push({
          id: `edge-${categoryId}-${attrId}`,
          source: categoryId,
          target: attrId,
          type: 'smoothstep',
          style: { stroke: color, strokeWidth: 1, opacity: 0.3 },
          animated: false,
        });

        // Add expert keyword and description nodes
        group.expertKeywords.forEach((kw, kwIndex) => {
          const keywordId = `keyword-${catIndex}-${attrIndex}-${kwIndex}`;
          const keywordY = currentY + kw.yOffset;

          // Expert keyword node
          nodes.push({
            id: keywordId,
            type: 'expertKeyword',
            position: { x: keywordX, y: keywordY },
            data: {
              label: kw.keyword,
              expertName: kw.expertName,
              expertId: kw.expertId,
              color,
              isDark,
            },
            draggable: false,
            selectable: false,
          });

          // Edge from original attribute to expert keyword
          edges.push({
            id: `edge-${attrId}-${keywordId}`,
            source: attrId,
            target: keywordId,
            type: 'smoothstep',
            style: {
              stroke: '#1890ff',
              strokeWidth: 2,
              opacity: 0.7,
              strokeDasharray: '5,5',
            },
            animated: true,
          });

          // Find matching description - match by expert_id and source_attribute
          // (more lenient than exact keyword match since LLM may return slightly different text)
          const matchingDesc = result.descriptions.find(
            (d) => d.expert_id === kw.expertId &&
              (d.keyword === kw.keyword ||
                d.keyword.includes(kw.keyword) ||
                kw.keyword.includes(d.keyword))
          );

          // Always show a description node (use fallback if not found)
          const descId = `desc-${catIndex}-${attrIndex}-${kwIndex}`;
          const descY = currentY + group.descriptionYPositions[kwIndex];

          nodes.push({
            id: descId,
            type: 'description',
            position: { x: descriptionX, y: descY },
            data: {
              keyword: matchingDesc?.keyword || kw.keyword,
              description: matchingDesc?.description || '（描述生成中...）',
              color,
              isDark,
            },
            draggable: false,
            selectable: false,
          });

          // Edge from expert keyword to description (always show)
          edges.push({
            id: `edge-${keywordId}-${descId}`,
            source: keywordId,
            target: descId,
            type: 'smoothstep',
            style: {
              stroke: matchingDesc ? '#52c41a' : '#999',
              strokeWidth: 2,
              opacity: matchingDesc ? 0.6 : 0.3,
              strokeDasharray: matchingDesc ? undefined : '4,4',
            },
            animated: !matchingDesc,
          });
        });

        currentY += group.totalHeight;
      });

      // Add divider line between categories (except after last one)
      if (catIndex < data.results.length - 1) {
        const dividerY = currentY + categoryRowGap / 2 - 1;
        nodes.push({
          id: `divider-${catIndex}`,
          type: 'divider',
          position: { x: queryX, y: dividerY },
          data: {
            isDark,
          },
          draggable: false,
          selectable: false,
          style: {
            width: descriptionX + 400,
            zIndex: -1,
          },
        });

        currentY += categoryRowGap;
      }
    });

    return { nodes, edges };
  }, [data, categories, config]);
}
