fcf.module({
  name: "fcfControls:templates/menu.hooks.js",
  dependencies: [],
  module: function(){

    function findItem(a_item, a_uri){
      if (a_item.uri == a_uri)
        return a_item;
      for(let key in a_item.childs){
        let found = findItem(a_item.childs[key], a_uri);
        if (found)
          return found;
      }
    }

    function nodeToTree(a_node, a_extend, a_dynamic, a_visibleLevel, a_loadingDepth, a_level){
      a_level = a_level || 1;
      let extendItem   = a_extend ? findItem(a_extend, a_node.uri) : undefined;

      let root = {
        title:   a_node.title ? a_node.title : a_node.uri,
        uri:     a_node.uri,
        childs:  {},
        visible: a_level <= a_visibleLevel+1,
        end:     false,
        open:    a_level <= a_visibleLevel,
      };

      if (extendItem && "visible" in extendItem && extendItem.visible)
        root.visible = true;


      if (!extendItem)
        extendItem = a_extend;
      let extendChilds = extendItem ? extendItem.childs : {};
      fcf.each(extendChilds, (a_key, a_item)=>{
        a_item.visible = "visible" in a_item ? a_item.visible : true;
        a_item.open = a_level <= a_visibleLevel;
        a_item.noempty = !fcf.empty(a_item.childs)
      });

      if (extendItem && extendItem.current)
        root.current = true;

      fcf.each(a_node.childs, (a_key, a_child)=>{
        let subChild = extendChilds[a_key];
        let childInfo = nodeToTree(a_child, subChild, a_dynamic, a_visibleLevel, a_loadingDepth, a_level + 1);
        root.childs[a_key] = childInfo;
      })

      for(let key in extendChilds) {
        if (!root.childs[key])
          root.childs[key] = extendChilds[key];
      }

      root.noempty = !fcf.empty(root.childs);
      root.open    = fcf.first(root.childs) ? fcf.first(root.childs).visible : false;
      root.end     = a_loadingDepth < a_level;

      return root;
    }

    async function getCurrentTreeImpl(a_uri, a_dynamic, a_isRoot){
      let nodeInfo;
      try {
        nodeInfo = await fcf.application.getRouter().getMenuNode(a_uri, a_dynamic ? 2 : 1);
      } catch (e) {
        return undefined;
      }

      let result = undefined;

      let curItem = {
        title:   nodeInfo.title ? nodeInfo.title : nodeInfo.uri,
        uri:     nodeInfo.uri,
        childs:  {},
      };
      if (a_isRoot) {
        curItem.current = true;
      }
      fcf.each(nodeInfo.childsOrder, (a_key, a_alias)=>{
        let chlid = nodeInfo.childs[a_alias];
        curItem.childs[a_alias] = {
          title:   chlid.title ? chlid.title : chlid.uri,
          uri:     chlid.uri,
          childs:  {},
        }

        if (a_dynamic){
          fcf.each(nodeInfo.childs[a_alias].childsOrder, (a_key, a_subalias)=>{
            let subchild = nodeInfo.childs[a_alias].childs[a_subalias];
            curItem.childs[a_alias].childs[a_subalias] = {
              title:   subchild.title ? subchild.title : subchild.uri,
              uri:     subchild.uri,
              visible: false,
              childs:  {},
              end:     true,
            }
          });
        }
      });

      if (nodeInfo.parent !== undefined){
        let parentItem = nodeInfo.parent !== undefined ? await getCurrentTreeImpl(nodeInfo.parent, a_dynamic, false)
                                                       : undefined;
        if (parentItem) {
          parentItem.node.childs[nodeInfo.uri] = curItem;
          result = {
            node: curItem,
            root: parentItem.root
          };
        }
      }

      if (!result) {
        result = {
          node: curItem,
          root: curItem
        };
      }

      return result;
    }

    async function getCurrentTree(a_uri, a_dynamic){
      let info = await getCurrentTreeImpl(a_uri, a_dynamic, true);
      if (!info)
        throw new fcf.Exception("ERROR", {error: `Can't find menu node "${a_uri}"`});
      return info.root;
    }

    function getDepthTree(a_tree, a_depth){
      let depth   = !!a_depth ? a_depth : 1;
      let result  = depth;
      fcf.each(a_tree.childs, (a_key, a_item)=>{
        result = fcf.max(result, getDepthTree(a_item, depth + 1));
      });
      return result;
    }

    function treeToList(a_tree, a_enableRootElement, a_level, a_dst){
      if (!a_level)
        a_level = 1;
      if (!a_dst)
        a_dst = [];

      if (a_enableRootElement){
        a_dst.push({
          uri:      a_tree.uri,
          title:    a_tree.title,
          current:  !!a_tree.current,
          level:    a_level-1,
          visible:  true,
          open:     true,
          noempty:  false,

        });
      }

      for(let key in a_tree.childs) {
        a_dst.push({
          uri:      a_tree.childs[key].uri,
          title:    a_tree.childs[key].title,
          current:  !!a_tree.childs[key].current,
          level:    a_level,
          visible:  a_tree.childs[key].visible,
          open:     a_tree.childs[key].open,
          noempty:  a_tree.childs[key].noempty,
          end:      !!a_tree.childs[key].end,
        });
        treeToList(a_tree.childs[key], false, a_level + 1, a_dst);
      }

      return a_dst;
    }

    return {

      //
      // void hookBeforeBuild(a_taskInfo)
      // The hook is executed before assembling the template arguments
      //
      // hookBeforeBuild: function(a_taskInfo) {
      // },

      //
      // void hookAfterBuild(a_taskInfo)
      // The hook is executed after assembling the template arguments
      //
      // hookAfterBuild: function(a_taskInfo) {
      // },

      hookAfterRender: async (a_taskInfo)=>{
        let items = a_taskInfo.args.items;
        fcf.each(items, (a_key, a_item)=>{
          a_item.animation = false;
        });
        await a_taskInfo.setValue("items", items);
      },

      //
      // void hookAfterBuild(a_taskInfo)
      // The hook is executed after building the template's system arguments
      //
      // hookAfterSystemBuild: function(a_taskInfo) {
      // },

      //
      // Object of hooks for programmatically populated arguments
      //
      hooksProgramableArgument: {
        //
        // @result Returns the value of an argument or a Promise object
        //
        items: async(a_taskInfo)=>{
          if (a_taskInfo.args.items){
            fcf.each(a_taskInfo.args.items, (a_key, a_item)=>{
              a_item.current = a_taskInfo.args.current == a_item.uri;
            });
            return a_taskInfo.args.items;
          }
          let loadingDepth = fcf.max(a_taskInfo.args.loadingDepth, a_taskInfo.args.disclosure);
          let current   = await getCurrentTree(a_taskInfo.args.current, a_taskInfo.args.dynamic);
          if (!current)
            return [];
          let treeInfo  = await fcf.application.getRouter().getMenuNode(a_taskInfo.args.root, a_taskInfo.args.dynamic ? loadingDepth + 1 : loadingDepth, false);
          if (!treeInfo)
            return [];

          let tree      = nodeToTree( treeInfo,
                                      current,
                                      a_taskInfo.args.dynamic,
                                      a_taskInfo.args.disclosure,
                                      a_taskInfo.args.dynamic ? loadingDepth + 1 : loadingDepth
                                    );
          let items     = treeToList(tree, a_taskInfo.args.enableRoot);
          return items;
        }
      },

      //
      // Object of the hooks preprocessing of the template arguments
      //
      // hooksBeforeArgument: {
      //   //
      //   // @result Can return the value of an argument or Promise or undefined
      //   //
      //   "ARG_NAME": function(a_taskInfo) {
      //   }
      // },

      //
      // Object of the hooks postprocessing of the template arguments
      // hooksAfterArgument: {
      //   //
      //   // @result Can return the value of an argument or Promise or undefined
      //   //
      //   "ARG_NAME": function(a_taskInfo) {
      //   }
      // },
    };
  }
});
