<?php

/**
 * @package JPageBuilder
 * @author Joomla! Extensions Store
 * @copyright (C) 2024 - Joomla! Extensions Store
 * @license http://www.gnu.org/licenses/gpl-2.0.html GNU/GPLv2 or later
 */
use Joomla\CMS\Component\ComponentHelper;
use Joomla\CMS\Factory;
use Joomla\Filesystem\File;
use Joomla\Filesystem\Folder;
use Joomla\CMS\Http\Http;

// No direct access
defined ( '_JEXEC' ) or die ( 'Restricted access' );

/**
 * Google fonts trait
 *
 * @since 5.0.0
 */
trait JPageBuilderFrameworkFonts {
	private function getInstalledFont(string $familyName, string $fontType) {
		$db = Factory::getContainer()->get('DatabaseDriver');
		$query = method_exists ( $db, 'createQuery' ) ? $db->createQuery () : $db->getQuery ( true );

		$query->select ( [ 
				'id',
				'data'
		] )->from ( $db->quoteName ( '#__jpagebuilder_fonts' ) )->where ( $db->quoteName ( 'family_name' ) . ' = ' . $db->quote ( $familyName ) )->where ( $db->quoteName ( 'type' ) . ' = ' . $db->quote ( $fontType ) );

		$db->setQuery ( $query );

		try {
			$font = $db->loadObject ();

			if (! empty ( $font->data )) {
				$font->data = \json_decode ( $font->data );
			}

			return $font;
		} catch ( \Exception $e ) {
			return null;
		}
	}
	private function isInstalledFont(string $familyName, string $fontType) {
		return ! empty ( $this->getInstalledFont ( $familyName, $fontType ) );
	}
	private function getFonts() {
		$installed = $this->getInput ( 'installed', 0, 'int' );

		if ($installed) {
			$this->loadInstalledFonts ();
		} else {
			$this->loadGoogleFonts ();
		}
	}
	private function loadInstalledFonts() {
		$limit = $this->getInput ( 'limit', 10, 'int' );
		$offset = $this->getInput ( 'offset', 0, 'int' );
		$search = $this->getInput ( 'search', '', 'string' );
		$type = $this->getInput ( 'type', '', 'string' );

		$db = Factory::getContainer()->get('DatabaseDriver');
		$query = method_exists ( $db, 'createQuery' ) ? $db->createQuery () : $db->getQuery ( true );

		$query->select ( '*' )->from ( $db->quoteName ( '#__jpagebuilder_fonts' ) );

		if (! empty ( $search )) {
			$search = preg_replace ( "@\s+@", ' ', $search );
			$search = explode ( ' ', $search );
			$search = array_filter ( $search, function ($word) {
				return ! empty ( $word );
			} );

			$search = implode ( '|', $search );

			$query->where ( $db->quoteName ( 'family_name' ) . ' REGEXP ' . $db->quote ( $search ) );
		}

		if (! empty ( $type )) {
			$query->where ( $db->quoteName ( 'type' ) . ' = ' . $db->quote ( $type ) );
		}

		$fullQuery = method_exists ( $db, 'createQuery' ) ? $db->createQuery () : $db->getQuery ( true );
		$fullQuery = $query->__toString ();

		$results = [ ];

		if (! empty ( $limit )) {
			$query->setLimit ( $limit, $offset );
		}

		$db->setQuery ( $query );

		try {
			$results = $db->loadObjectList ();
		} catch ( \Exception $e ) {
			$this->sendResponse ( [ 
					'message' => $e->getMessage ()
			], 500 );
		}

		$db->setQuery ( $fullQuery );
		$db->execute ();
		$allItems = $db->getNumRows ();

		if (! empty ( $results )) {
			foreach ( $results as &$result ) {
				$result->data = ! empty ( $result->data ) ? \json_decode ( $result->data ) : null;

				if ($result->type === 'google') {
					$installedFont = $this->getInstalledFont ( $result->family_name, 'google' );
					$result->installed_variants = $installedFont->data->variants;
				}
			}

			unset ( $result );
		}

		$response = ( object ) [ 
				'totalItems' => $allItems,
				'totalPages' => ceil ( $allItems / $limit ),
				'results' => $results
		];

		$this->sendResponse ( $response );
	}
	private function loadGoogleFonts() {
		$limit = $this->getInput ( 'limit', 10, 'int' );
		$offset = $this->getInput ( 'offset', 0, 'int' );
		$search = $this->getInput ( 'search', '', 'string' );
		$category = $this->getInput ( 'category', '', 'string' );

		$cParams = ComponentHelper::getParams ( 'com_jpagebuilder' );
		$http = new Http ();
		$response = [ 
				'message' => ''
		];

		$API_KEY = $cParams->get ( 'google_font_api_key', 'AIzaSyDksiuBas8gVxA8_nxLUw69sq3FmJQR1wU' );

		if (empty ( $API_KEY )) {
			$response ['message'] = 'Invalid api key.';
			$this->sendResponse ( $response, 403 );
		}

		$endpoint = 'https://www.googleapis.com/webfonts/v1/webfonts?key=' . trim ( $API_KEY );

		$cachePath = JPATH_CACHE . '/jpagebuilder';
		$cacheFile = $cachePath . '/google-web-fonts.json';

		if (! is_dir ( $cachePath )) {
			Folder::create ( $cachePath, 0755 );
		}

		$fonts = [ ];

		if (file_exists ( $cacheFile ) && (filemtime ( $cacheFile ) > (time () - (24 * 60 * 60)))) {
			$fonts = \file_get_contents ( $cacheFile );
		} else {
			try {
				$apiResponse = $http->get ( $endpoint );
			} catch ( \Exception $e ) {
				$this->sendResponse ( [ 
						'message' => $e->getMessage ()
				], 500 );
			}

			if ($apiResponse->code === 400) {
				$responseBody = \is_string ( $apiResponse->body ) ? \json_decode ( $apiResponse->body ) : $apiResponse->body;
				$response ['message'] = $responseBody->error->message;
				$this->sendResponse ( $response, 400 );
			}

			if ($apiResponse->code >= 400) {
				$responseBody = \is_string ( $apiResponse->body ) ? \json_decode ( $apiResponse->body ) : $apiResponse->body;
				$response ['message'] = $responseBody->error->message;
				$this->sendResponse ( $response, $apiResponse->code );
			}

			$fonts = $apiResponse->body;

			if (! empty ( $fonts )) {
				if (! File::write ( $cacheFile, $fonts )) {
					$response ['message'] = 'Error caching the font data.';
					$this->sendResponse ( $response, 500 );
				}
			}
		}

		if (! empty ( $fonts )) {
			$fonts = \json_decode ( $fonts );

			if (\json_last_error () > 0) {
				$response ['message'] = 'Error parsing font data.';
				$this->sendResponse ( $response, 500 );
			}

			$fonts = ! empty ( $fonts->items ) ? $fonts->items : [ ];

			if (! empty ( $search )) {
				$search = preg_replace ( "@\s+@", ' ', $search );
				$search = explode ( ' ', $search );
				$search = array_filter ( $search, function ($word) {
					return ! empty ( $word );
				} );

				$search = implode ( '|', $search );

				$fonts = array_filter ( $fonts, function ($font) use ($search) {
					return preg_match ( "@$search@i", $font->family );
				} );

				$fonts = array_values ( $fonts );
			}

			if (! empty ( $category )) {
				$fonts = array_filter ( $fonts, function ($font) use ($category) {
					return $font->category === $category;
				} );

				$fonts = array_values ( $fonts );
			}

			$resultChunk = array_slice ( $fonts, $offset, $limit );

			foreach ( $resultChunk as &$item ) {
				unset ( $item->version, $item->lastModified, $item->files, $item->kind, $item->menu );
				$item->is_installed = $this->isInstalledFont ( $item->family, 'google' );
				$item->installed_variants = [ ];

				if ($item->is_installed) {
					$installedFont = $this->getInstalledFont ( $item->family, 'google' );
					$item->installed_variants = $installedFont->data->variants;
				}
			}

			unset ( $item );

			$totalFonts = count ( $fonts );
			$response = ( object ) [ 
					'totalItems' => $totalFonts,
					'totalPages' => ceil ( $totalFonts / $limit ),
					'results' => $resultChunk
			];

			$this->sendResponse ( $response );
		}

		$response ['message'] = 'Fonts not found.';
		$this->sendResponse ( $response, 404 );
	}
	private function installFont() {
		$familyName = $this->getInput ( 'family_name', '', 'string' );
		$type = $this->getInput ( 'type', 'google', 'string' );
		$fontData = $this->getInput ( 'data', '', 'RAW' );
		$response = [ 
				'message' => ''
		];

		if (empty ( $familyName ) || empty ( $fontData )) {
			$this->sendResponse ( [ 
					'message' => 'Missing request data.'
			], 400 );
		}

		$cParams = ComponentHelper::getParams ( 'com_jpagebuilder' );
		$API_KEY = $cParams->get ( 'google_font_api_key', 'AIzaSyDksiuBas8gVxA8_nxLUw69sq3FmJQR1wU' );

		if (empty ( $API_KEY )) {
			$response ['message'] = 'Invalid API KEY';
			$this->sendResponse ( $response, 403 );
		}

		if ($type === 'google') {
			$this->installGoogleFont ( $fontData );
		}
	}
	private function saveFontData(string $familyName, string $type, $fontData) {
		$data = ( object ) [ 
				'id' => null,
				'family_name' => $familyName,
				'data' => $fontData,
				'type' => $type,
				'created_by' => Factory::getApplication()->getIdentity()->id,
				'created' => Factory::getDate ()->toSql (),
				'published' => 1
		];

		$db = Factory::getContainer()->get('DatabaseDriver');

		try {
			$installedFont = $this->getInstalledFont ( $familyName, $type );

			if (! empty ( $installedFont->id )) {
				$data->id = $installedFont->id;
				$db->updateObject ( '#__jpagebuilder_fonts', $data, 'id', true );
				$response = ( object ) [ 
						'id' => $data->id
				];

				$this->sendResponse ( $response, 200 );
			} else {
				$db->insertObject ( '#__jpagebuilder_fonts', $data, 'id' );
				$response = ( object ) [ 
						'id' => $data->id
				];

				$this->sendResponse ( $response, 201 );
			}
		} catch ( \Exception $e ) {
			$this->sendResponse ( [ 
					'message' => $e->getMessage ()
			], 500 );
		}
	}
	private function hasNumber($str) {
		return preg_match ( '/\d/', $str );
	}
	private function extractWeightAndStyle($str) {
		preg_match ( '/\d+/', $str, $weightMatches );
		$weight = $weightMatches [0] ?? '';

		return [ 
				'weight' => $weight ? ( int ) $weight : 400,
				'style' => preg_match ( '/[a-zA-Z]+/', $str, $styleMatches ) ? $styleMatches [0] : ''
		];
	}
	private function generateFamily($data) {
		$family = $data->family;
		$variants = $data->variants;

		$variantString = implode ( '', $variants );
		$hasItalic = strpos ( $variantString, 'italic' ) !== false;
		$hasWeight = $this->hasNumber ( $variantString );

		$individualStyles = array_map ( function ($variant) use ($hasWeight, $hasItalic) {
			$parsedData = $this->extractWeightAndStyle ( $variant );
			$parsedWeight = $parsedData ['weight'];
			$style = $parsedData ['style'];
			$weight = $parsedWeight;

			if ($hasWeight && ($style === 'regular' || $style === 'italic') && ! $weight) {
				$weight = 400;
			}

			if ($hasItalic) {
				if ($hasWeight) {
					return ($style === 'italic' ? 1 : 0) . ',' . $weight;
				}
				return $style === 'italic' ? 1 : 0;
			}

			return ( string ) $weight;
		}, $variants );

		sort ( $individualStyles );
		$individualStylesString = implode ( ';', $individualStyles );

		$linkFamily = str_replace ( ' ', '+', $family ) . ($hasItalic ? ':ital' : '') . ($hasItalic && $hasWeight ? ',' : '') . (! $hasItalic && $hasWeight ? ':' : '') . ($hasWeight ? 'wght' : '') . (($hasWeight || $hasItalic) && $individualStylesString ? '@' . $individualStylesString : '');

		return $linkFamily;
	}
	private function installGoogleFont($fontData) {
		$fontData = is_string ( $fontData ) ? \json_decode ( $fontData ) : $fontData;

		$familyName = $this->generateFamily ( $fontData );

		$url = "https://fonts.googleapis.com/css2?family=" . $familyName;
		$url .= '&display=swap';

		$options = [ 
				'ssl' => [ 
						'verify_peer' => false,
						'verify_peer_name' => false
				],
				'http' => [ 
						'user_agent' => 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.82 Safari/537.36'
				]
		];

		$context = stream_context_create ( $options );
		$content = file_get_contents ( $url, false, $context );

		$matches = [ ];
		preg_match_all ( "@url\((.*?)\)@", $content, $matches );

		$fontUrls = ! empty ( $matches [1] ) ? $matches [1] : [ ];

		$baseDirectory = JPATH_ROOT . '/media/com_jpagebuilder/assets/google-fonts/' . $fontData->family;

		if (\file_exists ( $baseDirectory )) {
			Folder::delete ( $baseDirectory );
		}

		Folder::create ( $baseDirectory, 0755 );

		if (! empty ( $fontUrls )) {
			foreach ( $fontUrls as $url ) {
				$fileName = \basename ( $url );
				$content = str_replace ( $url, $fileName, $content );
				$fileContent = \file_get_contents ( $url );

				\file_put_contents ( $baseDirectory . '/' . $fileName, $fileContent );
			}
		}

		if (! empty ( $content )) {
			\file_put_contents ( $baseDirectory . '/stylesheet.css', $content );
		}

		$this->saveFontData ( $fontData->family, 'google', json_encode ( $fontData ) );
	}
	private function uninstallFont() {
		$familyName = $this->getInput ( 'family_name', '', 'string' );
		$type = $this->getInput ( 'type', '', 'string' );

		$baseDirectory = JPATH_ROOT . '/media/com_jpagebuilder/assets/';
		$directory = $type === 'google' ? 'google-fonts' : 'custom-fonts';

		if (! $this->isInstalledFont ( $familyName, $type )) {
			$this->sendResponse ( [ 
					'message' => 'Do not find the font to uninstall.'
			], 400 );
		}

		$db = Factory::getContainer()->get('DatabaseDriver');
		$query = method_exists ( $db, 'createQuery' ) ? $db->createQuery () : $db->getQuery ( true );

		$query->delete ( '#__jpagebuilder_fonts' )
			  ->where ( $db->quoteName ( 'family_name' ) . ' = ' . $db->quote ( $familyName ) )
			  ->where ( $db->quoteName ( 'type' ) . ' = ' . $db->quote ( $type ) );

		$db->setQuery ( $query );

		try {
			$db->execute ();
			$fontFile = $baseDirectory . $directory . '/' . $familyName;

			if (\file_exists ( $fontFile )) {
				Folder::delete ( $fontFile );
			}

			$this->sendResponse ( true );
		} catch ( \Exception $e ) {
			$this->sendResponse ( [ 
					'message' => $e->getMessage ()
			], 500 );
		}
	}
	public function fonts() {
		$method = $this->getInputMethod ();
		$this->checkNotAllowedMethods ( [ 
				'PUT',
				'PATCH'
		], $method );

		if ($method === 'GET') {
			$this->getFonts ();
		} elseif ($method === 'POST') {
			$this->installFont ();
		} elseif ($method === 'DELETE') {
			$this->uninstallFont ();
		}
	}
}
