module.exports = (async () => {
  //##INJECTED_HEADER
  

  // Imports npm packages for this .gbdialog conversational application.

  require('isomorphic-fetch');
  const YAML = require('yaml');
  const http = require('node:http');
  const retry = require('async-retry');
  const createRpcClient = require('@push-rpc/core').createRpcClient;
  const createHttpClient = require('@push-rpc/http').createHttpClient;

  // Unmarshalls Local variables from server VM.

  const pid = this.pid;
  let id = this.id;
  let username = this.username;
  let mobile = this.mobile;
  let from = this.from;
  const channel = this.channel;
  const ENTER = this.ENTER;
  const headers = this.headers;
  let httpUsername = this.httpUsername;
  let httpPs = this.httpPs;
  let today = this.today;
  let now = this.now;
  let date = new Date();
  let page = null;
  const files = [];
  let col = 1;
  let index = 1;

  const mid = (arr, start, length) => {
    if (length === undefined) {
      return arr.slice(start);
    }
    return arr.slice(start, start + length);
  };

  // Makes objects in BASIC insensitive.

  const caseInsensitive = listOrRow => {
    if (!listOrRow) {
      return listOrRow;
    }

    const lowercase = oldKey => (typeof oldKey === 'string' ? oldKey.toLowerCase() : oldKey);

    const createCaseInsensitiveProxy = obj => {
      const propertiesMap = new Map(Object.keys(obj).map(propKey => [lowercase(propKey), obj[propKey]]));
      const caseInsensitiveGetHandler = {
        get: (target, property) => propertiesMap.get(lowercase(property))
      };
      return new Proxy(obj, caseInsensitiveGetHandler);
    };

    if (listOrRow.length) {
      return listOrRow.map(row => createCaseInsensitiveProxy(row));
    } else {
      return createCaseInsensitiveProxy(listOrRow);
    }
  };

  // Transfers auto variables into global object.

  for (const key of Object.keys(this.variables)) {
    global[key] = this.variables[key];
    console.log('Defining global variable: ' + key);
  }

  // Defines local utility BASIC functions.

  const ubound = gbarray => {
    let length = 0;
    if (gbarray) {
      length = gbarray.length;
      if (length > 0) {
        if (gbarray[0].gbarray) {
          return length - 1;
        }
      }
    }
    return length;
  };

  const isarray = gbarray => {
    return Array.isArray(gbarray);
  };

  // Proxies remote functions as BASIC functions.

  const weekday = v => {
    return (async () => {
      return await dk.getWeekFromDate({ v });
    })();
  };
  const hour = v => {
    return (async () => {
      return await dk.getHourFromDate({ v });
    })();
  };
  const base64 = v => {
    return (async () => {
      return await dk.getCoded({ v });
    })();
  };
  const tolist = v => {
    return (async () => {
      return await dk.getToLst({ v });
    })();
  };
  const uuid = () => {
    var dt = new Date().getTime();
    var uuid = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, c => {
      var r = (dt + Math.random() * 16) % 16 | 0;
      dt = Math.floor(dt / 16);
      return (c == 'x' ? r : (r & 0x3) | 0x8).toString(16);
    });
    return uuid;
  };
  const random = () => {
    return Number.parseInt(((Math.random() * 8) % 8) * 100000000);
  };

  // Setups interprocess communication from .gbdialog run-time to the BotServer API.

  const optsRPC = {
    callTimeout: this.callTimeout,
    messageParser: data => {
      return JSON.parse(data);
    }
  };
  let url;
  const agent = http.Agent({ keepAlive: true });
  
  
  url = `http://localhost:${port}/${botId}/dk`;
  const dk = (await createRpcClient(() => createHttpClient(url, { agent: agent }), optsRPC)).remote;
  url = `http://localhost:${port}/${botId}/sys`;
  const sys = (await createRpcClient(() => createHttpClient(url, { agent: agent }), optsRPC)).remote;
  url = `http://localhost:${port}/${botId}/wa`;
  const wa = (await createRpcClient(() => createHttpClient(url, { agent: agent }), optsRPC)).remote;
  url = `http://localhost:${port}/${botId}/img`;
  const img = (await createRpcClient(() => createHttpClient(url, { agent: agent }), optsRPC)).remote;

  const timeout = ms => {
    return new Promise(resolve => setTimeout(resolve, ms));
  };

  const ensureTokens = async firstTime => {
    const REFRESH_THRESHOLD_MS = 10 * 60 * 1000; // 10 minutes in milliseconds
    const tokens = this.tokens ? this.tokens.split(',') : [];

    for (let i = 0; i < tokens.length; i++) {
      const tokenName = tokens[i];

      // Auto update Bearer authentication for the first token.
      const expiresOn = new Date(global[tokenName + '_expiresOn']);
      const expiration = expiresOn.getTime() - REFRESH_THRESHOLD_MS;

      // Expires token 10min. before or if it the first time, load it.
      if (expiration < Date.now() || firstTime) {
        console.log('Expired. Refreshing token...' + expiration);
        try {
          const result = await sys.getCustomToken({ pid: this.pid, tokenName: tokenName });
          global[tokenName] = result.token;
          global[tokenName + '_expiresOn'] = result.expiresOn;
          console.log('DONE:' + new Date(global[tokenName + '_expiresOn']));
        } catch (error) {
          console.error('Failed to refresh token for ' + tokenName + ':', error);
          continue;
        }
      }

      if (i == 0) {
        headers['Authorization'] = 'Bearer ' + global[tokenName];
      }
    }
  };
  const sleep = async ms => {
    return new Promise(resolve => {
      setTimeout(resolve, ms);
    });
  };

  const TOYAML = json => {
    const doc = new YAML.Document();
    doc.contents = json;
    return doc.toString();
  };

  // Line of Business logic.

  let __reportMerge = { adds: 0, updates: 0, skipped: 0 };
  let __report = () => {
    return (
      __reportMerge.title +
      ' adds: ' +
      __reportMerge.adds +
      ', updates: ' +
      __reportMerge.updates +
      ' and skipped: ' +
      __reportMerge.skipped +
      '.'
    );
  };
  let REPORT = 'No report yet';

  try {
    await ensureTokens(true);
    
    //##INJECTED_CODE_HERE

  } catch (e) {
    console.log(e);

    reject({ message: e.message, name: e.name });
  } finally {
    // Closes handles if any.

    await wa.closeHandles({ pid: pid });
    await sys.closeHandles({ pid: pid });

    resolve(true);
  }
})();