/*eslint no-undef:0*/
/*eslint-env browser*/

"use strict";
var apx = apx || {};
console.log("apx");
/**************************************************************************
 * apx.js manage data to interact with an apxtri instance from a webpage *
 *************************************************************************
 * This code is not minify and target to be understood by a maximum of 
 * curious people to audit and give any feedback to support@ndda.fr 
 * 
 * To audit it in a browser like chrome:
 *  - open web developpement (F12)
 *  - Menu Element auth_lg.html (2 script manage screen shown and run
 *  apx.authentifyme that is the function to manage authentification
 *  into headers)
 *  - Menu Sources find apx.js all is their, see below for more information
 *  
 * 
 * Main principle:
 * - Usage of localStorage to store any usefull data and save it between session
 * - All data and template can be download at loading page (to allow PWA)
 * - Only usage of crypto is allow in this script if you find any call to openpgp outside of this clear file, the owner of a signed app will loose his stake crypto. See manifesto rules in /nationchains/nations/conf.js 
 * 
 * ------------------
 * ? State management
 * ------------------
 * html page must have apxtri with at least headers key
 * After authentification it goes back to pagename of the same language with wallet_{lg}.html#url=source pagename

    <script>
      const apxtri={
       version:0, // 0 mean it is a first visit if you force to 0 then it will reload all app data
       pagename:"pagename", // without _lg.html ex:pageadmin
       pageauth:"pagename with authentification ",ex:wallet to be call if need authentification
       allowaedprofils:['druids'],  // list of profil's xtribe allowed to access this page
       headers:{
        xalias:"anonymous",
        xhash:"anonymous",
        xtribe:"tribeId",
        xapp:"app name",
        xlang:"en" }
       }
    } ;
    </script>   
 * ++++++++   
 * ? apx.ready(callback) await DOM is ready equivalent of jquery Document.ready()
 * +++++++++  
 * ? apx.save() save apx.data as xapp value in localStorage
 * +++++++++
 * ? apx.update() :
 * - get localStorage up to date get from any tag tolocalin
 *    - tpl  (template)      : {name:text template with {{data.key}}}
 *    - ref  (referential)   : {name:{keyword:text in html.lang"}
 *    - data (to provide tpl): {name:{key:text in html.lang or apx.data.headers.xlang}}  
 *   in html:<div tolocalin="tpl|ref|data|..." name="storagename" url="relative or absolute"></div>
 * 
 *   example: <div tolocalin="ref" url="nationchains/models/Pagans_fr.json" name="Pagans" ></div>
 *
 * - manage route with url/page.html?action=availablefunction like object.load
 *     This will open page.html then run object.load function that have to exist
 * ++++++++++
 * ? apx.loadfile({url:name},dest,axiosoptions) 
 * async that wait to load a list of relativ url and store it in a apx.data[dest] 
 * ++++++++++
 * 
 * --------------------- 
 * ? Identity management
 * ---------------------
 * An identity is store in apx.data.auth this is only available for enduser
 * Any code that is identify to collect privatekey will be ban under rule1. 
 * 
 * apx.generateKey(alias, passphrase) => {publickey,privatekey} with userIds = [{alias}]
 * 
 * apx.detachedSignature (pubK, privK, passphrase, message) => return a hash to 
 * authentify message from a publicK (alias)
 * 
 * apx.authenticatedetachedSignature(alias,pubK,detachedSignature,message) => true/false
 *  if message is coming from publickey
 * 
 * apx.createIdentity(alias, passphrase) => manage creation process of an identity from 
 * html cdn/lib/apx/src/components/identity/identity.mustache
 * 
 * apx.checkauthorization(profil) => this will test if profil exist in your xprofils after runing a isauth on the backend. If not the case it redirects to apxtri.pageauth
 *  
 * @TODO apx.cipherdata(alias, passphrase,privatekey, message) => ciphermessage 
 *
 * apx.authentifyme(alias, passphrase, privatekey, publickeycreate, redirecttrue) =>
 *   if true, redirect to redirectiontrue (do nothng if empty)
 *   if false, show auth.html page on signin
 */

apx.ready = (callback) => {
  /**
   * Wait loading DOM and apx.update (await 10 times) for localstorage from backend, when ready run callback function
   */
  if (!callback) {
    alert(
      "You have an unknown callback pwa.state.ready(callback), you need to order your code callback = ()=>{} then pwa.state.ready(callback), boring but js rules ;-)"
    );
  }
  if (document.readyState != "loading") callback();
  // modern browsers
  else if (document.addEventListener)
    document.addEventListener("DOMContentLoaded", callback);
  // IE <= 8
  else
    document.attachEvent("onreadystatechange", function () {
      if (document.readyState == "complete") callback();
    });
};
apx.readyafterupdate=(callback)=>{
  if(!apx.afterupdate) apx.afterupdate=[];
  apx.afterupdate.push(callback)
}
apx.loadfile = async (list, dest, axiosoptions) => {
  /**
   * Load external file as tpl, tpldata (temmplate and data), objects index of object
   * @param {object} list {name:url} it will run as promise all the requested url
   * @param {string} dest where to store it in apx.data (tpl, tpldata, objectname,...)
   * @param {object} axiosoptions alow to add headers any option to axios
   * @return {object} load into apx.data[dest]={name:{object}} and localStorage.xapp[dest]={name:{object}}
   *
   * @example <caption>Load into apx.data.tpl.blocncol ="string"</caption>
   * apx.loadfile({blocncol:"static/tpl/blocncol.mustache"},"tpl",{headers:apx.data.header})
   *
   */
  const invertlist = {};
  Object.keys(list).forEach((n) => {
    invertlist[list[n]] = n;
  });
  //console.log(list)
  //console.log( 'invertlist', invertlist )

  if (
    apx.data.headers.forcereload ||
    !apx.data[dest] ||
    Object.keys(apx.data[dest]).length == 0
  ) {
    if (!apx.data[dest]) apx.data[dest] = {};
    const fetchURL = (r, options) => {
      return axios.get(r, options);
    };
    const urls = Object.keys(invertlist);
    if (!axiosoptions) {
      // force no cache browser
      apx.data.headers["Cache-Control"] = "no-cache";
      apx.data.headers["Pragma"] = "no-cache";
      apx.data.headers["Expires"] = 0;
      axiosoptions = { headers: apx.data.headers };
    }
    console.log("axiosoptions", axiosoptions);
    const promiseArray = urls.map((r) => fetchURL(r, axiosoptions));
    await Promise.all(promiseArray)
      .then((data) => {
        console.log(data);
        for (let pos = 0; pos < urls.length; pos++) {
          console.log("url", urls[pos]);
          console.log(data[pos]);
          let content = data[pos].data;
          if (
            data[pos].data.status &&
            data[pos].data.status == 200 &&
            data[pos].data.ref
          ) {
            // case of api feedback we get only data
            content = data[pos].data.data;
          }
          apx.data[dest][invertlist[urls[pos]]] = content;
          console.log(
            `store apx.data[ ${dest} ][ invertlist[ ${urls[pos]} ] ]`
          );
          apx.save();
        }
      })
      .catch((err) => {
        console.log("erreur de loading", err);
      });
  }
};
apx.generateKey = async (alias, passphrase) => {
  /**
   * @param {string} alias a unique alias that identify an identity
   * @param {string} passphrase a string to cipher the publickey (can be empty, less secure but simpler)
   * @return {publickey,privatekey} with userIds = [{alias}]
   */
  const pgpparam = {
    type: "ecc", // Type of the key, defaults to ECC
    curve: "curve25519", // ECC curve name, defaults to curve25519
    userIDs: [{ alias: alias }], // you can pass multiple user IDs
    passphrase: passphrase, // protects the private key
    format: "armored", // output key format, defaults to 'armored' (options: 'armored', 'binary' or 'object')
  };
  const { privateKey, publicKey } = await openpgp.generateKey(pgpparam);
  // key start by '-----BEGIN PGP PRIVATE KEY BLOCK ... '
  // get liste  of alias:pubklickey await axios.get('api/v0/pagans')
  // check alias does not exist
  return { alias, privatekey: privateKey, publickey: publicKey };
};
apx.detachedSignature = async (privK, passphrase, message) => {
  /**
   * @privK {string} a test priv key
   * @passphrase {string} used to read privK
   * @message {string} message to sign
   * @Return a detached Signature of the message
   */
  let privatekey;
  if (passphrase == "" || passphrase == undefined) {
    privatekey = await openpgp.readKey({ armoredKey: privK });
  } else {
    privatekey = await openpgp.decryptKey({
      privateKey: await openpgp.readPrivateKey({ armoredKey: privK }),
      passphrase,
    });
  }
  //console.log(message);
  const msg = await openpgp.createMessage({ text: message });
  //console.log(msg);
  const sig = await openpgp.sign({
    message: msg,
    signingKeys: privatekey,
    detached: true,
  });
  return btoa(sig);
};
apx.clearmsgSignature = async (privK, passphrase, message) => {
  /**
   * @privK {string} a test priv key
   * @passphrase {string} used to read privK
   * @message {string} message to sign
   * @Return an base64 Signature of the message
   */
  let privatekey;
  if (passphrase == "" || passphrase == undefined) {
    privatekey = await openpgp.readKey({ armoredKey: privK });
  } else {
    privatekey = await openpgp.decryptKey({
      privateKey: await openpgp.readPrivateKey({ armoredKey: privK }),
      passphrase,
    });
  }
  const unsignedMessage = await openpgp.createCleartextMessage({
    text: message,
  });
  const cleartextMessage = await openpgp.sign({
    message: unsignedMessage,
    signingKeys: privatekey,
  });
  console.log(cleartextMessage); // '-----BEGIN PGP SIGNED MESSAGE ... END PGP SIGNATURE-----'

  return btoa(cleartextMessage);
};
apx.authenticatedetachedSignature = async (
  alias,
  pubK,
  detachedSignature,
  message
) => {
  /**
   *  Check that alias (pubkey) signe a message
   *  @alias {string} alias link to the publickey
   *  @pubK {string} publiKey text format
   *  @detachedSignature  {string} a detachedsignatured get from apx.detachedSignature
   *  @message {string} the message signed
   *  @return {boolean} true the message was signed by alias
   *                    false the message was not signed by alias
   */
  const publickey = await openpgp.readKey({ armoredKey: pubK });
  const msg = await openpgp.createMessage({ text: message });
  const signature = await openpgp.readSignature({
    armoredSignature: atob(detachedSignature), // parse detached signature
  });
  const verificationResult = await openpgp.verify({
    msg, // Message object
    signature,
    verificationKeys: publickey,
  });
  const { verified, keyID } = verificationResult.signatures[0];
  try {
    await verified; // throws on invalid signature
    console.log("Signed by key id " + keyID.toHex());
    return KeyId.toHex().alias == alias;
  } catch (e) {
    console.log("Signature could not be verified: " + e.message);
    return false;
  }
};
apx.createIdentity = async (alias, passphrase = "") => {
  // A mettre dans identity
  const aliasregex = /^[a-z0-9]*$/;
  console.log(aliasregex.test(alias));
  if (!(alias && alias.length > 3 && aliasregex.test(alias))) {
    apx.notification("#createidentity .msginfo", {
      status: "406",
      ref: "Pagans",
      msg: "invalidalias",
      data: {},
    });
    return false;
  }
  document.querySelector("#createidentity .msginfo").innerHTML = "";
  axios
    .get(`/api/apxtri/pagans/alias/${alias}`, {
      headers: apx.data.headers,
    })
    .then((rep) => {
      console.log(rep);
      apx.notification("#createidentity .msginfo", {
        ref: "Pagans",
        msg: "aliasexist",
        data: { alias },
      });
    })
    .catch(async (err) => {
      console.log("checkalias:", err);
      if (err.response && err.response.status == 404) {
        // alias does not exist create it is possible
        const keys = await apx.generateKey(alias, passphrase);
        apx.data.tmp = keys; // to make it available for btn download
        apx.save();
        apx.identity.setkeytrustdomain(keys);
      } else {
        apx.notification("#createidentity .msginfo", {
          ref: "middlewares",
          msg: "errrequest",
          data: {},
        });
      }
    });
};

/**
 *
 * @param {string} alias to create
 * @param {string} publickey
 * @param {string} trustedtribe if none => means no passphrase, no privatekey, no trustedtribe
 * @param {string} passphrase
 * @param {string} privatekey
 * @param {string} email  if none => means no passphrase, no privatekey, no trustedtribe
 *
 * if email!=none and trustedtribe!= none create a person with parson profil in trustedtribe
 * if email!=none and trustedtribe==none then send an email at registration with all element but doi not store in backend for futur recovery
 *
 */
apx.registerIdentity = async (
  alias,
  publickey,
  trustedtribe,
  passphrase,
  privatekey,
  email
) => {
  const setheaders = await apx.setheadersauth(
    alias,
    passphrase,
    publickey,
    privatekey
  );
  if (setheaders.status != 200) {
    apx.notification("#createidentity .msginfo", setheaders);
  } else {
    // add withpublickeyforcreate to check isAuthenticated alias does not already exist
    const data = {};
    data.alias = alias;
    data.publickey = publickey;
    if (email != "none") {
      data.passphrase = passphrase;
      data.privatekey = privatekey;
      data.email = email;
    }
    if (trustedtribe != "none") {
      data.trustedtribe = trustedtribe;
    }
    axios
      .post(`/api/apxtri/pagans`, data, { headers: apx.data.headers })
      .then((reppagan) => {
        console.log(reppagan.data);
        apx.notification("#createidentity .msginfo", reppagan.data);
        document.querySelector("#createId button").classList.add("d-none");
        //remove tmp cause created
        delete apx.data.tmp;
        apx.save();
      })
      .catch((err) => {
        console.log("sorry", err);
        if (err.response && err.response.data)
          apx.notification("#createidentity .msginfo", err.response.data);
        else
          apx.notification("#createidentity .msginfo", {
            status: 500,
            ref: "Pagans",
            msg: "errcreate",
            data: {},
          });
      });
  }
};
apx.jointribe = () => {
  /**
   * Allow a pagan to register as a person into a tribe
   * header must be authenticated with alias into an app belonging to xtribe AND schema person must have apxaccessright with role "pagan": {"C": []}
   */
  console.log(apx.data);
  if (!apx.data.headers.xprofils.includes("persons")) {
    apx.data.headers.xprofils.push("persons");
  }
  const data = {
    alias: apx.data.headers.xalias,
    profils: apx.data.headers.xprofils,
  };
  axios
    .put(`/api/apxtri/pagans/person/${apx.data.headers.xtribe}`, data, {
      headers: apx.data.headers,
    })
    .then((rep) => {
      apx.notification("#logout .msginfo", rep.data);
      axios
        .get(`/api/apxtri/pagans/logout`, {
          headers: apx.data.headers,
        })
        .then((rep) => {
          console.log("logout", rep);
          apx.authentifyme(
            apx.data.auth.alias,
            apx.data.auth.passphrase,
            apx.data.auth.privatekey
          );
        })
        .catch((err) => {
          console.log("Erreur logout check:", err);
        });
    })
    .catch((err) => {
      console.log("sorry", err);
      if (err.response && err.response.data)
        apx.notification("#logout .msginfo", err.response.data);
      else
        apx.notification("#logout .msginfo", {
          status: 500,
          ref: "Pagans",
          msg: "errcreate",
          data: {},
        });
    });
};
apx.setheadersauth = async (alias, passphrase, publickey, privatekey) => {
  /**
   * Set header with relevant authentification data
   * @return {status=200 if apx.data.headers and apx.data.auth properly set}
   *         {status: 406 or 500 in case issue}
   */
  console.log(alias, passphrase, publickey, privatekey);
  if (
    alias.length < 3 ||
    publickey.length < 200 ||
    (privatekey && privatekey.lengtht < 200)
  ) {
    return {
      status: 406,
      ref: "Pagans",
      msg: "aliasorprivkeytooshort",
      data: {},
    };
  }
  if (!passphrase) passphrase = "";
  apx.data.auth = {
    alias: alias,
    publickey: publickey,
    privatekey: privatekey,
    passphrase: passphrase,
  };
  apx.data.headers.xalias = alias;
  apx.data.headers.xdays = dayjs().valueOf();
  const msg = `${alias}_${apx.data.headers.xdays}`;
  console.log("pvk", privatekey);
  try {
    apx.data.headers.xhash = await apx.clearmsgSignature(
      privatekey,
      passphrase,
      msg
    );
  } catch (err) {
    return {
      status: 500,
      ref: "middlewares",
      msg: "unconsistentpgp",
      data: { err: err },
    };
  }
  apx.save();
  console.log("xhash set with:", apx.data.headers.xhash);
  return { status: 200 };
};
apx.authentifyme = async (alias, passphrase, privatekey) => {
  /**
   * Set apx.data.auth with pub, priv, passphrase alias that allow authentification
   * set headers with xdays (timestamp) and xhash of message: {alias}_{timestamp} generate with pub & priv key
   *
   * @Param {key} publickeycreate  optional when alias does not exist
   */
  console.log(alias, passphrase);
  console.log(privatekey);
  console.log(`/api/apxtri/pagans/alias/${alias}`);
  //clean previous answer if exist
  document.querySelector("#signin .msginfo").innerHTML = "";
  if (alias.length < 3 || privatekey.length < 200) {
    apx.notification("#signin .msginfo", {
      status: 500,
      ref: "Pagans",
      msg: "aliasorprivkeytooshort",
      data: {},
    });
    return false;
  }
  axios
    .get(`/api/apxtri/pagans/alias/${alias}`, {
      headers: apx.data.headers,
    })
    .then(async (rep) => {
      console.log(rep.data);
      const setheaders = await apx.setheadersauth(
        alias,
        passphrase,
        rep.data.data.publickey,
        privatekey
      );
      if (setheaders.status != 200) {
        apx.notification("#signin .msginfo", setheaders);
      } else {
        console.log("SetheadersOK");
        console.log(`/api/apxtri/pagans/isauth`);
        axios
          .get(`/api/apxtri/pagans/isauth`, {
            headers: apx.data.headers,
          })
          .then((rep) => {
            // Authenticate then store profils in header
            // if apx.data.pageifauth redirect automaticaly to this url
            // if apx.data.pageifauthclick then allow to stay on aut_lg.html to logout and create other identity
            apx.data.headers.xprofils = rep.data.data.xprofils;
            console.log(apx.data.headers.xalias);
            apx.save();
            console.log(apx.data.headers.xalias);
            if (apx.identity.identityswitchscreen) {
              apx.identity.identityswitchscreen("logout");
            }
            /* alert(localStorage.getItem('pageifauth'))
            if (localStorage.getItem('pageifauth')) {
            window.location.href = `${localStorage.getItem('pageifauth')}`;
            }*/
            if (apx.pagecontext.hash.url) {
              window.location.href = `${apx.pagecontext.hash.url}`;
            }
          })
          .catch((err) => {
            console.log("check auth", err);
            delete apx.data.auth;
            apx.save();
            if (apx.identity.identityswitchscreen) {
              apx.identity.identitycheckscreen("signin");
              if (err.response) {
                apx.notification("#signin .msginfo", err.response.data);
              } else if (err.request) {
                apx.notification("#signin .msginfo", {
                  status: 500,
                  ref: "middlewares",
                  msg: "errrequest",
                  data: { err: err.request.response },
                });
              }
            }
          });
      }
    })
    .catch((err) => {
      console.log(err.response);
      console.log(err.request);
      console.log("checkalias:", err);
      if (err.response && err.response.data.msg) {
        //remove auth if not well created previously
        console.log(err.response.data.msg);
        if (err.response.data.msg == "aliasdoesnotexist") {
          delete apx.data.auth;
          apx.save();
          window.location.reload();
        }
        apx.notification("#signin .msginfo", err.response.data);
      } else {
        apx.notification("#signin .msginfo", {
          status: 500,
          ref: "middlewares",
          msg: "errrequest",
          data: { err },
        });
      }
    });
};
apx.notification = (selector, data) => {
  /**
   * @selector a text to use querySelector() in document
   * @data apxtri return from any request {status:200,ref:"",msg:"key", data }
   *   if {status,multimsg:[{ref,msg,data}]} then a multi feedback are in
   *
   * @return update the dom selctor with the relevant render message in the relevant language
   */
  const el = document.querySelector(selector);
  if (!el) {
    console.log(
      `WARNING !!! check apx.notification selector:${selector} does not exist in this page`
    );
    return false;
  }
  const multimsg = data.multimsg ? data.multimsg : [data];
  multimsg.forEach((info) => {
    if (!apx.data.ref[info.ref]) {
      console.log(
        `check apx.data.ref, ${info.ref} does not exist in this page, add it `
      );
      return false;
    } else if (!apx.data.ref[info.ref][info.msg]) {
      console.log(
        `check apx.data.ref.${info.ref} does not contain ${info.msg} update /schema/lg or /model/lg`
      );
      return false;
    }
    el.innerHTML +=
      " " + Mustache.render(apx.data.ref[info.ref][info.msg], info.data);
    if (data.status == 200) {
      el.classList.remove("text-danger");
      el.classList.add("text-success");
    } else {
      el.classList.add("text-danger");
      el.classList.remove("text-success");
    }
  });
};

/**
 * logout
 * Clean any private key into memory of this app
 */
apx.logout = () => {
  delete apx.data.auth;
  delete apx.data.objects;
  axios
    .get(`/api/apxtri/pagans/logout`, {
      headers: apx.data.headers,
    })
    .then((rep) => {
      console.log("logout", rep);
    })
    .catch((err) => {
      console.log("Erreur logout check:", err);
    });
  apx.data.headers = apxtri.headers;
  apx.save();
  location.reload();
};
/**
 * delete person
 * Remove person item fot his tribe, need to be the owner or have acessright D on person
 */
apx.deleteIdentity = () => {
  axios
    .delete(
      `/api/apxtri/pagans/person/${apx.data.headers.xtribe}/${apx.data.headers.xalias}`,
      {
        headers: apx.data.headers,
      }
    )
    .then((rep) => {
      //apx.notification("#logout .msginfo",rep.data)
      apx.logout();
    })
    .catch((err) => {
      console.log("sorry", err);
      if (err.response && err.response.data)
        apx.notification("#logout .msginfo", err.response.data);
      else
        apx.notification("#logout .msginfo", {
          status: 500,
          ref: "Odmdb",
          msg: "errordelete",
          data: {},
        });
    });
};

/**
 * @param {object} a json object
 * @param {pathcontent} a path in object to store content as text
 * @param {prefixcontent} a path in object to get a prefix name of file
 *
 * Create a link that download subobject as a textfile
 * example:
 *  const object={tmp:{alias:"toto",privatekey:"123", publickey:"AZE"}}
 *  const prefixcontent = "tmp.alias"
 *  const pathcontent= "tmp.privatekey"
 *
 * in <html>
 *  <button class="btn btn-outline-primary" onclick="app.downloadlink(object,'tmp.privatekey','tmp.alias);" >Download PrivateKey</button>
 *
 * will store in local download folder: /toto.privatekey.txt with content of object.tmp.privatekey as text
 */
apx.downloadlink = (object, pathcontent, prefixcontent) => {
  const dwn = (fn, t) => {
    var element = document.createElement("a");
    element.setAttribute(
      "href",
      "data:text/plain;charset=utf-8," + encodeURIComponent(t)
    );
    element.setAttribute("download", fn);
    element.style.display = "none";
    document.body.appendChild(element);
    element.click();
    document.body.removeChild(element);
  };
  const getval = (obj, path) => {
    const subobj = path.split(".").reduce((val, k) => (val = val[k]), obj);
    return typeof subobj == "string" ? subobj : JSON.stringify(subobj);
  };
  dwn(
    `${getval(object, prefixcontent)}_${pathcontent.split(".").pop()}.txt`,
    getval(object, pathcontent)
  );
};
/**
 * Manage form data posting to api per tribe without Odmbd manager
 *
 * @param {object} eltbtn a dom element button that call this function
 * @param {object} formdata at least route to /api/{apx.data.headers.xtribe}/{route}{anonymous or not) src to identify from where
 * @param {object} cb callback 2 functions 1 object
 *        cb.checktype = {type:/regex/} liste of type to evaluate by a regex
 *        cb.feedback(elt,isvalid) elt is the button clicked isvalid a bool to action data sent ok or not
 *        cb.invalidfield(elt) input elt to call in case of an input invalidity
 * @returns check form, send data, update interface with callback
 */
apx.sendform = async (eltbtn, formdata, cb) => {
  let formvalid = true;
  if (!cb.feedback || !cb.invalidfield) {
    alert(
      "For dev: It miss function cb.feedback(id,boolean) to act if form was successfully sent or not (boolean) and/or cb.invalidfield(elt) that act if a field is invalid into an element"
    );
    return false;
  }
  console.log(formdata);
  if (!formdata.route || !formdata.order || !formdata.srckey) {
    let keylst;
    Object.keys(formata).forEach((k) => (keylst += " k"));
    alert(
      "For dev: it miss route or order or srckey in formdata of the form, must have it to run Actions. check apx.sendform: " +
        keylst
    );
    return false;
  }
  const checktype = {
    email:
      /^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/,
    telephone: /^0[1-9][0-9]{9}$/,
  };
  if (cb.checktype) Object.assign(checktype, cb.checktype);
  eltbtn
    .closest(".contactform")
    .querySelectorAll("input,textarea,option:checked")
    .forEach((e) => {
      if (!e.name) e.name = info.getAttribute("name");
      formvalid =
        e.required && e.value.length == "" ? cb.invalidfield(e) : formvalid;
      Object.keys(checktype).forEach((t) => {
        formvalid =
          e.type == t && !checktype[t].test(e.value)
            ? cb.invalidfield(e)
            : formvalid;
      });
      if (!formdata[e.name]) {
        formdata[e.name] = e.value;
      } else {
        // array case multi input with same name
        formdata[e.name] = [formadat[e.name]];
        formdata[e.name].push(e.value);
      }
    });
  // if authenticate then api/tribes/action

  let url = `/api/apxtri/notifications/${apx.data.headers.xtribe}/${formdata.route}`;
  url += apx.data.headers.xalias == "anonymous" ? "anonymous" : "";
  if (formvalid) {
    console.log(eltbtn);
    const datasent = await axios.post(url, formdata, {
      headers: apx.data.headers,
    });
    cb.feedback(eltbtn, datasent.status == 200);
  }
};
/**
 * Manage tracking base on consentcookie value in localStorage
 * By apx.track() => Set a uuid first time then hit a firstpres then if 6hours elapse hit a visitpres
 *
 * Load dynamicaly any <img data-trksrc> on a lazy loading that track it
 * Add event click on a or btn with data-trksrc apx.track('trkcodename')"
 *
 */
apx.trackvisit = (srckey = "visitpres") => {
  const createuuid = () => {
    const uuid = ([1e7] + -1e3 + -4e3 + -8e3 + -1e11).replace(/[018]/g, (c) =>
      (
        c ^
        (crypto.getRandomValues(new Uint8Array(1))[0] & (15 >> (c / 4)))
      ).toString(16)
    );
    console.log(
      `firstpres /trk/cdn/log/empty.json?alias=anonymous&uuid=${uuid}&srckey=firstpres&version=${apx.data.headers.xtrkversion}`
    );
    axios.get(
      `/trk/cdn/log/empty.json?alias=anonymous&uuid=${uuid}&srckey=firstpres&version=${apx.data.headers.xtrkversion}&lg=${apx.data.headers.xlang}&consentcookie=unknown`,
      { headers: apx.data.headers }
    );
    localStorage.setItem("xuuid", uuid);
    return uuid;
  };
  try {
    apx.consentcookie.check();
  } catch (e) {
    console.log("No apx.consentcookie");
  }
  apx.data.headers.xuuid = localStorage.getItem("xuuid") ?? createuuid();
  apx.save();
  let lasttm = dayjs().unix();
  if (localStorage.getItem("lasttm")) {
    lasttm = localStorage.getItem("lasttm");
  } else {
    localStorage.setItem("lasttm", lasttm);
  }
  //const lasttm = localStorage.getItem("lasttm") ?? dayjs().unix();
  console.log(lasttm, srckey, dayjs().unix());
  console.log(parseInt(dayjs().unix()) - parseInt(lasttm));
  console.log(apxtri.headers.xalias);
  // trk new visit last 6hours :21600000  or 2hours:7200000
  if (srckey != "visitpres" || dayjs().unix() - parseInt(lasttm) > 21600000) {
    const trackhit = `/trk/cdn/log/empty.json?alias=${
      apx.data.headers.xalias
    }&uuid=${
      apx.data.headers.xuuid
    }&srckey=${srckey}&consentcookie=${localStorage.getItem(
      "consentcookie"
    )}&version=${apx.data.headers.xtrkversion}&lg=${apx.data.headers.xlang}`;

    console.log("trackhit ", trackhit);
    axios
      .get(trackhit, { headers: apx.data.headers })
      .then((rep) => console.log("trkok", rep))
      .catch((err) => console.log("trkko", err));
    localStorage.setItem("lasttm", dayjs().unix());
  }
};
apx.lazyload = () => {
  document.querySelectorAll("img[data-lazysrc]").forEach((e) => {
    //  in src/page.html:  src contain original img and data-lazysrc='true'
    //  in dist/page.html src is removed img src is in data-lazysrc=newimage.webp or svg
    let src = e.getAttribute("src")
      ? e.getAttribute("src")
      : e.getAttribute("data-lazysrc");
    if (e.getAttribute("data-trksrckey")) {
      src = `/trk/${src}?alias=${apx.data.headers.xalias}&uuid=${
        apx.data.headers.xuuid
      }&srckey=${e.getAttribute("data-trksrckey")}&version=${
        apx.data.headers.xtrkversion
      }&consentcookie=${localStorage.getItem("consentcookie")}&lg=${
        apx.data.headers.xlang
      }`;
    }
    e.setAttribute("src", src);
    console.log("lazyload track:", src);
    e.removeAttribute("data-lazysrc");
  });
  document.querySelectorAll("[data-lazybgsrc]").forEach((e) => {
    e.style.backgroundImage = `url(${e.getAttribute("src")})`;
    e.removeAttribute("data-lazybgsrc");
  });
  document.querySelectorAll("a[data-trksrckey]").forEach((e) => {
    let urldestin = e.getAttribute("href");
    if (
      urldestin.substring(0, 4) != "http" &&
      urldestin.substring(0, 1) != "/"
    ) {
      urldestin = "/" + urldestin;
    }
    const hreftrack = `/trk/redirect?alias=${apx.data.headers.xalias}&uuid=${
      apx.data.headers.xuuid
    }&srckey=${e.getAttribute("data-trksrckey")}&version=${
      apx.data.headers.xtrkversion
    }&consentcookie=${localStorage.getItem("consentcookie")}&lg=${
      apx.data.headers.xlang
    }&url=${urldestin}`;
    console.log("href track:", hreftrack);
    e.setAttribute("href", hreftrack);
    e.removeAttribute("data-trksrckey");
  });
};
// State management
apx.save = () => {
  localStorage.setItem(apx.data.headers.xapp, JSON.stringify(apx.data));
};
apx.update = async () => {
  if (!apxtri) {
    console.log(
      'Please add to the html page header, this line  <script> const apxtri={headers:{xalias:"",xhash:"",xtribe:"smatchit", xapp:"smatchapp", xlang:"en" }};</script> '
    );
    return;
  }
  //if (apxtri.forcereload){localStorage.setItem("forcereload",true)};
  if (document.querySelector("html").getAttribute("lang")) {
    apxtri.headers.xlang = document.querySelector("html").getAttribute("lang");
  }
  //alert(localStorage.getItem(apxtri.headers.xapp))
  if (localStorage.getItem(apxtri.headers.xapp)) {
    apx.data = JSON.parse(localStorage.getItem(apxtri.headers.xapp));
    apx.data.pagename = apxtri.pagename;
    if (apxtri.pageauth) apx.data.pageauth = apxtri.pageauth;
    //console.log("mise à jour de apx.data avec ",apx.data)
    if (
      apx.data.headers.xtribe != apxtri.headers.xtribe ||
      apx.data.headers.xlang != apxtri.headers.xlang ||
      apx.data.headers.xtrkversion != apxtri.headers.xtrkversion
    ) {
      // if an app change of tribe
      alert("remove it");
      localStorage.removeItem(apxtri.headers.xapp);
      delete apx.data;
    }
  }
  if (!apx.data) {
    console.log("reinit apx.data");
    apx.data = apxtri;
  }
  apx.trackvisit(); //each updating state
  apx.pagecontext = { search: {}, hash: {} };
  if (window.location.hash != "") {
    window.location.hash
      .slice(1)
      .split("&")
      .forEach((kv) => {
        const keyval = kv.split("=");
        apx.pagecontext.hash[keyval[0]] = keyval[1];
      });
  }
  if (window.location.search != "") {
    window.location.search
      .slice(1)
      .split("&")
      .forEach((kv) => {
        const keyval = kv.split("=");
        apx.pagecontext.hash[keyval[0]] = keyval[1];
      });
  }
  console.log("apx.pagecontext:", apx.pagecontext);
  const ano = apx.data.headers.xalias == "anonymous" ? "anonymous" : "";
  // looking for localstorage, refresh management is done by apx.loadfile
  const toload = {};
  document.querySelectorAll("[tolocalin]").forEach((t) => {
    if (!toload[t.getAttribute("tolocalin")])
      toload[t.getAttribute("tolocalin")] = {};
    if (!toload[t.getAttribute("tolocalin")][t.getAttribute("name")]) {
      toload[t.getAttribute("tolocalin")][t.getAttribute("name")] =
        t.getAttribute("url");
    }
  });
  Object.keys(toload).forEach((d) => {
    console.log(d,toload[d])
    apx.loadfile(toload[d], d);
  });
  ////////////////////////////////////////////
  apx.data.version = 0; //this force an update to be removed in production
  ///////////////////////////////////////////
  try {
    const initset = await axios.get(
      `/api/apxtri/wwws/updatelocaldb${ano}/${apx.data.headers.xtribe}/${apx.data.headers.xapp}/${apx.data.pagename}/${apx.data.version}`,
      { headers: apx.data.headers }
    );
    console.log("recupe inidb");
    if (initset.data.msg == "forbidenaccess") {
      alert("Your profil does not allow access to this app");
      document.location.href = `/${apx.data.pageauth}_${apx.data.headers.xlang}.html#url=${apx.data.pagename}_${apx.data.headers.xlang}.html`;
    } else if (initset.data.msg == "datamodelupdate") {
      // mise à jour local
      Object.keys(initset.data.data).forEach((k) => {
        if (k != "headers") {
          apx.data[k] = initset.data.data[k];
        }
      });
      console.log("local update done");
      apx.save();
    } else {
      console.log(initset.data.msg);
    }
  } catch (err) {
    console.log("Check your backend");
  }
  apx.afterupdate.forEach(cb=>cb());
  /*    .then((rep) => {
      if (rep.data.msg == "forbidenaccess") {
        alert("Your profil does not allow access to this app");
        document.location.href = `/${apx.data.pageauth}_${apx.data.headers.xlang}.html#url=${apx.data.pagename}_${apx.data.headers.xlang}.html`;
      } else if (rep.data.msg == "datamodelupdate") {
        // mise à jour local
        Object.keys(rep.data.data).forEach((k) => {
          if (k!="headers"){
          apx.data[k] = rep.data.data[k];
          }
        });
        console.log("local update done");
        apx.save();
      } else {
        console.log(rep.data.msg);
      }
      console.log("remove apx.lock")
      if (apx.lock) delete apx.lock;
    })
    .then((err) => {
      if (err) {
        console.log(err);
        alertt("Check console");
      }
    )}
      */

  /*console.log(repapp)
  if (repapp.status=403) document.location.href=`/${apx.data.pageauth}_${apx.headers.xlang}.html`
  if (repapp.status==200) {
   Object.keys(repapp.data).forEach(k=>{
     apxtri[k]=repapp.data[k]
   })
  }else{
    Console.log("Check your data conf")
    console.log(repapp)
  }
  */

  apx.lazyload();

  //console.log( typeof identitycheckscreen)
  //apx.identity ? apx.identity.identitycheckscreen() : null; // in auth_lg.html from component identity

  apx.save();
};
apx.ready(apx.update); //2nd param optional=> true mean does not wait same if apx.lock is set

/**
 * dscreen keep updated dom tag with dscreen attribute
 * <span dscreen="firstname">fred</span>
 * In js
 *  dscreen.firstname="phil"
 *  dscreen.refresh() // then anywhere firstname appear is update with the relevant firstname value
 *
 */
var dscreen = dscreen || {};
dscreen.refresh = () => {
  document.querySelectorAll("[dscreen]").forEach((d) => {
    if (dscreen[d.getAttribute("dscreen")]) {
      d.innerHTML = dscreen[d.getAttribute("dscreen")];
    }
  });
};
