<?php

declare(strict_types=1);

namespace App\Support\Diff;

use BackedEnum;
use InvalidArgumentException;
use Stringable;

final readonly class DiffRow
{
    public string $key;

    public string $label;

    public DiffRowStatus $status;

    public mixed $oldValue;

    public mixed $newValue;

    public bool $isListLike;

    /**
     * @var array<int, mixed>
     */
    public array $addedItems;

    /**
     * @var array<int, mixed>
     */
    public array $removedItems;

    /**
     * @var array<int, mixed>
     */
    public array $unchangedItems;

    /**
     * @var array<string, mixed>
     */
    public array $meta;

    /**
     * @param  array<int, mixed>  $addedItems
     * @param  array<int, mixed>  $removedItems
     * @param  array<int, mixed>  $unchangedItems
     * @param  array<string, mixed>  $meta
     */
    public function __construct(
        string $key,
        string $label,
        DiffRowStatus $status,
        mixed $oldValue = null,
        mixed $newValue = null,
        bool $isListLike = false,
        array $addedItems = [],
        array $removedItems = [],
        array $unchangedItems = [],
        array $meta = [],
    ) {
        if (trim($key) === '') {
            throw new InvalidArgumentException('DiffRow key must be a non-empty string.');
        }

        if (trim($label) === '') {
            throw new InvalidArgumentException('DiffRow label must be a non-empty string.');
        }

        $this->key = $key;
        $this->label = $label;
        $this->status = $status;
        $this->oldValue = $oldValue;
        $this->newValue = $newValue;
        $this->isListLike = $isListLike;
        $this->addedItems = array_values($addedItems);
        $this->removedItems = array_values($removedItems);
        $this->unchangedItems = array_values($unchangedItems);
        $this->meta = $this->normalizeMeta($meta);
    }

    /**
     * @param  array<string, mixed>  $meta
     * @return array<string, mixed>
     */
    private function normalizeMeta(array $meta): array
    {
        $normalized = [];

        foreach ($meta as $key => $value) {
            if (! is_string($key) || trim($key) === '') {
                continue;
            }

            $normalized[$key] = $this->normalizeMetaValue($value);
        }

        return $normalized;
    }

    private function normalizeMetaValue(mixed $value): mixed
    {
        if ($value === null || is_scalar($value)) {
            return $value;
        }

        if ($value instanceof BackedEnum) {
            return $value->value;
        }

        if ($value instanceof Stringable) {
            return (string) $value;
        }

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

        $normalized = [];

        foreach ($value as $key => $item) {
            if (! is_int($key) && ! is_string($key)) {
                continue;
            }

            $normalized[$key] = $this->normalizeMetaValue($item);
        }

        return array_is_list($normalized) ? array_values($normalized) : $normalized;
    }
}
