<?php

declare(strict_types=1);

/**
 * Professional doctor-photo processing.
 *
 * Browser crops are preferred because the user can position the portrait. The
 * server validates every generated image and can create safe centered crops
 * with GD when JavaScript output is unavailable.
 */

function doctor_photo_variant_schema_ready(): bool
{
    $table = table('dentist_profiles');
    return schema_column_exists($table, 'photo_original_path')
        && schema_column_exists($table, 'photo_square_path')
        && schema_column_exists($table, 'photo_portrait_path')
        && schema_column_exists($table, 'photo_thumbnail_path');
}

function doctor_photo_relative_path(int $dentistUserId, string $fileName): string
{
    return 'dentists/' . $dentistUserId . '/' . $fileName;
}

function doctor_photo_absolute_path(string $relativePath): string
{
    return DENTAL_ROOT . '/storage/private/' . ltrim($relativePath, '/');
}

function doctor_photo_write_jpeg_data_url(
    int $dentistUserId,
    string $field,
    string $prefix,
    int $expectedWidth,
    int $expectedHeight,
    int $maxBytes,
    bool $allowVariableDimensions = false
): ?array {
    $value = trim((string) ($_POST[$field] ?? ''));
    if ($value === '') return null;
    if (!preg_match('#^data:image/jpeg;base64,([A-Za-z0-9+/=\r\n]+)$#', $value, $matches)) {
        throw new RuntimeException('The professional photo crop data is invalid. Select the photo again and review both crop formats.');
    }
    $binary = base64_decode(str_replace(["\r", "\n"], '', $matches[1]), true);
    if ($binary === false || $binary === '' || strlen($binary) > $maxBytes) {
        throw new RuntimeException('One of the generated doctor-photo files is empty or too large. Reduce the source image size and try again.');
    }
    $imageInfo = @getimagesizefromstring($binary);
    if (!$imageInfo || ($imageInfo['mime'] ?? '') !== 'image/jpeg') {
        throw new RuntimeException('The generated doctor photo is not a valid JPEG image.');
    }
    $width = (int) ($imageInfo[0] ?? 0);
    $height = (int) ($imageInfo[1] ?? 0);
    if ($allowVariableDimensions) {
        if ($width < 800 || $height < 800 || $width > 2600 || $height > 2600) {
            throw new RuntimeException('The normalized original doctor photo has invalid dimensions.');
        }
    } elseif ($width !== $expectedWidth || $height !== $expectedHeight) {
        throw new RuntimeException(sprintf('The %s crop must be exactly %d × %d pixels.', str_replace('_', ' ', $prefix), $expectedWidth, $expectedHeight));
    }
    $fileName = $prefix . '_' . date('Ymd_His') . '_' . bin2hex(random_bytes(5)) . '.jpg';
    $relative = doctor_photo_relative_path($dentistUserId, $fileName);
    $target = doctor_photo_absolute_path($relative);
    if (file_put_contents($target, $binary, LOCK_EX) === false) {
        throw new RuntimeException('Unable to store the protected doctor-photo crop. Check storage/private permissions.');
    }
    @chmod($target, 0640);
    return ['path' => $relative, 'width' => $width, 'height' => $height, 'size' => strlen($binary)];
}

function doctor_photo_load_gd_image(string $path, string $mime)
{
    if (!extension_loaded('gd')) {
        throw new RuntimeException('The server GD image extension is unavailable and the browser crop data was not received. Enable GD or select the photo again in a modern browser.');
    }
    $image = match ($mime) {
        'image/jpeg' => @imagecreatefromjpeg($path),
        'image/png' => @imagecreatefrompng($path),
        'image/webp' => function_exists('imagecreatefromwebp') ? @imagecreatefromwebp($path) : false,
        default => false,
    };
    if (!$image) throw new RuntimeException('The selected doctor photograph could not be decoded.');

    if ($mime === 'image/jpeg' && function_exists('exif_read_data')) {
        $exif = @exif_read_data($path);
        $orientation = (int) ($exif['Orientation'] ?? 1);
        if ($orientation === 3) $image = imagerotate($image, 180, 0);
        if ($orientation === 6) $image = imagerotate($image, -90, 0);
        if ($orientation === 8) $image = imagerotate($image, 90, 0);
        if (in_array($orientation, [2,4,5,7], true) && function_exists('imageflip')) {
            imageflip($image, IMG_FLIP_HORIZONTAL);
        }
    }
    return $image;
}

function doctor_photo_save_gd_jpeg($source, int $dentistUserId, string $prefix, int $targetWidth, int $targetHeight, float $verticalBias = 0.18): array
{
    $sourceWidth = imagesx($source);
    $sourceHeight = imagesy($source);
    $targetRatio = $targetWidth / $targetHeight;
    $sourceRatio = $sourceWidth / max(1, $sourceHeight);
    if ($sourceRatio > $targetRatio) {
        $cropHeight = $sourceHeight;
        $cropWidth = (int) round($cropHeight * $targetRatio);
        $sourceX = (int) round(($sourceWidth - $cropWidth) / 2);
        $sourceY = 0;
    } else {
        $cropWidth = $sourceWidth;
        $cropHeight = (int) round($cropWidth / $targetRatio);
        $sourceX = 0;
        $excess = max(0, $sourceHeight - $cropHeight);
        $sourceY = (int) round($excess * max(0.0, min(1.0, $verticalBias)));
    }
    $canvas = imagecreatetruecolor($targetWidth, $targetHeight);
    $white = imagecolorallocate($canvas, 255, 255, 255);
    imagefill($canvas, 0, 0, $white);
    imagecopyresampled($canvas, $source, 0, 0, $sourceX, $sourceY, $targetWidth, $targetHeight, $cropWidth, $cropHeight);
    $fileName = $prefix . '_' . date('Ymd_His') . '_' . bin2hex(random_bytes(5)) . '.jpg';
    $relative = doctor_photo_relative_path($dentistUserId, $fileName);
    $target = doctor_photo_absolute_path($relative);
    if (!imagejpeg($canvas, $target, 90)) {
        imagedestroy($canvas);
        throw new RuntimeException('Unable to create the professional doctor-photo crop.');
    }
    imagedestroy($canvas);
    @chmod($target, 0640);
    return ['path' => $relative, 'width' => $targetWidth, 'height' => $targetHeight, 'size' => filesize($target) ?: 0];
}

function doctor_photo_save_gd_original($source, int $dentistUserId): array
{
    $sourceWidth = imagesx($source);
    $sourceHeight = imagesy($source);
    $scale = min(1.0, 2000 / max($sourceWidth, $sourceHeight));
    $width = max(800, (int) round($sourceWidth * $scale));
    $height = max(800, (int) round($sourceHeight * $scale));
    $canvas = imagecreatetruecolor($width, $height);
    $white = imagecolorallocate($canvas, 255, 255, 255);
    imagefill($canvas, 0, 0, $white);
    imagecopyresampled($canvas, $source, 0, 0, 0, 0, $width, $height, $sourceWidth, $sourceHeight);
    $fileName = 'photo_original_' . date('Ymd_His') . '_' . bin2hex(random_bytes(5)) . '.jpg';
    $relative = doctor_photo_relative_path($dentistUserId, $fileName);
    $target = doctor_photo_absolute_path($relative);
    if (!imagejpeg($canvas, $target, 92)) {
        imagedestroy($canvas);
        throw new RuntimeException('Unable to create the privacy-safe original doctor photograph.');
    }
    imagedestroy($canvas);
    @chmod($target, 0640);
    return ['path' => $relative, 'width' => $width, 'height' => $height, 'size' => filesize($target) ?: 0];
}


function doctor_photo_store_validated_original_upload(string $tmpPath, string $extension, int $dentistUserId, int $width, int $height): array
{
    $extension = $extension === 'jpeg' ? 'jpg' : $extension;
    $fileName = 'photo_original_' . date('Ymd_His') . '_' . bin2hex(random_bytes(5)) . '.' . $extension;
    $relative = doctor_photo_relative_path($dentistUserId, $fileName);
    $target = doctor_photo_absolute_path($relative);
    if (!copy($tmpPath, $target)) {
        throw new RuntimeException('Unable to retain the protected original doctor photograph.');
    }
    @chmod($target, 0640);
    return ['path' => $relative, 'width' => $width, 'height' => $height, 'size' => filesize($target) ?: 0];
}

function doctor_photo_process_upload(int $dentistUserId, string $field = 'photo_file'): ?array
{
    if (!isset($_FILES[$field]) || (int) ($_FILES[$field]['error'] ?? UPLOAD_ERR_NO_FILE) === UPLOAD_ERR_NO_FILE) return null;
    if (!doctor_photo_variant_schema_ready()) {
        throw new RuntimeException('Apply Version 0.7.9.1 before uploading a professional doctor photograph.');
    }
    $file = $_FILES[$field];
    private_dentist_directory($dentistUserId);
    if ((int) $file['error'] !== UPLOAD_ERR_OK) throw new RuntimeException('The selected doctor photograph could not be uploaded.');
    if ((int) $file['size'] <= 0 || (int) $file['size'] > 5 * 1024 * 1024) throw new RuntimeException('The doctor photograph must be no larger than 5 MB.');
    $extension = strtolower(pathinfo(basename((string) $file['name']), PATHINFO_EXTENSION));
    if (!in_array($extension, ['jpg','jpeg','png','webp'], true)) throw new RuntimeException('Use a JPG, PNG, or WebP doctor photograph.');
    $finfo = new finfo(FILEINFO_MIME_TYPE);
    $mime = (string) $finfo->file((string) $file['tmp_name']);
    $allowedMimes = ['image/jpeg','image/png','image/webp'];
    if (!in_array($mime, $allowedMimes, true)) throw new RuntimeException('The doctor photograph content does not match an allowed image format.');
    $sourceInfo = @getimagesize((string) $file['tmp_name']);
    if (!$sourceInfo) throw new RuntimeException('The selected doctor photograph is corrupted or unreadable.');
    $sourceWidth = (int) $sourceInfo[0];
    $sourceHeight = (int) $sourceInfo[1];
    if ($sourceWidth < 800 || $sourceHeight < 800) {
        throw new RuntimeException('Use a doctor photograph at least 800 × 800 pixels so both the 2×2 and half-body formats remain clear.');
    }

    $createdPaths = [];
    try {
        $original = doctor_photo_write_jpeg_data_url($dentistUserId, 'photo_original_data', 'photo_original', 0, 0, 5 * 1024 * 1024, true);
        $square = doctor_photo_write_jpeg_data_url($dentistUserId, 'photo_square_data', 'photo_square', 600, 600, 2 * 1024 * 1024);
        $portrait = doctor_photo_write_jpeg_data_url($dentistUserId, 'photo_portrait_data', 'photo_portrait', 800, 1000, 3 * 1024 * 1024);
        $thumbnail = doctor_photo_write_jpeg_data_url($dentistUserId, 'photo_thumbnail_data', 'photo_thumbnail', 240, 240, 512 * 1024);
        foreach ([$original,$square,$portrait,$thumbnail] as $item) if ($item) $createdPaths[] = $item['path'];

        if (!$original || !$square || !$portrait || !$thumbnail) {
            if (extension_loaded('gd')) {
                $source = doctor_photo_load_gd_image((string) $file['tmp_name'], $mime);
                $original = $original ?: doctor_photo_save_gd_original($source, $dentistUserId);
                $square = $square ?: doctor_photo_save_gd_jpeg($source, $dentistUserId, 'photo_square', 600, 600, 0.14);
                $portrait = $portrait ?: doctor_photo_save_gd_jpeg($source, $dentistUserId, 'photo_portrait', 800, 1000, 0.10);
                $thumbnail = $thumbnail ?: doctor_photo_save_gd_jpeg($source, $dentistUserId, 'photo_thumbnail', 240, 240, 0.14);
                imagedestroy($source);
            } else {
                $original = $original ?: doctor_photo_store_validated_original_upload((string)$file['tmp_name'], $extension, $dentistUserId, $sourceWidth, $sourceHeight);
                if (!$square || !$portrait || !$thumbnail) {
                    throw new RuntimeException('The browser crop was not received and the server GD extension is unavailable. Reopen the photo editor and apply both professional crops.');
                }
            }
            foreach ([$original,$square,$portrait,$thumbnail] as $item) if ($item && !in_array($item['path'], $createdPaths, true)) $createdPaths[] = $item['path'];
        }

        return [
            'original_path' => $original['path'],
            'square_path' => $square['path'],
            'portrait_path' => $portrait['path'],
            'thumbnail_path' => $thumbnail['path'],
            'source_width' => $sourceWidth,
            'source_height' => $sourceHeight,
        ];
    } catch (Throwable $exception) {
        foreach ($createdPaths as $path) {
            $full = doctor_photo_absolute_path($path);
            if (is_file($full)) @unlink($full);
        }
        throw $exception;
    }
}

function doctor_photo_delete_replaced_files(int $dentistUserId, array $oldPaths, array $newPaths): void
{
    $newPaths = array_values(array_filter(array_unique(array_map('strval', $newPaths))));
    $expectedPrefix = 'dentists/' . $dentistUserId . '/';
    foreach (array_values(array_filter(array_unique(array_map('strval', $oldPaths)))) as $path) {
        if ($path === '' || in_array($path, $newPaths, true) || !str_starts_with($path, $expectedPrefix)) continue;
        $full = doctor_photo_absolute_path($path);
        if (is_file($full)) @unlink($full);
    }
}

function doctor_photo_variant_column(string $variant): string
{
    return match ($variant) {
        'original' => 'photo_original_path',
        'portrait' => 'photo_portrait_path',
        'thumbnail' => 'photo_thumbnail_path',
        'square' => 'photo_square_path',
        default => 'photo_path',
    };
}

function doctor_photo_profile_path(array $profile, string $variant = 'square'): ?string
{
    $variant = in_array($variant, ['original','square','portrait','thumbnail','legacy'], true) ? $variant : 'square';
    if ($variant !== 'legacy' && doctor_photo_variant_schema_ready()) {
        $column = doctor_photo_variant_column($variant);
        $path = trim((string)($profile[$column] ?? ''));
        if ($path !== '') return $path;
    }
    $legacy = trim((string)($profile['photo_path'] ?? ''));
    return $legacy !== '' ? $legacy : null;
}

function doctor_photo_paths_from_profile(array $profile): array
{
    $keys = ['photo_path','photo_original_path','photo_square_path','photo_portrait_path','photo_thumbnail_path'];
    $paths = [];
    foreach ($keys as $key) {
        $path = trim((string)($profile[$key] ?? ''));
        if ($path !== '') $paths[] = $path;
    }
    return array_values(array_unique($paths));
}
