<?php

declare(strict_types=1);

/**
 * HILO — the ATDT reference door. Deliberately tiny: this file IS the SDK
 * example (doors/README.md walks it line by line). Guess 1-100 in seven
 * tries; winning extends your streak, losing ends it; the ten best
 * streaks hold the board.
 */

namespace Doors\Hilo;

use Atdt\DoorContext;
use Atdt\DoorInterface;
use Atdt\In;

final class Door implements DoorInterface
{
    private const TRIES = 7;

    /** First frame set: paint the room, arm the input line. */
    public function enter(DoorContext $ctx): array
    {
        // The state bag persists across requests exactly like screen state.
        $ctx->state['secret'] = random_int(1, 100);
        $ctx->state['left'] = self::TRIES;
        $ctx->log($ctx->handle() . ' sat down');
        return $this->board($ctx, '|07A number between |151|07 and |15100|07 hides. Seven tries.');
    }

    /** Every reply arrives here: a typed line (the guess) or a keypress. */
    public function input(DoorContext $ctx, In $in): array
    {
        if ($in->kind !== 'line') {
            return [$ctx->bell()];
        }
        if ($in->line === null || trim($in->line) === '') {
            return [$ctx->exitDoor()];   // empty line leaves, like everywhere
        }
        $guess = (int)trim($in->line);
        $secret = (int)$ctx->state['secret'];
        if ($guess < 1 || $guess > 100) {
            return $this->board($ctx, '|12Whole numbers 1-100 only.');
        }
        if ($guess === $secret) {
            return $this->won($ctx);
        }
        $ctx->state['left'] = (int)$ctx->state['left'] - 1;
        if ((int)$ctx->state['left'] <= 0) {
            return $this->lost($ctx, $secret);
        }
        $hint = $guess < $secret ? '|10higher' : '|12lower';
        return $this->board($ctx, sprintf('|15%d|07 is wrong - go %s|07.', $guess, $hint));
    }

    /** Runs once on the way out, however the caller leaves. */
    public function leave(DoorContext $ctx): void
    {
        $ctx->log($ctx->handle() . ' walked out');
    }

    // ------------------------------------------------------------------

    private function won(DoorContext $ctx): array
    {
        // kv() is this door's own key/value shelf, scoped by slug.
        $kv = $ctx->kv();
        $streak = 1 + (int)$kv->get('streak:' . $ctx->userId(), '0');
        $kv->set('streak:' . $ctx->userId(), (string)$streak);
        $best = (int)$kv->get('best:' . $ctx->userId(), '0');
        if ($streak > $best) {
            $kv->set('best:' . $ctx->userId(), (string)$streak);
            $kv->set('bestname:' . $ctx->userId(), $ctx->handle());
        }
        $ctx->award(10);   // the global door scoreboard on the board's Stats
        $ctx->state['secret'] = random_int(1, 100);
        $ctx->state['left'] = self::TRIES;
        return $this->board($ctx, sprintf(
            '|14Got it! |07Streak |15%d|07. A new number hides - or Enter alone leaves.',
            $streak
        ));
    }

    private function lost(DoorContext $ctx, int $secret): array
    {
        $ctx->kv()->set('streak:' . $ctx->userId(), '0');
        $ctx->state['secret'] = random_int(1, 100);
        $ctx->state['left'] = self::TRIES;
        return $this->board($ctx, sprintf(
            '|12Out of tries - it was |15%d|12. |07Streak reset. New number up.',
            $secret
        ));
    }

    /** One classic screen: title, the top-ten streaks, a message, a prompt. */
    private function board(DoorContext $ctx, string $msg): array
    {
        $o = $ctx->out()->cls();
        $o->at(2, 1)->txt($ctx->center($ctx->pipe('|11H I - L O |03· |07seven tries between 1 and 100')));
        $o->at(3, 1)->fg(8)->txt($ctx->center(str_repeat('─', 44)))->reset();

        // Best streaks: read every best:* key, sort, show ten.
        $bests = [];
        foreach ($ctx->kv()->all('best:') as $k => $v) {
            $uid = (string)substr($k, 5);
            $bests[] = ['n' => (int)$v, 'who' => $ctx->kv()->get('bestname:' . $uid, 'caller')];
        }
        usort($bests, fn(array $a, array $b) => $b['n'] <=> $a['n']);
        $o->at(5, 30)->txt($ctx->pipe('|03BEST STREAKS'));
        $row = 6;
        foreach (array_slice($bests, 0, 10) as $i => $b) {
            $o->at($row++, 28)->txt($ctx->pipe(sprintf(
                '|08%2d. |15%-16s |11%d',
                $i + 1,
                mb_strimwidth((string)$b['who'], 0, 16),
                (int)$b['n']
            )));
        }
        if ($bests === []) {
            $o->at(6, 1)->txt($ctx->center($ctx->pipe('|08Nobody on the board yet. Be first.')));
        }

        $o->at(18, 1)->txt($ctx->center($ctx->pipe($msg)));
        $o->at(20, 1)->txt($ctx->center($ctx->pipe(sprintf(
            '|07Tries left: |15%d|07 · Enter alone leaves',
            (int)$ctx->state['left']
        ))));
        $prompt = $ctx->pipe('|03Your guess |08: |15');
        $o->at(22, 30)->txt($prompt);
        // line(x, y, max): the next reply arrives as In::line.
        return [$ctx->ansi($o->reset()->str()), $ctx->line(43, 22, 3)];
    }
}
