/**
 * The evidence graph (§49, §50, §51, §52, §53).
 *
 * This is the module behind the PROVE IT button, and it exists because of a specific
 * failure mode in AI products: the demo shows a confident conclusion, a judge asks how it
 * got there, and the answer is a diagram of the intended pipeline rather than a record of
 * what actually ran.
 *
 * So the graph is assembled from the real artefacts of one analysis — the readings that
 * were used, their provenance, the DNA match that fired, the physics check that passed,
 * the cascade that was walked, the simulation that was run — with each node carrying the
 * value it contributed and where that value came from. If a stage did not run, there is
 * no node for it, and the gap is visible.
 *
 * The chain is linear by construction because the pipeline is, but the edges are typed:
 * a physics check *constrains* a recommendation, it does not derive it, and drawing both
 * the same way would misrepresent what the validator does.
 */

import type { DataSource } from '../domain/provenance'
import { clamp, round } from './rng'

// ─────────────────────────────────────────────────────────────────────────────
// Types
// ─────────────────────────────────────────────────────────────────────────────

export const EVIDENCE_STAGES = [
  'input',
  'observation',
  'data_quality',
  'dna',
  'prediction',
  'physics',
  'twin',
  'cascade',
  'simulation',
  'recommendation',
  'impact',
] as const
export type EvidenceStage = (typeof EVIDENCE_STAGES)[number]

export interface EvidenceNode {
  key: string
  stage: EvidenceStage
  title: string
  titleAr: string
  detail: string
  detailAr: string
  sourceType: DataSource
  sourceId: string
  value: number | null
  unit: string
  confidence: number
  freshnessMin: number
  modelVersion: string
  orderIndex: number
}

export type EvidenceRelation = 'supports' | 'derives' | 'contradicts' | 'constrains'

export interface EvidenceEdge {
  fromKey: string
  toKey: string
  relation: EvidenceRelation
  weight: number
  label: string
  labelAr: string
}

export interface EvidenceBundle {
  code: string
  assetCode: string
  nodes: EvidenceNode[]
  edges: EvidenceEdge[]
  /** The weakest confidence anywhere in the chain — a chain is only as good as this. */
  weakestLink: { key: string; title: string; titleAr: string; confidence: number } | null
  /** Stages that did not run, so an incomplete chain reads as incomplete (§51). */
  missingStages: EvidenceStage[]
  /** Share of the values in the chain that are measured rather than modelled, 0–100. */
  measuredSharePct: number
}

// ─────────────────────────────────────────────────────────────────────────────
// Builder
// ─────────────────────────────────────────────────────────────────────────────

export interface EvidenceInput {
  code: string
  assetCode: string
  modelVersion: string
  twinVersion: string
  observations: Array<{
    channel: string
    label: string
    labelAr: string
    value: number
    unit: string
    sourceType: DataSource
    sourceId: string
    freshnessMin: number
    trust: number
  }>
  dataQuality: { score: number; band: string; missingSensors: number; untrusted: number }
  dna: { code: string; name: string; nameAr: string; similarity: number } | null
  prediction: {
    currentRisk: number
    peakRisk: number
    etaMinutes: number | null
    confidence: number
  }
  uncertainty: {
    probability: number
    windowLowMinutes: number | null
    windowHighMinutes: number | null
    predictionConfidence: number
  } | null
  physics: {
    verdict: string
    checksRun: number
    checksFailed: number
    confidence: number
    summary: string
    summaryAr: string
  } | null
  twin: { code: string; fidelity: number; band: string } | null
  cascade: {
    steps: number
    affectedAssets: number
    probability: number
    containedAtMin: number | null
    /** Depth of the deepest branch — confidence falls as the chain gets longer. */
    maxDepth: number
  } | null
  simulation: {
    code: string
    riskBefore: number
    riskAfter: number
    strategyLabel: string
    strategyLabelAr: string
  } | null
  recommendation: { label: string; labelAr: string; confidence: number } | null
  impact: { customersProtected: number; unservedAvoidedMw: number; confidence: number } | null
}

const STAGE_TITLES: Record<EvidenceStage, [string, string]> = {
  input: ['Inputs', 'المدخلات'],
  observation: ['Observation', 'الرصد'],
  data_quality: ['Data quality', 'جودة البيانات'],
  dna: ['Failure DNA', 'بصمة العطل'],
  prediction: ['AI prediction', 'توقع الذكاء الاصطناعي'],
  physics: ['Physics check', 'الفحص الفيزيائي'],
  twin: ['Digital twin', 'التوأم الرقمي'],
  cascade: ['Cascade', 'التسلسل'],
  simulation: ['Simulation', 'المحاكاة'],
  recommendation: ['Recommendation', 'التوصية'],
  impact: ['Expected impact', 'الأثر المتوقع'],
}

/**
 * Assemble the chain.
 *
 * Order is fixed and matches the pipeline in §50, so the same journey renders the same
 * way every time — a judge stepping through two different assets should be looking at the
 * same structure with different numbers, not at a graph that reshuffles itself.
 */
export function buildEvidenceBundle(input: EvidenceInput): EvidenceBundle {
  const nodes: EvidenceNode[] = []
  const edges: EvidenceEdge[] = []
  let order = 0

  const add = (node: Omit<EvidenceNode, 'orderIndex'>) => {
    nodes.push({ ...node, orderIndex: order++ })
    return node.key
  }
  const link = (
    fromKey: string,
    toKey: string,
    relation: EvidenceRelation,
    label: string,
    labelAr: string,
    weight = 1,
  ) => {
    edges.push({ fromKey, toKey, relation, weight, label, labelAr })
  }

  // ── Inputs ────────────────────────────────────────────────────────────────
  const inputKey = add({
    key: 'input',
    stage: 'input',
    title: STAGE_TITLES.input[0],
    titleAr: STAGE_TITLES.input[1],
    detail: `${input.observations.length} channel${input.observations.length === 1 ? '' : 's'} read for ${input.assetCode}`,
    detailAr: `قراءة ${input.observations.length} قناة لـ${input.assetCode}`,
    sourceType: input.observations[0]?.sourceType ?? 'synthetic',
    sourceId: input.assetCode,
    value: input.observations.length,
    unit: 'channels',
    confidence: 100,
    freshnessMin: 0,
    modelVersion: '',
  })

  // ── Observations ──────────────────────────────────────────────────────────
  let previous = inputKey
  for (const obs of input.observations) {
    const key = add({
      key: `obs:${obs.channel}`,
      stage: 'observation',
      title: obs.label,
      titleAr: obs.labelAr,
      detail: `${round(obs.value, 2)} ${obs.unit} from ${obs.sourceId}`,
      detailAr: `${round(obs.value, 2)} ${obs.unit} من ${obs.sourceId}`,
      sourceType: obs.sourceType,
      sourceId: obs.sourceId,
      value: round(obs.value, 2),
      unit: obs.unit,
      confidence: round(obs.trust, 1),
      freshnessMin: round(obs.freshnessMin, 1),
      modelVersion: '',
    })
    link(inputKey, key, 'derives', 'read', 'قراءة')
    previous = key
  }

  // ── Data quality ──────────────────────────────────────────────────────────
  const qualityKey = add({
    key: 'data_quality',
    stage: 'data_quality',
    title: STAGE_TITLES.data_quality[0],
    titleAr: STAGE_TITLES.data_quality[1],
    detail:
      input.dataQuality.missingSensors > 0
        ? `${input.dataQuality.score} / 100 (${input.dataQuality.band}) — ${input.dataQuality.missingSensors} sensor(s) missing`
        : `${input.dataQuality.score} / 100 (${input.dataQuality.band})`,
    detailAr:
      input.dataQuality.missingSensors > 0
        ? `${input.dataQuality.score} / ١٠٠ — ${input.dataQuality.missingSensors} حساس مفقود`
        : `${input.dataQuality.score} / ١٠٠`,
    sourceType: 'estimated',
    sourceId: 'quality-engine',
    value: input.dataQuality.score,
    unit: '/100',
    confidence: input.dataQuality.score,
    freshnessMin: 0,
    modelVersion: '',
  })
  for (const obs of input.observations) link(`obs:${obs.channel}`, qualityKey, 'supports', 'scored', 'تقييم')
  if (!input.observations.length) link(inputKey, qualityKey, 'derives', 'scored', 'تقييم')

  // ── DNA ───────────────────────────────────────────────────────────────────
  let dnaKey: string | null = null
  if (input.dna) {
    dnaKey = add({
      key: 'dna',
      stage: 'dna',
      title: input.dna.name,
      titleAr: input.dna.nameAr,
      detail: `${input.dna.code} matched at ${round(input.dna.similarity, 1)} %`,
      detailAr: `${input.dna.code} تطابق بنسبة ${round(input.dna.similarity, 1)}٪`,
      sourceType: 'estimated',
      sourceId: input.dna.code,
      value: round(input.dna.similarity, 1),
      unit: '%',
      confidence: round(input.dna.similarity, 1),
      freshnessMin: 0,
      modelVersion: input.modelVersion,
    })
    link(qualityKey, dnaKey, 'derives', 'compared', 'مقارنة')
  }

  // ── Prediction ────────────────────────────────────────────────────────────
  const predictionKey = add({
    key: 'prediction',
    stage: 'prediction',
    title: STAGE_TITLES.prediction[0],
    titleAr: STAGE_TITLES.prediction[1],
    detail:
      input.prediction.etaMinutes === null
        ? `Risk ${round(input.prediction.currentRisk, 1)} % rising to ${round(input.prediction.peakRisk, 1)} %; no limit crossing predicted`
        : `Limit crossing in ~${round(input.prediction.etaMinutes, 0)} min, peak risk ${round(input.prediction.peakRisk, 1)} %`,
    detailAr:
      input.prediction.etaMinutes === null
        ? `الخطر ${round(input.prediction.currentRisk, 1)}٪ ويرتفع إلى ${round(input.prediction.peakRisk, 1)}٪ دون تجاوز حد`
        : `تجاوز الحد خلال ~${round(input.prediction.etaMinutes, 0)} دقيقة، ذروة الخطر ${round(input.prediction.peakRisk, 1)}٪`,
    sourceType: 'predicted',
    sourceId: 'prediction-engine',
    value: round(input.prediction.peakRisk, 1),
    unit: '%',
    confidence: round(input.uncertainty?.predictionConfidence ?? input.prediction.confidence, 1),
    freshnessMin: 0,
    modelVersion: input.modelVersion,
  })
  link(qualityKey, predictionKey, 'supports', 'confidence bounded by', 'تحدّ الثقة')
  if (dnaKey) link(dnaKey, predictionKey, 'supports', 'pattern support', 'دعم النمط')

  // ── Physics ───────────────────────────────────────────────────────────────
  let physicsKey: string | null = null
  if (input.physics) {
    physicsKey = add({
      key: 'physics',
      stage: 'physics',
      title: STAGE_TITLES.physics[0],
      titleAr: STAGE_TITLES.physics[1],
      detail: input.physics.summary,
      detailAr: input.physics.summaryAr,
      sourceType: 'estimated',
      sourceId: 'physics-validator',
      value: input.physics.checksRun,
      unit: 'checks',
      confidence: round(input.physics.confidence, 1),
      freshnessMin: 0,
      modelVersion: '',
    })
    // A failing check contradicts the prediction rather than supporting it, and the
    // graph should say which of the two it is.
    link(
      predictionKey,
      physicsKey,
      input.physics.checksFailed > 0 ? 'contradicts' : 'supports',
      input.physics.checksFailed > 0 ? 'breaches a limit' : 'physically feasible',
      input.physics.checksFailed > 0 ? 'يخالف حدًا' : 'ممكن فيزيائيًا',
    )
  }

  // ── Twin ──────────────────────────────────────────────────────────────────
  let twinKey: string | null = null
  if (input.twin) {
    twinKey = add({
      key: 'twin',
      stage: 'twin',
      title: STAGE_TITLES.twin[0],
      titleAr: STAGE_TITLES.twin[1],
      detail: `${input.twin.code} at ${round(input.twin.fidelity, 1)} % fidelity (${input.twin.band})`,
      detailAr: `${input.twin.code} بدقة ${round(input.twin.fidelity, 1)}٪`,
      sourceType: 'simulated',
      sourceId: input.twin.code,
      value: round(input.twin.fidelity, 1),
      unit: '%',
      confidence: round(input.twin.fidelity, 1),
      freshnessMin: 0,
      modelVersion: input.twinVersion,
    })
    link(predictionKey, twinKey, 'derives', 'walked forward on', 'تُمشى على')
  }

  // ── Cascade ───────────────────────────────────────────────────────────────
  let cascadeKey: string | null = null
  if (input.cascade) {
    cascadeKey = add({
      key: 'cascade',
      stage: 'cascade',
      title: STAGE_TITLES.cascade[0],
      titleAr: STAGE_TITLES.cascade[1],
      detail:
        input.cascade.affectedAssets <= 1
          ? 'Contained at the origin — no propagation path found'
          : `${input.cascade.affectedAssets} assets over ${input.cascade.steps} steps`,
      detailAr:
        input.cascade.affectedAssets <= 1
          ? 'محتوى عند المصدر — لا مسار انتشار'
          : `${input.cascade.affectedAssets} أصلًا عبر ${input.cascade.steps} خطوة`,
      sourceType: 'simulated',
      sourceId: 'cascade-engine',
      value: input.cascade.affectedAssets,
      unit: 'assets',
      // Confidence in the *finding*, not the probability of propagation. A cascade that
      // stops at the origin is a definite answer and deserves high confidence; a
      // four-deep chain is a stack of assumptions and deserves less. Using the
      // propagation probability here would have said the opposite — reporting a
      // confidently-contained cascade as barely-trusted evidence.
      confidence: round(clamp(94 - input.cascade.maxDepth * 11, 25, 96), 1),
      freshnessMin: 0,
      modelVersion: '',
    })
    link(twinKey ?? predictionKey, cascadeKey, 'derives', 'propagated through', 'انتشر عبر')
  }

  // ── Simulation ────────────────────────────────────────────────────────────
  let simulationKey: string | null = null
  if (input.simulation) {
    simulationKey = add({
      key: 'simulation',
      stage: 'simulation',
      title: STAGE_TITLES.simulation[0],
      titleAr: STAGE_TITLES.simulation[1],
      detail: `${input.simulation.strategyLabel}: ${round(input.simulation.riskBefore, 1)} % → ${round(input.simulation.riskAfter, 1)} %`,
      detailAr: `${input.simulation.strategyLabelAr}: ${round(input.simulation.riskBefore, 1)}٪ ← ${round(input.simulation.riskAfter, 1)}٪`,
      sourceType: 'simulated',
      sourceId: input.simulation.code,
      value: round(input.simulation.riskAfter, 1),
      unit: '%',
      confidence: round(input.twin?.fidelity ?? 70, 1),
      freshnessMin: 0,
      modelVersion: input.twinVersion,
    })
    link(cascadeKey ?? predictionKey, simulationKey, 'derives', 'tested against', 'اختُبرت مقابل')
  }

  // ── Recommendation ────────────────────────────────────────────────────────
  let recommendationKey: string | null = null
  if (input.recommendation) {
    recommendationKey = add({
      key: 'recommendation',
      stage: 'recommendation',
      title: input.recommendation.label,
      titleAr: input.recommendation.labelAr,
      detail: STAGE_TITLES.recommendation[0],
      detailAr: STAGE_TITLES.recommendation[1],
      sourceType: 'estimated',
      sourceId: 'orchestrator',
      value: null,
      unit: '',
      confidence: round(input.recommendation.confidence, 1),
      freshnessMin: 0,
      modelVersion: input.modelVersion,
    })
    link(simulationKey ?? predictionKey, recommendationKey, 'derives', 'selected', 'اختيرت')
    if (physicsKey) link(physicsKey, recommendationKey, 'constrains', 'bounded by physics', 'مقيّدة فيزيائيًا')
  }

  // ── Impact ────────────────────────────────────────────────────────────────
  if (input.impact && recommendationKey) {
    const key = add({
      key: 'impact',
      stage: 'impact',
      title: STAGE_TITLES.impact[0],
      titleAr: STAGE_TITLES.impact[1],
      detail: `${input.impact.customersProtected.toLocaleString('en')} customers and ${round(input.impact.unservedAvoidedMw, 1)} MW of unserved load avoided — projected`,
      detailAr: `تفادي انقطاع عن ${input.impact.customersProtected.toLocaleString('en')} مشترك و${round(input.impact.unservedAvoidedMw, 1)} م.و غير مخدومة — تقدير`,
      sourceType: 'estimated',
      sourceId: 'impact-engine',
      value: input.impact.customersProtected,
      unit: 'customers',
      confidence: round(input.impact.confidence, 1),
      freshnessMin: 0,
      modelVersion: '',
    })
    link(recommendationKey, key, 'derives', 'projected', 'مُسقط')
  }

  void previous

  // ── Summary ───────────────────────────────────────────────────────────────
  const present = new Set(nodes.map((n) => n.stage))
  const missingStages = EVIDENCE_STAGES.filter((s) => !present.has(s))

  const weakest = nodes
    .filter((n) => n.stage !== 'input')
    .sort((a, b) => a.confidence - b.confidence)[0]

  const measured = nodes.filter((n) => n.sourceType === 'live').length
  const measuredSharePct = nodes.length ? round((measured / nodes.length) * 100, 1) : 0

  return {
    code: input.code,
    assetCode: input.assetCode,
    nodes,
    edges,
    weakestLink: weakest
      ? {
          key: weakest.key,
          title: weakest.title,
          titleAr: weakest.titleAr,
          confidence: round(clamp(weakest.confidence, 0, 100), 1),
        }
      : null,
    missingStages,
    measuredSharePct,
  }
}
