<?php
/**
 * Input Validators
 */

class Validator
{
    private array $errors = [];

    public function validate(array $data, array $rules): bool
    {
        $this->errors = [];

        foreach ($rules as $field => $fieldRules) {
            $value = $data[$field] ?? null;
            $ruleList = is_string($fieldRules) ? explode('|', $fieldRules) : $fieldRules;

            foreach ($ruleList as $rule) {
                $params = [];
                if (str_contains($rule, ':')) {
                    [$rule, $paramStr] = explode(':', $rule, 2);
                    $params = explode(',', $paramStr);
                }

                $method = 'validate_' . $rule;
                if (method_exists($this, $method)) {
                    $this->$method($field, $value, $params, $data);
                }
            }
        }

        return empty($this->errors);
    }

    public function getErrors(): array
    {
        return $this->errors;
    }

    public function getFirstError(): ?string
    {
        return $this->errors[0] ?? null;
    }

    private function addError(string $field, string $message): void
    {
        $this->errors[] = $message;
    }

    private function validate_required(string $field, $value, array $params, array $data): void
    {
        if ($value === null || $value === '' || (is_array($value) && empty($value))) {
            $this->addError($field, ucfirst($field) . ' is required.');
        }
    }

    private function validate_email(string $field, $value, array $params, array $data): void
    {
        if ($value !== null && $value !== '' && !filter_var($value, FILTER_VALIDATE_EMAIL)) {
            $this->addError($field, 'Please enter a valid email address.');
        }
    }

    private function validate_min(string $field, $value, array $params, array $data): void
    {
        $min = (int) $params[0];
        if (is_string($value) && strlen($value) < $min) {
            $this->addError($field, ucfirst($field) . " must be at least $min characters.");
        }
        if (is_numeric($value) && $value < $min) {
            $this->addError($field, ucfirst($field) . " must be at least $min.");
        }
    }

    private function validate_max(string $field, $value, array $params, array $data): void
    {
        $max = (int) $params[0];
        if (is_string($value) && strlen($value) > $max) {
            $this->addError($field, ucfirst($field) . " must not exceed $max characters.");
        }
    }

    private function validate_numeric(string $field, $value, array $params, array $data): void
    {
        if ($value !== null && $value !== '' && !is_numeric($value)) {
            $this->addError($field, ucfirst($field) . ' must be a number.');
        }
    }

    private function validate_integer(string $field, $value, array $params, array $data): void
    {
        if ($value !== null && $value !== '' && !filter_var($value, FILTER_VALIDATE_INT)) {
            $this->addError($field, ucfirst($field) . ' must be an integer.');
        }
    }

    private function validate_in(string $field, $value, array $params, array $data): void
    {
        if ($value !== null && $value !== '' && !in_array($value, $params)) {
            $this->addError($field, ucfirst($field) . ' has an invalid value.');
        }
    }

    private function validate_unique(string $field, $value, array $params, array $data): void
    {
        if (empty($params[0])) return;
        $table = $params[0];
        $column = $params[1] ?? $field;
        $exceptId = $params[2] ?? null;

        $db = Database::getInstance();
        $sql = "SELECT COUNT(*) FROM `$table` WHERE `$column` = ?";
        $params_list = [$value];

        if ($exceptId) {
            $sql .= " AND id != ?";
            $params_list[] = $exceptId;
        }

        $count = $db->fetchColumn($sql, $params_list);
        if ($count > 0) {
            $this->addError($field, ucfirst($field) . ' already exists.');
        }
    }

    private function validate_confirmed(string $field, $value, array $params, array $data): void
    {
        $confirmField = $field . '_confirmation';
        if (($data[$confirmField] ?? '') !== $value) {
            $this->addError($field, ucfirst($field) . ' confirmation does not match.');
        }
    }

    private function validate_date(string $field, $value, array $params, array $data): void
    {
        if ($value !== null && $value !== '' && strtotime($value) === false) {
            $this->addError($field, ucfirst($field) . ' must be a valid date.');
        }
    }
}
