<?php

declare(strict_types=1);

namespace FecaMailshots\Domain;

final class DslLexer
{
    /** @return list<DslToken> */
    public function lex(string $input): array
    {
        $tokens = [];
        $len = strlen($input);
        $i = 0;

        while ($i < $len) {
            $ch = $input[$i];
            if (ctype_space($ch)) {
                $i++;
                continue;
            }

            if ($ch === '(' || $ch === ')' || $ch === ',' || $ch === '.') {
                $tokens[] = new DslToken($ch, $ch, $i);
                $i++;
                continue;
            }

            if ($ch === '!' && ($i + 1 < $len) && $input[$i + 1] === '=') {
                $tokens[] = new DslToken('OP', '!=', $i);
                $i += 2;
                continue;
            }

            if ($ch === '=') {
                $tokens[] = new DslToken('OP', '=', $i);
                $i++;
                continue;
            }

            if ($ch === '\'') {
                $start = $i;
                $i++;
                $value = '';
                while ($i < $len && $input[$i] !== '\'') {
                    $value .= $input[$i];
                    $i++;
                }
                if ($i >= $len) {
                    throw new AppError('dsl_lex', 'Unterminated string literal', ['offset' => $start]);
                }
                $i++;
                $tokens[] = new DslToken('STRING', $value, $start);
                continue;
            }

            if ($ch === '`') {
                $start = $i;
                $i++;
                $value = '';
                while ($i < $len && $input[$i] !== '`') {
                    $value .= $input[$i];
                    $i++;
                }
                if ($i >= $len) {
                    throw new AppError('dsl_lex', 'Unterminated quoted identifier', ['offset' => $start]);
                }
                $i++;
                $tokens[] = new DslToken('IDENT', $value, $start);
                continue;
            }

            if (ctype_digit($ch)) {
                $start = $i;
                $value = '';
                while ($i < $len && ctype_digit($input[$i])) {
                    $value .= $input[$i];
                    $i++;
                }
                $tokens[] = new DslToken('NUMBER', $value, $start);
                continue;
            }

            if (ctype_alpha($ch) || $ch === '_') {
                $start = $i;
                $value = '';
                while ($i < $len && (ctype_alnum($input[$i]) || $input[$i] === '_' || $input[$i] === '-')) {
                    $value .= $input[$i];
                    $i++;
                }
                $lower = strtolower($value);
                $keywords = [
                    'and', 'where', 'not', 'in', 'contains', 'starts-with', 'ends-with',
                    'true', 'false', 'blank',
                ];
                $tokens[] = in_array($lower, $keywords, true)
                    ? new DslToken('KW', $lower, $start)
                    : new DslToken('IDENT', $value, $start);
                continue;
            }

            throw new AppError('dsl_lex', 'Unexpected character in DSL', ['offset' => $i, 'char' => $ch]);
        }

        $tokens[] = new DslToken('EOF', '', $len);
        return $tokens;
    }
}
