<?php

declare(strict_types=1);

/*
'########:'##::::'##::'######::'##::::'##::'######:::'#######::'########::'########:
... ##..:: ##:::: ##:'##... ##: ##:::: ##:'##... ##:'##.... ##: ##.... ##: ##.....::
::: ##:::: ##:::: ##: ##:::..:: ##:::: ##: ##:::..:: ##:::: ##: ##:::: ##: ##:::::::
::: ##:::: ##:::: ##:. ######:: #########: ##::::::: ##:::: ##: ##:::: ##: ######:::
::: ##:::: ##:::: ##::..... ##: ##.... ##: ##::::::: ##:::: ##: ##:::: ##: ##...::::
::: ##:::: ##:::: ##:'##::: ##: ##:::: ##: ##::: ##: ##:::: ##: ##:::: ##: ##:::::::
::: ##::::. #######::. ######:: ##:::: ##:. ######::. #######:: ########:: ########:
:::..::::::.......::::......:::..:::::..:::......::::.......:::........:::........::                                                                                   
*/

/**
 * Return true if the request method is POST
 *
 * @return boolean
 */
function is_post_request(): bool
{
    return strtoupper($_SERVER['REQUEST_METHOD']) === 'POST';
}

/**
 * Return true if the request method is GET
 *
 * @return boolean
 */
function is_get_request(): bool
{
    return strtoupper($_SERVER['REQUEST_METHOD']) === 'GET';
}

function currentURL()
{
    return (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] === 'on' ? "https" : "http") . "://$_SERVER[HTTP_HOST]$_SERVER[REQUEST_URI]";
}

function baseurl($url)
{
    $parsed_url = parse_url($url);
    return $parsed_url['scheme'] . '://' . $parsed_url['host'];
}

// Function to update environment variables in the .env file
function updateEnvVariable($key, $value)
{
    $envFilePath = APPROOT . '/config/.env';

    // Read the current content of the .env file
    $currentEnvContent = file_get_contents($envFilePath);

    // Replace the existing value or add a new line if the variable doesn't exist
    $updatedEnvContent = preg_replace("/$key=[^\r\n]*/", "$key=$value", $currentEnvContent, 1);

    // If the variable doesn't exist, add it to the end of the file
    if ($updatedEnvContent === $currentEnvContent) {
        $updatedEnvContent .= "$key=$value\n";
    }

    // Write the updated content back to the .env file
    file_put_contents($envFilePath, $updatedEnvContent);
}


function removeExtension(string $filename): string
{
    return pathinfo($filename, PATHINFO_FILENAME);
}

function gen_uuid()
{
    return strtoupper(
        sprintf(
            '%04x%04x-%04x-%04x-%04x-%04x%04x%04x',
            // 32 bits for "time_low"
            mt_rand(0, 0xffff),
            mt_rand(0, 0xffff),

            // 16 bits for "time_mid"
            mt_rand(0, 0xffff),

            // 16 bits for "time_hi_and_version",
            // four most significant bits holds version number 4
            mt_rand(0, 0x0fff) | 0x4000,

            // 16 bits, 8 bits for "clk_seq_hi_res",
            // 8 bits for "clk_seq_low",
            // two most significant bits holds zero and one for variant DCE1.1
            mt_rand(0, 0x3fff) | 0x8000,

            // 48 bits for "node"
            mt_rand(0, 0xffff),
            mt_rand(0, 0xffff),
            mt_rand(0, 0xffff)
        )
    );
}

function isValidLatitude($latitude)
{
    $latitude = (float) $latitude;
    return is_numeric($latitude) && $latitude >= -90 && $latitude <= 90;
}

function isValidLongitude($longitude)
{
    $longitude = (float) $longitude;
    return is_numeric($longitude) && $longitude >= -180 && $longitude <= 180;
}


/**
 * Checks if the given latitude and longitude values are valid.
 *
 * @param float $latitude The latitude value to be checked.
 * @param float $longitude The longitude value to be checked.
 * @return bool Returns true if the latitude and longitude values are valid, otherwise false.
 */
function isValidLatLong($latitude, $longitude)
{
    $latitude = (float) $latitude;
    $longitude = (float) $longitude;

    if (!empty($latitude) && !empty($longitude) && $latitude >= -90 && $latitude <= 90 && $longitude >= -180 && $longitude <= 180) {
        return true; // Valid latitude and longitude values.
    }

    return false; // Invalid latitude and longitude values.
}

function calculateDistance($lat1, $lon1, $lat2, $lon2, $unit) {
	$lat1 = (float) $lat1;
	$lon1 = (float) $lon1;
	$lat2 = (float) $lat2;
	$lon2 = (float) $lon2;

	$theta = $lon1 - $lon2;
	$dist = sin(deg2rad($lat1)) * sin(deg2rad($lat2)) + cos(deg2rad($lat1)) * cos(deg2rad($lat2)) * cos(deg2rad($theta));
	$dist = acos($dist);
	$dist = rad2deg($dist);
	$miles = $dist * 60 * 1.1515;
	$unit = strtoupper($unit);

	switch ($unit) {
		case "K":
			return round($miles * 1.609344, 2);
		case "N":
			return round($miles * 0.8684, 2);
		default:
			return round($miles, 2);
	}
}

function sanitize_post($data = null) {
    $postData = $data ?? $_POST;
    foreach ($postData as $key => $value) {
        $postData[$key] = filter_var($value, FILTER_SANITIZE_FULL_SPECIAL_CHARS);
    }
    return $postData;
}

// function sanitize_post() {
//     return filter_input_array(INPUT_POST, FILTER_SANITIZE_FULL_SPECIAL_CHARS);
// }

function generateTimeLoop($startTime, $endTime, $format = 'select', $defaultTime = null) {
    $start_time = strtotime($startTime);
    $end_time = strtotime($endTime);

    // Check if the end time is earlier than the start time (e.g., '12:00' to '00:00')
    if ($end_time < $start_time) {
        $end_time += 86400; // Add 24 hours in seconds
    }

    $interval = 900; // 15 minutes in seconds
    $times = array();

    while ($start_time <= $end_time) {
        $formatted_time = date('H:i', $start_time);
        $times[] = $formatted_time;

        $start_time += $interval;
    }

    if ($format === 'select') {
        // Generate select options
        $options = '';
        foreach ($times as $time) {
            $selected = ($defaultTime !== null && $defaultTime === $time) ? 'selected' : '';
            $options .= "<option value='$time' $selected>$time</option>";
        }
        return $options;
    } elseif ($format === 'ul') {
        // Generate an unordered list
        $list = '';
        foreach ($times as $time) {
            $list .= "<li>$time</li>";
        }
        return $list;
    } elseif ($format === 'array') {
        // Return the times as an array
        return $times;
    } else {
        return null; // Invalid format
    }
}

/**
 * Retrieves the user's timezone.
 *
 * This function first checks if the user has explicitly set their timezone
 * in their user preferences. If not, it tries to detect the timezone using
 * the server's settings. If the timezone is still not set, it attempts to
 * detect it by using the user's IP address.
 *
 * @throws Exception if the timezone cannot be determined.
 * @return string The user's timezone.
 */
function getUserTimezone() {

    // If not, try to detect the timezone using the server's settings
    $timezone = date_default_timezone_get();

    // If the timezone is still not set, try to detect it using the user's IP address
    if (!$timezone) {
        $ip = $_SERVER['REMOTE_ADDR'];
        $json = file_get_contents('http://smart-ip.net/geoip-json/' . $ip);
        $ipData = json_decode($json, true);
        $timezone = $ipData['timezone'];
    }

    return $timezone;
}

/**
 * Retrieves client data from an API based on the provided IP address.
 *
 * @param string $ip_address The IP address for which client data is to be retrieved.
 * @throws Exception If there is an error retrieving the client data.
 * @return array The decoded client data.
 */
function getClientData($ip_address) {
    // API endpoint
    $api_url = "http://ip-api.com/json/{$ip_address}";

    // Initialize cURL session
    $ch = curl_init($api_url);

    // Set cURL options
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);

    // Execute cURL session and get the response
    $response = curl_exec($ch);

    // Close cURL session
    curl_close($ch);

    // Decode JSON response
    $data = json_decode($response, true);

    // Return the decoded data
    return $data;
}

function getTimezoneWithLatLong($latitudeTo, $longitudeTo, $earthRadius = 6371000, $threshold = 100000)
{
  $timezones = timezone_identifiers_list();
  $getUserTimeZone = '';

  foreach ($timezones as $timezone) {
    $location = timezone_location_get(new DateTimeZone($timezone));
    //debug_code($location);
    
    
    // convert from degrees to radians
    $latFrom = deg2rad($location['latitude']);
    $lonFrom = deg2rad($location['longitude']);
  
    $latTo = deg2rad($latitudeTo);
    $lonTo = deg2rad($longitudeTo);
    
    $latDelta = $latTo - $latFrom;
    $lonDelta = $lonTo - $lonFrom;
    
    $angle = 2 * asin(sqrt(pow(sin($latDelta / 2), 2) +
    cos($latFrom) * cos($latTo) * pow(sin($lonDelta / 2), 2)));
    $distance = $angle * $earthRadius;
    
    if ($distance <= $threshold) {
        $getUserTimeZone = $timezone;
        // break;
    }
    
  }
  
  return $getUserTimeZone;
}

/**
 * Calculates the time left until a specified closing datetime.
 *
 * @param string $closingDatetime The closing datetime.
 * @param string $interval The time interval to use for calculation. Defaults to '1 Hour'.
 * @return string The time left until the closing datetime, in the format 'X hours and Y minutes', or 'Closed' if the closing time is in the past.
 */
function getTimeLeft(string $closingDatetime, $interval = '1 Hour'): string {
    $now = new DateTime();
    $closingTime = new DateTime($closingDatetime);

    // Calculate the time difference
    $interval = $now->diff($closingTime);

    $hoursLeft = $interval->h + $interval->days * 24;
    $minutesLeft = $interval->i;

    // Generate the result string
    $result = '';

    if ($hoursLeft > 0) {
        $result .= $hoursLeft . ' hour' . ($hoursLeft > 1 ? 's' : '');

        if ($minutesLeft > 0) {
            $result .= ' and ';
        }
    }

    if ($minutesLeft > 0) {
        $result .= $minutesLeft . ' minute' . ($minutesLeft > 1 ? 's' : '');
    }

    // Handle the case when the closing time is in the past
    if ($now > $closingTime) {
        $result = 'Closed';
    }

    return $result;
}

/**
 * Checks if a specified time falls within a given interval.
 *
 * @param string $intervalTime The interval time in the format "X days", "X hours", "X minutes", etc.
 * @param string $specifiedTime The specified time in the format "HH:MM".
 * @throws Exception If the interval format is invalid.
 * @return bool Returns true if the specified time falls within the interval, false otherwise.
 */
function isWithinInterval($intervalTime, $specifiedTime) {
    $interval = date_interval_create_from_date_string($intervalTime);
    
    if (!$interval) {
        throw new Exception("Invalid interval format");
    }

    $now = new DateTime();
    $endTime = clone $now;
    $endTime->add($interval);

    $specifiedDateTime = DateTime::createFromFormat('H:i', $specifiedTime);

    return $specifiedDateTime >= $now && $specifiedDateTime <= $endTime;
}


/**
 * Finds the nearest closed day based on the current day of the week.
 *
 * @param array $closedDays An array of closed days.
 * @return string The nearest closed day.
 */
function findNearestDay(array $closedDays) {
    $today = date('N'); // Get the current day of the week (1 for Monday, 7 for Sunday)

    $minDistance = INF; // Initialize the minimum distance to infinity
    $nearestClosedDay = null; // Initialize the nearest closed day

    foreach ($closedDays as $closedDay) {
        $closedDay = date('N', strtotime($closedDay));
        if ($closedDay >= $today) {
            $distance = $closedDay - $today; // Calculate the distance between the current day and the closed day

            if ($distance < $minDistance) {
                $minDistance = $distance;
                $nearestClosedDay = $closedDay;
            }
        }
    }

    return date('l', strtotime($nearestClosedDay));
}

function getUserTimezoneByLatLong($latitude, $longitude, $googleMapsKey){
    $timestamp = time();
    $url = "https://maps.googleapis.com/maps/api/timezone/json?location={$latitude},{$longitude}&timestamp=$timestamp&key={$googleMapsKey}";

    $json = file_get_contents($url);
    $data = json_decode($json, true);
    $timezoneId = $data['timeZoneId'];
    $timezoneName = $data['timeZoneName'];
    return ['timezone' => $timezoneId, 'timezoneName' => $timezoneName];
}

function generateInvoiceNumber($input, $padLength = 7, $prefix = null)
{
    if ($padLength <= strlen($input)) {
        trigger_error('<strong>$padLength</strong> cannot be less than or equal to the length of <strong>$input</strong> to generate invoice number', E_USER_ERROR);
    }
    if (is_string($prefix)) {
        return sprintf("%s%s", $prefix, str_pad($input, $padLength, "0", STR_PAD_LEFT));
    }
    return str_pad($input, $padLength, "0", STR_PAD_LEFT);
}

function clearCache()
{
    header("Expires: Tue, 01 Jan 2000 00:00:00 GMT");
    header("Last-Modified: " . gmdate("D, d M Y H:i:s") . " GMT");
    header("Cache-Control: no-store, no-cache, must-revalidate, max-age=0");
    header("Cache-Control: post-check=0, pre-check=0", false);
    header("Pragma: no-cache");
}

function downloadFile(string $file, ?string $name = null): void
{
    $mime = 'application/force-download';
    header('Pragma: public');
    header('Expires: 0');
    header('Cache-Control: must-revalidate, post-check=0, pre-check=0');
    header('Cache-Control: private', false);
    header('Content-Type: '.$mime);

    // Changed the order of the headers to make it more readable. 
    header('Content-Disposition: attachment; filename="'.($name ?: basename($file)).'"'); 
    header('Content-Transfer-Encoding: binary'); 
    header('Connection: close'); 

    readfile($file); 

    // Added exit statement to ensure that the script stops executing after the file is downloaded. 
    exit;  
}

function minifyHTML(string $html): string
{
    $search = [
        '/\s+/', # Replace all whitespaces with a single space
        '/<!--.*?-->/', # Remove HTML comments
    ];

    $replace = [' ', '',];

    return preg_replace($search, $replace, $html);
}

function formatCurrency($amount, $currencySymbol = '₦', $position = 'left') {
    $formattedAmount = $amount; // Format the amount with two decimal places

    if ($position === 'left') {
        $formattedCurrency = $currencySymbol . $formattedAmount; // Add the symbol to the left
    } else {
        $formattedCurrency = $formattedAmount . $currencySymbol; // Add the symbol to the right
    }

    return $formattedCurrency;
}


function getAvatar(string $targetFolder, string $name, int $size = 150, int $length = 8): string
{
    $bgColor = dechex(rand(0, 10000000));
    $avatarUrl = 'https://ui-avatars.com/api/?name=' . $name . '&background=' . $bgColor . '&color=fff&size=' . $size . '&length=' . $length . '&rounded=false&bold=false&uppercase=true';

    // Get the avatar content from the URL and generate a unique filename for it. 
    $avatarContent = file_get_contents($avatarUrl);
    $avatarFilename = uniqid((string) mt_rand(100000, 99999999)) . ".png";

    // Save the avatar content to the target folder with its unique filename. 
    file_put_contents($targetFolder . '/' . $avatarFilename, $avatarContent);

    // Return the unique filename of the avatar. 
    return $avatarFilename;
}

function sanitize($input) {
    if (is_array($input)) {
        foreach ($input as $var => $val) {
            $output[$var] = sanitize($val);
        }
    } else {
        $search = [
            '@<script[^>]*?>.*?</script>@si', // Strip out javascript
            '@<[\/\!]*?[^<>]*?>@si', // Strip out HTML tags
            '@<style[^>]*?>.*?</style>@siU', // Strip style tags properly
            '@<![\s\S]*?--[ \t\n\r]*>@' // Strip multi-line comments
        ];

        $input = preg_replace($search, '', $input);
        $input = str_replace('"', "", $input);
        $input = str_replace("'", "", $input);
        $output = htmlentities($input, ENT_QUOTES);
    }

    return @$output;
}


function hexToRgb(string $hexColor): string {
    // Remove any "#" characters from the start of the string
    $hexColor = ltrim($hexColor, '#');
  
    // Split the hex color code into its red, green, and blue components
    $red = hexdec(substr($hexColor, 0, 2));
    $green = hexdec(substr($hexColor, 2, 2));
    $blue = hexdec(substr($hexColor, 4, 2));

    // Return the RGB color as a string
    return "{$red}, {$green}, {$blue}";
}



function debug_code($code)
{
    echo "<pre>";
    var_dump($code);
    echo "</pre>";
}

function extractUrl(){
    $url = trim($_GET['url'], '/');
    $url = filter_var($url, FILTER_SANITIZE_URL);
    $url = explode('/', $url);
    return $url;
}

function generate_breadcrumb(array $pages)
{
    $breadcrumb = '';
    $num_pages = count($pages);
    
    // Loop through each page and add it to breadcrumb\n    
    foreach ($pages as $index => $page) {
        if ($index < $num_pages - 1) {
            $breadcrumb .= '<a class="d-none d-md-block" href="' . $page['url'] . '">' . $page['title'] . '</a><i data-feather="chevron-right" class="breadcrumb__icon d-none d-md-block"></i>';
        } else {
            $breadcrumb .= '<a href="' . $page['url'] . '" class="breadcrumb--active">' . $page['title'] . '</a>';
        }
    }
    
    return $breadcrumb;
}

function copyright($year = false)
{
    if ($year == false) {
        $year = date('Y');
    }
    if (intval($year) == date('Y')) {
        return intval($year);
    }
    if (intval($year) < date('Y')) {
        return intval($year) . ' - ' . date('Y');
    }
    if (intval($year) > date('Y')) {
        return date('Y');
    }
}

function paginate_range($total_page, $current_page)
{
    $array_output = [];

    if ($total_page <= 1) {
        return $array_output; // No need to paginate if there's only one page.
    }

    // Always include the first page.
    $array_output[] = 1;

    // Calculate the start and end points for the pagination.
    $start = max(2, $current_page - 1);
    $end = min($total_page - 1, $start + 2);

    // Add an initial separator if necessary.
    if ($start > 2) {
        $array_output[] = 0;
    }

    // Page subgroup: pages from start to end.
    for ($i = $start; $i <= $end; $i++) {
        $array_output[] = $i;
    }

    // Add a final separator if necessary.
    if ($end < $total_page - 1) {
        $array_output[] = 0;
    }

    // Always include the last page.
    $array_output[] = $total_page;

    return $array_output;
}

function removeQueryParams(string $url, ...$paramsToRemove) {
    // Parse the URL into its components
    $parsedUrl = parse_url($url);

    // If the URL doesn't have a query string, just return the original URL
    if (!isset($parsedUrl['query'])) {
        return $url;
    }

    // Parse the query string into an associative array
    parse_str($parsedUrl['query'], $queryParams);

    // Remove the specified parameters from the array
    foreach ($paramsToRemove as $param) {
        unset($queryParams[$param]);
    }

    // Rebuild the query string
    $newQueryString = http_build_query($queryParams);

    // Reassemble the URL with the modified query string
    $newUrl = $parsedUrl['scheme'] . '://' . $parsedUrl['host'] . $parsedUrl['path'];
    if (!empty($newQueryString)) {
        $newUrl .= '?' . $newQueryString;
    }

    // Add back the fragment, if it exists
    if (isset($parsedUrl['fragment'])) {
        $newUrl .= '#' . $parsedUrl['fragment'];
    }

    return $newUrl;
}


function removeComma($text)
{
    $text = str_replace(",", "", $text);
    return $text;
}

function removeHyphen($text)
{
    $text = str_replace("-", " ", $text);
    return $text;
}