<?php

declare(strict_types=1);

/**
 * This code was auto-generated by {this script}[https://github.com/cucumber/messages/blob/main/jsonschema/scripts/codegen.rb]
 */

namespace Cucumber\Messages;

use JsonSerializable;
use Cucumber\Messages\DecodingException\SchemaViolationException;

/**
 * Represents the TestStep message in Cucumber's message protocol
 * @see https://github.com/cucumber/messages
 *
 * A `TestStep` is derived from either a `PickleStep`
 * combined with a `StepDefinition`, or from a `Hook`. */
final class TestStep implements JsonSerializable
{
    use JsonEncodingTrait;

    /**
     * Construct the TestStep with all properties
     *
     * @param ?list<string> $stepDefinitionIds
     * @param ?list<StepMatchArgumentsList> $stepMatchArgumentsLists
     */
    public function __construct(

        /**
         * Pointer to the `Hook` (if derived from a Hook)
         */
        public readonly ?string $hookId = null,
        public readonly string $id = '',

        /**
         * Pointer to the `PickleStep` (if derived from a `PickleStep`)
         */
        public readonly ?string $pickleStepId = null,

        /**
         * Pointer to all the matching `StepDefinition`s (if derived from a `PickleStep`)
         */
        public readonly ?array $stepDefinitionIds = null,

        /**
         * A list of list of StepMatchArgument (if derived from a `PickleStep`).
         * Each element represents a matching step definition. A size of 0 means `UNDEFINED`,
         * and a size of 2+ means `AMBIGUOUS`
         */
        public readonly ?array $stepMatchArgumentsLists = null,
    ) {
    }

    /**
     * @throws SchemaViolationException
     *
     * @internal
     */
    public static function fromArray(array $arr): self
    {
        self::ensureHookId($arr);
        self::ensureId($arr);
        self::ensurePickleStepId($arr);
        self::ensureStepDefinitionIds($arr);
        self::ensureStepMatchArgumentsLists($arr);

        return new self(
            isset($arr['hookId']) ? (string) $arr['hookId'] : null,
            (string) $arr['id'],
            isset($arr['pickleStepId']) ? (string) $arr['pickleStepId'] : null,
            isset($arr['stepDefinitionIds']) ? array_values(array_map(fn (mixed $member) => (string) $member, $arr['stepDefinitionIds'])) : null,
            isset($arr['stepMatchArgumentsLists']) ? array_values(array_map(fn (array $member) => StepMatchArgumentsList::fromArray($member), $arr['stepMatchArgumentsLists'])) : null,
        );
    }

    /**
     * @psalm-assert array{hookId?: string|int|bool} $arr
     */
    private static function ensureHookId(array $arr): void
    {
        if (array_key_exists('hookId', $arr) && is_array($arr['hookId'])) {
            throw new SchemaViolationException('Property \'hookId\' was array');
        }
    }

    /**
     * @psalm-assert array{id: string|int|bool} $arr
     */
    private static function ensureId(array $arr): void
    {
        if (!array_key_exists('id', $arr)) {
            throw new SchemaViolationException('Property \'id\' is required but was not found');
        }
        if (array_key_exists('id', $arr) && is_array($arr['id'])) {
            throw new SchemaViolationException('Property \'id\' was array');
        }
    }

    /**
     * @psalm-assert array{pickleStepId?: string|int|bool} $arr
     */
    private static function ensurePickleStepId(array $arr): void
    {
        if (array_key_exists('pickleStepId', $arr) && is_array($arr['pickleStepId'])) {
            throw new SchemaViolationException('Property \'pickleStepId\' was array');
        }
    }

    /**
     * @psalm-assert array{stepDefinitionIds?: array} $arr
     */
    private static function ensureStepDefinitionIds(array $arr): void
    {
        if (array_key_exists('stepDefinitionIds', $arr) && !is_array($arr['stepDefinitionIds'])) {
            throw new SchemaViolationException('Property \'stepDefinitionIds\' was not array');
        }
    }

    /**
     * @psalm-assert array{stepMatchArgumentsLists?: array} $arr
     */
    private static function ensureStepMatchArgumentsLists(array $arr): void
    {
        if (array_key_exists('stepMatchArgumentsLists', $arr) && !is_array($arr['stepMatchArgumentsLists'])) {
            throw new SchemaViolationException('Property \'stepMatchArgumentsLists\' was not array');
        }
    }
}
