/**
 * National Energy Knowledge Graph (§16) and the data behind the explorer (§17).
 *
 * The relational schema already knows that an asset belongs to a region and that an
 * alert belongs to an asset. What it does not hold is the other half of an operator's
 * mental model: that heat raises transformer temperature, that temperature raises
 * failure risk, that risk is what a load transfer is for. Those are relationships
 * between *concepts*, and they are the ones that let a question like "why is Riyadh
 * getting worse" be answered by following edges instead of by reading twelve tables.
 *
 * This module puts both kinds of node in one graph: physical entities from the register,
 * and the causal concepts that connect them.
 */

export type KnowledgeKind =
  | 'concept'
  | 'region'
  | 'asset'
  | 'site'
  | 'incident'
  | 'pattern'
  | 'alert'
  | 'prediction'
  | 'weather'
  | 'action'
  | 'role'

export interface KnowledgeNode {
  code: string
  kind: KnowledgeKind
  label: string
  labelAr: string
  detail: string
  detailAr: string
  regionCode: string | null
  weight: number
  refType: string
  refId: string
}

export interface KnowledgeEdge {
  from: string
  to: string
  relation: string
  relationAr: string
  weight: number
}

export interface KnowledgeGraphData {
  nodes: KnowledgeNode[]
  edges: KnowledgeEdge[]
}

/**
 * The causal backbone.
 *
 * These eleven concepts and the edges between them are the domain knowledge the
 * platform reasons with: they are what turns "temperature is high" into "a heat wave is
 * raising temperature, which raises failure risk, which a load transfer reduces". They
 * are declared rather than derived because they are physics and operating practice, not
 * measurements.
 */
const CONCEPTS: Array<Omit<KnowledgeNode, 'regionCode' | 'refType' | 'refId'>> = [
  {
    code: 'C-HEAT',
    kind: 'concept',
    label: 'Ambient heat',
    labelAr: 'الحرارة المحيطة',
    detail: 'Air temperature around the asset, the boundary condition every thermal model starts from.',
    detailAr: 'حرارة الهواء حول الأصل، وهي الشرط الحدّي الذي يبدأ منه كل نموذج حراري.',
    weight: 3,
  },
  {
    code: 'C-COOLING',
    kind: 'concept',
    label: 'Cooling efficiency',
    labelAr: 'كفاءة التبريد',
    detail: 'How much of the temperature rise the cooling plant is actually shedding.',
    detailAr: 'مقدار الارتفاع الحراري الذي تتخلص منه منظومة التبريد فعليًا.',
    weight: 2.5,
  },
  {
    code: 'C-DEMAND',
    kind: 'concept',
    label: 'Industrial and cooling demand',
    labelAr: 'الطلب الصناعي وطلب التبريد',
    detail: 'Demand that rises with temperature and with industrial activity.',
    detailAr: 'الطلب الذي يرتفع مع الحرارة ومع النشاط الصناعي.',
    weight: 3,
  },
  {
    code: 'C-TEMP',
    kind: 'concept',
    label: 'Transformer temperature',
    labelAr: 'حرارة المحول',
    detail: 'Top-oil temperature, the quantity that decides insulation ageing rate.',
    detailAr: 'حرارة الزيت العلوي، وهي الكمية التي تحدد معدل تقادم العزل.',
    weight: 3.5,
  },
  {
    code: 'C-LOADING',
    kind: 'concept',
    label: 'Asset loading',
    labelAr: 'تحميل الأصل',
    detail: 'Utilisation against nameplate capacity.',
    detailAr: 'الاستغلال مقارنة بالسعة الاسمية.',
    weight: 3.5,
  },
  {
    code: 'C-RISK',
    kind: 'concept',
    label: 'Failure risk',
    labelAr: 'خطر العطل',
    detail: 'Combined stress and vulnerability, the score the console shows.',
    detailAr: 'مزيج الإجهاد والهشاشة، وهو المؤشر الذي تعرضه المنصة.',
    weight: 4,
  },
  {
    code: 'C-RENEWABLE',
    kind: 'concept',
    label: 'Renewable variability',
    labelAr: 'تقلب المتجددة',
    detail: 'Swing in solar and wind output that the network has to absorb.',
    detailAr: 'التأرجح في إنتاج الشمس والرياح الذي يتعين على الشبكة استيعابه.',
    weight: 2.5,
  },
  {
    code: 'C-CASCADE',
    kind: 'concept',
    label: 'Cascading propagation',
    labelAr: 'الانتشار المتتالي',
    detail: 'Transfer of a failed asset’s load onto its neighbours, and onward.',
    detailAr: 'انتقال حمل الأصل المتعطل إلى جيرانه ثم إلى ما بعدهم.',
    weight: 3,
  },
  {
    code: 'C-TRANSFER',
    kind: 'action',
    label: 'Load transfer',
    labelAr: 'تحويل الحمل',
    detail: 'Moving load onto adjacent circuits that still hold thermal headroom.',
    detailAr: 'نقل الحمل إلى دوائر مجاورة ما زالت تملك هامشًا حراريًا.',
    weight: 2.5,
  },
  {
    code: 'C-BATTERY',
    kind: 'action',
    label: 'Battery dispatch',
    labelAr: 'تشغيل البطاريات',
    detail: 'Serving peak demand from storage behind the constraint.',
    detailAr: 'تغذية ذروة الطلب من التخزين خلف نقطة الاختناق.',
    weight: 2.5,
  },
  {
    code: 'C-DR',
    kind: 'action',
    label: 'Demand response',
    labelAr: 'الاستجابة للطلب',
    detail: 'Deferring contracted flexible demand across the peak window.',
    detailAr: 'تأجيل الأحمال المرنة المتعاقد عليها عبر نافذة الذروة.',
    weight: 2.5,
  },
]

const CONCEPT_EDGES: KnowledgeEdge[] = [
  { from: 'C-HEAT', to: 'C-TEMP', relation: 'increases', relationAr: 'يرفع', weight: 3 },
  { from: 'C-HEAT', to: 'C-DEMAND', relation: 'increases', relationAr: 'يرفع', weight: 2.6 },
  { from: 'C-HEAT', to: 'C-COOLING', relation: 'reduces', relationAr: 'يخفض', weight: 2.2 },
  { from: 'C-COOLING', to: 'C-TEMP', relation: 'reduces', relationAr: 'يخفض', weight: 2.8 },
  { from: 'C-DEMAND', to: 'C-LOADING', relation: 'increases', relationAr: 'يرفع', weight: 3 },
  { from: 'C-LOADING', to: 'C-TEMP', relation: 'increases', relationAr: 'يرفع', weight: 3 },
  { from: 'C-TEMP', to: 'C-RISK', relation: 'increases', relationAr: 'يرفع', weight: 3.4 },
  { from: 'C-LOADING', to: 'C-RISK', relation: 'increases', relationAr: 'يرفع', weight: 3.2 },
  { from: 'C-RENEWABLE', to: 'C-LOADING', relation: 'shifts onto', relationAr: 'ينقل إلى', weight: 2.4 },
  { from: 'C-RISK', to: 'C-CASCADE', relation: 'can trigger', relationAr: 'قد يُطلق', weight: 2.8 },
  { from: 'C-TRANSFER', to: 'C-LOADING', relation: 'reduces', relationAr: 'يخفض', weight: 2.8 },
  { from: 'C-BATTERY', to: 'C-LOADING', relation: 'reduces', relationAr: 'يخفض', weight: 2.6 },
  { from: 'C-DR', to: 'C-DEMAND', relation: 'reduces', relationAr: 'يخفض', weight: 2.6 },
  { from: 'C-TRANSFER', to: 'C-RISK', relation: 'mitigates', relationAr: 'يخفف', weight: 2.4 },
  { from: 'C-BATTERY', to: 'C-RISK', relation: 'mitigates', relationAr: 'يخفف', weight: 2.2 },
  { from: 'C-DR', to: 'C-RISK', relation: 'mitigates', relationAr: 'يخفف', weight: 2.2 },
]

export interface KnowledgeInput {
  regions: Array<{ code: string; name: string; nameAr: string; peakDemandMw: number; climate: string; climateAr: string }>
  assets: Array<{
    code: string
    name: string
    nameAr: string
    assetTypeKey: string
    regionCode: string
    capacityMw: number
    customersServed: number
    riskScore: number
    healthScore: number
  }>
  patterns: Array<{ code: string; name: string; nameAr: string; eventType: string; severity: string }>
  incidents: Array<{ code: string; title: string; titleAr: string; assetCode: string | null; regionCode: string | null; severity: string }>
  alerts: Array<{ code: string; title: string; titleAr: string; assetCode: string | null; level: string }>
  predictions: Array<{ code: string; assetCode: string; eventType: string; riskScore: number; dnaCode: string | null }>
}

/** Only assets worth putting on a graph a human will read. */
function isGraphWorthy(asset: KnowledgeInput['assets'][number]): boolean {
  return (
    asset.assetTypeKey === 'substation' ||
    asset.assetTypeKey === 'transmission_line' ||
    asset.assetTypeKey === 'power_plant' ||
    asset.riskScore >= 35 ||
    asset.customersServed >= 120_000
  )
}

/**
 * Assemble the graph.
 *
 * Entities are filtered to what an explorer can usefully render — every one of 252
 * assets on one canvas is a hairball, not a map — while every relationship that does
 * exist between the surviving nodes is kept.
 */
export function buildKnowledgeGraph(input: KnowledgeInput): KnowledgeGraphData {
  const nodes: KnowledgeNode[] = CONCEPTS.map((concept) => ({
    ...concept,
    regionCode: null,
    refType: 'concept',
    refId: concept.code,
  }))
  const edges: KnowledgeEdge[] = [...CONCEPT_EDGES]
  const present = new Set(nodes.map((node) => node.code))

  const push = (node: KnowledgeNode) => {
    if (present.has(node.code)) return
    present.add(node.code)
    nodes.push(node)
  }
  const link = (edge: KnowledgeEdge) => {
    if (!present.has(edge.from) || !present.has(edge.to)) return
    edges.push(edge)
  }

  for (const region of input.regions) {
    push({
      code: `R-${region.code}`,
      kind: 'region',
      label: region.name,
      labelAr: region.nameAr,
      detail: `${region.climate} · peak demand ${Math.round(region.peakDemandMw)} MW`,
      detailAr: `${region.climateAr} · ذروة الطلب ${Math.round(region.peakDemandMw)} ميجاواط`,
      regionCode: region.code,
      weight: 4,
      refType: 'region',
      refId: region.code,
    })
    // Climate is what ties a region to the causal backbone: it is why the same asset
    // behaves differently in Jazan and in Tabuk.
    link({ from: 'C-HEAT', to: `R-${region.code}`, relation: 'affects', relationAr: 'يؤثر في', weight: 2 })
  }

  const worthy = input.assets.filter(isGraphWorthy)
  for (const asset of worthy) {
    push({
      code: `A-${asset.code}`,
      kind: 'asset',
      label: asset.name,
      labelAr: asset.nameAr,
      detail: `${asset.assetTypeKey} · ${Math.round(asset.capacityMw)} MW · risk ${Math.round(asset.riskScore)} %`,
      detailAr: `${asset.assetTypeKey} · ${Math.round(asset.capacityMw)} ميجاواط · الخطر ${Math.round(asset.riskScore)}٪`,
      regionCode: asset.regionCode,
      weight: 1 + asset.riskScore / 40,
      refType: 'asset',
      refId: asset.code,
    })
    link({
      from: `R-${asset.regionCode}`,
      to: `A-${asset.code}`,
      relation: 'contains',
      relationAr: 'تضم',
      weight: 1.4,
    })
    link({
      from: `A-${asset.code}`,
      to: 'C-RISK',
      relation: 'carries',
      relationAr: 'يحمل',
      weight: 1 + asset.riskScore / 50,
    })
    if (asset.assetTypeKey === 'transformer') {
      link({ from: 'C-TEMP', to: `A-${asset.code}`, relation: 'constrains', relationAr: 'يقيّد', weight: 1.6 })
    }
    if (asset.assetTypeKey === 'solar_farm' || asset.assetTypeKey === 'wind_farm') {
      link({
        from: `A-${asset.code}`,
        to: 'C-RENEWABLE',
        relation: 'contributes to',
        relationAr: 'يساهم في',
        weight: 1.8,
      })
    }
  }

  for (const pattern of input.patterns) {
    push({
      code: `P-${pattern.code}`,
      kind: 'pattern',
      label: pattern.name,
      labelAr: pattern.nameAr,
      detail: `${pattern.eventType} · ${pattern.severity}`,
      detailAr: `${pattern.eventType} · ${pattern.severity}`,
      regionCode: null,
      weight: 2.4,
      refType: 'failure_pattern',
      refId: pattern.code,
    })
    link({ from: `P-${pattern.code}`, to: 'C-RISK', relation: 'signature of', relationAr: 'بصمة لـ', weight: 2 })
  }

  for (const incident of input.incidents) {
    push({
      code: `I-${incident.code}`,
      kind: 'incident',
      label: incident.title,
      labelAr: incident.titleAr,
      detail: `severity ${incident.severity}`,
      detailAr: `الشدة ${incident.severity}`,
      regionCode: incident.regionCode,
      weight: 2.2,
      refType: 'incident',
      refId: incident.code,
    })
    if (incident.assetCode) {
      link({
        from: `A-${incident.assetCode}`,
        to: `I-${incident.code}`,
        relation: 'experienced',
        relationAr: 'تعرّض لـ',
        weight: 2,
      })
    }
    if (incident.regionCode) {
      link({
        from: `I-${incident.code}`,
        to: `R-${incident.regionCode}`,
        relation: 'occurred in',
        relationAr: 'وقع في',
        weight: 1.6,
      })
    }
  }

  for (const alert of input.alerts) {
    push({
      code: `AL-${alert.code}`,
      kind: 'alert',
      label: alert.title,
      labelAr: alert.titleAr,
      detail: `level ${alert.level}`,
      detailAr: `المستوى ${alert.level}`,
      regionCode: null,
      weight: 1.6,
      refType: 'alert',
      refId: alert.code,
    })
    if (alert.assetCode) {
      link({
        from: `A-${alert.assetCode}`,
        to: `AL-${alert.code}`,
        relation: 'raised',
        relationAr: 'أطلق',
        weight: 1.6,
      })
    }
  }

  for (const prediction of input.predictions) {
    push({
      code: `PR-${prediction.code}`,
      kind: 'prediction',
      label: prediction.code,
      labelAr: prediction.code,
      detail: `${prediction.eventType} · risk ${Math.round(prediction.riskScore)} %`,
      detailAr: `${prediction.eventType} · الخطر ${Math.round(prediction.riskScore)}٪`,
      regionCode: null,
      weight: 1.8,
      refType: 'prediction',
      refId: prediction.code,
    })
    link({
      from: `A-${prediction.assetCode}`,
      to: `PR-${prediction.code}`,
      relation: 'predicted for',
      relationAr: 'تنبؤ لـ',
      weight: 1.8,
    })
    if (prediction.dnaCode) {
      link({
        from: `PR-${prediction.code}`,
        to: `P-${prediction.dnaCode}`,
        relation: 'matched',
        relationAr: 'طابق',
        weight: 2,
      })
    }
  }

  return { nodes, edges }
}

export interface KnowledgeNeighbourhood {
  focus: KnowledgeNode | null
  nodes: KnowledgeNode[]
  edges: KnowledgeEdge[]
}

/**
 * The sub-graph around one node, out to `depth` hops.
 *
 * Traversal is undirected on purpose: "what is connected to this" does not care which
 * way the arrow points, and following only outgoing edges from an asset would hide the
 * region that contains it.
 */
export function neighbourhood(
  graph: KnowledgeGraphData,
  code: string,
  depth = 1,
  limit = 60,
): KnowledgeNeighbourhood {
  const byCode = new Map(graph.nodes.map((node) => [node.code, node]))
  const focus = byCode.get(code) ?? null
  if (!focus) return { focus: null, nodes: [], edges: [] }

  const included = new Set<string>([code])
  let frontier = [code]

  for (let hop = 0; hop < depth; hop += 1) {
    const next: string[] = []
    for (const edge of graph.edges) {
      if (frontier.includes(edge.from) && !included.has(edge.to)) {
        included.add(edge.to)
        next.push(edge.to)
      } else if (frontier.includes(edge.to) && !included.has(edge.from)) {
        included.add(edge.from)
        next.push(edge.from)
      }
      if (included.size >= limit) break
    }
    if (next.length === 0 || included.size >= limit) break
    frontier = next
  }

  const nodes = graph.nodes.filter((node) => included.has(node.code))
  const edges = graph.edges.filter((edge) => included.has(edge.from) && included.has(edge.to))
  return { focus, nodes, edges }
}

/** Free-text search across labels in both languages. */
export function searchKnowledge(graph: KnowledgeGraphData, query: string, limit = 20): KnowledgeNode[] {
  const needle = query.trim().toLowerCase()
  if (!needle) return []
  return graph.nodes
    .filter(
      (node) =>
        node.label.toLowerCase().includes(needle) ||
        node.labelAr.includes(query.trim()) ||
        node.code.toLowerCase().includes(needle) ||
        node.refId.toLowerCase().includes(needle),
    )
    .sort((a, b) => b.weight - a.weight)
    .slice(0, limit)
}
