<?php

declare(strict_types=1);

namespace Atdt;

/**
 * One caller input: a hotkey, or a completed line/editor payload (§9.4).
 *
 * Ingestion is where input hygiene lives (§19.7): control characters are
 * stripped and lengths are capped HERE, once, so no screen ever sees a
 * raw ESC (ANSI injection into an echo), a NUL, or a megabyte "line" —
 * whatever an individual screen forgets. Screens still apply their own
 * tighter, field-specific caps on top.
 */
final class In
{
    public const MAX_LINE = 512;      // widest legitimate line-mode answer
    public const MAX_EDITOR_LINES = 400; // Messages enforces 200; belt here

    public function __construct(
        public readonly string $kind, // key|line|editor
        public readonly ?string $key = null,
        public readonly ?string $line = null,   // null = ESC-aborted line
        public readonly ?array $lines = null    // null = aborted editor
    ) {
    }

    public static function key(string $key): self
    {
        // Key names are single glyphs or short words (ENTER, PGUP, F10).
        return new self('key', key: mb_substr(self::scrub($key), 0, 12));
    }

    public static function line(?string $line): self
    {
        return new self('line', line: $line === null ? null : self::clamp($line));
    }

    public static function editor(?array $lines): self
    {
        if ($lines === null) {
            return new self('editor', lines: null);
        }
        $clean = [];
        foreach (array_slice(array_values($lines), 0, self::MAX_EDITOR_LINES) as $l) {
            $clean[] = self::clamp((string)$l);
        }
        return new self('editor', lines: $clean);
    }

    /** Strip ASCII control bytes (incl. ESC, DEL). UTF-8-safe: continuation
     *  bytes are 0x80+, so byte-range stripping can't split a character. */
    private static function scrub(string $s): string
    {
        return preg_replace('/[\x00-\x08\x0A-\x1F\x7F]/', '', str_replace("\t", ' ', $s)) ?? '';
    }

    private static function clamp(string $s): string
    {
        return mb_substr(self::scrub($s), 0, self::MAX_LINE);
    }
}
