<?php

namespace onespace\tools\widgets\input;

use onespace\tools\icons\widgets\FontAwesomeIcon6;
use yii\bootstrap\ActiveForm;
use yii\helpers\Html;
use yii\widgets\InputWidget;

/**
 * Draw an file input field, which automatically converts the image to a WebP, resizing and other tasks,
 * all on the frontend, before it can be submitted up to the server.
 * 
 * {@inheritdoc}
 * 
 * @author  Gareth Palmer
 */

final class ImageConverterFileInput extends InputWidget {

    /**
     * Define the maximum width of the converted image. Set to `null` to leave the width as uploaded.
     * 
     * @var int|null $maxWidth: Default null
     * 
     * @access  public
     */

    public ?int $maxWidth = null;

    /**
     * Define the maximum height of the converted image. Set to `null` to leave the height as uploaded.
     * 
     * @var int|null $maxHeight: Default null
     * 
     * @access  public
     */

    public ?int $maxHeight = null;

    /**
     * Whether or not to draw a preview of the image after conversion.
     * 
     * @var bool    $showPreview    Default: true
     * 
     * @access  public
     */

    public bool $showPreview = true;

    /**
     * Whether or not to include form-control class to the container div. This is usually disabled but
     * not when using widgets. Off is generally the desired behaviour.
     * 
     * @var bool    $includeFormControl Default: false
     * 
     * @access  public
     */

    public bool $includeFormControl = false;

    /**
     * Set the MIMETYPE limitation.
     * 
     * @var string  $accept Default: `image/*`
     * 
     * @access  public
     */

    public string $accept = 'image/*';

    /**
     * The ID of the image preview container.
     * 
     * @var string  $imgId
     * 
     * @access  private
     */

    private string $imgId;

    /**
     * Parse the ActiveForm element into the widget. This allows for the auto setting
     * of some required settings.
     * 
     * @var yii\bootstrap\ActiveForm  $form
     * 
     * @access  public
     */

    public ActiveForm $form;

    /**
     * OPTIONAL - Set a layout type for the input field. Maps to the constants beginning with `LAYOUT_`.
     * 
     * Default: `LAYOUT_STANDARD`
     * 
     * @var int $layout
     * 
     * @access  public
     */

    public int $layout;


    public const LAYOUT_STANDARD = 0;
    public const LAYOUT_DROPPER = 1;

    /**
     * OPTIONAL - Sets the width of the generated preview.
     * 
     * Default: null
     * 
     * @var int|null    $previewWidth
     * 
     * @access  public
     */

    public ?int $previewWidth = null;

    /**
     * OPTIONAL - Sets the height of the generated preview.
     * 
     * Default: null
     * 
     * @var int|null    $previeHeight
     * 
     * @access  public
     */

    public ?int $previeHeight = null;


    /**
     * {@inheritdoc}
     * 
     * @access  public
     */

    public function init(): void {
        parent::init();

        $this->layout ??= self::LAYOUT_STANDARD;

        if (isset($this->accept) && !isset($this->options['accept'])) {
            $this->options['accept'] = $this->accept;
        }

        $view = $this->getView();
        $view->registerJs(<<<JS
        const convertToWebP = (file, maxWidth, maxHeight, callback) => {
            const reader = new FileReader();
            reader.onload = function (event) {
                const img = new Image();
                img.src = event.target.result;
                img.onload = function () {
                    const canvas = document.createElement('canvas');

                    let width = img.width;
                    let height = img.height;

                    const aspectRatio = width / height;

                    // Adjust dimensions if necessary
                    if (maxWidth !== null) {
                        if (width > maxWidth) {
                            width = maxWidth;
                            height = width / aspectRatio;
                        }
                    }
                    if (maxHeight !== null) {
                        if (height > maxHeight) {
                            height = maxHeight;
                            width = height * aspectRatio;
                        }
                    }

                    canvas.width = width;
                    canvas.height = height;


                    const ctx = canvas.getContext('2d');
                    ctx.drawImage(img, 0, 0, width, height);
                    canvas.toBlob(function (blob) {
                        callback(blob);
                    }, 'image/webp');
                };
            };
            reader.readAsDataURL(file);
        }
        JS);

        if (isset($this->form)) {
            $this->form->options['enctype'] = 'multipart/form-data';
        }

        if ($this->hasModel()) {
            $id = Html::getInputId($this->model, $this->attribute);
            $this->imgId = "{$id}-img";
            $view->registerJs(<<<JS
            var fileInput = document.querySelector('#{$id}');
            JS);
        } else {
            $this->imgId = "{$this->name}-img";
            $view->registerJs(<<<JS
            var fileInput = document.querySelector('[name="$this->name"]')
            JS);
        }

        $maxWidth = $this->maxWidth;
        if (empty($maxWidth)) {
            $maxWidth = "null";
        }
        $maxHeight = $this->maxHeight;
        if (empty($maxHeight)) {
            $maxHeight = "null";
        }
        $showPreview = $this->showPreview ? 1 : 0;
        $previewWidth = $this->previewWidth;
        $previewWidth ??= 'null';
        $previewHeight = $this->previeHeight;
        $previewHeight ??= 'null';

        $view->registerJs(<<<JS
        const showHideUploader = (text) => {
            const toHide = document.querySelectorAll('.upload-show')
            const toShow = document.querySelector('.upload-hidden')
            toHide.forEach(el => el.classList.add('hidden'));
            toShow.classList.remove('hidden');
            toShow.innerHTML = text;
        };
        const hideShowUploader = () => {
            const toHide = document.querySelectorAll('.upload-show')
            const toShow = document.querySelector('.upload-hidden')
            toHide.forEach(el => el.classList.remove('hidden'));
            toShow.classList.add('hidden');
            toShow.innerHTML = '';   
        }
        var imageConverter = () => {
            const imgContainer = document.querySelector('#{$this->imgId}');
            const dropperId = '#{$this->imgId}-dropper';
            const dropUploader = document.querySelector(dropperId);
            imgContainer.innerHTML = '';
            if (fileInput.files.length > 0) {
                if (dropUploader) {
                    showHideUploader('Converting image...<br><div class="text-center"><br><i class="fa-solid fa-loader fa-spin fa-2xl"></i></div>');
                } else {
                    imgContainer.innerHTML = 'Converting image...<br><div class="text-center"><br><i class="fa-solid fa-loader fa-spin fa-2xl"></i></div>';
                }
                const file = fileInput.files[0];
                const maxWidth = {$maxWidth} == 'null' ? null : {$maxWidth};
                const maxHeight = {$maxHeight} == 'null' ? null : {$maxHeight};

                convertToWebP(file, maxWidth, maxHeight, function (webPBlob) {
                    const newName = file.name.substring(0, file.name.lastIndexOf('.')) + '.webp'
                    const webPFile = new File([webPBlob], newName, { type: 'image/webp' });

                    const container = new DataTransfer();
                    container.items.add(webPFile)

                    fileInput.files = container.files

                    if ($showPreview == 1) {
                        const convertedImageURL = URL.createObjectURL(webPBlob);
                        const imgElement = document.createElement('img');
                        imgElement.src = convertedImageURL;

                        if ($previewWidth !== null) {
                            imgElement.width = $previewWidth;
                        }
                        if ($previewHeight !== null) {
                            imgElement.height = $previewHeight;
                        }

                        imgContainer.innerHTML = '';
                        imgContainer.appendChild(imgElement);

                        if (dropUploader) {
                            showHideUploader(newName);
                        }
                    } else {
                        imgContainer.innerHTML = 'Image converted';
                    }
                });
            } else {
                imgContainer.innerHTML = '';
            }
        }
        fileInput.addEventListener('input', () => imageConverter());
        JS);
    }


    /**
     * {@inheritdoc}
     * 
     * @return  string
     * 
     * @access  public
     */

    public function run(): string {
        $html = parent::run();

        if (!$this->includeFormControl && isset($this->options['class'])) {
            if (is_array($this->options['class'])) {
                $opts = array_flip($this->options['class']);
                unset($opts['form-control']);
                $this->options['class'] = array_flip($opts);
            } else {
                $this->options['class'] = trim(str_replace('form-control', '', $this->options['class']));
            }
        }

        $html .= match ($this->layout) {
            self::LAYOUT_STANDARD => $this->standardInputField(),
            self::LAYOUT_DROPPER => $this->dropperInputField(),
            default => $this->standardInputField(),
        };

        $html .= Html::tag('br');
        $html .= Html::tag("div", options: [
            'id' => $this->imgId,
        ]);

        return $html;
    }


    /**
     * Draw a drag & drop visual HTML `<input type="file">`.
     * 
     * @return  string
     * 
     * @access  private
     */

    private function dropperInputField(): string {
        $dropperId = $this->imgId . '-dropper';

        $view = $this->getView();
        $view->registerCss(<<<CSS
        .uploader-dropper-container {
            width: 100%;
            height: 20rem;
            background-color: #c9dadf;
            border: 2px dashed #000;
            outline: 5px solid #c9dadf;
            border-radius: 5px;
            padding: 0 2rem;
            margin: 1rem 0;
            cursor: pointer;
            display: flex;
            flex-direction: column;
            justify-content: center;
            align-items: center;
        }

        .uploader-dropper-container:hover,
        .uploader-dropper-container.da-highlight {
            border: 1px dashed #000;
        }

        .uploader-dropper-container:hover div,
        .uploader-dropper-container.da-highlight div {
            scale: 1.1;
        }

        .uploader-dropper-container.da-highlight {
            background-color: #f0f0f0;
            outline: 5px solid #f0f0f0;
        }
        CSS);


        $fileInputId = $this->hasModel() ? "#" . Html::getInputId($this->model, $this->attribute) : "[name=\"{$this->name}\"]";

        $view->registerJs(<<<JS
        const input = document.querySelector("{$fileInputId}");
        const dropArea = document.querySelector('#{$dropperId}');
        dropArea.addEventListener("click", () => input.click());

        ['dragenter', 'dragover', 'dragleave', 'drop'].forEach(eventName => {
            dropArea.addEventListener(eventName, e => {
                e.preventDefault();
                e.stopPropagation();
            }, false);
        });

        let boxContents = '';
        ['dragenter', 'dragover'].forEach(eventName => {
            dropArea.addEventListener(eventName, () => {
                dropArea.classList.add('da-highlight');
                boxContents = document.querySelector('.upload-hidden').innerHTML;
                showHideUploader('Drop to upload')
            }, false);
        });

        ['dragleave', 'drop'].forEach(eventName => {
            dropArea.addEventListener(eventName, () => {
                dropArea.classList.remove('da-highlight');
                if (input.files.length == 0) {
                    hideShowUploader();
                } else {
                    showHideUploader(boxContents)
                    boxContents = '';
                }
            }, false);
        });

        dropArea.addEventListener('drop', e => {
            const dt = e.dataTransfer;
            input.files = dt.files
            imageConverter();
        }, false);
        JS);

        $html = '';

        $html .= Html::beginTag('div', ['class' => 'uploader-dropper-container', 'id' => $dropperId]);

        $html .= Html::tag(
            'div',
            FontAwesomeIcon6::widget(['identifier' => 'upload', 'style' => 'light', 'content' => '']),
            ['class' => 'text-center upload-show', 'style' => 'font-size: 7rem;']
        );
        $html .= Html::tag('div', "Click to <strong>SELECT</strong> or <strong>DROP</strong> a file to upload.", ['class' => 'text-center upload-show']);
        $html .= Html::tag('div', options: ['class' => 'text-center upload-hidden hidden']);

        $html .= Html::endTag('div');

        $html .= Html::tag('div', $this->standardInputField(), [
            'class' => 'hidden',
        ]);

        return $html;
    }


    /**
     * Draw a standard unformatted HTML `<input type="file">`.
     * 
     * @return  string
     * 
     * @access  private
     */

    private function standardInputField(): string {
        if ($this->hasModel()) {
            return Html::activeFileInput($this->model, $this->attribute, $this->options);
        } else {
            return Html::fileInput($this->name, $this->value, $this->options);
        }
    }
}
