/**
 * Markdown 关键字/区块抽取工具
 *
 * 功能：
 * 1. 接收一段 markdown 文本 + 配置
 * 2. 按配置里的关键字在 markdown 中查找对应「段落/区块」
 * 3. 返回结构化的 JSON，方便其它组件使用
 *
 * 约定：
 * - 目前按 “标题 + 标题下方到下一个同级或更高标题前的内容” 作为一个区块
 * - 比如：`### 下一步建议` 下面到下一个 `###` 之前的内容
 */

/**
 * @typedef {Object} SectionConfig
 * @property {string} key - 在返回结果中的唯一 key（如 'nextStepSuggestion'）
 * @property {string} label - 用于匹配的关键字，一般为标题文本（如 '下一步建议'）
 * @property {RegExp} [labelPattern] - 可选，自定义匹配标题的正则；若存在则优先使用
 * @property {number} [headingLevel=3] - 期望的标题级别（如 2 对应 '## '，3 对应 '### '），仅用于限定搜索范围
 */

/**
 * @typedef {Object} ExtractedItem
 * @property {number} id - 序号，从 1 开始递增
 * @property {string} name - 业务可直接使用的“建议/措施”等名称（已去掉 markdown 标记）
 * @property {string} raw - 原始行内容（已去掉行首的数字序号与列表前缀，但保留 markdown）
 */

/**
 * @typedef {Object} ExtractedSection
 * @property {string} key - 与配置中的 key 对应
 * @property {string} label - 与配置中的 label 对应
 * @property {string[]} lines - 区块原始内容行（仅用于调试，后续可考虑去掉）
 * @property {string} content - 区块内容整体字符串（`\n` 拼接）
 * @property {ExtractedItem[]} items - 业务可直接使用的结构化条目列表
 */

/**
 * 判断一行是否 markdown 标题（返回 { level, text } 或 null）
 * 目前只关心前 1~6 个 # 的形式，例如：`### 下一步建议`
 * @param {string} line
 * @returns {{ level: number, text: string } | null}
 */
function parseHeading (line) {
  if (typeof line !== 'string') return null
  const m = /^(#{1,6})\s*(.+?)\s*$/.exec(line)
  if (!m) return null
  return {
    level: m[1].length,
    text: m[2]
  }
}

/**
 * 在行数组中查找与配置匹配的标题行索引
 * @param {string[]} lines
 * @param {SectionConfig} cfg
 * @returns {number} - 标题所在索引（0-based）；找不到返回 -1
 */
function findHeadingIndex (lines, cfg) {
  const targetLevel = cfg.headingLevel || 3
  const labelPattern = cfg.labelPattern instanceof RegExp ? cfg.labelPattern : null
  const label = String(cfg.label || '').trim()

  for (let i = 0; i < lines.length; i++) {
    const h = parseHeading(lines[i])
    if (!h) continue
    if (h.level !== targetLevel) continue

    if (labelPattern) {
      if (labelPattern.test(h.text)) return i
    } else if (label && h.text.includes(label)) {
      return i
    }
  }

  return -1
}

/**
 * 根据标题行索引查找对应区块的起止范围
 * 规则：
 * - 内容从 标题下一行（headingIndex + 1）开始
 * - 向后扫描，遇到「同级或更高等级」标题时结束（不包含该行）
 * - 若直到文档结束都没遇到，则区块一直到末尾
 *
 * @param {string[]} lines
 * @param {number} headingIndex - 标题所在索引（0-based）
 * @returns {{ start: number, end: number }} start/end 为内容行索引（0-based，闭区间）；若无内容则 start > end
 */
function findSectionRange (lines, headingIndex) {
  const headingInfo = parseHeading(lines[headingIndex] || '')
  const level = headingInfo ? headingInfo.level : 1
  const start = headingIndex + 1
  let end = lines.length - 1

  for (let i = start; i < lines.length; i++) {
    const h = parseHeading(lines[i])
    if (h && h.level <= level) {
      end = i - 1
      break
    }
  }

  if (start > end) {
    return { start: start, end: start - 1 }
  }
  return { start, end }
}

/**
 * 去掉 markdown 强调标记（目前仅处理最常见的 * / ** ）
 * @param {string} text
 * @returns {string}
 */
function stripMarkdownEmphasis (text) {
  if (!text) return ''
  return text.replace(/\*\*(.+?)\*\*/g, '$1').replace(/\*(.+?)\*/g, '$1')
}

/**
 * 提取嵌套层级的结构化数据
 * 保留二级标题（如 "立即行头颅MRI及MR静脉成像（MRV）"）和对应的 bullet 内容
 *
 * @param {string[]} sectionLines - 不含标题本身的行
 * @returns {Array<{ title: string, items: string[] }>}
 */
function buildHierarchicalItems (sectionLines) {
  if (!Array.isArray(sectionLines) || sectionLines.length === 0) return []

  const result = []
  // 匹配数字标题："1. xxx" 或 "**1. xxx**"（行首必须是数字或 **）
  // 使用捕获组：组1=数字(普通), 组2=内容(普通)，组3=数字(加粗), 组4=内容(加粗)
  const numberLineRegex = /^(?:(\d+)\.\s+(.+)|\*\*(\d+)\.\s+(.+)\*\*)$/
  // 匹配 bullet 标题："- **xxx**"（行首是 -）
  const bulletTitleRegex = /^-\s+\*\*(.+)\*\*$/
  // 匹配 bullet 内容："- xxx"（行首是 -，后面不是 **）
  const bulletRegex = /^-\s+([^*].*)$/

  let currentTitle = null
  let currentBullets = []

  const flushCurrent = () => {
    if (currentTitle !== null) {
      const plainTitle = stripMarkdownEmphasis(currentTitle).trim()
      const colonIndex = plainTitle.indexOf('：')
      const finalTitle = colonIndex > 0 ? plainTitle.slice(0, colonIndex) : plainTitle
      result.push({
        title: finalTitle,
        items: currentBullets.map(b => stripMarkdownEmphasis(b).trim()).filter(Boolean)
      })
    }
    currentTitle = null
    currentBullets = []
  }

  for (const line of sectionLines) {
    const trimmed = line || ''

    // 跳过空行
    if (!trimmed.trim()) continue

    // 调试日志
    // console.log('Processing line:', JSON.stringify(trimmed))

    // 匹配数字标题 "1. xxx" 或 "**1. xxx**"
    const numMatch = numberLineRegex.exec(trimmed)
    if (numMatch) {
      // 组1=数字(普通), 组2=内容(普通)，组3=数字(加粗), 组4=内容(加粗)
      flushCurrent()
      currentTitle = numMatch[2] || numMatch[4] || ''
      continue
    }

    // 匹配 bullet 标题 "- **标题**"
    const bulletTitleMatch = bulletTitleRegex.exec(trimmed)
    if (bulletTitleMatch) {
      // console.log('Matched bullet title:', bulletTitleMatch[1])
      flushCurrent()
      currentTitle = bulletTitleMatch[1] || ''
      continue
    }

    // 匹配 bullet 内容 "- xxx"
    const bulletMatch = bulletRegex.exec(trimmed)
    if (bulletMatch) {
      // console.log('Matched bullet content:', bulletMatch[1])
      currentBullets.push(bulletMatch[1] || '')
      continue
    }

    // 处理缩进的普通文本（无前缀），作为内容
    if (/^\s{2,}/.test(trimmed) && currentTitle !== null) {
      currentBullets.push(trimmed)
    }
  }

  flushCurrent()
  return result
}

/**
 * 基于「下一步建议」等模式，把区块里的内容拆成结构化 items。
 *
 * 目前使用的启发式规则（与示例对齐）：
 * 1. 先按「数字. 起头」识别一级条目，例如：`1. **尽快完善头颅影像学检查**：`
 * 2. 若某个数字条目后面紧跟着若干 `-` 开头的子 bullet：
 *    - 对于第一个数字条目：优先取其“第一个子 bullet”的内容作为 name
 *      例如：`- **头颅MRI + MRV（磁共振静脉成像）**：排除...`
 *    - 对于其余数字条目：若没有子 bullet，则直接用数字行本身的文本
 * 3. 若数字行本身存在中文全角冒号 `：`，则只取冒号前的内容作为名称主体
 *
 * 这样在你的示例里会得到：
 *   1 -> "头颅MRI + MRV（磁共振静脉成像）"
 *   2 -> "血液学检验"
 *   3 -> "妇产科会诊"
 *   4 -> "必要时腰椎穿刺"
 *
 * @param {string[]} sectionLines - 不含标题本身的行
 * @returns {ExtractedItem[]}
 */
function buildStructuredItems (sectionLines) {
  if (!Array.isArray(sectionLines) || sectionLines.length === 0) return []

  const items = []
  // 匹配数字标题："1. xxx" 或 "**1. xxx**"
  const numberLineRegex = /^(?:(\d+)\.\s+(.+)|\*\*(\d+)\.\s+(.+)\*\*)$/
  // 匹配 bullet 标题："- **xxx**"
  const bulletTitleRegex = /^-\s+\*\*(.+)\*\*$/
  // 匹配 bullet 内容："- xxx"（不包含 ** 的）
  const bulletRegex = /^-\s+([^*].*)$/

  // 先收集所有标题行（数字或 bullet）
  const titleIndexes = []
  sectionLines.forEach((line, idx) => {
    if (numberLineRegex.test(line || '')) {
      titleIndexes.push({ idx, type: 'number' })
    } else if (bulletTitleRegex.test(line || '')) {
      titleIndexes.push({ idx, type: 'bullet' })
    }
  })

  if (titleIndexes.length === 0) {
    const joined = sectionLines.join(' ').trim()
    if (!joined) return []
    const plain = stripMarkdownEmphasis(joined)
    return [{ id: 1, name: plain }]
  }

  titleIndexes.forEach((titleInfo, idx) => {
    const startIdx = titleInfo.idx
    const line = sectionLines[startIdx] || ''

    let titleText = ''
    if (titleInfo.type === 'number') {
      const m = numberLineRegex.exec(line)
      // numMatch[2] 是 "1. xxx" 的内容，numMatch[4] 是 "**1. xxx**" 的内容
      titleText = m ? (m[2] || m[4] || '') : ''
    } else {
      const m = bulletTitleRegex.exec(line)
      titleText = m ? (m[1] || '') : ''
    }

    const bullets = []
    const nextIdx = titleIndexes[idx + 1] ? titleIndexes[idx + 1].idx : sectionLines.length

    for (let i = startIdx + 1; i < nextIdx; i++) {
      const l = sectionLines[i] || ''
      if (!l.trim()) continue
      const bm = bulletRegex.exec(l)
      if (bm) {
        bullets.push(bm[1] || '')
      } else if (/^\s{2,}/.test(l)) {
        bullets.push(l.trim())
      }
    }

    let candidateText = titleText
    if (bullets.length > 0) {
      candidateText = bullets[0]
    }

    const colonIndex = candidateText.indexOf('：')
    if (colonIndex > 0) {
      candidateText = candidateText.slice(0, colonIndex)
    }

    const name = stripMarkdownEmphasis(candidateText).trim()
    if (!name) return

    items.push({ id: items.length + 1, name })
  })

  return items
}

/**
 * 从 markdown 文本中按配置抽取区块
 *
 * @param {string} markdownText - 完整的 markdown 文本
 * @param {SectionConfig[]} sectionConfigs - 区块配置
 * @returns {ExtractedSection[]} 抽取结果数组
 */
export function extractSectionsFromMarkdown (markdownText, sectionConfigs) {
  if (!markdownText || typeof markdownText !== 'string') return []
  if (!Array.isArray(sectionConfigs) || sectionConfigs.length === 0) return []

  const lines = markdownText.split(/\r?\n/)
  const results = []

  sectionConfigs.forEach((cfg, cfgIndex) => {
    if (!cfg || !cfg.key) return

    const headingIndex = findHeadingIndex(lines, cfg)
    if (headingIndex === -1) return

    const range = findSectionRange(lines, headingIndex)
    const sectionLines = range.start <= range.end
      ? lines.slice(range.start, range.end + 1)
      : []

    const item = {
      key: cfg.key,
      label: cfg.label,
      lines: sectionLines,
      content: sectionLines.join('\n'),
      items: buildStructuredItems(sectionLines),
      hierarchicalItems: buildHierarchicalItems(sectionLines),
      // 仅用于排序稳定性，不对外承诺该字段（必要时可随时移除）
      __headingLine: headingIndex + 1,
      // 仅用于排序稳定性，不对外承诺该字段（必要时可随时移除）
      __cfgIndex: cfgIndex
    }

    results.push(item)
  })

  // 关键：按 markdown 里“标题出现的先后”输出，避免因为 sectionsConfig 顺序导致乱序
  results.sort((a, b) => {
    const la = (a && typeof a.__headingLine === 'number') ? a.__headingLine : Number.MAX_SAFE_INTEGER
    const lb = (b && typeof b.__headingLine === 'number') ? b.__headingLine : Number.MAX_SAFE_INTEGER
    if (la !== lb) return la - lb
    const ia = (a && typeof a.__cfgIndex === 'number') ? a.__cfgIndex : 0
    const ib = (b && typeof b.__cfgIndex === 'number') ? b.__cfgIndex : 0
    return ia - ib
  })

  // 清理内部字段，保持返回结构稳定
  results.forEach(sec => {
    if (sec && Object.prototype.hasOwnProperty.call(sec, '__cfgIndex')) {
      delete sec.__cfgIndex
    }
    if (sec && Object.prototype.hasOwnProperty.call(sec, '__headingLine')) {
      delete sec.__headingLine
    }
  })

  return results
}

/**
 * @typedef {Object} FlattenedItem
 * @property {string} sectionKey
 * @property {string} sectionLabel
 * @property {number} id
 * @property {string} name
 */

/**
 * 把 ExtractedSection[] 打平成更便于业务消费/入库/接口传输的一维 JSON 数组。
 *
 * 约定：
 * - 若某个 section 没有 items，则不会产生任何 item（避免产生“空条目”）
 * - 每条 item 都带上 section 元信息，方便溯源
 *
 * @param {ExtractedSection[]} sections
 * @returns {FlattenedItem[]}
 */
export function flattenSectionsToItemsArray (sections) {
  if (!Array.isArray(sections) || sections.length === 0) return []

  const out = []
  sections.forEach(sec => {
    if (!sec || !Array.isArray(sec.items) || sec.items.length === 0) return
    sec.items.forEach(it => {
      if (!it) return
      out.push({
        sectionKey: sec.key,
        sectionLabel: sec.label,
        id: it.id,
        name: it.name
      })
    })
  })

  return out
}

/**
 * @typedef {Object} ItemsGroup
 * @property {string} label - 该分组对应的区块标题/关键字（如“初步诊断”）
 * @property {FlattenedItem[]} value - 该关键字抽取到的结构化条目数组
 */

/**
 * 把一维 itemsArray 按 sectionKey 分桶成多个 JSON 对象（带 label + value）。
 * 例如：{ initialDiagnosis: { label: '初步诊断', value: [...] } }
 *
 * @param {FlattenedItem[]} itemsArray
 * @returns {Record<string, ItemsGroup>}
 */
export function groupItemsArrayBySectionKey (itemsArray) {
  if (!Array.isArray(itemsArray) || itemsArray.length === 0) return {}
  const out = {}
  itemsArray.forEach(it => {
    if (!it) return
    const k = it.sectionKey || ''
    if (!k) return
    if (!out[k]) {
      out[k] = {
        label: it.sectionLabel || '',
        value: []
      }
    } else if (!out[k].label && it.sectionLabel) {
      // 若首次没拿到 label，后续遇到可用的则补上
      out[k].label = it.sectionLabel
    }
    out[k].value.push(it)
  })
  return out
}

/**
 * 直接从 sections 得到按 sectionKey 分组后的 JSON 数组集合。
 *
 * @param {ExtractedSection[]} sections
 * @returns {Record<string, ItemsGroup>}
 */
export function flattenSectionsToItemsByKey (sections) {
  return groupItemsArrayBySectionKey(flattenSectionsToItemsArray(sections))
}

/**
 * 输出格式类型枚举
 */
export const OutputFormat = {
  /** 默认格式：sections 数组（原有格式） */
  DEFAULT: 'default',
  /** XList 默认模式格式：[{ number, name }] */
  XLIST_DEFAULT: 'xlist-default',
  /** XList 卡片模式格式：[{ label, value, type }] */
  XLIST_CARD: 'xlist-card',
  /** XList 标题-内容模式：[{ sectionTitle, items: [{ title, content: [] }] }] */
  XLIST_TITLE_CONTENT: 'xlist-title-content'
}

/**
 * 将抽取的 sections 转换为指定格式
 *
 * @param {ExtractedSection[]} sections - 抽取结果
 * @param {string} format - 输出格式，可选 'default' | 'xlist-default' | 'xlist-card'
 * @param {Object} options - 额外选项
 * @param {string} [options.numberField='number'] - 序号字段名（xlist-default 模式）
 * @param {string} [options.nameField='name'] - 名称字段名（xlist-default 模式）
 * @param {string} [options.labelField='label'] - 标签字段名（xlist-card 模式）
 * @param {string} [options.valueField='name'] - 值字段名（xlist-card 模式）
 * @returns {any} 转换后的数据
 */
export function convertToFormat (sections, format = OutputFormat.DEFAULT, options = {}) {
  const {
    numberField = 'number',
    nameField = 'name',
    labelField = 'label',
    valueField = 'name',
    sectionTitleField = 'sectionTitle',
    titleField = 'title',
    contentField = 'content'
  } = options

  switch (format) {
    case OutputFormat.XLIST_DEFAULT: {
      // XList 默认模式：[{ number, name }, ...]
      const itemsArray = flattenSectionsToItemsArray(sections)
      return itemsArray.map((item, idx) => ({
        [numberField]: item.id || idx + 1,
        [nameField]: item.name || ''
      }))
    }

    case OutputFormat.XLIST_CARD: {
      // XList 卡片模式：二维数组，每行 [{ label, value, type }, ...]
      // 每个 section 生成一行，section 的 label 作为标题 type='title'，items 作为内容
      return sections.map(section => {
        const row = []
        // 标题项
        row.push({
          [labelField]: section.label || '',
          [valueField]: section.label || '',
          type: 'title',
          bold: true
        })
        // 内容项
        if (Array.isArray(section.items)) {
          section.items.forEach(item => {
            row.push({
              [labelField]: '',
              [valueField]: item.name || '',
              type: 'text'
            })
          })
        }
        return row
      })
    }

    case OutputFormat.XLIST_TITLE_CONTENT: {
      // XList 标题-内容模式：[{ sectionTitle, items: [{ title, content: [] }] }]
      // 每个 section 保留：区块标题 + 数字项标题 + bullet 内容
      return sections.map(section => {
        const sectionTitle = section.label || ''
        const items = Array.isArray(section.hierarchicalItems)
          ? section.hierarchicalItems.map(hItem => ({
            [titleField]: hItem.title || '',
            [contentField]: Array.isArray(hItem.items) ? hItem.items : []
          }))
          : []
        return {
          [sectionTitleField]: sectionTitle,
          items
        }
      })
    }

    case OutputFormat.DEFAULT:
    default:
      // 默认返回 sections 数组
      return sections
  }
}

/**
 * 便捷函数：将 sections 转换为 XList 默认模式格式
 * @param {ExtractedSection[]} sections
 * @param {Object} options - 可选字段映射 { numberField, nameField }
 * @returns {Array<{number, name}>}
 */
export function convertToXListDefault (sections, options = {}) {
  return convertToFormat(sections, OutputFormat.XLIST_DEFAULT, options)
}

/**
 * 便捷函数：将 sections 转换为 XList 卡片模式格式
 * @param {ExtractedSection[]} sections
 * @param {Object} options - 可选字段映射 { labelField, valueField }
 * @returns {Array<Array<{label, value, type}>>}
 */
export function convertToXListCard (sections, options = {}) {
  return convertToFormat(sections, OutputFormat.XLIST_CARD, options)
}

/**
 * 去除 markdown 格式符号，保留纯文本
 * - 去掉 ** 和 * 强调标记
 * - 去掉 - bullet 列表标记（行首的 "- "）
 * - 去掉 ^[数字]^ 引用标记
 * - 去掉 [文字](url) 链接格式，保留文字
 *
 * @param {string} text
 * @returns {string}
 */
function stripMarkdownFormat (text) {
  if (!text || typeof text !== 'string') return ''
  return text
    // 去掉 ^[数字]^ 引用格式
    .replace(/\^\[(\d+)\]\^/g, '')
    // 去掉加粗 **xxx**
    .replace(/\*\*(.+?)\*\*/g, '$1')
    // 去掉斜体 *xxx*
    .replace(/\*(.+?)\*/g, '$1')
    // 去掉行首的 "- "（保留后面的内容）
    .replace(/^- /gm, '')
    // 去掉 [文字](url) 链接格式，保留文字
    .replace(/\[([^\]]+)\]\([^)]+\)/g, '$1')
    // 清理多余的空行（保留最多一个空行）
    .replace(/\n{3,}/g, '\n\n')
}

/**
 * 根据关键字提取 markdown 中各区块的内容
 *
 * 用途：从一份 markdown 病历中按「主诉、现病史、既往史」等标题快速提取对应的区块内容。
 * 返回格式为数组，每个元素是一个以关键字为 key 的对象。
 *
 * 规则：
 * - 每个关键字会找到对应标题（默认 2 级标题即 ## 开头），提取从该标题到下一个同级或更高等级标题之前的内容
 * - 若文档中没有匹配的关键字，则该关键字对应的内容为空字符串
 * - 只返回有匹配的关键字，未匹配的不返回
 *
 * @param {string} markdownText - markdown 格式的字符串
 * @param {string[]} keywords - 关键字数组，如 ['主诉', '现病史', '既往史']
 * @param {Object} [options] - 可选配置
 * @param {number} [options.headingLevel=2] - 标题级别，默认 2（对应 ##），可设为 3（对应 ###）等
 * @param {boolean} [options.trimContent=true] - 是否去除内容首尾空白，默认为是
 * @param {boolean} [options.stripMarkdown=true] - 是否去除 markdown 格式符号（如 **、-、引用等），默认为是
 * @returns {Array<Object>} - 数组，每个元素以关键字为 key，如 [{ "主诉": "反复左额部..." }, { "现病史": "患者女性..." }]
 *
 * @example
 * const md = `## 主诉\n反复左额部搏动性疼痛2年余，加重伴呕吐1天。\n\n## 现病史\n患者女性，2年前无明显诱因出现左侧额部头痛...`
 * const result = extractByKeywords(md, ['主诉', '现病史', '既往史'])
 * // result = [
 * //   { "主诉": "反复左额部搏动性疼痛2年余，加重伴呕吐1天。" },
 * //   { "现病史": "患者女性，2年前无明显诱因出现左侧额部头痛..." }
 * // ]
 */
export function extractByKeywords (markdownText, keywords, options = {}) {
  if (!markdownText || typeof markdownText !== 'string') return []
  if (!Array.isArray(keywords) || keywords.length === 0) return []

  const {
    headingLevel = 2,
    trimContent = true,
    stripMarkdown = true
  } = options

  const lines = markdownText.split(/\r?\n/)
  const results = []

  keywords.forEach(keyword => {
    const cfg = {
      key: `kw_${keyword}`,
      label: String(keyword).trim(),
      headingLevel
    }
    const headingIndex = findHeadingIndex(lines, cfg)

    let content = ''
    if (headingIndex !== -1) {
      const range = findSectionRange(lines, headingIndex)
      if (range.start <= range.end) {
        const sectionLines = lines.slice(range.start, range.end + 1)
        content = sectionLines.join('\n')
        if (trimContent) content = content.trim()
        if (stripMarkdown) content = stripMarkdownFormat(content)
      }
    }

    results.push({ [keyword]: content })
  })

  return results
}
