Source: templates/If.js

import React, { useEffect, useMemo, useState } from "react";
import { useSelector } from "react-redux";
import { IfCondition } from "./IfCondition";
import { IfThen } from "./IfThen";
const objectPath = require("object-path");
/**
 * Componente que inicia un contexto de contenido condicional (if)
 * @name Reactor.Components.Templates.If
 * @param {string} variable Nombre de la variable (o path), para enviar valores absolutos (o parametros) comenzar con comilla simple la cadena, es este caso no se requiere context
 * @param {string} [context = "global"] Nombre del contexto a usar
 * @param {string} value Valor de la condicion
 * @param {LogicOperator} [operator = "="] Operador para comparar el valor con la variable
 * @param {VariableType} [type = "text"] Tipo de variable que se va a comparar (tambien se puede setear en condition)
 * @param {BooleanString} [debug = "false"] Si es true muestra en consola algunos datos de depuracion
 * @class 
 * @example 
<h6>if simple</h6>
<if variable="test_input" context="test_if" value="admin" operator="=" type="text">
    contenido condicional <span>que aparece</span> solo si la condicion se cumple y no tiene then ni else
</if>
<h6>if then else</h6>
<if variable="test_input" context="test_if" value="admin" operator="=" type="text">
  <then>
    contenido condicional
  </then>
  <else>
    contenido en caso contrario
  </else>
</if>
<h6>if con valor absoluto y parametro</h6>
<if variable="'{{rolUsuario}}">
  <condition value="admin">
    esto aparece si es admin <br/>
  </condition>
  <condition value="user">
    esto aparece si es user <br/>
  </condition>
  <else>
    si no se cumplen condiciones <br/>
  </else>
</if>
<h6>if conditions then else</h6>
<if variable="test_input" context="test_if">
  <condition value="admin">
    esto aparece si es admin <br/>
  </condition>
  <condition value="user">
    esto aparece si es user <br/>
  </condition>
  <then>
    esto aparece cuando se cumple alguna condicion <br/>
  </then>
  <else>
    esto aparece si no se cumplen condiciones <br/>
  </else>
</if>
<h6>if conditions else con numeros</h6>
<p>te convierte el tipo de la variable en numero </p>
<if variable="test_input" context="test_if" type="number">
  <condition value="0" operator="<">
    error el valor minimo el cero
  </condition>
  <condition value="0">
    no hay ninguno
  </condition>
  <condition value="1">
    es uno solo
  </condition>
  <condition value="5" operator="<=">
    son varios
  </condition>
  <condition value="5" operator=">">
    son muchos (mas de 5)
  </condition>
  <else>
    numero invalido
  </else>
</if>
<input_oqt change_var="test_input" context="test_if" var-value="test_input" placeholder="escribi admin, user, 0, 1, 5, 6 o cualquier otra cosa"/>
*/

export const If = ({variable, context = "global", children, type = "text", operator = "=", value, debug = "false"}) => {
  
  const hookIf = useIf({variable, context, defaultType: type, debug: debug === "true"});
  const elseOrConditions = useMemo(()=>{
    if(debug === "true"){
      console.log(children);
    }
    return children.some(c => c && typeof c === "object" && (c.type?.name === "IfElse" || c.type?.name === "IfCondition"))
  }, [children]);

  useEffect(()=>{
    if(debug==="true"){
      console.log("if properties", {variable, context, children, type, operator, value, debug});
    }
  }, [])
  
  return (
    <IfContext.Provider value={hookIf}>
      {value
        ?<>
          {elseOrConditions
            ? <><IfCondition {...{operator, type, value}}></IfCondition> {children}</>
            : <IfCondition {...{operator, type, value}}>{children}</IfCondition>
          }
        </>
        : children
      }
    </IfContext.Provider>
  )
}

const useIf = ({
  variable, 
  context = "global", 
  defaultType = "text",
  debug
}) => {

  const [conditions, setConditions] = useState([]);
  const [matchs, setMatchs] = useState([]);
  const [match, setMatch] = useState(null);
  const varsContext = useSelector(state => state.app.variables[context]);

  const varValue = useMemo(() => {
    if(variable.startsWith("'")){
      return variable.substring(1);
    }
    return objectPath.get(varsContext, variable) 
  }, [variable, varsContext]);

  useEffect(()=>{
    if(debug){
      console.log("if refresh variable", {context, varsContext, variable})
    }    
    //try{
      const newMatchs = conditions
        .map((c, index) => {
          return evalMatch({value: evalValue({type: c.type, value: evalPath(c.path)}), operator: c.operator, propValue: c.value})
            ? index 
            : null
        }).filter(c => c !== null);
      if(debug){
        console.log("if refresh matchs", {matchs: newMatchs, conditions, varValue})
      }      
      const newMatch = (!newMatchs || newMatchs.length === 0)
        ? null 
        : conditions[[...newMatchs].sort()[0]].guid;
      if(debug){
        console.log("if refresh match", {matchs: newMatch, matchs: newMatchs, conditions, varValue})
      }      
      setMatchs(newMatchs);
      setMatch(newMatch);

    /*}catch(e){
      console.log(e);
    }*/
  }, [variable, context, defaultType, conditions, varValue]);

  const addCondition = ({guid}) => {
    if(debug){
      console.log("if addCondition", {item: {guid}, conditions})
    }
    setConditions(conditions => [...conditions, {guid}]);
  };

  const updateCondition = ({guid, value, operator, path, type}) => {
    if(debug){
      console.log("if updateCondition", {item: {guid, value, operator, path, type}, conditions})
    }
    setConditions(conditions => conditions.map(c => c.guid === guid ? {...c, value, operator, path, type} : c));
  };
  
  const removeCondition = ({guid, value}) => {
    if(debug){
      console.log("if removeCondition", {item: {guid}, conditions})
    }
    setConditions(conditions => conditions.filter(c => c.guid !== guid));
  };


  const evalValue = ({type, value}) => {
    switch (type || defaultType) {
      case "text":
        return `${value}`;
      case "number":
        try{
          return parseFloat(value);
        }catch(e){
          try{
            return parseInt(value);
          }catch(e){
            return 0;
          }
        };
      case "boolean":
        return value === "true" || value === "1" || value === true;
      default: 
        return null;
    }
  }

  const evalMatch = ({value, operator, propValue}) => {
    if(debug){
      console.log("if evalMatch", {value, operator, propValue})
    }  
    switch (operator) {
      case "=":
        return propValue === value;
      case "!=":
        return propValue !== value;
      case ">":
        return value > propValue;
      case "<":
        return value < propValue;
      case ">=":
        return value >= propValue;
      case "<=":
        return value <= propValue;
      case "startsWith":
        return `${value}`.startsWith(propValue);
      case "endsWith":
        return `${value}`.endsWith(propValue);
      case "??":
        return value === null || value === undefined;
      case "!!":
        return value !== null && value !== undefined;
      case "?":
        return !value;
      case "!":
        return !!value;      
      default:
        return false;
    }
  }

  const evalPath = (path) => {
    return typeof varValue === "object" ? objectPath.get(varValue, path) : varValue;
  }

  return {match, addCondition, removeCondition, updateCondition, evalValue};

}

const defIf = {
  match: "", 
  addCondition: ({guid, value}) => {}, 
  removeCondition: ({guid, value}) => {}, 
  updateCondition: ({guid, value, operator, path}) => {}, 
  evalValue: ({type, value}) => null,
}
export const IfContext = React.createContext(defIf);