import { blueprintToModuleOutline, normalizeModuleOutline } from './appModuleOutline.js'

const CANVAS_MIN_WIDTH = 640
const DEFAULT_ROOT_TITLE = '系统模块设计'
const CAPTION = '系统功能结构图'

const DENSITY_CONFIGS = {
  compact: {
    canvasHeight: 390,
    rootWidth: 230,
    rootHeight: 40,
    groupWidth: 112,
    groupHeight: 38,
    moduleWidth: 44,
    moduleHeight: 124,
    moduleGap: 10,
    groupGap: 72,
    rootY: 16,
    rootBranchY: 78,
    groupY: 98,
    moduleBranchY: 160,
    moduleY: 180
  },
  standard: {
    canvasHeight: 420,
    rootWidth: 260,
    rootHeight: 42,
    groupWidth: 128,
    groupHeight: 40,
    moduleWidth: 48,
    moduleHeight: 138,
    moduleGap: 14,
    groupGap: 96,
    rootY: 18,
    rootBranchY: 88,
    groupY: 110,
    moduleBranchY: 178,
    moduleY: 198
  },
  relaxed: {
    canvasHeight: 460,
    rootWidth: 280,
    rootHeight: 46,
    groupWidth: 140,
    groupHeight: 44,
    moduleWidth: 54,
    moduleHeight: 154,
    moduleGap: 18,
    groupGap: 124,
    rootY: 20,
    rootBranchY: 100,
    groupY: 126,
    moduleBranchY: 202,
    moduleY: 224
  }
}

const FONT_SIZE_CONFIGS = {
  small: { root: 16, group: 15, module: 15, caption: 13 },
  standard: { root: 17, group: 16, module: 16, caption: 14 },
  large: { root: 19, group: 18, module: 18, caption: 15 }
}

function cleanText(value, fallback = '') {
  const text = String(value ?? '').trim()
  return text || fallback
}

function normalizeKey(value, fallback) {
  const text = cleanText(value, fallback)
    .toLowerCase()
    .replace(/[^a-z0-9_]+/g, '_')
    .replace(/_+/g, '_')
    .replace(/^_+|_+$/g, '')
  return text || fallback
}

function uniqueKey(value, fallbackPrefix, index, used) {
  const base = normalizeKey(value, `${fallbackPrefix}_${index + 1}`)
  let candidate = base
  let suffix = 2
  while (used.has(candidate)) {
    candidate = `${base}_${suffix}`
    suffix += 1
  }
  used.add(candidate)
  return candidate
}

function normalizedOptions(options = {}) {
  const density = DENSITY_CONFIGS[options.density] ? options.density : 'standard'
  const direction = options.direction === 'vertical' ? 'vertical' : 'horizontal'
  const fontSize = FONT_SIZE_CONFIGS[options.fontSize] ? options.fontSize : 'standard'
  return { density, direction, fontSize }
}

function layoutConfig(options) {
  return DENSITY_CONFIGS[options.density]
}

function fontConfig(options) {
  return FONT_SIZE_CONFIGS[options.fontSize]
}

function sourceToOutline(source, options = {}) {
  const input = source && typeof source === 'object' ? source : {}
  if (Array.isArray(input.groups)) {
    return normalizeModuleOutline(input)
  }
  return blueprintToModuleOutline(input, options)
}

function menuTitle(menu, fallback) {
  return cleanText(menu?.name ?? menu?.title ?? menu?.code, fallback)
}

function normalizeBlueprintMenus(menus, lane, fallbackPrefix) {
  const used = new Set()
  return (Array.isArray(menus) ? menus : []).filter((menu) => {
    return menu && typeof menu === 'object' && cleanText(menu.name ?? menu.title ?? menu.code)
  }).map((menu, index) => {
    const code = uniqueKey(menu.code ?? menu.name ?? menu.title, fallbackPrefix, index, used)
    return {
      code,
      id: `${lane}:${code}`,
      title: menuTitle(menu, code),
      kind: 'module',
      lane,
      vertical: true
    }
  })
}

function normalizeOutlineItems(items, lane, fallbackPrefix) {
  return (Array.isArray(items) ? items : []).filter(Boolean).map((item, index) => {
    const code = `${fallbackPrefix}_${index + 1}`
    return {
      code,
      id: `${lane}:${code}`,
      title: cleanText(item.title ?? item.name ?? item.code, code),
      kind: 'module',
      lane,
      vertical: true
    }
  })
}

function groupWidth(itemCount, config) {
  const moduleWidth = itemCount * config.moduleWidth + Math.max(itemCount - 1, 0) * config.moduleGap
  return Math.max(config.groupWidth, moduleWidth)
}

function centerX(node) {
  return node.x + node.width / 2
}

function bottomY(node) {
  return node.y + node.height
}

function treePath(source, target, branchY) {
  const sourceX = centerX(source)
  const targetX = centerX(target)
  const sourceY = bottomY(source)
  const targetY = target.y

  if (sourceX === targetX) {
    return `M ${sourceX} ${sourceY} V ${targetY}`
  }
  return `M ${sourceX} ${sourceY} V ${branchY} H ${targetX} V ${targetY}`
}

function horizontalTreePath(source, target) {
  const sourceX = source.x + source.width
  const sourceY = source.y + source.height / 2
  const targetX = target.x
  const targetY = target.y + target.height / 2
  const branchX = sourceX + (targetX - sourceX) / 2
  return `M ${sourceX} ${sourceY} H ${branchX} V ${targetY} H ${targetX}`
}

function buildSections(outline, source = {}) {
  const input = source && typeof source === 'object' ? source : {}
  if (!Array.isArray(input.groups)) {
    return [
      {
        key: 'admin',
        title: '管理员端',
        lane: 'admin',
        items: normalizeBlueprintMenus(input.adminMenus, 'admin', 'admin_menu')
      },
      {
        key: 'frontend',
        title: '普通用户端',
        lane: 'frontend',
        items: normalizeBlueprintMenus(input.frontendMenus, 'frontend', 'frontend_menu')
      }
    ].filter((section) => section.items.length)
  }

  return (Array.isArray(outline.groups) ? outline.groups : [])
    .map((group, index) => {
      const lane = `group_${index + 1}`
      return {
        key: lane,
        title: cleanText(group.title, `模块${index + 1}`),
        lane,
        items: normalizeOutlineItems(group.items, lane, 'module')
      }
    })
    .filter((section) => section.items.length)
}

function buildHorizontalGraph(rootTitle, sections, graphOptions, config, fonts) {
  const sectionWidths = sections.map((section) => groupWidth(section.items.length, config))
  const sectionsWidth = sectionWidths.reduce((sum, width) => sum + width, 0) + config.groupGap * Math.max(sections.length - 1, 0)
  const canvasWidth = Math.max(CANVAS_MIN_WIDTH, sectionsWidth + 96 + config.groupGap)
  const root = {
    id: 'root',
    title: rootTitle,
    kind: 'root',
    lane: 'root',
    x: (canvasWidth - config.rootWidth) / 2,
    y: config.rootY,
    width: config.rootWidth,
    height: config.rootHeight,
    fontSize: fonts.root
  }

  const nodes = [root]
  const edges = []
  const groups = []
  let sectionX = (canvasWidth - sectionsWidth) / 2

  sections.forEach((section, sectionIndex) => {
    const width = sectionWidths[sectionIndex]
    const groupNode = {
      id: `group:${section.key}`,
      title: section.title,
      kind: 'group',
      lane: section.lane,
      x: sectionX + (width - config.groupWidth) / 2,
      y: config.groupY,
      width: config.groupWidth,
      height: config.groupHeight,
      fontSize: fonts.group
    }
    const moduleRowWidth = section.items.length * config.moduleWidth + Math.max(section.items.length - 1, 0) * config.moduleGap
    const moduleStartX = sectionX + (width - moduleRowWidth) / 2
    const childIds = section.items.map((item) => item.id)

    groups.push({
      key: section.key,
      title: section.title,
      nodeId: groupNode.id,
      childIds,
      x: sectionX,
      y: config.groupY,
      width,
      height: config.moduleY + config.moduleHeight - config.groupY
    })
    nodes.push(groupNode)
    edges.push({
      id: `root->${groupNode.id}`,
      source: root.id,
      target: groupNode.id,
      path: treePath(root, groupNode, config.rootBranchY)
    })

    section.items.forEach((item, itemIndex) => {
      const moduleNode = {
        ...item,
        x: moduleStartX + itemIndex * (config.moduleWidth + config.moduleGap),
        y: config.moduleY,
        width: config.moduleWidth,
        height: config.moduleHeight,
        fontSize: fonts.module
      }
      nodes.push(moduleNode)
      edges.push({
        id: `${groupNode.id}->${moduleNode.id}`,
        source: groupNode.id,
        target: moduleNode.id,
        path: treePath(groupNode, moduleNode, config.moduleBranchY)
      })
    })

    sectionX += width + config.groupGap
  })

  return {
    title: root.title,
    caption: CAPTION,
    nodes,
    edges,
    groups,
    options: graphOptions,
    canvas: {
      width: canvasWidth,
      height: config.canvasHeight
    }
  }
}

function buildVerticalGraph(rootTitle, sections, graphOptions, config, fonts) {
  const groupBlockHeight = Math.max(config.groupHeight, config.moduleHeight / 2)
  const groupGap = config.groupGap / 2
  const moduleGap = config.moduleGap
  const leftX = 48
  const groupX = leftX + config.rootWidth + 80
  const moduleX = groupX + config.groupWidth + 80
  const startY = 36
  const root = {
    id: 'root',
    title: rootTitle,
    kind: 'root',
    lane: 'root',
    x: leftX,
    y: startY,
    width: config.rootWidth,
    height: config.rootHeight,
    fontSize: fonts.root
  }

  const nodes = [root]
  const edges = []
  const groups = []
  let y = startY

  sections.forEach((section) => {
    const moduleColumnHeight = section.items.length * config.groupHeight + Math.max(section.items.length - 1, 0) * moduleGap
    const blockHeight = Math.max(groupBlockHeight, moduleColumnHeight)
    const groupNode = {
      id: `group:${section.key}`,
      title: section.title,
      kind: 'group',
      lane: section.lane,
      x: groupX,
      y,
      width: config.groupWidth,
      height: config.groupHeight,
      fontSize: fonts.group
    }
    nodes.push(groupNode)
    edges.push({
      id: `root->${groupNode.id}`,
      source: root.id,
      target: groupNode.id,
      path: horizontalTreePath(root, groupNode)
    })

    const childIds = []
    section.items.forEach((item, itemIndex) => {
      const moduleNode = {
        ...item,
        x: moduleX,
        y: y + itemIndex * (config.groupHeight + moduleGap),
        width: config.groupWidth,
        height: config.groupHeight,
        vertical: false,
        fontSize: fonts.module
      }
      childIds.push(moduleNode.id)
      nodes.push(moduleNode)
      edges.push({
        id: `${groupNode.id}->${moduleNode.id}`,
        source: groupNode.id,
        target: moduleNode.id,
        path: horizontalTreePath(groupNode, moduleNode)
      })
    })

    groups.push({
      key: section.key,
      title: section.title,
      nodeId: groupNode.id,
      childIds,
      x: groupX,
      y,
      width: moduleX + config.groupWidth - groupX,
      height: blockHeight
    })
    y += blockHeight + groupGap
  })

  return {
    title: root.title,
    caption: CAPTION,
    nodes,
    edges,
    groups,
    options: graphOptions,
    canvas: {
      width: moduleX + config.groupWidth + 80,
      height: Math.max(config.canvasHeight + 80, y + 48)
    }
  }
}

export function buildAppModuleGraph(source = {}, options = {}) {
  const graphOptions = normalizedOptions(options)
  const config = layoutConfig(graphOptions)
  const fonts = fontConfig(graphOptions)
  const outline = sourceToOutline(source, options)
  const rootTitle = cleanText(outline.title, DEFAULT_ROOT_TITLE)
  const sections = buildSections(outline, source)

  if (!sections.length) {
    return {
      title: rootTitle,
      caption: CAPTION,
      nodes: [],
      edges: [],
      groups: [],
      options: graphOptions,
      canvas: {
        width: CANVAS_MIN_WIDTH,
        height: config.canvasHeight
      }
    }
  }

  if (graphOptions.direction === 'vertical') {
    return buildVerticalGraph(rootTitle, sections, graphOptions, config, fonts)
  }

  return buildHorizontalGraph(rootTitle, sections, graphOptions, config, fonts)
}
