// Moonlight - Pure Calculation Functions
// Signal や DOM に依存しない純粋な計算関数

///|
/// 要素を描画順にソート(親要素を先、子要素を後に)
/// 子要素(Text等)が親要素(Line等)の上に描画されるようにする
pub fn sort_elements_for_render(elements : Array[Element]) -> Array[Element] {
  // 親要素(parent_id が None)を先に、子要素を後に
  let parents : Array[Element] = []
  let children : Array[Element] = []
  for el in elements {
    if el.parent_id is None {
      parents.push(el)
    } else {
      children.push(el)
    }
  }
  // 親の後に子を追加(子は親の描画後に描画される = 上に表示)
  parents.append(children)
  parents
}

///|
/// 2D 座標点
pub(all) struct Point {
  x : Double
  y : Double
} derive(Show, Eq)

///|
/// アンカー位置(図形上の接続点)
pub(all) enum Anchor {
  Center
  Top
  Bottom
  Left
  Right
  TopLeft
  TopRight
  BottomLeft
  BottomRight
  LineStart // 線の始点
  LineEnd // 線の終点
} derive(Show, Eq)

///|
/// 接続情報(線の端点が他の要素に接続している場合)
pub(all) struct Connection {
  element_id : String // 接続先の要素 ID
  anchor : Anchor // 接続点の位置
} derive(Show, Eq)

///|
/// 線の接続情報
pub(all) struct LineConnections {
  start : Connection? // 始点の接続(None = 自由端点)
  end : Connection? // 終点の接続(None = 自由端点)
} derive(Show, Eq)

///|
/// デフォルトの接続情報(接続なし)
pub fn LineConnections::none() -> LineConnections {
  { start: None, end: None }
}

///|
/// 矢印の種類
pub(all) enum ArrowType {
  NoArrow // 矢印なし
  Arrow // 標準の矢印
} derive(Show, Eq)

///|
/// テーマモード
pub(all) enum ThemeMode {
  Light
  Dark
} derive(Show, Eq)

///|
/// テーマ設定
pub(all) struct Theme {
  background : String // 背景色
  stroke : String // デフォルト線色(実際の値)
  fill : String // デフォルト塗りつぶし色(実際の値)
  text : String // テキスト色(実際の値)
  // UI用
  ui_bg : String // UIパネル背景
  ui_bg_hover : String // UIホバー背景
  ui_bg_muted : String // UI薄い背景(アコーディオンヘッダーなど)
  ui_border : String // UIボーダー
  ui_text : String // UIテキスト
  ui_text_muted : String // UI補助テキスト
  ui_accent : String // UIアクセント
} derive(Show, Eq)

///|
/// CSS変数名
pub let css_var_stroke : String = "var(--ml-stroke)"

///|
pub let css_var_fill : String = "var(--ml-fill)"

///|
pub let css_var_text : String = "var(--ml-text)"

///|
/// ライトテーマのデフォルト
pub fn Theme::light() -> Theme {
  {
    background: "#ffffff",
    stroke: "#000000",
    fill: "transparent",
    text: "#000000",
    ui_bg: "rgba(255,255,255,0.98)",
    ui_bg_hover: "#f3f4f6",
    ui_bg_muted: "#f9fafb",
    ui_border: "#d1d5db",
    ui_text: "#1f2937",
    ui_text_muted: "#6b7280",
    ui_accent: "#2563eb",
  }
}

///|
/// ダークテーマのデフォルト
pub fn Theme::dark() -> Theme {
  {
    background: "#1a1a1a",
    stroke: "#ffffff",
    fill: "transparent",
    text: "#ffffff",
    ui_bg: "rgba(31,31,31,0.98)",
    ui_bg_hover: "#374151",
    ui_bg_muted: "#27272a",
    ui_border: "#4b5563",
    ui_text: "#f3f4f6",
    ui_text_muted: "#9ca3af",
    ui_accent: "#60a5fa",
  }
}

///|
/// プリセットカラー
pub(all) struct PresetColor {
  name : String
  light : String // ライトモード用
  dark : String // ダークモード用
} derive(Show, Eq)

///|
/// プリセットカラー一覧
pub fn preset_colors() -> Array[PresetColor] {
  [
    { name: "Black", light: "#000000", dark: "#ffffff" },
    { name: "Gray", light: "#6b7280", dark: "#9ca3af" },
    { name: "Red", light: "#dc2626", dark: "#f87171" },
    { name: "Orange", light: "#ea580c", dark: "#fb923c" },
    { name: "Yellow", light: "#ca8a04", dark: "#facc15" },
    { name: "Green", light: "#16a34a", dark: "#4ade80" },
    { name: "Blue", light: "#2563eb", dark: "#60a5fa" },
    { name: "Purple", light: "#9333ea", dark: "#c084fc" },
  ]
}

///|
/// 共通スタイル属性
pub(all) struct Style {
  fill : String? // 塗りつぶし色 (e.g., "#ff0000", "none")
  stroke : String? // 線の色
  stroke_width : Double? // 線の太さ
  opacity : Double? // 不透明度 (0.0 - 1.0)
  stroke_dasharray : String? // 破線パターン (e.g., "5,5", "10,5,2,5")
  marker_start : ArrowType? // 始点の矢印
  marker_end : ArrowType? // 終点の矢印
  font_family : String? // フォント (e.g., "sans-serif", "serif", "monospace")
} derive(Show, Eq)

///|
/// デフォルトスタイル
pub fn Style::default() -> Style {
  {
    fill: None,
    stroke: Some("#000000"),
    stroke_width: Some(1.0),
    opacity: None,
    stroke_dasharray: None,
    marker_start: None,
    marker_end: None,
    font_family: None,
  }
}

///|
/// 図形の種類
pub(all) enum ShapeType {
  /// 矩形: width, height, rx (角丸X), ry (角丸Y)
  Rect(Double, Double, Double?, Double?)
  /// 円: 半径
  Circle(Double)
  /// 楕円: rx, ry
  Ellipse(Double, Double)
  /// 線分: 終点座標 (始点は Element.x, Element.y)
  Line(Double, Double)
  /// 折れ線
  Polyline(Array[Point])
  /// パス (SVG path d 属性, 始点X, 始点Y, 終点X, 終点Y)
  Path(String, Double, Double, Double, Double)
  /// テキスト: 内容, フォントサイズ
  Text(String, Double?)
} derive(Show, Eq)

///|
/// 図形が有効(生成可能)かどうかを判定
/// - Circle: 半径 > 0
/// - Rect: 幅 > 0 かつ 高さ > 0
/// - Ellipse: rx > 0 かつ ry > 0
/// - Line: 始点と終点が異なる(距離 > 0)
/// - Path: 常に無効(UIから直接生成不可)
/// - Polyline: ポイント数 >= 2
/// - Text: 常に有効
pub fn ShapeType::is_valid(self : ShapeType, x : Double, y : Double) -> Bool {
  match self {
    Circle(r) => r > 0.0
    Rect(w, h, _, _) => w > 0.0 && h > 0.0
    Ellipse(rx, ry) => rx > 0.0 && ry > 0.0
    Line(x2, y2) => {
      // 始点 (x, y) と終点 (x2, y2) が異なる
      let dx = x2 - x
      let dy = y2 - y
      dx * dx + dy * dy > 0.0
    }
    Path(_, _, _, _, _) => false // Path は UI から直接生成不可
    Polyline(points) => points.length() >= 2
    Text(_, _) => true
  }
}

///|
/// シェープごとの設定(UI表示、編集可能なスタイル、アンカー、接続)
pub(all) struct ShapeConfig {
  /// Stroke 色を表示・編集可能か
  has_stroke : Bool
  /// Fill 色を表示・編集可能か
  has_fill : Bool
  /// Stroke 幅を表示・編集可能か
  has_stroke_width : Bool
  /// フォント選択を表示するか(Text用)
  has_font : Bool
  /// 破線パターンを表示するか(Line, Path用)
  has_dasharray : Bool
  /// 矢印を表示するか(Line, Path用)
  has_arrows : Bool
  /// 他の要素への接続をサポートするか(Line, Path用)
  can_connect : Bool
  /// 接続ターゲットになれるか(Line, Path 以外)
  is_connection_target : Bool
  /// 利用可能なアンカータイプ
  anchor_types : Array[Anchor]
}

///|
/// シェープタイプから設定を取得
pub fn ShapeType::config(self : ShapeType) -> ShapeConfig {
  match self {
    Rect(_, _, _, _) =>
      {
        has_stroke: true,
        has_fill: true,
        has_stroke_width: true,
        has_font: false,
        has_dasharray: false,
        has_arrows: false,
        can_connect: false,
        is_connection_target: true,
        anchor_types: [
          Center,
          Top,
          Bottom,
          Left,
          Right,
          TopLeft,
          TopRight,
          BottomLeft,
          BottomRight,
        ],
      }
    Circle(_) | Ellipse(_, _) =>
      {
        has_stroke: true,
        has_fill: true,
        has_stroke_width: true,
        has_font: false,
        has_dasharray: false,
        has_arrows: false,
        can_connect: false,
        is_connection_target: true,
        anchor_types: [Center, Top, Bottom, Left, Right],
      }
    Line(_, _) =>
      {
        has_stroke: true,
        has_fill: false, // Line は Fill 不要
        has_stroke_width: true,
        has_font: false,
        has_dasharray: true,
        has_arrows: true,
        can_connect: true,
        is_connection_target: false, // Line は接続ターゲットにならない
        anchor_types: [LineStart, LineEnd],
      }
    Polyline(_) =>
      {
        has_stroke: true,
        has_fill: false,
        has_stroke_width: true,
        has_font: false,
        has_dasharray: true,
        has_arrows: false,
        can_connect: false,
        is_connection_target: false,
        anchor_types: [LineStart, LineEnd],
      }
    Path(_, _, _, _, _) =>
      {
        has_stroke: true,
        has_fill: false, // Path は Fill 不要(フリーハンド線)
        has_stroke_width: true,
        has_font: false,
        has_dasharray: true,
        has_arrows: true,
        can_connect: true,
        is_connection_target: false, // Path は接続ターゲットにならない
        anchor_types: [LineStart, LineEnd, Center],
      }
    Text(_, _) =>
      {
        has_stroke: true, // Text の色(実質 fill だが stroke として扱う)
        has_fill: false, // Text は Fill 不要
        has_stroke_width: false,
        has_font: true,
        has_dasharray: false,
        has_arrows: false,
        can_connect: false,
        is_connection_target: true, // Text も接続ターゲットになれる
        anchor_types: [Center], // Text は中心のみ
      }
  }
}

///|
/// 図形要素
pub(all) struct Element {
  id : String // 一意な識別子
  x : Double // 基準 X 座標
  y : Double // 基準 Y 座標
  shape : ShapeType // 図形タイプ
  style : Style // スタイル属性
  transform : String? // SVG transform 属性
  parent_id : String? // 親要素の ID(グループ化用)
  connections : LineConnections? // 線の接続情報(Line のみ使用)
} derive(Show, Eq)

///|
/// 新しい要素を作成
pub fn Element::new(
  id : String,
  x : Double,
  y : Double,
  shape : ShapeType,
) -> Element {
  {
    id,
    x,
    y,
    shape,
    style: Style::default(),
    transform: None,
    parent_id: None,
    connections: None,
  }
}

///|
/// 親要素を設定(空文字列の場合はNone)
pub fn Element::with_parent(self : Element, parent_id : String) -> Element {
  if parent_id == "" {
    self
  } else {
    { ..self, parent_id: Some(parent_id) }
  }
}

///|
/// スタイルを設定した要素を作成
pub fn Element::with_style(self : Element, style : Style) -> Element {
  { ..self, style, }
}

///|
/// 要素が有効(追加可能)かどうかを判定
pub fn Element::is_valid(self : Element) -> Bool {
  self.shape.is_valid(self.x, self.y)
}

///|
/// テキスト要素の塗りつぶし色を計算
/// Excalidraw 仕様: 親要素がある場合、親の stroke 色をテキストの fill として使用
/// parent_stroke: 親要素のストローク色
/// 戻り値: テキストに適用する fill 色
pub fn get_text_fill_color(
  text_style : Style,
  parent_stroke : String?,
) -> String {
  match parent_stroke {
    Some(stroke) => stroke
    None => {
      // "none" だとテキストが不可視になるためCSS変数にフォールバック
      let fill = text_style.fill.unwrap_or(css_var_text)
      if fill == "none" || fill == "" {
        css_var_text
      } else {
        fill
      }
    }
  }
}

///|
/// 要素配列から指定IDの親要素のストローク色を取得
pub fn get_parent_stroke(
  elements : Array[Element],
  parent_id : String?,
) -> String? {
  match parent_id {
    None => None
    Some(pid) => {
      for el in elements {
        if el.id == pid {
          return el.style.stroke
        }
      }
      None
    }
  }
}

///|
/// 要素のバウンディングボックスを計算
pub fn Element::bounding_box(self : Element) -> BoundingBox {
  match self.shape {
    Rect(w, h, _, _) => { x: self.x, y: self.y, width: w, height: h }
    Circle(r) =>
      { x: self.x - r, y: self.y - r, width: r * 2.0, height: r * 2.0 }
    Ellipse(rx, ry) =>
      { x: self.x - rx, y: self.y - ry, width: rx * 2.0, height: ry * 2.0 }
    Line(x2, y2) => {
      let min_x = if self.x < x2 { self.x } else { x2 }
      let min_y = if self.y < y2 { self.y } else { y2 }
      let max_x = if self.x > x2 { self.x } else { x2 }
      let max_y = if self.y > y2 { self.y } else { y2 }
      { x: min_x, y: min_y, width: max_x - min_x, height: max_y - min_y }
    }
    Polyline(points) =>
      if points.length() == 0 {
        { x: self.x, y: self.y, width: 0.0, height: 0.0 }
      } else {
        let mut min_x = self.x
        let mut min_y = self.y
        let mut max_x = self.x
        let mut max_y = self.y
        for p in points {
          if p.x < min_x {
            min_x = p.x
          }
          if p.y < min_y {
            min_y = p.y
          }
          if p.x > max_x {
            max_x = p.x
          }
          if p.y > max_y {
            max_y = p.y
          }
        }
        { x: min_x, y: min_y, width: max_x - min_x, height: max_y - min_y }
      }
    Path(_, start_x, start_y, end_x, end_y) => {
      // Path のバウンディングボックス
      // el.x, el.y は始点の実際の位置(移動後の位置)
      // transform = (el.x - start_x, el.y - start_y) で描画される
      // 実際の始点: el.x, el.y
      // 実際の終点: el.x + (end_x - start_x), el.y + (end_y - start_y)
      let actual_end_x = self.x + (end_x - start_x)
      let actual_end_y = self.y + (end_y - start_y)
      let min_x = if self.x < actual_end_x { self.x } else { actual_end_x }
      let max_x = if self.x > actual_end_x { self.x } else { actual_end_x }
      let min_y = if self.y < actual_end_y { self.y } else { actual_end_y }
      let max_y = if self.y > actual_end_y { self.y } else { actual_end_y }
      // 最小サイズを確保(点や短い線でも選択可能に)
      let width = if max_x - min_x < 10.0 { 10.0 } else { max_x - min_x }
      let height = if max_y - min_y < 10.0 { 10.0 } else { max_y - min_y }
      { x: min_x, y: min_y, width, height }
    }
    Text(content, font_size) => {
      let size = font_size.unwrap_or(16.0)
      // テキストのバウンディングボックスは概算(中央基準)
      // text-anchor="middle", dominant-baseline="middle" に対応
      // 平均文字幅係数: 0.55 (半角文字の平均的な幅)
      let char_count = content.iter().count().to_double()
      let estimated_width = char_count * size * 0.55
      let min_width = size * 2.0 // 最小幅はフォントサイズの2倍
      let width = if estimated_width < min_width {
        min_width
      } else {
        estimated_width
      }
      let height = size * 1.2 // 行の高さ
      { x: self.x - width / 2.0, y: self.y - height / 2.0, width, height }
    }
  }
}

///|
/// ポイントが要素上にあるか判定
pub fn Element::hit_test(self : Element, p : Point) -> Bool {
  self.bounding_box().contains(p)
}

///|
/// 指定アンカーの座標を取得
pub fn Element::get_anchor_point(self : Element, anchor : Anchor) -> Point {
  // Line/Path の場合は始点・終点を直接返す
  match (self.shape, anchor) {
    (Line(_, _), LineStart) => { x: self.x, y: self.y }
    (Line(x2, y2), LineEnd) => { x: x2, y: y2 }
    // Path: el.x, el.y が現在の始点位置(ドラッグ後)
    // 終点は相対位置で計算
    (Path(_, _, _, _, _), LineStart) => { x: self.x, y: self.y }
    (Path(_, start_x, start_y, end_x, end_y), LineEnd) =>
      { x: self.x + (end_x - start_x), y: self.y + (end_y - start_y) }
    _ => {
      let bbox = self.bounding_box()
      let cx = bbox.x + bbox.width / 2.0
      let cy = bbox.y + bbox.height / 2.0
      match anchor {
        Center => { x: cx, y: cy }
        Top => { x: cx, y: bbox.y }
        Bottom => { x: cx, y: bbox.y + bbox.height }
        Left => { x: bbox.x, y: cy }
        Right => { x: bbox.x + bbox.width, y: cy }
        TopLeft => { x: bbox.x, y: bbox.y }
        TopRight => { x: bbox.x + bbox.width, y: bbox.y }
        BottomLeft => { x: bbox.x, y: bbox.y + bbox.height }
        BottomRight => { x: bbox.x + bbox.width, y: bbox.y + bbox.height }
        // Line 以外の要素に LineStart/LineEnd が指定された場合は中心を返す
        LineStart | LineEnd => { x: cx, y: cy }
      }
    }
  }
}

///|
/// 全てのアンカー位置を取得(ShapeConfig.anchor_types を使用)
pub fn Element::get_all_anchors(self : Element) -> Array[(Anchor, Point)] {
  let anchors = self.shape.config().anchor_types
  anchors.map(fn(a) { (a, self.get_anchor_point(a)) })
}

///|
/// 点から最も近いアンカーを探す
pub fn Element::find_nearest_anchor(
  self : Element,
  point : Point,
  threshold : Double,
) -> (Anchor, Point, Double)? {
  let mut nearest : (Anchor, Point, Double)? = None
  for anchor_data in self.get_all_anchors() {
    let (anchor, anchor_point) = anchor_data
    let dx = point.x - anchor_point.x
    let dy = point.y - anchor_point.y
    let dist = (dx * dx + dy * dy).sqrt()
    if dist <= threshold {
      match nearest {
        None => nearest = Some((anchor, anchor_point, dist))
        Some((_, _, prev_dist)) =>
          if dist < prev_dist {
            nearest = Some((anchor, anchor_point, dist))
          }
      }
    }
  }
  nearest
}

///|
/// バウンディングボックス
pub(all) struct BoundingBox {
  x : Double
  y : Double
  width : Double
  height : Double
} derive(Show, Eq)

///|
/// ポイントがバウンディングボックス内にあるか判定
pub fn BoundingBox::contains(self : BoundingBox, p : Point) -> Bool {
  p.x >= self.x &&
  p.x <= self.x + self.width &&
  p.y >= self.y &&
  p.y <= self.y + self.height
}

///|
/// リサイズハンドルの位置
pub(all) enum HandlePosition {
  NW // 左上
  NE // 右上
  SW // 左下
  SE // 右下
  LineStart // 線の始点
  LineEnd // 線の終点
} derive(Show, Eq)

///|
/// 矩形のリサイズ計算結果
pub(all) struct RectResizeResult {
  x : Double
  y : Double
  width : Double
  height : Double
} derive(Show, Eq)

///|
/// 矩形のリサイズを計算(純粋関数)
pub fn calc_rect_resize(
  start_x : Double,
  start_y : Double,
  start_w : Double,
  start_h : Double,
  dx : Double,
  dy : Double,
  handle : HandlePosition,
  min_size : Double,
) -> RectResizeResult {
  let (new_x, new_y, new_w, new_h) = match handle {
    NW => (start_x + dx, start_y + dy, start_w - dx, start_h - dy)
    NE => (start_x, start_y + dy, start_w + dx, start_h - dy)
    SW => (start_x + dx, start_y, start_w - dx, start_h + dy)
    SE | LineStart | LineEnd => (start_x, start_y, start_w + dx, start_h + dy)
  }
  // 最小サイズを保証
  let final_w = if new_w < min_size { min_size } else { new_w }
  let final_h = if new_h < min_size { min_size } else { new_h }
  let final_x = if new_w < min_size {
    match handle {
      NW | SW => start_x + start_w - min_size
      _ => new_x
    }
  } else {
    new_x
  }
  let final_y = if new_h < min_size {
    match handle {
      NW | NE => start_y + start_h - min_size
      _ => new_y
    }
  } else {
    new_y
  }
  { x: final_x, y: final_y, width: final_w, height: final_h }
}

///|
/// 円のリサイズを計算(純粋関数)
pub fn calc_circle_resize(
  start_r : Double,
  dx : Double,
  dy : Double,
  handle : HandlePosition,
  min_r : Double,
) -> Double {
  // 円の場合は対角距離から半径を計算
  let dist = match handle {
    SE | LineStart | LineEnd => (dx + dy) / 2.0
    NW => -(dx + dy) / 2.0
    NE => (dx - dy) / 2.0
    SW => (-dx + dy) / 2.0
  }
  let new_r = start_r + dist
  if new_r < min_r {
    min_r
  } else {
    new_r
  }
}

///|
/// 楕円リサイズ結果
pub(all) struct EllipseResizeResult {
  cx : Double
  cy : Double
  rx : Double
  ry : Double
} derive(Show, Eq)

///|
/// 楕円のリサイズを計算(純粋関数)
pub fn calc_ellipse_resize(
  cx : Double,
  cy : Double,
  start_rx : Double,
  start_ry : Double,
  dx : Double,
  dy : Double,
  handle : HandlePosition,
  min_r : Double,
) -> EllipseResizeResult {
  // 楕円のバウンディングボックスの4隅
  let x1 = cx - start_rx
  let y1 = cy - start_ry
  let x2 = cx + start_rx
  let y2 = cy + start_ry
  // ハンドルに応じて新しい座標を計算
  let (new_x1, new_y1, new_x2, new_y2) = match handle {
    SE | LineStart | LineEnd => (x1, y1, x2 + dx, y2 + dy)
    NW => (x1 + dx, y1 + dy, x2, y2)
    NE => (x1, y1 + dy, x2 + dx, y2)
    SW => (x1 + dx, y1, x2, y2 + dy)
  }
  // 最小サイズを保証
  let new_rx = (new_x2 - new_x1).abs() / 2.0
  let new_ry = (new_y2 - new_y1).abs() / 2.0
  let final_rx = if new_rx < min_r { min_r } else { new_rx }
  let final_ry = if new_ry < min_r { min_r } else { new_ry }
  // 中心を再計算
  let final_cx = (new_x1 + new_x2) / 2.0
  let final_cy = (new_y1 + new_y2) / 2.0
  { cx: final_cx, cy: final_cy, rx: final_rx, ry: final_ry }
}

///|
/// Line リサイズ結果
pub(all) struct LineResizeResult {
  x1 : Double
  y1 : Double
  x2 : Double
  y2 : Double
} derive(Show, Eq)

///|
/// Line のリサイズを計算(純粋関数)
pub fn calc_line_resize(
  start_x : Double,
  start_y : Double,
  start_x2 : Double,
  start_y2 : Double,
  dx : Double,
  dy : Double,
  handle : HandlePosition,
) -> LineResizeResult {
  match handle {
    LineStart =>
      { x1: start_x + dx, y1: start_y + dy, x2: start_x2, y2: start_y2 }
    LineEnd =>
      { x1: start_x, y1: start_y, x2: start_x2 + dx, y2: start_y2 + dy }
    _ => { x1: start_x, y1: start_y, x2: start_x2 + dx, y2: start_y2 + dy }
  }
}

///|
/// Text リサイズ結果
pub(all) struct TextResizeResult {
  font_size : Double
} derive(Show, Eq)

///|
/// Text のリサイズを計算(純粋関数)
/// フォントサイズを変更
pub fn calc_text_resize(
  start_font_size : Double,
  dx : Double,
  dy : Double,
  handle : HandlePosition,
  min_size : Double,
) -> TextResizeResult {
  // 斜め方向の変化量から新しいフォントサイズを計算
  let change = match handle {
    SE | LineEnd => (dx + dy) / 4.0
    NW => -(dx + dy) / 4.0
    NE => (dx - dy) / 4.0
    SW => (-dx + dy) / 4.0
    LineStart => (dx + dy) / 4.0
  }
  let new_size = start_font_size + change
  { font_size: if new_size < min_size { min_size } else { new_size } }
}

///|
/// 配列内の要素を新しいインデックスに移動
pub fn[T] reorder_array(
  arr : Array[T],
  from_idx : Int,
  to_idx : Int,
) -> Array[T] {
  guard from_idx >= 0 && from_idx < arr.length() else { return arr }
  guard to_idx >= 0 && to_idx < arr.length() else { return arr }
  guard from_idx != to_idx else { return arr }
  let result : Array[T] = []
  let element = arr[from_idx]
  for i, el in arr {
    if i == from_idx {
      continue
    }
    if i == to_idx {
      if from_idx < to_idx {
        result.push(el)
        result.push(element)
      } else {
        result.push(element)
        result.push(el)
      }
    } else {
      result.push(el)
    }
  }
  result
}

///|
/// 配列の並べ替え計算(bring_forward相当)
pub fn calc_bring_forward(current_idx : Int, length : Int) -> Int? {
  guard current_idx >= 0 && current_idx < length else { return None }
  let new_idx = current_idx + 1
  guard new_idx < length else { return None }
  Some(new_idx)
}

///|
/// 配列の並べ替え計算(send_backward相当)
pub fn calc_send_backward(current_idx : Int, length : Int) -> Int? {
  guard current_idx >= 0 && current_idx < length else { return None }
  guard current_idx > 0 else { return None }
  Some(current_idx - 1)
}

///|
/// 配列の並べ替え計算(bring_to_front相当)
pub fn calc_bring_to_front(current_idx : Int, length : Int) -> Int? {
  guard current_idx >= 0 && current_idx < length else { return None }
  let new_idx = length - 1
  guard current_idx != new_idx else { return None }
  Some(new_idx)
}

///|
/// 配列の並べ替え計算(send_to_back相当)
pub fn calc_send_to_back(current_idx : Int, length : Int) -> Int? {
  guard current_idx >= 0 && current_idx < length else { return None }
  guard current_idx != 0 else { return None }
  Some(0)
}

///|
/// Viewport (表示領域) 座標変換
pub(all) struct Viewport {
  scroll_x : Double
  scroll_y : Double
  zoom : Double // 1.0 = 100%
} derive(Show, Eq)

///|
/// デフォルトビューポート
pub fn Viewport::default() -> Viewport {
  { scroll_x: 0.0, scroll_y: 0.0, zoom: 1.0 }
}

///|
/// 画面座標からシーン座標への変換
pub fn Viewport::screen_to_scene(
  self : Viewport,
  screen_x : Double,
  screen_y : Double,
) -> Point {
  {
    x: screen_x / self.zoom + self.scroll_x,
    y: screen_y / self.zoom + self.scroll_y,
  }
}

///|
/// シーン座標から画面座標への変換
pub fn Viewport::scene_to_screen(
  self : Viewport,
  scene_x : Double,
  scene_y : Double,
) -> Point {
  {
    x: (scene_x - self.scroll_x) * self.zoom,
    y: (scene_y - self.scroll_y) * self.zoom,
  }
}

// ============================================================
// UI 状態(純粋なデータ構造)
// ============================================================

///|
/// ドラッグ状態
pub(all) struct DragState {
  element_id : String // ドラッグ中の要素 ID
  start_x : Double // ドラッグ開始時の要素 X
  start_y : Double // ドラッグ開始時の要素 Y
  offset_x : Double // クリック位置と要素位置の差分 X
  offset_y : Double // クリック位置と要素位置の差分 Y
  start_shape : ShapeType? // Line の場合、開始時の形状を保存
  start_connections : LineConnections? // Line の場合、開始時の接続情報を保存
  connected_start_positions : Array[(String, Double, Double)] // 接続要素の開始位置
} derive(Show, Eq)

///|
/// コンテキストメニューの状態
pub(all) struct ContextMenu {
  x : Double // メニュー表示位置 X(スクリーン座標)
  y : Double // メニュー表示位置 Y(スクリーン座標)
  scene_x : Double // シーン座標 X(図形挿入用)
  scene_y : Double // シーン座標 Y(図形挿入用)
  target_id : String? // 右クリック対象の要素 ID(None = 空白部分)
} derive(Show, Eq)

///|
/// リサイズ状態
pub(all) struct ResizeState {
  element_id : String // リサイズ中の要素 ID
  handle : HandlePosition // ドラッグ中のハンドル
  start_x : Double // リサイズ開始時の要素 X
  start_y : Double // リサイズ開始時の要素 Y
  start_shape : ShapeType // リサイズ開始時の形状
  mouse_start_x : Double // マウス開始位置 X
  mouse_start_y : Double // マウス開始位置 Y
} derive(Show, Eq)

///|
/// 矩形選択状態
pub(all) struct BoxSelectState {
  start_x : Double // 選択開始 X(シーン座標)
  start_y : Double // 選択開始 Y(シーン座標)
  current_x : Double // 現在の X(シーン座標)
  current_y : Double // 現在の Y(シーン座標)
} derive(Show, Eq)

///|
/// 矩形選択のバウンディングボックスを取得
pub fn BoxSelectState::to_bbox(self : BoxSelectState) -> BoundingBox {
  let min_x = if self.start_x < self.current_x {
    self.start_x
  } else {
    self.current_x
  }
  let min_y = if self.start_y < self.current_y {
    self.start_y
  } else {
    self.current_y
  }
  let max_x = if self.start_x > self.current_x {
    self.start_x
  } else {
    self.current_x
  }
  let max_y = if self.start_y > self.current_y {
    self.start_y
  } else {
    self.current_y
  }
  { x: min_x, y: min_y, width: max_x - min_x, height: max_y - min_y }
}

///|
/// 複数要素のドラッグ状態
pub(all) struct MultiDragState {
  element_ids : Array[String] // ドラッグ中の要素 ID リスト
  start_positions : Array[(String, Double, Double)] // 各要素の開始位置 (id, x, y)
  line_endpoints : Array[(String, Double, Double)] // Line の終点初期値 (id, x2, y2)
  mouse_start_x : Double // マウス開始位置 X
  mouse_start_y : Double // マウス開始位置 Y
} derive(Show, Eq)

///|
/// テキスト編集状態
pub(all) struct TextEditState {
  parent_id : String // 親要素のID
  x : Double // 入力欄のX座標(シーン座標)
  y : Double // 入力欄のY座標(シーン座標)
  editing_id : String? // 編集中のテキスト要素ID(None = 新規作成)
  initial_text : String // 初期テキスト(編集時は既存の内容)
  font_size : Double? // フォントサイズ(None = デフォルト16px)
} derive(Show, Eq)

///|
/// ドラッグ位置を計算(純粋関数)
pub fn calc_drag_position(
  offset_x : Double,
  offset_y : Double,
  mouse_x : Double,
  mouse_y : Double,
) -> Point {
  { x: mouse_x - offset_x, y: mouse_y - offset_y }
}

///|
/// 要素配列から座標にヒットする要素を検索(上から順に、純粋関数)
pub fn find_element_at(elements : Array[Element], point : Point) -> Element? {
  // 後ろから探す(上にあるものが優先)
  for i = elements.length() - 1; i >= 0; i = i - 1 {
    if elements[i].hit_test(point) {
      return Some(elements[i])
    }
  }
  None
}

///|
/// 2つのバウンディングボックスが交差するか判定
pub fn BoundingBox::intersects(self : BoundingBox, other : BoundingBox) -> Bool {
  // 一方が他方の完全に外側にある場合は交差しない
  not(
    self.x + self.width < other.x ||
    other.x + other.width < self.x ||
    self.y + self.height < other.y ||
    other.y + other.height < self.y,
  )
}

///|
/// 矩形選択内の要素を検索(純粋関数)
pub fn find_elements_in_box(
  elements : Array[Element],
  box_bbox : BoundingBox,
) -> Array[Element] {
  let result : Array[Element] = []
  for el in elements {
    // 親要素のみを対象(子要素は除外)
    if el.parent_id is None {
      let el_bbox = el.bounding_box()
      if box_bbox.intersects(el_bbox) {
        result.push(el)
      }
    }
  }
  result
}

///|
/// 座標をグリッドにスナップ(純粋関数)
pub fn snap_to_grid(
  x : Double,
  y : Double,
  grid_size : Int,
  enabled : Bool,
) -> (Double, Double) {
  if not(enabled) {
    return (x, y)
  }
  let size = grid_size.to_double()
  let snapped_x = (x / size).round() * size
  let snapped_y = (y / size).round() * size
  (snapped_x, snapped_y)
}

///|
/// ShapeType の名前を小文字で取得
pub fn shape_name(shape : ShapeType) -> String {
  match shape {
    Rect(_, _, _, _) => "rect"
    Circle(_) => "circle"
    Ellipse(_, _) => "ellipse"
    Line(_, _) => "line"
    Polyline(_) => "polyline"
    Path(_, _, _, _, _) => "path"
    Text(_, _) => "text"
  }
}

///|
/// ShapeType を文字列にフォーマット
pub fn format_shape(shape : ShapeType) -> String {
  match shape {
    Rect(w, h, _, _) => "Rect(\{w.to_int()}×\{h.to_int()})"
    Circle(r) => "Circle(r=\{r.to_int()})"
    Ellipse(rx, ry) => "Ellipse(\{rx.to_int()}×\{ry.to_int()})"
    Line(x2, y2) => "Line(→\{x2.to_int()},\{y2.to_int()})"
    Polyline(points) => "Polyline(\{points.length()} pts)"
    Path(_, _, _, _, _) => "Path"
    Text(content, _) => "Text(\"\{content}\")"
  }
}

///|
/// ハンドル文字列を HandlePosition に変換
pub fn parse_handle_position(handle_str : String) -> HandlePosition {
  match handle_str {
    "nw" => NW
    "ne" => NE
    "sw" => SW
    "se" => SE
    "line-start" => LineStart
    "line-end" => LineEnd
    _ => SE // デフォルト
  }
}

///|
/// アンカー名文字列を Anchor に変換
pub fn parse_anchor(anchor_str : String) -> Anchor? {
  match anchor_str {
    "center" => Some(Center)
    "top" => Some(Top)
    "bottom" => Some(Bottom)
    "left" => Some(Left)
    "right" => Some(Right)
    "top-left" => Some(TopLeft)
    "top-right" => Some(TopRight)
    "bottom-left" => Some(BottomLeft)
    "bottom-right" => Some(BottomRight)
    "line-start" => Some(LineStart)
    "line-end" => Some(LineEnd)
    _ => None
  }
}

///|
/// Anchor を文字列に変換
pub fn anchor_to_string(anchor : Anchor) -> String {
  match anchor {
    Center => "center"
    Top => "top"
    Bottom => "bottom"
    Left => "left"
    Right => "right"
    TopLeft => "top-left"
    TopRight => "top-right"
    BottomLeft => "bottom-left"
    BottomRight => "bottom-right"
    LineStart => "line-start"
    LineEnd => "line-end"
  }
}

// ============================================================
// 接続ライン計算(純粋関数)
// ============================================================

///|
/// LineConnections から接続要素のIDリストを取得
pub fn LineConnections::get_connected_ids(
  self : LineConnections,
) -> Array[String] {
  let ids : Array[String] = []
  match self.start {
    Some(conn) =>
      if not(ids.contains(conn.element_id)) {
        ids.push(conn.element_id)
      }
    None => ()
  }
  match self.end {
    Some(conn) =>
      if not(ids.contains(conn.element_id)) {
        ids.push(conn.element_id)
      }
    None => ()
  }
  ids
}

///|
/// 接続ラインの更新結果
pub(all) struct ConnectedLineUpdate {
  line_id : String
  new_x : Double
  new_y : Double
  new_x2 : Double
  new_y2 : Double
} derive(Show, Eq)

///|
/// 要素移動後の接続ラインの新座標を計算(純粋関数)
/// target_id: 移動した要素のID
/// new_target_x, new_target_y: 移動後の座標
/// lines: 接続されている可能性があるLine要素のリスト
/// all_elements: 全要素(アンカー位置計算用)
pub fn calc_connected_line_updates(
  target_id : String,
  new_target_x : Double,
  new_target_y : Double,
  lines : Array[Element],
  all_elements : Array[Element],
) -> Array[ConnectedLineUpdate] {
  let updates : Array[ConnectedLineUpdate] = []
  // 移動後のターゲット要素を作成(アンカー計算用)
  let mut moved_target : Element? = None
  for el in all_elements {
    if el.id == target_id {
      moved_target = Some({ ..el, x: new_target_x, y: new_target_y })
      break
    }
  }
  // ターゲット要素が見つからない場合は空配列を返す
  guard moved_target is Some(target) else { return updates }
  for line in lines {
    // Line 要素のみ処理
    guard line.shape is Line(x2, y2) else { continue }
    guard line.connections is Some(conns) else { continue }
    let mut new_x = line.x
    let mut new_y = line.y
    let mut new_x2 = x2
    let mut new_y2 = y2
    let mut changed = false
    // 始点の接続を確認
    match conns.start {
      Some(conn) if conn.element_id == target_id => {
        let anchor_point = target.get_anchor_point(conn.anchor)
        new_x = anchor_point.x
        new_y = anchor_point.y
        changed = true
      }
      _ => ()
    }
    // 終点の接続を確認
    match conns.end {
      Some(conn) if conn.element_id == target_id => {
        let anchor_point = target.get_anchor_point(conn.anchor)
        new_x2 = anchor_point.x
        new_y2 = anchor_point.y
        changed = true
      }
      _ => ()
    }
    if changed {
      updates.push({ line_id: line.id, new_x, new_y, new_x2, new_y2 })
    }
  }
  updates
}

///|
/// 子要素の移動後座標を計算(純粋関数)
pub(all) struct ChildMoveResult {
  child_id : String
  new_x : Double
  new_y : Double
} derive(Show, Eq)

///|
/// 親要素移動時の子要素の新座標を計算
pub fn calc_child_positions(
  parent_id : String,
  dx : Double,
  dy : Double,
  elements : Array[Element],
) -> Array[ChildMoveResult] {
  let results : Array[ChildMoveResult] = []
  for el in elements {
    if el.parent_id == Some(parent_id) {
      results.push({ child_id: el.id, new_x: el.x + dx, new_y: el.y + dy })
    }
  }
  results
}

///|
/// 要素の移動結果を計算(純粋関数)
/// 入力の要素配列を変更せず、更新後の新しい配列を返す
pub fn apply_element_move(
  elements : Array[Element],
  target_id : String,
  new_x : Double,
  new_y : Double,
) -> Array[Element] {
  // 移動差分を計算
  let mut dx = 0.0
  let mut dy = 0.0
  for el in elements {
    if el.id == target_id {
      dx = new_x - el.x
      dy = new_y - el.y
      break
    }
  }
  // 子要素のIDを収集
  let child_ids : Array[String] = []
  for el in elements {
    if el.parent_id == Some(target_id) {
      child_ids.push(el.id)
    }
  }
  // Line要素を収集(接続ライン更新用)
  let lines : Array[Element] = []
  for el in elements {
    if el.shape is Line(_, _) && el.connections is Some(_) && el.id != target_id {
      lines.push(el)
    }
  }
  // 接続ラインの更新を計算
  let line_updates = calc_connected_line_updates(
    target_id, new_x, new_y, lines, elements,
  )
  // 要素配列を更新
  elements.map(fn(el) {
    // ターゲット要素を移動
    if el.id == target_id {
      return { ..el, x: new_x, y: new_y }
    }
    // 子要素を移動
    if child_ids.contains(el.id) {
      return { ..el, x: el.x + dx, y: el.y + dy }
    }
    // 接続ラインを更新
    for update in line_updates {
      if el.id == update.line_id {
        return {
          ..el,
          x: update.new_x,
          y: update.new_y,
          shape: Line(update.new_x2, update.new_y2),
        }
      }
    }
    el
  })
}

// ============================================================
// 接続ポイント検索(純粋関数)
// ============================================================

///|
/// 接続ポイント検索結果
pub(all) struct NearestConnectionResult {
  element : Element
  anchor : Anchor
  point : Point
} derive(Show, Eq)

///|
/// 指定座標から最も近い接続ポイントを探す(純粋関数)
/// - 子要素(parent_id がある要素)は除外される
/// - exclude_ids に含まれる要素は除外される
pub fn find_nearest_connection(
  elements : Array[Element],
  point : Point,
  exclude_ids : Array[String],
  threshold : Double,
) -> NearestConnectionResult? {
  let mut nearest : (Element, Anchor, Point, Double)? = None
  for el in elements {
    // 除外リストに含まれる要素はスキップ
    guard not(exclude_ids.contains(el.id)) else { continue }
    // 子要素はスキップ
    guard el.parent_id is None else { continue }
    // 最寄りのアンカーを探す
    guard el.find_nearest_anchor(point, threshold)
      is Some((anchor, anchor_point, dist)) else {
      continue
    }
    match nearest {
      None => nearest = Some((el, anchor, anchor_point, dist))
      Some((_, _, _, prev_dist)) =>
        if dist < prev_dist {
          nearest = Some((el, anchor, anchor_point, dist))
        }
    }
  }
  match nearest {
    Some((el, anchor, point, _)) => Some({ element: el, anchor, point })
    None => None
  }
}

// ===== ユーティリティ関数 =====

///|
/// XML エスケープ
pub fn escape_xml(s : String) -> String {
  s
  .replace(old="&", new="&")
  .replace(old="<", new="<")
  .replace(old=">", new=">")
  .replace(old="\"", new=""")
}

///|
/// 数値を文字列に変換(小数点以下2桁に丸め)
pub fn num_str(value : Double) -> String {
  let rounded = (value * 100.0).round() / 100.0
  rounded.to_string()
}

///|
/// 座標値を整数に丸める(データ可読性向上のため)
pub fn round_coord(value : Double) -> Double {
  value.round()
}

///|
/// 要素の座標を整数に丸める
pub fn round_element_coords(el : Element) -> Element {
  let x = round_coord(el.x)
  let y = round_coord(el.y)
  let shape = match el.shape {
    Rect(w, h, rx, ry) => Rect(round_coord(w), round_coord(h), rx, ry)
    Circle(r) => Circle(round_coord(r))
    Ellipse(rx, ry) => Ellipse(round_coord(rx), round_coord(ry))
    Line(x2, y2) => Line(round_coord(x2), round_coord(y2))
    Polyline(points) =>
      Polyline(
        points.map(fn(p) { { x: round_coord(p.x), y: round_coord(p.y) } }),
      )
    Path(d, sx, sy, ex, ey) =>
      Path(
        d,
        round_coord(sx),
        round_coord(sy),
        round_coord(ex),
        round_coord(ey),
      )
    Text(content, font_size) => Text(content, font_size)
  }
  { ..el, x, y, shape }
}

// ============================================================
// Line 接続トポロジー
// ============================================================

///|
/// Line の更新後座標
pub(all) struct LineCoords {
  id : String
  x : Double
  y : Double
  x2 : Double
  y2 : Double
} derive(Show, Eq)

///|
/// LineCoords からアンカーポイントを計算
fn get_anchor_from_coords(coords : LineCoords, anchor : Anchor) -> Point {
  match anchor {
    LineStart => { x: coords.x, y: coords.y }
    LineEnd => { x: coords.x2, y: coords.y2 }
    _ => { x: (coords.x + coords.x2) / 2.0, y: (coords.y + coords.y2) / 2.0 }
  }
}

///|
/// Line の座標からアンカーポイントを計算(公開版)
pub fn get_anchor_from_line_coords(
  x : Double,
  y : Double,
  x2 : Double,
  y2 : Double,
  anchor : Anchor,
) -> Point {
  match anchor {
    LineStart => { x, y }
    LineEnd => { x: x2, y: y2 }
    _ => { x: (x + x2) / 2.0, y: (y + y2) / 2.0 }
  }
}

///|
/// Line 移動時の座標更新を計算(トポロジー検出)
///
/// 移動した Line から BFS で接続グラフをたどり、
/// 影響を受ける全ての Line の新しい座標を計算する
pub fn compute_line_move_topology(
  elements : Array[Element],
  moved_line_id : String,
  new_x : Double,
  new_y : Double,
  dx : Double,
  dy : Double,
) -> Array[LineCoords] {
  let moved_line = elements.iter().find_first(fn(e) { e.id == moved_line_id })
  guard moved_line is Some(el) else { return [] }
  guard el.shape is Line(x2, y2) else { return [] }

  // 移動した Line の新しい座標を計算
  // ドラッグ時は接続先に固定せず、自由に移動させる
  // 接続された他の Line は BFS で追従する
  let final_x = new_x
  let final_y = new_y
  let final_x2 = x2 + dx
  let final_y2 = y2 + dy

  // 座標を Map で管理(複数回の更新に対応)
  let coords_map : Map[String, LineCoords] = {}
  coords_map[moved_line_id] = {
    id: moved_line_id,
    x: final_x,
    y: final_y,
    x2: final_x2,
    y2: final_y2,
  }

  // BFS で接続された Line を順次更新
  let queue : Array[String] = [moved_line_id]
  let in_queue : Array[String] = [moved_line_id]
  while queue.length() > 0 {
    let current_id = queue.remove(0)
    guard coords_map.get(current_id) is Some(current_coords)

    // current_id に接続している他の Line を探す
    for e in elements {
      guard e.shape is Line(e_x2, e_y2) else { continue }
      guard e.id != current_id else { continue }
      guard e.connections is Some(conns) else { continue }

      // 既存の座標があればそれを使用、なければ元の座標
      let (base_x, base_y, base_x2, base_y2) = match coords_map.get(e.id) {
        Some(c) => (c.x, c.y, c.x2, c.y2)
        None => (e.x, e.y, e_x2, e_y2)
      }
      let mut new_x = base_x
      let mut new_y = base_y
      let mut new_x2 = base_x2
      let mut new_y2 = base_y2
      let mut needs_update = false

      // 始点が current_id に接続している場合
      if conns.start is Some(conn) && conn.element_id == current_id {
        let p = get_anchor_from_coords(current_coords, conn.anchor)
        new_x = p.x
        new_y = p.y
        needs_update = true
      }

      // 終点が current_id に接続している場合
      if conns.end is Some(conn) && conn.element_id == current_id {
        let p = get_anchor_from_coords(current_coords, conn.anchor)
        new_x2 = p.x
        new_y2 = p.y
        needs_update = true
      }
      if needs_update {
        coords_map[e.id] = {
          id: e.id,
          x: new_x,
          y: new_y,
          x2: new_x2,
          y2: new_y2,
        }
        if not(in_queue.contains(e.id)) {
          in_queue.push(e.id)
          queue.push(e.id)
        }
      }
    }
  }
  coords_map.values().collect()
}