<?php

namespace Formatrica\Submission\File;

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

use Formatrica\Submission\Validation\File_Validator;

/**
 * Handles secure file uploads.
 *
 * Single-file (`file`) and multi-file (Pro `multi_file`) share the same
 * validate-then-move core (process_one). Multi-file is two-phase: every file is
 * moved, and if any move fails the already-moved files are rolled back so a
 * partial upload never persists.
 */
final class File_Uploader
{
    private File_Validator $validator;

    public function __construct(?File_Validator $validator = null)
    {
        $this->validator = $validator ?? new File_Validator();
    }

    /**
     * Edition upload handlers beyond the native single-file type (Pro
     * registers multi_file). Signature: fn(array $field, array $files,
     * File_Uploader $u): array{0:mixed,1:array,2:?string} [value, meta, error]
     *
     * @var array<string, callable>
     */
    private static array $upload_handlers = [];

    public static function register_upload_handler(string $type, callable $handler): void
    {
        self::$upload_handlers[$type] = $handler;
    }

    /** @return callable|null */
    public static function upload_handler(string $type)
    {
        return self::$upload_handlers[$type] ?? null;
    }

    /**
     * Handle a single-file upload.
     *
     * @param array<string, mixed> $field
     * @param array<string, mixed> $files
     * @return array{0: string, 1: array<string, mixed>, 2: ?string} [filename, meta, error]
     */
    public function handle(array $field, array $files): array
    {
        $name = $field['name'] ?? '';

        if ($name === '') {
            return ['', [], null];
        }

        $file = $this->validator->extract_uploaded_file($name, $files);

        if (!$file || ($file['error'] ?? UPLOAD_ERR_NO_FILE) === UPLOAD_ERR_NO_FILE) {
            return ['', [], null];
        }

        [$filename, $meta, $error] = $this->process_one($file, $field);
        unset($meta['abs_path']); // internal, multi-file-rollback only
        return [$filename, $meta, $error];
    }

    /**
     * Validate + move one file. Returns [filename, meta, error]. Meta carries an
     * internal `abs_path` (removed before it reaches storage) so a multi-file
     * batch can roll the file back on a later failure.
     *
     * @param array<string, mixed> $file  A single $_FILES-shaped entry.
     * @param array<string, mixed> $field
     * @return array{0: string, 1: array<string, mixed>, 2: ?string}
     */
    /** @internal shared with the Pro multi-file handler. */
    public function process_one(array $file, array $field): array
    {
        // Per-file security + compliance checks (size, MIME, image dims).
        $error = $this->validator->validate_one($file, $field);
        if ($error !== null) {
            return ['', [], $error];
        }

        // Load wp_handle_sideload if not available
        if (!function_exists('wp_handle_sideload')) {
            require_once ABSPATH . 'wp-admin/includes/file.php';
        }

        $final_allowed_types = $this->validator->resolve_allowed_types($field);

        $check = wp_check_filetype_and_ext($file['tmp_name'], $file['name']);
        $type = $check['type'] ?? '';
        $proper_filename = $check['proper_filename'] ?? false;

        if ($proper_filename) {
            $file['name'] = $proper_filename;
        }

        // Prepare upload directory
        $uploads = wp_upload_dir();

        if (!empty($uploads['error'])) {
            return ['', [], __('Unable to determine upload directory.', 'formatrica')];
        }

        $target_dir = trailingslashit($uploads['basedir']) . 'formatrica/';

        if (!wp_mkdir_p($target_dir)) {
            return ['', [], __('Unable to prepare upload directory.', 'formatrica')];
        }

        // Generate random filename
        $original_name = sanitize_file_name($file['name']);
        $extension = strtolower(pathinfo($original_name, PATHINFO_EXTENSION));
        $random_name = wp_generate_uuid4() . '.' . $extension;

        // Store filter callbacks so we can remove only these specific filters later
        $upload_dir_filter = function ($upload_dir) use ($target_dir, $uploads) {
            $upload_dir['path'] = rtrim($target_dir, '/');
            $upload_dir['url'] = trailingslashit($uploads['baseurl']) . 'formatrica';
            $upload_dir['subdir'] = '';
            return $upload_dir;
        };

        $upload_mimes_filter = function ($mimes) use ($final_allowed_types) {
            $ext_to_mime = [
                'jpg|jpeg|jpe' => 'image/jpeg',
                'png' => 'image/png',
                'gif' => 'image/gif',
                'webp' => 'image/webp',
                'pdf' => 'application/pdf',
                'doc' => 'application/msword',
                'docx' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
                'xls' => 'application/vnd.ms-excel',
                'xlsx' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
                'txt' => 'text/plain',
                'csv' => 'text/csv',
            ];

            $allowed = [];
            foreach ($ext_to_mime as $ext => $mime) {
                if (in_array($mime, $final_allowed_types, true)) {
                    $allowed[$ext] = $mime;
                }
            }

            return $allowed;
        };

        $sideload_filter = function ($file_arr) use ($random_name) {
            $file_arr['name'] = $random_name;
            return $file_arr;
        };

        // Add filters
        add_filter('upload_dir', $upload_dir_filter);
        add_filter('upload_mimes', $upload_mimes_filter);
        add_filter('wp_handle_sideload_prefilter', $sideload_filter);

        $overrides = [
            'test_form' => false,
            'test_type' => true,
        ];

        $result = wp_handle_sideload($file, $overrides);

        // Clean up only our filters (not other plugins' filters)
        remove_filter('upload_dir', $upload_dir_filter);
        remove_filter('upload_mimes', $upload_mimes_filter);
        remove_filter('wp_handle_sideload_prefilter', $sideload_filter);

        if (isset($result['error'])) {
            return ['', [], $result['error']];
        }

        if (empty($result['file']) || empty($result['url'])) {
            return ['', [], __('Failed to store uploaded file.', 'formatrica')];
        }

        $filename = basename($result['file']);
        $relative = 'formatrica/' . $filename;

        return [
            $filename,
            [
                'path' => $relative,
                'url' => $result['url'],
                'type' => $type,
                'original_name' => $original_name,
                // Internal, storage-only: absolute path for multi-file rollback.
                'abs_path' => $result['file'],
            ],
            null,
        ];
    }
}
