/home/silvwabw/www/wp-content/plugins/post-duplicator/includes/mtphr-settings/index.php
<?php
namespace Mtphr\PostDuplicator;

/**
 * Create the class
 */
final class Settings {

  private static $instance;

  private $version = '1.1.3.1';
  private $id = '';
  private $textdomain = 'mtphr-settings';
  private $settings_dir = '';
  private $settings_url = '';
  private $settings_ready = false;
  private $fields_ready = false;

  private $admin_pages = [];
  private $options = [];
  private $sections = [];
  private $settings = [];
  private $values = [];
  private $default_values = [];
  private $sanitize_settings = [];
  private $encryption_settings = [];
  private $type_settings = [];
  private $noupdate_settings = [];
  private $admin_notices = [];
  private $sidebar_items = [];
  private $sidebar_width = '320px';
  private $main_content_max_width = '1000px';
  private $default_sanitizer = 'wp_kses_post';
  private $encryption_key_1 = '7Q@_DvLVTiHPEA';
  private $encryption_key_2 = 'YgM2iCX-BtoBpJ';

  /**
   * Set up the instance
   */
  public static function instance() {
    if ( ! isset( self::$instance ) && ! ( self::$instance instanceof Settings ) ) {	
			self::$instance = new Settings;
      
      // Initialize the ID based on namespace
      if ( empty( self::$instance->id ) ) {
        self::$instance->id = self::$instance->get_namespace_identifier();
      }
      
      // Register WordPress hooks for admin functionality
      add_action( 'admin_menu', array( self::$instance, 'create_admin_pages' ) );
      add_action( 'admin_enqueue_scripts', array( self::$instance, 'enqueue_scripts' ) );
      add_action( 'rest_api_init', array( self::$instance, 'register_routes' ) );
      add_action( 'admin_notices', array( self::$instance, 'admin_notices' ) );

      // Register initialization hooks - fires namespace-specific action hooks
      add_action( 'rest_api_init', array( self::$instance, 'initialize_settings' ), 20 );
      add_action( 'init', array( self::$instance, 'initialize_settings' ), 20 );
      add_action( 'init', array( self::$instance, 'initialize_fields' ), 20 );

      list( $path, $url ) = self::$instance->get_path( dirname( __FILE__ ) );
      self::$instance->settings_dir = $path;
      self::$instance->settings_url = $url;
    }
    return self::$instance;
  }

  /**
   * Throw error on object clone
   */
  public function __clone() {
		_doing_it_wrong( __FUNCTION__, __( 'Cheatin&#8217; huh?', self::$instance->textdomain ), '1.0.0' );
	}

  /**
   * Disable unserializing of the class
   */
  public function __wakeup() {
		_doing_it_wrong( __FUNCTION__, __( 'Cheatin&#8217; huh?', self::$instance->textdomain ), '1.0.0' );
	}

  private function get_path( $path = '' ) {
		// Plugin base path.
		$path       = wp_normalize_path( untrailingslashit( $path ) );
		$themes_dir = wp_normalize_path( untrailingslashit( dirname( get_stylesheet_directory() ) ) );

		// Default URL.
		$url = plugins_url( '', $path . '/' . basename( $path ) . '.php' );

		// Included into themes.
		if (
			0 !== strpos( $path, wp_normalize_path( WP_PLUGIN_DIR ) )
			&& 0 !== strpos( $path, wp_normalize_path( WPMU_PLUGIN_DIR ) )
			&& 0 === strpos( $path, $themes_dir )
		) {
			$themes_url = untrailingslashit( dirname( get_stylesheet_directory_uri() ) );
			$url        = str_replace( $themes_dir, $themes_url, $path );
		}

		$path = trailingslashit( $path );
		$url  = trailingslashit( $url );

		return array( $path, $url );
	}

  /**
   * Init settings
   */
  // public function init_settings( $option, $settings ) {
  //   if ( ! is_array( $settings ) ) {
  //     return false;
  //   }
  //   if ( is_array( $settings ) && ! empty( $settings ) ) {
  //     foreach ( $settings as $setting ) {
  //       $setting['option'] = $option;
  //       self::$instance->process_setting_data( $setting );
  //     }
  //   }
  // }

  /**
   * Set the settings ready
   */
  public function set_settings_ready( $ready ) {
    return self::$instance->settings_ready = boolval( $ready );
  }

  /**
   * Get the settings ready
   */
  public function get_settings_ready() {
    return self::$instance->settings_ready;
  }

  /**
   * Set the fields ready
   */
  public function set_fields_ready( $ready ) {
    return self::$instance->fields_ready = boolval( $ready );
  }

  /**
   * Get the fields ready
   */
  public function get_fields_ready() {
    return self::$instance->fields_ready;
  }

  /**
   * Get namespace identifier from the class namespace
   */
  private function get_namespace_identifier() {
    $reflection = new \ReflectionClass( $this );
    $namespace = $reflection->getNamespaceName();
    
    // Convert namespace to identifier by removing backslashes
    // e.g., "Mtphr\PostDuplicator" -> "MtphrPostDuplicator"
    $identifier = str_replace( '\\', '', $namespace );
    
    // Fallback to 'mtphr' if namespace is empty (shouldn't happen, but safety check)
    return ! empty( $identifier ) ? $identifier : 'mtphr';
  }

  /**
   * Force a string to camel case
   */
  private function to_camel_case( $string ) {
    // Replace non-alphanumeric characters with spaces
    $string = preg_replace( '/[^a-zA-Z0-9]+/', ' ', $string );

    // Uppercase first letter of each word, then remove spaces
    $string = str_replace( ' ', '', ucwords( strtolower( $string ) ) );

    // Lowercase first character for camelCase
    return lcfirst( $string );
  }

  /**
   * Get the custom id for the settings
   */
  public function get_id() {
    return self::$instance->id . 'Settings';
  }

  /**
   * Set a textdomain for the settings
   */
  public function set_textdomain( $textdomain ) {
    self::$instance->textdomain = $textdomain;
  }

  /**
   * Get the textdomain for the settings
   */
  public function get_textdomain() {
    return self::$instance->textdomain;
  }

  /**
   * Set the default sanitizer
   */
  public function set_default_sanitizer( $sanitizer ) {
    self::$instance->default_sanitizer = $sanitizer;
  }

  /**
   * Get the default sanitizer
   */
  public function get_default_sanitizer() {
    return self::$instance->default_sanitizer;
  }

  /**
   * Set the default sanitizer
   */
  public function set_default_encryption_keys( $keys = [] ) {
    if ( isset( $keys['key_1'] ) ) {
      self::$instance->encryption_key_1 = esc_attr( $keys['key_1'] );
    }
    if ( isset( $keys['key_2'] ) ) {
      self::$instance->encryption_key_1 = esc_attr( $keys['key_2'] );
    }
  }

  /**
   * Add an option key
   */
  public function add_admin_page( $admin_page ) {
    if ( ! is_array( $admin_page ) ) {
      return false;
    }
    if ( ! isset( $admin_page['page_title'] ) || ! isset( $admin_page['menu_title'] ) || ! isset( $admin_page['capability'] ) || ! isset( $admin_page['menu_slug'] ) ) {
      return false;
    }
    $admin_pages = self::$instance->get_admin_pages();

    // Check if top level and slug already exists
    if ( ! isset( $admin_page['parent_slug'] ) ) {
      if ( in_array( $admin_page['menu_slug'], array_column( array_filter( $admin_pages, fn( $page ) => ! isset( $page['parent_slug'] ) ), 'menu_slug' ) ) ) {
        return false;
      }

    // Check if submenu and same slug exists with same parent
    } else {
      $exists = array_filter( $admin_pages, function ( $page ) use ( $admin_page ) {
        return isset( $page['parent_slug'] ) 
          && $page['parent_slug'] === $admin_page['parent_slug'] 
          && $page['menu_slug'] === $admin_page['menu_slug'];
      } );
      if ( ! empty( $exists ) ) {
        return false;
      }
    }
    
    $admin_pages[] = $admin_page;
    self::$instance->admin_pages = $admin_pages;
    return self::$instance->admin_pages;
  }

  /**
   * Return available options keys
   */
  public function get_admin_pages( $admin_page = false ) {
    $admin_pages = self::$instance->admin_pages;
    if ( ! is_array( $admin_pages ) ) {
      return false;
    }
    if ( $admin_page ) {
      if ( isset( $admin_pages[$admin_page] ) ) {
        return $admin_pages[$admin_page];
      }
    } else {
      return $admin_pages;
    }
  }

  /**
   * Return available options keys
   */
  public function get_options( $option = false ) {
    $options = self::$instance->options;
    if ( ! is_array( $options ) ) {
      return false;
    }
    if ( $option ) {
      if ( isset( $options[$option] ) ) {
        return $options[$option];
      }
    } else {
      return $options;
    }
  }

  /**
   * Return option keys by sections
   */
  public function get_option_keys( $sections = false ) {
    if ( $sections ) {
      if ( is_array( $sections ) ) {
        if ( ! empty( $sections ) ) {
          $option_keys = [];
          foreach ( $sections as $section ) {
            if ( isset( $section['option'] ) ) {
              $option_keys[$section['option']] = $section['option'];
            }
          }
          return array_values( $option_keys );
        }
      } else {
        if ( isset( $sections['option'] ) ) {
          return $section['option'];
        }
      }
    }
  }

  /**
   * Add an option key
   */
  public function add_section( $section ) {
    $sections = self::$instance->get_sections();
    $options = self::$instance->get_options();

    $id = false;
    $label = false;
    $order = 10;

    if ( ! is_array( $section ) ) {
      return false;
    }
    if ( ! isset( $section['id'] ) || ! isset( $section['slug'] ) || ! isset( $section['menu_slug'] ) ) {
      return false;
    }

    // Check if a section with the same ID already exists
    $ids = array_column( $sections, 'id' ); // Extract all existing IDs
    if ( in_array( $section['id'], $ids ) ) {
      $message = "<p><strong>{$section['id']}</strong> can not be added for <strong>{$section['menu_slug']}</strong>. This section id is already being used with mtphr-settings.</p>";
      self::$instance->add_admin_notice( 'error', $message );
      return false;
    }

    if ( ! isset( $section['label'] ) ) {
      $section['label'] = ucfirst( $section['id'] );
    }
    if ( ! isset( $section['order'] ) ) {
      $section['order'] = $order;
    }
    if ( ! isset( $section['type'] ) ) {
      $section['type'] = 'primary';
    }

    // Check if top level and slug already exists
    if ( ! isset( $section['parent_slug'] ) ) {
      $exists = array_filter( $sections, function ( $s ) use ( $section ) {
        return $s['menu_slug'] === $section['menu_slug'] 
          && $s['id'] === $section['id'];
      } );
      if ( ! empty( $exists ) ) {
        return false;
      }

    // Check if submenu and same slug exists with same parent
    } else {
      $exists = array_filter( $sections, function ( $s ) use ( $section ) {
        return isset( $s['parent_slug'] ) && $s['parent_slug'] === $section['parent_slug']
          && $s['menu_slug'] === $section['menu_slug']
          && $s['id'] === $section['id'];
      } );
      if ( ! empty( $exists ) ) {
        return false;
      }
    }

    $sections[] = $section;
    self::$instance->sections = $sections;

    $section_option = $section['option'];

    // Add to the options array
    if ( ! in_array( $section_option, $options ) ) {
      $options[] = $section_option;
      self::$instance->options = $options;
    }

    return self::$instance->sections;
  }

  /**
   * Return available options keys
   */
  public function get_sections( $page = false ) {
    $sections = self::$instance->sections;
    if ( ! is_array( $sections ) ) {
      return false;
    }
    if ( $page ) {
      $menu_slug = $page['menu_slug'];
      $parent_slug = isset( $page['parent_slug'] ) ? $page['parent_slug'] : false;     
      $page_sections = [];
      if ( is_array( $sections ) && ! empty( $sections ) ) {
        foreach ( $sections as $section ) {
          if ( $menu_slug == $section['menu_slug'] ) {
            if ( $parent_slug ) {
              if ( isset( $section['parent_slug'] ) &&  $parent_slug = $section['parent_slug'] ) {
                $page_sections[] = $section;
              }
            } else {
              $page_sections[] = $section;
            }
          }
        }
      }
      return $page_sections;
    } else {
      return $sections;
    }
  }

  /**
   * Return available options keys
   */
  public function get_section( $id ) {
    $sections = self::$instance->get_sections();
    if ( ! is_array( $sections ) ) {
      return false;
    }
    if ( is_array( $sections ) && ! empty( $sections ) ) {
      foreach ( $sections as $section ) {
        if ( $id == $section['id'] ) {
          return $section;
        }
      }
    }
  }

  /**
   * Add a single field
   */
  private function add_field( $setting ) {
    if ( ! is_array( $setting ) ) {
      return false;
    }

    $sections = self::$instance->get_sections();
    $settings = self::$instance->get_settings();
    if ( empty( $sections ) ) {
      return false;
    }
    $default_section = $sections[0]['id'];
    $default_order = 10;

    if ( ! isset( $setting['section'] ) ) {
      $setting['section'] = $default_section;
    }

    if ( ! isset( $setting['option'] ) ) {
      $section = self::$instance->get_section( $setting['section'] );
      if ( ! $section ) {
        $message = "<p>Section <strong>{$setting['section']}</strong> does not exist.</p>";
        self::$instance->add_admin_notice( 'error', $message );
        return false;
      }
      $setting['option'] = $section['option'];
    }
    
    if ( ! isset( $setting['order'] ) ) {
      $setting['order'] = $default_order;
    }

    // Make sure the section exists
    if ( ! in_array( $setting['section'], array_column( $sections, 'id' ) ) ) {
      return false;
    }
 
    $settings[] = $setting;
    self::$instance->settings = $settings;

    self::$instance->process_setting_data( $setting );

    return $setting;
  }

  /**
   * Add fields
   */
  public function add_fields( $data ) {
    if ( ! is_array( $data ) ) {
      return false;
    }
    if ( ! isset( $data['section'] ) || ! isset( $data['fields'] ) ) {
      return false;
    }

    $updated_settings = [];

    $section = isset( $data['section'] ) ? $data['section'] : false;
    if ( is_array( $data['fields'] ) && ! empty( $data['fields'] ) ) {
      foreach ( $data['fields'] as $key => $field ) {
        $field['section'] = $data['section'];
        if ( $setting = self::$instance->add_field( $field ) ) {
          $updated_settings[] = $setting;
        } 
      }
    }
    
    return $updated_settings;
  }

  /**
   * Add sidebar
   */
  public function add_sidebar( $data ) {
    if ( ! is_array( $data ) ) {
      return false;
    }
    if ( ! isset( $data['items'] ) || ! is_array( $data['items'] ) ) {
      return false;
    }

    // Store sidebar items
    self::$instance->sidebar_items = $data['items'];

    // Store sidebar width if provided
    if ( isset( $data['width'] ) && ! empty( $data['width'] ) ) {
      self::$instance->sidebar_width = esc_attr( $data['width'] );
    }

    // Store main content max-width if provided
    if ( isset( $data['main_max_width'] ) && ! empty( $data['main_max_width'] ) ) {
      self::$instance->main_content_max_width = esc_attr( $data['main_max_width'] );
    }

    return true;
  }

  /**
   * Get sidebar items
   */
  public function get_sidebar_items() {
    return self::$instance->sidebar_items;
  }

  /**
   * Get sidebar width
   */
  public function get_sidebar_width() {
    return self::$instance->sidebar_width;
  }

  /**
   * Get main content max-width
   */
  public function get_main_content_max_width() {
    return self::$instance->main_content_max_width;
  }

  /**
   * Return all settings
   */
  public function get_settings( $sections = false ) {
    $settings = self::$instance->settings;
    if ( $sections ) {
      $section_settings = [];
      if ( is_array( $sections ) && ! empty( $sections ) ) {
        foreach ( $sections as $section ) {
          if ( is_array( $settings ) && ! empty( $settings ) ) {
            foreach ( $settings as $setting ) {
              if ( $setting['section'] == $section['id'] ) {
                $section_settings[] = $setting;
              }
            }
          }
        }
      }
      return $section_settings;
    }
    return $settings;
  }

  /**
   * Process settings data
   */
  private function process_setting_data( $setting, $option = false, $parents = [] ) {
    // Reference to instance storage
    $type_storage =& self::$instance->type_settings;
    //$default_storage =& self::$instance->default_values;
    //$sanitize_storage =& self::$instance->sanitize_settings;
    $encryption_storage =& self::$instance->encryption_settings;
    $noupdate_storage =& self::$instance->noupdate_settings;

    // Extract setting data
    $id = isset( $setting['id'] ) ? $setting['id'] : false;
    $type = isset( $setting['type'] ) ? $setting['type'] : false;
    //$default = isset( $setting['default'] ) ? $setting['default'] : null;
    //$sanitize = isset( $setting['sanitize'] ) ? $setting['sanitize'] : self::$instance->get_default_sanitizer();
    $encrypt = isset( $setting['encrypt'] ) ? $setting['encrypt'] : false;
    $noupdate = isset( $setting['noupdate'] ) ? $setting['noupdate'] : false;
    $option = isset( $setting['option'] ) ? $setting['option'] : $option;

    if ( $option && $id !== false ) {
      // Ensure option storage exists
      if ( !isset( $type_storage[$option] ) ) {
        $type_storage[$option] = [];
      }
      // if ( !isset( $default_storage[$option] ) ) {
      //   $default_storage[$option] = [];
      // }
      // if ( !isset( $sanitize_storage[$option] ) ) {
      //   $sanitize_storage[$option] = [];
      // }
      if ( !isset( $encryption_storage[$option] ) ) {
        $encryption_storage[$option] = [];
      }
      if ( !isset( $noupdate_storage[$option] ) ) {
        $noupdate_storage[$option] = [];
      }

      // Reference correct location in storage
      $type_target =& $type_storage[$option];
      //$default_target =& $default_storage[$option];
      //$sanitize_target =& $sanitize_storage[$option];
      $encryption_target =& $encryption_storage[$option];
      $noupdate_target =& $noupdate_storage[$option];

      // Traverse parents to ensure proper nesting
      if ( !empty( $parents ) ) {
        foreach ( $parents as $parent ) {
          if ( !isset( $type_target[$parent] ) || !is_array( $type_target[$parent] ) ) {
            $type_target[$parent] = [
              '__type' => $type
            ];
          }
          // if ( !isset( $default_target[$parent] ) || !is_array( $default_target[$parent] ) ) {
          //   $default_target[$parent] = [];
          // }
          // if ( !isset( $sanitize_target[$parent] ) || !is_array( $sanitize_target[$parent] ) ) {
          //   $sanitize_target[$parent] = [];
          // }
          if ( !isset( $encryption_target[$parent] ) || !is_array( $encryption_target[$parent] ) ) {
            $encryption_target[$parent] = [];
          }
          if ( !isset( $noupdate_target[$parent] ) || !is_array( $noupdate_target[$parent] ) ) {
            $noupdate_target[$parent] = [];
          }
          $type_target =& $type_target[$parent];
          //$default_target =& $default_target[$parent];
          //$sanitize_target =& $sanitize_target[$parent];
          $encryption_target =& $encryption_target[$parent];
          $noupdate_target =& $noupdate_target[$parent];
        }
      }

      // Assign values at the correct depth
      $type_target[$id] = $type;
      //$default_target[$id] = $default;
      //$sanitize_target[$id] = $sanitize;
      $noupdate_target[$id] = $noupdate;

      // Handle encryption settings
      if ( $encrypt !== false ) {
        if ( is_array( $encrypt ) ) {
          $encryption_target[$id] = [
            'key_1' => isset( $encrypt['key_1'] ) ? esc_attr( $encrypt['key_1'] ) : self::$instance->encryption_key_1,
            'key_2' => isset( $encrypt['key_2'] ) ? esc_attr( $encrypt['key_2'] ) : self::$instance->encryption_key_2,
          ];
        } elseif ( $encrypt === true || $encrypt === 1 ) {
          $encryption_target[$id] = [
            'key_1' => self::$instance->encryption_key_1,
            'key_2' => self::$instance->encryption_key_2,
          ];
        }
      }
    }

    // Process sub-fields if they exist
    if ( !empty( $setting['fields'] ) && is_array( $setting['fields'] ) ) {
      $new_parents = $parents;
      if ( $id !== false ) {
        $new_parents[] = $id;
      }
      foreach ( $setting['fields'] as $field ) {
        self::$instance->process_setting_data( $field, $option, $new_parents );
      }
    }
  }

  /**
   * Add default values
   */
  public function add_default_values( $option, $values = [] ) {
    $default_values = self::$instance->default_values;
    $default_option_values = isset( $default_values[$option] ) ? $default_values[$option] : [];
    if ( is_array( $values ) && ! empty( $values ) ) {
      foreach ( $values as $key => $value ) {
        $default_option_values[$key] = $value;
      }
    }
    $default_values[$option] = $default_option_values;
    self::$instance->default_values = $default_values;

    return $default_values;
  }

  /**
   * Get default values
   */
  public function get_default_values( $options = false ) {
    $default_values = self::$instance->default_values;
    if ( $options ) {
      if ( is_array( $options ) ) {
        if ( ! empty( $options ) ) {
          $values = [];
          foreach ( $options as $option ) {
            $values[$option] = isset( $default_values[$option] ) ? $default_values[$option] : [];
          }
          return $values;
        }
      } else {
        return isset( $default_values[$options] ) ? $default_values[$options] : [];
      }
    }
    return $default_values;
  }

  /**
   * Add sanitize settings
   */
  public function add_sanitize_settings( $option, $values = [] ) {
    $sanitize_settings = self::$instance->sanitize_settings;
    $sanitize_option_settings = isset( $sanitize_settings[$option] ) ? $sanitize_settings[$option] : [];
    if ( is_array( $values ) && ! empty( $values ) ) {
      foreach ( $values as $key => $value ) {
        $sanitize_option_settings[$key] = $value;
      }
    }
    $sanitize_settings[$option] = $sanitize_option_settings;
    self::$instance->sanitize_settings = $sanitize_settings;

    return $sanitize_settings;
  }

  /**
   * Get sanitize settings
   */
  public function get_sanitize_settings( $options = false ) {
    $sanitize_settings = self::$instance->sanitize_settings;
    if ( $options ) {
      if ( is_array( $options ) ) {
        if ( ! empty( $options ) ) {
          $settings = [];
          foreach ( $options as $option ) {
            $settings[$option] = isset( $sanitize_settings[$option] ) ? $sanitize_settings[$option] : [];
          }
          return $settings;
        }
      } else {
        return isset( $sanitize_settings[$options] ) ? $sanitize_settings[$options] : [];
      }
    }
    return $sanitize_settings;
  }

  /**
   * Add encryption settings
   */
  public function add_encryption_settings( $option, $values = [] ) {
    $encryption_settings = self::$instance->encryption_settings;
    $encryption_option_settings = isset( $encryption_settings[$option] ) ? $encryption_settings[$option] : [];
    if ( is_array( $values ) && ! empty( $values ) ) {
      foreach ( $values as $key => $value ) {
        if ( is_array( $value ) ) {
          $encryption_option_settings[$key] = [
            'key_1' => isset( $value['key_1'] ) ? esc_attr( $value['key_1'] ) : self::$instance->encryption_key_1,
            'key_2' => isset( $value['key_2'] ) ? esc_attr( $value['key_2'] ) : self::$instance->encryption_key_2,
          ];
        } elseif ( $value === true || $value === 1 ) {
          $encryption_option_settings[$key] = [
            'key_1' => self::$instance->encryption_key_1,
            'key_2' => self::$instance->encryption_key_2,
          ];
        }
      }
    }
    $encryption_settings[$option] = $encryption_option_settings;
    self::$instance->encryption_settings = $encryption_settings;
    return $encryption_settings;
  }

  /**
   * Get sanitize settings
   */
  public function get_encryption_settings( $options = false ) {
    $encryption_settings = self::$instance->encryption_settings;
    if ( $options ) {
      if ( is_array( $options ) ) {
        if ( ! empty( $options ) ) {
          $settings = [];
          foreach ( $options as $option ) {
            $settings[$option] = isset( $encryption_settings[$option] ) ? $encryption_settings[$option] : [];
          }
          return $settings;
        }
      } else {
        return isset( $encryption_settings[$options] ) ? $encryption_settings[$options] : [];
      }
    }
    return $encryption_settings;
  }
  
  /**
   * Get field type settings
   */
  public function get_type_settings( $options = false ) {
    $type_settings = self::$instance->type_settings;
    if ( $options ) {
      if ( is_array( $options ) ) {
        if ( ! empty( $options ) ) {
          $settings = [];
          foreach ( $options as $option ) {
            $settings[$option] = isset( $type_settings[$option] ) ? $type_settings[$option] : [];
          }
          return $settings;
        }
      } else {
        return isset( $type_settings[$options] ) ? $type_settings[$options] : [];
      }
    }
    return $type_settings;
  }

  /**
   * Get no update settings
   */
  public function get_noupdate_settings( $options = false ) {
    $noupdate_settings = self::$instance->noupdate_settings;
    if ( $options ) {
      if ( is_array( $options ) ) {
        if ( ! empty( $options ) ) {
          $settings = [];
          foreach ( $options as $option ) {
            $settings[$option] = isset( $noupdate_settings[$option] ) ? $noupdate_settings[$option] : [];
          }
          return $settings;
        }
      } else {
        return isset( $noupdate_settings[$options] ) ? $noupdate_settings[$options] : [];
      }
    }
    return $noupdate_settings;
  }

  /**
   * Set option values
   */
  public function set_option_values( $option, $set_values = [] ) {

    $values = self::$instance->values;
    $option_values = isset( $values[$option] ) ? $values[$option] : [];
    if ( is_array( $set_values ) && ! empty( $set_values ) ) {
      foreach ( $set_values as $key => $value ) {
        $option_values[$key] = $value;
      }
    }

    // Update the option
    update_option( $option, $option_values );

    // Update the 
    $values[$option] = $option_values;
    self::$instance->values = $value;

    return $values;
  }

  /**
   * Return an individual options values
   */
  public function get_option_values( $option, $cache = true ) {

    // Make sure values is an array
    if ( ! is_array( self::$instance->values ) ) {
      self::$instance->values = [];
    }
    
    // If the option values have been compiled, return them
    $cache = false;
    if ( isset( self::$instance->values[$option] ) && $cache ) {
      return self::$instance->values[$option];
    }

    // Get the option
    $settings = get_option( $option, [] );
    if ( ! is_array( $settings ) ) {
      $settings = [];
    }

    // Decrypt
    $enc_settings = $this->get_encryption_settings( $option );
    $settings     = $this->decrypt_values( $settings, $enc_settings );

    // Inject defaults
    $parsed_settings = self::$instance->inject_default_values( $settings, $option );
    
    // Sanitize
    $sanitized_settings = self::$instance->sanitize_values( $parsed_settings, $option, 'get' );
    
    // Add to the global values array
    self::$instance->values[$option] = $sanitized_settings;

    return $sanitized_settings;
  }

  /**
   * Return values
   */
  public function get_values( $options = false ) {
    $options = $options ? $options : self::$instance->get_options();
    if ( is_array( $options ) ) {
      if ( ! empty( $options ) ) {
        $values = [];
        foreach ( $options as $option ) {
          $values[$option] = self::$instance->get_option_values( $option );
        }
        return $values;
      }
    } else {
      return self::$instance->get_option_values( $options );
    }

    // If empty, just return an empty array or something suitable
    return [];
  }

  /**
   * Create admin pages
   */
  public function create_admin_pages() {
    $admin_pages = self::$instance->get_admin_pages();
    $updated_admin_pages = [];
    if ( is_array( $admin_pages ) && ! empty( $admin_pages ) ) {
      foreach ( $admin_pages as &$admin_page ) {
        if ( isset( $admin_page['parent_slug'] ) ) {
          $id = add_submenu_page(
            $admin_page['parent_slug'],
            $admin_page['page_title'],
            $admin_page['menu_title'],
            $admin_page['capability'],
            $admin_page['menu_slug'],
            function () use ( $admin_page ) {
              echo '<div class="wrap">';
                echo '<div id="mtphr-settings-app" data-id="' . self::$instance->get_id() . '" data-title="' . esc_attr( $admin_page['page_title'] ) . '"></div>'; // React App will be injected here
              echo '</div>';
            },
            isset( $admin_page['position'] ) ? $admin_page['position'] : null,
          );
          $updated_admin_pages[$id] = $admin_page;
        } else {
          $id = add_menu_page(
            $admin_page['page_title'],
            $admin_page['menu_title'],
            $admin_page['capability'],
            $admin_page['menu_slug'],
            function () use ( $admin_page ) {
              echo '<div class="wrap">';
                echo '<div id="mtphr-settings-app" data-id="' . self::$instance->get_id() . '" data-title="' . esc_attr( $admin_page['page_title'] ) . '"></div>'; // React App will be injected here
              echo '</div>';
            },
            isset( $admin_page['icon'] ) ? $admin_page['icon'] : null,
            isset( $admin_page['position'] ) ? $admin_page['position'] : null,
          );
          $updated_admin_pages[$id] = $admin_page;
        }  
      }
    }
    self::$instance->admin_pages = $updated_admin_pages;
  }

  /**
   * Enqueue admin scripts
   */
  public function enqueue_scripts() {

    // Only load script if on an admin page
    $current_screen = get_current_screen();
    $admin_pages = self::$instance->get_admin_pages();
    if ( ! isset( $admin_pages[$current_screen->id] ) ) {
      return false;
    }

    $admin_page = $admin_pages[$current_screen->id];
    $sections = self::$instance->get_sections( $admin_page );
    $settings = self::$instance->get_settings( $sections );
    $options = self::$instance->get_option_keys( $sections );
    $values = self::$instance->get_values( $options );
    
    // Get header metadata (icon, description, version) from admin page
    // Only escape URL if it's actually a URL (starts with http:// or https://)
    // Otherwise, pass it as-is for dashicons or WordPress icon names
    $header_icon_raw = isset( $admin_page['header_icon'] ) ? $admin_page['header_icon'] : '';
    $header_icon = '';
    if ( $header_icon_raw ) {
      if ( strpos( $header_icon_raw, 'http://' ) === 0 || strpos( $header_icon_raw, 'https://' ) === 0 ) {
        $header_icon = esc_url( $header_icon_raw );
      } else {
        // For dashicons or WordPress icon names, pass as-is (but sanitize)
        $header_icon = sanitize_text_field( $header_icon_raw );
      }
    }
    $header_description = isset( $admin_page['header_description'] ) ? $admin_page['header_description'] : '';
    $header_version = isset( $admin_page['header_version'] ) ? esc_html( $admin_page['header_version'] ) : '';

    // Load the Component Registry first
    $asset_file = include( self::$instance->settings_dir . 'assets/build/mtphrSettingsRegistry.asset.php' );
    wp_enqueue_script(
      self::$instance->get_id() . 'Registry',
      self::$instance->settings_url . 'assets/build/mtphrSettingsRegistry.js',
      $asset_file['dependencies'],
      filemtime( self::$instance->settings_dir . 'assets/build/mtphrSettingsRegistry.js' ),
      true
    );

    // Add a hook for other scripts to register custom fields
    do_action( self::$instance->get_id() . '/enqueue_fields', self::$instance->get_id() . 'Registry' );

    $asset_file = include( self::$instance->settings_dir . 'assets/build/mtphrSettings.asset.php' );
    wp_enqueue_style(
      self::$instance->get_id(),
      self::$instance->settings_url . 'assets/build/mtphrSettings.css',
      ['wp-components'],
      filemtime( self::$instance->settings_dir . 'assets/build/mtphrSettings.css' ),
    );
    wp_enqueue_script(
      self::$instance->get_id(),
      self::$instance->settings_url . 'assets/build/mtphrSettings.js',
      array_unique( array_merge( $asset_file['dependencies'], ['wp-element', 'wp-data', 'wp-components', 'wp-notices'] ) ),
      filemtime( self::$instance->settings_dir . 'assets/build/mtphrSettings.js' ),
      true
    ); 

    wp_add_inline_script( self::$instance->get_id(), self::$instance->get_id() . 'Vars = ' . wp_json_encode( array(
      'siteUrl'        => site_url(),
      'restUrl'        => esc_url_raw( rest_url( self::$instance->get_id() . '\/v1/' ) ),
      'values'         => $values,
      'fields'         => $settings,
      'field_sections' => $sections,
      'sidebar_items'  => self::$instance->get_sidebar_items(),
      'sidebar_width'  => self::$instance->get_sidebar_width(),
      'main_max_width' => self::$instance->get_main_content_max_width(),
      'header_icon'    => $header_icon,
      'header_description' => $header_description,
      'header_version' => $header_version,
      'nonce'          => wp_create_nonce( 'wp_rest' )
    ) ), 'before' ) . ';';
  }

  /**
   * Register rest routes
   */
  public function register_routes() {
    register_rest_route( self::$instance->get_id() . '/v1', 'settings', array(
      'methods' 	=> 'POST',
      'permission_callback' => function () {
        return current_user_can('manage_options');
      },
      'callback' => function( $request ) {
        $args = $request->get_params();
        $data = $request->get_json_params();

        $value_keys = isset( $data['valueKeys'] ) ? $data['valueKeys'] : [];
        $values = isset( $data['values'] ) ? $data['values'] : [];

        if ( is_array( $value_keys ) && ! empty( $value_keys ) ) {
          foreach ( $value_keys as $option => $keys ) {
            $values_to_update = array_intersect_key( $values[$option], array_flip( $keys ) );
            $updated_values = self::$instance->update_values( $option, $values_to_update );
            $values[$option] = $updated_values;
          }
        }
        
        //self::$instance->update_values( $values );
        return rest_ensure_response( $values , 200);
      },
    ) );
  }

  /**
   * Inject default values into $values if they do not exist.
   */
  private function inject_default_values( $values, $option ) { 
    $default_values = self::$instance->get_default_values( $option );
    return $this->recursive_inject_default_values( $values, $default_values );
  }

  /**
  * Recursively inject default values into the values array.
  */
  private function recursive_inject_default_values( $values, $default_values ) {
    if ( ! is_array( $default_values ) || empty( $default_values ) ) {
      return $values; // No defaults to inject
    }

    foreach ( $default_values as $key => $default_value ) {
      if ( is_array( $default_value ) ) {
        // Ensure $values[$key] is an array before merging
        if ( !isset( $values[$key] ) || !is_array( $values[$key] ) ) {
          $values[$key] = [];
        }

        // Recursively process nested arrays
        $values[$key] = $this->recursive_inject_default_values( 
          $values[$key], 
          $default_value 
        );
      } else {
        // Only inject default if key does not exist in $values
        if ( is_array( $values ) && ! array_key_exists( $key, $values ) ) {
          $values[$key] = $default_value;
        }
      }
    }
    return $values;
  }

  /**
   * Update the settings
   */
  public function update_values( $option, $values = [] ) {
    $settings          = self::$instance->get_settings( $option );
    $sanitize_settings = self::$instance->get_sanitize_settings( $option );
    $option_values     = self::$instance->get_option_values( $option );
    $type_settings     = self::$instance->get_type_settings( $option );

    // 1) Recursively merge and sanitize
    $updated_values = $this->recursive_update_values(
      $option_values,
      $values,
      $sanitize_settings,
      $type_settings,
      $option
    );

    // 2) Retrieve the encryption settings for this option
    $encryption_settings = $this->get_encryption_settings( $option );
    
    // 3) Recursively encrypt the updated values
    $updated_values = $this->encrypt_values( $updated_values, $encryption_settings );

    // 4) Finally save the updated (and possibly encrypted) array
    if ( 'disabled' != $option ) {
      update_option( $option, $updated_values );
    }

    // Add to the global values array
    self::$instance->values[$option] = $updated_values;

    return $updated_values;
  }

  /**
   * Recursively update and sanitize values.
   */
  private function recursive_update_values( $existing_values, $new_values, $sanitize_settings, $type_settings, $option ) {
    if ( ! is_array( $new_values ) || empty( $new_values ) ) {
        return $existing_values;
    }

    foreach ( $new_values as $key => $value ) {
        // Get the field type data for this key at the current level
        $current_field_type = isset( $type_settings[$key] ) ? $type_settings[$key] : null;
        $has_child_fields = is_array( $current_field_type ) && isset( $current_field_type['__type'] );

        if ( is_array( $value ) && $has_child_fields ) {
            // Recursively update only if this key actually has child fields
            $existing_values[$key] = $this->recursive_update_values(
                isset( $existing_values[$key] ) ? $existing_values[$key] : [],
                $value,
                isset( $sanitize_settings[$key] ) ? $sanitize_settings[$key] : [],
                $current_field_type, // Pass only the relevant section of type_settings
                $option
            );
        } else {
            // Get the sanitizer for this specific key, or fallback to default
            $sanitizer = isset( $sanitize_settings[$key] ) 
                ? $sanitize_settings[$key] 
                : self::$instance->get_default_sanitizer();

            $existing_values[$key] = self::$instance->sanitize_value( $value, $sanitizer, $key, $option, 'update' );
        }
    }

    return $existing_values;
}


  /**
   * Sanitize a value
   */
  private function sanitize_values( $values, $option, $type = false ) { 
    $sanitize_settings = self::$instance->get_sanitize_settings( $option );
    $type_settings     = self::$instance->get_type_settings( $option );
    return $this->recursive_sanitize_values( $values, $sanitize_settings, $option, $type_settings, $type );
  }

  /**
   * Recursively sanitize values based on the sanitize settings structure.
   */
  private function recursive_sanitize_values( $values, $sanitize_settings, $option, $type_settings, $type = false ) {
    $sanitized_values = [];
    if ( is_array( $values ) && ! empty( $values ) ) {
      foreach ( $values as $key => $value ) {
        // Get the field type data for this key at the current level
        $current_field_type = isset( $type_settings[$key] ) ? $type_settings[$key] : null;
        $has_child_fields = is_array( $current_field_type ) && isset( $current_field_type['__type'] );

        if ( is_array( $value ) && $has_child_fields ) {
          // Recursively sanitize nested arrays using the same key structure
          $sanitized_values[$key] = $this->recursive_sanitize_values(
            $value, 
            isset( $sanitize_settings[$key] ) ? $sanitize_settings[$key] : [],
            $option,
            $current_field_type,
            $type
          );
        } else {
          
          // Retrieve the appropriate sanitizer or fallback to the default
          $sanitizer = isset( $sanitize_settings[$key] ) 
            ? $sanitize_settings[$key] 
            : self::$instance->get_default_sanitizer();

          $sanitized_values[$key] = self::$instance->sanitize_value( $value, $sanitizer, $key, $option, $type );
        }
      }
    }
    return $sanitized_values;
  }

  /**
   * Apply a sanitizer to a scalar value.
   * Handles core simple sanitizers, 'none', custom callbacks, and fallback default.
   */
  private function apply_sanitizer( $sanitizer, $value, $key, $option, $type ) {
    switch ( $sanitizer ) {
      case 'esc_attr':
      case 'sanitize_text_field':
      case 'wp_kses_post':
      case 'intval':
      case 'floatval':
      case 'boolval':
        return $sanitizer( $value );

      case 'none':
        return $value;

      default:
        if ( is_callable( $sanitizer ) ) {
          // Custom sanitizer gets full context.
          return call_user_func( $sanitizer, $value, $key, $option, $type );
        }

        // Fallback to your default sanitizer.
        $default = self::$instance->get_default_sanitizer();
        return is_callable( $default ) ? $default( $value ) : $value;
    }
  }

  /**
   * Recursively sanitize arrays of any depth.
   */
  private function loop_sanitize_value( $value, $sanitizer, $key, $option, $type = false ) {
    $sanitized = [];

    foreach ( (array) $value as $k => $v ) {
      if ( is_array( $v ) ) {
        $sanitized[ $k ] = $this->loop_sanitize_value( $v, $sanitizer, $key, $option, $type );
      } else {
        $sanitized[ $k ] = $this->apply_sanitizer( $sanitizer, $v, $key, $option, $type );
      }
    }

    return $sanitized;
  }

  /**
   * Sanitize a value (scalar or multi-level array).
   */
  private function sanitize_value( $value, $sanitizer, $key, $option, $type = false ) {
    // If it's an array, recurse no matter which sanitizer we use.
    if ( is_array( $value ) ) {
      return $this->loop_sanitize_value( $value, $sanitizer, $key, $option, $type );
    }

    // If it's an object, return as-is (can't sanitize objects with string sanitizers).
    if ( is_object( $value ) ) {
      return $value;
    }

    // Scalar: just apply.
    return $this->apply_sanitizer( $sanitizer, $value, $key, $option, $type );
  }

  /**
   * Recursively encrypt values based on encryption settings.
   * 
   * - If `['key_1' => X, 'key_2' => Y]` is found at `$encryption_settings[$key]`, 
   *   encrypt the entire `$values[$key]` with those keys and **do not** recurse deeper.
   * - Otherwise, if `$encryption_settings[$key]` is an array but lacks `key_1`/`key_2`, 
   *   we assume it's a nested structure and continue recursing.
   */
  private function encrypt_values( $values, $encryption_settings ) {
    // If there is no encryption to apply or $values isn't an array, bail early.
    if ( ! is_array( $encryption_settings ) || ! is_array( $values ) ) {
      return $values;
    }

    foreach ( $encryption_settings as $key => $enc_info ) {
      // Skip if $values does not have this key at all.
      if ( ! is_array( $values ) || ! array_key_exists( $key, $values ) ) {
        continue;
      }

      // Check if we have an actual encryption array with 'key_1'/'key_2'.
      if ( is_array( $enc_info ) && isset( $enc_info['key_1'] ) && isset( $enc_info['key_2'] ) ) {
        // Encrypt this entire branch with the custom keys and skip children.
        $values[$key] = $this->encrypt( $values[$key], $enc_info['key_1'], $enc_info['key_2'] );
      } 
      // If $enc_info is an array but no direct 'key_1'/'key_2', 
      // it's likely nested encryption settings. Recurse deeper if $values[$key] is array.
      elseif ( is_array( $enc_info ) ) {
        if ( is_array( $values[$key] ) ) {
          $values[$key] = $this->encrypt_values( $values[$key], $enc_info );
        }
      }
    }

    return $values;
  }

  /**
   * Encrypt data
  */
  private function encrypt( $string = '', $custom_key_1 = null, $custom_key_2 = null ) {
    if ( ! $string || '' === $string ) {
      return $string;
    }

    // Convert arrays to JSON so we can encrypt them as strings.
    if ( is_array( $string ) ) {
      $string = json_encode( $string );
    }

    // Use custom keys if provided, otherwise fall back to defaults.
    $key_1 = $custom_key_1 ?: $this->encryption_key_1;
    $key_2 = $custom_key_2 ?: $this->encryption_key_2;

    $key = hash( 'sha256', $key_1 );
    $iv  = substr( hash( 'sha256', $key_2 ), 0, 16 );

    return base64_encode(
      openssl_encrypt( $string, 'AES-256-CBC', $key, 0, $iv )
    );
  }

  /**
   * Recursively decrypt values based on the same encryption settings structure.
   *
   * - If we find `['key_1' => ..., 'key_2' => ...]` at `$encryption_settings[$key]`,
   *   we decrypt the entire `$values[$key]` with those keys, and do NOT recurse deeper.
   * - If `$enc_info` is an array but lacks `key_1`/`key_2`, we assume it's nested settings.
   *   We keep recursing into `$values[$key]` if it's an array.
   */
  private function decrypt_values( $values, $encryption_settings ) {
    if ( ! is_array( $values ) || ! is_array( $encryption_settings ) ) {
      return $values;
    }

    foreach ( $encryption_settings as $key => $enc_info ) {
      // Skip if $values does not have this key at all
      if ( ! is_array( $values ) || ! array_key_exists( $key, $values ) ) {
        continue;
      }

      // If this level has explicit 'key_1' and 'key_2', decrypt the entire branch
      if ( is_array( $enc_info ) && isset( $enc_info['key_1'] ) && isset( $enc_info['key_2'] ) ) {
        $values[$key] = $this->decrypt( $values[$key], $enc_info['key_1'], $enc_info['key_2'] );
      }
      // If $enc_info is an array but no direct 'key_1'/'key_2',
      // it might be nested encryption settings. Recurse deeper if $values[$key] is array
      elseif ( is_array( $enc_info ) ) {
        if ( is_array( $values[$key] ) ) {
          $values[$key] = $this->decrypt_values( $values[$key], $enc_info );
        }
      }
    }

    return $values;
  }

  /**
   * Decrypt data with optional custom keys.
   */
  private function decrypt( $string, $custom_key_1 = null, $custom_key_2 = null ) {
    // If already an array, it might have been double-processed or not encrypted at all
    if ( is_array( $string ) || '' === $string ) {
      return $string;
    }

    // Use custom keys if provided; otherwise, use defaults
    $key_1 = $custom_key_1 ?: $this->encryption_key_1;
    $key_2 = $custom_key_2 ?: $this->encryption_key_2;

    $key = hash( 'sha256', $key_1 );
    $iv  = substr( hash( 'sha256', $key_2 ), 0, 16 );

    $output = openssl_decrypt(
      base64_decode( $string ), 
      'AES-256-CBC', 
      $key, 
      0, 
      $iv
    );

    // Attempt to JSON-decode the result to restore arrays if originally encrypted from an array
    $decoded = json_decode( $output, true );
    return (json_last_error() === JSON_ERROR_NONE) ? $decoded : $output;
  }

  private function get_admin_notices() {
    $admin_notices = self::$instance->admin_notices;
    if ( ! is_array( $admin_notices ) ) {
      return [];
    }
    return $admin_notices;
  }

  private function add_admin_notice( $type, $message ) {
    $admin_notices = self::$instance->get_admin_notices();
    $admin_notices[] = [
      'type' => $type,
      'message' => $message,
    ];
    self::$instance->admin_notices = $admin_notices;
  }

  /**
   * Display admin notices
   */
  public function admin_notices() {
    $admin_notices = self::$instance->get_admin_notices();
    if ( is_array( $admin_notices ) && ! empty( $admin_notices ) ) {
      foreach ( $admin_notices as $notice ) {
        echo '<div class="' . $notice['type'] . '">';
          echo wp_kses_post( $notice['message'] );
        echo '</div>';
      }
    }
  }

  /**
   * Initialize settings - fires the init_settings action hook
   */
  public function initialize_settings() {
    if ( ! $this->settings_ready ) {
      do_action( $this->get_id() . '/init_settings' );
      $this->settings_ready = true;
    }
  }

  /**
   * Initialize fields - fires the init_fields action hook
   */
  public function initialize_fields() {
    if ( ! $this->fields_ready ) {
      do_action( $this->get_id() . '/init_fields' );
      $this->fields_ready = true;
    }
  }

  /*--------------------------------------------------------------------------
   * Static API Methods
   * These provide a cleaner interface: Settings::add_admin_page($data)
   *------------------------------------------------------------------------*/

  /**
   * Static: Add an admin page
   */
  public static function admin_page( $data ) {
    return self::instance()->add_admin_page( $data );
  }

  /**
   * Static: Add a section
   */
  public static function section( $data ) {
    return self::instance()->add_section( $data );
  }

  /**
   * Static: Add fields
   */
  public static function fields( $data ) {
    return self::instance()->add_fields( $data );
  }

  /**
   * Static: Add sidebar
   */
  public static function sidebar( $data ) {
    return self::instance()->add_sidebar( $data );
  }

  /**
   * Static: Add default values
   */
  public static function default_values( $option, $values = [] ) {
    return self::instance()->add_default_values( $option, $values );
  }

  /**
   * Static: Add sanitize settings
   */
  public static function sanitize_settings( $option, $values = [] ) {
    return self::instance()->add_sanitize_settings( $option, $values );
  }

  /**
   * Static: Add encryption settings
   */
  public static function encryption_settings( $option, $values = [] ) {
    return self::instance()->add_encryption_settings( $option, $values );
  }

  /**
   * Static: Get an option value
   */
  public static function get_value( $option, $key = false ) {
    $values = self::instance()->get_option_values( $option );
    if ( $key ) {
      if ( isset( $values[$key] ) ) {
        return $values[$key];
      }
      return null;
    }
    return $values;
  }

  /**
   * Static: Set an option value
   */
  public static function set_value( $option, $key, $value = false ) {
    if ( is_array( $key ) ) {
      $updated_values = $key;
    } else {
      $updated_values = [
        $key => $value,
      ];
    }
    return self::instance()->update_values( $option, $updated_values );
  }
}