<?php

declare(strict_types=1);

namespace Atdt\Import\Sources;

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

/**
 * ENiGMA 1/2 (SPEC-IMPORT §13): no binary parsing at all, just PDO
 * against db/message.sqlite3. Areas come from the message rows with
 * their real tags; bodies are already UTF-8 and transcode DOWN to the
 * CP437 repertoire; reply linkage is relational and maps directly.
 * The private mail area is excluded at the source.
 */
final class Enigma extends Source
{
    public function open(string $dir): array
    {
        $db = self::dbFile($dir);
        if ($db === null) {
            return ['ok' => false, 'error' => 'No message.sqlite3 found. Point the import at the ENiGMA db/ '
                . 'directory (or upload a zip holding message.sqlite3).', 'areas' => [], 'units' => []];
        }
        try {
            $pdo = new \PDO('sqlite:' . $db, null, null, [\PDO::ATTR_ERRMODE => \PDO::ERRMODE_EXCEPTION]);
            $st = $pdo->prepare(
                'SELECT DISTINCT area_tag FROM message WHERE area_tag <> ? ORDER BY area_tag'
            );
            $st->execute(['private_mail']);
            $tags = $st->fetchAll(\PDO::FETCH_COLUMN);
        } catch (\Throwable $e) {
            return ['ok' => false, 'error' => 'message.sqlite3 would not open as an ENiGMA message database: '
                . $e->getMessage(), 'areas' => [], 'units' => []];
        }
        if (!is_array($tags) || $tags === []) {
            return ['ok' => false, 'error' => 'The database opened but holds no public message areas.',
                'areas' => [], 'units' => []];
        }
        $areas = [];
        $units = [];
        foreach ($tags as $tag) {
            $tag = (string)$tag;
            $areas[$tag] = ['name' => $tag, 'tag' => $tag, 'descr' => '', 'echo_tag' => null];
            $units[] = $tag;
        }
        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
    {
        $db = self::dbFile($dir);
        if ($db === null) {
            return ['cursor' => $cursor, 'done' => true, 'skipped' => 0];
        }
        $u = (int)($cursor['u'] ?? 0);
        $last = (int)($cursor['last'] ?? 0);
        $skipped = 0;
        $emitted = 0;
        try {
            $pdo = new \PDO('sqlite:' . $db, null, null, [\PDO::ATTR_ERRMODE => \PDO::ERRMODE_EXCEPTION]);
            while ($u < count($units) && $emitted < $limit) {
                $tag = (string)$units[$u];
                $st = $pdo->prepare(
                    'SELECT message_id, reply_to_message_id, to_user_name, from_user_name, subject, message,
                            modified_timestamp
                       FROM message WHERE area_tag = ? AND message_id > ? ORDER BY message_id LIMIT ?'
                );
                $st->execute([$tag, $last, $limit - $emitted]);
                $rows = $st->fetchAll(\PDO::FETCH_ASSOC) ?: [];
                if ($rows === []) {
                    $u++;
                    $last = 0;
                    continue;
                }
                foreach ($rows as $r) {
                    $last = (int)$r['message_id'];
                    $reply = (int)($r['reply_to_message_id'] ?? 0);
                    $emit([
                        'area_key' => $tag,
                        'from_raw' => (string)($r['from_user_name'] ?? ''),
                        'to_raw' => (string)($r['to_user_name'] ?? ''),
                        'subject_raw' => (string)($r['subject'] ?? ''),
                        'body_raw' => (string)($r['message'] ?? ''),
                        'date' => $n->dateFromLocal((string)($r['modified_timestamp'] ?? '')),
                        'id' => (string)$r['message_id'],
                        'reply_to' => $reply > 0 ? (string)$reply : null,
                    ]);
                    $emitted++;
                }
            }
        } catch (\Throwable $e) {
            return ['cursor' => ['u' => count($units), 'last' => 0], 'done' => true, 'skipped' => $skipped];
        }
        return ['cursor' => ['u' => $u, 'last' => $last], 'done' => $u >= count($units), 'skipped' => $skipped];
    }

    private static function dbFile(string $dir): ?string
    {
        foreach ([$dir . '/message.sqlite3', $dir . '/db/message.sqlite3'] as $f) {
            if (is_file($f)) {
                return $f;
            }
        }
        return null;
    }
}
