<?php

declare(strict_types=1);

namespace ModernFields\FieldGroups\Entities\Traits;

use ReflectionClass;

/**
 * Trait providing methods for normalizing meta data and JSON serialization.
 * 
 * Classes using this trait can normalize string and array meta values,
 * and automatically serialize to array using reflection to get all properties.
 * 
 * To customize which properties are included, override getArrayableProperties()
 * or toArray() method.
 * 
 * @since 1.0.0
 */
trait NormalizesMeta
{
  /**
   * Convert the object into a serializable payload.
   * 
   * Automatically collects all properties using reflection.
   * Override this method or getArrayableProperties() to customize behavior.
   *
   * @return array<string, mixed>
   */
  public function toArray(): array
  {
    $properties = $this->getArrayableProperties();
    $result = [];

    foreach ($properties as $propertyName) {
      $result[$propertyName] = $this->$propertyName ?? null;
    }

    return $result;
  }

  /**
   * Get the list of property names to include in the array representation.
   * 
   * Override this method to customize which properties are included.
   * Returns all non-static properties by default.
   *
   * @return array<string>
   */
  protected function getArrayableProperties(): array
  {
    return $this->getAllProperties();
  }

  /**
   * Get all non-static property names from the class using reflection.
   * 
   * This is a helper method that can be used by classes to get all properties
   * and then filter them as needed.
   *
   * @return array<string>
   */
  protected function getAllProperties(): array
  {
    $reflection = new ReflectionClass($this);
    $properties = $reflection->getProperties();
    $propertyNames = [];

    foreach ($properties as $property) {
      # Skip static properties
      if ($property->isStatic()) {
        continue;
      }

      # Get property name
      $propertyName = $property->getName();
      $propertyNames[] = $propertyName;
    }

    return $propertyNames;
  }

  /**
   * @return array<string, mixed>
   */
  public function jsonSerialize(): array
  {
    return $this->toArray();
  }

  /**
   * Normalize raw string/meta values.
   *
   * @param mixed $value
   * @param string $default
   * @return string
   */
  protected function normalizeString($value, string $default = ''): string
  {
    if (is_array($value)) {
      $value = reset($value);
    }

    $value = is_string($value) ? $value : (string) $value;
    $value = trim($value);

    return $value !== '' ? $value : $default;
  }

  /**
   * Normalize meta that should always be an associative array.
   *
   * @param mixed $value
   * @param array<string, mixed> $default
   * @return array<string, mixed>
   */
  protected function normalizeArray($value, array $default = []): array
  {
    if (empty($value)) {
      return $default;
    }

    if (is_array($value)) {
      return $value;
    }

    if (is_string($value)) {
      # Try JSON decode first (most common case)
      $decoded = json_decode($value, true);
      if (json_last_error() === JSON_ERROR_NONE && is_array($decoded)) {
        return $decoded;
      }

      # Fall back to PHP unserialize for backward compatibility
      $value = maybe_unserialize($value);
    }

    return is_array($value) ? $value : $default;
  }
}
