/**
 * Seed for the 2.0 decision-intelligence layer (§49).
 *
 * Everything here is *computed*, not written down: the graph comes from the asset
 * register, the cascade from the graph and the live twin, the strategies from the same
 * optimiser the console calls, the counterfactual from the same search. That matters
 * more than it sounds — it means the stored history a judge opens on the first page load
 * was produced by the code paths they are about to exercise, and if the engine changes,
 * re-seeding tells you so.
 *
 * Runs with `tsx` alongside the rest of the seed, so it may only import pure engine
 * modules — nothing under `src/lib/services`, which is server-only.
 */

import type { PrismaClient } from '@prisma/client'

import { REGIONS } from '../../src/lib/data/regions'
import { AGENT_DEFS } from '../../src/lib/engine/agents'
import { buildGridGraph, type GraphAsset } from '../../src/lib/engine/graph'
import { buildGridSnapshot, environmentFor, resourcesFor, type DnaPatternRef } from '../../src/lib/engine/grid'
import { dnaFromRow } from '../../src/lib/engine/dna'
import { simulateCascade, type CascadeAssetState } from '../../src/lib/engine/cascade'
import { predictFailure } from '../../src/lib/engine/prediction'
import {
  evaluateCandidates,
  evaluateStrategy,
  generateCandidates,
  type ActionContext,
} from '../../src/lib/engine/actions'
import { generateStrategies, optimiseStrategies } from '../../src/lib/engine/optimize'
import { findMinimalIntervention, type Lever } from '../../src/lib/engine/counterfactual'
import { assessDataQuality, sensorTrust, type SensorObservation } from '../../src/lib/engine/quality'
import { buildKnowledgeGraph } from '../../src/lib/engine/knowledge'
import { DEFAULT_RISK_WEIGHTS } from '../../src/lib/engine/risk'
import { round } from '../../src/lib/engine/rng'
import type { AssetProfile } from '../../src/lib/engine/telemetry'
import { DEMO_ASSET_CODE, demoBaseTimestamp, demoPerturbation } from '../../src/lib/demo/config'

const SAMPLE_INTERVAL_MIN = 15

/** Physically plausible bounds per channel, mirrored from the quality service. */
const PLAUSIBLE_RANGE: Record<string, { min: number; max: number }> = {
  voltage: { min: 0, max: 800 },
  current: { min: 0, max: 6_000 },
  frequency: { min: 55, max: 65 },
  temperature: { min: -10, max: 160 },
  load: { min: -130, max: 160 },
  power_factor: { min: 0.5, max: 1.05 },
  thd: { min: 0, max: 25 },
}

export interface IntelligenceSeedResult {
  agents: number
  graphNodes: number
  graphEdges: number
  knowledgeNodes: number
  knowledgeEdges: number
  cascadeSteps: number
  strategies: number
  counterfactualActions: number
  preventionPatterns: number
  qualityScores: number
}

export async function seedIntelligence(
  prisma: PrismaClient,
  profiles: Map<string, AssetProfile>,
  userIdByRole: Map<string, string>,
  log: (step: string, detail?: string) => void,
): Promise<IntelligenceSeedResult> {
  const ts = demoBaseTimestamp()
  const perturbation = demoPerturbation()

  // ── Agent registry ───────────────────────────────────────────────────────
  await prisma.aiAgent.createMany({
    data: AGENT_DEFS.map((agent) => ({
      key: agent.key,
      name: agent.name,
      nameAr: agent.nameAr,
      role: agent.role,
      roleAr: agent.roleAr,
      description: agent.description,
      descriptionAr: agent.descriptionAr,
      ordinal: agent.ordinal,
    })),
  })
  log('agents', `${AGENT_DEFS.length} specialist agents registered`)

  // ── Grid graph ───────────────────────────────────────────────────────────
  const assetRows = await prisma.asset.findMany({
    select: {
      id: true,
      code: true,
      name: true,
      nameAr: true,
      lat: true,
      lon: true,
      capacityMw: true,
      voltageKv: true,
      customersServed: true,
      assetType: { select: { key: true } },
      region: { select: { code: true } },
      site: { select: { code: true } },
    },
  })

  const graphAssets: GraphAsset[] = assetRows.map((row) => ({
    code: row.code,
    name: row.name,
    nameAr: row.nameAr,
    assetTypeKey: row.assetType.key,
    regionCode: row.region.code,
    siteCode: row.site.code,
    lat: row.lat,
    lon: row.lon,
    capacityMw: row.capacityMw,
    voltageKv: row.voltageKv,
    customersServed: row.customersServed,
  }))
  const graph = buildGridGraph(graphAssets)
  const assetIdByCode = new Map(assetRows.map((row) => [row.code, row.id]))

  await prisma.gridNode.createMany({
    data: graph.nodes.map((node) => ({
      code: node.code,
      assetId: assetIdByCode.get(node.code) ?? null,
      regionCode: node.regionCode,
      kind: node.assetTypeKey,
      name: node.name,
      nameAr: node.nameAr,
      lat: node.lat,
      lon: node.lon,
      capacityMw: node.capacityMw,
      voltageKv: node.voltageKv,
      centrality: node.centrality,
      customers: node.customersServed,
      isSupply: node.isSupply,
      isLoad: node.isLoad,
    })),
  })
  const nodeRows = await prisma.gridNode.findMany({ select: { id: true, code: true } })
  const nodeIdByCode = new Map(nodeRows.map((row) => [row.code, row.id]))
  const edgeData = graph.edges
    .map((edge) => {
      const fromNodeId = nodeIdByCode.get(edge.from)
      const toNodeId = nodeIdByCode.get(edge.to)
      if (!fromNodeId || !toNodeId) return null
      return {
        code: edge.code,
        fromNodeId,
        toNodeId,
        kind: edge.kind,
        capacityMw: edge.capacityMw,
        lengthKm: edge.lengthKm,
        impedance: edge.impedance,
        shareOfFlow: edge.shareOfFlow,
        isRedundant: edge.isRedundant,
      }
    })
    .filter((edge): edge is NonNullable<typeof edge> => edge !== null)
  await prisma.gridEdge.createMany({ data: edgeData })
  log('grid graph', `${graph.nodes.length} nodes, ${edgeData.length} edges`)

  // ── Live snapshot under the demo scenario ────────────────────────────────
  const patternRows = await prisma.failurePattern.findMany({ include: { dna: true } })
  const patterns: DnaPatternRef[] = patternRows
    .filter((row) => row.dna)
    .map((row) => ({
      code: row.code,
      name: row.name,
      nameAr: row.nameAr,
      eventType: row.eventType,
      severity: row.severity,
      occurredAt: row.occurredAt.getTime(),
      dna: dnaFromRow(row.dna as never),
    }))

  const profileList = [...profiles.values()]
  const snapshot = buildGridSnapshot(profileList, ts, {
    perturbation,
    patterns,
    weights: DEFAULT_RISK_WEIGHTS,
  })
  const focus = snapshot.assets.find((asset) => asset.profile.code === DEMO_ASSET_CODE)
  if (!focus) throw new Error(`Demo asset ${DEMO_ASSET_CODE} missing from the snapshot`)

  const states = new Map<string, CascadeAssetState>(
    snapshot.assets.map((asset) => [
      asset.profile.code,
      {
        code: asset.profile.code,
        loadMw: Math.max(0, asset.sample.loadMw),
        capacityMw: asset.profile.capacityMw,
        loadPct: asset.sample.loadPct,
        isOutage: asset.sample.isOutage,
      },
    ]),
  )

  // ── Cascade runs ─────────────────────────────────────────────────────────
  // Two, deliberately. The demo asset, whose failure the bus absorbs, and the origin
  // that propagates furthest on this fleet — so the stored history shows both answers
  // the engine can give rather than only the comfortable one.
  const cascade = simulateCascade(graph, states, DEMO_ASSET_CODE, { horizonMin: 60 })
  const worstCascade = snapshot.assets
    .map((asset) => simulateCascade(graph, states, asset.profile.code, { horizonMin: 60 }))
    .filter((result) => result.steps.length > 1)
    .sort((a, b) => b.affectedCustomers - a.affectedCustomers || b.maxDepth - a.maxDepth)[0]

  for (const [suffix, result] of [
    [DEMO_ASSET_CODE, cascade],
    ...(worstCascade && worstCascade.originCode !== DEMO_ASSET_CODE
      ? ([[worstCascade.originCode, worstCascade]] as const)
      : []),
  ] as Array<[string, typeof cascade]>) {
    const spread = result.steps.filter((step) => step.outcome === 'failed' && step.depth > 0)
    await prisma.cascadeSimulation.create({
      data: {
        code: `CSC-SEED-${suffix}`,
        originAssetId: assetIdByCode.get(result.originCode) ?? null,
        originCode: result.originCode,
        triggerKind: 'failure',
        horizonMin: result.horizonMin,
        ts: new Date(ts),
        affectedAssets: result.affectedAssets,
        affectedMw: result.affectedMw,
        affectedCustomers: result.affectedCustomers,
        maxDepth: result.maxDepth,
        regionalRisk: result.regionalRisk,
        containedAtMin: result.containedAtMin,
        summary:
          spread.length > 0
            ? `A failure on ${result.originCode} propagates through ${spread.length} further assets, reaching ${result.affectedCustomers.toLocaleString('en-US')} customers.`
            : `A failure on ${result.originCode} stays contained; adjacent units absorb the transfer.`,
        summaryAr:
          spread.length > 0
            ? `العطل على ${result.originCode} ينتشر عبر ${spread.length} أصول إضافية ويصل إلى ${result.affectedCustomers.toLocaleString('en-US')} مشترك.`
            : `العطل على ${result.originCode} يبقى محصورًا؛ الوحدات المجاورة تستوعب التحويل.`,
        createdById: userIdByRole.get('control_operator') ?? null,
        steps: {
          create: result.steps.map((step) => ({
            ordinal: step.ordinal,
            depth: step.depth,
            offsetMin: step.offsetMin,
            assetCode: step.assetCode,
            assetName: step.assetName,
            assetNameAr: step.assetNameAr,
            effect: step.outcome,
            effectAr: step.outcome,
            mechanism: step.mechanism,
            loadPct: step.loadPctAfter,
            probability: step.probability,
            severity: step.severity,
            customers: step.customers,
            capacityMw: step.capacityMw,
          })),
        },
      },
    })
  }
  log(
    'cascade',
    `${cascade.steps.length} step(s) from ${DEMO_ASSET_CODE}` +
      (worstCascade ? `, ${worstCascade.steps.length} from ${worstCascade.originCode}` : ''),
  )


  // ── Optimisation and strategies ──────────────────────────────────────────
  const region = REGIONS.find((entry) => entry.code === focus.profile.regionCode)!
  const envAt = environmentFor(region, perturbation)
  const prediction = predictFailure(focus.profile, ts, envAt, {
    perturbation,
    dnaSimilarity: focus.dnaSimilarity,
    neighbourStress: focus.neighbourStress,
    weights: DEFAULT_RISK_WEIGHTS,
    horizonMin: 180,
    stepMin: 5,
  })

  const resources = resourcesFor(focus.profile, snapshot.assets)
  const actionContext: ActionContext = {
    profile: focus.profile,
    ts,
    envAt,
    perturbation,
    dnaSimilarity: focus.dnaSimilarity,
    neighbourStress: focus.neighbourStress,
    weights: DEFAULT_RISK_WEIGHTS,
    resources,
    evaluateAtMin: prediction.etaMinutes ?? 45,
  }
  const actions = evaluateCandidates(actionContext, generateCandidates(actionContext))
  const optimisation = optimiseStrategies(actionContext, generateStrategies(actionContext, actions))

  const optimizationRun = await prisma.optimizationRun.create({
    data: {
      code: `OPT-SEED-${DEMO_ASSET_CODE}`,
      assetCode: DEMO_ASSET_CODE,
      objectiveKey: optimisation.objectiveKey,
      candidateCount: optimisation.strategies.length,
      bestScore: optimisation.best?.score ?? 0,
      horizonMin: prediction.horizonMin,
      weightsDigest: Object.entries(optimisation.weights)
        .map(([key, value]) => `${key}=${value}`)
        .join(','),
      createdById: userIdByRole.get('control_operator') ?? null,
    },
  })

  let recommendedStrategyId: string | null = null
  for (const strategy of optimisation.strategies) {
    const created = await prisma.strategy.create({
      data: {
        code: `OPT-SEED-${DEMO_ASSET_CODE}-${strategy.key}`,
        optimizationId: optimizationRun.id,
        assetCode: DEMO_ASSET_CODE,
        label: strategy.label,
        labelAr: strategy.labelAr,
        kind: strategy.kind,
        rank: strategy.rank,
        score: strategy.score,
        riskBefore: strategy.evaluation.riskBefore,
        riskAfter: strategy.evaluation.riskAfter,
        costSar: strategy.costSar,
        executionMin: strategy.executionMin,
        customerImpact: strategy.customerImpact,
        totalMw: strategy.evaluation.totalMw,
        confidence: strategy.confidence,
        isRecommended: strategy.isRecommended,
        actions: {
          create: strategy.sourceActions.map((action, index) => ({
            ordinal: index,
            actionType: action.actionType,
            actionKey: action.key,
            assetCode: DEMO_ASSET_CODE,
            magnitudeMw: action.magnitudeMw,
            durationMin: action.durationMin,
            costSar: action.costEstimateSar,
            label: action.title,
            labelAr: action.titleAr,
          })),
        },
        results: {
          create: strategy.metrics.map((metric) => ({
            metricKey: metric.key,
            value: metric.value,
            normalised: metric.normalised,
            weight: metric.weight,
            unit: metric.unit,
          })),
        },
      },
    })
    if (strategy.isRecommended) recommendedStrategyId = created.id
  }
  if (recommendedStrategyId) {
    await prisma.optimizationRun.update({
      where: { id: optimizationRun.id },
      data: { bestStrategyId: recommendedStrategyId },
    })
  }
  log('optimisation', `${optimisation.strategies.length} strategies scored for ${DEMO_ASSET_CODE}`)

  // ── Counterfactual: the smallest intervention that clears the watch band ──
  // Capped by what is actually flowing through the asset, for the reason set out in
  // src/lib/services/counterfactual-service.ts: a dose larger than the load is not a
  // larger intervention, it is an unphysical one.
  const removableMw = Math.max(0.5, Math.abs(focus.sample.loadMw))
  const cap = (available: number) => round(Math.min(available, removableMw), 1)

  const levers: Lever[] = []
  if (resources.transferCapacityMw > 0.5) {
    levers.push({
      key: 'load_transfer',
      actionType: 'load_transfer',
      assetCode: DEMO_ASSET_CODE,
      unit: 'MW',
      minDose: 0.3,
      maxDose: cap(resources.transferCapacityMw),
      costPerUnit: 950,
      durationMin: 12,
      effortPerUnit: 1,
      label: (dose) => `Transfer ${dose} MW to adjacent feeders`,
      labelAr: (dose) => `تحويل ${dose} ميجاواط إلى المغذيات المجاورة`,
      toActions: (dose) => [{ type: 'load_transfer', assetCode: DEMO_ASSET_CODE, magnitudeMw: dose }],
    })
  }
  if (resources.batteryPowerMw > 0.5) {
    levers.push({
      key: 'battery_discharge',
      actionType: 'battery_discharge',
      assetCode: DEMO_ASSET_CODE,
      unit: 'MW',
      minDose: 0.3,
      maxDose: cap(resources.batteryPowerMw),
      costPerUnit: 540,
      durationMin: 45,
      effortPerUnit: 0.7,
      label: (dose) => `Dispatch ${dose} MW from battery storage`,
      labelAr: (dose) => `تشغيل ${dose} ميجاواط من تخزين البطاريات`,
      toActions: (dose) => [{ type: 'battery_discharge', assetCode: DEMO_ASSET_CODE, magnitudeMw: dose }],
    })
  }
  if (resources.flexibleLoadMw > 0.4) {
    levers.push({
      key: 'demand_response',
      actionType: 'demand_response',
      assetCode: DEMO_ASSET_CODE,
      unit: 'MW',
      minDose: 0.3,
      maxDose: cap(resources.flexibleLoadMw),
      costPerUnit: 1_400,
      durationMin: 60,
      effortPerUnit: 2.4,
      label: (dose) => `Defer ${dose} MW of contracted flexible demand`,
      labelAr: (dose) => `تأجيل ${dose} ميجاواط من الأحمال المرنة المتعاقد عليها`,
      toActions: (dose) => [{ type: 'demand_response', assetCode: DEMO_ASSET_CODE, magnitudeMw: dose }],
    })
  }

  const target = 12
  const counterfactual = findMinimalIntervention(levers, {
    measure: (applied) => evaluateStrategy(actionContext, applied).riskAfter,
    satisfied: (metric) => metric <= target,
    higherIsBetter: false,
  })

  await prisma.counterfactualRun.create({
    data: {
      code: `CF-SEED-${DEMO_ASSET_CODE}`,
      assetId: assetIdByCode.get(DEMO_ASSET_CODE) ?? null,
      assetCode: DEMO_ASSET_CODE,
      objective: 'risk_below',
      targetValue: target,
      riskBefore: counterfactual.metricBefore,
      riskAfter: counterfactual.metricAfter,
      achieved: counterfactual.achieved,
      iterations: counterfactual.iterations,
      totalMw: counterfactual.totalMw,
      totalCostSar: counterfactual.totalCostSar,
      confidence: round(Math.min(prediction.confidence, 92), 1),
      horizonMin: prediction.horizonMin,
      summary: `Minimum intervention to bring risk below ${target} %: ${counterfactual.doses.map((dose) => dose.label).join(' + ') || 'none required'}.`,
      summaryAr: `أقل تدخل لخفض الخطر دون ${target}٪: ${counterfactual.doses.map((dose) => dose.labelAr).join(' + ') || 'لا حاجة لأي إجراء'}.`,
      createdById: userIdByRole.get('control_operator') ?? null,
      actions: {
        create: counterfactual.doses.map((dose, index) => ({
          ordinal: index,
          actionType: dose.actionType,
          actionKey: dose.key,
          magnitudeMw: dose.unit === 'MW' ? dose.dose : 0,
          durationMin: dose.durationMin,
          unit: dose.unit,
          label: dose.label,
          labelAr: dose.labelAr,
          contribution: dose.contribution,
        })),
      },
    },
  })
  log(
    'counterfactual',
    `${counterfactual.doses.length} levers, risk ${counterfactual.metricBefore} → ${counterfactual.metricAfter}`,
  )

  // ── Prevention patterns from incidents that were actually prevented ──────
  const preventedIncidents = await prisma.incident.findMany({
    where: { wasPrevented: true },
    include: { asset: { include: { assetType: { select: { key: true } } } } },
    take: 6,
  })
  const patternData = preventedIncidents
    .filter((incident) => incident.asset)
    .map((incident, index) => {
      const profile = profiles.get(incident.asset!.code)
      const riskBefore = 62 + ((index * 7) % 26)
      return {
        code: `P-${101 + index}`,
        problemKey: incident.rootCause.toLowerCase().includes('thermal')
          ? 'cooling_degradation'
          : 'demand_surge',
        problem: incident.rootCause,
        problemAr: incident.rootCauseAr,
        strategyLabel: 'Load transfer with battery support',
        strategyLabelAr: 'تحويل الحمل مع دعم البطاريات',
        // Incidents record a root cause rather than an event type; the pattern is keyed
        // by the failure mode the asset class actually exhibits.
        eventType:
          incident.asset!.assetType.key === 'transmission_line'
            ? 'line_overload'
            : 'transformer_overload',
        assetTypeKey: incident.asset!.assetType.key,
        riskBefore,
        riskAfter: round(riskBefore * 0.16, 1),
        timesApplied: 1 + (index % 3),
        successRate: round(84 - index * 2.5, 1),
        loadPct: round(profile ? profile.baselineLoadPct + 24 : 92, 1),
        tempC: round(profile ? profile.ratedTempC - 6 : 88, 1),
        dnaSimilarity: round(78 + ((index * 5) % 14), 1),
        isDemo: true,
      }
    })
  if (patternData.length > 0) await prisma.preventionPattern.createMany({ data: patternData })
  log('prevention patterns', `${patternData.length} reusable patterns recorded`)

  // ── Data quality and sensor trust ────────────────────────────────────────
  const windowHours = 24
  const since = new Date(ts - windowHours * 3_600_000)
  const watchAssets = await prisma.asset.findMany({
    where: { sensors: { some: {} } },
    select: {
      id: true,
      code: true,
      sensors: {
        select: {
          id: true,
          code: true,
          kind: true,
          accuracy: true,
          status: true,
          lastSeenAt: true,
          installedAt: true,
        },
      },
    },
    take: 40,
  })

  const expectedCount = Math.max(1, Math.round((windowHours * 60) / SAMPLE_INTERVAL_MIN))
  let qualityScores = 0
  for (const asset of watchAssets) {
    const readings = await prisma.sensorReading.findMany({
      where: { assetId: asset.id, ts: { gte: since } },
      select: { sensorId: true, kind: true, value: true, ts: true },
    })
    if (readings.length === 0) continue

    const counts = new Map<string, { total: number; outliers: number }>()
    for (const reading of readings) {
      const entry = counts.get(reading.sensorId) ?? { total: 0, outliers: 0 }
      entry.total += 1
      const range = PLAUSIBLE_RANGE[reading.kind]
      if (range && (reading.value < range.min || reading.value > range.max)) entry.outliers += 1
      counts.set(reading.sensorId, entry)
    }

    const observations: SensorObservation[] = asset.sensors.map((sensor) => ({
      code: sensor.code,
      kind: sensor.kind,
      accuracy: sensor.accuracy,
      status: sensor.status,
      lastSeenAt: sensor.lastSeenAt?.getTime() ?? null,
      readingCount: counts.get(sensor.id)?.total ?? 0,
      expectedCount,
      outlierCount: counts.get(sensor.id)?.outliers ?? 0,
      installedAt: sensor.installedAt.getTime(),
      lastCalibratedAt: null,
    }))

    const earliest = Math.min(...readings.map((reading) => reading.ts.getTime()))
    const quality = assessDataQuality(observations, ts, {
      expectedSensors: asset.sensors.length,
      coverageHours: (ts - earliest) / 3_600_000,
    })

    await prisma.dataQualityScore.create({
      data: {
        scopeKind: 'asset',
        scopeCode: asset.code,
        score: quality.score,
        sensorsExpected: quality.sensorsExpected,
        sensorsReporting: quality.sensorsReporting,
        staleSensors: quality.staleSensors,
        outlierCount: quality.outlierCount,
        coverageHours: quality.coverageHours,
        band: quality.band,
        computedAt: new Date(ts),
      },
    })
    qualityScores += 1

    for (const sensor of asset.sensors) {
      const observation = observations.find((entry) => entry.code === sensor.code)
      if (!observation) continue
      const trust = sensorTrust(observation, ts)
      await prisma.sensorTrustScore.create({
        data: {
          sensorId: sensor.id,
          score: trust.score,
          missingRate: trust.missingRate,
          noiseLevel: trust.noiseLevel,
          driftPct: trust.driftPct,
          outlierRate: trust.outlierRate,
          band: trust.band,
          computedAt: new Date(ts),
        },
      })
    }
  }
  log('data quality', `${qualityScores} assets scored for data confidence`)

  // ── Knowledge graph ──────────────────────────────────────────────────────
  const [knowledgeIncidents, knowledgeAlerts, knowledgePredictions] = await Promise.all([
    prisma.incident.findMany({
      orderBy: { startedAt: 'desc' },
      take: 20,
      select: {
        code: true,
        title: true,
        titleAr: true,
        severity: true,
        asset: { select: { code: true } },
        region: { select: { code: true } },
      },
    }),
    prisma.alert.findMany({
      orderBy: { createdAt: 'desc' },
      take: 25,
      select: { code: true, title: true, titleAr: true, level: true, asset: { select: { code: true } } },
    }),
    prisma.prediction.findMany({
      orderBy: { createdAt: 'desc' },
      take: 25,
      select: {
        code: true,
        eventType: true,
        riskScore: true,
        dnaCode: true,
        asset: { select: { code: true } },
      },
    }),
  ])

  const nameByCode = new Map(assetRows.map((row) => [row.code, row]))
  const knowledge = buildKnowledgeGraph({
    regions: REGIONS.map((entry) => ({
      code: entry.code,
      name: entry.name,
      nameAr: entry.nameAr,
      peakDemandMw: entry.peakDemandMw,
      climate: entry.climate,
      climateAr: entry.climateAr,
    })),
    assets: snapshot.assets.map((asset) => {
      const record = nameByCode.get(asset.profile.code)
      return {
        code: asset.profile.code,
        name: record?.name ?? asset.profile.code,
        nameAr: record?.nameAr ?? asset.profile.code,
        assetTypeKey: asset.profile.assetTypeKey,
        regionCode: asset.profile.regionCode,
        capacityMw: asset.profile.capacityMw,
        customersServed: asset.profile.customersServed,
        riskScore: asset.risk.score,
        healthScore: asset.sample.effectiveHealth,
      }
    }),
    patterns: patternRows.map((row) => ({
      code: row.code,
      name: row.name,
      nameAr: row.nameAr,
      eventType: row.eventType,
      severity: row.severity,
    })),
    incidents: knowledgeIncidents.map((incident) => ({
      code: incident.code,
      title: incident.title,
      titleAr: incident.titleAr,
      assetCode: incident.asset?.code ?? null,
      regionCode: incident.region?.code ?? null,
      severity: incident.severity,
    })),
    alerts: knowledgeAlerts.map((alert) => ({
      code: alert.code,
      title: alert.title,
      titleAr: alert.titleAr,
      assetCode: alert.asset?.code ?? null,
      level: alert.level,
    })),
    predictions: knowledgePredictions
      .filter((prediction) => prediction.asset)
      .map((prediction) => ({
        code: prediction.code,
        assetCode: prediction.asset!.code,
        eventType: prediction.eventType,
        riskScore: prediction.riskScore,
        dnaCode: prediction.dnaCode,
      })),
  })

  await prisma.knowledgeNode.createMany({
    data: knowledge.nodes.map((node) => ({
      code: node.code,
      kind: node.kind,
      label: node.label,
      labelAr: node.labelAr,
      detail: node.detail,
      detailAr: node.detailAr,
      regionCode: node.regionCode,
      weight: node.weight,
      refType: node.refType,
      refId: node.refId,
    })),
  })
  const knowledgeRows = await prisma.knowledgeNode.findMany({ select: { id: true, code: true } })
  const knowledgeIdByCode = new Map(knowledgeRows.map((row) => [row.code, row.id]))
  const knowledgeEdgeData = knowledge.edges
    .map((edge) => {
      const fromNodeId = knowledgeIdByCode.get(edge.from)
      const toNodeId = knowledgeIdByCode.get(edge.to)
      if (!fromNodeId || !toNodeId) return null
      return {
        fromNodeId,
        toNodeId,
        relation: edge.relation,
        relationAr: edge.relationAr,
        weight: edge.weight,
      }
    })
    .filter((edge): edge is NonNullable<typeof edge> => edge !== null)
  await prisma.knowledgeEdge.createMany({ data: knowledgeEdgeData })
  log('knowledge graph', `${knowledge.nodes.length} nodes, ${knowledgeEdgeData.length} relations`)

  // ── A worked decision, with its approval trail ───────────────────────────
  const best = optimisation.best
  const operatorId = userIdByRole.get('control_operator') ?? null
  if (best && recommendedStrategyId) {
    const decision = await prisma.decisionRecord.create({
      data: {
        code: `DEC-SEED-${DEMO_ASSET_CODE}`,
        assetCode: DEMO_ASSET_CODE,
        strategyId: recommendedStrategyId,
        title: best.label,
        titleAr: best.labelAr,
        rationale: `Recommended after scoring ${optimisation.strategies.length} candidate strategies on the same objective; risk falls from ${round(best.evaluation.riskBefore, 1)} % to ${round(best.evaluation.riskAfter, 1)} %.`,
        rationaleAr: `مُوصى بها بعد تقييم ${optimisation.strategies.length} استراتيجيات مرشحة على الهدف نفسه؛ وينخفض الخطر من ${round(best.evaluation.riskBefore, 1)}٪ إلى ${round(best.evaluation.riskAfter, 1)}٪.`,
        riskBefore: best.evaluation.riskBefore,
        riskAfter: best.evaluation.riskAfter,
        recommendationConfidence: best.confidence,
        dataConfidence: 91,
        predictionConfidence: prediction.confidence,
        simulationConfidence: 86,
        status: 'pending',
        dataTs: new Date(ts),
        createdById: operatorId,
      },
    })
    await prisma.approval.createMany({
      data: [
        { decisionId: decision.id, step: 'operator', decision: 'pending' },
        { decisionId: decision.id, step: 'supervisor', decision: 'pending' },
      ],
    })
    log('decision', `${decision.code} raised and awaiting operator review`)
  }

  return {
    agents: AGENT_DEFS.length,
    graphNodes: graph.nodes.length,
    graphEdges: edgeData.length,
    knowledgeNodes: knowledge.nodes.length,
    knowledgeEdges: knowledgeEdgeData.length,
    cascadeSteps: cascade.steps.length,
    strategies: optimisation.strategies.length,
    counterfactualActions: counterfactual.doses.length,
    preventionPatterns: patternData.length,
    qualityScores,
  }
}

/** Tables the 2.0 layer owns, ordered child-first for a clean reset. */
export async function resetIntelligence(prisma: PrismaClient): Promise<void> {
  await prisma.$transaction([
    prisma.approval.deleteMany(),
    prisma.decisionRecord.deleteMany(),
    prisma.strategyResult.deleteMany(),
    prisma.strategyAction.deleteMany(),
    prisma.strategy.deleteMany(),
    prisma.optimizationRun.deleteMany(),
    prisma.counterfactualAction.deleteMany(),
    prisma.counterfactualRun.deleteMany(),
    prisma.cascadeStep.deleteMany(),
    prisma.cascadeSimulation.deleteMany(),
    prisma.agentFinding.deleteMany(),
    prisma.agentRun.deleteMany(),
    prisma.aiAgent.deleteMany(),
    prisma.knowledgeEdge.deleteMany(),
    prisma.knowledgeNode.deleteMany(),
    prisma.gridEdge.deleteMany(),
    prisma.gridNode.deleteMany(),
    prisma.rootCauseAnalysis.deleteMany(),
    prisma.sensorTrustScore.deleteMany(),
    prisma.dataQualityScore.deleteMany(),
    prisma.preventionPattern.deleteMany(),
  ])
}
