// 块展开:把 INSERT 换成世界坐标下的实际几何。
//
// 块是图纸的复用单位:BLOCKS 段里定义一次,ENTITIES 段里用 INSERT
// 带着位置 / 缩放 / 旋转引用多次。这一层把它们**摊平**——下游的
// 加工检查不该再关心「这条线在哪个块的哪一层引用里」。
//
// 两个关键决定:
//
// **先离散、再变换。** 非均匀缩放会把圆变成椭圆、圆弧变成椭圆弧,
// 在几何层变换形状要把每种曲线都推导一遍;在折线点列上变换就一行
// 矩阵乘法。精度由离散容差控制,和别的曲线一个口径。
//
// **变换用仿射矩阵复合。** 嵌套引用(块里再插块)的变换是一层层
// 乘出来的——「减基点 → 缩放 → 旋转 → 平移」单层好写,但旋转和
// 缩放不同级之间不可交换,复合必须老老实实按矩阵乘。
//
// 图层有个 DXF 的老规矩:**块内画在图层 0 上的实体继承引用的图层**。
// 加工检查判断「这条轮廓归哪个图层」靠的就是它——不实现这条,
// 块画的轮廓会全落回图层 0,按图层过滤就错了。

///|
/// 二维仿射变换:线性部分(2×2)+ 平移。
priv struct Affine {
  a : Double
  b : Double
  c : Double
  d : Double
  tx : Double
  ty : Double
}

///|
fn Affine::identity() -> Affine {
  { a: 1.0, b: 0.0, c: 0.0, d: 1.0, tx: 0.0, ty: 0.0, }
}

///|
fn Affine::apply(self : Affine, p : Vec2) -> Vec2 {
  Vec2::new(
    self.a * p.x + self.b * p.y + self.tx,
    self.c * p.x + self.d * p.y + self.ty,
  )
}

///|
/// `self ∘ inner`:先做 inner、再做 self(嵌套时父变换在外层)。
fn Affine::compose(self : Affine, inner : Affine) -> Affine {
  {
    a: self.a * inner.a + self.b * inner.c,
    b: self.a * inner.b + self.b * inner.d,
    c: self.c * inner.a + self.d * inner.c,
    d: self.c * inner.b + self.d * inner.d,
    tx: self.a * inner.tx + self.b * inner.ty + self.tx,
    ty: self.c * inner.tx + self.d * inner.ty + self.ty,
  }
}

///|
/// 一条 INSERT 的变换:块内坐标先减基点,再缩放(sx/sy),
/// 再旋转,最后平移到插入点。线性部分就是 R·S。
fn insert_transform(
  at : Vec3,
  base : Vec2,
  scale : Vec3,
  rotation : Double,
) -> Affine {
  let cs = @math.cos(rotation)
  let sn = @math.sin(rotation)
  let a = cs * scale.x
  let b = -sn * scale.y
  let c = sn * scale.x
  let d = cs * scale.y
  let p = at.xy()
  // 平移 = 插入点 − 线性部分·基点(基点映射到插入点)
  {
    a,
    b,
    c,
    d,
    tx: p.x - (a * base.x + b * base.y),
    ty: p.y - (c * base.x + d * base.y),
  }
}

///|
/// 展开之后的一条几何:世界坐标。
pub(all) struct Placed {
  /// 源实体——类型名、句柄、原记录都在里面。检查报告要引用它们
  ///(「图层『轮廓』句柄 2D 的圆半径是负数」),所以不能只留离散结果
  entity : Entity
  /// 生效图层——块内画在图层 0 上的实体继承引用的图层
  layer : String
  flat : Flat
}

///|
/// 展开结果。
pub(all) struct Expansion {
  placed : Array[Placed]
  /// 引用缺失的块、循环引用、超深度,都记在这
  warnings : Array[String]
}

///|
/// 展开过程共享的东西。
priv struct ExpandCtx {
  blocks : Array[Block]
  tol : Double
  warnings : Array[String]
  limits : ExpandLimits
  mut visits : Int
  mut points : Int
  mut stopped : Bool
}

///|
/// 图纸级展开预算。访问次数也计入空块和 INSERT,防止只有空引用的扇出。
pub(all) struct ExpandLimits {
  max_placed : Int
  max_points : Int
  max_visits : Int
}

///|
/// 图纸全部实体展开成世界坐标的几何。
///
/// INSERT 递归展开,块定义来自 BLOCKS 段;普通实体先按容差离散、
/// 再套上当前累积的变换。展开不了的东西(缺块、循环、太深)
/// **跳过并警告**,不猜位置。
pub fn Drawing::expand(self : Drawing, tol : Double) -> Expansion {
  self.expand_with_limits(tol, {
    max_placed: 10000,
    max_points: 200000,
    max_visits: 100000,
  })
}

///|
/// 达到全局预算即停止并报告不完整;不承诺恒定内存或实时性。
pub fn Drawing::expand_with_limits(
  self : Drawing,
  tol : Double,
  limits : ExpandLimits,
) -> Expansion {
  if !positive_tolerance(tol) {
    return {
      placed: [],
      warnings: ["离散容差必须是正的有限数字"],
    }
  }
  if limits.max_placed <= 0 || limits.max_points <= 0 || limits.max_visits <= 0 {
    return {
      placed: [],
      warnings: ["展开资源预算必须为正整数;检查不完整"],
    }
  }
  let es = self.entities()
  let blocks = self.blocks()
  let warnings = self.warnings.copy()
  warnings.append(es.warnings)
  warnings.append(blocks.warnings)
  let ctx : ExpandCtx = {
    blocks: blocks.blocks,
    tol,
    warnings,
    limits,
    visits: 0,
    points: 0,
    stopped: false,
  }
  let placed : Array[Placed] = []
  for e in es.entities {
    if ctx.stopped {
      break
    }
    expand_entity(e, ctx, Affine::identity(), "", [], placed)
  }
  { placed, warnings: ctx.warnings, }
}

///|
/// 展开一条实体。`xform` 是当前累积的变换(顶层是单位阵),
/// `inherited` 是外层 INSERT 的图层(顶层是空串 = 不继承),
/// `path` 是当前展开链(循环检测用)。
fn expand_entity(
  e : Entity,
  ctx : ExpandCtx,
  xform : Affine,
  inherited : String,
  path : Array[String],
  out : Array[Placed],
) -> Unit {
  if ctx.stopped {
    return
  }
  if ctx.visits >= ctx.limits.max_visits {
    ctx.stopped = true
    ctx.warnings.push(
      "展开达到全局资源上限(实体访问次数);结果不完整",
    )
    return
  }
  ctx.visits += 1
  match e.geom {
    Insert(at~, block~, scale~, rotation~) => {
      // INSERT disappears during expansion, so its unsupported coordinate
      // system must be diagnosed here, before only child records remain.
      let record = e.common.record
      if record.get_double(210, 0.0) != 0.0 ||
        record.get_double(220, 0.0) != 0.0 ||
        record.get_double(230, 1.0) != 1.0 {
        ctx.warnings.push(
          "INSERT(句柄 \{e.common.handle},图层 \{e.layer()},块 \{block})带非默认挤出方向;未转换 OCS,已跳过该引用及其子实体",
        )
        return
      }
      if e.common.record.get_double(70, 1.0) != 1.0 ||
        e.common.record.get_double(71, 1.0) != 1.0 {
        ctx.warnings.push(
          "INSERT 阵列暂只展开首个实例,其他行列未检查:\{block}",
        )
      }
      let mut found : Block? = None
      for b in ctx.blocks {
        if b.name == block {
          found = Some(b)
        }
      }
      match found {
        None =>
          ctx.warnings.push(
            "引用了不存在的块「\{block}」,已跳过",
          )
        Some(b) => {
          let mut cyclic = false
          for n in path {
            if n == block {
              cyclic = true
            }
          }
          if cyclic {
            ctx.warnings.push(
              "块「\{block}」在展开链上重复出现(循环引用),已截断",
            )
          } else if path.length() >= 8 {
            ctx.warnings.push("块嵌套超过 8 层(\{block}),已截断")
          } else {
            // 引用的生效图层:自己不是 0 层就用自己,是 0 层就继承外层
            let insert_layer = if e.layer() == "" || e.layer() == "0" {
              inherited
            } else {
              e.layer()
            }
            let inner_xform = xform.compose(
              insert_transform(at, b.base.xy(), scale, rotation),
            )
            let new_path = path.copy()
            new_path.push(block)
            for inner in b.entities {
              if ctx.stopped {
                break
              }
              expand_entity(
                inner, ctx, inner_xform, insert_layer, new_path, out,
              )
            }
          }
        }
      }
    }
    _ => {
      if out.length() >= ctx.limits.max_placed {
        ctx.stopped = true
        ctx.warnings.push(
          "展开达到全局资源上限(输出实体数);结果不完整",
        )
        return
      }
      // 图层继承只发生在块里:顶层的 0 层实体就是 0 层
      let eff = if inherited != "" && (e.layer() == "" || e.layer() == "0") {
        inherited
      } else {
        e.layer()
      }
      let stretch = xform.stretch_bound()
      if !finite_number(stretch) ||
        !finite_number(xform.tx) ||
        !finite_number(xform.ty) {
        ctx.warnings.push(
          "实体 \{e.kind()} 的变换超出有限坐标范围,已跳过",
        )
        return
      }
      let local_tol = if stretch > 0.0 { ctx.tol / stretch } else { ctx.tol }
      let mut flat = match e.flatten_checked(local_tol) {
        Ok(flat) => transform_flat(flat, xform)
        Err(msg) => {
          ctx.warnings.push(
            "实体 \{e.kind()}(句柄 \{e.common.handle})未能离散:\{msg}",
          )
          Empty
        }
      }
      if !flat_is_finite(flat) {
        ctx.warnings.push(
          "实体 \{e.kind()}(句柄 \{e.common.handle})变换后坐标溢出,已跳过几何",
        )
        flat = Empty
      }
      let count = match flat {
        Outline(points~, ..) => points.length()
        Dot(..) | Label(..) => 1
        Empty => 0
      }
      if count > ctx.limits.max_points - ctx.points {
        ctx.stopped = true
        ctx.warnings.push(
          "展开达到全局资源上限(离散点总数);结果不完整",
        )
        return
      }
      ctx.points += count
      out.push({ entity: e, layer: eff, flat, })
    }
  }
}

///|
/// sqrt(||A||1 * ||A||∞) 是最大拉伸的上界,先归一化避免中间平方溢出。
fn Affine::stretch_bound(self : Affine) -> Double {
  let scale = self.a.abs().max(self.b.abs()).max(self.c.abs()).max(self.d.abs())
  if scale == 0.0 || !finite_number(scale) {
    return scale
  }
  let a = (self.a / scale).abs()
  let b = (self.b / scale).abs()
  let c = (self.c / scale).abs()
  let d = (self.d / scale).abs()
  scale * ((a + c).max(b + d) * (a + b).max(c + d)).sqrt()
}

///|
/// 把离散好的折线套上变换。点列变换就一行矩阵乘法——
/// 非均匀缩放把圆变成椭圆也是自动的,不用管形状语义。
fn transform_flat(flat : Flat, xform : Affine) -> Flat {
  match flat {
    Outline(points~, closed~) => {
      let out : Array[Vec2] = []
      for p in points {
        out.push(xform.apply(p))
      }
      Outline(points=out, closed~)
    }
    Dot(at~) => Dot(at=xform.apply(at))
    // 文字:位置跟着变换走;字高按缩放的比例因子放大
    //(均匀缩放精确,非均匀取几何平均——预览够用)。
    // 旋转这版不带,斜着排的字画出来是平的。
    Label(at~, height~, value~) => {
      let scale = (xform.a * xform.d - xform.b * xform.c).abs().sqrt()
      Label(at=xform.apply(at), height=height * scale, value~)
    }
    Empty => Empty
  }
}