<?php

/**
 * Settings change tracker helper
 *
 * @package   Duplicator
 * @copyright (c) 2024, Snap Creek LLC
 */

namespace Duplicator\Utils\ActivityLog;

use Duplicator\Models\ActivityLog\LogEventSettingsChange;

/**
 * Helper class to track settings changes at controller level
 */
class SettingsChangeTracker
{
    /** @var array<int,array{key:string,format:string,data:scalar[]}> */
    protected array $changes = [];

    /**
     * Adds a change entry for a specific setting if values differ
     *
     * @param string                  $key            Setting key identifier
     * @param mixed                   $currentValue   Current value before change
     * @param mixed                   $newValue       New value to set
     * @param string                  $format         Format string for description (e.g., 'Changed from %s to %s')
     * @param array<int|string,mixed> $optionsMapping Optional mapping of values to human-readable labels
     *
     * @return void
     */
    public function addChange(
        string $key,
        $currentValue,
        $newValue,
        string $format,
        array $optionsMapping = []
    ): void {
        if (self::valuesAreEqual($currentValue, $newValue)) {
            return;
        }

        // Create change entry for the setting
        $this->changes[] = self::createChangeEntry($key, $currentValue, $newValue, $format, $optionsMapping);
    }

    /**
     * Create log event if changes exist and clear the changes array
     *
     * @param string $event      Settings change log event identifier
     * @param string $actionType Action type for the log
     *
     * @return void
     */
    public function createLog($event, $actionType = 'settings_updated'): void
    {
        if (count($this->changes) === 0) {
            return;
        }
        LogEventSettingsChange::create($event, ['changes' => $this->changes, 'actionType' => $actionType]);
        $this->changes = []; // Clear changes after logging
    }

    /**
     * Create a change entry for a specific setting
     *
     * @param string                   $key            Setting key identifier
     * @param mixed                    $currentValue   Current value before change
     * @param mixed                    $newValue       New value to set
     * @param string                   $format         Format string for description (e.g., 'Changed from %s to %s')
     * @param array<int|string, mixed> $optionsMapping Optional mapping of values to human-readable labels
     *
     * @return array{key:string,format:string,data:scalar[]} Array with change entry or empty array
     */
    protected static function createChangeEntry(
        string $key,
        $currentValue,
        $newValue,
        string $format,
        array $optionsMapping = []
    ): array {
        // Generate data array for format string (old value, new value)
        // For complex array formats, we need the original arrays for comparison
        if (in_array($format, ['emailListChanged', 'singleCapabilityChanged'])) {
            $data = [
                $currentValue,
                $newValue,
            ];
        } else {
            $data = [
                self::formatValueForData($currentValue),
                self::formatValueForData($newValue),
            ];
        }

        $changeEntry = [
            'key'    => $key,
            'format' => $format,
            'data'   => $data,
        ];

        // Add options mapping if provided
        if (!empty($optionsMapping)) {
            $changeEntry['options'] = $optionsMapping;
        }

        return $changeEntry;
    }

    /**
     * Format a value for inclusion in data array
     *
     * @param mixed $value Value to format
     *
     * @return bool|string
     */
    private static function formatValueForData($value)
    {
        if (is_bool($value)) {
            return $value;
        }

        if (is_array($value)) {
            return implode(', ', array_map('strval', $value));
        }

        if (is_null($value)) {
            return '';
        }

        return (string) $value;
    }

    /**
     * Check if two values are equal (moved to static for reuse)
     *
     * @param mixed $value1 First value
     * @param mixed $value2 Second value
     *
     * @return bool
     */
    private static function valuesAreEqual($value1, $value2): bool
    {
        if (is_array($value1) && is_array($value2)) {
            return empty(array_diff($value1, $value2)) && empty(array_diff($value2, $value1));
        }

        return $value1 === $value2;
    }
}
