<?php

if (!function_exists('str_contains')) {
    function str_contains($haystack, $needle)
    {
        return $needle === '' || strpos((string) $haystack, (string) $needle) !== false;
    }
}

if (!function_exists('str_starts_with')) {
    function str_starts_with($haystack, $needle)
    {
        return substr((string) $haystack, 0, strlen((string) $needle)) === (string) $needle;
    }
}

function normalizeDateInput($value)
{
    $value = trim((string) $value);
    if ($value === '') {
        return null;
    }

    $date = DateTime::createFromFormat('Y-m-d', $value);
    $errors = DateTime::getLastErrors();
    $warningCount = is_array($errors) ? (int) (isset($errors['warning_count']) ? $errors['warning_count'] : 0) : 0;
    $errorCount = is_array($errors) ? (int) (isset($errors['error_count']) ? $errors['error_count'] : 0) : 0;

    if (!$date || $warningCount > 0 || $errorCount > 0 || $date->format('Y-m-d') !== $value) {
        return null;
    }

    return $value;
}

function normalizeSubmittedDate($value)
{
    $value = trim((string) $value);
    if ($value === '') {
        return null;
    }

    $date = null;
    if (preg_match('/^\d{2}\/\d{2}\/\d{4}$/', $value) === 1) {
        $date = DateTime::createFromFormat('d/m/Y', $value);
    } elseif (preg_match('/^\d{4}-\d{2}-\d{2}$/', $value) === 1) {
        $date = DateTime::createFromFormat('Y-m-d', $value);
    }

    if (!$date) {
        return null;
    }

    return $date->format('Y-m-d');
}

function normalizeCategoria($categoria)
{
    $categoria = trim((string) $categoria);
    if ($categoria === '') {
        return null;
    }

    $categoriaLower = function_exists('mb_strtolower')
        ? mb_strtolower($categoria, 'UTF-8')
        : strtolower($categoria);

    return $categoriaLower === 'sem categoria' ? null : $categoria;
}

function normalizeSearchTerm($value)
{
    $normalized = preg_replace('/\s+/u', ' ', trim((string) $value));
    return $normalized === null ? '' : $normalized;
}

function normalizeCategoryList(array $values)
{
    $normalized = array();
    foreach ($values as $value) {
        $display = normalizeCategoria((string) $value);
        $display = $display === null ? 'Sem categoria' : $display;
        if ($display === '' || in_array($display, $normalized, true)) {
            continue;
        }
        $normalized[] = $display;
    }

    return $normalized;
}

function storedFileDisplayName($value)
{
    $value = trim((string) $value);
    if ($value === '') {
        return '';
    }

    $path = parse_url($value, PHP_URL_PATH);
    $basename = basename((string) ($path ?: $value));
    return $basename === '' || $basename === '.' || $basename === '/' ? $value : $basename;
}

function resolveStoredLocalFilePath($value, $baseDir = null)
{
    $value = trim((string) $value);
    if ($value === '') {
        return null;
    }

    $baseDir = $baseDir ?: __DIR__;
    $baseReal = realpath($baseDir) ?: $baseDir;

    if (preg_match('#^https?://#i', $value)) {
        return null;
    }

    $path = parse_url($value, PHP_URL_PATH);
    $rawPath = str_replace(array("\0", '\\'), array('', '/'), (string) ($path ?: $value));

    if ($rawPath === '' || str_contains($rawPath, '../')) {
        return null;
    }

    if (preg_match('#^(?:[A-Za-z]:)?/#', $rawPath) === 1) {
        $absoluteCandidate = realpath($rawPath) ?: $rawPath;
        $basePrefix = rtrim($baseReal, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR;
        return str_starts_with($absoluteCandidate, $basePrefix) ? $absoluteCandidate : null;
    }

    $relative = ltrim($rawPath, '/');
    $candidate = rtrim($baseReal, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR . str_replace('/', DIRECTORY_SEPARATOR, $relative);
    $candidateReal = realpath($candidate);

    if ($candidateReal !== false) {
        $basePrefix = rtrim($baseReal, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR;
        return str_starts_with($candidateReal, $basePrefix) ? $candidateReal : null;
    }

    if (!str_starts_with($relative, 'uploads/')) {
        return null;
    }

    return $candidate;
}

function deleteStoredFiles(array $paths, $baseDir = null)
{
    foreach ($paths as $path) {
        $filePath = resolveStoredLocalFilePath((string) $path, $baseDir);
        if ($filePath !== null && is_file($filePath)) {
            @unlink($filePath);
        }
    }
}

function insertRowFromArray(PDO $pdo, $table, array $row)
{
    if ($row === array()) {
        throw new InvalidArgumentException('Nenhum dado informado para inserção.');
    }

    $columns = array_keys($row);
    $quotedColumns = array();
    $placeholders = array();
    foreach ($columns as $column) {
        $quotedColumns[] = '`' . str_replace('`', '``', $column) . '`';
        $placeholders[] = ':' . $column;
    }

    $sql = sprintf(
        'INSERT INTO `%s` (%s) VALUES (%s)',
        str_replace('`', '``', (string) $table),
        implode(', ', $quotedColumns),
        implode(', ', $placeholders)
    );

    $stmt = $pdo->prepare($sql);
    foreach ($row as $column => $value) {
        if ($value === null) {
            $stmt->bindValue(':' . $column, null, PDO::PARAM_NULL);
        } else {
            $stmt->bindValue(':' . $column, $value);
        }
    }
    $stmt->execute();

    return (int) $pdo->lastInsertId();
}

function filterDuplicableRow(array $row, array $exclude = array('id', 'created_at', 'updated_at', 'deleted_at', 'uuid', 'token', 'public_token', 'slug', 'hash'))
{
    foreach (array_unique($exclude) as $column) {
        unset($row[$column]);
    }

    return $row;
}

function truncateText($value, $maxLength)
{
    $value = trim((string) $value);
    $maxLength = (int) $maxLength;
    if ($maxLength <= 0) {
        return '';
    }

    if (function_exists('mb_substr')) {
        return mb_substr($value, 0, $maxLength, 'UTF-8');
    }

    return substr($value, 0, $maxLength);
}



function getUploadExtensionsMap()
{
    return array(
        'image' => array('jpg', 'jpeg', 'png', 'gif', 'webp'),
        'file' => array('pdf', 'doc', 'docx', 'jpg', 'jpeg', 'png', 'gif', 'webp'),
    );
}

function getAllowedUploadExtensions($tipo)
{
    $map = getUploadExtensionsMap();
    return isset($map[$tipo]) ? $map[$tipo] : array();
}

function getFileInputAccept($tipo)
{
    $extensions = getAllowedUploadExtensions($tipo);
    if ($extensions === array()) {
        return '';
    }

    $parts = array();
    foreach ($extensions as $ext) {
        $parts[] = '.' . $ext;
    }

    return implode(',', $parts);
}

function sanitizeUploadOriginalName($name, $fallbackExt = '')
{
    $name = preg_replace('/[^a-zA-Z0-9._-]/', '', (string) $name);
    if ($name !== null) {
        $name = trim($name);
    }
    if ($name === '') {
        $fallbackExt = trim((string) $fallbackExt);
        return $fallbackExt !== '' ? ('arquivo.' . $fallbackExt) : 'arquivo';
    }

    return $name;
}

function validateUploadedFileForField(array $file, $tipo, $label, $maxUploadBytes)
{
    $label = trim((string) $label);
    $label = $label !== '' ? $label : 'arquivo';
    $maxUploadBytes = (int) $maxUploadBytes;

    if (($file['error'] ?? UPLOAD_ERR_NO_FILE) !== UPLOAD_ERR_OK) {
        throw new RuntimeException('Falha no upload do campo ' . $label . '.');
    }

    $originalName = (string) ($file['name'] ?? 'arquivo');
    $tmpName = (string) ($file['tmp_name'] ?? '');
    $fileSize = (int) ($file['size'] ?? 0);
    $ext = strtolower((string) pathinfo($originalName, PATHINFO_EXTENSION));
    $allowedForField = getAllowedUploadExtensions($tipo);

    if ($allowedForField === array()) {
        throw new RuntimeException('Tipo de upload inválido no campo ' . $label . '.');
    }
    if (!in_array($ext, $allowedForField, true)) {
        throw new RuntimeException('Extensão não permitida no campo ' . $label . '.');
    }
    if ($fileSize <= 0 || ($maxUploadBytes > 0 && $fileSize > $maxUploadBytes)) {
        throw new RuntimeException('Tamanho de arquivo inválido no campo ' . $label . '.');
    }
    if ($tmpName === '' || !is_uploaded_file($tmpName)) {
        throw new RuntimeException('Upload inválido no campo ' . $label . '.');
    }

    return array(
        'original_name' => $originalName,
        'tmp_name' => $tmpName,
        'size' => $fileSize,
        'ext' => $ext,
        'safe_name' => sanitizeUploadOriginalName($originalName, $ext),
    );
}

function normalizePostedArrayValues($rawValue)
{
    $items = array();
    foreach ((array) $rawValue as $item) {
        $item = trim((string) $item);
        if ($item !== '') {
            $items[] = $item;
        }
    }
    return array_values($items);
}

function splitOptions($opcoes)
{
    if ($opcoes === null || trim((string) $opcoes) === '') {
        return array();
    }

    $items = explode(',', (string) $opcoes);
    $output = array();
    foreach ($items as $item) {
        $item = trim((string) $item);
        if ($item !== '') {
            $output[] = $item;
        }
    }

    return array_values($output);
}

function escapeLike($value, $escapeChar = '\\')
{
    return str_replace(
        array($escapeChar, '%', '_'),
        array($escapeChar . $escapeChar, $escapeChar . '%', $escapeChar . '_'),
        (string) $value
    );
}

function digitsOnly($value)
{
    return preg_replace('/\D+/', '', (string) $value) ?: '';
}

function isValidCpf($value)
{
    $cpf = digitsOnly($value);
    if (strlen($cpf) !== 11 || preg_match('/^(\d)\1{10}$/', $cpf) === 1) {
        return false;
    }

    for ($t = 9; $t < 11; $t++) {
        $sum = 0;
        for ($i = 0; $i < $t; $i++) {
            $sum += ((int) $cpf[$i]) * (($t + 1) - $i);
        }
        $digit = ((10 * $sum) % 11) % 10;
        if ((int) $cpf[$t] !== $digit) {
            return false;
        }
    }

    return true;
}

function isValidCnpj($value)
{
    $cnpj = digitsOnly($value);
    if (strlen($cnpj) !== 14 || preg_match('/^(\d)\1{13}$/', $cnpj) === 1) {
        return false;
    }

    $weights1 = array(5, 4, 3, 2, 9, 8, 7, 6, 5, 4, 3, 2);
    $weights2 = array(6, 5, 4, 3, 2, 9, 8, 7, 6, 5, 4, 3, 2);

    $sum = 0;
    for ($i = 0; $i < 12; $i++) {
        $sum += ((int) $cnpj[$i]) * $weights1[$i];
    }
    $rest = $sum % 11;
    $digit1 = $rest < 2 ? 0 : 11 - $rest;
    if ((int) $cnpj[12] !== $digit1) {
        return false;
    }

    $sum = 0;
    for ($i = 0; $i < 13; $i++) {
        $sum += ((int) $cnpj[$i]) * $weights2[$i];
    }
    $rest = $sum % 11;
    $digit2 = $rest < 2 ? 0 : 11 - $rest;

    return (int) $cnpj[13] === $digit2;
}
