<?php

declare(strict_types=1);

namespace App\Framework\Svg\Charts\ValueObjects;

use App\Framework\Exception\ErrorCode;
use App\Framework\Exception\FrameworkException;

/**
 * Value Object representing chart data
 */
final readonly class ChartData
{
    /**
     * @param array<DataPoint> $points
     */
    public function __construct(
        public array $points
    ) {
        if (empty($points)) {
            throw FrameworkException::create(
                ErrorCode::VAL_INVALID_FORMAT,
                'Chart data cannot be empty'
            );
        }

        // Validate all points are DataPoint instances
        foreach ($points as $point) {
            if (!$point instanceof DataPoint) {
                throw FrameworkException::create(
                    ErrorCode::VAL_INVALID_FORMAT,
                    'All chart data points must be DataPoint instances'
                );
            }
        }
    }

    /**
     * Create from array of values with labels
     */
    public static function fromArray(array $data): self
    {
        $points = [];
        foreach ($data as $label => $value) {
            $points[] = new DataPoint((string) $label, (float) $value);
        }

        return new self($points);
    }

    /**
     * Get point count
     */
    public function count(): int
    {
        return count($this->points);
    }

    /**
     * Get minimum value
     */
    public function getMinValue(): float
    {
        return min(array_map(fn (DataPoint $p) => $p->value, $this->points));
    }

    /**
     * Get maximum value
     */
    public function getMaxValue(): float
    {
        return max(array_map(fn (DataPoint $p) => $p->value, $this->points));
    }

    /**
     * Get sum of all values
     */
    public function getSum(): float
    {
        return array_sum(array_map(fn (DataPoint $p) => $p->value, $this->points));
    }

    /**
     * Get average value
     */
    public function getAverage(): float
    {
        return $this->getSum() / $this->count();
    }

    /**
     * Normalize values to 0-1 range
     */
    public function normalize(): self
    {
        $min = $this->getMinValue();
        $max = $this->getMaxValue();
        $range = $max - $min;

        if ($range === 0.0) {
            return $this;
        }

        $normalized = array_map(
            fn (DataPoint $p) => new DataPoint(
                $p->label,
                ($p->value - $min) / $range
            ),
            $this->points
        );

        return new self($normalized);
    }

    public function toArray(): array
    {
        return array_map(fn (DataPoint $p) => $p->toArray(), $this->points);
    }
}
