<?php

/**
 * @package         Convert Forms
 * @version         5.2.5-RC2 Free
 *
 * @author          Tassos Marinos <info@tassos.gr>
 * @link            https://www.tassos.gr
 * @copyright       Copyright © 2024 Tassos All Rights Reserved
 * @license         GNU GPLv3 <http://www.gnu.org/licenses/gpl.html> or later
*/

namespace ConvertForms;

defined('_JEXEC') or die('Restricted access');

use Tassos\Framework\File;
use Joomla\CMS\Factory;

/**
 * Collects file uploads whose destination folder contains submission-level Smart Tags
 * (eg. {submission.id}) and moves them to their final folder once the submission has
 * been created and its ID is known.
 *
 * Such destinations cannot be resolved during the pre-save upload pass because the
 * submission row does not exist yet. Instead of moving the file twice, the File Upload
 * field records the pending move here and the conversion model finalizes it right after
 * the submission is saved — before the submission item is prepared — so every downstream
 * consumer (Smart Tags, email notifications, backend views) is built from the final path.
 */
class DeferredUploads
{
	/**
	 * The pending moves recorded during the current request.
	 *
	 * @var array
	 */
	private static $moves = [];

	/**
	 * Record a pending upload move.
	 *
	 * @param   array  $move  Expects: field (the FileUpload field instance), field_name,
	 *                        key (file index within the field), source (absolute temp path),
	 *                        template (absolute destination path with submission tags pending).
	 *
	 * @return  void
	 */
	public static function defer(array $move): void
	{
		self::$moves[] = $move;
	}

	/**
	 * Whether any pending moves have been recorded.
	 *
	 * @return  bool
	 */
	public static function any(): bool
	{
		return !empty(self::$moves);
	}

	/**
	 * Move every recorded upload to its final folder and update the submission params
	 * with the final relative paths.
	 *
	 * A failed move falls back to the default uploads folder so the file is never lost
	 * and the stored path always points to a real file.
	 *
	 * @param   object  $submission  The created submission (or the mocked one when the form
	 *                               does not save to the database) — used to resolve
	 *                               submission-level Smart Tags.
	 * @param   array   $params      The submission params, updated in place.
	 *
	 * @return  void
	 */
	public static function finalize($submission, array &$params): void
	{
		$app = Factory::getApplication();

		foreach (self::$moves as $move)
		{
			$field = $move['field'];

			// Resolve the remaining runtime tags (field.* and submission.*) in one pass.
			$destination = $field->resolveRuntimeTags($move['template'], $params, $submission);

			try
			{
				$destination = File::move($move['source'], $destination);
			}
			catch (\Throwable $th)
			{
				// Fall back to the default uploads folder so the file is never lost.
				Helper::log('Convert Forms: Deferred upload move failed, falling back to the default uploads folder. ' . $th->getMessage());

				try
				{
					$destination = File::move($move['source'], JPATH_ROOT . '/media/com_convertforms/uploads/' . basename($destination));
				}
				catch (\Throwable $th)
				{
					// Keep the temp path so the stored value still points to an existing file.
					Helper::log('Convert Forms: Deferred upload fallback move failed. ' . $th->getMessage());
					continue;
				}
			}

			$tmpData = [
				'form_id'    => $submission->form_id,
				'field_name' => $move['field_name'],
				'params'     => $params
			];

			

			// Give a chance to manipulate the file with a hook, exactly like the pre-save upload pass.
			$app->triggerEvent('onConvertFormsFileUpload', [&$destination, $tmpData]);

			// Always save the relative path to the database.
			$params[$move['field_name']][$move['key']] = Helper::pathTorelative($destination);
		}

		self::$moves = [];
	}
}
