<?php

use Illuminate\Support\Collection;
use Twig\Environment;

/**
 * AssetLoader
 *
 * Used to load assets directly from files instead of using the
 * gulpfile's rendered assets or having to use gulp watch. It
 * should mimic the exact behavior of the gulpfile.
 *
 * @author Craig McCreath <craig.mccreath@mtcmedia.co.uk>
 */
class AssetLoader
{
    const SESSION_VAR = 'AssetLoader';

    /**
     * List of JS files to look into by default
     *
     * @var array
     */
    protected $js_files = [
        '/core/js/jquery.js',
        '/core/js/jquery.ui.js',
        '/core/js/vue' . ((!DEV_MODE) ? '.min' : '') . '.js',
        '/core/js/less.js',
        '/core/js/moment.min.js',
        '/core/js/plugins/mtc_*.js',
        '/core/admin/js/libs/tagit/js/tag-it.min.js'
    ];

    /**
     * Scripts that now rely on ES module bundling and should not be loaded directly.
     * When the asset loader is active we skip these to avoid syntax errors in the browser.
     *
     * @var array
     */
    protected $module_js_files = [
        '/core/js/functions.js',
        '/core/js/plugins/mtc_vue_directives.js',
        '/core/js/plugins/mtc_vue_filters.js',
        '/core/js/plugins/mtc_vue_resource.js',
        '/modules/Members/js/mtc_follow_ups.js',
        '/modules/BasketRecovery/admin/js/mtc_follow_ups.js',
    ];

    /**
     * Render all of the assets
     *
     * @return bool
     */
    public function all() {

        if (empty($_SESSION[self::SESSION_VAR]) || $_SESSION[self::SESSION_VAR] != true) {
            return false;
        }

        $this->less();
        $this->js();

        return true;
    }

    /**
     * Output all the HTML tags to include LESS stylesheets
     *
     * @return void
     */
    protected function less()
    {
        $files = $this->getFilesMatching([
            THEME_PATH . '/css/mtc_*.less',
            '/abs/css/mtc_*.less',
            '/shop/css/mtc_*.less',
            '/cms/css/mtc_*.less'
        ]);
        foreach ($files as $file) {
            $file .= '?' . time();
            ?>
            <link rel="stylesheet/less" href="<?= htmlentities($file) ?>" media="screen, print">
            <?php
        }
    }

    /**
     * Output all the JS script tags
     *
     * @return void
     */
    protected function js()
    {
        // Generate a list of JS
        $search = $this->js_files;

        array_push($search,
            THEME_PATH . '/js/plugins/mtc_*.js',
            '/core/js/functions.js',
            THEME_PATH . '/js/functions.js'
        );

        // Add plugins to this
        if (is_dir(SITE_PATH . '/plugins')) {
            foreach ($this->getFoldersIn('/plugins') as $directory) {
                array_push($search, '/plugins/' . $directory . '/js/mtc_*.js',
                                THEME_PATH . '/plugins/' . $directory . '/js/mtc_*.js');
            }
        }

        // Add plugins to this
        if (is_dir(SITE_PATH . '/modules')) {
            foreach ($this->getFoldersIn('/modules') as $directory) {
                array_push($search, '/modules/' . $directory . '/js/mtc_*.js',
                                THEME_PATH . '/modules/' . $directory . '/js/mtc_*.js');
            }
        }

        // Add shop & cms files
        array_push($search, '/shop/js/mtc_*.js',
                            '/cms/js/mtc_*.js',
                            '/abs/js/mtc_*.js');

        // Add site files
        array_push($search, '/core/js/site_scripts/mtc_*.js',
                            THEME_PATH . '/js/site_scripts/mtc_*.js',
                            THEME_PATH . '/js/script.js');

        // Get the matching files
        $skip_files = array_map(function ($path) {
            return SITE_PATH . $path;
        }, $this->module_js_files);

        $files = $this->getFilesMatching($search);
        $all_files = $files;

        $files = $files->reject(function ($file) use ($skip_files) {
            if (in_array($file, $skip_files, true)) {
                return true;
            }

            return $this->isEsModule($file);
        })->filter(function ($file) use ($all_files) {
            // Filter out ones that are included within the site as override
            return $all_files->contains(THEME_PATH . $file) == false;
        })->each(function ($file) {
            $file .= '?' . time();
            ?>
            <script src="<?=htmlentities($file)?>"></script>
            <?php
        });

    }

    /**
     * Heuristically determine whether the file is an ES module (starts with an import/export statement).
     */
    protected function isEsModule(string $path): bool
    {
        if (!is_readable($path)) {
            return false;
        }

        $handle = fopen($path, 'r');
        if (! $handle) {
            return false;
        }

        $chunk = fread($handle, 512);
        fclose($handle);

        if ($chunk === false) {
            return false;
        }

        $chunk = ltrim($chunk);

        return str_starts_with($chunk, 'import ') || str_starts_with($chunk, 'export ');
    }

    /**
     * Return a list of folders in a directory
     *
     * @param string $folder Full path to directory
     * @return Illuminate\Support\Collection
     */
    protected function getFoldersIn($directory)
    {
        $directory = SITE_PATH . $directory;

        return (new Collection(scandir($directory)))
            ->filter(function ($file) use ($directory) {
                return is_dir($directory . DIRECTORY_SEPARATOR . $file) &&
                       in_array($file, ['.', '..']) == false;
            });
    }

    /**
     * Return files matching the array of patterns
     *
     * @param array $searches
     * @return Illuminate\Support\Collection
     */
    protected function getFilesMatching(array $searches)
    {
        $files = new Collection;

        foreach ($searches as $search) {
            $search = SITE_PATH . $search;
            if (is_file($search)) {

                $files->push($search);
            } elseif (is_dir(dirname($search))) {
                $file_array = glob($search);
                sort($file_array);
                $files->push($file_array);
            }
        }

        $files->sort();


        return $files->flatten()
            ->reject(function ($item) {
                return $item == '.' || $item == '..';
            })->map(function ($item) {
                return $this->stripSitePath($item);
            });
    }

    /**
     * Helper method to strip the current site path
     * from a specific file.
     *
     * @param string $file
     * @return string
     */
    protected function stripSitePath($file)
    {
        return str_ireplace(SITE_PATH, '', $file);
    }

    /**
     * Generate DebugBar header for the site.
     * Echo instead of return due to hook functionality
     *
     * @param Environment $twig
     */
    public static function addDebugBarHeader(Environment $twig): void
    {
        echo $twig->render('debug/header.twig');
    }

    /**
     * Generate DebugBar render statement for the site.
     * Echo instead of return due to hook functionality
     *
     * @param Environment $twig
     */
    public static function addDebugBarFooter(Environment $twig): void
    {
        echo $twig->render('debug/footer.twig');
    }
}
