<?php

declare(strict_types=1);

namespace PpPhp\Plugins;

use PpPhp\Core\EditPlugin;
use PpPhp\Core\Storage\StorageRef;
use PpPhp\Core\Storage\StorageRegistry;

/**
 * File / Image Upload edit control.
 *
 * A modern drag-and-drop uploader (previews, per-file progress, chunked /
 * resumable transfer, multi-file, drag-to-reorder) that speaks to the modular
 * core/Storage backend — self-hosted today, S3 / Google Drive / OneDrive on the
 * same interface later. Files upload by AJAX to api/upload.php, which returns a
 * StorageRef; the refs live as JSON in a hidden input named for the field, so
 * the surrounding <form> persists them with no multipart handling. A no-JS
 * fallback keeps a plain <input type=file> working.
 *
 * The heavy lifting is in public/assets/vendor/pp-uploader/* (loaded via
 * assets()); render() only emits the container + the field's resolved config.
 */
class FileImage implements EditPlugin
{
    public static function id(): string { return 'FileImage'; }
    public static function label(): string { return 'File / Image Upload'; }

    // ── Plugin Manager metadata ──────────────────────────────────
    public static function version(): string { return '2.0.0'; }
    public static function description(): string
    {
        return 'Drag-and-drop file & image uploader with live previews, progress, resumable chunked uploads, multi-file and reordering. Pluggable storage (self-hosted disk, and S3 / Google Drive / OneDrive via the core Storage drivers).';
    }
    public static function vendor(): array
    {
        return [[
            'name'    => 'pp-uploader',
            'version' => '2.0.0',
            'author'  => 'PPPHP',
            'license' => 'Proprietary (bundled)',
            'files'   => ['assets/vendor/pp-uploader/pp-uploader.js', 'assets/vendor/pp-uploader/pp-uploader.css'],
        ]];
    }

    private static function flag(array $s, string $key, bool $default = false): bool
    {
        $v = $s[$key] ?? ($default ? 'yes' : 'no');
        return in_array(strtolower((string) $v), ['1', 'yes', 'true', 'on'], true);
    }

    public static function render(string $fieldName, mixed $value, array $settings): string
    {
        $multi   = self::flag($settings, 'multiple');
        $maxFiles = max(0, (int) ($settings['maxFiles'] ?? 0));
        $imgOnly = self::flag($settings, 'imageOnly');
        $maxMb   = (float) ($settings['maxSizeMb'] ?? 10);
        $accept  = trim((string) ($settings['allowedExtensions'] ?? ''));
        $conn    = trim((string) ($settings['connection'] ?? 'local')) ?: 'local';
        $private = self::flag($settings, 'privateFiles', true);   // private by default (matches schema)
        $folder  = trim((string) ($settings['subfolder'] ?? $settings['uploadFolder'] ?? ''));

        // Decode existing value into clean refs; resolve display URLs server-side
        // (works for local public, private-via-file-endpoint, and cloud signed).
        $refs = StorageRef::decode($value);
        $resolved = [];
        if (class_exists(StorageRegistry::class)) {
            foreach ($refs as $r) {
                try {
                    $drv = StorageRegistry::forRef($r, $private ? ['private' => true] : []);
                    $resolved[] = [
                        'url'      => $drv->url($r, ['inline' => true]),
                        'thumbUrl' => $drv->url($r, ['thumb' => true, 'inline' => true]),
                    ];
                } catch (\Throwable $e) {
                    $resolved[] = ['url' => (string) ($r['url'] ?? ''), 'thumbUrl' => (string) ($r['url'] ?? '')];
                }
            }
        }

        $appBase = defined('APP_BASE') ? (string) \APP_BASE : '';
        $base = defined('\PpPhp\Core\Storage\LocalDriver::BASE') ? \PpPhp\Core\Storage\LocalDriver::BASE : 'Files';
        $cfg = [
            'field'      => $fieldName,
            'connection' => $conn,
            'endpoint'   => $appBase . '/api/upload.php',
            'multi'      => $multi,
            'maxFiles'   => $multi ? $maxFiles : 1,
            'maxSize'    => (int) round($maxMb * 1024 * 1024),
            'accept'     => $accept,
            'imageOnly'  => $imgOnly,
            'private'    => $private,
            'folder'     => $folder,
            'baseUploads' => $appBase . '/' . $base,
            'chunkSize'  => (int) (($settings['chunkMb'] ?? 4) * 1024 * 1024),
            'collectAlt' => self::flag($settings, 'collectAlt', false),
            'resolved'   => $resolved,
        ];
        if (self::flag($settings, 'clientCompress', false) && $imgOnly) {
            $cfg['compress'] = [
                'maxDim'  => max(200, (int) ($settings['compressMaxDim'] ?? 1920)),
                'quality' => max(0.4, min(0.95, (float) ($settings['compressQuality'] ?? 0.82))),
            ];
        }
        $aspect = trim((string) ($settings['cropAspect'] ?? ''));
        if ($aspect !== '' && $imgOnly) $cfg['cropAspect'] = $aspect;
        // CSRF token if the app exposes one.
        if (function_exists('csrf_token')) { $cfg['token'] = (string) csrf_token(); }
        elseif (class_exists('\PpPhp\Core\Security')) {
            try { $cfg['token'] = (string) \PpPhp\Core\Security::CSRF()->Token(); } catch (\Throwable $e) {}
        }

        $hiddenVal = StorageRef::encode($refs, $multi);
        $json = htmlspecialchars(
            json_encode($cfg, JSON_UNESCAPED_SLASHES | JSON_HEX_APOS | JSON_HEX_QUOT | JSON_UNESCAPED_UNICODE),
            ENT_QUOTES, 'UTF-8'
        );
        $cls = 'pp-uploader' . ($imgOnly && !$multi ? ' pp-up-avatar' : '');

        $html  = '<div class="' . $cls . '" data-pp-uploader="' . $json . '">';
        $html .= '<input type="hidden" class="pp-up-hidden" name="' . htmlspecialchars($fieldName) . '" value="' . htmlspecialchars($hiddenVal) . '">';
        $html .= '<div class="pp-up-drop"></div><div class="pp-up-list"></div>';
        $html .= '<input type="file" class="pp-up-file" hidden' . ($multi ? ' multiple' : '') . '>';
        // No-JS fallback: a real file input the server also accepts (multipart).
        $html .= '<noscript><input type="file" name="' . htmlspecialchars($fieldName) . '__file"' . ($multi ? ' multiple' : '') . ' class="pp-input"></noscript>';
        $html .= '</div>';

        return $html . self::enhanceInline();
    }

    /** One-time inline bootstrap so dynamically-injected controls also enhance. */
    private static function enhanceInline(): string
    {
        return <<<'HTML'
<script>(function(){if(window.__ppUpBoot)return;window.__ppUpBoot=1;function go(){if(window.__ppUploaderInit)window.__ppUploaderInit(document);}if(window.__ppUploaderInit)go();else document.addEventListener('DOMContentLoaded',go);})();</script>
HTML;
    }

    public static function settingsSchema(): array
    {
        $img = ['key' => 'imageOnly', 'in' => ['yes']];   // reused showIf predicate
        return [
            // ── Storage ───────────────────────────────────────────
            ['key' => 'connection', 'type' => 'select', 'label' => 'Store on', 'default' => 'local',
             'options' => self::connectionOptions(), 'group' => 'Storage',
             'hint' => 'Where uploaded files live. This server (self-hosted) is always available. To use S3 / Google Drive / OneDrive, add a connection first in Settings → Storage Connections (/settings/storage) — it then appears here.'],
            ['key' => 'subfolder', 'type' => 'text', 'label' => 'Sub-folder', 'default' => '', 'placeholder' => 'defaults to the table name',
             'group' => 'Storage', 'hint' => 'Folder for these files, under Files/public/ (or Files/private/ when Private is on). Nested paths allowed (docs/contracts). Blank = the table name, e.g. Files/public/customers/.'],
            ['key' => 'privateFiles', 'type' => 'bool', 'label' => 'Private files', 'default' => 'yes', 'group' => 'Storage',
             'hint' => 'Serve files through the app (authorized users only) instead of a public URL. Stored under Files/private with direct web access blocked. On by default.'],

            // ── Files & limits ────────────────────────────────────
            ['key' => 'imageOnly', 'type' => 'bool', 'label' => 'Images only', 'default' => 'no', 'group' => 'Files & limits',
             'hint' => 'Accept only image files (jpg, png, gif, webp…).'],
            ['key' => 'multiple', 'type' => 'bool', 'label' => 'Allow multiple', 'default' => 'no', 'group' => 'Files & limits',
             'hint' => 'Let the user attach more than one file to this field.'],
            ['key' => 'maxFiles', 'type' => 'number', 'label' => 'Max files', 'default' => 0, 'half' => true,
             'group' => 'Files & limits', 'showIf' => ['key' => 'multiple', 'in' => ['yes']], 'hint' => '0 = unlimited.'],
            ['key' => 'maxSizeMb', 'type' => 'number', 'label' => 'Max size (MB)', 'default' => 10, 'half' => true, 'group' => 'Files & limits'],
            ['key' => 'allowedExtensions', 'type' => 'text', 'label' => 'Allowed types', 'default' => '', 'placeholder' => 'jpg, png, pdf  (blank = safe defaults)',
             'group' => 'Files & limits', 'showIf' => ['key' => 'imageOnly', 'in' => ['no', '']],
             'hint' => 'Comma-separated extensions. Blank allows a safe default set; dangerous types (php, exe…) are always blocked.'],
            ['key' => 'required', 'type' => 'bool', 'label' => 'Required', 'default' => 'no', 'half' => true, 'group' => 'Files & limits'],
            ['key' => 'deleteOnRecordDelete', 'type' => 'bool', 'label' => 'Delete file with record', 'default' => 'no', 'half' => true,
             'group' => 'Files & limits', 'hint' => 'Remove the stored file(s) when the record is deleted.'],

            // ── Images ────────────────────────────────────────────
            ['key' => 'makeThumbnail', 'type' => 'bool', 'label' => 'Generate thumbnails', 'default' => 'yes', 'group' => 'Images',
             'hint' => 'Create a small preview alongside each image for fast lists & galleries.'],
            ['key' => 'clientCompress', 'type' => 'bool', 'label' => 'Downscale big images', 'default' => 'no', 'group' => 'Images',
             'showIf' => $img, 'hint' => 'Shrink oversized images in the browser before upload — saves bandwidth & storage.'],
            ['key' => 'compressMaxDim', 'type' => 'number', 'label' => 'Max dimension (px)', 'default' => 1920, 'half' => true,
             'group' => 'Images', 'showIf' => ['key' => 'clientCompress', 'in' => ['yes']]],
            ['key' => 'compressQuality', 'type' => 'number', 'label' => 'Quality (0.4–0.95)', 'default' => 0.82, 'half' => true,
             'group' => 'Images', 'showIf' => ['key' => 'clientCompress', 'in' => ['yes']]],
            ['key' => 'cropAspect', 'type' => 'text', 'label' => 'Crop to aspect', 'default' => '', 'placeholder' => 'e.g. 1:1, 16:9, 4:3',
             'group' => 'Images', 'showIf' => $img, 'hint' => 'Center-crop images to this ratio before upload (great for avatars/cover art). Blank = keep original.'],
            ['key' => 'collectAlt', 'type' => 'bool', 'label' => 'Ask for alt text', 'default' => 'no', 'group' => 'Images',
             'showIf' => $img, 'hint' => 'Prompt for a short description per image — improves accessibility (screen readers) and SEO.'],
            ['key' => 'stripExif', 'type' => 'bool', 'label' => 'Strip EXIF metadata', 'default' => 'yes', 'group' => 'Images',
             'showIf' => $img, 'hint' => 'Remove camera/location metadata on store (privacy).'],

            // ── Advanced ──────────────────────────────────────────
            ['key' => 'chunkMb', 'type' => 'number', 'label' => 'Chunk size (MB)', 'default' => 4, 'group' => 'Advanced',
             'hint' => 'Large uploads are sent in resumable chunks of this size, so a small server upload limit never blocks a big file.'],
        ];
    }

    /**
     * Storage-connection choices for the settings dropdown, as [value,label]
     * pairs (the inspector's pluginSettingCtrl understands the pair form).
     * Safe if the Storage classes aren't present.
     */
    private static function connectionOptions(): array
    {
        $seen = ['local' => true];
        $opts = [['local', 'This server (self-hosted)']];
        if (class_exists('\PpPhp\Core\Storage\StorageConfig')) {
            try {
                foreach (\PpPhp\Core\Storage\StorageConfig::choices() as $c) {
                    $id = (string) $c['id'];
                    if (isset($seen[$id])) continue;
                    $seen[$id] = true;
                    $opts[] = [$id, (string) $c['label']];
                }
            } catch (\Throwable $e) {}
        }
        return $opts;
    }

    public static function assets(): array
    {
        return [
            'js'  => ['assets/vendor/pp-uploader/pp-uploader.js'],
            'css' => ['assets/vendor/pp-uploader/pp-uploader.css'],
        ];
    }
}
