<?php

declare(strict_types=1);

namespace Atdt\Import\Sources;

use Atdt\Import\Normalizer;
use Atdt\Import\Source;

/**
 * FTS-1 stored messages (SPEC-IMPORT §11): one numbered .msg file per
 * message, a 190-byte fixed header, then NUL-terminated text. One
 * directory is one area; a directory of directories imports each child
 * as its own area. The simplest parser in the set, included because
 * some boards used *.msg for echo areas and it costs nothing once the
 * kludge stripper exists.
 */
final class Fts1 extends Source
{
    private const HDR = 190;

    public function open(string $dir): array
    {
        $units = [];
        $areas = [];
        if (self::msgFiles($dir) !== []) {
            $key = basename($dir);
            $areas[$key] = ['name' => $key, 'tag' => $key, 'descr' => '', 'echo_tag' => null];
            $units[] = '.';
        } else {
            foreach (glob($dir . '/*', GLOB_ONLYDIR) ?: [] as $sub) {
                if (self::msgFiles($sub) === []) {
                    continue;
                }
                $key = basename($sub);
                $areas[$key] = ['name' => $key, 'tag' => $key, 'descr' => '', 'echo_tag' => null];
                $units[] = $key;
            }
        }
        if ($units === []) {
            return ['ok' => false, 'error' => 'No .msg files found: FTS-1 areas are directories of numbered '
                . '*.msg files, one file per message.', 'areas' => [], 'units' => []];
        }
        return ['ok' => true, 'error' => null, 'areas' => $areas, 'units' => $units];
    }

    public function walk(string $dir, array $units, array $cursor, int $limit, Normalizer $n, callable $emit): array
    {
        $u = (int)($cursor['u'] ?? 0);
        $i = (int)($cursor['i'] ?? 0);
        $skipped = 0;
        $emitted = 0;
        while ($u < count($units) && $emitted < $limit) {
            $sub = (string)$units[$u];
            $path = $sub === '.' ? $dir : "$dir/$sub";
            $key = $sub === '.' ? basename($dir) : $sub;
            $files = self::msgFiles($path);
            for (; $i < count($files) && $emitted < $limit; $i++) {
                $raw = (string)@file_get_contents($files[$i]);
                if (strlen($raw) < self::HDR) {
                    $skipped++;
                    continue;
                }
                $body = substr($raw, self::HDR);
                $nul = strpos($body, "\0");
                if ($nul !== false) {
                    $body = substr($body, 0, $nul);
                }
                $replyTo = self::u16($raw, 184);
                $emit([
                    'area_key' => $key,
                    'from_raw' => self::zstr($raw, 0, 36),
                    'to_raw' => self::zstr($raw, 36, 36),
                    'subject_raw' => self::zstr($raw, 72, 72),
                    'body_raw' => $body,
                    'date' => $n->dateFromFts(self::zstr($raw, 144, 20)),
                    'id' => (string)(int)pathinfo($files[$i], PATHINFO_FILENAME),
                    'reply_to' => $replyTo > 0 ? (string)$replyTo : null,
                ]);
                $emitted++;
            }
            if ($i >= count($files)) {
                $u++;
                $i = 0;
            }
        }
        return ['cursor' => ['u' => $u, 'i' => $i], 'done' => $u >= count($units), 'skipped' => $skipped];
    }

    /** Numbered *.msg files, sorted numerically. @return list<string> */
    private static function msgFiles(string $dir): array
    {
        $files = [];
        foreach (glob($dir . '/*') ?: [] as $f) {
            if (is_file($f) && preg_match('/^\d+\.msg$/i', basename($f)) === 1) {
                $files[] = $f;
            }
        }
        usort($files, fn($a, $b) => (int)pathinfo($a, PATHINFO_FILENAME) <=> (int)pathinfo($b, PATHINFO_FILENAME));
        return $files;
    }
}
