<?php

namespace BitCode\BitForm\Frontend\Form;

if (!defined('ABSPATH')) {
  exit;
}

use BitCode\BitForm\Admin\Form\AdminFormHandler;
use BitCode\BitForm\Admin\Form\FrontEndScriptGenerator;
use BitCode\BitForm\Admin\Form\Helpers;
use BitCode\BitForm\Core\Database\FormEntryMetaModel;
use BitCode\BitForm\Core\Database\FormModel;
use BitCode\BitForm\Core\Form\FormManager;
use BitCode\BitForm\Core\Integration\IntegrationHandler;
use BitCode\BitForm\Core\Util\EscapingHelper;
use BitCode\BitForm\Core\Util\FieldValueHandler;
use BitCode\BitForm\Core\Util\FileDownloadProvider;
use BitCode\BitForm\Core\Util\FrontendHelpers;
use BitCode\BitForm\Core\Util\SmartTags;
use BitCode\BitForm\Core\Util\SmartTagRegistry;
use BitCode\BitForm\Core\WorkFlow\WorkFlow;
use Error;

final class FrontendFormHandler
{
  public function __construct()
  {
    // before markup load - formids [], posts [1,2]
    add_action('wp_enqueue_scripts', [$this, 'loadAssets']);
    // markup loads - formids []
    add_shortcode('bitform', [$this, 'handleFrontendRenderRequest']);
    // after markup load - formids [1,35,3]
    add_action('wp_footer', [$this, 'generateJS']);
  }

  private function validPassowordResetToken($token, $userID, $formId)
  {
    $existResetInteg = (new IntegrationHandler($formId))->getAllIntegration('wp_user_auth', 'wp_auth', 1);
    if (!is_wp_error($existResetInteg) && count($existResetInteg) > 0) {
      if ('reset' === $existResetInteg[0]->integration_name) {
        $user = get_userdata($userID);
        if ($user) {
          $validKey = check_password_reset_key($token, $user->user_login);
          if (is_wp_error($validKey)) {
            echo "<div id='bf-resp' style='display:grid;justify-content:center;color:#860000;'>This password reset token is invalid.</div>";
            exit();
          }
        } else {
          echo "<div id='bf-resp' style='display:grid;justify-content:center;color:#860000;'>Invalid User!!</div>";
          exit();
        }
      }
    }
  }

  private function getJSFileSrc($postId)
  {
    $formUpdateVersion = get_option('bitform_form_update_version');
    $formScriptSrc = BITFORMS_UPLOAD_BASE_URL . "/form-scripts/$postId/bitform-js-$postId.js?bfv=$formUpdateVersion";

    return $formScriptSrc;
  }

  public function generateJs($formID = null, $entryID = null, $formType = null)
  {
    // return true;
    $isFormPreview = get_transient('bitform_form_preview');
    if ($isFormPreview && !$formID) {
      delete_transient('bitform_form_preview');
      return;
    }
    $frontendScriptGenObj = new FrontEndScriptGenerator();
    $isPageBuilder = FrontendHelpers::checkIsPageBuilder($_SERVER);
    $bfFrontendFormIds = FrontendHelpers::$bfFrontendFormIds;
    if ($isPageBuilder || empty($bfFrontendFormIds)) {
      return;
    }
    // for unique fields ids in the same form (e.g. multiple forms in the same page)
    $allFields = [];
    $formContents = [];
    $contentIds = [];
    $formIDs = [];
    $previewMode = 'classic';
    $postId = '';

    $formUpdateVersion = get_option('bitform_form_update_version');
    if ($formID) {
      $formIDs[] = $formID;
      $FrontendFormManager = FrontendFormManager::getInstance($formID, 1);
      $formInfo = $FrontendFormManager->getFormInfo();
      $FormIdentifier = esc_js($FrontendFormManager->getFormIdentifier());
      $formContent = $FrontendFormManager->getFormContentWithValue($this->getValuesFromQueryParams());
      $formContent->formId = $formID;
      $formContents[] = $formContent;
      $workFlowRunType = $entryID ? 'edit' : 'create';
      $fields = $formContent->fields;
      if ($entryID) {
        $fields = $this->setFieldsValue($fields, $formID, $entryID);
      }
      $fields = $this->triggerWorkflowOnLoad($formID, 1, $fields, $workFlowRunType);
      array_push($contentIds, $FormIdentifier);

      foreach ($fields as $fk => $field) {
        $allFields[$field->typ][] = ['fk' => $fk, 'field' => $field, 'formID' => $formID, 'contentId' => $FormIdentifier];
      }
      //Generate JS file for conversational form
      if (!empty($formInfo->conversationalSettings->enable) && $formInfo->conversationalSettings->enable) {
        $frontendScriptGenObj->generateJsFile([$formContent], $allFields, [$FormIdentifier], $formID, [$formID], 'conversational');
      }
      $previewMode = 'preview';
      $postId = $formID;
    } else {
      global $post;
      if (!is_object($post) && !isset($post->ID)) {
        return;
      }
      $bfFrontendFormIds = FrontendHelpers::$bfFrontendFormIds;
      $bfUniqFormIds = FrontendHelpers::getAllUniqFormIdsInPage();
      $formIDs = $bfUniqFormIds;
      $regenerateScriptFlag = $this->regenerateScriptChecker($bfUniqFormIds);

      $postId = $post->ID;
      if (!$regenerateScriptFlag) {
        $regenerateScriptFlag = $this->deleteUnusedFormPageIds($postId, $bfUniqFormIds);
      }
      $isJsGenerating = get_option('bitforms_frontend_js_generating');
      if (!$regenerateScriptFlag && !$isJsGenerating && !empty($formIDs)) {
        wp_enqueue_script('bit-form-all-script-test', $this->getJSFileSrc($postId), [], $formUpdateVersion, true);
        return;
      }
      foreach ($bfFrontendFormIds as $index => $formId) {
        $shortCodeCounter = $index + 1;
        $FrontendFormManager = FrontendFormManager::getInstance($formId, $shortCodeCounter);
        $formInfo = $FrontendFormManager->getFormInfo();
        $FormIdentifier = esc_js($FrontendFormManager->getFormIdentifier());
        $formContent = $FrontendFormManager->getFormContentWithValue($this->getValuesFromQueryParams());
        $formContent->formId = $formId;
        $formContents[] = $formContent;
        $fields = $this->triggerWorkflowOnLoad($formId, $shortCodeCounter, $formContent->fields);
        $contentIds[] = $FormIdentifier;
        $formFields = []; // indivisual form fields array for conversational view
        foreach ($fields as $fk => $field) {
          $fieldArr = ['fk' => $fk, 'field' => $field, 'formID' => $formId, 'contentId' => $FormIdentifier];
          $allFields[$field->typ][] = $fieldArr;
          $formFields[$field->typ][] = $fieldArr;
        }
        //Generate JS file for conversational form
        if (!empty($formInfo->conversationalSettings->enable) && $formInfo->conversationalSettings->enable) {
          $frontendScriptGenObj->generateJsFile([$formContent], $formFields, [$FormIdentifier], $formId, [$formId], 'conversational');
        }
      }
    }
    if (empty($formIDs)) {
      return;
    }

    $frontendScriptGenObj->generateJsFile($formContents, $allFields, $contentIds, $postId, $formIDs, $previewMode);
    if ('preview' === $previewMode) {
      return;
    }
    wp_enqueue_script('bit-form-all-script-test', $this->getJSFileSrc($postId), [], $formUpdateVersion, true);
  }

  private function deleteUnusedFormPageIds($postId, $formIDs)
  {
    global $post;
    if (!is_object($post) && !isset($post->ID)) {
      return;
    }
    $postId = $post->ID;
    $formModel = new FormModel();
    $forms = $formModel->get(
      ['id', 'generated_script_page_ids']
    );
    $regenerateScriptFlag = false;
    foreach ($forms as $form) {
      $formId = $form->id;
      $generatedScriptPageIdsDecoded = json_decode($form->generated_script_page_ids, true);
      $generatedScriptPageIds = is_array($generatedScriptPageIdsDecoded) ? array_keys($generatedScriptPageIdsDecoded) : [];
      if (!empty($generatedScriptPageIds) && !in_array($formId, $formIDs) && in_array($postId, $generatedScriptPageIds)) {
        unset($generatedScriptPageIdsDecoded[$postId]);
        if (empty($generatedScriptPageIdsDecoded)) {
          $generatedScriptPageIdsDecoded = new \stdClass();
        }
        $regenerateScriptFlag = true;
        $formModel->update(['generated_script_page_ids' => wp_json_encode($generatedScriptPageIdsDecoded)], ['id' => $formId]);
      }
    }
    if ($regenerateScriptFlag) {
      $formUpdateVersion = get_option('bitform_form_update_version');
      if (!$formUpdateVersion) {
        $formUpdateVersion = 1;
      } else {
        $formUpdateVersion = (int) $formUpdateVersion + 1;
      }
      update_option('bitform_form_update_version', $formUpdateVersion);
    }
    return $regenerateScriptFlag;
  }

  private function regenerateScriptChecker($formsIds)
  {
    global $post;
    if (!is_a($post, 'WP_Post') && !isset($post->ID)) {
      return;
    }
    $postId = $post->ID;
    $regenerateScriptFlag = false;
    $formModel = new FormModel();
    foreach ($formsIds as $formId) {
      $formInstance = FormManager::getInstance($formId);
      if (!$formInstance->isExist()) {
        continue;
      }
      $generatedPages = $formInstance->getFormData('generated_script_page_ids');
      if (empty($generatedPages)) {
        $regenerateScriptFlag = true;
      } elseif (is_object($generatedPages) && (!isset($generatedPages->{$postId}) || (isset($generatedPages->{$postId}) && false === $generatedPages->{$postId}))) {
        $regenerateScriptFlag = true;
      }
      if (!$regenerateScriptFlag) {
        continue;
      }
      if (!is_object($generatedPages)) {
        $generatedPages = (object) [];
      }
      $generatedPages->{$postId} = true;
      $formModel->update(
        [
          'generated_script_page_ids' => \wp_json_encode($generatedPages)
        ],
        [
          'id' => $formId,
        ]
      );
    }
    return $regenerateScriptFlag;
  }

  private function addInlineScript($code, $handle = '', $position = 'after')
  {
    $scriptHandle = !empty($handle) ? $handle : 'bf-inline-script';
    $formUpdateVersion = get_option('bitform_form_update_version');
    if (!wp_script_is($scriptHandle)) {
      wp_register_script($scriptHandle, '', [], $formUpdateVersion, true);
      wp_enqueue_script($scriptHandle);
    }
    wp_add_inline_script($scriptHandle, $code, $position);
  }

  private function addInlineStyle($code, $handle = '')
  {
    $styleHandle = !empty($handle) ? $handle : 'bf-inline-style';
    $formUpdateVersion = get_option('bitform_form_update_version');
    if (!wp_style_is($styleHandle)) {
      wp_register_style($styleHandle, '', [], $formUpdateVersion);
      wp_enqueue_style($styleHandle);
    }
    wp_add_inline_style($styleHandle, $code);
  }

  private function triggerWorkflowOnLoad($formID, $shortCodeCounter, $fields, $workFlowRunType = 'create')
  {
    $FrontendFormManager = new FrontendFormManager($formID, $shortCodeCounter);
    $previousValue = $this->getValuesFromQueryParams();
    $formContent = $FrontendFormManager->getFormContentWithValue($previousValue);
    if (!empty($formContent->workFlowExist)) {
      $workFlowRunHelper = new WorkFlow($formID);
      if (!empty($formContent->workFlowExist->onload)) {
        $workFlowreturnedOnLoad = $workFlowRunHelper->executeOnLoad(
          $workFlowRunType,
          $fields
        );

        if (!empty($workFlowreturnedOnLoad['fields'])) {
          return $workFlowreturnedOnLoad['fields'];
        }
      }
    }

    return $fields;
  }

  private function executeOnUserInput($formID, $shortCodeCounter, $fields)
  {
    $FrontendFormManager = FrontendFormManager::getInstance($formID, $shortCodeCounter);
    $previousValue = $this->getValuesFromQueryParams();
    $formContent = $FrontendFormManager->getFormContentWithValue($previousValue);
    $customCodesExist = strpos(FrontEndScriptGenerator::getCustomCodes($formID)['JavaScript'], 'bfVars');
    if ($customCodesExist || (!empty($formContent->workFlowExist) && !empty($formContent->workFlowExist->oninput))) {
      $workFlowRunHelper = new WorkFlow($formID);
      return $workFlowRunHelper->executeOnUserInput('create', $fields);
    }
  }

  private function getValuesFromQueryParams()
  {
    // Read-only: query string parsed to pre-fill form fields. Values are sanitized per field before use.
    $queryParamsValue = [];
    if (isset($_SERVER['QUERY_STRING']) && !empty($_SERVER['QUERY_STRING'])) {
      $reqField = sanitize_text_field(wp_unslash($_SERVER['QUERY_STRING']));
      foreach (explode('&', $reqField) as $keyValue) {
        // $pattern = '/([a-zA-Z0-9])([a-zA-Z])\=+/';
        $pattern = '/([^.]+)=(.*?)([^.]+)/';
        $matches = preg_match($pattern, $keyValue, $matchFormat);
        if ($matches) {
          list($field, $value) = explode('=', $keyValue, 2);

          if (!trim($value)) {
            continue;
          }

          $queryParamsValue[$field][] = sanitize_text_field(urldecode($value));
        }
      }
    }

    return $queryParamsValue;
  }

  public function handleFrontendRenderRequest($atts)
  {
    $formType = isset($atts['type']) ? $atts['type'] : 'classic';
    $formPreview = isset($atts['form_preview']) ? $atts['form_preview'] : false;
    if (isset($atts['form_id'])) {
      $formID = intval($atts['form_id']);
    }
    if (isset($atts['entry_id'])) {
      $entryId = intval($atts['entry_id']);
      // Read-only: entry ID from query string for shortcode render. No state mutation.
    } elseif (isset($_GET['bf_entry_id']) && !is_array($_GET['bf_entry_id'])) {
      $entryId = intval(sanitize_text_field(wp_unslash($_GET['bf_entry_id'])));
    } else {
      $entryId = false;
    }
    if (isset($atts['id'])) {
      $atts = shortcode_atts(['id' => 0], $atts);
      $formID = intval($atts['id']);
    }

    if (!$formID) {
      return __('Form ID cannot be empty', 'bit-form');
    }

    if (!$this->isExist($formID)) {
      /* translators: %s: form ID */
      return sprintf(__('#%s no. Form doesn\'t exists', 'bit-form'), $formID);
    }

    // Add-ons may detect whether the current visitor is resuming an abandoned entry.
    $isAbandoned = (bool) apply_filters('bitform_is_abandoned_entry', false, $formID, $entryId, $atts);

    FrontendHelpers::setBfFrontendFormIds($formID);
    $bfFrontendFormIds = FrontendHelpers::$bfFrontendFormIds;
    $shortCodeCounter = count($bfFrontendFormIds);
    $FrontendFormManager = FrontendFormManager::getInstance($formID, $shortCodeCounter);

    if (!$FrontendFormManager->checkStatus()) {
      /* translators: %s: form ID */
      return  sprintf(__('#%s no. Form is not active', 'bit-form'), $formID);
    }
    ob_start();
    $this->loadAssets($formID, $formType);

    $font = $FrontendFormManager->getFont();

    if ($font && !$formPreview) {
      wp_enqueue_style('bf-google-font', $font, '1.0.0', true);
    }

    // Read-only: password reset token from URL for display-time validation. No state written until form is submitted.
    if (!empty($_GET['token']) && !empty($_GET['id'])) {
      $this->validPassowordResetToken(sanitize_text_field(wp_unslash($_GET['token'])), sanitize_text_field(wp_unslash($_GET['id'])), $formID);
    }

    $previousValue = $this->getValuesFromQueryParams();
    $errorMessages = []; // delete
    $FormIdentifier = esc_js($FrontendFormManager->getFormIdentifier());
    $nonce = $FrontendFormManager->getFormToken();
    $file = count($FrontendFormManager->getUploadFields()) > 0 ? $FrontendFormManager->getUploadFields() : false;

    $FrontendFormManager->setViewCount();

    $formContent = $FrontendFormManager->getFormContentWithValue($previousValue);
    $fields = $formContent->fields;
    $layout = $formContent->layout;
    $nestedLayout = isset($formContent->nestedLayout) ? $formContent->nestedLayout : (object) [];
    $buttons = !empty($formContent->buttons) ? $formContent->buttons : '';
    $additional = $formContent->additional;

    // $workFlowRunType = $entryId ? 'edit' : 'create';
    if ($entryId && (FrontendHelpers::is_current_user_can_access($formID, 'entryEditAccess'))) {
      $workFlowRunType = 'edit';
      $adminFormHandler = new AdminFormHandler();
      $getEntry = $adminFormHandler->getSingleEntry($formID, $entryId);
      if (FrontendHelpers::is_current_user_can_access($formID, 'entryEditAccess', '', $getEntry->__user_id)) {
        $fields = $this->setFieldsValue($fields, $formID, $entryId);
      } elseif (!$isAbandoned) {
        $entryId = false;
        $workFlowRunType = 'create';
      }
    } else {
      $entryId = false;
      $workFlowRunType = 'create';
    }

    // if ($entryId) {
    //   $fields = $this->setFieldsValue($fields, $formID, $entryId);
    // }

    $fields = apply_filters('bitform_filter_before_workflow_onload_fields', $fields, $formID);
    $fields = $this->triggerWorkflowOnLoad($formID, $shortCodeCounter, $fields, $workFlowRunType);
    $fields = apply_filters('bitform_filter_after_workflow_onload_fields', $fields, $formID);
    do_action('bitform_onload_fields', $fields, $formID);
    $workFlowreturnedOnUserInput = $this->executeOnUserInput($formID, $shortCodeCounter, $fields);

    // test for form before remove
    $noLabelFieldTypes = ['decision-box', 'gdpr', 'html', 'shortcode', 'button', 'paypal', 'razorpay', 'recaptcha', 'turnstile', 'hcaptcha', 'stripe', 'spacer'];
    foreach ($fields as $fldKey => $field) {
      if (!in_array($field->typ, $noLabelFieldTypes) && isset($field->lbl)) {
        $lblReplaceToBackslash = str_replace('$_bf_$', '\\', $field->lbl);
        $fields->{$fldKey}->lbl = FieldValueHandler::replaceSmartTagWithValue($lblReplaceToBackslash);
      }
    }
    $fieldsKey = $FrontendFormManager->getFieldsKey();

    $captchaV3Settings = $FrontendFormManager->getCaptchaV3Settings();
    if ($FrontendFormManager->getCaptchaSettings() || $captchaV3Settings || $FrontendFormManager->getTurnstileSettings() || $FrontendFormManager->isFieldTypeExist('hcaptcha')) {
      $integrationHandler = new IntegrationHandler(0);
      $allFormIntegrations = $integrationHandler->getAllIntegration('app');
      if (!is_wp_error($allFormIntegrations)) {
        foreach ($allFormIntegrations as $integration) {
          if (
            $FrontendFormManager->getCaptchaSettings()
            && !is_null($integration->integration_type)
            && 'gReCaptcha' === $integration->integration_type
          ) {
            $integrationDetails = json_decode($integration->integration_details);
            $integrationDetails->id = $integration->id;
            $reCAPTCHA = $integrationDetails;
            $reCAPTCHAVersion = 'v2';
          }

          if (
            $FrontendFormManager->getTurnstileSettings()
            && !is_null($integration->integration_type)
            && 'turnstileCaptcha' === $integration->integration_type
          ) {
            $integrationDetails = json_decode($integration->integration_details);
            $turnstileSiteKey = $integrationDetails->siteKey;
          }

          if (
            $FrontendFormManager->isFieldTypeExist('hcaptcha')
            && !is_null($integration->integration_type)
            && 'hcaptcha' === $integration->integration_type
          ) {
            $integrationDetails = json_decode($integration->integration_details);
            $hCaptchaSiteKey = $integrationDetails->siteKey;
          }

          if ($captchaV3Settings) {
            if (!is_null($integration->integration_type) && 'gReCaptchaV3' === $integration->integration_type) {
              $integrationDetails = json_decode($integration->integration_details);
              $integrationDetails->id = $integration->id;
              $reCAPTCHA = $integrationDetails;
              $reCAPTCHAVersion = 'v3';
            }
          }
        }
      }
    }

    if ($captchaV3Settings && !empty($reCAPTCHA->siteKey)) {
      // DANGER: no matter what, DONT CHANGE THE SCRIPT ID OF THIS SCRIPT
      $scriptId = BITFORMS_PREFIX . 'recaptcha';
      // External Google reCAPTCHA script; version managed by URL query param. Loaded in header because
      // standalone form views do not render wp_footer(), making footer enqueue unreliable.
      wp_enqueue_script($scriptId, "https://www.google.com/recaptcha/api.js?render={$reCAPTCHA->siteKey}", [], null, false);
    }

    $configs = [
      'bf_separator' => BITFORMS_BF_SEPARATOR,
    ];

    // check if fields has paypal or razorpay
    $paymentFields = ['paypal', 'razorpay', 'stripe'];
    $paymentFieldData = [];
    foreach ($fields as $key => $field) {
      if (in_array($field->typ, $paymentFields)) {
        $paymentFieldData[$key] = $field;
      }
    }

    if (!empty($paymentFieldData)) {
      $integrationHandler = new IntegrationHandler(0);
      foreach ($paymentFieldData as $fldKey => $fldData) {
        $paymentIntegration = $integrationHandler->getAIntegration($fldData->payIntegID);
        if (is_wp_error($paymentIntegration)) {
          continue;
        }
        if ('paypal' === $fldData->typ) {
          $integrationDetails = json_decode($paymentIntegration[0]->integration_details);
          $clientID = $integrationDetails->clientID;
          $fields->{$fldKey}->clientId = $clientID;
        } elseif ('razorpay' === $fldData->typ) {
          $integrationDetails = json_decode($paymentIntegration[0]->integration_details);
          $clientID = $integrationDetails->apiKey;
          $fields->{$fldKey}->clientId = $clientID;
        } elseif ('stripe' === $fldData->typ) {
          $integrationDetails = json_decode($paymentIntegration[0]->integration_details);
          $publishableKey = $integrationDetails->publishableKey;
          $fields->{$fldKey}->publishableKey = $publishableKey;
        }
      }
    }

    $bitFormFrontArr = [
      'ajaxURL'                        => admin_url('admin-ajax.php'),
      'nonce'                          => $nonce,
      'version'                        => BITFORMS_VERSION,
      'layout'                         => $layout,
      'nestedLayout'                   => $nestedLayout,
      'fields'                         => $fields,
      'buttons'                        => $buttons,
      'fieldsKey'                      => $fieldsKey,
      'file'                           => $file,
      'configs'                        => $configs,
      'formId'                         => $formID,
      'appID'                          => "bitforms_{$formID}",
      'GCLID'                          => $FrontendFormManager->isGCLIDEnabled(),
      'assetUrl'                       => BITFORMS_ASSET_URI,
      'onfieldCondition'               => !empty($workFlowreturnedOnUserInput['onfield_input_conditions']) ? $workFlowreturnedOnUserInput['onfield_input_conditions'] : false,
      'smartTags'                      => $this->buildFrontendSmartTags($formID, $workFlowreturnedOnUserInput, $fields),
      'paymentCallbackUrl'             => get_rest_url() . 'bitform/v1/payments/razorpay',
      'gRecaptchaSiteKey'              => !empty($reCAPTCHA->siteKey) ? $reCAPTCHA->siteKey : null,
      'gRecaptchaVersion'              => !empty($reCAPTCHAVersion) ? $reCAPTCHAVersion : null,
      'turnstileSiteKey'               => !empty($turnstileSiteKey) ? $turnstileSiteKey : null,
      'hCaptchaSiteKey'                => !empty($hCaptchaSiteKey) ? $hCaptchaSiteKey : null,
    ];

    if ($entryId) {
      $bitFormFrontArr['entryId'] = $entryId;
    }

    if (isset($additional->enabled->validateFocusLost)) {
      $bitFormFrontArr['validateFocusLost'] = true;
    }

    if (!empty($isAbandoned)) {
      $bitFormFrontArr['oldValues'] = $this->getFieldsValue($formID, $isAbandoned);
      if (empty($entryId)) {
        $bitFormFrontArr['entryId'] = $isAbandoned;
      }
    }

    $formInfo = $FrontendFormManager->getFormInfo();
    $bitFormFrontArr['formName'] = $formInfo->formName ?? '';
    if (is_array($layout) && count($layout) > 1) {
      $multiStepSettings = isset($formInfo->multiStepSettings) ? $formInfo->multiStepSettings : null;
      $newTempSettings = (object) [
        'validateOnStepChange' => isset($multiStepSettings->validateOnStepChange) ? $multiStepSettings->validateOnStepChange : false,
        'maintainStepHistory'  => isset($multiStepSettings->maintainStepHistory) ? $multiStepSettings->maintainStepHistory : false,
        'saveProgress'         => isset($multiStepSettings->saveProgress) ? $multiStepSettings->saveProgress : false,
        'showPercentage'       => isset($multiStepSettings->progressSettings->showPercentage) ? $multiStepSettings->progressSettings->showPercentage : false,
      ];
      $bitFormFrontArr['formInfo'] = (object) [
        'multiStepSettings' => $newTempSettings
      ];
    }

    if (Helpers::property_exists_nested($formInfo, 'conversationalSettings->enable', true)) {
      if (!isset($bitFormFrontArr['formInfo'])) {
        $bitFormFrontArr['formInfo'] = new \stdClass();
      }
      $bitFormFrontArr['formInfo']->conversationalSettings = $formInfo->conversationalSettings;
    }

    $formAbandonmentSettings = $FrontendFormManager->getFormAbandonmentSettings();
    if (Helpers::property_exists_nested($formAbandonmentSettings, 'active', true)) {
      $bitFormFrontArr['formSettings'] = (object)[
        'formAbandonment' => $formAbandonmentSettings
      ];
    }

    $layout = wp_json_encode($layout);
    $buttons = wp_json_encode($buttons);
    $frontArr = wp_json_encode($bitFormFrontArr);

    $bfGlobals = sprintf('   
      if(!window.bf_globals) { 
        window.bf_globals = {} 
      } if(!window.bf_globals.%1$s) { 
        window.bf_globals.%1$s = {} 
      }
      if(document.getElementById("%1$s")) {
        window.bf_globals.%1$s = { 
          ...window.bf_globals.%1$s, 
          ...%2$s
        };
      }', $FormIdentifier, $frontArr);

    if ('conversational' === $formType
    && isset($formContent->formInfo->conversationalSettings->enable)
    && $formContent->formInfo->conversationalSettings->enable) {
      $html = $FrontendFormManager->conversationalFormView($fields, $file, $errorMessages);
    } else {
      $html = $FrontendFormManager->formView($fields, $file, $errorMessages);
    }

    // if form preview then return html otherwise echo with output buffer
    if ($formPreview) {
      ob_clean();
      $formViewObject = new \stdClass();
      $formViewObject->html = $html;
      $formViewObject->font = $font;
      $formViewObject->bfGlobals = $bfGlobals;
      $formViewObject->formContent = $formContent;
      return $formViewObject;
    }

    $bfGlobalsHandle = 'bitform-bf-globals-' . sanitize_key($FormIdentifier);
    $this->addInlineScript($bfGlobals, $bfGlobalsHandle, 'after');
    $this->emitShowPickerBridge();

    echo wp_kses(trim($html), EscapingHelper::getFormAllowedHtml($formContent));
    return ob_get_clean();
  }

  /**
   * Build the smart-tag map exposed to the browser in window.bf_globals[formId].smartTags.
   *
   * Security: the legacy code shipped the ENTIRE ~43-tag map to every visitor, leaking
   * PII (admin/user/author email) and freezing per-visitor request data (IP, time,
   * browser, referer) into cacheable HTML. We now emit ONLY tags that are (a) actually
   * referenced by this form's client-evaluated surfaces — conditional logic, payment
   * notes, admin custom JS — AND (b) flagged frontend-safe in the registry (static/post
   * context only). Sensitive (identity) and request/visitor tags are never emitted; they
   * resolve server-side at submit time instead.
   *
   * @param int|string $formID
   * @param mixed      $workflowConditions on-field input conditions (client-evaluated)
   * @param mixed      $fields             form fields object (carries payment notes, etc.)
   * @return array<string,string>
   */
  private function buildFrontendSmartTags($formID, $workflowConditions, $fields)
  {
    // Haystack = only surfaces the browser actually evaluates against smartTags.
    $haystack = wp_json_encode($workflowConditions) . ' ' . wp_json_encode($fields);
    $customJs = FrontEndScriptGenerator::getCustomCodes($formID)['JavaScript'];
    if (is_string($customJs) && '' !== $customJs) {
      $haystack .= ' ' . $customJs;
    }

    $ctx = SmartTags::getPostUserData();
    $frontendSmartTags = [];
    $referenced = [];
    foreach (SmartTags::smartTagFieldKeys() as $key) {
      if (!SmartTagRegistry::isFrontendExposable($key)) {
        continue; // identity / request / param tags never travel to the browser
      }
      // Match '${' . key prefix so keys containing spaces/slashes/commas are handled.
      if (false !== strpos($haystack, '${' . $key)) {
        $referenced[]   = $key;
        $frontendSmartTags[$key] = SmartTagRegistry::resolve($key, $ctx);
      }
    }

    /**
     * Escape hatch: a site that genuinely needs an extra tag client-side can opt it
     * back in explicitly here, rather than core shipping everything by default.
     *
     * @param array<string,string> $frontendSmartTags resolved frontend-safe smart tags
     * @param int|string           $formID
     * @param string[]             $referenced keys detected in client surfaces
     */
    return apply_filters('bitform_frontend_smarttags', $frontendSmartTags, $formID, $referenced);
  }

  /**
   * Delegated listener that opens the native picker on date/time inputs marked
   * with data-bf-show-picker. Replaces the legacy hardcoded onclick attribute.
   * Registered as inline script once per request via wp_add_inline_script so
   * the markup never travels through wp_kses().
   */
  private function emitShowPickerBridge()
  {
    static $emitted = false;
    if ($emitted) {
      return;
    }
    $emitted = true;
    $code = 'if(!window.__bfShowPickerBound){window.__bfShowPickerBound=true;document.addEventListener("click",function(e){var t=e.target;if(t&&t.matches&&t.matches("input[data-bf-show-picker=\"1\"]")&&typeof t.showPicker==="function"){try{t.showPicker();}catch(_){}}});}';
    $this->addInlineScript($code, 'bitform-show-picker-bridge', 'after');
  }

  private function isExist($formID)
  {
    $formModel = new FormModel();
    $form = $formModel->get(
      [
        'id'
      ],
      [
        'id' => $formID,
      ]
    );
    if (!is_wp_error($form)) {
      return true;
    }
    return false;
  }

  private function getFieldsValue($formID, $entryID)
  {
    $FrontendFormManager = FrontendFormManager::getInstance($formID, 1);
    $formEntryModel = new FormEntryMetaModel();
    $metaValues = $formEntryModel->get(
      [
        'meta_key',
        'meta_value'
      ],
      [
        'bitforms_form_entry_id' => $entryID,
      ]
    );
    $formFields = $FrontendFormManager->getFields();
    $fldsData = (object) [];
    if (!is_wp_error($metaValues)) {
      foreach ($metaValues as $metaValue) {
        $metaKey = $metaValue->meta_key;
        $metaVal = $metaValue->meta_value;
        // if meta value is array then convert to string
        if (preg_match('/^\[.*\]$/', $metaVal)) {
          $metaVal = json_decode($metaVal);
          //check is it array of objects
          if (is_array($metaVal) && is_object($metaVal[0])) {
            $metaVal = $metaValue->meta_value;
          } else {
            $metaVal = implode(BITFORMS_BF_SEPARATOR, $metaVal);
          }
        }
        if (!isset($fldsData->{$metaKey})) {
          $fldsData->{$metaKey} = '';
        }
        $fldsData->{$metaKey} = $metaVal;
        if (isset($formFields[$metaKey]['type']) && in_array($formFields[$metaKey]['type'], ['file-up', 'advanced-file-up'])) {
          $fldsData->{$metaKey} = $metaValue->meta_value;
        }
      }
    }

    return $fldsData;
  }

  public function setFieldsValue($fields, $formID, $entryID)
  {
    $formEntryModel = new FormEntryMetaModel();
    $metaValues = $formEntryModel->get(
      [
        'meta_key',
        'meta_value'
      ],
      [
        'bitforms_form_entry_id' => $entryID,
      ]
    );
    if (!is_wp_error($metaValues)) {
      foreach ($metaValues as $metaValue) {
        $metaKey = $metaValue->meta_key;
        $metaVal = $metaValue->meta_value;
        // if meta value is array then convert to string
        if (preg_match('/^\[.*\]$/', $metaVal)) {
          $metaVal = json_decode($metaVal);
          //check is it array of objects
          if (is_array($metaVal) && is_object($metaVal[0])) {
            $metaVal = $metaValue->meta_value;
          } else {
            $metaVal = implode(BITFORMS_BF_SEPARATOR, $metaVal);
          }
        }
        if (property_exists($fields, $metaKey)) {
          $fields->{$metaKey}->val = $metaVal;
          if ('file-up' === $fields->{$metaKey}->typ || 'advanced-file-up' === $fields->{$metaKey}->typ) {
            $fields->{$metaKey}->val = $metaValue->meta_value;
            $fields->{$metaKey}->config->oldFiles = $metaValue->meta_value;
            $urlQuery = wp_parse_url(FileDownloadProvider::getBaseDownloadURL(), PHP_URL_QUERY);
            $baseDLURL = FileDownloadProvider::getBaseDownloadURL();
            $baseDLURL = empty($urlQuery) ? $baseDLURL . '?' : $baseDLURL . '&';
            $fields->{$metaKey}->config->baseDLURL = $baseDLURL . "formID={$formID}&entryID={$entryID}";
          }
        }
      }
    }
    return $fields;
  }

  public function loadAssets($formID = 0, $fromType = 'classic')
  {
    $bfUniqFormIds = FrontendHelpers::getAllFormIdsInPage();
    $isPageBuilder = FrontendHelpers::$isPageBuilder;
    $bfMultipleFormsExists = $isPageBuilder ? true : count($bfUniqFormIds) > 1;

    if (!empty($formID)) {
      $formIds = [$formID];
    } else {
      $formIds = $bfUniqFormIds;
    }
    foreach ($formIds as $formID) {
      global $bitform_dequeued_styles;
      if (is_array($bitform_dequeued_styles) && in_array($formID, $bitform_dequeued_styles)) {
        continue;
      }
      if ($bfMultipleFormsExists) {
        $newFormId = $formID . '-formid';
      } else {
        $newFormId = $formID;
      }
      $formUpdateVersion = get_option('bitform_form_update_version');
      if (!wp_style_is('bitform-style-' . $newFormId) && is_readable(BITFORMS_CONTENT_DIR . '/form-styles/bitform-' . $newFormId . '.css')) {
        wp_enqueue_style(
          'bitform-style-' . $newFormId,
          BITFORMS_UPLOAD_BASE_URL . "/form-styles/bitform-{$newFormId}.css",
          [],
          $formUpdateVersion
        );
        if ($isPageBuilder) {
          $formStyle = file_get_contents(BITFORMS_CONTENT_DIR . '/form-styles/bitform-' . $newFormId . '.css');
          echo '<style id="bitform-style-' . esc_attr((string) $newFormId) . '">' . wp_kses($formStyle, []) . '</style>';
        }
      }
      if (!wp_style_is('bitform-style-custom-' . $formID) && is_readable(BITFORMS_CONTENT_DIR . '/form-styles/bitform-custom-' . $formID . '.css')) {
        wp_enqueue_style(
          'bitform-style-custom-' . $formID,
          BITFORMS_UPLOAD_BASE_URL . "/form-styles/bitform-custom-{$formID}.css",
          [],
          $formUpdateVersion
        );
        if ($isPageBuilder) {
          $formStyle = file_get_contents(BITFORMS_CONTENT_DIR . '/form-styles/bitform-custom-' . $formID . '.css');
          echo '<style id="bitform-style-custom-' . esc_attr((string) $formID) . '">' . wp_kses($formStyle, []) . '</style>';
        }
      }
      // load conversational form css
      if ('conversational' === $fromType) {
        if (!wp_style_is('bitform-conversational-style-' . $formID) &&
        is_readable(BITFORMS_CONTENT_DIR . "/form-styles/bitform-conversational-{$formID}.css")) {
          wp_enqueue_style(
            'bitform-conversational-style',
            BITFORMS_UPLOAD_BASE_URL . "/form-styles/bitform-conversational-{$formID}.css",
            [],
            $formUpdateVersion
          );
        }
      }
    }
  }
}
