/**
 * TextNodeMeasurer - 独立的文本测量器
 *
 * 职责：
 * - 测量文本渲染宽度
 * - 计算节点自适应尺寸（宽度按内容自适应，高度按换行行数自适应）
 * - 纯计算逻辑，不依赖渲染器
 *
 * 设计原则：
 * - 使用 OffscreenCanvas 测量文本，与渲染 Canvas 完全解耦
 * - 布局引擎可直接引用，无需经过渲染器
 * - 无副作用，不修改任何外部状态
 */

// ===== 节点样式配置 =====

/** 节点样式常量（单一来源，渲染器和布局引擎共享） */
export const NODE_STYLE = {
  // 字体
  fontSize: 13,
  rootFontSize: 14,
  fontWeight: '400',
  fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif',

  // 宽度范围（自适应：短标签窄，长标签宽，超出换行）
  minWidth: 80,        // 最小宽度（如 "新节点"）
  maxWidth: 160,       // 最大宽度（超出则换行）
  rootMinWidth: 100,   // 根节点最小宽度
  rootMaxWidth: 200,   // 根节点最大宽度

  // 高度
  minHeight: 36,       // 最小高度（单行文本）

  // 内边距
  paddingX: 14,
  paddingY: 8,

  // 行高
  lineHeight: 1.4,
  maxLines: 4,

  // 内容区域常量（测量用）
  collapseIconWidth: 16,
  collapseIconGap: 6,
  countGap: 6,
  descIconReserved: 16,
  minTextWidth: 40
} as const

/** 节点尺寸计算结果 */
export interface NodeSize {
  width: number
  height: number
}

/** 节点文本布局结果（换行文本 + 尺寸） */
export interface NodeTextLayout {
  lines: string[]
  size: NodeSize
}

/**
 * 文本测量器
 */
export class TextNodeMeasurer {
  private ctx: CanvasRenderingContext2D | OffscreenCanvasRenderingContext2D

  constructor() {
    try {
      const offscreen = new OffscreenCanvas(1, 1)
      const ctx = offscreen.getContext('2d')
      if (ctx) {
        this.ctx = ctx
        return
      }
    } catch (_) {
      // OffscreenCanvas 不可用，降级
    }
    const fallbackCanvas = document.createElement('canvas')
    fallbackCanvas.width = 1
    fallbackCanvas.height = 1
    this.ctx = fallbackCanvas.getContext('2d')!
  }

  // ===== 基础测量 =====

  /** 设置字体 */
  private setFont(weight: string, size: number): void {
    this.ctx.font = `${weight} ${size}px ${NODE_STYLE.fontFamily}`
  }

  /** 测量单行文本宽度 */
  measureTextWidth(text: string, fontSize: number = NODE_STYLE.fontSize, fontWeight: string = NODE_STYLE.fontWeight): number {
    this.setFont(fontWeight, fontSize)
    return this.ctx.measureText(text).width
  }

  /** 构建字体字符串（供渲染器使用） */
  buildFontStr(weight: string, size: number): string {
    return `${weight} ${size}px ${NODE_STYLE.fontFamily}`
  }

  // ===== 文本换行 =====

  /**
   * 二分查找截断文本并添加省略号
   * 性能：O(log n) 次 measureText，优于逐字符截断的 O(n)
   */
  private truncateWithEllipsis(text: string, maxWidth: number): string {
    let lo = 0
    let hi = text.length
    while (lo < hi) {
      const mid = Math.ceil((lo + hi) / 2)
      if (this.ctx.measureText(text.slice(0, mid) + '...').width <= maxWidth) {
        lo = mid
      } else {
        hi = mid - 1
      }
    }
    return lo > 0 ? text.slice(0, lo) + '...' : '...'
  }

  /**
   * 将文本按指定宽度自动换行
   * 纯计算，无副作用
   */
  wrapText(text: string, maxWidth: number, fontSize: number = NODE_STYLE.fontSize, fontWeight: string = NODE_STYLE.fontWeight, maxLines: number = NODE_STYLE.maxLines): string[] {
    if (!text) return []
    this.setFont(fontWeight, fontSize)

    const lines: string[] = []
    let currentLine = ''

    for (let i = 0; i < text.length; i++) {
      const char = text[i]
      if (char === '\n') {
        lines.push(currentLine)
        currentLine = ''
        continue
      }

      const testLine = currentLine + char
      if (this.ctx.measureText(testLine).width > maxWidth && currentLine.length > 0) {
        if (lines.length >= maxLines - 1) {
          // 最后一行截断 + 省略号（二分查找最大可容纳字符数）
          const truncated = this.truncateWithEllipsis(currentLine, maxWidth)
          lines.push(truncated)
          return lines
        }
        lines.push(currentLine)
        currentLine = char
      } else {
        currentLine = testLine
      }
    }

    if (currentLine) lines.push(currentLine)
    return lines.length > 0 ? lines : [text]
  }

  // ===== 节点尺寸计算 =====

  /**
   * 计算节点自适应尺寸
   *
   * 宽度策略：
   * - 短标签：宽度收缩到内容宽度（不浪费空间）
   * - 长标签：宽度扩展到 maxWidth，超出部分换行
   * - 始终在 [minWidth, maxWidth] 范围内
   *
   * 高度策略：
   * - 单行文本：minHeight（36px）
   * - 多行文本：paddingY * 2 + 行数 * 行高
   */
  measureNodeSize(label: string, isRoot: boolean = false, hasChildren: boolean = false, collapsed: boolean = false, descendantCount: number = 0): NodeSize {
    const minW = isRoot ? NODE_STYLE.rootMinWidth : NODE_STYLE.minWidth
    const maxW = isRoot ? NODE_STYLE.rootMaxWidth : NODE_STYLE.maxWidth
    const fontSize = isRoot ? NODE_STYLE.rootFontSize : NODE_STYLE.fontSize
    const fontWeight = NODE_STYLE.fontWeight

    // 1. 计算非文本元素占用的水平空间（只算一次，避免重复测量）
    let nonTextWidth = NODE_STYLE.paddingX * 2 + NODE_STYLE.descIconReserved
    if (hasChildren) nonTextWidth += NODE_STYLE.collapseIconWidth + NODE_STYLE.collapseIconGap
    let countTextWidth = 0
    if (collapsed && hasChildren && descendantCount > 0) {
      countTextWidth = this.measureTextWidth(`(${descendantCount})`, fontSize, fontWeight)
      nonTextWidth += countTextWidth + NODE_STYLE.countGap
    }

    // 2. 文本本身宽度 + 非文本空间 = 内容总宽度
    const textWidth = this.measureTextWidth(label, fontSize, fontWeight)
    const contentWidth = textWidth + nonTextWidth

    // 3. 节点宽度 = clamp(contentWidth, minWidth, maxWidth)
    const nodeWidth = Math.max(minW, Math.min(maxW, Math.ceil(contentWidth)))

    // 4. 文本可用宽度 = 节点实际宽度 - 非文本空间
    const availableTextWidth = Math.max(nodeWidth - nonTextWidth, NODE_STYLE.minTextWidth)

    // 5. 换行并计算高度
    const lines = this.wrapText(label, availableTextWidth, fontSize, fontWeight, NODE_STYLE.maxLines)
    const lineHeight = fontSize * NODE_STYLE.lineHeight
    const textHeight = lines.length * lineHeight
    const nodeHeight = Math.max(NODE_STYLE.minHeight, Math.ceil(NODE_STYLE.paddingY * 2 + textHeight))

    return { width: nodeWidth, height: nodeHeight }
  }
}

// 全局单例（避免重复创建 OffscreenCanvas）
let _instance: TextNodeMeasurer | null = null

/** 获取全局 TextNodeMeasurer 实例 */
export function getTextMeasurer(): TextNodeMeasurer {
  if (!_instance) {
    _instance = new TextNodeMeasurer()
  }
  return _instance
}
