	/**
	 * The external code/string to be added
	 *
	 * @var      array
	 * @since 3.2.0
	 */
	protected array $code = [];

	/**
	 * The external code/string cutter
	 *
	 * @var      array
	 * @since 3.2.0
	 */
	protected array $cutter = [];

	/**
	 * Compiler Placeholder
	 *
	 * @var    Placeholder
	 * @since 3.2.0
	 **/
	protected Placeholder $placeholder;

	/**
	 * Database object to query local DB
	 *
	 * @var    \JDatabaseDriver
	 * @since 3.2.0
	 **/
	protected \JDatabaseDriver $db;

	/**
	 * User object
	 *
	 * @var    User
	 * @since 3.2.0
	 **/
	protected User $user;

	/**
	 * Database object to query local DB
	 *
	 * @var    CMSApplication
	 * @since 3.2.0
	 **/
	protected CMSApplication $app;

	/**
	 * Constructor.
	 *
	 * @param Placeholder|null        $placeholder   The compiler placeholder object.
	 * @param \JDatabaseDriver|null   $db            The Database Driver object.
	 * @param User|null               $user          The User object.
	 * @param CMSApplication|null     $app           The CMS Application object.
	 *
	 * @throws \Exception
	 * @since 3.2.0
	 */
	public function __construct(?Placeholder $placeholder = null,
		?\JDatabaseDriver $db = null, ?User $user = null, ?CMSApplication $app = null)
	{
		$this->placeholder = $placeholder ?: Compiler::_('Placeholder');
		$this->db = $db ?: Factory::getDbo();
		$this->user = $user ?: Factory::getUser();
		$this->app = $app ?: Factory::getApplication();
	}

	/**
	 * Set the external code string & load it in to string
	 *
	 * @param   string  $string  The content to check
	 * @param   int     $debug   The switch to debug the update
	 *
	 * @return  string
	 * @since 3.2.0
	 */
	public function set(string $string, int $debug = 0): string
	{
		// check if content has custom code placeholder
		if (strpos($string, '[EXTERNA' . 'LCODE=') !== false)
		{
			// if debug
			if ($debug)
			{
				echo 'External Code String:';
				var_dump($string);
			}
			// target content
			$bucket = [];
			$found  = GetHelper::allBetween(
				$string, '[EXTERNA' . 'LCODE=', ']'
			);
			if (ArrayHelper::check($found))
			{
				// build local bucket
				foreach ($found as $target)
				{
					// check for cutting sequence
					// example: >{3|4
					// will cut 3 rows at top and 4 rows at bottom
					// if the external code has 8 or more lines
					if (($pos = strpos((string) $target, '>{')) !== false)
					{
						// the length
						$target_len = strlen((string) $target);
						// where to cut
						$cutting = $target_len - $pos;
						// get the sequence
						$sequence = substr((string) $target, "-$cutting");
						// remove from the URL
						$target_url = str_replace($sequence, '', (string) $target);
						// set the cut key for this target if not set
						$this->cutter[trim((string) $target)] = str_replace('>{', '', $sequence);
					}
					else
					{
						$target_url = $target;
					}
					// check if the target is valid URL or path
					if ((!filter_var($target_url, FILTER_VALIDATE_URL) === false
							&& FileHelper::exists($target_url))
						|| (Path::clean($target_url) === $target_url
							&& FileHelper::exists($target_url)))
					{
						$this->getCode($target, $bucket);
					}
					// give notice that target is not a valid url/path
					else
					{
						// set key
						$key = '[EXTERNA' . 'LCODE=' . $target . ']';
						// set the notice
						$this->app->enqueueMessage(
							Text::_('<hr /><h3>External Code Warning</h3>'
							), 'Warning'
						);
						$this->app->enqueueMessage(
							Text::sprintf('The <b>%s</b> is not a valid url/path!',
								$key
							), 'Warning'
						);
						// remove the placeholder
						$bucket[$key] = '';
					}
				}
				// now update local string if bucket has values
				if (ArrayHelper::check($bucket))
				{
					$string = $this->placeholder->update($string, $bucket);
				}
			}
			// if debug
			if ($debug)
			{
				echo 'External Code String After Update:';
				var_dump($string);
			}
		}

		return $string;
	}

	/**
	 * Get the External Code/String
	 *
	 * @param   string  $string  The content to check
	 * @param   array   $bucket  The Placeholders bucket
	 *
	 * @return  void
	 * @since 3.2.0
	 */
	protected function getCode(string $target, array &$bucket)
	{
		// set URL key
		$target_key = trim($target);
		// set key
		$key = '[EXTERNA' . 'LCODE=' . $target . ']';
		// remove the cut sequence from the url
		if (isset($this->cutter[$target_key]))
		{
			// remove from the URL
			$target_url = trim(str_replace('>{' . $this->cutter[$target_key], '', $target));
		}
		else
		{
			$target_url = trim($target);
		}
		// check if we already fetched this
		if (!isset($this->code[$target_key]))
		{
			// get the data string (code)
			$this->code[$target_key]
				= FileHelper::getContent($target_url);
			// check if we must cut this
			if (isset($this->cutter[$target_key]) &&
				$this->cutter[$target_key])
			{
				$this->code[$target_key] = $this->cut(
					$this->code[$target_key],
					$this->cutter[$target_key],
					$key
				);
			}
			// did we get any value
			if (StringHelper::check(
				$this->code[$target_key]
			))
			{
				// check for changes
				$live_hash = md5($this->code[$target_key]);
				// check if it exists local
				if ($hash = GetHelper::var(
					'external_code', $target_key, 'target', 'hash'
				))
				{
					// must be an admin make a change to use EXTERNAL code (we may add a custom access switch - use ADMIN for now)
					if ($hash !== $live_hash && $this->user->authorise(
						'core.admin', 'com_componentbuilder'
					))
					{
						// update the hash since it changed
						$object         = new \stdClass();
						$object->target = $target_key;
						$object->hash   = $live_hash;
						// update local hash
						$this->db->updateObject(
							'#__componentbuilder_external_code', $object,
							'target'
						);
						// give notice of the change
						$this->app->enqueueMessage(
							Text::_('<hr /><h3>External Code Warning</h3>'),
							'Warning'
						);
						$this->app->enqueueMessage(
							Text::sprintf('The code/string from <b>%s</b> has been <b>changed</b> since the last compilation. Please investigate to ensure the changes are safe! <b>Should you not expect this change to the external code/string being added, then this is a serious issue! and requires immediate attention!</b> Do not ignore this warning as it will only show <b>once</b>.',
								$key
							), 'Warning'
						);
					}
					elseif ($hash !== $live_hash)
					{
						// set the notice
						$this->app->enqueueMessage(
							Text::_('<hr /><h3>External Code Error</h3>'),
							'Error'
						);
						$this->app->enqueueMessage(
							Text::sprintf('%s, we detected a change in <b>EXTERNALCODE</b>, but you do not have permission to allow this change so <b>%s</b> was removed from the compilation. Please contact your system administrator for more info!<br /><small>(admin access required)</small>',
								$this->user->get('name'), $key
							), 'Error'
						);
						// remove the code/string
						$this->code[$target_key] = '';
					}
				}
				// only an admin can add new EXTERNAL code (we may add a custom access switch - use ADMIN for now)
				elseif ($this->user->authorise(
					'core.admin', 'com_componentbuilder'
				))
				{
					// add the hash to track changes
					$object         = new \stdClass();
					$object->target = $target_key;
					$object->hash   = $live_hash;
					// insert local hash
					$this->db->insertObject(
						'#__componentbuilder_external_code', $object
					);
					// give notice the first time this is added
					$this->app->enqueueMessage(
						Text::_('<hr /><h3>External Code Notice</h3>'),
						'Warning'
					);
					$this->app->enqueueMessage(
						Text::sprintf('The code/string from <b>%s</b> has been added for the <b>first time</b>. Please <i>investigate</i> to ensure the correct code/string was used! <b>Should you not know about this NEW external code/string being added, then this is a serious danger! and requires immediate attention!</b> Do not ignore this warning as it will only show <b>once</b>.',
							$key
						), 'Warning'
					);
				}
				else
				{
					// set the notice
					$this->app->enqueueMessage(
						Text::_('<hr /><h3>External Code Error</h3>'),
						'Error'
					);
					$this->app->enqueueMessage(
						Text::sprintf('%s, we detected <b>NEW EXTERNALCODE</b>, but you do not have permission to allow this new code/string so <b>%s</b> was removed from the compilation. Please contact you system administrator for more info!<br /><small>(admin access required)</small>',
							$this->user->get('name'), $key
						), 'Error'
					);
					// remove the code/string
					$this->code[$target_key] = '';
				}
			}
			else
			{
				// set notice that we could not get a valid string from the target
				$this->app->enqueueMessage(
					Text::_('<hr /><h3>External Code Warning</h3>'), 'Error'
				);
				$this->app->enqueueMessage(
					Text::sprintf('The <b>%s</b> returned an invalid string!', $key
					), 'Error'
				);
			}
		}

		// add to local bucket
		$bucket[$key] = $this->code[$target_key] ?? '';
	}

	/**
	 * Cut the External Code/String
	 *
	 * @param   string  $string    The content to cut
	 * @param   string  $sequence  The cutting sequence
	 * @param   string  $key       The content key
	 *
	 * @return  string
	 * @since 3.2.0
	 */
	protected function cut(string $string, string $sequence, string $key): string
	{
		// we first break the string up in rows
		$rows = (array) explode(PHP_EOL, $string);
		// get the cutting sequence
		$cutter = (array) explode('|', $sequence);
		// we only continue if we have more rows than we have to cut
		if (array_sum($cutter) < ArrayHelper::check($rows))
		{
			// remove the rows at the bottom if needed
			if (isset($cutter[1]) && $cutter[1] > 0)
			{
				array_splice($rows, "-$cutter[1]");
			}
			// remove the rows at the top if needed
			if ($cutter[0] > 0)
			{
				$rows = array_splice($rows, $cutter[0]);
			}

			// return the remaining rows
			return implode(PHP_EOL, $rows);
		}

		// we set an error message about too few lines to cut
		$this->app->enqueueMessage(
			Text::_('<hr /><h3>External Code Notice</h3>'),
			'Error'
		);
		$this->app->enqueueMessage(
			Text::sprintf('The <b>%s</b> cut sequence failed on the returned external code/string as more lines has to be cut then was found in the code/string. We have completely removed the code. Please check this code/string!',
				$key
			), 'Error'
		);

		return '';
	}
