// ============ 常量配置 ============

const TEXT_ELEMENTS = ['p', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'blockquote', 'li'];
const HEADING_ELEMENTS = ['h1', 'h2', 'h3', 'h4', 'h5', 'h6'];
const NON_TEXT_ELEMENTS = ['img', 'table', 'video', 'audio', 'canvas', 'svg', 'iframe', 'pre'];
const ALL_ELEMENTS = [...TEXT_ELEMENTS, ...NON_TEXT_ELEMENTS];
const DIRECT_CHILD_SELECTOR = ALL_ELEMENTS.map(tag => `div > ${tag}`).join(', ');
const DEFAULT_LINE_HEIGHT_RATIO = 1.54;

// ============ 类型定义 ============

interface LineSpanOptions {
  lineNum: number;
  top: number;
  height: number;
  isGap?: boolean;
  isBreakpoint?: boolean;
}

interface ElementMetrics {
  top: number;
  height: number;
  marginTop: number;
  marginBottom: number;
  paddingTop: number;
  lineHeight: number;
  lines: number;
  isHeading: boolean;
}

interface GapLinesOptions {
  fragment: DocumentFragment;
  gapStart: number;
  gapEnd: number;
  lineHeight: number;
  startLine: number;
}

interface LineNumbersState {
  $el?: HTMLElement;
  totalLines: number;
  resizeObserver: ResizeObserver | null;
  init(): void;
  calculateLineNumbers(): void;
  destroy(): void;
}

// ============ 工具函数 ============

const isNonTextElement = (tagName: string): boolean =>
  NON_TEXT_ELEMENTS.includes(tagName.toLowerCase());

const isHeadingElement = (tagName: string): boolean =>
  HEADING_ELEMENTS.includes(tagName.toLowerCase());

const isEmptyElement = (element: HTMLElement): boolean => {
  if (isNonTextElement(element.tagName.toLowerCase())) return false;
  return !element.textContent?.trim();
};

const getStyleValue = (style: CSSStyleDeclaration, prop: 'paddingTop' | 'paddingBottom' | 'lineHeight' | 'fontSize' | 'marginTop' | 'marginBottom'): number =>
  parseFloat(style[prop]) || 0;

const getDefaultLineHeight = (element: HTMLElement): number => {
  const style = getComputedStyle(element);
  return getStyleValue(style, 'lineHeight') || getStyleValue(style, 'fontSize') * DEFAULT_LINE_HEIGHT_RATIO;
};

// 获取元素度量信息（合并多次 getComputedStyle 调用）
const getElementMetrics = (element: HTMLElement, containerRect: DOMRect, defaultLineHeight: number): ElementMetrics => {
  const tagName = element.tagName.toLowerCase();
  const style = getComputedStyle(element);
  const paddingTop = getStyleValue(style, 'paddingTop');
  const paddingBottom = getStyleValue(style, 'paddingBottom');
  const marginTop = getStyleValue(style, 'marginTop');
  const marginBottom = getStyleValue(style, 'marginBottom');
  const height = element.offsetHeight;
  const contentHeight = height - paddingTop - paddingBottom;
  const isNonText = isNonTextElement(tagName);

  const lineHeight = isNonText ? contentHeight : (getStyleValue(style, 'lineHeight') || defaultLineHeight);
  const lines = isNonText ? 1 : Math.max(1, Math.round(contentHeight / lineHeight));

  return {
    top: element.getBoundingClientRect().top - containerRect.top,
    height,
    marginTop,
    marginBottom,
    paddingTop,
    lineHeight: contentHeight / lines,
    lines,
    isHeading: isHeadingElement(tagName)
  };
};

// ============ DOM 创建 ============

const createLineSpan = (options: LineSpanOptions): HTMLSpanElement => {
  const { lineNum, top, height, isGap = false, isBreakpoint = false } = options;
  const span = document.createElement('span');

  span.className = `line-number${isGap ? ' line-number--gap' : ''}${isBreakpoint ? ' line-number--breakpoint' : ''}`;
  span.style.cssText = `top:${top}px;height:${height}px;line-height:${height}px`;
  span.textContent = String(lineNum);

  if (isBreakpoint) {
    const breakpoint = document.createElement('span');
    breakpoint.className = 'line-breakpoint';
    breakpoint.textContent = '●';
    span.appendChild(breakpoint);
  }

  return span;
};

// ============ 行号生成 ============

const appendGapLines = (options: GapLinesOptions): number => {
  const { fragment, gapStart, gapEnd, lineHeight, startLine } = options;
  const gapHeight = gapEnd - gapStart;

  if (gapHeight < lineHeight) return startLine;

  const gapLines = Math.round(gapHeight / lineHeight);

  for (let i = 0; i < gapLines; i++) {
    fragment.appendChild(createLineSpan({
      lineNum: startLine + i,
      top: gapStart + i * lineHeight,
      height: lineHeight,
      isGap: true
    }));
  }

  return startLine + gapLines;
};

const appendElementLines = (fragment: DocumentFragment, metrics: ElementMetrics, startLine: number): number => {
  const { top, paddingTop, lineHeight, lines, isHeading } = metrics;
  const startTop = top + paddingTop;

  for (let i = 0; i < lines; i++) {
    fragment.appendChild(createLineSpan({
      lineNum: startLine + i,
      top: startTop + i * lineHeight,
      height: lineHeight,
      isBreakpoint: i === 0 && isHeading
    }));
  }

  return startLine + lines;
};

// ============ 主组件 ============

export const postLineNum = (): LineNumbersState => ({
  totalLines: 0,
  resizeObserver: null,

  init() {
    const content = this.$el?.querySelector('.post-content') as HTMLElement;
    if (!content) return;

    const recalculate = () => this.calculateLineNumbers();

    // 延迟计算确保 DOM 渲染完成
    requestAnimationFrame(() => setTimeout(recalculate, 50));

    // 监听内容区域大小变化（带防抖）
    if (typeof ResizeObserver !== 'undefined') {
      let resizeTimer: number;
      this.resizeObserver = new ResizeObserver(() => {
        clearTimeout(resizeTimer);
        resizeTimer = window.setTimeout(recalculate, 100);
      });
      this.resizeObserver.observe(content);
    }

    // 窗口大小变化时重新计算
    let windowResizeTimer: number;
    window.addEventListener('resize', () => {
      clearTimeout(windowResizeTimer);
      windowResizeTimer = window.setTimeout(recalculate, 200);
    });

    // 监听图片加载（使用 once 避免重复触发）
    content.querySelectorAll('img').forEach((img: HTMLImageElement) => {
      if (!img.complete) {
        img.addEventListener('load', recalculate, { once: true });
        img.addEventListener('error', recalculate, { once: true });
      }
    });
  },

  destroy() {
    this.resizeObserver?.disconnect();
  },

  calculateLineNumbers() {
    const content = this.$el?.querySelector('.post-content') as HTMLElement;
    const gutter = this.$el?.querySelector('.post-line-gutter') as HTMLElement;
    if (!content || !gutter) return;

    const container = (content.querySelector(':scope > div') || content) as HTMLElement;
    const containerRect = container.getBoundingClientRect();
    const defaultLineHeight = getDefaultLineHeight(content);

    const elements = Array.from(container.querySelectorAll(DIRECT_CHILD_SELECTOR))
      .filter(el => !isEmptyElement(el as HTMLElement)) as HTMLElement[];

    const fragment = document.createDocumentFragment();
    let currentLine = 1;
    let lastBottom = 0;

    // 派发清空目录事件
    document.dispatchEvent(new CustomEvent('post-toc:clear'));

    for (const element of elements) {
      const metrics = getElementMetrics(element, containerRect, defaultLineHeight);
      const tagName = element.tagName.toLowerCase();

      // 如果是标题元素，派发添加目录事件
      if (metrics.isHeading) {
        const level = parseInt(tagName.charAt(1), 10);
        const title = element.textContent?.trim() || '';
        document.dispatchEvent(new CustomEvent('post-toc:add', {
          detail: { level, title, element, tagName }
        }));
      }

      // 计算实际的内容起始位置（考虑 margin-top）
      const contentStart = metrics.top - metrics.marginTop;

      // 只有当间隙足够大时才填充行号
      if (lastBottom > 0 && contentStart > lastBottom) {
        currentLine = appendGapLines({
          fragment,
          gapStart: lastBottom,
          gapEnd: contentStart,
          lineHeight: defaultLineHeight,
          startLine: currentLine
        });
      }

      currentLine = appendElementLines(fragment, metrics, currentLine);
      // 更新 lastBottom，包含 margin-bottom
      lastBottom = metrics.top + metrics.height + metrics.marginBottom;
    }

    // 派发初始化滚动高亮事件
    document.dispatchEvent(new CustomEvent('post-toc:init-highlight'));

    gutter.innerHTML = '';
    gutter.appendChild(fragment);
    gutter.style.height = `${container.scrollHeight}px`;
    this.totalLines = currentLine - 1;
  }
});
