// 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.

///|
/// IR (Intermediate Representation) for diago
///
/// The IR is a tree data structure that keeps track of resolved values
/// of diago keys after processing variable substitutions, imports, and globs.

///|
/// A scalar value in the IR.
///
/// Scalars carry a source `range` for error reporting and reference tracking.
pub struct Scalar {
  value : ScalarValue
  range : @lexer.Range
} derive(Eq, Show)

///|
/// Types of scalar values
pub(all) enum ScalarValue {
  Null
  Bool(String)
  Number(String)
  String(@ast.StringValue)
} derive(Eq, Show)

///|
/// An IR Map containing fields and edges
pub struct Map {
  fields : Array[Field]
  edges : Array[Edge]
} derive(Eq, Show)

///|
/// A field in the IR map
pub struct Field {
  name : String
  primary : Scalar?
  composite : Composite?
  references : Array[FieldReference]
} derive(Eq, Show)

///|
/// Composite values (Array or Map)
pub(all) enum Composite {
  Array(IRArray)
  Map(Map)
} derive(Eq, Show)

///|
/// An array of values
pub struct IRArray {
  values : Array[Value]
} derive(Eq, Show)

///|
/// Value types in IR
pub(all) enum Value {
  Scalar(Scalar)
  Array(IRArray)
  Map(Map)
} derive(Eq, Show)

///|
/// An edge in the IR
pub struct Edge {
  id : EdgeID
  primary : Scalar?
  map : Map?
  references : Array[EdgeReference]
} derive(Eq, Show)

///|
/// Unique identifier for an edge
pub struct EdgeID {
  src_path : Array[String]
  src_arrow : Bool
  dst_path : Array[String]
  dst_arrow : Bool
  index : Int?
  glob : Bool
} derive(Eq, Show)

///|
/// Reference to a field from the AST
pub struct FieldReference {
  range : @lexer.Range
  string : String
  key_path_index : Int
  primary : Bool
  due_to_glob : Bool
  due_to_lazy_glob : Bool
} derive(Eq, Show)

///|
/// Reference to an edge from the AST
pub struct EdgeReference {
  range : @lexer.Range
  edge_index : Int
  primary : Bool
  due_to_glob : Bool
  due_to_lazy_glob : Bool
} derive(Eq, Show)

///|
/// Boxed IR node for generic traversal.
pub(all) enum Node {
  Scalar(Scalar)
  Field(Field)
  Edge(Edge)
  IRArray(IRArray)
  Map(Map)
  Composite(Composite)
  Value(Value)
} derive(Eq, Show)

///|
pub fn Node::node_type(self : Node) -> String {
  match self {
    Scalar(_) => "Scalar"
    Field(_) => "Field"
    Edge(_) => "Edge"
    IRArray(_) => "IRArray"
    Map(_) => "Map"
    Composite(_) => "Composite"
    Value(_) => "Value"
  }
}

///|
pub fn Node::children(self : Node) -> Array[Node] {
  let out : Array[Node] = []
  match self {
    Scalar(_) => ()
    Field(f) => {
      match f.primary {
        Some(s) => out.push(Scalar(s))
        None => ()
      }
      match f.composite {
        Some(c) => out.push(Composite(c))
        None => ()
      }
    }
    Edge(e) => {
      match e.primary {
        Some(s) => out.push(Scalar(s))
        None => ()
      }
      match e.map {
        Some(m) => out.push(Map(m))
        None => ()
      }
    }
    IRArray(a) =>
      for v in a.values {
        out.push(Value(v))
      }
    Map(m) => {
      for f in m.fields {
        out.push(Field(f))
      }
      for e in m.edges {
        out.push(Edge(e))
      }
    }
    Composite(c) =>
      match c {
        Array(a) => out.push(IRArray(a))
        Map(m) => out.push(Map(m))
      }
    Value(v) =>
      match v {
        Scalar(s) => out.push(Scalar(s))
        Array(a) => out.push(IRArray(a))
        Map(m) => out.push(Map(m))
      }
  }
  out
}

// ============================================================================
// Constructors
// ============================================================================

///|
fn default_range() -> @lexer.Range {
  let p = @lexer.Position::zero()
  @lexer.Range::new(p, p)
}

///|
pub fn Scalar::at(range : @lexer.Range, value : ScalarValue) -> Scalar {
  { value, range }
}

///|
pub fn Scalar::new(value : ScalarValue) -> Scalar {
  Scalar::at(default_range(), value)
}

///|
pub fn Scalar::null() -> Scalar {
  Scalar::at(default_range(), ScalarValue::Null)
}

///|
pub fn Scalar::bool(b : Bool) -> Scalar {
  let s = if b { "true" } else { "false" }
  Scalar::at(default_range(), ScalarValue::Bool(s))
}

///|
pub fn Scalar::number(n : String) -> Scalar {
  Scalar::at(default_range(), ScalarValue::Number(n))
}

///|
pub fn Scalar::string(s : String) -> Scalar {
  let r = default_range()
  Scalar::at(
    r,
    ScalarValue::String(
      @ast.StringValue::Unquoted(
        @ast.UnquotedString::new(r, [@ast.Interpolation::Text(s)], []),
      ),
    ),
  )
}

///|
pub fn Map::new() -> Map {
  { fields: [], edges: [] }
}

///|
pub fn Field::new(
  name : String,
  primary : Scalar?,
  composite : Composite?,
  references : Array[FieldReference],
) -> Field {
  { name, primary, composite, references }
}

///|
pub fn Field::simple(name : String) -> Field {
  { name, primary: None, composite: None, references: [] }
}

///|
pub fn IRArray::new(values : Array[Value]) -> IRArray {
  { values, }
}

///|
pub fn Edge::new(
  id : EdgeID,
  primary : Scalar?,
  map : Map?,
  references : Array[EdgeReference],
) -> Edge {
  { id, primary, map, references }
}

///|
pub fn EdgeID::new(
  src_path : Array[String],
  dst_path : Array[String],
  src_arrow : Bool,
  dst_arrow : Bool,
) -> EdgeID {
  { src_path, src_arrow, dst_path, dst_arrow, index: None, glob: false }
}

///|
pub fn EdgeID::with_index(
  src_path : Array[String],
  dst_path : Array[String],
  src_arrow : Bool,
  dst_arrow : Bool,
  index : Int,
) -> EdgeID {
  { src_path, src_arrow, dst_path, dst_arrow, index: Some(index), glob: false }
}

///|
pub fn EdgeID::with_glob(
  src_path : Array[String],
  dst_path : Array[String],
  src_arrow : Bool,
  dst_arrow : Bool,
) -> EdgeID {
  { src_path, src_arrow, dst_path, dst_arrow, index: None, glob: true }
}

///|
pub fn FieldReference::new(
  range : @lexer.Range,
  string : String,
  key_path_index : Int,
  primary : Bool,
) -> FieldReference {
  {
    range,
    string,
    key_path_index,
    primary,
    due_to_glob: false,
    due_to_lazy_glob: false,
  }
}

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

// ============================================================================
// Map operations
// ============================================================================

///|
/// Get a field by name (case-insensitive)
pub fn Map::get_field(self : Map, name : String) -> Field? {
  let lower = name.to_lower()
  for f in self.fields {
    if f.name.to_lower() == lower {
      return Some(f)
    }
  }
  None
}

///|
/// Check if map has a field with given name
pub fn Map::has_field(self : Map, name : String) -> Bool {
  self.get_field(name) is Some(_)
}

///|
/// Add a field to the map
pub fn Map::add_field(self : Map, field : Field) -> Unit {
  self.fields.push(field)
}

///|
fn Map::set_field(self : Map, field : Field) -> Unit {
  let lower = field.name.to_lower()
  for i, f in self.fields {
    if f.name.to_lower() == lower {
      self.fields[i] = field
      return
    }
  }
  self.fields.push(field)
}

///|
/// Add an edge to the map
pub fn Map::add_edge(self : Map, edge : Edge) -> Unit {
  self.edges.push(edge)
}

///|
fn Map::set_edge(self : Map, edge : Edge) -> Unit {
  for i, e in self.edges {
    if e.id == edge.id {
      self.edges[i] = edge
      return
    }
  }
  self.edges.push(edge)
}

///|
/// Get or create a field by name
pub fn Map::ensure_field(self : Map, name : String) -> Field {
  match self.get_field(name) {
    Some(f) => f
    None => {
      let f = Field::simple(name)
      self.fields.push(f)
      f
    }
  }
}

///|
/// Delete a field by name, returning it if found
pub fn Map::delete_field(self : Map, name : String) -> Field? {
  let lower = name.to_lower()
  for i, f in self.fields {
    if f.name.to_lower() == lower {
      // Remove and return
      let removed = self.fields.remove(i)
      return Some(removed)
    }
  }
  None
}

///|
/// Find edges matching an EdgeID
pub fn Map::find_edges(self : Map, id : EdgeID) -> Array[Edge] {
  let result : Array[Edge] = []
  for e in self.edges {
    if e.id.matches(id) {
      result.push(e)
    }
  }
  result
}

// ============================================================================
// EdgeID operations
// ============================================================================

///|
/// Check if this EdgeID matches another (for queries)
pub fn EdgeID::matches(self : EdgeID, other : EdgeID) -> Bool {
  // Check index if both have it
  match (self.index, other.index) {
    (Some(i1), Some(i2)) => if i1 != i2 { return false }
    _ => ()
  }
  // Check arrows
  if self.src_arrow != other.src_arrow {
    return false
  }
  if self.dst_arrow != other.dst_arrow {
    return false
  }
  // Check paths (case-insensitive)
  if self.src_path.length() != other.src_path.length() {
    return false
  }
  for i, s in self.src_path {
    if s.to_lower() != other.src_path[i].to_lower() {
      return false
    }
  }
  if self.dst_path.length() != other.dst_path.length() {
    return false
  }
  for i, s in self.dst_path {
    if s.to_lower() != other.dst_path[i].to_lower() {
      return false
    }
  }
  true
}

///|
/// Create a copy of this EdgeID
pub fn EdgeID::copy(self : EdgeID) -> EdgeID {
  {
    src_path: self.src_path.copy(),
    src_arrow: self.src_arrow,
    dst_path: self.dst_path.copy(),
    dst_arrow: self.dst_arrow,
    index: self.index,
    glob: self.glob,
  }
}

// ============================================================================
// Value operations
// ============================================================================

///|
/// Get the scalar value if this is a scalar
pub fn Value::as_scalar(self : Value) -> Scalar? {
  match self {
    Scalar(s) => Some(s)
    _ => None
  }
}

///|
/// Get the map if this is a map
pub fn Value::as_map(self : Value) -> Map? {
  match self {
    Map(m) => Some(m)
    _ => None
  }
}

///|
/// Get the array if this is an array
pub fn Value::as_array(self : Value) -> IRArray? {
  match self {
    Array(a) => Some(a)
    _ => None
  }
}

// ============================================================================
// Scalar operations
// ============================================================================

///|
/// Get the string representation of a scalar value
pub fn Scalar::to_string(self : Scalar) -> String {
  match self.value {
    Null => "null"
    Bool(s) => s
    Number(n) => n
    String(s) => s.content()
  }
}

///|
/// Get underlying string value (including block-string metadata) if present
pub fn Scalar::as_string_value(self : Scalar) -> @ast.StringValue? {
  match self.value {
    String(s) => Some(s)
    _ => None
  }
}

///|
/// Get block-string language tag if this scalar is a block string
pub fn Scalar::block_string_tag(self : Scalar) -> String? {
  match self.value {
    String(@ast.StringValue::Block(bs)) => Some(bs.tag)
    _ => None
  }
}

///|
/// Check if scalar is null
pub fn Scalar::is_null(self : Scalar) -> Bool {
  self.value is Null
}

///|
/// Check if scalar is a boolean
pub fn Scalar::is_bool(self : Scalar) -> Bool {
  self.value is Bool(_)
}

///|
/// Get boolean value if this is a boolean
pub fn Scalar::as_bool(self : Scalar) -> Bool? {
  match self.value {
    Bool(s) =>
      match s.to_lower() {
        "true" => Some(true)
        "false" => Some(false)
        _ => None
      }
    _ => None
  }
}

///|
pub fn Scalar::as_int(self : Scalar) -> Int? {
  match self.value {
    Number(n) => Some(@strconv.parse_int(n)) catch { _ => None }
    String(s) => Some(@strconv.parse_int(s.content())) catch { _ => None }
    _ => None
  }
}

///|
pub fn Scalar::as_double(self : Scalar) -> Double? {
  match self.value {
    Number(n) => Some(@strconv.parse_double(n)) catch { _ => None }
    String(s) => Some(@strconv.parse_double(s.content())) catch { _ => None }
    _ => None
  }
}

///|
pub fn Scalar::get_range(self : Scalar) -> @lexer.Range {
  self.range
}

///|
/// Check if scalar is a number
pub fn Scalar::is_number(self : Scalar) -> Bool {
  self.value is Number(_)
}

///|
/// Check if scalar is a string
pub fn Scalar::is_string(self : Scalar) -> Bool {
  self.value is String(_)
}

// ============================================================================
// Field operations
// ============================================================================

///|
/// Get the map composite if this field has one
pub fn Field::map(self : Field) -> Map? {
  match self.composite {
    Some(Composite::Map(m)) => Some(m)
    _ => None
  }
}

///|
/// Get the array composite if this field has one
pub fn Field::array(self : Field) -> IRArray? {
  match self.composite {
    Some(Composite::Array(a)) => Some(a)
    _ => None
  }
}

///|
/// Check if this field has a primary value
pub fn Field::has_primary(self : Field) -> Bool {
  self.primary is Some(_)
}

///|
/// Check if this field has a composite value
pub fn Field::has_composite(self : Field) -> Bool {
  self.composite is Some(_)
}