<?php
function e(?string $value): string
{
    return htmlspecialchars((string)$value, ENT_QUOTES, 'UTF-8');
}

function mask_secret(?string $value, int $visible = 4): string
{
    $value = (string)$value;
    if ($value === '') {
        return '';
    }
    if (strlen($value) <= $visible * 2) {
        return str_repeat('*', max(4, strlen($value)));
    }
    return substr($value, 0, $visible) . str_repeat('*', max(4, strlen($value) - ($visible * 2))) . substr($value, -$visible);
}

function app_config_path(): string
{
    return __DIR__ . '/../../config/config.php';
}

function app_config_exists(): bool
{
    return file_exists(app_config_path());
}

function config(string $key, $default = null)
{
    static $config = null;
    if ($config === null) {
        $file = app_config_exists() ? app_config_path() : (__DIR__ . '/../../config/config.example.php');
        $config = require $file;
    }

    $segments = explode('.', $key);
    $value = $config;
    foreach ($segments as $segment) {
        if (!is_array($value) || !array_key_exists($segment, $value)) {
            return $default;
        }
        $value = $value[$segment];
    }
    return $value;
}

function app_key(): string
{
    $key = (string)config('app.key', '');
    if ($key === '') {
        $key = hash('sha256', (string)config('app.base_url', 'local-app') . '|' . (string)config('db.database', 'local-db'));
    }
    return substr(hash('sha256', $key, true), 0, 32);
}

function encrypt_value(?string $plaintext): ?string
{
    if ($plaintext === null || $plaintext === '') {
        return $plaintext;
    }
    if (str_starts_with($plaintext, 'enc::')) {
        return $plaintext;
    }
    $iv = random_bytes(16);
    $cipher = openssl_encrypt($plaintext, 'AES-256-CBC', app_key(), OPENSSL_RAW_DATA, $iv);
    if ($cipher === false) {
        return $plaintext;
    }
    return 'enc::' . base64_encode($iv . $cipher);
}

function decrypt_value(?string $ciphertext): ?string
{
    if ($ciphertext === null || $ciphertext === '') {
        return $ciphertext;
    }
    if (!str_starts_with($ciphertext, 'enc::')) {
        return $ciphertext;
    }
    $payload = base64_decode(substr($ciphertext, 5), true);
    if ($payload === false || strlen($payload) < 17) {
        return null;
    }
    $iv = substr($payload, 0, 16);
    $cipher = substr($payload, 16);
    $plain = openssl_decrypt($cipher, 'AES-256-CBC', app_key(), OPENSSL_RAW_DATA, $iv);
    return $plain === false ? null : $plain;
}

function sensitive_setting_keys(): array
{
    return ['twilio_token','mail_smtp_password','imap_password'];
}

function setting_value(string $key, $default = null)
{
    static $cache = null;
    if ($cache === null) {
        $cache = [];
        try {
            require_once __DIR__ . '/../models/Setting.php';
            $cache = (new Setting())->all();
        } catch (Throwable $e) {
            $cache = [];
        }
    }
    return array_key_exists($key, $cache) && $cache[$key] !== '' && $cache[$key] !== null ? $cache[$key] : $default;
}

function user_integration_profile(?int $userId = null): array
{
    static $profiles = [];
    $userId = $userId ?: (current_user()['id'] ?? 0);
    if ($userId <= 0) {
        return [];
    }
    if (!array_key_exists($userId, $profiles)) {
        try {
            require_once __DIR__ . '/../models/UserIntegrationProfile.php';
            $profiles[$userId] = (new UserIntegrationProfile())->findByUserId($userId) ?: [];
        } catch (Throwable $e) {
            $profiles[$userId] = [];
        }
    }
    return $profiles[$userId];
}

function effective_twilio_config(?int $userId = null): array
{
    $profile = user_integration_profile($userId);
    $usePersonal = !empty($profile['use_personal_twilio']);
    return [
        'sid' => $usePersonal && !empty($profile['twilio_sid']) ? $profile['twilio_sid'] : setting_value('twilio_sid', config('twilio.sid')),
        'token' => $usePersonal && !empty($profile['twilio_token']) ? $profile['twilio_token'] : setting_value('twilio_token', config('twilio.token')),
        'from_number' => $usePersonal && !empty($profile['twilio_from_number']) ? $profile['twilio_from_number'] : setting_value('twilio_from_number', config('twilio.from_number')),
        'validate_signature' => (bool)setting_value('twilio_validate_signature', config('twilio.validate_signature', true)),
    ];
}

function effective_mail_config(?int $userId = null): array
{
    $profile = user_integration_profile($userId);
    $usePersonal = !empty($profile['use_personal_mail']);
    return [
        'from_name' => $usePersonal && !empty($profile['mail_from_name']) ? $profile['mail_from_name'] : setting_value('mail_from_name', config('mail.from_name')),
        'from_email' => $usePersonal && !empty($profile['mail_from_email']) ? $profile['mail_from_email'] : setting_value('mail_from_email', config('mail.from_email')),
        'smtp_enabled' => $usePersonal ? !empty($profile['smtp_enabled']) : (bool)setting_value('mail_smtp_enabled', config('mail.smtp_enabled', false)),
        'smtp_host' => $usePersonal && !empty($profile['smtp_host']) ? $profile['smtp_host'] : setting_value('mail_smtp_host', config('mail.smtp_host')),
        'smtp_port' => (int)($usePersonal && !empty($profile['smtp_port']) ? $profile['smtp_port'] : setting_value('mail_smtp_port', config('mail.smtp_port', 587))),
        'smtp_username' => $usePersonal && !empty($profile['smtp_username']) ? $profile['smtp_username'] : setting_value('mail_smtp_username', config('mail.smtp_username')),
        'smtp_password' => $usePersonal && !empty($profile['smtp_password']) ? $profile['smtp_password'] : setting_value('mail_smtp_password', config('mail.smtp_password')),
        'smtp_secure' => $usePersonal && !empty($profile['smtp_secure']) ? $profile['smtp_secure'] : setting_value('mail_smtp_secure', config('mail.smtp_secure', 'tls')),
        'reply_to_email' => $usePersonal && !empty($profile['reply_to_email']) ? $profile['reply_to_email'] : setting_value('reply_to_email', config('mail.from_email')),
    ];
}

function integration_preferences(?int $userId = null): array
{
    $profile = user_integration_profile($userId);
    return [
        'preferred_channel' => $profile['preferred_channel'] ?? setting_value('preferred_channel', 'email'),
        'default_sms_footer' => $profile['default_sms_footer'] ?? setting_value('default_sms_footer', ''),
        'default_email_footer' => $profile['default_email_footer'] ?? setting_value('default_email_footer', ''),
        'signature_name' => $profile['signature_name'] ?? '',
    ];
}

function apply_sms_preferences(string $body, ?int $userId = null): string
{
    $prefs = integration_preferences($userId);
    $footer = trim((string)($prefs['default_sms_footer'] ?? ''));
    return $footer !== '' ? rtrim($body) . "\n" . $footer : $body;
}

function apply_email_preferences(string $body, ?int $userId = null): string
{
    $prefs = integration_preferences($userId);
    $footer = trim((string)($prefs['default_email_footer'] ?? ''));
    $signature = trim((string)($prefs['signature_name'] ?? ''));
    if ($footer !== '') {
        $body .= "<hr><p>" . nl2br(e($footer)) . "</p>";
    }
    if ($signature !== '') {
        $body .= "<p>" . e($signature) . "</p>";
    }
    return $body;
}


function client_ip(): string
{
    foreach (['HTTP_CF_CONNECTING_IP', 'HTTP_X_FORWARDED_FOR', 'REMOTE_ADDR'] as $key) {
        $value = trim((string)($_SERVER[$key] ?? ''));
        if ($value !== '') {
            return trim(explode(',', $value)[0]);
        }
    }
    return '';
}

function start_session(): void
{
    if (session_status() !== PHP_SESSION_ACTIVE) {
        session_start();
    }
}

function redirect(string $route): void
{
    header('Location: index.php?route=' . $route);
    exit;
}

function redirect_url(string $url): void
{
    header('Location: ' . $url);
    exit;
}

function flash(string $key, ?string $message = null)
{
    start_session();
    if ($message === null) {
        $value = $_SESSION['_flash'][$key] ?? null;
        unset($_SESSION['_flash'][$key]);
        return $value;
    }
    $_SESSION['_flash'][$key] = $message;
}

function current_user(): ?array
{
    start_session();
    return $_SESSION['user'] ?? null;
}

function is_logged_in(): bool
{
    return current_user() !== null;
}

function require_auth(): void
{
    $route = $_GET['route'] ?? 'dashboard/index';
    $publicRoutes = [
        'auth/login','auth/authenticate','auth/forgotPassword','auth/sendResetLink','auth/resetPassword','auth/updatePassword','auth/twoFactor','auth/verifyTwoFactor',
        'webhook/twilio','webhook/twilioStatus','webhook/trackOpen','webhook/trackClick','webhook/emailEvent','messages/unsubscribe'
    ];
    if (!in_array($route, $publicRoutes, true) && !is_logged_in()) {
        flash('error', 'Please sign in to continue.');
        redirect('auth/login');
    }
}

function require_role(array $roles): void
{
    $user = current_user();
    if (!$user || !in_array($user['role'], $roles, true)) {
        http_response_code(403);
        exit('Access denied.');
    }
}

function permission_matrix(): array
{
    return [
        'admin' => ['dashboard.view','contacts.view','contacts.manage','contacts.delete','segments.manage','templates.view','templates.manage','templates.delete','messages.view','messages.send','messages.reply','messages.campaigns','messages.export','settings.view','settings.manage','users.manage'],
        'manager' => ['dashboard.view','contacts.view','contacts.manage','segments.manage','templates.view','templates.manage','messages.view','messages.send','messages.reply','messages.campaigns','messages.export','settings.view'],
        'agent' => ['dashboard.view','contacts.view','messages.view','messages.send','messages.reply'],
    ];
}

function can_access(string $permission): bool
{
    $user = current_user();
    if (!$user) {
        return false;
    }
    $permissions = permission_matrix()[$user['role'] ?? 'agent'] ?? [];
    $allowed = in_array($permission, $permissions, true);
    try {
        require_once __DIR__ . '/../models/UserPermission.php';
        $overrides = (new UserPermission())->mapForUser((int)$user['id']);
        if ($overrides) {
            return $overrides[$permission] ?? false;
        }
    } catch (Throwable $e) {
    }
    return $allowed;
}

function require_permission(string $permission): void
{
    if (!can_access($permission)) {
        http_response_code(403);
        exit('Access denied. Missing permission: ' . e($permission));
    }
}

function all_permissions(): array
{
    $all = [];
    foreach (permission_matrix() as $permissions) {
        foreach ($permissions as $permission) {
            $all[$permission] = $permission;
        }
    }
    ksort($all);
    return array_values($all);
}

function csrf_token(): string
{
    start_session();
    if (empty($_SESSION['_csrf'])) {
        $_SESSION['_csrf'] = bin2hex(random_bytes(32));
    }
    return $_SESSION['_csrf'];
}

function verify_csrf(): void
{
    start_session();
    $token = $_POST['_csrf'] ?? '';
    if (!$token || !hash_equals($_SESSION['_csrf'] ?? '', $token)) {
        http_response_code(419);
        render_boot_error_page('Security check failed', 'The request could not be verified.', 'Invalid CSRF token.', ['Back to sign in' => 'index.php?route=auth/login']);
    }
}

function full_url(string $path = ''): string
{
    return rtrim((string)config('app.base_url'), '/') . '/' . ltrim($path, '/');
}



function app_version_source(): string
{
    $file = __DIR__ . '/../../VERSION';
    if (is_file($file)) {
        $value = trim((string)file_get_contents($file));
        if ($value !== '') {
            return 'VERSION';
        }
    }
    return 'config';
}

function app_version(): string
{
    static $version = null;
    if ($version !== null) {
        return $version;
    }
    $file = __DIR__ . '/../../VERSION';
    if (is_file($file)) {
        $value = trim((string)file_get_contents($file));
        if ($value !== '') {
            return $version = $value;
        }
    }
    return $version = (string)config('app.version', '12.6');
}

function app_environment(): string
{
    return (string)config('app.environment', 'production');
}

function setting_value_fresh(string $key, $default = null)
{
    try {
        require_once __DIR__ . '/../models/Setting.php';
        return (new Setting())->get($key, $default);
    } catch (Throwable $e) {
        return $default;
    }
}

function schema_version(): string
{
    return (string)setting_value_fresh('schema_version', 'unknown');
}

function schema_matches_app_version(): bool
{
    $schema = schema_version();
    return $schema !== 'unknown' && $schema === app_version();
}

function worker_heartbeat(): array
{
    $raw = (string)setting_value_fresh('worker_last_heartbeat', '');
    if ($raw === '') {
        return ['status' => 'unknown', 'label' => 'No heartbeat yet', 'minutes' => null, 'at' => null];
    }
    try {
        $at = new DateTimeImmutable($raw);
        $now = new DateTimeImmutable('now');
        $minutes = max(0, (int) floor(($now->getTimestamp() - $at->getTimestamp()) / 60));
        $status = $minutes <= 10 ? 'healthy' : ($minutes <= 30 ? 'warning' : 'stale');
        $label = $status === 'healthy' ? 'Worker healthy' : ($status === 'warning' ? 'Worker delayed' : 'Worker stale');
        return ['status' => $status, 'label' => $label, 'minutes' => $minutes, 'at' => $at->format('Y-m-d H:i:s')];
    } catch (Throwable $e) {
        return ['status' => 'unknown', 'label' => 'Heartbeat unreadable', 'minutes' => null, 'at' => $raw];
    }
}

function app_schema_path(): string
{
    return __DIR__ . '/../../sql/schema.sql';
}

function app_expected_tables(): array
{
    return [
        'users','contacts','templates','campaigns','messages','campaign_recipients','password_resets','settings',
        'message_events','audit_logs','queue_jobs','queue_dead_letters','segments','user_permissions',
        'suppression_list','user_integration_profiles','custom_fields','contact_custom_values','consent_logs',
        'workflows','contact_tasks','contact_notes','canned_replies','conversation_states','accounts','pipelines','reminders'
    ];
}

function app_missing_tables(PDO $pdo, ?array $expected = null): array
{
    $expected = $expected ?: app_expected_tables();
    $stmt = $pdo->query('SHOW TABLES');
    $existing = array_map('strtolower', array_map('strval', $stmt->fetchAll(PDO::FETCH_COLUMN) ?: []));
    return array_values(array_diff($expected, $existing));
}

function render_boot_error_page(string $headline, string $message, string $detail = '', array $links = []): void
{
    http_response_code(http_response_code() >= 400 ? http_response_code() : 500);
    echo '<!doctype html><html><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">';
    echo '<title>' . e($headline) . '</title>';
    echo '<style>body{margin:0;font-family:Arial,sans-serif;background:#11345a;color:#0f172a}.wrap{max-width:960px;margin:40px auto;background:#f8fafc;border-radius:22px;padding:34px}.eyebrow{font-size:13px;letter-spacing:.12em;color:#64748b;text-transform:uppercase}.card{margin-top:28px;border:1px solid #cbd5e1;border-radius:20px;padding:28px;background:#fff}.detail{margin:18px 0;padding:16px;border:1px solid #dbe3ee;border-radius:12px;background:#f8fafc;font-family:ui-monospace,monospace;white-space:pre-wrap}.links a{display:inline-block;margin-right:16px;margin-top:8px;color:#0f4c81;text-decoration:none;font-weight:600}</style>';
    echo '</head><body><div class="wrap"><div class="eyebrow">Corporate Messaging Suite</div><h1 style="font-size:58px;line-height:1.05;margin:8px 0 0">' . e($headline) . '</h1><p style="font-size:18px;color:#475569">' . e($message) . '</p><div class="card">';
    if ($detail !== '') {
        echo '<div class="detail">' . e($detail) . '</div>';
    }
    if ($links) {
        echo '<div class="links">';
        foreach ($links as $label => $href) {
            echo '<a href="' . e($href) . '">' . e($label) . '</a>';
        }
        echo '</div>';
    }
    echo '</div></div></body></html>';
    exit;
}

function csrf_input(): string
{
    return '<input type="hidden" name="_csrf" value="' . e(csrf_token()) . '">';
}

function upload_file(array $file, array $allowedExtensions = []): ?string
{
    if (($file['error'] ?? UPLOAD_ERR_NO_FILE) !== UPLOAD_ERR_OK) {
        return null;
    }
    $extension = strtolower(pathinfo($file['name'] ?? '', PATHINFO_EXTENSION));
    if ($allowedExtensions && !in_array($extension, $allowedExtensions, true)) {
        throw new RuntimeException('Invalid file type uploaded.');
    }
    $targetDir = __DIR__ . '/../../public/uploads';
    if (!is_dir($targetDir)) {
        mkdir($targetDir, 0775, true);
    }
    $filename = uniqid('upload_', true) . '.' . $extension;
    $destination = $targetDir . '/' . $filename;
    if (!move_uploaded_file($file['tmp_name'], $destination)) {
        throw new RuntimeException('Unable to save uploaded file.');
    }
    return $filename;
}

function uploaded_file_path(string $filename): string
{
    return __DIR__ . '/../../public/uploads/' . $filename;
}

function normalize_address(string $address): string
{
    return trim(strtolower($address));
}


function normalize_phone_address(string $address): string
{
    $digits = preg_replace('/\D+/', '', (string)$address);
    if (str_starts_with($digits, '1') && strlen($digits) === 11) {
        $digits = substr($digits, 1);
    }
    return $digits;
}

function normalize_channel_address(string $channel, ?string $address): string
{
    $address = (string)$address;
    return $channel === 'sms' ? normalize_phone_address($address) : normalize_address($address);
}

function conversation_key(string $channel, ?string $fromAddress, ?string $toAddress, ?int $contactId = null): string
{
    $channel = strtolower(trim($channel));
    if ($contactId) {
        return $channel . ':contact:' . $contactId;
    }
    $candidate = normalize_channel_address($channel, (string)$toAddress);
    if ($candidate === '' || in_array($candidate, [normalize_channel_address($channel, (string)effective_twilio_config()['from_number']), normalize_channel_address($channel, (string)effective_mail_config()['from_email'])], true)) {
        $candidate = normalize_channel_address($channel, (string)$fromAddress);
    }
    return $channel . ':addr:' . $candidate;
}

function paginate_meta(int $page, int $perPage, int $total): array
{
    $pages = max(1, (int)ceil($total / max(1, $perPage)));
    $page = max(1, min($page, $pages));
    return [
        'page' => $page,
        'per_page' => $perPage,
        'total' => $total,
        'pages' => $pages,
        'offset' => ($page - 1) * $perPage,
    ];
}

function qs_without(array $keys): string
{
    $query = $_GET;
    foreach ($keys as $key) {
        unset($query[$key]);
    }
    return http_build_query($query);
}

function audit_log(string $action, ?string $entityType = null, ?int $entityId = null, array $meta = []): void
{
    try {
        require_once __DIR__ . '/../models/AuditLog.php';
        (new AuditLog())->create([
            'user_id' => current_user()['id'] ?? null,
            'action' => $action,
            'entity_type' => $entityType,
            'entity_id' => $entityId,
            'metadata_json' => $meta ? json_encode($meta) : null,
        ]);
    } catch (Throwable $e) {
    }
}

function render_template_content(string $content, array $contact = [], array $extra = []): string
{
    $replace = array_merge([
        '{{first_name}}' => $contact['first_name'] ?? '',
        '{{last_name}}' => $contact['last_name'] ?? '',
        '{{company}}' => $contact['company'] ?? '',
        '{{email}}' => $contact['email'] ?? '',
        '{{phone}}' => $contact['phone'] ?? '',
        '{{timezone}}' => $contact['timezone'] ?? '',
    ], $extra);

    foreach ($contact as $key => $value) {
        if (is_scalar($value) || $value === null) {
            $replace['{{contact.' . $key . '}}'] = (string)$value;
        }
    }

    foreach ($replace as $key => $value) {
        $content = str_replace($key, (string)$value, $content);
    }
    return $content;
}

function instrument_email_html(string $html, int $messageId): string
{
    $trackingPixel = '<img src="' . e(full_url('index.php?route=webhook/trackOpen&message_id=' . $messageId)) . '" width="1" height="1" alt="" style="display:none">';
    $html = preg_replace_callback('/href="([^"]+)"/i', function ($matches) use ($messageId) {
        $url = $matches[1];
        $tracked = full_url('index.php?route=webhook/trackClick&message_id=' . $messageId . '&url=' . urlencode($url));
        return 'href="' . e($tracked) . '"';
    }, $html);
    return $html . $trackingPixel;
}

function base32_chars(): string
{
    return 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567';
}

function generate_totp_secret(int $length = 32): string
{
    $chars = base32_chars();
    $secret = '';
    for ($i = 0; $i < $length; $i++) {
        $secret .= $chars[random_int(0, strlen($chars) - 1)];
    }
    return $secret;
}

function base32_decode_bytes(string $secret): string
{
    $secret = strtoupper(preg_replace('/[^A-Z2-7]/', '', $secret));
    $alphabet = array_flip(str_split(base32_chars()));
    $bits = '';
    foreach (str_split($secret) as $char) {
        $bits .= str_pad(decbin($alphabet[$char] ?? 0), 5, '0', STR_PAD_LEFT);
    }
    $bytes = '';
    foreach (str_split($bits, 8) as $chunk) {
        if (strlen($chunk) === 8) {
            $bytes .= chr(bindec($chunk));
        }
    }
    return $bytes;
}

function totp_code(string $secret, ?int $timeSlice = null): string
{
    $timeSlice = $timeSlice ?? (int)floor(time() / 30);
    $secretKey = base32_decode_bytes($secret);
    $binaryTime = pack('N*', 0) . pack('N*', $timeSlice);
    $hash = hash_hmac('sha1', $binaryTime, $secretKey, true);
    $offset = ord(substr($hash, -1)) & 0x0F;
    $truncated = substr($hash, $offset, 4);
    $value = unpack('N', $truncated)[1] & 0x7FFFFFFF;
    return str_pad((string)($value % 1000000), 6, '0', STR_PAD_LEFT);
}

function verify_totp_code(string $secret, string $code, int $window = 1): bool
{
    $code = preg_replace('/\D+/', '', $code);
    $slice = (int)floor(time() / 30);
    for ($i = -$window; $i <= $window; $i++) {
        if (hash_equals(totp_code($secret, $slice + $i), $code)) {
            return true;
        }
    }
    return false;
}

function otp_auth_uri(string $email, string $secret): string
{
    $issuer = rawurlencode((string)config('app.name', 'INS Communications'));
    $label = rawurlencode((string)config('app.name', 'INS Communications') . ':' . $email);
    return 'otpauth://totp/' . $label . '?secret=' . rawurlencode($secret) . '&issuer=' . $issuer . '&algorithm=SHA1&digits=6&period=30';
}

function provider_status_summary(): array
{
    $mail = effective_mail_config();
    $twilio = effective_twilio_config();
    return [
        'twilio' => ['configured' => !empty($twilio['sid']) && !empty($twilio['token']) && !empty($twilio['from_number']), 'from' => $twilio['from_number'] ?? ''],
        'smtp' => ['configured' => !empty($mail['from_email']) && (!empty($mail['smtp_host']) || !$mail['smtp_enabled']), 'from' => $mail['from_email'] ?? ''],
        'imap' => ['configured' => (bool)setting_value('imap_host', config('imap.host')) && (bool)setting_value('imap_username', config('imap.username')), 'mailbox' => setting_value('imap_mailbox', config('imap.mailbox', 'INBOX'))],
    ];
}

function quiet_hours_settings(): array
{
    return [
        'enabled' => (bool)setting_value('quiet_hours_enabled', 0),
        'start' => (string)setting_value('quiet_hours_start', '20:00'),
        'end' => (string)setting_value('quiet_hours_end', '08:00'),
        'timezone' => (string)setting_value('quiet_hours_timezone', config('app.timezone', 'America/Chicago')),
        'suppression_automation_enabled' => (bool)setting_value('suppression_automation_enabled', 1),
    ];
}


function quiet_hours_window(string $timezone): array
{
    $settings = quiet_hours_settings();
    $tz = new DateTimeZone($timezone ?: ($settings['timezone'] ?: config('app.timezone', 'America/Chicago')));
    $now = new DateTimeImmutable('now', $tz);
    [$sh, $sm] = array_pad(explode(':', $settings['start']), 2, '00');
    [$eh, $em] = array_pad(explode(':', $settings['end']), 2, '00');
    $start = $now->setTime((int)$sh, (int)$sm, 0);
    $end = $now->setTime((int)$eh, (int)$em, 0);
    $crosses = $start >= $end;
    return compact('settings', 'tz', 'now', 'start', 'end', 'crosses');
}

function is_within_quiet_hours(?string $timezone = null, ?int $timestamp = null): bool
{
    $settings = quiet_hours_settings();
    if (empty($settings['enabled'])) {
        return false;
    }
    $timezone = $timezone ?: ($settings['timezone'] ?: config('app.timezone', 'America/Chicago'));
    $tz = new DateTimeZone($timezone);
    $moment = $timestamp ? (new DateTimeImmutable('@' . $timestamp))->setTimezone($tz) : new DateTimeImmutable('now', $tz);
    [$sh, $sm] = array_pad(explode(':', $settings['start']), 2, '00');
    [$eh, $em] = array_pad(explode(':', $settings['end']), 2, '00');
    $start = $moment->setTime((int)$sh, (int)$sm, 0);
    $end = $moment->setTime((int)$eh, (int)$em, 0);
    if ($start < $end) {
        return $moment >= $start && $moment < $end;
    }
    return $moment >= $start || $moment < $end;
}

function next_allowed_send_at(?string $timezone = null, ?int $timestamp = null): string
{
    $settings = quiet_hours_settings();
    $timezone = $timezone ?: ($settings['timezone'] ?: config('app.timezone', 'America/Chicago'));
    $tz = new DateTimeZone($timezone);
    $moment = $timestamp ? (new DateTimeImmutable('@' . $timestamp))->setTimezone($tz) : new DateTimeImmutable('now', $tz);
    [$eh, $em] = array_pad(explode(':', $settings['end']), 2, '00');
    $candidate = $moment->setTime((int)$eh, (int)$em, 0);
    if (!is_within_quiet_hours($timezone, $moment->getTimestamp()) && $candidate <= $moment) {
        $candidate = $candidate->modify('+1 day');
    } elseif (is_within_quiet_hours($timezone, $moment->getTimestamp())) {
        $start = $moment->setTime((int)explode(':', $settings['start'])[0], (int)(explode(':', $settings['start'])[1] ?? 0), 0);
        $end = $moment->setTime((int)$eh, (int)$em, 0);
        if ($start < $end && $moment >= $end) {
            $candidate = $candidate->modify('+1 day');
        } elseif ($start >= $end && $moment >= $start) {
            $candidate = $candidate->modify('+1 day');
        }
    }
    return $candidate->setTimezone(new DateTimeZone(config('app.timezone', 'America/Chicago')))->format('Y-m-d H:i:s');
}

function quiet_hours_notice(?string $timezone = null): string
{
    $timezone = $timezone ?: quiet_hours_settings()['timezone'];
    return 'Sending is currently blocked by quiet hours. Next allowed send time: ' . next_allowed_send_at($timezone);
}
