<?php

namespace GriffinForms\Admin\App\Html\Widgets\RightSidebar;

final class ItemSummary extends \GriffinForms\Admin\App\Html\Widgets\RightSidebar\Format
{
  protected $item_type;
  protected $delete_guard_message = '';
  protected $field_type_available = null;
  
  public function getHtml()
  {
      $title = __('Summary', 'griffinforms-form-builder');
      $this->accordionHtml($title);
  }

  protected function accordionBody()
  {
      echo $this->getPlaceholderHtml();
  }

  public function widgetJs()
  {
    $this->showSummaryJs();
    $this->hideSummaryJs();
    $this->deleteItemBtnRequestJs();
    $this->removeFieldBtnRequestJs();
  }

  public function getItemSummaryContent()
  {
      $nonce_status = isset($_POST['nonce']) ? wp_verify_nonce(sanitize_text_field(wp_unslash($_POST['nonce'])), 'item_summary') : false;
      
      if ($nonce_status === false) {
          $this->nonceError();
      }

      $this->item_id = isset($_POST['item_id']) ? absint(wp_unslash($_POST['item_id'])) : 0;
      $this->item_type = isset($_POST['item_type']) ? sanitize_text_field(wp_unslash($_POST['item_type'])) : '';
      $parent_id = isset($_POST['parent_id']) ? absint(wp_unslash($_POST['parent_id'])) : 0;
      $parent_type = isset($_POST['parent_type']) ? sanitize_text_field(wp_unslash($_POST['parent_type'])) : '';

      if ($this->loadItem() === false) {
        echo esc_html(__('This item no longer exists.', 'griffinforms-form-builder'));
        wp_die();
      }

      $this->delete_guard_message = $this->getMinimumStructureDeleteGuardMessage($this->item_type, $this->item_id);

      $this->itemSummary();
      $this->itemActions();

      wp_die();
  }

  private function loadItem()
  {
    $item_class = 'GriffinForms\Items\\' . $this->item_type;
    $this->item = new $item_class();
    return (bool) $this->item->loadItem($this->item_id);
  }

  protected function itemSummary()
  {
    echo '<div class="p-3 mb-3">';
    $this->itemId();
    $this->itemType();
    $this->itemName();
    $this->fieldType(); // only for fields
    $this->isItemReusable();
    $this->itemCreatedBy();
    $this->itemCreatedOn();
    echo '</div>';
  }

  protected function itemId()
  {
    $this->propRow(__('ID', 'griffinforms-form-builder'), $this->item_id);
  }

  protected function itemType()
  {
    $type = $this->config->getFriendlyName($this->item_type);
    $this->propRow(__('Type', 'griffinforms-form-builder'), $type);
  }

  protected function itemName()
  {
    $this->propRow(__('Name', 'griffinforms-form-builder'), $this->item->getProp('name'));
  }

  protected function fieldType()
  {
    if ($this->item_type === 'field') {
      $fieldType = strtolower($this->item->getProp('field_type'));
      $friendlyFieldType = $this->config->getFriendlyName($fieldType);
      $this->propRow(__('Field Type', 'griffinforms-form-builder'), $friendlyFieldType);
      $this->propRow(__('Editor State', 'griffinforms-form-builder'), $this->fieldTypeAvailable() ? __('Available in Core', 'griffinforms-form-builder') : __('Preserved / unavailable', 'griffinforms-form-builder'));
    }
  }

  protected function isItemReusable()
  {
    if ($this->item_type !== 'field') { return;};
    $is_reusable = $this->item->getProp('is_reusable') ? __('Yes', 'griffinforms-form-builder') : __('No', 'griffinforms-form-builder');

    $this->propRow(__('Reusable', 'griffinforms-form-builder'), $is_reusable);

  }

  protected function itemCreatedBy()
  {
    $username = __('User not found', 'griffinforms-form-builder');
    $user_id = absint($this->item->getProp('author'));
    $user = get_user_by('id', $user_id);

    if ($user !== false) {
        $username = $user->display_name;
    }

    if ($user_id === get_current_user_id()) {
      // translators: %s refers to the current username
      $username = sprintf(__('Me – %s', 'griffinforms-form-builder'), $username);
  }

    $this->propRow(__('Created By', 'griffinforms-form-builder'), $username);
  }

  protected function itemCreatedOn()
  {
    $date = wp_date($this->config->getDateTimeFormat(), strtotime($this->item->getProp('created')));
    $this->propRow(__('Created On', 'griffinforms-form-builder'), $date);
  }

  protected function itemActions()
  {
    echo '<div class="mt-2 d-flex justify-content-start p-2">';
    $this->deleteItemBtn();
    $this->removeFieldBtn();
    $this->editItemBtn();
    echo '</div>';
  }

  protected function deleteItemBtn()
  {
      if ($this->item_type === "field" and $this->item->getProp('is_reusable')) {
          return;
      }

      $data_attrs = [
          'data-gf-itemtype=' . $this->item_type,
          'data-gf-itemid=' . $this->item_id
      ];

      if ($this->delete_guard_message !== '') {
          $data_attrs[] = 'data-gf-guardmsg=' . rawurlencode($this->delete_guard_message);
          $data_attrs[] = 'data-gf-disabled=1';
      }

      $this->buttons->bsBtn([
          'class' => 'gf-btn-light btn-sm flex-fill me-1' . ($this->delete_guard_message !== '' ? ' disabled' : ''),
          'id'    => $this->getHtmlId('delete-item-btn'),
          // Translators: %s refers to the friendly name of the item type (e.g., Field, Page).
          'label' => sprintf(__('Delete %s', 'griffinforms-form-builder'), $this->config->getFriendlyName($this->item_type)),
          'spinner' => true,
          'aria' => $this->delete_guard_message !== '' ? ['aria-disabled' => 'true'] : [],
          'data'  => $data_attrs
      ]);
  }

  protected function removeFieldBtn()
  {
      if ($this->item_type === "field" and $this->item->getProp('is_reusable')) {
          $this->buttons->bsBtn([
              'class' => 'gf-btn-light btn-sm flex-fill me-1',
              'id'    => $this->getHtmlId('remove-field-btn'),
              // translators: %s refers to the friendly name of the item type (e.g., Field, Page)
              'label' => sprintf(__('Remove %s', 'griffinforms-form-builder'), $this->config->getFriendlyName($this->item_type)),
              'spinner' => true,
              'data'  => [
                  'data-gf-itemtype=' . $this->item_type,
                  'data-gf-itemid=' . $this->item_id
              ]
          ]);
      }
  }

  protected function editItemBtn()
  {
      if ($this->item_type === "rowcolumn") {
          return;
      }

      $link = admin_url() . 'admin.php?page=griffinforms-' . $this->item_type . '&id=' . $this->item_id . '&return=1';

      if ($this->item_type === 'field') {
          $link .= '&field-type=' . $this->item->getProp('field_type');
      }
      $form_id = $this->resolveFormIdFromContext([
          'item_type' => $this->item_type,
          'item_id' => $this->item_id
      ]);
      if ($form_id > 0) {
          $link .= '&form_id=' . $form_id;
      }

      $is_available = !($this->item_type === 'field' && !$this->fieldTypeAvailable());

      $this->buttons->bsBtn([
          'class'   => 'gf-btn-light btn-sm flex-fill' . ($is_available ? '' : ' disabled'),
          'id'      => $this->getHtmlId('edit-item-btn'),
          // translators: %s refers to the friendly name of the item type
          'label'   => $is_available
              ? sprintf(__('Edit %s', 'griffinforms-form-builder'), $this->config->getFriendlyName($this->item_type))
              : __('Field editor unavailable', 'griffinforms-form-builder'),
          'spinner' => true,
          'data'    => [
              'data-gf-itemtype=' . $this->item_type,
              'data-gf-itemid=' . $this->item_id,
              'data-gf-disabled=' . ($is_available ? '0' : '1')
          ],
          'link'    => $is_available ? $link : ''
      ]);
  }

  protected function fieldTypeAvailable(): bool
  {
      if ($this->item_type !== 'field') {
          return true;
      }

      if ($this->field_type_available !== null) {
          return (bool) $this->field_type_available;
      }

      $field_type = strtolower((string) $this->item->getProp('field_type'));
      $this->field_type_available = $this->config->isRegisteredFieldType($field_type);
      return (bool) $this->field_type_available;
  }

  /**
    * When a form element is selected, it sends request to fetch its summary.
    * @see getItemSummaryHtml()
    */
  private function showSummaryJs()
  {
      $item_summary_nonce = wp_create_nonce('item_summary');

      echo '
      document.addEventListener("item-selected", () => {

          $("#' . esc_attr($this->getHtmlId()) . '").show();
          $("#' . esc_attr($this->getHtmlId('spinner')) . '").show();
          $("#' . esc_attr($this->getHtmlId('content')) . '").find(".accordion-body").html(`' . addslashes($this->getPlaceholderHtml()) . '`);

          let selectedItem = $("[data-gf-selected=true][data-gf-itemtype][data-gf-itemid]").first();
          if (!selectedItem.length) {
              // fallback: find child with data attributes inside any selected wrapper
              const selectedWrapper = $("[data-gf-selected=true]").first();
              if (selectedWrapper.length) {
                  selectedItem = selectedWrapper.find("[data-gf-itemtype][data-gf-itemid]").first();
              }
          }

          const selectedType = selectedItem.attr("data-gf-itemtype") || "";
          const selectedId = selectedItem.attr("data-gf-itemid") || "";
          if (!selectedType || !selectedId) {
              $("#' . esc_attr($this->getHtmlId()) . '").hide();
              $("#' . esc_attr($this->getHtmlId('spinner')) . '").hide();
              return;
          }

          const data = {
              "action": "getItemSummaryContent",
              "item_type": selectedType,
              "item_id": selectedId,
              "nonce": "' . esc_js($item_summary_nonce) . '"
          };

          $.post(ajaxurl, data, function(response) {
              $("#' . esc_attr($this->getHtmlId('content')) . '").find(".accordion-body").html(response);
              $("#' . esc_attr($this->getHtmlId('spinner')) . '").hide();
          }).fail(function() {
              $("#' . esc_attr($this->getHtmlId('content')) . '").find(".accordion-body").html("<div class=\"gf-notice gf-notice-danger\">' . esc_js(__('Failed to load item summary. Please try again.', 'griffinforms-form-builder')) . '</div>");
              $("#' . esc_attr($this->getHtmlId('spinner')) . '").hide();
          });

      });
      ' . PHP_EOL;
  }

  private function hideSummaryJs()
  {
      echo '
      document.addEventListener("item-deselected", () => {
          $("#' . esc_attr($this->getHtmlId()) . '").hide();
          $("#' . esc_attr($this->getHtmlId()) . '").find(".accordion-body").html("");
      });
      ' . PHP_EOL;
  }

  protected function deleteItemBtnRequestJs()
  {
    echo '
    let wrapperId = "#' . esc_html($this->getHtmlId('')) . '";
    let btnId = "#' . esc_html($this->getHtmlId('delete-item-btn')) . '";
    
    $(wrapperId).on("click", btnId, function(){
      const guardMsgEncoded = $(this).attr("data-gf-guardmsg") || "";
      if (guardMsgEncoded !== "") {
        const guardMsg = decodeURIComponent(guardMsgEncoded);
        alert(guardMsg);
        statusbarMsg("failure", guardMsg);
        return;
      }
    
      showBtnSpinner(btnId);

      let selectedItem = $("[data-gf-selected=true][data-gf-itemtype][data-gf-itemid]").first();
      if (!selectedItem.length) {
        const wrapper = $("[data-gf-selected=true]").first();
        if (wrapper.length) {
          selectedItem = wrapper.find("[data-gf-itemtype][data-gf-itemid]").first();
        }
      }

      const data = {
        "action": "deleteSelectedItem",
        "item_type": selectedItem.attr("data-gf-itemtype") || "",
        "parent_type": selectedItem.attr("data-gf-parenttype"),
        "item_id": selectedItem.attr("data-gf-itemid"),
        "parent_id": selectedItem.attr("data-gf-parentid"),
        "form_id": $(".griffinforms-app-formlayout-form").attr("data-gf-itemid"),
        "nonce": "' . esc_js(wp_create_nonce('delete_selected_item')) . '"
      };

      $.post(ajaxurl, data, function(response) {
        hideBtnSpinner(btnId);
        response = JSON.parse(response);
        
        if (response.success) {
          statusbarMsg("success", response.msg);
          deselectAllFormElements();

          if (data.item_type === "formpage") {
            selectedItem.parent().remove();
          }

          if (data.item_type === "rowcolumn") {
            let column = $(".griffinforms-app-formlayout-rowcolumn[data-gf-itemid=" + data.item_id + "]");
            column.width("0px");
            toggleCreateRowColumnBtn(column);
          }

          selectedItem.remove();
          document.dispatchEvent(formHtmlReceived);

        } else {
          statusbarMsg("failure", response.msg);
        }

      });

    });
    ' . PHP_EOL;
  }

  protected function removeFieldBtnRequestJs()
  {
    echo '
    wrapperId = "#' . esc_html($this->getHtmlId('')) . '";
    btnId = "#' . esc_html($this->getHtmlId('remove-field-btn')) . '";
    
    $(wrapperId).on("click", btnId, function(){
    
      showBtnSpinner(btnId);

      const selectedItem = $("[data-gf-selected=true]");
    
      const data = {
        "action": "removeSelectedField",
        "field_id": selectedItem.attr("data-gf-itemid"),
        "column_id": selectedItem.parents(".griffinforms-app-formlayout-rowcolumn").attr("data-gf-itemid"),
        "form_id": $(".griffinforms-app-formlayout-form").attr("data-gf-itemid"),
        "nonce": "' . esc_js(wp_create_nonce('remove_selected_field')) . '"
      };

      $.post(ajaxurl, data, function(response) {
        hideBtnSpinner(btnId);
        response = JSON.parse(response);
        
        if (response.success) {
          statusbarMsg("success", response.msg);
          deselectAllFormElements();
          selectedItem.remove();
          document.dispatchEvent(formHtmlReceived);
        } else {
          statusbarMsg("failure", response.msg);
        }
      });
    });
    ' . PHP_EOL;
  }

  public function deleteSelectedItem()
  {
      $this->checkNonce('delete_selected_item');
      $item_type = isset($_POST['item_type']) ? sanitize_text_field(wp_unslash($_POST['item_type'])) : '';
      $parent_type = isset($_POST['parent_type']) ? sanitize_text_field(wp_unslash($_POST['parent_type'])) : '';
      $item_id = isset($_POST['item_id']) ? absint(wp_unslash($_POST['item_id'])) : 0;
      $parent_id = isset($_POST['parent_id']) ? absint(wp_unslash($_POST['parent_id'])) : 0;

      $this->item_type = $item_type;
      $this->item_id = $item_id;

      if (!$this->loadItem()) {
          $this->response['success'] = false;
          $this->response['msg'] = __('This item no longer exists.', 'griffinforms-form-builder');
          echo wp_json_encode($this->response);
          wp_die();
      }
      
      $sql_method = 'delete' . $item_type;
      if (!method_exists($this->sql, $sql_method)) {
          $this->response['success'] = false;
          $this->response['msg'] = sprintf(__('Unable to delete %s.', 'griffinforms-form-builder'), $this->config->getFriendlyName($item_type));
          echo wp_json_encode($this->response);
          wp_die();
      }

      $guard_message = $this->getMinimumStructureDeleteGuardMessage($item_type, $item_id);
      if ($guard_message !== '') {
          $this->response['success'] = false;
          $this->response['msg'] = $guard_message;
          echo wp_json_encode($this->response);
          wp_die();
      }

      // Most delete methods take (id, parent_id) except deleteForm which can accept a second flag.
      if ($sql_method === 'deleteForm') {
          $result = $this->sql->deleteForm($item_id, false);
      } else {
          $result = $this->sql->$sql_method($item_id, $parent_id);
      }
      
      if ($result) {
          $this->response['success'] = true;
          // translators: %s refers to the friendly name of the item type
          $this->response['msg'] = sprintf(__(' %s deleted successfully!', 'griffinforms-form-builder'), $this->config->getFriendlyName($item_type));
          $this->refreshFormLayoutMetadata(
              $this->resolveFormIdFromContext([
                  'form_id' => isset($_POST['form_id']) ? absint(wp_unslash($_POST['form_id'])) : 0,
                  'parent_type' => $parent_type,
                  'parent_id' => $parent_id,
                  'item_type' => $item_type,
                  'item_id' => $item_id
              ]),
              'builder_delete_item',
              array(
                  'parent_type' => $parent_type,
                  'parent_id' => $parent_id,
                  'item_type' => $item_type,
                  'item_id' => $item_id
              )
          );
      } else {
          $this->response['success'] = false;
          // translators: %s refers to the friendly name of the item type
          $this->response['msg'] = sprintf(__(' Unable to delete %s.', 'griffinforms-form-builder'), $this->config->getFriendlyName($item_type));
      }

      echo wp_json_encode($this->response);
      wp_die();
  }

  public function removeSelectedField()
  {
      $this->checkNonce('remove_selected_field');
      $field_id = isset($_POST['field_id']) ? absint(wp_unslash($_POST['field_id'])) : 0;
      $column_id = isset($_POST['column_id']) ? absint(wp_unslash($_POST['column_id'])) : 0;
      
      $result = $this->sql->removeField($column_id, $field_id);

      if ($result) {
        $this->response['success'] = true;
        $this->response['msg'] = __('Field successfully removed from the form.', 'griffinforms-form-builder');
        $this->refreshFormLayoutMetadata(
            $this->resolveFormIdFromContext([
                'form_id' => isset($_POST['form_id']) ? absint(wp_unslash($_POST['form_id'])) : 0,
                'column_id' => $column_id
            ]),
            'builder_remove_field',
            array(
                'column_id' => $column_id,
                'field_id' => $field_id
            )
        );
      } else {
        $this->response['success'] = false;
        $this->response['msg'] = __('Unable to remove field from the form.', 'griffinforms-form-builder');
      }

      echo wp_json_encode($this->response);
      wp_die();
  }

  public function __construct()
  {
      $this->setAll();
      add_action('wp_ajax_getItemSummaryContent', array($this, 'getItemSummaryContent'));
      add_action('wp_ajax_deleteSelectedItem', array($this, 'deleteSelectedItem'));
      add_action('wp_ajax_removeSelectedField', array($this, 'removeSelectedField'));
  }

  private function resolveFormIdFromContext(array $context = []): int
  {
      if (!empty($context['form_id'])) {
          return (int) $context['form_id'];
      }

      if (!empty($context['parent_type']) && !empty($context['parent_id'])) {
          $parent_type = (string) $context['parent_type'];
          $parent_id = (int) $context['parent_id'];
          if ($parent_type === 'form') {
              return $parent_id;
          }
          if ($parent_type === 'formpage') {
              return $this->findFormIdByPageId($parent_id);
          }
          if ($parent_type === 'pagerow') {
              return $this->findFormIdByRowId($parent_id);
          }
          if ($parent_type === 'rowcolumn') {
              return $this->findFormIdByColumnId($parent_id);
          }
      }

      if (!empty($context['item_type']) && !empty($context['item_id'])) {
          $item_type = (string) $context['item_type'];
          $item_id = (int) $context['item_id'];
          if ($item_type === 'form') {
              return $item_id;
          }
          if ($item_type === 'field') {
              return $this->findFormIdByFieldId($item_id);
          }
          if ($item_type === 'formpage') {
              return $this->findFormIdByPageId($item_id);
          }
          if ($item_type === 'pagerow') {
              return $this->findFormIdByRowId($item_id);
          }
          if ($item_type === 'rowcolumn') {
              return $this->findFormIdByColumnId($item_id);
          }
      }

      if (!empty($context['column_id'])) {
          return $this->findFormIdByColumnId((int) $context['column_id']);
      }

      return 0;
  }

  private function findFormIdByFieldId(int $field_id): int
  {
      if ($field_id <= 0) {
          return 0;
      }

      global $wpdb;
      $column_table = $this->config->getTable('rowcolumn');
      $like_str = '%' . $wpdb->esc_like((string) $field_id) . '%';
      $column_id = $wpdb->get_var(
          $wpdb->prepare(
              "SELECT id FROM {$column_table} WHERE fields LIKE %s LIMIT 1",
              $like_str
          )
      );

      return $column_id ? $this->findFormIdByColumnId((int) $column_id) : 0;
  }

  private function findFormIdByPageId(int $page_id): int
  {
      if ($page_id <= 0) {
          return 0;
      }

      global $wpdb;
      $page_table = $this->config->getTable('formpage');
      if ($this->tableHasColumn($page_table, 'form_id')) {
          $direct = $wpdb->get_var(
              $wpdb->prepare(
                  "SELECT form_id FROM %i WHERE id = %d",
                  $page_table,
                  $page_id
              )
          );
          if (!empty($direct)) {
              return (int) $direct;
          }
      }

      $table = $this->config->getTable('form');
      $like_str = '%' . $wpdb->esc_like((string) $page_id) . '%';
      $form_id = $wpdb->get_var(
          $wpdb->prepare(
              "SELECT id FROM %i WHERE formpages LIKE %s LIMIT 1",
              $table,
              $like_str
          )
      );

      return $form_id ? (int) $form_id : 0;
  }

  private function findFormIdByRowId(int $row_id): int
  {
      if ($row_id <= 0) {
          return 0;
      }

      global $wpdb;
      $row_table = $this->config->getTable('pagerow');
      if ($this->tableHasColumn($row_table, 'form_id')) {
          $direct = $wpdb->get_var(
              $wpdb->prepare(
                  "SELECT form_id FROM %i WHERE id = %d",
                  $row_table,
                  $row_id
              )
          );
          if (!empty($direct)) {
              return (int) $direct;
          }
      }

      $page_table = $this->config->getTable('formpage');
      $like_str = '%' . $wpdb->esc_like((string) $row_id) . '%';
      $page_id = $wpdb->get_var(
          $wpdb->prepare(
              "SELECT id FROM %i WHERE pagerows LIKE %s LIMIT 1",
              $page_table,
              $like_str
          )
      );

      return $page_id ? $this->findFormIdByPageId((int) $page_id) : 0;
  }

  private function findPageIdByRowId(int $row_id): int
  {
      if ($row_id <= 0) {
          return 0;
      }

      global $wpdb;
      $page_table = $this->config->getTable('formpage');
      $like_str = '%' . $wpdb->esc_like((string) $row_id) . '%';
      $page_id = $wpdb->get_var(
          $wpdb->prepare(
              "SELECT id FROM %i WHERE pagerows LIKE %s LIMIT 1",
              $page_table,
              $like_str
          )
      );

      return $page_id ? (int) $page_id : 0;
  }

  private function findRowIdByColumnId(int $column_id): int
  {
      if ($column_id <= 0) {
          return 0;
      }

      global $wpdb;
      $row_table = $this->config->getTable('pagerow');
      $like_str = '%' . $wpdb->esc_like((string) $column_id) . '%';
      $row_id = $wpdb->get_var(
          $wpdb->prepare(
              "SELECT id FROM %i WHERE rowcolumns LIKE %s LIMIT 1",
              $row_table,
              $like_str
          )
      );

      return $row_id ? (int) $row_id : 0;
  }

  private function findFormIdByColumnId(int $column_id): int
  {
      if ($column_id <= 0) {
          return 0;
      }

      global $wpdb;
      $column_table = $this->config->getTable('rowcolumn');
      if ($this->tableHasColumn($column_table, 'form_id')) {
          $direct = $wpdb->get_var(
              $wpdb->prepare(
                  "SELECT form_id FROM %i WHERE id = %d",
                  $column_table,
                  $column_id
              )
          );
          if (!empty($direct)) {
              return (int) $direct;
          }
      }

      $row_table = $this->config->getTable('pagerow');
      $like_str = '%' . $wpdb->esc_like((string) $column_id) . '%';
      $row_id = $wpdb->get_var(
          $wpdb->prepare(
              "SELECT id FROM %i WHERE rowcolumns LIKE %s LIMIT 1",
              $row_table,
              $like_str
          )
      );

      return $row_id ? $this->findFormIdByRowId((int) $row_id) : 0;
  }

  private function getMinimumStructureDeleteGuardMessage(string $item_type, int $item_id): string
  {
      $item_type = strtolower(trim($item_type));
      $item_id = absint($item_id);

      if ($item_id <= 0) {
          return '';
      }

      if ($item_type === 'formpage') {
          $form_id = absint($this->item->getProp('form_id'));
          if ($form_id <= 0) {
              $form_id = $this->findFormIdByPageId($item_id);
          }
          $form = $form_id > 0 ? $this->sql->itemValues($form_id, 'form') : null;
          if (!is_object($form)) {
              return '';
          }
          $page_ids = is_object($form) ? $this->maybeDecode($form->formpages) : [];
          $count = is_array($page_ids) ? count($page_ids) : 0;
          if ($count <= 1) {
              return __('You must keep at least one page in the form.', 'griffinforms-form-builder');
          }
      }

      if ($item_type === 'pagerow') {
          $page_id = absint($this->item->getProp('page_id'));
          if ($page_id <= 0) {
              $page_id = $this->findPageIdByRowId($item_id);
          }
          $page = $page_id > 0 ? $this->sql->itemValues($page_id, 'formpage') : null;
          if (!is_object($page)) {
              return '';
          }
          $row_ids = is_object($page) ? $this->maybeDecode($page->pagerows) : [];
          $count = is_array($row_ids) ? count($row_ids) : 0;
          if ($count <= 1) {
              return __('You must keep at least one row in the page.', 'griffinforms-form-builder');
          }
      }

      if ($item_type === 'rowcolumn') {
          $row_id = absint($this->item->getProp('row_id'));
          if ($row_id <= 0) {
              $row_id = $this->findRowIdByColumnId($item_id);
          }
          $row = $row_id > 0 ? $this->sql->itemValues($row_id, 'pagerow') : null;
          if (!is_object($row)) {
              return '';
          }
          $column_ids = is_object($row) ? $this->maybeDecode($row->rowcolumns) : [];
          $count = is_array($column_ids) ? count($column_ids) : 0;
          if ($count <= 1) {
              return __('You must keep at least one column in the row.', 'griffinforms-form-builder');
          }
      }

      return '';
  }

  private function tableHasColumn(string $table, string $column): bool
  {
      static $cache = array();
      $table = (string) $table;
      $column = sanitize_key($column);

      if ($table === '' || $column === '') {
          return false;
      }

      if (!array_key_exists($table, $cache)) {
          global $wpdb;
          $columns = $wpdb->get_col($wpdb->prepare('SHOW COLUMNS FROM %i', $table), 0);
          if (!is_array($columns)) {
              $columns = array();
          }

          $cache[$table] = array_fill_keys(array_map('strval', $columns), true);
      }

      return isset($cache[$table][$column]);
  }

  private function getPlaceholderHtml(): string
  {
      ob_start();
      echo '<div class="p-3">';
      for ($i = 0; $i < 5; $i++) {
          echo '<div class="row mb-2 small align-items-center">';
          echo '<div class="col-md-4 fw-semibold mb-1">';
          echo '<span class="placeholder col-8 bg-secondary"></span>';
          echo '</div>';
          echo '<div class="col-md-8">';
          echo '<span class="placeholder col-10 bg-secondary"></span>';
          echo '</div>';
          echo '</div>';
      }
      echo '</div>';
      return ob_get_clean();
  }

  private function refreshFormLayoutMetadata(int $form_id, string $context = 'unknown', array $meta = array()): void
  {
      if ($form_id <= 0) {
          return;
      }

      global $wpdb;
      $form_table = $this->config->getTable('form');
      $snapshot_table = $this->config->getTable('layout_snapshot');
      $form_row = $wpdb->get_row(
          $wpdb->prepare(
              "SELECT compliance_mode, layout_version, layout_hash FROM %i WHERE id = %d",
              $form_table,
              $form_id
          ),
          ARRAY_A
      );

      if (!is_array($form_row)) {
          return;
      }

      $layout_data = $this->buildLayoutSnapshotData($form_id);
      $new_hash = $layout_data['layout_hash'];
      if ($new_hash === '') {
          return;
      }

      $current_hash = isset($form_row['layout_hash']) ? (string) $form_row['layout_hash'] : '';
      $current_version = isset($form_row['layout_version']) ? (int) $form_row['layout_version'] : 0;
      $updated_version = $current_version;

      if ($new_hash !== $current_hash) {
          $updated_version = $current_version + 1;
          $wpdb->update(
              $form_table,
              array(
                  'layout_version' => $updated_version,
                  'layout_hash' => $new_hash
              ),
              array('id' => $form_id),
              array('%d', '%s'),
              array('%d')
          );

          if (!empty($this->log)) {
              $this->log->add(
                  'info',
                  'form',
                  $form_id,
                  get_current_user_id(),
                  sprintf('Form layout updated to version %d.', $updated_version),
                  'audit'
              );
          }

          if ($layout_data['layout_json'] !== '') {
              $wpdb->insert(
                  $snapshot_table,
                  array(
                      'form_id' => $form_id,
                      'submission_id' => null,
                      'layout_version' => $updated_version,
                      'layout_hash' => $new_hash,
                      'layout_json' => $layout_data['layout_json'],
                      'created_at' => current_time('mysql', true),
                      'created_by' => get_current_user_id()
                  ),
                  array('%d', '%d', '%d', '%s', '%s', '%s', '%d')
              );
          }

          do_action(
              'griffinforms_form_layout_changed',
              $form_id,
              $context,
              array_merge(
                  array(
                      'previous_version' => $current_version,
                      'new_version' => $updated_version,
                      'previous_hash' => $current_hash,
                      'new_hash' => $new_hash,
                      'compliance_mode' => !empty($form_row['compliance_mode']) ? 1 : 0
                  ),
                  $meta
              )
          );
      }
  }

  private function buildLayoutSnapshotData(int $form_id): array
  {
      $result = array(
          'layout_payload' => array(),
          'layout_json' => '',
          'layout_hash' => ''
      );

      if ($form_id <= 0) {
          return $result;
      }

      try {
          $exporter = new \GriffinForms\Includes\ImportExport\FormExport($form_id);
          $payload = $exporter->getFormData();
          if (!is_array($payload)) {
              return $result;
          }

          $payload = $this->normalizeLayoutPayload($payload);
          $result['layout_payload'] = $payload;
          $result['layout_json'] = \GriffinForms\Includes\Helpers\Compliance::canonicalJson($payload);
          $result['layout_hash'] = \GriffinForms\Includes\Helpers\Compliance::hashLayout($payload);
      } catch (\Throwable $t) {
          if (!empty($this->log)) {
              $this->log->add(
                  'warning',
                  'form',
                  $form_id,
                  get_current_user_id(),
                  'Unable to compute form layout snapshot: ' . $t->getMessage(),
                  'audit'
              );
          }
      }

      return $result;
  }

  private function normalizeLayoutPayload(array $payload): array
  {
      $strip_keys = array(
          'author',
          'created',
          'last_editor',
          'last_edited',
          'created_at',
          'updated_at',
          'lifetime_submission_count',
          'layout_version',
          'layout_hash',
          'compliance_mode',
          'compliance_profile',
          'compliance_flags'
      );

      return \GriffinForms\Includes\Helpers\Compliance::stripKeysRecursive($payload, $strip_keys);
  }
}
