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

///|
pub struct Tree {
  root : NodeId
  toolkit_name : String?
  toolkit_version : String?
} derive(Eq, Show)

///|
pub fn Tree::new(root : NodeId) -> Tree {
  { root, toolkit_name: None, toolkit_version: None }
}

///|
pub fn Tree::root_id(self : Tree) -> NodeId {
  self.root
}

///|
pub fn Tree::toolkit_name(self : Tree) -> String? {
  self.toolkit_name
}

///|
pub fn Tree::toolkit_version(self : Tree) -> String? {
  self.toolkit_version
}

///|
pub struct NodeUpdate {
  priv id : NodeId
  priv node : Node
}

///|
pub fn NodeUpdate::new(id : NodeId, node : Node) -> NodeUpdate {
  { id, node }
}

///|
pub fn NodeUpdate::id(self : NodeUpdate) -> NodeId {
  self.id
}

///|
pub fn NodeUpdate::node(self : NodeUpdate) -> Node {
  self.node
}

///|
pub struct TreeUpdate {
  priv nodes : Array[NodeUpdate]
  priv mut tree : Tree?
  priv tree_id : TreeId
  priv focus : NodeId
}

///|
pub fn TreeUpdate::new(tree_id : TreeId, focus : NodeId) -> TreeUpdate {
  { nodes: [], tree: None, tree_id, focus }
}

///|
pub fn TreeUpdate::nodes(self : TreeUpdate) -> Array[NodeUpdate] {
  self.nodes
}

///|
pub fn TreeUpdate::tree(self : TreeUpdate) -> Tree? {
  self.tree
}

///|
pub fn TreeUpdate::tree_id(self : TreeUpdate) -> TreeId {
  self.tree_id
}

///|
pub fn TreeUpdate::focus(self : TreeUpdate) -> NodeId {
  self.focus
}

///|
pub fn TreeUpdate::push_node(
  self : TreeUpdate,
  id : NodeId,
  node : Node,
) -> Unit {
  self.nodes.push(NodeUpdate::new(id, node))
}

///|
pub fn TreeUpdate::set_tree(self : TreeUpdate, tree : Tree) -> Unit {
  self.tree = Some(tree)
}

///|
pub(all) enum ActionData {
  CustomAction(Int)
  Value(String)
  NumericValue(Double)
  ScrollUnit(ScrollUnit)
  ScrollHint(ScrollHint)
  ScrollToPoint(Point)
  SetScrollOffset(Point)
  SetTextSelection(TextSelection)
} derive(Eq, Show)

///|
pub struct ActionRequest {
  priv action : Action
  priv target_tree : TreeId
  priv target_node : NodeId
  priv mut data : ActionData?
}

///|
pub fn ActionRequest::new(
  action : Action,
  target_tree : TreeId,
  target_node : NodeId,
) -> ActionRequest {
  { action, target_tree, target_node, data: None }
}

///|
pub fn ActionRequest::action(self : ActionRequest) -> Action {
  self.action
}

///|
pub fn ActionRequest::target_tree(self : ActionRequest) -> TreeId {
  self.target_tree
}

///|
pub fn ActionRequest::target_node(self : ActionRequest) -> NodeId {
  self.target_node
}

///|
pub fn ActionRequest::data(self : ActionRequest) -> ActionData? {
  self.data
}

///|
pub fn ActionRequest::set_data(self : ActionRequest, data : ActionData) -> Unit {
  self.data = Some(data)
}

///|
pub(open) trait ActivationHandler {
  request_initial_tree(Self) -> TreeUpdate?
}

///|
pub(open) trait ActionHandler {
  do_action(Self, ActionRequest) -> Unit
}

///|
pub(open) trait DeactivationHandler {
  deactivate_accessibility(Self) -> Unit
}

///|
pub impl ToJson for Tree with to_json(self : Tree) -> Json {
  Json::object({
    "root": self.root.to_json(),
    "toolkitName": match self.toolkit_name {
      Some(v) => Json::string(v)
      None => Json::null()
    },
    "toolkitVersion": match self.toolkit_version {
      Some(v) => Json::string(v)
      None => Json::null()
    },
  })
}

///|
pub impl @json.FromJson for Tree with from_json(json, path) {
  guard json is Object(obj) else {
    json_decode_error(path, "Tree::from_json: expected object")
  }
  for key, _ in obj {
    if key != "root" && key != "toolkitName" && key != "toolkitVersion" {
      json_decode_error(path, "Tree::from_json: unknown field \{key}")
    }
  }
  guard obj.get("root") is Some(root_json) else {
    json_decode_error(path, "Tree::from_json: missing field root")
  }
  guard obj.get("toolkitName") is Some(name_json) else {
    json_decode_error(path, "Tree::from_json: missing field toolkitName")
  }
  guard obj.get("toolkitVersion") is Some(ver_json) else {
    json_decode_error(path, "Tree::from_json: missing field toolkitVersion")
  }
  let root : NodeId = @json.FromJson::from_json(root_json, path.add_key("root"))
  let toolkit_name : String? = match name_json {
    Null => None
    String(s) => Some(s)
    _ =>
      json_decode_error(
        path.add_key("toolkitName"),
        "Tree::from_json: expected string or null",
      )
  }
  let toolkit_version : String? = match ver_json {
    Null => None
    String(s) => Some(s)
    _ =>
      json_decode_error(
        path.add_key("toolkitVersion"),
        "Tree::from_json: expected string or null",
      )
  }
  { root, toolkit_name, toolkit_version }
}

///|
pub impl ToJson for NodeUpdate with to_json(self : NodeUpdate) -> Json {
  Json::array([self.id.to_json(), self.node.to_json()])
}

///|
pub impl @json.FromJson for NodeUpdate with from_json(json, path) {
  guard json is Array(arr) else {
    json_decode_error(path, "NodeUpdate::from_json: expected array")
  }
  if arr.length() != 2 {
    json_decode_error(path, "NodeUpdate::from_json: expected 2 elements")
  }
  let id : NodeId = @json.FromJson::from_json(arr[0], path.add_index(0))
  let node : Node = @json.FromJson::from_json(arr[1], path.add_index(1))
  NodeUpdate::new(id, node)
}

///|
pub impl ToJson for TreeUpdate with to_json(self : TreeUpdate) -> Json {
  let nodes : Array[Json] = []
  for node in self.nodes {
    nodes.push(node.to_json())
  }
  Json::object({
    "nodes": Json::array(nodes),
    "tree": match self.tree {
      Some(t) => t.to_json()
      None => Json::null()
    },
    "treeId": self.tree_id.to_json(),
    "focus": self.focus.to_json(),
  })
}

///|
pub impl @json.FromJson for TreeUpdate with from_json(json, path) {
  guard json is Object(obj) else {
    json_decode_error(path, "TreeUpdate::from_json: expected object")
  }
  for key, _ in obj {
    if key != "nodes" && key != "tree" && key != "treeId" && key != "focus" {
      json_decode_error(path, "TreeUpdate::from_json: unknown field \{key}")
    }
  }
  guard obj.get("nodes") is Some(nodes_json) else {
    json_decode_error(path, "TreeUpdate::from_json: missing field nodes")
  }
  guard obj.get("tree") is Some(tree_json) else {
    json_decode_error(path, "TreeUpdate::from_json: missing field tree")
  }
  guard obj.get("treeId") is Some(tree_id_json) else {
    json_decode_error(path, "TreeUpdate::from_json: missing field treeId")
  }
  guard obj.get("focus") is Some(focus_json) else {
    json_decode_error(path, "TreeUpdate::from_json: missing field focus")
  }
  let nodes : Array[NodeUpdate] = @json.FromJson::from_json(
    nodes_json,
    path.add_key("nodes"),
  )
  let tree : Tree? = match tree_json {
    Null => None
    _ => Some(@json.FromJson::from_json(tree_json, path.add_key("tree")))
  }
  let tree_id : TreeId = @json.FromJson::from_json(
    tree_id_json,
    path.add_key("treeId"),
  )
  let focus : NodeId = @json.FromJson::from_json(
    focus_json,
    path.add_key("focus"),
  )
  let update = TreeUpdate::new(tree_id, focus)
  update.tree = tree
  for node in nodes {
    update.nodes.push(node)
  }
  update
}

///|
pub impl ToJson for ActionData with to_json(self : ActionData) -> Json {
  match self {
    ActionData::CustomAction(id) =>
      Json::object({ "customAction": id.to_json() })
    ActionData::Value(v) => Json::object({ "value": Json::string(v) })
    ActionData::NumericValue(v) => Json::object({ "numericValue": v.to_json() })
    ActionData::ScrollUnit(v) => Json::object({ "scrollUnit": v.to_json() })
    ActionData::ScrollHint(v) => Json::object({ "scrollHint": v.to_json() })
    ActionData::ScrollToPoint(v) =>
      Json::object({ "scrollToPoint": v.to_json() })
    ActionData::SetScrollOffset(v) =>
      Json::object({ "setScrollOffset": v.to_json() })
    ActionData::SetTextSelection(v) =>
      Json::object({ "setTextSelection": v.to_json() })
  }
}

///|
pub impl @json.FromJson for ActionData with from_json(json, path) {
  guard json is Object(obj) else {
    json_decode_error(path, "ActionData::from_json: expected object")
  }
  let mut found_key : String? = None
  let mut found_value : Json? = None
  for key, value in obj {
    if found_key is Some(_) {
      json_decode_error(
        path, "ActionData::from_json: expected single-key object",
      )
    }
    found_key = Some(key)
    found_value = Some(value)
  }
  guard found_key is Some(key) && found_value is Some(value) else {
    json_decode_error(path, "ActionData::from_json: expected single-key object")
  }
  let value_path = path.add_key(key)
  match key {
    "customAction" =>
      ActionData::CustomAction(@json.FromJson::from_json(value, value_path))
    "value" => ActionData::Value(@json.FromJson::from_json(value, value_path))
    "numericValue" =>
      ActionData::NumericValue(@json.FromJson::from_json(value, value_path))
    "scrollUnit" =>
      ActionData::ScrollUnit(@json.FromJson::from_json(value, value_path))
    "scrollHint" =>
      ActionData::ScrollHint(@json.FromJson::from_json(value, value_path))
    "scrollToPoint" =>
      ActionData::ScrollToPoint(@json.FromJson::from_json(value, value_path))
    "setScrollOffset" =>
      ActionData::SetScrollOffset(@json.FromJson::from_json(value, value_path))
    "setTextSelection" =>
      ActionData::SetTextSelection(@json.FromJson::from_json(value, value_path))
    _ => json_decode_error(path, "ActionData::from_json: unknown variant")
  }
}

///|
pub impl ToJson for ActionRequest with to_json(self : ActionRequest) -> Json {
  Json::object({
    "action": self.action.to_json(),
    "targetTree": self.target_tree.to_json(),
    "targetNode": self.target_node.to_json(),
    "data": match self.data {
      Some(v) => v.to_json()
      None => Json::null()
    },
  })
}

///|
pub impl @json.FromJson for ActionRequest with from_json(json, path) {
  guard json is Object(obj) else {
    json_decode_error(path, "ActionRequest::from_json: expected object")
  }
  for key, _ in obj {
    if key != "action" &&
      key != "targetTree" &&
      key != "targetNode" &&
      key != "data" {
      json_decode_error(path, "ActionRequest::from_json: unknown field \{key}")
    }
  }
  guard obj.get("action") is Some(action_json) else {
    json_decode_error(path, "ActionRequest::from_json: missing field action")
  }
  guard obj.get("targetTree") is Some(tree_json) else {
    json_decode_error(
      path, "ActionRequest::from_json: missing field targetTree",
    )
  }
  guard obj.get("targetNode") is Some(node_json) else {
    json_decode_error(
      path, "ActionRequest::from_json: missing field targetNode",
    )
  }
  guard obj.get("data") is Some(data_json) else {
    json_decode_error(path, "ActionRequest::from_json: missing field data")
  }
  let action : Action = @json.FromJson::from_json(
    action_json,
    path.add_key("action"),
  )
  let target_tree : TreeId = @json.FromJson::from_json(
    tree_json,
    path.add_key("targetTree"),
  )
  let target_node : NodeId = @json.FromJson::from_json(
    node_json,
    path.add_key("targetNode"),
  )
  let data : ActionData? = match data_json {
    Null => None
    _ => Some(@json.FromJson::from_json(data_json, path.add_key("data")))
  }
  let request = ActionRequest::new(action, target_tree, target_node)
  if data is Some(value) {
    request.data = Some(value)
  }
  request
}