const fs = require('fs')
const path = require('path')

const ROOT = path.resolve(__dirname, '..')
const DOCS_DIR = path.join(ROOT, 'docs')
const OUT_FILE = path.join(DOCS_DIR, 'wc-spc-knowledge-base.json')

function ensureDir(dir) {
  if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true })
}

function walk(dir, predicate, result) {
  result = result || []
  if (!fs.existsSync(dir)) return result
  fs.readdirSync(dir, { withFileTypes: true }).forEach(entry => {
    const fullPath = path.join(dir, entry.name)
    if (entry.isDirectory()) {
      if (entry.name === 'node_modules' || entry.name === 'dist') return
      walk(fullPath, predicate, result)
    } else if (!predicate || predicate(fullPath)) {
      result.push(fullPath)
    }
  })
  return result
}

function readText(file) {
  try {
    return fs.readFileSync(file, 'utf8').replace(/^\uFEFF/, '')
  } catch (error) {
    return ''
  }
}

function rel(file) {
  return path.relative(ROOT, file).replace(/\//g, '\\')
}

function lineNumber(text, index) {
  return text.slice(0, index).split(/\r?\n/).length
}

function csvEscape(value) {
  const text = String(value == null ? '' : value)
  return '"' + text.replace(/"/g, '""') + '"'
}

function writeCsv(file, headers, rows) {
  const body = [headers.map(csvEscape).join(',')]
  rows.forEach(row => {
    body.push(headers.map(header => csvEscape(row[header])).join(','))
  })
  fs.writeFileSync(path.join(DOCS_DIR, file), body.join('\n'), 'utf8')
}

function unique(values) {
  return Array.from(new Set(values.filter(Boolean)))
}

function normalizeSlash(value) {
  return String(value || '').replace(/\//g, '\\')
}

function moduleFromView(view) {
  const match = normalizeSlash(view).match(/^src\\views\\([^\\]+)/)
  return match ? match[1] : ''
}

function viewToRoutePath(view) {
  const normalized = normalizeSlash(view)
  const match = normalized.match(/^src\\views\\(.+)\\index\.vue$/i)
  if (!match) return ''
  return '/' + match[1].replace(/\\/g, '/')
}

function displayTitleFromView(view) {
  const normalized = normalizeSlash(view)
  const match = normalized.match(/^src\\views\\(.+?)\\index\.vue$/i)
  if (!match) return normalized.replace(/^src\\views\\/, '').replace(/\\/g, ' / ')
  return match[1].replace(/\\/g, ' / ')
}

function keywordsFromText(text) {
  const raw = String(text || '')
    .replace(/[^\w\u4e00-\u9fa5]+/g, ' ')
    .split(/\s+/)
    .map(item => item.trim())
    .filter(item => item && item.length > 1)
  const grams = []
  raw.forEach(item => {
    if (/[\u4e00-\u9fa5]/.test(item) && item.length > 2) {
      for (let size = 2; size <= Math.min(4, item.length); size++) {
        for (let index = 0; index <= item.length - size; index++) {
          grams.push(item.substr(index, size))
        }
      }
    }
  })
  return unique(raw.concat(grams)).slice(0, 120)
}

function inferAction(name) {
  const text = String(name || '')
  if (/删除|delete/i.test(text)) return '删除'
  if (/修改|编辑|更新|update/i.test(text)) return '修改'
  if (/增加|新增|添加|insert|add/i.test(text)) return '增加'
  if (/导入|上传|upload/i.test(text)) return '导入'
  if (/导出|下载|download|excel/i.test(text)) return '导出'
  if (/select|查询|获取|列表|综合|分页|统计|明细|检索/i.test(text)) return '查询'
  return ''
}

function isQueryAction(action, type, name) {
  if (/删除|修改|增加|导入|上传|提交|审核|执行|启用|禁用/.test(action)) return false
  if (/^\s*select\b/i.test(String(name || ''))) return true
  return action === '查询' || type === '11' || type === '2001' || type === '3'
}

function inferDomain(name, fallback) {
  const text = String(name || '')
  if (text.indexOf('_') > -1) return text.split('_')[0]
  if (text.indexOf('SPC') > -1) return 'SPC分析'
  if (text.indexOf('质量') > -1) return '质量管理'
  return fallback || ''
}

function extractTables(sql) {
  const tables = []
  const re = /\b(?:from|join)\s+((?:\[?[A-Za-z0-9_\u4e00-\u9fa5]+\]?\.)?\[?[A-Za-z0-9_\u4e00-\u9fa5]+\]?)/gi
  let match
  while ((match = re.exec(String(sql || '')))) {
    tables.push(match[1].replace(/\[/g, '').replace(/\]/g, ''))
  }
  return unique(tables)
}

function inferObjectFromName(name) {
  const value = String(name || '').trim()
  if (!value || /^\s*select\b/i.test(value)) return ''
  return value
    .replace(/_查询数据.*$/, '')
    .replace(/_查询.*$/, '')
    .replace(/_分页.*$/, '')
    .replace(/_列表.*$/, '')
    .replace(/_综合.*$/, '')
    .replace(/_统计.*$/, '')
    .replace(/_增加.*$/, '')
    .replace(/_修改.*$/, '')
    .replace(/_删除.*$/, '')
}

function getAttr(attrs, name) {
  const re = new RegExp('(?:^|\\s)(?::?' + name + '|v-bind:' + name + ')\\s*=\\s*([\'"])([\\s\\S]*?)\\1', 'i')
  const match = String(attrs || '').match(re)
  return match ? match[2].replace(/\s+/g, ' ').trim() : ''
}

function textFromTemplate(value) {
  return String(value || '')
    .replace(/<!--[\s\S]*?-->/g, '')
    .replace(/<script[\s\S]*?<\/script>/gi, '')
    .replace(/<style[\s\S]*?<\/style>/gi, '')
    .replace(/<[^>]+>/g, ' ')
    .replace(/\{\{|\}\}/g, ' ')
    .replace(/\s+/g, ' ')
    .trim()
}

function extractClickMethod(value) {
  const methods = []
  const re = /([A-Za-z_$\u4e00-\u9fa5][\w$\u4e00-\u9fa5]*)\s*\(/g
  let match
  while ((match = re.exec(String(value || '')))) methods.push(match[1])
  return methods.filter(item => !/^(if|for|while|switch|confirm|parseInt|Number|String)$/.test(item)).pop() || ''
}

function findBalancedBlock(text, openBraceIndex) {
  if (openBraceIndex < 0) return ''
  let depth = 0
  let quote = ''
  let escaped = false
  for (let i = openBraceIndex; i < text.length; i++) {
    const ch = text[i]
    if (quote) {
      if (escaped) {
        escaped = false
      } else if (ch === '\\') {
        escaped = true
      } else if (ch === quote) {
        quote = ''
      }
      continue
    }
    if (ch === '"' || ch === "'" || ch === '`') {
      quote = ch
      continue
    }
    if (ch === '{') depth++
    if (ch === '}') {
      depth--
      if (depth === 0) return text.slice(openBraceIndex + 1, i)
    }
  }
  return ''
}

function extractOptionsBlock(text, name) {
  const re = new RegExp('\\b' + name + '\\s*:\\s*{', 'g')
  const match = re.exec(text)
  if (!match) return ''
  return findBalancedBlock(text, match.index + match[0].lastIndexOf('{'))
}

function extractLifecycleBlock(text, name) {
  const patterns = [
    new RegExp('\\b' + name + '\\s*\\([^)]*\\)\\s*{', 'g'),
    new RegExp('\\b' + name + '\\s*:\\s*function\\s*\\([^)]*\\)\\s*{', 'g')
  ]
  for (let i = 0; i < patterns.length; i++) {
    const match = patterns[i].exec(text)
    if (match) return findBalancedBlock(text, match.index + match[0].lastIndexOf('{'))
  }
  return ''
}

function extractButtons(text) {
  const rows = []
  const re = /<el-button\b([^>]*)>([\s\S]*?)<\/el-button>/gi
  let match
  while ((match = re.exec(text))) {
    const attrs = match[1]
    const label = textFromTemplate(match[2]) || getAttr(attrs, 'title') || getAttr(attrs, 'icon') || '按钮'
    const click = getAttr(attrs, '@click') || getAttr(attrs, 'click') || getAttr(attrs, '@click.native.prevent') || getAttr(attrs, '@click.native')
    rows.push({
      label,
      action: inferAction(label + ' ' + click) || '操作',
      click,
      method: extractClickMethod(click),
      icon: getAttr(attrs, 'icon'),
      type: getAttr(attrs, 'type')
    })
  }
  return unique(rows.map(row => JSON.stringify(row))).map(row => JSON.parse(row)).slice(0, 40)
}

function extractFilterControls(text) {
  const rows = []
  const re = /<(el-date-picker|el-select|el-input|el-cascader|el-checkbox|el-radio-group|el-switch|el-input-number)\b([^>]*)/gi
  let match
  while ((match = re.exec(text))) {
    const attrs = match[2]
    const model = getAttr(attrs, 'v-model') || getAttr(attrs, 'model')
    const label = getAttr(attrs, 'placeholder') || getAttr(attrs, 'start-placeholder') || getAttr(attrs, 'end-placeholder') || model || match[1]
    rows.push({
      control: match[1],
      label,
      model,
      type: getAttr(attrs, 'type'),
      clearable: /\sclearable(\s|>|$)/i.test(attrs),
      filterable: /\sfilterable(\s|>|$)/i.test(attrs)
    })
  }
  return unique(rows.map(row => JSON.stringify(row))).map(row => JSON.parse(row)).slice(0, 40)
}

function extractTableFields(text) {
  const rows = []
  const re = /<el-table-column\b([^>]*)(?:\/>|>([\s\S]*?)<\/el-table-column>)/gi
  let match
  while ((match = re.exec(text))) {
    const attrs = match[1]
    const body = match[2] || ''
    const rowRefs = []
    const refRe = /scope\.row\.([A-Za-z0-9_$\u4e00-\u9fa5]+)/g
    let refMatch
    while ((refMatch = refRe.exec(body))) rowRefs.push(refMatch[1])
    const prop = getAttr(attrs, 'prop') || unique(rowRefs).join('|')
    const label = getAttr(attrs, 'label') || prop || textFromTemplate(body).slice(0, 30)
    if (!label && !prop) continue
    rows.push({
      label,
      prop,
      width: getAttr(attrs, 'width') || getAttr(attrs, 'min-width'),
      fixed: getAttr(attrs, 'fixed')
    })
  }
  return unique(rows.map(row => JSON.stringify(row))).map(row => JSON.parse(row)).slice(0, 80)
}

function extractMethodProfiles(text) {
  const block = extractOptionsBlock(text, 'methods')
  if (!block) return []
  const rows = []
  const skip = /^(if|for|while|switch|catch|then|map|filter|forEach|setTimeout|setInterval)$/
  const re = /(?:^|\n)\s*([A-Za-z_$\u4e00-\u9fa5][\w$\u4e00-\u9fa5]*)\s*(?:\([^)]*\)\s*{|:\s*function\s*\([^)]*\)\s*{)/g
  let match
  while ((match = re.exec(block))) {
    const name = match[1]
    if (skip.test(name)) continue
    rows.push({
      name,
      action: inferAction(name),
      line: lineNumber(text, text.indexOf(match[0].trim()))
    })
  }
  return unique(rows.map(row => JSON.stringify(row))).map(row => JSON.parse(row)).slice(0, 80)
}

function extractLifecycleProfiles(text) {
  return ['created', 'mounted'].map(name => {
    const block = extractLifecycleBlock(text, name)
    const calls = []
    const re = /this\.([A-Za-z_$\u4e00-\u9fa5][\w$\u4e00-\u9fa5]*)\s*\(/g
    let match
    while ((match = re.exec(block))) calls.push(match[1])
    return { hook: name, calls: unique(calls) }
  }).filter(item => item.calls.length)
}

function riskLevelFromCalls(calls, buttons) {
  const text = calls.concat(buttons || []).map(item => [item.Action, item.Type, item.NameOrSql, item.action, item.label].join(' ')).join(' ')
  if (/删除|delete/i.test(text)) return '包含删除操作'
  if (/修改|编辑|更新|增加|新增|导入|上传|保存|type.?12/i.test(text)) return '包含写入操作'
  if (/导出|Excel|下载/i.test(text)) return '包含导出操作'
  return '只读查询为主'
}

function buildModuleFeatureProfiles(viewCalls, viewImports, apiMappings) {
  const views = walk(path.join(ROOT, 'src', 'views'), file => /\.vue$/i.test(file)).map(rel).sort()
  return views.map(view => {
    const fullPath = path.join(ROOT, view)
    const text = readText(fullPath)
    const calls = viewCalls.filter(row => row.View === view)
    const imports = viewImports.filter(row => row.View === view)
    const apiFiles = unique(imports.map(row => row.ApiFile))
    const apiFunctions = unique(apiFiles.flatMap(file => apiMappings
      .filter(row => normalizeSlash(row.ApiFile) === normalizeSlash(file))
      .map(row => row.FunctionName)))
      .filter(name => new RegExp('\\b' + name + '\\s*\\(').test(text))
    const buttons = extractButtons(text)
    const filters = extractFilterControls(text)
    const tableFields = extractTableFields(text)
    const methods = extractMethodProfiles(text)
    const lifecycle = extractLifecycleProfiles(text)
    const actions = unique(buttons.map(row => row.action).concat(calls.map(row => row.Action))).filter(Boolean)
    const backendCalls = calls.map(row => ({
      source: row.Source,
      type: row.Type,
      action: row.Action,
      domain: row.Domain,
      nameOrSql: row.NameOrSql,
      line: Number(row.Line || 0),
      role: row.Source === 'RawSQL' ? 'Raw SQL 查询' : 'MESCommonBase 查询入口'
    }))
    const title = displayTitleFromView(view)
    const description = title + ' 页面支持 ' + (actions.length ? actions.join('、') : '页面查看') +
      '；主要控件 ' + (filters.length ? filters.map(row => row.label).slice(0, 8).join('、') : '无显式筛选控件') +
      '；表格字段 ' + (tableFields.length ? tableFields.map(row => row.label).slice(0, 10).join('、') : '未识别到表格字段') + '。'
    return {
      view,
      module: moduleFromView(view),
      title,
      routePath: viewToRoutePath(view),
      filters,
      buttons,
      tableFields,
      lifecycle,
      methods,
      backendCalls,
      apiFiles,
      apiFunctions,
      actions,
      riskLevel: riskLevelFromCalls(calls, buttons),
      description,
      keywords: keywordsFromText([view, title, actions.join(' '), filters.map(row => row.label).join(' '), buttons.map(row => row.label).join(' '), tableFields.map(row => row.label).join(' '), backendCalls.map(row => row.nameOrSql).join(' ')].join(' '))
    }
  })
}

function buildQualityDataQueryProfiles(moduleFeatureProfiles) {
  const assembly = moduleFeatureProfiles.find(profile => normalizeSlash(profile.view) === 'src\\views\\QualityAssurance\\AssemblyQualitydataQuery\\index.vue')
  if (!assembly) return []
  return [{
    key: 'assembly-quality-data',
    title: '质量数据查询',
    routePath: assembly.routePath,
    view: assembly.view,
    module: assembly.module,
    method: 'selectDate',
    type: '11',
    nameOrSql: '质量数据_发动机质量数据_各个工位_视图_发动机型号_综合查询',
    exportType: '2001',
    exportNameOrSql: '质量数据查询_综合查询新',
    description: '按时间、工位、工件编号、订货号、订货号ID、机型号、工单ID、工单号、是否合格、测量位置、测量项目查询发动机各工位质量数据；无筛选条件时按全时间范围返回最新 200 条。',
    defaultStrategy: '无查询条件时使用 1900-01-01 00:00:00 至 2099-12-31 23:59:59、全部筛选_ischeck=0、PageCurrent=1、PageSize=200，并按生产日期等时间字段倒序展示最新 200 条。',
    latestSortFields: ['生产日期', '操作时间', '完成时间', '到达时间', '离开时间', '时间'],
    defaultUseWhen: ['查询质量数据', '质量数据查询', '发动机质量数据', '工件质量数据', '测量值查询'],
    parameters: [
      { name: '开始时间', source: 'dateTime[0]', arg: 'startTime', defaultValue: '1900-01-01 00:00:00', required: true },
      { name: '结束时间', source: 'dateTime[1]', arg: 'endTime', defaultValue: '2099-12-31 23:59:59', required: true },
      { name: '工位号_ischeck', source: 'stationNumberValue', arg: 'stationNumber', checkFor: '工位号' },
      { name: '工位号', source: 'stationNumberValue', arg: 'stationNumber' },
      { name: 'PageCurrent', source: 'pageCurrent', arg: 'pageCurrent', defaultValue: 1 },
      { name: 'PageSize', source: 'pageSize', arg: 'pageSize', defaultValue: 200 },
      { name: 'PageCount', source: 'output', defaultValue: '1111', type: 'int', output: '1' },
      { name: 'ItemCount', source: 'output', defaultValue: '1111', type: 'int', output: '1' },
      { name: '工件编号_ischeck', source: 'engineId', arg: 'engineId', checkFor: '工件编号' },
      { name: '工件编号', source: 'engineId', arg: 'engineId' },
      { name: '订货号_ischeck', source: 'orderCode', arg: 'orderCode', checkFor: '订货号' },
      { name: '订货号', source: 'orderCode', arg: 'orderCode' },
      { name: '订货号ID_ischeck', source: 'orderCodeId', arg: 'orderCodeId', checkFor: '订货号ID' },
      { name: '订货号ID', source: 'orderCodeId', arg: 'orderCodeId' },
      { name: '机型号_ischeck', source: 'engineType', arg: 'engineType', checkFor: '机型号' },
      { name: '机型号', source: 'engineType', arg: 'engineType' },
      { name: '工单ID_ischeck', source: 'workOrderId', arg: 'workOrderId', checkFor: '工单ID' },
      { name: '工单ID', source: 'workOrderId', arg: 'workOrderId' },
      { name: '工单号_ischeck', source: 'workOrderCode', arg: 'workOrderCode', checkFor: '工单号' },
      { name: '工单号', source: 'workOrderCode', arg: 'workOrderCode' },
      { name: '是否合格', source: 'isOk', arg: 'isOk', defaultValue: '0' },
      { name: '测量位置_ischeck', source: 'testPosition', arg: 'testPosition', checkFor: '测量位置' },
      { name: '测量位置', source: 'testPosition', arg: 'testPosition' },
      { name: '测量项目_ischeck', source: 'testItem', arg: 'testItem', checkFor: '测量项目' },
      { name: '测量项目', source: 'testItem', arg: 'testItem' }
    ],
    tableFields: assembly.tableFields,
    filters: assembly.filters,
    backendCalls: assembly.backendCalls,
    keywords: keywordsFromText([assembly.title, assembly.routePath, assembly.description, '查询质量数据 质量数据查询 发动机质量数据 工件质量数据 测量值查询'].join(' '))
  }]
}

function extractApiMappings() {
  const apiRoots = [
    path.join(ROOT, 'src', 'api'),
    path.join(ROOT, 'src', 'assets', 'img', 'api')
  ]
  const rows = []
  apiRoots.forEach(root => {
    walk(root, file => /\.js$/i.test(file)).forEach(file => {
      const text = readText(file)
      const relative = rel(file)
      const endpointMatches = []
      const endpointRe = /['"`]([^'"`]*\/submit\/([^'"`?]+\.ashx)\?type=([^'"`&]+)[^'"`]*)['"`]/gi
      let endpointMatch
      while ((endpointMatch = endpointRe.exec(text))) {
        endpointMatches.push({
          line: lineNumber(text, endpointMatch.index),
          endpoint: endpointMatch[2],
          type: endpointMatch[3],
          url: endpointMatch[1]
        })
      }
      const functionRe = /export\s+function\s+([A-Za-z0-9_$\u4e00-\u9fa5]+)\s*\(([^)]*)\)/g
      let fnMatch
      while ((fnMatch = functionRe.exec(text))) {
        const line = lineNumber(text, fnMatch.index)
        const nearest = endpointMatches
          .map(item => Object.assign({ distance: Math.abs(item.line - line) }, item))
          .sort((a, b) => a.distance - b.distance)[0]
        const action = inferAction((nearest && nearest.type) || fnMatch[1])
        rows.push({
          ApiFile: relative,
          Line: line,
          FunctionName: fnMatch[1],
          Params: fnMatch[2].replace(/\s+/g, ' '),
          Endpoint: nearest ? nearest.endpoint : '',
          Type: nearest ? nearest.type : '',
          Action: action,
          Domain: inferDomain((nearest && nearest.endpoint) || relative, moduleFromView(relative)),
          ApiNameOrSql: nearest ? nearest.type : fnMatch[1],
          InferredObject: inferObjectFromName((nearest && nearest.type) || fnMatch[1]),
          ParamExpression: fnMatch[2].replace(/\s+/g, ' ')
        })
      }
    })
  })
  return rows
}

function extractViewCalls() {
  const rows = []
  const imports = []
  walk(path.join(ROOT, 'src', 'views'), file => /\.vue$/i.test(file)).forEach(file => {
    const text = readText(file)
    const relative = rel(file)
    const moduleName = moduleFromView(relative)
    const importRe = /from\s+['"]@\/(api\/[^'"]+)['"]/g
    let importMatch
    while ((importMatch = importRe.exec(text))) {
      imports.push({
        View: relative,
        ApiFile: ('src\\' + importMatch[1]).replace(/\//g, '\\'),
        Line: lineNumber(text, importMatch.index)
      })
    }
    const createDataRe = /CreateData\s*\(\s*['"]([^'"]*)['"]\s*,\s*(['"`])([\s\S]*?)\2/g
    let match
    while ((match = createDataRe.exec(text))) {
      const name = match[3].replace(/\s+/g, ' ').trim()
      const action = inferAction(name)
      rows.push({
        View: relative,
        ViewModule: moduleName,
        RoutePath: viewToRoutePath(relative),
        Line: lineNumber(text, match.index),
        Source: 'CreateData',
        Type: match[1],
        Action: action,
        Domain: inferDomain(name, moduleName),
        NameOrSql: name,
        Queryable: isQueryAction(action, match[1], name)
      })
    }
    const rawSqlRe = /(['"`])\s*(select\s+[\s\S]{10,}?)\1/gi
    while ((match = rawSqlRe.exec(text))) {
      const sql = match[2].replace(/\s+/g, ' ').trim()
      if (!/\bfrom\b/i.test(sql)) continue
      rows.push({
        View: relative,
        ViewModule: moduleName,
        RoutePath: viewToRoutePath(relative),
        Line: lineNumber(text, match.index),
        Source: 'RawSQL',
        Type: '3',
        Action: '查询',
        Domain: 'RawSQL',
        NameOrSql: sql,
        Queryable: true
      })
    }
  })
  return { rows, imports }
}

function buildSpcMappings(apiMappings, viewCalls) {
  const known = [
    { chartType: 'basicTrend', title: '基本趋势图', apiFile: 'src\\api\\SPCanalysis\\basicTrendTap.js', endpoint: 'QualityData_TrendPictureBasic.ashx' },
    { chartType: 'sampleTrend', title: '样本趋势图', apiFile: 'src\\api\\SPCanalysis\\sampleTrendChart.js', endpoint: 'QualityData_TrendPicture.ashx' },
    { chartType: 'histogram', title: '直方图', apiFile: 'src\\api\\SPCanalysis\\histogram.js', endpoint: 'QualityData_Histogram.ashx' },
    { chartType: 'normalDistribution', title: '正态分布图', apiFile: 'src\\api\\SPCanalysis\\normal_distribution.js', endpoint: 'QualityData_NormalDistribution.ashx' },
    { chartType: 'processCapability', title: '过程能力分析', apiFile: 'src\\api\\SPCanalysis\\processCapabilityAnalysis.js', endpoint: 'QualityData_NormalDistribution.ashx' },
    { chartType: 'pareto', title: '排列图', apiFile: 'src\\api\\SPCanalysis\\arrangeChart.js', endpoint: 'QualityData_Pareto.ashx' },
    { chartType: 'xr', title: 'X-R 控制图', apiFile: 'src\\api\\SPCanalysis\\QualityData_XR.js', endpoint: 'QualityData_XR.ashx' },
    { chartType: 'xs', title: 'X-S 控制图', apiFile: 'src\\api\\SPCanalysis\\QualityData_XS.js', endpoint: 'QualityData_XS.ashx' }
  ]
  return known.map(item => {
    const apiRows = apiMappings.filter(row => normalizeSlash(row.ApiFile) === item.apiFile)
    const views = unique(viewCalls.filter(row => normalizeSlash(row.View).indexOf('SearchData') > -1 && row.NameOrSql.indexOf('SPC') > -1).map(row => row.View))
    return Object.assign({}, item, {
      functions: unique(apiRows.map(row => row.FunctionName)).join('|'),
      types: unique(apiRows.map(row => row.Type)).join('|'),
      views: views.join('|')
    })
  })
}

function buildQualityModules(viewCalls) {
  const map = {}
  viewCalls.forEach(row => {
    const text = [row.View, row.Domain, row.NameOrSql].join(' ')
    const quality = /Quality|质量|SPC|Andon|巡检|条码|LineQuality|QualityData/i.test(text)
    if (!quality) return
    if (!map[row.View]) {
      map[row.View] = {
        View: row.View,
        Module: row.ViewModule,
        RoutePath: row.RoutePath,
        Title: displayTitleFromView(row.View),
        Domains: [],
        QueryNames: [],
        Queryable: false
      }
    }
    map[row.View].Domains.push(row.Domain)
    if (row.Queryable) {
      map[row.View].Queryable = true
      map[row.View].QueryNames.push(row.NameOrSql)
    }
  })
  return Object.keys(map).sort().map(key => {
    const item = map[key]
    item.Domains = unique(item.Domains).join('|')
    item.QueryNames = unique(item.QueryNames).join('|')
    return item
  })
}

function main() {
  ensureDir(DOCS_DIR)
  const apiMappings = extractApiMappings()
  const viewData = extractViewCalls()
  const viewCalls = viewData.rows
  const viewImports = viewData.imports
  const summaryMap = {}
  viewCalls.forEach(row => {
    if (!summaryMap[row.View]) {
      summaryMap[row.View] = {
        View: row.View,
        ViewModule: row.ViewModule,
        RoutePath: row.RoutePath,
        Count: 0,
        Domains: [],
        Names: []
      }
    }
    summaryMap[row.View].Count++
    summaryMap[row.View].Domains.push(row.Domain)
    summaryMap[row.View].Names.push(row.NameOrSql)
  })
  const viewSummary = Object.keys(summaryMap).sort().map(key => {
    const item = summaryMap[key]
    item.Domains = unique(item.Domains).join('|')
    item.Names = unique(item.Names).join('|')
    return item
  })
  const rawSqlRows = []
  viewCalls.filter(row => row.Source === 'RawSQL').forEach(row => {
    rawSqlRows.push({
      ApiFile: row.View,
      Line: row.Line,
      Tables: extractTables(row.NameOrSql).join('|'),
      Sql: row.NameOrSql
    })
  })
  const spcMappings = buildSpcMappings(apiMappings, viewCalls)
  const qualityModules = buildQualityModules(viewCalls)
  const moduleFeatureProfiles = buildModuleFeatureProfiles(viewCalls, viewImports, apiMappings)
  const qualityDataQueries = buildQualityDataQueryProfiles(moduleFeatureProfiles)
  const modules = viewSummary.map(row => {
    const calls = viewCalls.filter(call => call.View === row.View)
    const featureProfile = moduleFeatureProfiles.find(profile => profile.view === row.View)
    const primaryQueries = calls.filter(call => call.Queryable).map(call => ({
      kind: call.Source === 'RawSQL' ? 'raw-sql' : 'create-data',
      view: call.View,
      line: Number(call.Line || 0),
      source: call.Source,
      type: call.Type,
      action: call.Action,
      domain: call.Domain,
      nameOrSql: call.NameOrSql,
      queryTables: call.Source === 'RawSQL' ? extractTables(call.NameOrSql) : [],
      backendQueryName: call.Source === 'RawSQL' ? '' : call.NameOrSql,
      inferredObject: call.Source === 'RawSQL' ? '' : inferObjectFromName(call.NameOrSql),
      keywords: keywordsFromText([call.View, call.Domain, call.NameOrSql].join(' '))
    }))
    return {
      module: row.ViewModule,
      view: row.View,
      title: displayTitleFromView(row.View),
      routePath: row.RoutePath,
      callCount: Number(row.Count || calls.length || 0),
      domains: row.Domains ? row.Domains.split('|') : [],
      functionNames: row.Names ? row.Names.split('|') : [],
      featureProfile,
      calls,
      primaryQueries,
      queryable: primaryQueries.length > 0,
      queryTables: unique(primaryQueries.flatMap(query => query.queryTables || [])),
      qualityRelated: qualityModules.some(item => item.View === row.View),
      keywords: keywordsFromText([row.ViewModule, row.View, row.Domains, row.Names].join(' '))
    }
  })
  const sqlTables = []
  rawSqlRows.forEach(row => {
    row.Tables.split('|').filter(Boolean).forEach(table => {
      sqlTables.push({
        table,
        sourceFile: row.ApiFile,
        line: row.Line,
        sql: row.Sql,
        source: 'raw-sql',
        queryable: true,
        keywords: keywordsFromText([table, row.ApiFile, row.Sql].join(' '))
      })
    })
  })
  modules.forEach(module => {
    module.primaryQueries.forEach(query => {
      if (query.kind !== 'raw-sql') return
      ;(query.queryTables || []).forEach(table => {
        if (!table) return
        sqlTables.push({
          table,
          sourceFile: module.view,
          line: query.line,
          sql: query.nameOrSql,
          source: 'raw-sql',
          queryable: true,
          keywords: keywordsFromText([table, module.view, query.nameOrSql].join(' '))
        })
      })
    })
  })
  const tableMap = {}
  sqlTables.forEach(item => {
    if (!tableMap[item.table]) tableMap[item.table] = Object.assign({}, item, { references: [] })
    tableMap[item.table].references.push({
      sourceFile: item.sourceFile,
      line: item.line,
      source: item.source,
      sql: item.sql
    })
  })
  const dedupedTables = Object.keys(tableMap).sort().map(key => tableMap[key])
  const knowledgeBase = {
    generatedAt: new Date().toISOString(),
    project: 'WC-SPC MES-Manager_View',
    modules,
    apiMappings: apiMappings.map(row => ({
      apiFile: row.ApiFile,
      line: Number(row.Line || 0),
      functionName: row.FunctionName,
      params: row.Params,
      endpoint: row.Endpoint,
      type: row.Type,
      action: row.Action,
      domain: row.Domain,
      apiNameOrSql: row.ApiNameOrSql,
      inferredObject: row.InferredObject,
      queryable: isQueryAction(row.Action, row.Type, row.ApiNameOrSql),
      keywords: keywordsFromText([row.ApiFile, row.FunctionName, row.Endpoint, row.Type].join(' '))
    })),
    sqlTables: dedupedTables,
    qualityModules,
    moduleFeatureProfiles,
    qualityDataQueries,
    spcCharts: spcMappings,
    queryHints: modules.map(item => ({
      kind: 'module',
      key: item.view,
      title: item.title,
      keywords: item.keywords,
      tableCandidates: item.queryTables
    }))
  }
  writeCsv('api-name-mapping.csv', ['ApiFile', 'Line', 'FunctionName', 'Params', 'Endpoint', 'Type', 'Action', 'Domain', 'ApiNameOrSql', 'InferredObject', 'ParamExpression'], apiMappings)
  writeCsv('view-api-imports.csv', ['View', 'ApiFile', 'Line'], viewImports)
  writeCsv('view-database-calls.csv', ['View', 'ViewModule', 'RoutePath', 'Line', 'Source', 'Type', 'Action', 'Domain', 'NameOrSql', 'Queryable'], viewCalls)
  writeCsv('view-database-calls-summary.csv', ['View', 'ViewModule', 'RoutePath', 'Count', 'Domains', 'Names'], viewSummary)
  writeCsv('raw-sql-table-mapping.csv', ['ApiFile', 'Line', 'Tables', 'Sql'], rawSqlRows)
  writeCsv('spc-chart-mapping.csv', ['chartType', 'title', 'apiFile', 'endpoint', 'functions', 'types', 'views'], spcMappings)
  writeCsv('quality-module-mapping.csv', ['View', 'Module', 'RoutePath', 'Title', 'Domains', 'QueryNames', 'Queryable'], qualityModules)
  writeCsv('quality-data-query-profiles.csv', [
    'Key',
    'Title',
    'RoutePath',
    'View',
    'Type',
    'NameOrSql',
    'ExportType',
    'ExportNameOrSql',
    'Description',
    'Parameters',
    'TableFields'
  ], qualityDataQueries.map(profile => ({
    Key: profile.key,
    Title: profile.title,
    RoutePath: profile.routePath,
    View: profile.view,
    Type: profile.type,
    NameOrSql: profile.nameOrSql,
    ExportType: profile.exportType,
    ExportNameOrSql: profile.exportNameOrSql,
    Description: profile.description,
    Parameters: profile.parameters.map(row => [row.name, row.arg, row.defaultValue, row.output].filter(value => value != null && value !== '').join(':')).join('|'),
    TableFields: profile.tableFields.map(row => [row.label, row.prop].filter(Boolean).join(':')).join('|')
  })))
  writeCsv('module-feature-profiles.csv', [
    'View',
    'Module',
    'RoutePath',
    'Title',
    'Description',
    'Actions',
    'Filters',
    'Buttons',
    'TableFields',
    'Lifecycle',
    'Methods',
    'BackendCalls',
    'ApiFiles',
    'ApiFunctions',
    'RiskLevel'
  ], moduleFeatureProfiles.map(profile => ({
    View: profile.view,
    Module: profile.module,
    RoutePath: profile.routePath,
    Title: profile.title,
    Description: profile.description,
    Actions: profile.actions.join('|'),
    Filters: profile.filters.map(row => [row.label, row.model, row.control].filter(Boolean).join(':')).join('|'),
    Buttons: profile.buttons.map(row => [row.label, row.method, row.action].filter(Boolean).join(':')).join('|'),
    TableFields: profile.tableFields.map(row => [row.label, row.prop].filter(Boolean).join(':')).join('|'),
    Lifecycle: profile.lifecycle.map(row => row.hook + ':' + row.calls.join('/')).join('|'),
    Methods: profile.methods.map(row => [row.name, row.action].filter(Boolean).join(':')).join('|'),
    BackendCalls: profile.backendCalls.map(row => [row.type, row.action, row.nameOrSql].filter(Boolean).join(':')).join('|'),
    ApiFiles: profile.apiFiles.join('|'),
    ApiFunctions: profile.apiFunctions.join('|'),
    RiskLevel: profile.riskLevel
  })))
  fs.writeFileSync(OUT_FILE, JSON.stringify(knowledgeBase, null, 2), 'utf8')
  const report = [
    '# WC-SPC 项目静态分析',
    '',
    '生成时间：' + new Date().toLocaleString('zh-CN', { hour12: false }),
    '',
    '## 结论',
    '',
    '- 项目为 Vue 2 + Element UI 前端和 ASP.NET Web Site 后端组合。',
    '- 前端通用业务接口通过 `src/utils/request.js` 调用 `MESCommonBase.ashx`。',
    '- SPC 专项接口位于 `src/api/SPCanalysis`，后端对应 `QualityData_*.ashx`。',
    '- 本文件由 `mcp-server/build-knowledge-base.js` 自动生成，用于 AI MCP 服务。',
    '',
    '## 统计',
    '',
    '| 项目 | 数量 |',
    '| --- | ---: |',
    '| 页面查询调用 | ' + viewCalls.length + ' |',
    '| 页面汇总 | ' + viewSummary.length + ' |',
    '| API 映射 | ' + apiMappings.length + ' |',
    '| Raw SQL | ' + rawSqlRows.length + ' |',
    '| 质量相关模块 | ' + qualityModules.length + ' |',
    '| 质量数据查询画像 | ' + qualityDataQueries.length + ' |',
    '| 页面功能画像 | ' + moduleFeatureProfiles.length + ' |',
    '| SPC 图表类型 | ' + spcMappings.length + ' |'
  ].join('\n')
  fs.writeFileSync(path.join(DOCS_DIR, 'wc-spc-static-analysis.md'), report, 'utf8')
  console.log('Knowledge base generated: ' + OUT_FILE)
  console.log('modules=' + modules.length)
  console.log('apiMappings=' + apiMappings.length)
  console.log('qualityModules=' + qualityModules.length)
  console.log('qualityDataQueries=' + qualityDataQueries.length)
  console.log('moduleFeatureProfiles=' + moduleFeatureProfiles.length)
  console.log('spcCharts=' + spcMappings.length)
}

main()
