<?php

namespace BitApps\PiPro\src\Integrations\GoogleDrive;

if (!\defined('ABSPATH')) {
    exit;
}

use BitApps\Pi\Helpers\MixInputHandler;
use BitApps\Pi\src\Authorization\AuthorizationFactory;
use BitApps\Pi\src\Authorization\AuthorizationType;
use BitApps\Pi\src\Flow\NodeInfoProvider;
use BitApps\PiPro\Deps\BitApps\WPKit\Helpers\JSON;

final class GoogleDriveService
{
    private const RESOURCE_FOLDER_DEFAULT = 'root';
    private const RESOURCE_DRIVE_DEFAULT = 'My Drive';
    private const DRIVE_FOLDER = 'application/vnd.google-apps.folder';

    private $http;

    private $baseUrl;

    /**
     * GoogleDriveService constructor.
     *
     * @param mixed $httpClient
     * @param mixed $baseUrl
     */
    public function __construct($httpClient, $baseUrl)
    {
        $this->http = $httpClient;
        $this->baseUrl = $baseUrl;
    }

    /**
     * Set parent folder and drive helper.
     *
     * @param string|null $folderId
     * @param string|null $driveId
     *
     * @return array{folderId: string|null, driveId: string|null}
     */
    private function setParentFolder($folderId, $driveId)
    {
        $result = [
            'folderId' => null,
            'driveId' => null,
        ];
        if ($folderId && $folderId !== self::RESOURCE_FOLDER_DEFAULT) {
            $result['folderId'] = $folderId;
        } else {
            $result['folderId'] = 'root';
        }
        if ($driveId && $driveId !== self::RESOURCE_DRIVE_DEFAULT) {
            $result['driveId'] = $driveId;
        }
        return $result;
    }

    /**
     * Upload file to Google Drive.
     *
     * @param NodeInfoProvider $nodeInfoProvider
     * @param string|null $accessToken
     *
     * @return array
     */
    public function uploadFile(NodeInfoProvider $nodeInfoProvider, $accessToken)
    {
        $fileUpload = MixInputHandler::replaceMixTagValue($nodeInfoProvider->getFieldMapConfigs('file-upload.value'));
        $fileName = MixInputHandler::replaceMixTagValue($nodeInfoProvider->getFieldMapConfigs('file-name.value'));
        $folderId = $nodeInfoProvider->getFieldMapConfigs('folder-id.value');
        $driveId = $nodeInfoProvider->getFieldMapConfigs('drive-id.value');

        if (!$fileUpload || empty($fileUpload)) {
            return [
                'response'    => ['error' => 'File path or URL is required'],
                'payload'     => ['file-upload' => $fileUpload, 'file-name' => $fileName, 'folder-id' => $folderId, 'drive-id' => $driveId],
                'status_code' => 400,
            ];
        }

        // Get file content
        $fileContent = null;
        $actualFileName = $fileName;

        if (filter_var($fileUpload, FILTER_VALIDATE_URL)) {
            // Handle URL
            $fileContent = @file_get_contents($fileUpload);
            if ($fileContent === false) {
                return [
                    'response'    => ['error' => 'Failed to download file from URL'],
                    'payload'     => ['file-upload' => $fileUpload, 'file-name' => $fileName, 'folder-id' => $folderId, 'drive-id' => $driveId],
                    'status_code' => 400,
                ];
            }
            // Use URL's filename if no custom name provided
            if (!$actualFileName) {
                $actualFileName = basename(parse_url($fileUpload, PHP_URL_PATH));
            }
        } elseif (file_exists($fileUpload)) {
            // Handle local file path
            $fileContent = @file_get_contents($fileUpload);
            if ($fileContent === false) {
                return [
                    'response'    => ['error' => 'Failed to read file from path'],
                    'payload'     => ['file-upload' => $fileUpload, 'file-name' => $fileName, 'folder-id' => $folderId, 'drive-id' => $driveId],
                    'status_code' => 400,
                ];
            }
            // Use file's basename if no custom name provided
            if (!$actualFileName) {
                $actualFileName = basename($fileUpload);
            }
        } else {
            return [
                'response'    => ['error' => 'File not found at the specified path'],
                'payload'     => ['file-upload' => $fileUpload, 'file-name' => $fileName, 'folder-id' => $folderId, 'drive-id' => $driveId],
                'status_code' => 400,
            ];
        }

        // Default filename if still not set
        if (!$actualFileName) {
            $actualFileName = 'untitled';
        }

        // Build multipart upload body
        $boundary = 'BITCODE_PI_' . md5(time());
        $parent = $this->setParentFolder($folderId, $driveId);

        $metadata = [
            'name' => $actualFileName,
            'parents' => [$parent],
        ];

        $body = '--' . $boundary . "\r\n";
        $body .= "Content-Type: application/json; charset=UTF-8\r\n\r\n";
        $body .= JSON::encode($metadata) . "\r\n";
        $body .= '--' . $boundary . "\r\n";
        $body .= "Content-Type: application/octet-stream\r\n\r\n";
        $body .= $fileContent . "\r\n";
        $body .= '--' . $boundary . "--\r\n";

        // Use multipart upload endpoint
        $endpoint = $this->baseUrl . '/upload/drive/v3/files?uploadType=multipart&supportsAllDrives=true';
        $response = $this->http->request(
            $endpoint,
            'POST',
            $body,
            ['Authorization' => $accessToken, 'Content-Type' => 'multipart/related; boundary="' . $boundary . '"']
        );
        return [
            'response'    => $response,
            'payload'     => ['file-upload' => $fileUpload, 'file-name' => $actualFileName, 'folder-id' => $folderId, 'drive-id' => $driveId],
            'status_code' => $this->http->getResponseCode(),
        ];
    }

    /**
     * Get file metadata from Google Drive.
     *
     * @param NodeInfoProvider $nodeInfoProvider
     *
     * @return array
     */
    public function getFile(NodeInfoProvider $nodeInfoProvider)
    {
        $fileId = $nodeInfoProvider->getFieldMapConfigs('file-id.value');
        $fields = MixInputHandler::replaceMixTagValue($nodeInfoProvider->getFieldMapConfigs('fields.value'));

        if (!$fileId) {
            return [
                'response'    => ['error' => 'File ID is required'],
                'payload'     => ['file-id' => $fileId, 'fields' => $fields],
                'status_code' => 400,
            ];
        }
        $params = [
            'supportsAllDrives' => 'true',
        ];

        $fields = $fields ?? '*';
        if (\is_array($fields)) {
            $fields = implode(',', $fields);
        }
        $params['fields'] = $fields;

        $endpoint = $this->baseUrl . '/drive/v3/files/' . urlencode($fileId);

        $endpoint .= '?' . http_build_query($params);

        $response = $this->http->request($endpoint, 'GET', []);
        return [
            'response'    => $response,
            'payload'     => ['file-id' => $fileId, 'fields' => $fields],
            'status_code' => $this->http->getResponseCode(),
        ];
    }

    /**
     * Download file from Google Drive.
     *
     * @param NodeInfoProvider $nodeInfoProvider
     *
     * @return array
     */
    public function downloadFile(NodeInfoProvider $nodeInfoProvider)
    {
        $fileId = $nodeInfoProvider->getFieldMapConfigs('file-id.value');
        $mimeType = MixInputHandler::replaceMixTagValue($nodeInfoProvider->getFieldMapConfigs('mime-type.value'));

        if (!$fileId) {
            return [
                'response'    => ['error' => 'File ID is required'],
                'payload'     => ['file-id' => $fileId, 'mime-type' => $mimeType],
                'status_code' => 400,
            ];
        }

        $endpoint = $this->baseUrl . '/drive/v3/files/' . urlencode($fileId);

        if ($mimeType && strpos($mimeType, 'vnd.google-apps') !== false) {
            $endpoint .= '/export?mimeType=' . urlencode($mimeType) . '&supportsAllDrives=true';
        } else {
            $endpoint .= '?alt=media&supportsAllDrives=true';
        }


        $response = $this->http->request($endpoint, 'GET', []);

        return [
            'response'    => $response,
            'payload'     => ['file-id' => $fileId, 'mime-type' => $mimeType],
            'status_code' => $this->http->getResponseCode(),
        ];
    }

    /**
     * Copy file in Google Drive.
     *
     * @param NodeInfoProvider $nodeInfoProvider
     *
     * @return array
     */
    public function copyFile(NodeInfoProvider $nodeInfoProvider)
    {
        $fileId = $nodeInfoProvider->getFieldMapConfigs('file-id.value');
        $newFileName = MixInputHandler::replaceMixTagValue($nodeInfoProvider->getFieldMapConfigs('file-name.value'));
        $sameFolder = $nodeInfoProvider->getFieldMapConfigs('same-folder.value');
        $folderId = $nodeInfoProvider->getFieldMapConfigs('folder-id.value');
        $driveId = $nodeInfoProvider->getFieldMapConfigs('drive-id.value');

        if (!$fileId) {
            return [
                'response'    => ['error' => 'File ID is required'],
                'payload'     => ['file-id' => $fileId, 'file-name' => $newFileName, 'same-folder' => $sameFolder, 'folder-id' => $folderId, 'drive-id' => $driveId],
                'status_code' => 400,
            ];
        }

        $sameFolder = $sameFolder ?? true;
        $body = [];
        if ($newFileName) {
            $body['name'] = $newFileName;
        }
        $parents = '';
        if ($sameFolder) {
            // Get original file's parents to keep copy in same location
            $getEndpoint = $this->baseUrl . '/drive/v3/files/' . urlencode($fileId) . '?fields=parents&supportsAllDrives=true';
            $fileResponse = $this->http->request($getEndpoint, 'GET', []);
            $parents = $fileResponse->parents[0] ?? [];
        } else {
            $parents = $folderId;
        }
        if (!empty($parents)) {
            $body['parents'] = $parents;
        }

        $endpoint = $this->baseUrl . '/drive/v3/files/' . urlencode($fileId) . '/copy?supportsAllDrives=true&includeItemsFromAllDrives=true';
        $response = $this->http->request($endpoint, 'POST', JSON::encode($body));



        return [
            'response'    => $response,
            'payload'     => ['file-id' => $fileId, 'file-name' => $newFileName, 'same-folder' => $sameFolder, 'folder-id' => $folderId, 'drive-id' => $driveId],
            'status_code' => $this->http->getResponseCode(),
        ];
    }

    /**
     * Delete file from Google Drive.
     *
     * @param NodeInfoProvider $nodeInfoProvider
     *
     * @return array
     */
    public function deleteFile(NodeInfoProvider $nodeInfoProvider)
    {
        $fileId = $nodeInfoProvider->getFieldMapConfigs('file-id.value');
        $deletePermanently = $nodeInfoProvider->getFieldMapConfigs('delete-permanently.value');

        if (!$fileId) {
            return [
                'response'    => ['error' => 'File ID is required'],
                'payload'     => ['file-id' => $fileId, 'delete-permanently' => $deletePermanently],
                'status_code' => 400,
            ];
        }

        $deletePermanently = $deletePermanently ?? false;

        $endpoint = $this->baseUrl . '/drive/v3/files/' . urlencode($fileId) . '?supportsAllDrives=true';

        if ($deletePermanently) {
            $response = $this->http->request($endpoint, 'DELETE', []);
        } else {
            $response = $this->http->request($endpoint, 'PATCH', JSON::encode(['trashed' => true]));
        }

        return [
            'response'    => $response ?: ['success' => true],
            'payload'     => ['file-id' => $fileId, 'delete-permanently' => $deletePermanently],
            'status_code' => $this->http->getResponseCode(),
        ];
    }

    /**
     * Move file in Google Drive.
     *
     * @param NodeInfoProvider $nodeInfoProvider
     *
     * @return array
     */
    public function moveFile(NodeInfoProvider $nodeInfoProvider)
    {
        $fileId = $nodeInfoProvider->getFieldMapConfigs('file-id.value');
        $folderId = $nodeInfoProvider->getFieldMapConfigs('folder-id.value');
        $driveId = $nodeInfoProvider->getFieldMapConfigs('drive-id.value');

        if (!$fileId) {
            return [
                'response'    => ['error' => 'File ID is required'],
                'payload'     => ['file-id' => $fileId, 'folder-id' => $folderId, 'drive-id' => $driveId],
                'status_code' => 400,
            ];
        }

        // First get current parents
        $getEndpoint = $this->baseUrl . '/drive/v3/files/' . urlencode($fileId) . '?fields=parents&supportsAllDrives=true';
        $fileResponse = $this->http->request($getEndpoint, 'GET', []);
        $currentParents = $fileResponse->parents[0] ?? [];

        $endpoint = $this->baseUrl . '/drive/v3/files/' . urlencode($fileId);
        $params = [
            'addParents' => $folderId,
            'supportsAllDrives' => 'true',
            'includeItemsFromAllDrives' => 'true',
        ];
        // Only include removeParents if there are current parents to remove
        if (!empty($currentParents)) {
            $params['removeParents'] = $currentParents;
        }
        $endpoint .= '?' . http_build_query($params);
        $response = $this->http->request($endpoint, 'PATCH', []);
        return [
            'response'    => $response,
            'payload'     => ['file-id' => $fileId, 'folder-id' => $folderId, 'drive-id' => $driveId],
            'status_code' => $this->http->getResponseCode(),
        ];
    }

    /**
     * Update file in Google Drive.
     *
     * @param NodeInfoProvider $nodeInfoProvider
     *
     * @return array
     */
    public function updateFile(NodeInfoProvider $nodeInfoProvider)
    {
        $fileId = $nodeInfoProvider->getFieldMapConfigs('file-id.value');
        $newFileName = MixInputHandler::replaceMixTagValue($nodeInfoProvider->getFieldMapConfigs('new-file-name.value'));

        if (!$fileId) {
            return [
                'response'    => ['error' => 'File ID is required'],
                'payload'     => ['file-id' => $fileId, 'new-file-name' => $newFileName],
                'status_code' => 400,
            ];
        }

        $body = [];
        if ($newFileName) {
            $body['name'] = $newFileName;
        }

        $endpoint = $this->baseUrl . '/drive/v3/files/' . urlencode($fileId) . '?supportsAllDrives=true';
        $response = $this->http->request($endpoint, 'PATCH', JSON::encode($body));

        return [
            'response'    => $response,
            'payload'     => ['file-id' => $fileId, 'new-file-name' => $newFileName],
            'status_code' => $this->http->getResponseCode(),
        ];
    }

    /**
     * Share file in Google Drive.
     *
     * @param NodeInfoProvider $nodeInfoProvider
     *
     * @return array
     */
    public function shareFile(NodeInfoProvider $nodeInfoProvider)
    {
        $fileId = $nodeInfoProvider->getFieldMapConfigs('file-id.value');
        $role = $nodeInfoProvider->getFieldMapConfigs('role.value');
        $type = $nodeInfoProvider->getFieldMapConfigs('type.value');
        $emailAddress = MixInputHandler::replaceMixTagValue($nodeInfoProvider->getFieldMapConfigs('email-address.value'));
        $domain = MixInputHandler::replaceMixTagValue($nodeInfoProvider->getFieldMapConfigs('domain.value'));
        $allowFileDiscovery = $nodeInfoProvider->getFieldMapConfigs('allow-file-discovery.value');
        $emailMessage = MixInputHandler::replaceMixTagValue($nodeInfoProvider->getFieldMapConfigs('email-message.value'));
        $sendNotificationEmail = $nodeInfoProvider->getFieldMapConfigs('send-notification-email.value');
        $useDomainAdminAccess = $nodeInfoProvider->getFieldMapConfigs('use-domain-admin-access.value');

        if (!$fileId) {
            return [
                'response'    => ['error' => 'File ID is required'],
                'payload'     => [
                    'file-id' => $fileId, 'role' => $role, 'type' => $type,
                    'email-address' => $emailAddress, 'domain' => $domain, 'allow-file-discovery' => $allowFileDiscovery,
                    'email-message' => $emailMessage, 'send-notification-email' => $sendNotificationEmail,
                    'supports-all-drives' => 'true', 'use-domain-admin-access' => $useDomainAdminAccess,
                ],
                'status_code' => 400,
            ];
        }

        $body = [];
        $qs = ['supportsAllDrives' => 'true'];

        // Build permissions from field map data
        if ($role) {
            $body['role'] = $role;
        }
        if ($type) {
            $body['type'] = $type;
        }
        if ($emailAddress && !empty($emailAddress) && $type !== 'anyone' && $type !== 'domain') {
            $body['emailAddress'] = $emailAddress;
        }
        if ($domain && !empty($domain)) {
            $body['domain'] = $domain;
        }
        if (isset($allowFileDiscovery) && empty($emailAddress)) {
            $body['allowFileDiscovery'] = (bool) $allowFileDiscovery;
        }

        // Share options
        if ($emailMessage && !empty($emailMessage)) {
            $qs['emailMessage'] = $emailMessage;
        }
        if (isset($sendNotificationEmail) && !empty($sendNotificationEmail)) {
            $qs['sendNotificationEmail'] = (bool) $sendNotificationEmail;
        }
        if (isset($useDomainAdminAccess) && !empty($useDomainAdminAccess)) {
            $qs['useDomainAdminAccess'] = (bool) $useDomainAdminAccess;
        }

        $endpoint = $this->baseUrl . '/drive/v3/files/' . urlencode($fileId) . '/permissions';
        if (!empty($qs)) {
            $endpoint .= '?' . http_build_query($qs);
        }

        $response = $this->http->request($endpoint, 'POST', JSON::encode($body));
        return [
            'response'    => $response,
            'payload'     => $body,
            'status_code' => $this->http->getResponseCode(),
        ];
    }

    /**
     * Create file from text in Google Drive.
     *
     * @param NodeInfoProvider $nodeInfoProvider
     *
     * @return array
     */
    public function createFileFromText(NodeInfoProvider $nodeInfoProvider)
    {
        $content = MixInputHandler::replaceMixTagValue($nodeInfoProvider->getFieldMapConfigs('content.value'));
        $fileName = MixInputHandler::replaceMixTagValue($nodeInfoProvider->getFieldMapConfigs('file-name.value'));
        $folderId = $nodeInfoProvider->getFieldMapConfigs('folder-id.value');
        $driveId = $nodeInfoProvider->getFieldMapConfigs('drive-id.value');
        $convertToGoogleDoc = $nodeInfoProvider->getFieldMapConfigs('convert-to-google-doc.value');

        $content = $content ?? '';
        $fileName = $fileName ?? 'Untitled';
        $convertToGoogleDoc = $convertToGoogleDoc ?? false;

        $parent = $this->setParentFolder($folderId, $driveId);

        // If converting to Google Doc, create file first then update content via Docs API
        if ($convertToGoogleDoc) {
            $metadata = [
                'name' => $fileName,
                'mimeType' => 'application/vnd.google-apps.document',
            ];

            if ($parent && $parent !== 'root') {
                $metadata['parents'] = [$parent];
            }

            $endpoint = $this->baseUrl . '/drive/v3/files?supportsAllDrives=true&includeItemsFromAllDrives=true';
            $response = $this->http->request($endpoint, 'POST', JSON::encode($metadata));

            // Update content via Docs API if content exists
            if (isset($response['id']) && !empty($content)) {
                $docsEndpoint = 'https://docs.googleapis.com/v1/documents/' . $response['id'] . ':batchUpdate';
                $docsBody = [
                    'requests' => [
                        [
                            'insertText' => [
                                'text' => $content,
                                'endOfSegmentLocation' => [
                                    'segmentId' => '',
                                ],
                            ],
                        ],
                    ],
                ];
                $this->http->request($docsEndpoint, 'POST', JSON::encode($docsBody));
            }

            return [
                'response'    => $response,
                'payload'     => ['content' => $content, 'file-name' => $fileName, 'folder-id' => $folderId, 'drive-id' => $driveId, 'convert-to-google-doc' => $convertToGoogleDoc],
                'status_code' => $this->http->getResponseCode(),
            ];
        }

        // For plain text files, use multipart upload if content exists
        if (!empty($content)) {
            $boundary = 'BITCODE_PI_' . md5(time());

            $metadata = [
                'name' => $fileName,
                'mimeType' => 'text/plain',
            ];

            if ($parent) {
                $metadata['parents'] = [$parent];
            }

            $body = '--' . $boundary . "\r\n";
            $body .= "Content-Type: application/json; charset=UTF-8\r\n\r\n";
            $body .= JSON::encode($metadata) . "\r\n";
            $body .= '--' . $boundary . "\r\n";
            $body .= "Content-Type: text/plain\r\n\r\n";
            $body .= $content . "\r\n";
            $body .= '--' . $boundary . "--\r\n";

            $endpoint = $this->baseUrl . '/upload/drive/v3/files?uploadType=multipart&supportsAllDrives=true';
            $response = $this->http->request(
                $endpoint,
                'POST',
                $body,
                ['Content-Type' => 'multipart/related; boundary="' . $boundary . '"']
            );

            return [
                'response'    => $response,
                'payload'     => ['content' => $content, 'file-name' => $fileName, 'folder-id' => $folderId, 'drive-id' => $driveId, 'convert-to-google-doc' => $convertToGoogleDoc],
                'status_code' => $this->http->getResponseCode(),
            ];
        }

        // If no content, create empty text file
        $metadata = [
            'name' => $fileName,
            'mimeType' => 'text/plain',
        ];

        if ($parent && $parent !== 'root') {
            $metadata['parents'] = [$parent];
        }

        $endpoint = $this->baseUrl . '/drive/v3/files?supportsAllDrives=true&includeItemsFromAllDrives=true';
        $response = $this->http->request($endpoint, 'POST', JSON::encode($metadata));

        return [
            'response'    => $response,
            'payload'     => ['content' => $content, 'file-name' => $fileName, 'folder-id' => $folderId, 'drive-id' => $driveId, 'convert-to-google-doc' => $convertToGoogleDoc],
            'status_code' => $this->http->getResponseCode(),
        ];
    }

    /**
     * Create folder in Google Drive.
     *
     * @param NodeInfoProvider $nodeInfoProvider
     *
     * @return array
     */
    public function createFolder(NodeInfoProvider $nodeInfoProvider)
    {
        $folderName = MixInputHandler::replaceMixTagValue($nodeInfoProvider->getFieldMapConfigs('folder-name.value'));
        $parentFolderId = $nodeInfoProvider->getFieldMapConfigs('parent-folder-id.value');
        $driveId = $nodeInfoProvider->getFieldMapConfigs('drive-id.value');

        $folderName = $folderName ?? 'Untitled';

        $body = [
            'name' => $folderName,
            'mimeType' => self::DRIVE_FOLDER,
        ];

        $parent = $this->setParentFolder($parentFolderId, $driveId);
        if ($parent && $parent !== 'root') {
            $body['parents'] = [$parent];
        }

        $endpoint = $this->baseUrl . '/drive/v3/files?supportsAllDrives=true&includeItemsFromAllDrives=true';
        $response = $this->http->request($endpoint, 'POST', JSON::encode($body));

        return [
            'response'    => $response,
            'payload'     => ['folder-name' => $folderName, 'parent-folder-id' => $parentFolderId, 'drive-id' => $driveId],
            'status_code' => $this->http->getResponseCode(),
        ];
    }

    /**
     * Delete folder from Google Drive.
     *
     * @param NodeInfoProvider $nodeInfoProvider
     *
     * @return array
     */
    public function deleteFolder(NodeInfoProvider $nodeInfoProvider)
    {
        $folderId = $nodeInfoProvider->getFieldMapConfigs('folder-id.value');
        $deletePermanently = $nodeInfoProvider->getFieldMapConfigs('delete-permanently.value');

        if (!$folderId) {
            return [
                'response'    => ['error' => 'Folder ID is required'],
                'payload'     => ['folder-id' => $folderId, 'delete-permanently' => $deletePermanently],
                'status_code' => 400,
            ];
        }

        $deletePermanently = $deletePermanently ?? false;

        $endpoint = $this->baseUrl . '/drive/v3/files/' . urlencode($folderId) . '?supportsAllDrives=true';

        if ($deletePermanently) {
            $response = $this->http->request($endpoint, 'DELETE', []);
        } else {
            $response = $this->http->request($endpoint, 'PATCH', JSON::encode(['trashed' => true]));
        }

        return [
            'response'    => $response ?: ['success' => true],
            'payload'     => ['folder-id' => $folderId, 'delete-permanently' => $deletePermanently],
            'status_code' => $this->http->getResponseCode(),
        ];
    }

    /**
     * Share folder in Google Drive.
     *
     * @param NodeInfoProvider $nodeInfoProvider
     *
     * @return array
     */
    public function shareFolder(NodeInfoProvider $nodeInfoProvider)
    {
        $folderId = $nodeInfoProvider->getFieldMapConfigs('folder-id.value');
        $role = $nodeInfoProvider->getFieldMapConfigs('role.value');
        $type = $nodeInfoProvider->getFieldMapConfigs('type.value');
        $emailAddress = MixInputHandler::replaceMixTagValue($nodeInfoProvider->getFieldMapConfigs('email-address.value'));
        $domain = MixInputHandler::replaceMixTagValue($nodeInfoProvider->getFieldMapConfigs('domain.value'));
        $allowFileDiscovery = $nodeInfoProvider->getFieldMapConfigs('allow-file-discovery.value');
        $emailMessage = MixInputHandler::replaceMixTagValue($nodeInfoProvider->getFieldMapConfigs('email-message.value'));
        $sendNotificationEmail = $nodeInfoProvider->getFieldMapConfigs('send-notification-email.value');
        $useDomainAdminAccess = $nodeInfoProvider->getFieldMapConfigs('use-domain-admin-access.value');

        if (!$folderId) {
            return [
                'response'    => ['error' => 'Folder ID is required'],
                'payload'     => [
                    'folder-id' => $folderId, 'role' => $role, 'type' => $type, 'email-address' => $emailAddress,
                    'domain' => $domain, 'allow-file-discovery' => $allowFileDiscovery, 'email-message' => $emailMessage,
                    'send-notification-email' => $sendNotificationEmail, 'use-domain-admin-access' => $useDomainAdminAccess
                ],
                'status_code' => 400,
            ];
        }

        $body = [];
        $qs = ['supportsAllDrives' => 'true'];

        // Build permissions from field map data
        if ($role) {
            $body['role'] = $role;
        }
        if ($type) {
            $body['type'] = $type;
        }
        if ($emailAddress && !empty($emailAddress) && $type !== 'anyone' && $type !== 'domain') {
            $body['emailAddress'] = $emailAddress;
        }
        if ($domain && !empty($domain)) {
            $body['domain'] = $domain;
        }
        if (isset($allowFileDiscovery) && !empty($allowFileDiscovery) && empty($emailAddress)) {
            $body['allowFileDiscovery'] = (bool) $allowFileDiscovery;
        }

        // Share options
        if ($emailMessage && !empty($emailMessage)) {
            $qs['emailMessage'] = $emailMessage;
        }
        if (isset($sendNotificationEmail) && !empty($sendNotificationEmail)) {
            $qs['sendNotificationEmail'] = (bool) $sendNotificationEmail;
        }
        if (isset($useDomainAdminAccess) && !empty($useDomainAdminAccess)) {
            $qs['useDomainAdminAccess'] = (bool) $useDomainAdminAccess;
        }
        $endpoint = $this->baseUrl . '/drive/v3/files/' . urlencode($folderId) . '/permissions';
        if (!empty($qs)) {
            $endpoint .= '?' . http_build_query($qs);
        }
        $response = $this->http->request($endpoint, 'POST', JSON::encode($body));

        return [
            'response'    => $response,
            'payload'     => $body,
            'status_code' => $this->http->getResponseCode(),
        ];
    }

    /**
     * Create shared drive in Google Drive.
     *
     * @param NodeInfoProvider $nodeInfoProvider
     *
     * @return array
     */
    public function createDrive(NodeInfoProvider $nodeInfoProvider)
    {
        $driveName = MixInputHandler::replaceMixTagValue($nodeInfoProvider->getFieldMapConfigs('drive-name.value'));

        if (!$driveName) {
            return [
                'response'    => ['error' => 'Drive name is required'],
                'payload'     => ['drive-name' => $driveName],
                'status_code' => 400,
            ];
        }

        $requestId = uniqid('drive_', true);

        $body = [
            'name' => $driveName,
        ];

        $endpoint = $this->baseUrl . '/drive/v3/drives?requestId=' . urlencode($requestId);
        $response = $this->http->request($endpoint, 'POST', JSON::encode($body));
        return [
            'response'    => $response,
            'payload'     => ['drive-name' => $driveName],
            'status_code' => $this->http->getResponseCode(),
        ];
    }

    /**
     * Get shared drive from Google Drive.
     *
     * @param NodeInfoProvider $nodeInfoProvider
     *
     * @return array
     */
    public function getDrive(NodeInfoProvider $nodeInfoProvider)
    {
        $driveId = $nodeInfoProvider->getFieldMapConfigs('drive-id.value');

        if (!$driveId) {
            return [
                'response'    => ['error' => 'Drive ID is required'],
                'payload'     => ['drive-id' => $driveId],
                'status_code' => 400,
            ];
        }

        $endpoint = $this->baseUrl . '/drive/v3/drives/' . urlencode($driveId);
        $response = $this->http->request($endpoint, 'GET', []);

        return [
            'response'    => $response,
            'payload'     => ['drive-id' => $driveId],
            'status_code' => $this->http->getResponseCode(),
        ];
    }

    /**
     * List shared drives in Google Drive.
     *
     * @param NodeInfoProvider $nodeInfoProvider
     *
     * @return array
     */
    public function listDrives(NodeInfoProvider $nodeInfoProvider)
    {
        $query = MixInputHandler::replaceMixTagValue($nodeInfoProvider->getFieldMapConfigs('query.value'));
        $returnAll = $nodeInfoProvider->getFieldMapConfigs('return-all.value');
        $limit = MixInputHandler::replaceMixTagValue($nodeInfoProvider->getFieldMapConfigs('limit.value'));

        $returnAll = $returnAll ?? false;
        $limit = $limit ?? 100;

        $endpoint = $this->baseUrl . '/drive/v3/drives';
        $allDrives = [];
        $nextPageToken = null;
        $totalCollected = 0;
        $finalStatusCode = 200;

        do {
            $params = [];

            if ($query) {
                $params['q'] = $query;
            }

            // Set pageSize for pagination
            if ($returnAll) {
                // When returning all, use a reasonable page size (max 100 per Google API)
                $params['pageSize'] = 100;
            } else {
                // When not returning all, calculate remaining items needed
                $remaining = $limit - $totalCollected;
                if ($remaining > 0) {
                    $params['pageSize'] = min($remaining, 100);
                }
            }

            // Add pageToken if we have one from previous iteration
            if ($nextPageToken) {
                $params['pageToken'] = $nextPageToken;
            }

            $requestEndpoint = $endpoint;
            if (!empty($params)) {
                $requestEndpoint .= '?' . http_build_query($params);
            }

            $pageResponse = $this->http->request($requestEndpoint, 'GET', []);
            $finalStatusCode = $this->http->getResponseCode();

            // Check for errors
            if ($finalStatusCode !== 200 || isset($pageResponse['error'])) {
                return [
                    'response'    => $pageResponse,
                    'payload'     => ['query' => $query, 'return-all' => $returnAll, 'limit' => $limit],
                    'status_code' => $finalStatusCode,
                ];
            }

            // Extract drives from this page
            if (isset($pageResponse['drives']) && \is_array($pageResponse['drives'])) {
                $allDrives = array_merge($allDrives, $pageResponse['drives']);
                $totalCollected = \count($allDrives);
            }

            // Get nextPageToken for next iteration
            $nextPageToken = $pageResponse['nextPageToken'] ?? null;

            // Stop if we've reached the limit (when returnAll is false)
            if (!$returnAll && $totalCollected >= $limit) {
                // Trim to exact limit if we exceeded it
                if ($totalCollected > $limit) {
                    $allDrives = \array_slice($allDrives, 0, $limit);
                }
                break;
            }
        } while ($nextPageToken);

        // Set response to accumulated drives
        $response = $allDrives;

        return [
            'response'    => $response,
            'payload'     => ['query' => $query, 'return-all' => $returnAll, 'limit' => $limit],
            'status_code' => $finalStatusCode,
        ];
    }

    /**
     * Update shared drive in Google Drive.
     *
     * @param NodeInfoProvider $nodeInfoProvider
     *
     * @return array
     */
    public function updateDrive(NodeInfoProvider $nodeInfoProvider)
    {
        $driveId = $nodeInfoProvider->getFieldMapConfigs('drive-id.value');
        $name = MixInputHandler::replaceMixTagValue($nodeInfoProvider->getFieldMapConfigs('name.value'));

        if (!$driveId) {
            return [
                'response'    => ['error' => 'Drive ID is required'],
                'payload'     => ['drive-id' => $driveId, 'name' => $name],
                'status_code' => 400,
            ];
        }

        $body = [];
        if ($name && !empty($name)) {
            $body['name'] = $name;
        }

        $endpoint = $this->baseUrl . '/drive/v3/drives/' . urlencode($driveId);
        $response = $this->http->request($endpoint, 'PATCH', JSON::encode($body));

        return [
            'response'    => $response,
            'payload'     => ['drive-id' => $driveId, 'name' => $name],
            'status_code' => $this->http->getResponseCode(),
        ];
    }

    /**
     * Delete shared drive from Google Drive.
     *
     * @param NodeInfoProvider $nodeInfoProvider
     *
     * @return array
     */
    public function deleteDrive(NodeInfoProvider $nodeInfoProvider)
    {
        $driveId = $nodeInfoProvider->getFieldMapConfigs('drive-id.value');

        if (!$driveId) {
            return [
                'response'    => ['error' => 'Drive ID is required'],
                'payload'     => ['drive-id' => $driveId],
                'status_code' => 400,
            ];
        }

        $endpoint = $this->baseUrl . '/drive/v3/drives/' . urlencode($driveId);
        $response = $this->http->request($endpoint, 'DELETE', []);

        return [
            'response'    => $response ?: ['success' => true],
            'payload'     => ['drive-id' => $driveId],
            'status_code' => $this->http->getResponseCode(),
        ];
    }

    /**
     * Search files and folders in Google Drive.
     *
     * @param NodeInfoProvider $nodeInfoProvider
     *
     * @return array
     */
    public function searchFilesFolders(NodeInfoProvider $nodeInfoProvider)
    {
        $searchMethod = $nodeInfoProvider->getFieldMapConfigs('search-method.value');
        $searchQuery = MixInputHandler::replaceMixTagValue($nodeInfoProvider->getFieldMapConfigs('search-query.value'));
        $queryString = MixInputHandler::replaceMixTagValue($nodeInfoProvider->getFieldMapConfigs('query-string.value'));
        $returnAll = $nodeInfoProvider->getFieldMapConfigs('return-all.value');
        $limit = MixInputHandler::replaceMixTagValue($nodeInfoProvider->getFieldMapConfigs('limit.value'));
        $filterDriveId = $nodeInfoProvider->getFieldMapConfigs('drive-id.value');
        $filterFolderId = $nodeInfoProvider->getFieldMapConfigs('folder-id.value');
        $includeTrashed = $nodeInfoProvider->getFieldMapConfigs('include-trashed.value');

        $searchMethod = $searchMethod ?? 'name';
        $searchQuery = $searchQuery ?? '';
        $queryString = $queryString ?? '';
        $returnAll = $returnAll ?? false;
        $limit = $limit ?? 1000;

        $query = [];
        if ($searchMethod === 'name') {
            if (!empty($searchQuery)) {
                $query[] = "name contains '" . str_replace("'", "\\'", $searchQuery) . "'";
            }
        } else {
            if (!empty($queryString)) {
                $query[] = $queryString;
            }
        }

        if ($filterFolderId && $filterFolderId !== self::RESOURCE_FOLDER_DEFAULT) {
            $query[] = "'" . str_replace("'", "\\'", $filterFolderId) . "' in parents";
        }

        if (!isset($includeTrashed) || !$includeTrashed) {
            $query[] = 'trashed = false';
        }

        $q = !empty($query) ? implode(' and ', $query) : 'trashed = false';

        $endpoint = $this->baseUrl . '/drive/v3/files';
        $params = [
            'q' => $q,
            'supportsAllDrives' => 'true',
            'includeItemsFromAllDrives' => 'true',
            'spaces' => 'appDataFolder,drive',
            'corpora' => 'allDrives',
        ];

        if ($filterDriveId && $filterDriveId !== self::RESOURCE_DRIVE_DEFAULT) {
            $params['driveId'] = $filterDriveId;
            $params['corpora'] = 'drive';
        } elseif (!$filterDriveId && $filterFolderId === self::RESOURCE_FOLDER_DEFAULT) {
            $params['corpora'] = 'user';
            $params['spaces'] = 'drive';
            $params['includeItemsFromAllDrives'] = 'false';
            $params['supportsAllDrives'] = 'false';
        }

        if (!$returnAll) {
            $params['pageSize'] = $limit;
        }

        $endpoint .= '?' . http_build_query($params);

        $response = $this->http->request($endpoint, 'GET', []);

        // Extract files array if present
        if (isset($response['files'])) {
            $response = $response['files'];
        }

        return [
            'response'    => $response,
            'payload'     => [
                'search-method' => $searchMethod, 'search-query' => $searchQuery,
                'query-string' => $queryString, 'return-all' => $returnAll, 'limit' => $limit,
                'drive-id' => $filterDriveId, 'folder-id' => $filterFolderId,
                'include-trashed' => $includeTrashed,
            ],
            'status_code' => $this->http->getResponseCode(),
        ];
    }
}
