<?php

declare(strict_types=1);

namespace Atdt\Screens;

use Atdt\Ansi;
use Atdt\Ctx;
use Atdt\DoorContext;
use Atdt\DoorRunner;
use Atdt\DoorStore;
use Atdt\In;
use Atdt\Screen;

/**
 * The engine side of a door (SPEC §13.2): pushes with d = {slug}, builds
 * the DoorContext, writes the traditional drop file on entry, persists
 * the door's state bag in this stack frame, and keeps the fatal wall —
 * a Throwable inside door code becomes a logged apology and a clean pop,
 * never a dead session.
 */
final class DoorScreen implements Screen
{
    public function describe(): string
    {
        return 'Playing a door game';
    }

    public function enter(Ctx $c): array
    {
        $slug = (string)$c->get('slug', '');
        $door = DoorRunner::load($c->db(), $slug);
        if ($door === null || $c->user() === null) {
            return $this->bail($c, 'That door is off its hinges right now.');
        }
        $ctx = $this->doorCtx($c, $slug);
        try {
            // Tradition: the drop file lands on disk at entry (§13.3).
            @file_put_contents(
                $ctx->kv()->path('node' . $c->node() . '.dropfile.json'),
                json_encode($ctx->dropfile(), JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES)
            );
            $frames = $door->enter($ctx);
            $ctx->persistState();
            return $this->filter($c, $ctx, $door, $frames);
        } catch (\Throwable $e) {
            return $this->crash($c, $slug, $e);
        }
    }

    public function input(Ctx $c, In $in): array
    {
        $slug = (string)$c->get('slug', '');
        $door = DoorRunner::load($c->db(), $slug);
        if ($door === null) {
            return $this->bail($c, 'That door is off its hinges right now.');
        }
        $ctx = $this->doorCtx($c, $slug);
        try {
            $frames = $door->input($ctx, $in);
            $ctx->persistState();
            return $this->filter($c, $ctx, $door, $frames);
        } catch (\Throwable $e) {
            return $this->crash($c, $slug, $e);
        }
    }

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

    private function doorCtx(Ctx $c, string $slug): DoorContext
    {
        $title = (string)($c->db()->val('SELECT title FROM doors WHERE slug = ?', [$slug]) ?? $slug);
        return new DoorContext($c, new DoorStore($c->db(), $slug), $slug, $title);
    }

    /** Swap the exit sentinel for a real pop (running leave() first). */
    private function filter(Ctx $c, DoorContext $ctx, \Atdt\DoorInterface $door, array $frames): array
    {
        $out = [];
        foreach ($frames as $f) {
            if (($f['t'] ?? '') === '_door_exit') {
                try {
                    $door->leave($ctx);
                    $ctx->persistState();
                } catch (\Throwable $e) {
                    $c->db()->insert('events', [
                        'at' => now_utc(), 'level' => 'error',
                        'actor' => 'door:' . (string)$c->get('slug'),
                        'msg' => mb_substr('leave() failed: ' . $e->getMessage(), 0, 500),
                    ]);
                }
                foreach ($c->pop() as $pf) {
                    $out[] = $pf;
                }
                return $out;
            }
            $out[] = $f;
        }
        return $out;
    }

    private function crash(Ctx $c, string $slug, \Throwable $e): array
    {
        $c->db()->insert('events', [
            'at' => now_utc(), 'level' => 'error', 'actor' => 'door:' . $slug,
            'msg' => mb_substr(get_class($e) . ': ' . $e->getMessage()
                . ' @ ' . basename($e->getFile()) . ':' . $e->getLine(), 0, 500),
        ]);
        $frames = $c->pop();
        $frames[] = $c->ansi(
            Ansi::make()->at(24, 1)->txt("\x1b[K")
                ->txt($c->center($c->pipe('|12The door jammed. The sysop has been told. |07You are back at the menu.')))
                ->str()
        );
        return $frames;
    }

    private function bail(Ctx $c, string $msg): array
    {
        $frames = $c->pop();
        $frames[] = $c->ansi(
            Ansi::make()->at(24, 1)->txt("\x1b[K")->txt($c->center($c->pipe('|11' . $msg . '|07')))->str()
        );
        return $frames;
    }
}
