<?php

declare(strict_types=1);

namespace Atdt\Screens;

use Atdt\Ansi;
use Atdt\Ctx;
use Atdt\In;
use Atdt\Screen;

/**
 * The post-login sequence (SPEC §10.6): logon art/welcome card → random
 * rumor → auto-message → MAIN. Steps that have nothing to show skip
 * themselves; M4/M5 splice in mail check, bulletins-if-updated, and last
 * callers. Any key advances.
 */
final class PostLoginScreen implements Screen
{
    public function describe(): string
    {
        return 'Just connected';
    }

    public function enter(Ctx $c): array
    {
        // Resume-aware: coming back from a pushed screen (mailbox jump)
        // continues the sequence instead of replaying it.
        if (!is_array($c->get('steps'))) {
            // §10.6 order: logon art → email capture → unread bulletins →
            // auto-message → rumor → mail check → last callers → MAIN.
            // Empty steps skip.
            $steps = ['welcome'];
            $user = $c->user();
            if ($user !== null && trim((string)($user['email'] ?? '')) === '') {
                $steps[] = 'email'; // 1.5.0: skippable; asked while empty
            }
            foreach ($this->unreadBulletins($c) as $b) {
                $steps[] = 'bull:' . (int)$b['id']; // 1.5.0: ALL unread, oldest first
            }
            if (trim((string)($c->settings()->get('automsg_body', '') ?? '')) !== '') {
                $steps[] = 'automsg';
            }
            $rumor = $c->db()->one(
                $c->db()->dialect() === \Atdt\Db::MYSQL
                    ? 'SELECT * FROM rumors WHERE approved = 1 ORDER BY RAND() LIMIT 1'
                    : 'SELECT * FROM rumors WHERE approved = 1 ORDER BY RANDOM() LIMIT 1'
            );
            if ($rumor !== null) {
                $steps[] = 'rumor';
            }
            if ($user !== null && \Atdt\Messages::unreadMailCount($c->db(), $user) > 0) {
                $steps[] = 'mail'; // §10.5 login mail check
            }
            $steps[] = 'lastcallers';
            $c->set('steps', $steps);
            $c->set('i', 0);
            $c->set('rumor', $rumor === null ? null : (string)$rumor['body']);
        }
        return $this->show($c);
    }

    public function input(Ctx $c, In $in): array
    {
        $i = (int)$c->get('i', 0);
        $steps = (array)$c->get('steps', ['welcome']);
        if (($steps[$i] ?? '') === 'email') {
            // The one line-mode step: an address, or ENTER to skip (1.5.0).
            if ($in->kind !== 'line') {
                return $this->show($c);
            }
            $email = trim((string)($in->line ?? ''));
            $c->set('i', $i + 1);
            if ($email !== '') {
                if (!\Atdt\Mailer::plausible($email)) {
                    $c->set('i', $i); // stay: the address has to look real
                    return $this->show($c, 'logon.email_bad');
                }
                $c->db()->q('UPDATE users SET email = ? WHERE id = ?', [
                    mb_strimwidth($email, 0, 120), (int)($c->user()['id'] ?? 0),
                ]);
            }
            return $this->show($c);
        }
        if ($in->kind !== 'key') {
            return [$c->bell()];
        }
        $c->set('i', $i + 1);
        if (($steps[$i] ?? '') === 'mail' && strtoupper((string)$in->key) === 'Y') {
            // 1.5.0: reading mail here replaces the rest of the ceremony;
            // leaving the mailbox lands on MAIN, not back in the tour.
            $c->set('i', count($steps));
            return $c->push('MailBox');
        }
        return $this->show($c);
    }

    private function show(Ctx $c, string $noticeKey = ''): array
    {
        $steps = (array)$c->get('steps', ['welcome']);
        $i = (int)$c->get('i', 0);
        if ($i >= count($steps)) {
            return $c->swap('Menu', ['menu' => 'MAIN']);
        }
        $step = (string)$steps[$i];
        if ($step === 'email') {
            return $this->askEmail($c, $noticeKey);
        }
        if (str_starts_with($step, 'bull:')) {
            $body = $this->bulletin($c, (int)substr($step, 5));
        } else {
            $body = match ($step) {
                'rumor' => $this->rumor($c),
                'automsg' => $this->automsg($c),
                'mail' => $this->mailCheck($c),
                'lastcallers' => Ansi::make()->cls()->str() . LastCallersScreen::renderBody($c, 3),
                default => $this->welcome($c),
            };
        }
        $pause = Ansi::make()->at(24, 1)->txt($c->center($c->tstr('pause.more')))->str();
        return [$c->ansi($body . $pause), $c->hotkey()];
    }

    /**
     * Every visible bulletin the caller has not seen, oldest first
     * (1.5.0): a brand-new caller walks the whole board history. The
     * watermark advances immediately, so a mid-tour hangup still counts
     * as read. Edits do not re-greet (owner ruling).
     */
    private function unreadBulletins(Ctx $c): array
    {
        $user = $c->user();
        if ($user === null) {
            return [];
        }
        $seen = (int)($user['last_bull'] ?? 0);
        $fresh = array_values(array_filter(
            BulletinsScreen::visible($c),
            fn(array $b) => (int)$b['id'] > $seen
        ));
        usort($fresh, fn(array $a, array $b) => (int)$a['id'] <=> (int)$b['id']);
        if ($fresh !== []) {
            $top = (int)$fresh[count($fresh) - 1]['id'];
            $c->db()->q('UPDATE users SET last_bull = ? WHERE id = ?', [$top, (int)$user['id']]);
        }
        return $fresh;
    }

    /** The one line-mode stop on the tour: a way to reach you (1.5.0). */
    private function askEmail(Ctx $c, string $noticeKey): array
    {
        $o = Ansi::make()->cls();
        $o->at(6, 1)->txt($c->center($c->tstr('logon.email_title')));
        $o->at(7, 1)->fg(8)->txt($c->center(str_repeat('─', 44)))->reset();
        $o->at(10, 6)->txt($c->tstr('logon.email_why'));
        $o->at(11, 6)->txt($c->tstr('logon.email_private'));
        if ($noticeKey !== '') {
            $o->at(20, 1)->txt($c->center($c->tstr($noticeKey)));
        }
        $o->at(14, 6)->txt($c->tstr('logon.email_ask'));
        return [$c->ansi($o->str()), $c->line(14, 15, 60)];
    }

    private function bulletin(Ctx $c, int $id): string
    {
        $b = $c->db()->one('SELECT * FROM bulletins WHERE id = ?', [$id]);
        if ($b === null) {
            return $this->welcome($c);
        }
        $o = Ansi::make()->cls();
        $o->at(2, 1)->txt($c->center($c->tstr('logon.bull_title')));
        $o->at(4, 1)->txt($c->center($c->pipe('|15' . \Atdt\Pipe::escape((string)$b['title']))));
        $o->at(5, 1)->fg(8)->txt($c->center(str_repeat('─', 50)))->reset();
        $row = 7;
        foreach (explode("\n", str_replace("\r", '', (string)$b['body'])) as $line) {
            foreach (\Atdt\Pipe::wrapVisible(rtrim($line), 70) as $piece) {
                $o->at($row++, 6)->fg(7)->txt(\Atdt\Pipe::render($piece, $c->mciVars()));
                if ($row > 19) {
                    break 2; // a teaser: the bulletins menu has the whole thing
                }
            }
        }
        $o->reset()->at(21, 1)->txt($c->center($c->tstr('logon.bull_more')));
        return $o->str();
    }

    private function mailCheck(Ctx $c): string
    {
        $user = $c->user();
        $n = $user === null ? 0 : \Atdt\Messages::unreadMailCount($c->db(), $user);
        $o = Ansi::make()->cls();
        $o->at(6, 1)->txt($c->center($c->tstr('logon.mail_title')));
        $o->at(7, 1)->fg(8)->txt($c->center(str_repeat('─', 30)))->reset();
        $o->at(10, 1)->txt($c->center($c->tstr('logon.mail_body', ['NL' => (string)$n])));
        $o->at(12, 1)->txt($c->center($c->tstr('logon.mail_keys')));
        return $o->str();
    }

    private function welcome(Ctx $c): string
    {
        $art = $c->theme()->art('logon');
        if ($art !== null) {
            return Ansi::make()->cls()->txt($c->mci($art))->str();
        }
        $u = $c->user() ?? [];
        $vars = [
            'CL' => (string)(int)($u['calls'] ?? 0),
            'PS' => (string)(int)($u['posts'] ?? 0),
        ];
        $o = Ansi::make()->cls();
        $o->at(2, 1)->txt($c->center($c->tstr('logon.title')));
        $o->at(3, 1)->fg(8)->txt($c->center(str_repeat('─', 44)))->reset();
        $row = 6;
        foreach (['row_handle', 'row_level', 'row_node', 'row_calls', 'row_posts'] as $key) {
            $o->at($row++, 24)->txt($c->tstr('logon.' . $key, $vars));
        }
        $o->at(14, 1)->txt($c->center($c->tstr('logon.motd')));
        return $o->str();
    }

    private function rumor(Ctx $c): string
    {
        $o = Ansi::make()->cls();
        $o->at(4, 1)->txt($c->center($c->tstr('logon.rumor_title')));
        $o->at(5, 1)->fg(8)->txt($c->center(str_repeat('─', 30)))->reset();
        $row = 9;
        foreach ($this->wrap((string)$c->get('rumor', ''), 64) as $line) {
            $o->at($row++, 1)->txt($c->center($c->pipe(self::userLine($c, $line, 64))));
            if ($row > 18) {
                break;
            }
        }
        return $o->reset()->str();
    }

    private function automsg(Ctx $c): string
    {
        $s = $c->settings();
        $o = Ansi::make()->cls();
        $o->at(4, 1)->txt($c->center($c->tstr('logon.automsg_title')));
        $o->at(5, 1)->fg(8)->txt($c->center(str_repeat('─', 30)))->reset();
        $row = 8;
        // Full width (1.6.0): the wall's editor writes 79-column lines, so
        // re-wrapping narrower split them into ragged orphan rows.
        foreach ($this->wrap((string)($s->get('automsg_body', '') ?? ''), 79) as $line) {
            $o->at($row++, 1)->txt($c->pipe(self::userLine($c, $line, 79)));
            if ($row > 18) {
                break;
            }
        }
        $by = trim((string)($s->get('automsg_by', '') ?? ''));
        if ($by !== '') {
            $o->at(20, 1)->txt($c->center($c->tstr('logon.automsg_by', ['AB' => \Atdt\Pipe::escape($by)])));
        }
        return $o->reset()->str();
    }

    /**
     * Caller-authored text, same rule as the chat room and the toys
     * (owner round 6): allow_user_color on renders the pipes, off shows
     * them as typed. Clipped by visible width so colors can't wrap rows.
     */
    private static function userLine(Ctx $c, string $line, int $width): string
    {
        $colors = $c->settings()->bool('allow_user_color', false);
        return ChatScreen::clip('|07' . ($colors ? $line : \Atdt\Pipe::escape($line)), $width);
    }

    /** @return list<string> */
    private function wrap(string $text, int $width): array
    {
        $out = [];
        foreach (explode("\n", str_replace("\r", '', $text)) as $para) {
            $wrapped = wordwrap($para, $width, "\n", true);
            foreach (explode("\n", $wrapped) as $line) {
                $out[] = $line;
            }
        }
        return $out;
    }
}
