<?php

namespace App\Agent;

/**
 * Choices a tool wants the person to pick from, rather than type.
 *
 * The agent answers in prose, so every step has been an open question and the
 * user has had to know the vocabulary: which objective, which placements, which
 * creative id. Offering the options instead turns most of a campaign into
 * clicking.
 *
 * Collected here during a turn and drained by the controller afterwards, which
 * is the same shape the workspace already uses. It deliberately does not touch
 * the model's reply: the words stay the model's, the options are ours, and the
 * options can only be values the platform actually accepts.
 *
 * Clicking sends an ordinary message. That keeps one entry point into the agent
 * rather than a second, parallel one that would then need its own approval,
 * ownership and audit handling.
 */
class Choices
{
    /** @var list<array<string, mixed>> */
    private array $offered = [];

    /**
     * @param  string  $field  what is being chosen, for example objective
     * @param  string  $question  short, and shown above the options
     * @param  list<array{value: string, label: string, hint?: ?string, image?: ?string, badge?: ?string}>  $options
     * @param  bool  $multiple  whether more than one may be picked
     */
    public function offer(string $field, string $question, array $options, bool $multiple = false, bool $allowOther = false): void
    {
        if ($options === []) {
            return;
        }

        // A question asked twice in one turn is one question. Two tools can
        // reach the same field, and the model can call one of them twice, and
        // the renderer counts answers by field: four questions over three
        // fields meant "Answer all 4 to continue" could never be satisfied and
        // the button stayed dead. Seen in the browser on 16 Sep with devices
        // asked twice alongside placements and click tracking.
        //
        // The later offer wins, because it was built from more recent state.
        $this->offered = array_values(array_filter(
            $this->offered,
            fn (array $existing): bool => $existing['field'] !== $field,
        ));

        $this->offered[] = [
            'field' => $field,
            'question' => $question,
            'multiple' => $multiple,
            'allow_other' => $allowOther,
            'options' => array_map(fn (array $o): array => [
                'value' => (string) $o['value'],
                'label' => (string) $o['label'],
                'hint' => isset($o['hint']) ? (string) $o['hint'] : null,
                'image' => isset($o['image']) ? (string) $o['image'] : null,
                // Stands in for a picture there is no way to produce. A video
                // has no stored poster frame, so beside options that do have
                // one its card rendered as blank space and read as broken.
                'icon' => isset($o['icon']) ? (string) $o['icon'] : null,
                // A short word above the label, for "Suggested" and the like.
                'badge' => isset($o['badge']) ? (string) $o['badge'] : null,
                'disabled' => ! empty($o['disabled']),
                // What clicking sends. Written here rather than in the browser
                // so the phrasing that reaches the model is ours.
                'sends' => (string) ($o['sends'] ?? $o['label']),
            ], $options),
        ];
    }

    /** @return list<array<string, mixed>> */
    public function all(): array
    {
        return $this->offered;
    }

    /**
     * Take what was offered and forget it.
     *
     * Drained rather than read so a later turn cannot re-show a question that
     * has already been answered.
     *
     * @return list<array<string, mixed>>
     */
    public function drain(): array
    {
        $offered = $this->offered;
        $this->offered = [];

        return $offered;
    }
}
