/**
 * Cascade as a tree, and where to cut it (§43, §44, §45).
 *
 * The 3.0 cascade produced a timeline: a flat list of things that happen in order. That
 * is the right shape for watching an event, and the wrong shape for two questions an
 * operator actually asks — *which* branch is the dangerous one, and *where* can I stop
 * it for the least effort.
 *
 * So this module reshapes the same walk into a tree and then searches it. Nothing here
 * re-simulates: the probabilities, delays and loads all come from the cascade result, so
 * the tree and the timeline can never disagree.
 *
 * The break-point search is the interesting part. Cutting the chain at the origin is
 * always possible and usually expensive; cutting it three hops down is cheap and usually
 * pointless. The value of a cut is the impact it removes — customers, unserved load,
 * branches — divided by what it costs to make it, and the best cut is rarely at either
 * end.
 */

import type { CascadeResult, CascadeStep } from './cascade'
import { clamp, round } from './rng'

// ─────────────────────────────────────────────────────────────────────────────
// The tree
// ─────────────────────────────────────────────────────────────────────────────

export interface CascadeTreeNode {
  assetCode: string
  assetName: string
  assetNameAr: string
  assetTypeKey: string
  depth: number
  offsetMin: number
  /** Conditional probability of this hop given its parent occurred. */
  hopProbability: number
  /** Unconditional probability of reaching this node from the origin. */
  probability: number
  /** How much of the hop is understood rather than extrapolated, 0–100 (§44). */
  confidence: number
  delayMin: number
  mechanism: CascadeStep['mechanism']
  outcome: CascadeStep['outcome']
  loadPctAfter: number
  capacityMw: number
  unservedMw: number
  customers: number
  severity: CascadeStep['severity']
  children: CascadeTreeNode[]
  /** Expected customers over this node and everything below it, probability-weighted. */
  subtreeCustomers: number
  subtreeUnservedMw: number
  subtreeNodes: number
}

/**
 * Confidence in one hop.
 *
 * Falls with depth, because each order of effect compounds an assumption; falls when the
 * hop is marginal, because a unit landing at 100.4 % might equally well hold; and rises
 * when the transfer is large enough that the outcome is not in doubt.
 */
function hopConfidence(step: CascadeStep): number {
  const depthPenalty = clamp(step.depth * 9, 0, 40)
  const marginality = Math.abs(step.loadPctAfter - 100)
  const marginPenalty = clamp(18 - marginality * 2.2, 0, 18)
  const base = step.mechanism === 'origin_failure' ? 96 : 88
  return round(clamp(base - depthPenalty - marginPenalty, 12, 99), 1)
}

/**
 * Build the propagation tree from a cascade result.
 *
 * Steps whose parent is missing from the result — possible when the walk hit its step
 * budget mid-branch — are attached to the origin rather than dropped, so the tree always
 * accounts for every step the timeline shows.
 */
export function buildCascadeTree(cascade: CascadeResult): CascadeTreeNode | null {
  if (!cascade.steps.length) return null

  const nodes = new Map<string, CascadeTreeNode>()
  const ordered = [...cascade.steps].sort((a, b) => a.offsetMin - b.offsetMin || a.ordinal - b.ordinal)

  for (const step of ordered) {
    // A unit can appear twice — stressed, then failed. Keep the more severe reading; it
    // is the one that drives everything downstream.
    const existing = nodes.get(step.assetCode)
    if (existing && existing.probability >= step.probability) continue

    nodes.set(step.assetCode, {
      assetCode: step.assetCode,
      assetName: step.assetName,
      assetNameAr: step.assetNameAr,
      assetTypeKey: step.assetTypeKey,
      depth: step.depth,
      offsetMin: step.offsetMin,
      hopProbability: 1,
      probability: step.probability,
      confidence: hopConfidence(step),
      delayMin: 0,
      mechanism: step.mechanism,
      outcome: step.outcome,
      loadPctAfter: step.loadPctAfter,
      capacityMw: step.capacityMw,
      unservedMw: step.mechanism === 'unserved_load' ? step.transferredMw : 0,
      customers: step.customers,
      severity: step.severity,
      children: [],
      subtreeCustomers: 0,
      subtreeUnservedMw: 0,
      subtreeNodes: 0,
    })
  }

  const root = nodes.get(cascade.originCode)
  if (!root) return null

  for (const step of ordered) {
    if (step.assetCode === cascade.originCode) continue
    const node = nodes.get(step.assetCode)
    if (!node || node.offsetMin !== step.offsetMin) continue

    const parent = (step.parentCode && nodes.get(step.parentCode)) || root
    if (parent === node) continue
    if (parent.children.some((c) => c.assetCode === node.assetCode)) continue

    node.hopProbability = round(
      parent.probability > 0 ? clamp(node.probability / parent.probability, 0, 1) : node.probability,
      3,
    )
    node.delayMin = round(Math.max(0, node.offsetMin - parent.offsetMin), 1)
    parent.children.push(node)
  }

  rollUp(root)
  return root
}

/** Probability-weighted totals for a node and everything under it. */
function rollUp(node: CascadeTreeNode): void {
  let customers = node.customers * node.probability
  let unserved = node.unservedMw * node.probability
  let count = 1

  for (const child of node.children) {
    rollUp(child)
    customers += child.subtreeCustomers
    unserved += child.subtreeUnservedMw
    count += child.subtreeNodes
  }

  node.subtreeCustomers = Math.round(customers)
  node.subtreeUnservedMw = round(unserved, 1)
  node.subtreeNodes = count
}

/** Depth-first list, useful where a tree cannot be rendered. */
export function flattenTree(root: CascadeTreeNode | null): CascadeTreeNode[] {
  if (!root) return []
  const out: CascadeTreeNode[] = []
  const walk = (node: CascadeTreeNode) => {
    out.push(node)
    for (const child of [...node.children].sort((a, b) => b.probability - a.probability)) walk(child)
  }
  walk(root)
  return out
}

// ─────────────────────────────────────────────────────────────────────────────
// Where to cut (§45)
// ─────────────────────────────────────────────────────────────────────────────

export interface CascadeBreakpoint {
  assetCode: string
  assetName: string
  assetNameAr: string
  depth: number
  offsetMin: number
  /** Share of the whole cascade's expected impact removed by cutting here, 0–100. */
  riskReduction: number
  branchesStopped: number
  customersProtected: number
  /** MW that must be moved, shed or supplied to hold this node below its limit. */
  interventionMw: number
  /** 0–100, where higher is more expensive. Combines size, depth and asset kind. */
  costIndex: number
  /** False when the node is the origin's own failure, which cannot be "broken" here. */
  feasible: boolean
  isBest: boolean
  rationale: string
  rationaleAr: string
}

/** Relative difficulty of intervening at each kind of asset. */
const ASSET_COST: Record<string, number> = {
  transformer: 1,
  transmission_line: 1.35,
  substation: 1.6,
  industrial_load: 0.7,
  ev_hub: 0.6,
  battery: 0.55,
}

/**
 * Rank the places the chain can be cut.
 *
 * The origin is included but marked infeasible: by the time the cascade is being walked
 * the origin has already failed, and "prevent the first failure" is the counterfactual
 * search's job, not the breaker's. Listing it keeps the comparison honest rather than
 * pretending the option does not exist.
 */
export function findCascadeBreakpoints(
  root: CascadeTreeNode | null,
  options: { maxResults?: number } = {},
): CascadeBreakpoint[] {
  if (!root) return []
  const maxResults = options.maxResults ?? 6

  const totalImpact = root.subtreeCustomers + root.subtreeUnservedMw * 120
  if (totalImpact <= 0) return []

  const candidates: CascadeBreakpoint[] = []

  for (const node of flattenTree(root)) {
    const isOrigin = node.assetCode === root.assetCode
    const impact = node.subtreeCustomers + node.subtreeUnservedMw * 120

    // Cutting at a node removes its own consequences and everything below it, but only
    // in proportion to how likely the chain was to reach it in the first place. A cut
    // on a branch with a 12 % chance of occurring is worth 12 % of that branch.
    const reduction = clamp((impact / totalImpact) * 100, 0, 100)

    // What it takes to hold this unit: the load above its limit, at minimum.
    const overloadPct = Math.max(0, node.loadPctAfter - 100)
    const interventionMw = round(
      isOrigin
        ? 0
        : Math.max(0.4, (overloadPct / 100) * node.capacityMw + node.unservedMw * 0.5),
      2,
    )

    const assetFactor = ASSET_COST[node.assetTypeKey] ?? 1
    // Deeper cuts are cheaper to make (less load has accumulated) but buy less time.
    const sizeCost = clamp(interventionMw / 25, 0, 1) * 55
    const urgencyCost = clamp((30 - node.offsetMin) / 30, 0, 1) * 25
    const costIndex = round(clamp((sizeCost + urgencyCost + 12) * assetFactor, 0, 100), 1)

    candidates.push({
      assetCode: node.assetCode,
      assetName: node.assetName,
      assetNameAr: node.assetNameAr,
      depth: node.depth,
      offsetMin: node.offsetMin,
      riskReduction: round(reduction, 1),
      branchesStopped: node.subtreeNodes - 1,
      customersProtected: node.subtreeCustomers,
      interventionMw,
      costIndex,
      feasible: !isOrigin && node.outcome !== 'unserved',
      isBest: false,
      rationale: '',
      rationaleAr: '',
    })
  }

  const feasible = candidates.filter((c) => c.feasible)
  // Value per unit of cost. Adding one to the denominator keeps a free-looking cut from
  // dominating on arithmetic alone.
  const score = (c: CascadeBreakpoint) => c.riskReduction / (c.costIndex + 1)
  feasible.sort((a, b) => score(b) - score(a))

  const best = feasible[0]
  if (best) best.isBest = true

  for (const c of candidates) {
    if (!c.feasible) {
      c.rationale = 'Origin of the chain — prevented by the intervention search, not by a break point'
      c.rationaleAr = 'أصل السلسلة — يُمنع عبر بحث التدخل لا عبر نقطة قطع'
      continue
    }
    const stopped = c.branchesStopped
    c.rationale = c.isBest
      ? `Best value: ${c.riskReduction} % of the expected impact removed for ${c.interventionMw} MW at ${c.assetCode}, stopping ${stopped} downstream step${stopped === 1 ? '' : 's'}`
      : `Removes ${c.riskReduction} % of the expected impact for ${c.interventionMw} MW`
    c.rationaleAr = c.isBest
      ? `أفضل قيمة: إزالة ${c.riskReduction}٪ من الأثر المتوقع مقابل ${c.interventionMw} م.و عند ${c.assetCode}، وإيقاف ${stopped} خطوة لاحقة`
      : `يزيل ${c.riskReduction}٪ من الأثر المتوقع مقابل ${c.interventionMw} م.و`
  }

  const ordered = [
    ...feasible.slice(0, maxResults),
    ...candidates.filter((c) => !c.feasible).slice(0, 1),
  ]
  return ordered
}
