<?php

declare(strict_types=1);

namespace Atdt\Import;

/**
 * One legacy message store (SPEC-IMPORT §5-§13). A source is opened
 * against a directory, surveyed once, then walked in resumable chunks.
 * The same walk drives both passes: the scan (counting, date ranges,
 * samples) and the staging (normalized rows into import_staging), so
 * what the preview reported is exactly what the import writes.
 */
abstract class Source
{
    /**
     * Validate and survey the directory. Never throws.
     * @return array{ok: bool, error: ?string,
     *   areas: array<string, array{name: string, tag: string, descr: string, echo_tag: ?string}>,
     *   units: list<mixed>}
     */
    abstract public function open(string $dir): array;

    /**
     * Walk up to $limit messages from $cursor, calling $emit(record).
     * A record: area_key, from_raw, to_raw, subject_raw, body_raw
     * (source bytes), date (?string UTC, via $n), id (string),
     * reply_to (?string). Skipped records (deleted, unparseable) are
     * counted, never emitted.
     * @param list<mixed> $units from open()
     * @return array{cursor: array, done: bool, skipped: int}
     */
    abstract public function walk(
        string $dir,
        array $units,
        array $cursor,
        int $limit,
        Normalizer $n,
        callable $emit
    ): array;

    // ---- shared binary helpers -------------------------------------------

    /** Little-endian u16 at offset. */
    protected static function u16(string $b, int $o): int
    {
        return (int)(unpack('v', substr($b, $o, 2))[1] ?? 0);
    }

    /** Little-endian u32 at offset. */
    protected static function u32(string $b, int $o): int
    {
        return (int)(unpack('V', substr($b, $o, 4))[1] ?? 0);
    }

    /** Fixed-width field, cut at the first NUL, trimmed. */
    protected static function zstr(string $b, int $o, int $len): string
    {
        $s = substr($b, $o, $len);
        $nul = strpos($s, "\0");
        if ($nul !== false) {
            $s = substr($s, 0, $nul);
        }
        return trim($s);
    }

    /** Turbo Pascal string: length byte + payload inside a fixed field. */
    protected static function pstr(string $b, int $o, int $field): string
    {
        $len = min(ord($b[$o] ?? "\0"), $field - 1);
        return trim(substr($b, $o + 1, $len));
    }
}
