<?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 Pickle message in Cucumber's message protocol
 * @see https://github.com/cucumber/messages
 *
 * //// Pickles
 *
 * A `Pickle` represents a template for a `TestCase`. It is typically derived
 * from another format, such as [GherkinDocument](#io.cucumber.messages.GherkinDocument).
 * In the future a `Pickle` may be derived from other formats such as Markdown or
 * Excel files.
 *
 * By making `Pickle` the main data structure Cucumber uses for execution, the
 * implementation of Cucumber itself becomes simpler, as it doesn't have to deal
 * with the complex structure of a [GherkinDocument](#io.cucumber.messages.GherkinDocument).
 *
 * Each `PickleStep` of a `Pickle` is matched with a `StepDefinition` to create a `TestCase` */
final class Pickle implements JsonSerializable
{
    use JsonEncodingTrait;

    /**
     * Construct the Pickle with all properties
     *
     * @param list<PickleStep> $steps
     * @param list<PickleTag> $tags
     * @param list<string> $astNodeIds
     */
    public function __construct(

        /**
         * A unique id for the pickle
         */
        public readonly string $id = '',

        /**
         * The uri of the source file
         */
        public readonly string $uri = '',

        /**
         * The name of the pickle
         */
        public readonly string $name = '',

        /**
         * The language of the pickle
         */
        public readonly string $language = '',

        /**
         * One or more steps
         */
        public readonly array $steps = [],

        /**
         * One or more tags. If this pickle is constructed from a Gherkin document,
         * It includes inherited tags from the `Feature` as well.
         */
        public readonly array $tags = [],

        /**
         * Points to the AST node locations of the pickle. The last one represents the unique
         * id of the pickle. A pickle constructed from `Examples` will have the first
         * id originating from the `Scenario` AST node, and the second from the `TableRow` AST node.
         */
        public readonly array $astNodeIds = [],
    ) {
    }

    /**
     * @throws SchemaViolationException
     *
     * @internal
     */
    public static function fromArray(array $arr): self
    {
        self::ensureId($arr);
        self::ensureUri($arr);
        self::ensureName($arr);
        self::ensureLanguage($arr);
        self::ensureSteps($arr);
        self::ensureTags($arr);
        self::ensureAstNodeIds($arr);

        return new self(
            (string) $arr['id'],
            (string) $arr['uri'],
            (string) $arr['name'],
            (string) $arr['language'],
            array_values(array_map(fn (array $member) => PickleStep::fromArray($member), $arr['steps'])),
            array_values(array_map(fn (array $member) => PickleTag::fromArray($member), $arr['tags'])),
            array_values(array_map(fn (mixed $member) => (string) $member, $arr['astNodeIds'])),
        );
    }

    /**
     * @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{uri: string|int|bool} $arr
     */
    private static function ensureUri(array $arr): void
    {
        if (!array_key_exists('uri', $arr)) {
            throw new SchemaViolationException('Property \'uri\' is required but was not found');
        }
        if (array_key_exists('uri', $arr) && is_array($arr['uri'])) {
            throw new SchemaViolationException('Property \'uri\' was array');
        }
    }

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

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

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

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

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