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

///|
struct ParityCase {
  name : String
  host_focused : Bool
  updates : Array[@common.TreeUpdate]
} derive(FromJson)

///|
struct FocusMove {
  old_focus : String?
  new_focus : String?
} derive(ToJson)

///|
struct ChangesObservation {
  added : Array[String]
  updated : Array[String]
  removed : Array[String]
  focus_moves : Array[FocusMove]
} derive(ToJson)

///|
struct RecordingHandler {
  added : Array[String]
  updated : Array[String]
  removed : Array[String]
  focus_moves : Array[FocusMove]
}

///|
fn RecordingHandler::new() -> RecordingHandler {
  { added: [], updated: [], removed: [], focus_moves: [], }
}

///|
fn local_id(node : @consumer.NodeRef) -> String {
  node.id().to_components().0.to_u64().to_string()
}

///|
fn optional_local_id(node : @consumer.NodeRef?) -> String? {
  node.map(local_id)
}

///|
impl @consumer.ChangeHandler for RecordingHandler with fn node_added(self, node) {
  self.added.push(local_id(node))
}

///|
impl @consumer.ChangeHandler for RecordingHandler with fn node_updated(
  self,
  _old_node,
  new_node,
) {
  self.updated.push(local_id(new_node))
}

///|
impl @consumer.ChangeHandler for RecordingHandler with fn focus_moved(
  self,
  old_focus,
  new_focus,
) {
  self.focus_moves.push({
    old_focus: optional_local_id(old_focus),
    new_focus: optional_local_id(new_focus),
  })
}

///|
impl @consumer.ChangeHandler for RecordingHandler with fn node_removed(
  self,
  node,
) {
  self.removed.push(local_id(node))
}

///|
fn RecordingHandler::observation(self : RecordingHandler) -> ChangesObservation {
  self.added.sort()
  self.updated.sort()
  self.removed.sort()
  {
    added: self.added,
    updated: self.updated,
    removed: self.removed,
    focus_moves: self.focus_moves,
  }
}

///|
fn empty_changes() -> ChangesObservation {
  { added: [], updated: [], removed: [], focus_moves: [], }
}

///|
struct NodeObservation {
  id : String
  parent_id : String?
  role : String
  label : String?
  value : String?
  is_hidden : Bool
  is_focused : Bool
  is_focused_in_tree : Bool
  common_filter : String
  children : Array[String]
  children_reversed : Array[String]
  children_mixed : Array[String]
  filtered_children : Array[String]
  filtered_children_reversed : Array[String]
  filtered_children_mixed : Array[String]
  following_siblings : Array[String]
  following_filtered_siblings : Array[String]
  preceding_siblings : Array[String]
  preceding_filtered_siblings : Array[String]
  labelled_by : Array[String]
  controls : Array[String]
  items : Array[String]
} derive(ToJson)

///|
struct TreeObservation {
  root : String
  focus : String?
  focus_in_tree : String
  active_dialog : String?
  nodes : Array[NodeObservation]
} derive(ToJson)

///|
struct StepObservation {
  state : TreeObservation
  changes : ChangesObservation
} derive(ToJson)

///|
struct CaseObservation {
  name : String
  steps : Array[StepObservation]
} derive(ToJson)

///|
fn ids(iter : @consumer.DoubleEndedIter[@consumer.NodeRef]) -> Array[String] {
  iter.iter().map(local_id).to_array()
}

///|
fn reversed_ids(
  iter : @consumer.DoubleEndedIter[@consumer.NodeRef],
) -> Array[String] {
  iter.rev_iter().map(local_id).to_array()
}

///|
fn mixed_ids(
  iter : @consumer.DoubleEndedIter[@consumer.NodeRef],
) -> Array[String] {
  let result : Array[String] = []
  let mut take_front = true
  for ;; {
    let next = if take_front { iter.next() } else { iter.next_back() }
    match next {
      None => break
      Some(node) => {
        result.push(local_id(node))
        take_front = !take_front
      }
    }
  }
  result
}

///|
fn filter_name(node : @consumer.NodeRef) -> String {
  match @consumer.common_filter(node) {
    Include => "Include"
    ExcludeNode => "ExcludeNode"
    ExcludeSubtree => "ExcludeSubtree"
  }
}

///|
fn observe_node(node : @consumer.NodeRef) -> NodeObservation {
  {
    id: local_id(node),
    parent_id: optional_local_id(node.parent()),
    role: node.role().to_string(),
    label: node.label(),
    value: node.value(),
    is_hidden: node.is_hidden(),
    is_focused: node.is_focused(),
    is_focused_in_tree: node.is_focused_in_tree(),
    common_filter: filter_name(node),
    children: ids(node.children()),
    children_reversed: reversed_ids(node.children()),
    children_mixed: mixed_ids(node.children()),
    filtered_children: ids(node.filtered_children(@consumer.common_filter)),
    filtered_children_reversed: reversed_ids(
      node.filtered_children(@consumer.common_filter),
    ),
    filtered_children_mixed: mixed_ids(
      node.filtered_children(@consumer.common_filter),
    ),
    following_siblings: ids(node.following_siblings()),
    following_filtered_siblings: ids(
      node.following_filtered_siblings(@consumer.common_filter),
    ),
    preceding_siblings: ids(node.preceding_siblings()),
    preceding_filtered_siblings: ids(
      node.preceding_filtered_siblings(@consumer.common_filter),
    ),
    labelled_by: ids(node.labelled_by()),
    controls: ids(node.controls()),
    items: ids(node.items(@consumer.common_filter)),
  }
}

///|
fn observe_state(state : @consumer.TreeState) -> TreeObservation {
  let stack : Array[@consumer.NodeRef] = [state.root()]
  let nodes : Array[NodeObservation] = []
  while stack.pop() is Some(node) {
    let children = node.children()
    while children.next_back() is Some(child) {
      stack.push(child)
    }
    nodes.push(observe_node(node))
  }
  {
    root: local_id(state.root()),
    focus: optional_local_id(state.focus()),
    focus_in_tree: local_id(state.focus_in_tree()),
    active_dialog: optional_local_id(state.active_dialog()),
    nodes,
  }
}

///|
fn run_case(case : ParityCase) -> CaseObservation {
  if case.updates.length() == 0 {
    abort("parity case \"" + case.name + "\" has no updates")
  }
  let tree = @consumer.Tree(case.updates[0], case.host_focused)
  let steps : Array[StepObservation] = [
    { state: observe_state(tree.state()), changes: empty_changes(), },
  ]
  for update_index in 1..")
  }
  let source = @fs.read_file_to_string(args[1])
  let cases : Array[ParityCase] = @json.from_json(@json.parse(source))
  let observations = cases.map(run_case)
  println(@json.to_json(observations).stringify(indent=2))
}