// Copyright 2025 International Digital Economy Academy
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

///|
/// Immutable data model for functional architecture

///|
/// Graph-scoped metadata values aligned with the reference's current config data shape.
pub(all) enum GraphDataValue {
  Text(String)
  StringArray(Array[String])
} derive(Eq, Debug)

///|
/// Immutable legend configuration
pub struct LegendModel {
  title : String?
  position : LegendPosition
  entries : Array[LegendEntryInput]
  fill : String?
  stroke : String?
  padding : Double
  entry_gap : Double
  icon_size : Double
} derive(Debug)

///|
pub fn LegendModel::new() -> LegendModel {
  {
    title: None,
    position: BottomRight,
    entries: [],
    fill: Some("#ffffff"),
    stroke: Some("#cccccc"),
    padding: 12.0,
    entry_gap: 8.0,
    icon_size: 20.0,
  }
}

///|
#warnings("-struct_never_constructed")
pub struct FragmentOperandModel {
  /// Immutable sequence fragment operand
  condition : String?
  separator_y : Double
} derive(Debug)

///|
/// Immutable sequence fragment
pub struct SequenceFragmentModel {
  fragment_type : String
  condition : String?
  bbox : Box?
  start_index : Int
  end_index : Int
  operands : Array[FragmentOperandModel]
} derive(Debug)

///|
pub fn SequenceFragmentModel::new(
  fragment_type : String,
  start_index : Int,
  end_index : Int,
) -> SequenceFragmentModel {
  {
    fragment_type,
    condition: None,
    bbox: None,
    start_index,
    end_index,
    operands: [],
  }
}

///|
pub struct ObjectReference {
  range : @lexer.Range
  key_path_index : Int
  in_edge : Bool
  scope_abs_id_syntax : String?
  source_path : String?
  primary : Bool
  due_to_glob : Bool
  due_to_lazy_glob : Bool
} derive(Eq, Debug)

///|
pub fn ObjectReference::new(
  range : @lexer.Range,
  key_path_index : Int,
  in_edge : Bool,
  scope_abs_id_syntax : String?,
  source_path : String?,
  primary : Bool,
  due_to_glob : Bool,
  due_to_lazy_glob : Bool,
) -> ObjectReference {
  {
    range,
    key_path_index,
    in_edge,
    scope_abs_id_syntax,
    source_path,
    primary,
    due_to_glob,
    due_to_lazy_glob,
  }
}

///|
pub struct EdgeReference {
  range : @lexer.Range
  edge_index : Int
  scope_abs_id_syntax : String?
  source_path : String?
  primary : Bool
  due_to_glob : Bool
  due_to_lazy_glob : Bool
} derive(Eq, Debug)

///|
pub fn EdgeReference::new(
  range : @lexer.Range,
  edge_index : Int,
  scope_abs_id_syntax : String?,
  source_path : String?,
  primary : Bool,
  due_to_glob : Bool,
  due_to_lazy_glob : Bool,
) -> EdgeReference {
  {
    range,
    edge_index,
    scope_abs_id_syntax,
    source_path,
    primary,
    due_to_glob,
    due_to_lazy_glob,
  }
}

///|
/// Immutable style model
pub struct StyleModel {
  opacity : StyleValue?
  stroke : StyleValue?
  fill : StyleValue?
  stroke_width : StyleValue?
  stroke_dash : StyleValue?
  border_radius : StyleValue?
  shadow : StyleValue?
  three_d : StyleValue?
  multiple : StyleValue?
  font : StyleValue?
  font_size : StyleValue?
  font_color : StyleValue?
  bold : StyleValue?
  italic : StyleValue?
  underline : StyleValue?
  animated : StyleValue?
  filled : StyleValue?
  double_border : StyleValue?
  text_transform : StyleValue?
  font_family : StyleValue?
  fill_pattern : StyleValue?
  sketch : StyleValue?
  curved : StyleValue?
} derive(Eq, Debug)

///|
pub fn StyleModel::new() -> StyleModel {
  {
    opacity: None,
    stroke: None,
    fill: None,
    stroke_width: None,
    stroke_dash: None,
    border_radius: None,
    shadow: None,
    three_d: None,
    multiple: None,
    font: None,
    font_size: None,
    font_color: None,
    bold: None,
    italic: None,
    underline: None,
    animated: None,
    filled: None,
    double_border: None,
    text_transform: None,
    font_family: None,
    fill_pattern: None,
    sketch: None,
    curved: None,
  }
}

///|
/// Immutable object model
pub struct ObjectModel {
  id : String
  id_val : String
  id_syntax : String
  abs_id_syntax : String
  references : Array[ObjectReference]
  label : String
  shape_type : ShapeType
  style : StyleModel
  box : Box?
  label_box : Box?
  children : Array[ObjectModel]
  z_index : Int
  icon : String?
  icon_position : String?
  tooltip : String?
  tooltip_position : String?
  link : String?
  label_position : String?
  classes : Array[String]
  language : String?
  sql_constraints : Array[String]
  grid_rows : Int?
  grid_columns : Int?
  grid_gap : Double?
  horizontal_gap : Double?
  vertical_gap : Double?
  grid_column_span : Int?
  grid_row_span : Int?
  near : String?
  top : Int?
  left : Int?
  direction : String?
} derive(Debug)

///|
pub fn ObjectModel::new(
  id : String,
  id_val? : String = id,
  id_syntax? : String = id,
  abs_id_syntax? : String = id,
) -> ObjectModel {
  {
    id,
    id_val,
    id_syntax,
    abs_id_syntax,
    references: [],
    label: id_val,
    shape_type: Rectangle,
    style: StyleModel::new(),
    box: None,
    label_box: None,
    children: [],
    z_index: 0,
    icon: None,
    icon_position: None,
    tooltip: None,
    tooltip_position: None,
    link: None,
    label_position: None,
    classes: [],
    language: None,
    sql_constraints: [],
    grid_rows: None,
    grid_columns: None,
    grid_gap: None,
    horizontal_gap: None,
    vertical_gap: None,
    grid_column_span: None,
    grid_row_span: None,
    near: None,
    top: None,
    left: None,
    direction: None,
  }
}

///|
/// Immutable edge model
pub struct EdgeModel {
  index : Int
  src_id : String
  dst_id : String
  src_id_syntax : String
  dst_id_syntax : String
  references : Array[EdgeReference]
  classes : Array[String]
  src_arrow : Bool
  dst_arrow : Bool
  src_arrowhead : ArrowheadType
  dst_arrowhead : ArrowheadType
  src_arrowhead_label : String?
  dst_arrowhead_label : String?
  src_arrowhead_label_color : String?
  dst_arrowhead_label_color : String?
  src_anchor : String?
  dst_anchor : String?
  label : String
  icon : String?
  icon_position : String?
  icon_border_radius : Double?
  link : String?
  style : StyleModel
  route : Array[Point]
  bend_points : Array[Point]?
  is_curve : Bool
  z_index : Int
  reference_count : Int
  label_box : Box?
  src_column_index : Int?
  dst_column_index : Int?
} derive(Debug)

///|
pub fn EdgeModel::new(
  index : Int,
  src_id : String,
  dst_id : String,
  src_arrow : Bool,
  dst_arrow : Bool,
  src_id_syntax? : String = src_id,
  dst_id_syntax? : String = dst_id,
) -> EdgeModel {
  {
    index,
    src_id,
    dst_id,
    src_id_syntax,
    dst_id_syntax,
    references: [],
    classes: [],
    src_arrow,
    dst_arrow,
    src_arrowhead: if src_arrow {
      Triangle
    } else {
      None
    },
    dst_arrowhead: if dst_arrow {
      Triangle
    } else {
      None
    },
    src_arrowhead_label: None,
    dst_arrowhead_label: None,
    src_arrowhead_label_color: None,
    dst_arrowhead_label_color: None,
    src_anchor: None,
    dst_anchor: None,
    label: "",
    icon: None,
    icon_position: None,
    icon_border_radius: None,
    link: None,
    style: StyleModel::new(),
    route: [],
    bend_points: None,
    is_curve: false,
    z_index: 0,
    reference_count: 1,
    label_box: None,
    src_column_index: None,
    dst_column_index: None,
  }
}

///|
/// Immutable graph model
pub struct GraphModel {
  name : String
  root : ObjectModel
  objects : Array[ObjectModel]
  edges : Array[EdgeModel]
  activation_boxes : Array[(String, Box)]
  sequence_notes : Array[(String, String, Box)]
  sequence_fragments : Array[SequenceFragmentModel]
  layers : Array[LayerModel]
  scenarios : Array[ScenarioModel]
  steps : Array[StepModel]
  legend : LegendModel?
  is_folder_only : Bool
  data : Map[String, GraphDataValue]
} derive(Debug)

///|
#warnings("-struct_never_constructed")
pub struct LayerModel {
  /// Immutable layer model
  name : String
  graph : GraphModel
} derive(Debug)

///|
#warnings("-struct_never_constructed")
pub struct ScenarioModel {
  /// Immutable scenario model
  name : String
  graph : GraphModel
} derive(Debug)

///|
#warnings("-struct_never_constructed")
pub struct StepModel {
  /// Immutable step model
  name : String
  graph : GraphModel
} derive(Debug)

///|
pub fn GraphModel::new(name : String) -> GraphModel {
  let root = ObjectModel::new("root")
  {
    name,
    root,
    objects: [],
    edges: [],
    activation_boxes: [],
    sequence_notes: [],
    sequence_fragments: [],
    layers: [],
    scenarios: [],
    steps: [],
    legend: None,
    is_folder_only: false,
    data: Map([]),
  }
}

///|
/// Functional helpers
pub fn ObjectModel::with_label(
  self : ObjectModel,
  label : String,
) -> ObjectModel {
  { ..self, label, }
}

///|
pub fn ObjectModel::with_box(self : ObjectModel, box : Box?) -> ObjectModel {
  { ..self, box, }
}

///|
pub fn ObjectModel::with_label_box(
  self : ObjectModel,
  label_box : Box?,
) -> ObjectModel {
  { ..self, label_box, }
}

///|
pub fn ObjectModel::add_child(
  self : ObjectModel,
  child : ObjectModel,
) -> ObjectModel {
  let children = self.children.copy()
  children.push(child)
  { ..self, children, }
}

///|
pub fn EdgeModel::with_label(self : EdgeModel, label : String) -> EdgeModel {
  { ..self, label, }
}

///|
pub fn EdgeModel::with_route(
  self : EdgeModel,
  route : Array[Point],
) -> EdgeModel {
  { ..self, route, }
}

///|
pub fn EdgeModel::with_label_box(
  self : EdgeModel,
  label_box : Box?,
) -> EdgeModel {
  { ..self, label_box, }
}

///|
pub fn GraphModel::add_object(
  self : GraphModel,
  obj : ObjectModel,
) -> GraphModel {
  let objects = self.objects.copy()
  objects.push(obj)
  { ..self, objects, }
}

///|
pub fn GraphModel::add_edge(self : GraphModel, edge : EdgeModel) -> GraphModel {
  let edges = self.edges.copy()
  edges.push(edge)
  { ..self, edges, }
}

///|
pub fn GraphModel::with_legend(
  self : GraphModel,
  legend : LegendModel?,
) -> GraphModel {
  { ..self, legend, }
}