import type { BrandData } from '../types/brand'

export const EXPAND_GRAPH_PIPELINE_STEPS = [
  { id: 'expand', title: '產生研究地圖', hint: '整理受眾方向、延伸知識與參考連結' },
] as const

export const PLACEMENT_SCAN_PIPELINE_STEPS = [
  { id: 'crawl', title: '雙軌海巡', hint: '相關軌 + 近期軌（7d/30d）' },
] as const

export const PAIN_TAG_MINIMUM = 8
export const MAX_TOP_PATROL_TAGS = 8

export type KnowledgeGraphEvidence = {
  url?: string
  snippet?: string
  query?: string
}

export type BraveSource = {
  query?: string
  title?: string
  url?: string
  snippet?: string
}

export type KnowledgeGraphNode = {
  id: string
  label: string
  node_kind: string
  type: string
  layer: number
  relation?: string
  placement_value?: string
  product_fit_score: number
  selected_for_scan: boolean
  relevance_tags: string[]
  recency_tags: string[]
  evidence?: KnowledgeGraphEvidence[]
}

export type KnowledgeGraphEdge = {
  from: string
  to: string
  relation: string
}

export type KnowledgeGraphData = {
  id: string
  brand_id: string
  seed: string
  nodes: KnowledgeGraphNode[]
  edges: KnowledgeGraphEdge[]
  brave_sources?: BraveSource[]
  expand_strategy?: string
  pain_tag_count: number
  generated_at: number
  create_at: number
  update_at: number
}

export type ExpandKnowledgeGraphData = {
  job_id: string
  status: string
  message?: string
}

const NODE_KIND_LABEL: Record<string, string> = {
  pain: '痛點',
  symptom: '症狀',
  cause: '成因',
  knowledge: '知識',
}

const LAYER_LABEL: Record<number, string> = {
  0: 'L0 核心',
  1: 'L1 延伸',
  2: 'L2 周邊',
}

const LAYER_HINT: Record<number, string> = {
  0: '種子主題',
  1: '直接相關',
  2: '周邊情境',
}

export function nodeKindLabel(kind: string) {
  return NODE_KIND_LABEL[kind] ?? (kind || '節點')
}

export function layerLabel(layer: number) {
  return LAYER_LABEL[layer] ?? `L${layer}`
}

export function layerHint(layer: number) {
  return LAYER_HINT[layer] ?? '延伸層'
}

export function productFitTone(score: number): 'success' | 'warning' | 'neutral' {
  if (score >= 70) return 'success'
  if (score >= 40) return 'warning'
  return 'neutral'
}

export function countSelectedNodes(nodes: KnowledgeGraphNode[]) {
  return nodes.filter((node) => node.selected_for_scan).length
}

export type PatrolTagContext = {
  brandName?: string
  productName?: string
  productFeatures?: string
  matchTags?: string[]
  questions?: string[]
  pillars?: string[]
  patrolKeywords?: string[]
}

const PATROL_CATEGORY_HINTS = [
  '沐浴乳',
  '沐浴露',
  '洗髮精',
  '洗髮乳',
  '洗面乳',
  '洗面奶',
  '乳液',
  '精華',
  '防曬',
  '卸妝',
  '潔面',
  '身體乳',
  '洗手乳',
  '清潔',
] as const

const PATROL_TOPIC_ANCHORS = [
  '化療', '乳癌', '荷爾蒙', '標靶', '康復', '癌症', '病友', '敏感', '無香', '抗敏',
  '香料', '香精', '皮膚', '乾癢', '沐浴', '洗髮', '洗面', '卸妝', '防曬', '懷孕',
  '換季', '屏障', '過敏', '搔癢', '紅腫',
] as const

const PATROL_MAX_LEN = 16
const PATROL_MIN_LEN = 4

function compressPatrolKeywords(text: string) {
  const category = PATROL_CATEGORY_HINTS.find((hint) => text.includes(hint)) ?? ''
  const anchors = PATROL_TOPIC_ANCHORS.filter((anchor) => text.includes(anchor)).slice(0, 2)
  const parts = [...anchors, ...(category ? [category] : [])]
  if (parts.length) return parts.join(' ')
  if (text.length <= 6) return text
  return `${text.slice(0, 4)} ${text.slice(-4)}`
}

function patrolTagFromQuestion(raw: string) {
  const normalized = raw.trim().replace(/\s+/g, ' ')
  if (!normalized) return ''
  const runes = [...normalized].length
  if (runes >= PATROL_MIN_LEN && runes <= PATROL_MAX_LEN) {
    if (
      normalized.includes(' ') ||
      /推薦|請問|怎麼辦|好用嗎|求助/.test(normalized)
    ) {
      return normalized
    }
    if (runes >= 8 && PATROL_CATEGORY_HINTS.some((hint) => normalized.includes(hint))) {
      return humanizePatrolPhrase(normalized, 'relevance')
    }
  }
  return humanizePatrolPhrase(normalized, 'relevance')
}

function humanizePatrolPhrase(raw: string, intent: 'relevance' | 'recency') {
  const normalized = raw.trim().replace(/\s+/g, ' ')
  if (!normalized) return ''
  if (
    normalized.length <= PATROL_MAX_LEN &&
    normalized.length >= PATROL_MIN_LEN &&
    (normalized.includes(' ') || /推薦|請問|怎麼辦|好用嗎|求助/.test(normalized))
  ) {
    return normalized
  }
  let phrase = compressPatrolKeywords(normalized)
  if (!phrase) phrase = normalized.slice(0, PATROL_MAX_LEN)
  if (!/推薦|請問|怎麼辦|好用嗎|有人|求助/.test(phrase)) {
    const suffix = intent === 'recency' ? ' 請問' : ' 推薦'
    if ((phrase + suffix).length <= PATROL_MAX_LEN) phrase += suffix
  }
  if (phrase.length > PATROL_MAX_LEN) phrase = phrase.slice(0, PATROL_MAX_LEN)
  if (phrase.length < PATROL_MIN_LEN) return ''
  if (!phrase.includes(' ') && phrase.length <= 4) return ''
  return phrase
}

function sanitizePatrolTag(text: string) {
  return humanizePatrolPhrase(text, 'relevance')
}

export function sanitizePatrolKeyword(text: string) {
  return sanitizePatrolTag(text.trim())
}

export function patrolTagContextFromPlacementTopic(
  topic: { brand_id: string; brand_display_name?: string; product_id?: string; research_map?: BrandData['research_map'] } | null | undefined,
  brand: BrandData | null | undefined,
): PatrolTagContext | null {
  if (!topic) return null
  return patrolTagContextFromBrand({
    id: topic.brand_id,
    display_name: topic.brand_display_name ?? brand?.display_name,
    product_id: topic.product_id,
    products: brand?.products,
    research_map: topic.research_map,
    create_at: brand?.create_at ?? 0,
    update_at: brand?.update_at ?? 0,
  })
}

export function patrolTagContextFromBrand(brand: BrandData | null | undefined): PatrolTagContext | null {
  if (!brand) return null
  const map = brand.research_map
  const productId = brand.product_id?.trim()
  let productName = ''
  let productFeatures = ''
  let brandName = brand.display_name?.trim() ?? ''
  let matchTags: string[] = []

  const selectedProduct = productId
    ? brand.products?.find((item) => item.id === productId)
    : brand.products?.[0]
  if (selectedProduct) {
    const fields = (() => {
      try {
        const parsed = JSON.parse(selectedProduct.product_context) as {
          brand?: string
          product?: string
          features?: string
        }
        return {
          brand: parsed.brand?.trim() ?? '',
          product: parsed.product?.trim() ?? '',
          features: parsed.features?.trim() ?? '',
        }
      } catch {
        return { brand: '', product: '', features: selectedProduct.product_context?.trim() ?? '' }
      }
    })()
    productName = fields.product || selectedProduct.label?.trim() || ''
    productFeatures = fields.features
    if (!brandName && fields.brand) {
      brandName = fields.brand
    }
    matchTags = [...(selectedProduct.match_tags ?? [])]
  } else if (brand.product_context?.trim()) {
    try {
      const parsed = JSON.parse(brand.product_context) as {
        brand?: string
        product?: string
        features?: string
      }
      productName = parsed.product?.trim() ?? ''
      productFeatures = parsed.features?.trim() ?? ''
      if (!brandName && parsed.brand?.trim()) {
        brandName = parsed.brand.trim()
      }
    } catch {
      productFeatures = brand.product_context.trim()
    }
  }

  return {
    brandName,
    productName,
    productFeatures,
    matchTags,
    questions: map?.questions ? [...map.questions] : [],
    pillars: map?.pillars ? [...map.pillars] : [],
    patrolKeywords: map?.patrol_keywords ? [...map.patrol_keywords] : [],
  }
}

export function preparePatrolKeywordForSave(text: string): string {
  const trimmed = text.trim().replace(/\s+/g, ' ')
  if (!trimmed) return ''
  const runes = [...trimmed].length
  if (runes < 2 || runes > PATROL_MAX_LEN) return ''
  return trimmed
}

export function savedPatrolKeywords(map: BrandData['research_map'] | null | undefined): string[] {
  const seen = new Set<string>()
  const out: string[] = []
  for (const item of map?.patrol_keywords ?? []) {
    const tag = preparePatrolKeywordForSave(item)
    if (!tag || seen.has(tag)) continue
    seen.add(tag)
    out.push(tag)
    if (out.length >= MAX_TOP_PATROL_TAGS) break
  }
  return out
}

export function suggestedPatrolKeywords(
  map: BrandData['research_map'] | null | undefined,
  nodes: KnowledgeGraphNode[],
  context?: PatrolTagContext | null,
): string[] {
  const saved = new Set(savedPatrolKeywords(map))
  return rankPatrolTags(nodes, context)
    .map((item) => item.tag)
    .filter((tag) => tag && !saved.has(tag))
    .slice(0, MAX_TOP_PATROL_TAGS)
}

export function resolvePatrolKeywords(
  map: BrandData['research_map'] | null | undefined,
  nodes: KnowledgeGraphNode[],
  context?: PatrolTagContext | null,
): string[] {
  const saved = savedPatrolKeywords(map)
  if (saved.length > 0) {
    return saved
  }
  return rankPatrolTags(nodes, context).map((item) => item.tag)
}

export type PatrolTagPick = {
  tag: string
  reason: string
  score: number
}

function patrolTagDedupeKey(tag: string) {
  return tag
    .replace(/ 推薦$/, '')
    .replace(/ 請問$/, '')
    .replace(/ 怎麼辦$/, '')
    .replace(/ 好用嗎$/, '')
    .replace(/ 有人用過嗎$/, '')
}

function scoreQuestion(index: number) {
  return Math.max(70, 100 - index * 4)
}

function scorePillar(index: number) {
  return Math.max(55, 74 - index * 3)
}

function scoreMatchTag(index: number) {
  return Math.max(70, 90 - index * 4)
}

function scoreProduct() {
  return 58
}

function scoreNode(node: KnowledgeGraphNode) {
  let base = 48
  if (node.layer === 0) base = 82
  else if (node.layer === 1) base = 68
  else if (node.layer === 2) base = 52
  if (node.node_kind === 'pain' || node.node_kind === 'symptom' || node.node_kind === 'cause') {
    base += 8
  }
  if (node.product_fit_score > 0) {
    base += Math.floor(node.product_fit_score / 6)
  }
  return base
}

function nodePatrolReason(node: KnowledgeGraphNode) {
  if (node.layer === 0) return '核心痛點'
  if (node.layer === 1) return '高相關延伸'
  return '周邊情境'
}

export function rankPatrolTags(
  nodes: KnowledgeGraphNode[],
  context?: PatrolTagContext | null,
  limit = MAX_TOP_PATROL_TAGS,
): PatrolTagPick[] {
  const candidates: PatrolTagPick[] = []
  const push = (raw: string, score: number, reason: string) => {
    const tag = sanitizePatrolTag(raw)
    if (!tag || score <= 0) return
    candidates.push({ tag, score, reason })
  }

  if (context) {
    for (const [index, tag] of (context.patrolKeywords ?? []).entries()) {
      push(tag, 120 - index, '手動精選')
    }
    for (const [index, q] of (context.questions ?? []).entries()) {
      push(patrolTagFromQuestion(q), scoreQuestion(index) + 12, '受眾提問')
    }
    for (const [index, pillar] of (context.pillars ?? []).entries()) {
      push(humanizePatrolPhrase(pillar, 'relevance'), scorePillar(index) + 6, '內容支柱')
    }
    for (const [index, matchTag] of (context.matchTags ?? []).entries()) {
      push(humanizePatrolPhrase(matchTag, 'relevance'), scoreMatchTag(index), '產品匹配')
    }
    if (context.productName) {
      push(humanizePatrolPhrase(context.productName, 'relevance'), scoreProduct(), '置入產品')
    }
  }

  for (const node of nodes) {
    if (node.layer > 1 && node.node_kind !== 'pain' && node.node_kind !== 'symptom' && node.node_kind !== 'cause') {
      continue
    }
    const nodeScore = scoreNode(node)
    const reason = nodePatrolReason(node)
    for (const tag of node.relevance_tags ?? []) {
      push(tag, nodeScore, reason)
    }
    for (const tag of node.recency_tags ?? []) {
      push(tag, nodeScore - 3, reason)
    }
  }

  candidates.sort((a, b) => b.score - a.score || a.tag.localeCompare(b.tag, 'zh-Hant'))
  const seenTag = new Set<string>()
  const seenKey = new Set<string>()
  const picks: PatrolTagPick[] = []
  for (const item of candidates) {
    if (seenTag.has(item.tag)) continue
    const key = patrolTagDedupeKey(item.tag)
    if (seenKey.has(key)) continue
    seenTag.add(item.tag)
    seenKey.add(key)
    picks.push(item)
    if (picks.length >= limit) break
  }
  return picks
}

export function collectPatrolTags(
  nodes: KnowledgeGraphNode[],
  context?: PatrolTagContext | null,
) {
  return rankPatrolTags(nodes, context).map((item) => item.tag)
}

export function groupNodesByLayer(nodes: KnowledgeGraphNode[]) {
  const groups = new Map<number, KnowledgeGraphNode[]>()
  for (const node of nodes) {
    const layer = node.layer ?? 0
    const list = groups.get(layer) ?? []
    list.push(node)
    groups.set(layer, list)
  }
  return [...groups.entries()].sort(([a], [b]) => a - b)
}