<?php

class Assets {
	protected bool $debug;
	protected string $outputDir;
	
	protected array $excludeDirs;
	protected array $excludeFilesBundle;
	protected array $excludeFilesAll;
	protected array $sortPriority;
	
	protected string $cssBundle = 'dist/bundle.min.css';
	protected string $jsBundle  = 'dist/bundle.min.js';
	
	public function __construct(
		bool $debug = false, 
		string $outputDir = 'dist', 
		array $excludeDirs = ['css/fonts', 'js/vendor'], 
		array $excludeFilesBundle = [], 
		array $excludeFilesAll = [], 
		array $sortPriority = ['jquery', 'jquery-ui', 'jquery-ui-i18n', 'fancybox', 'glide', 'functions']
	) {
    $this->debug = $debug;
    $this->outputDir = rtrim($outputDir, '/');

    $this->excludeDirs = array_map([$this, 'normalizePath'], $excludeDirs);
    $this->excludeFilesBundle = array_map([$this, 'normalizePath'], $excludeFilesBundle);
    $this->excludeFilesAll = array_map([$this, 'normalizePath'], $excludeFilesAll);
    $this->sortPriority = $sortPriority;

    if (!is_dir($this->outputDir)) {
    	mkdir($this->outputDir, 0755, true);
    }
	}
	
	public function favicon(string $dir = 'images/favicon', string $themeColor = '#111827'): string {
		$dir = trim($dir, '/');
		$path = '/'.$dir.'/';
		$file = $dir.'/favicon.ico';
		$version = is_file($file) ? filemtime($file) : time();
	
		if (!preg_match('/^#[a-f0-9]{6}$/i', $themeColor)) {
			$themeColor = '#111827';
		}
	
		$html = "";
		$html .= "<link rel='apple-touch-icon' sizes='180x180' href='".$path."apple-touch-icon.png?v=".$version."'>\n";
		$html .= "<link rel='icon' type='image/png' sizes='32x32' href='".$path."favicon-32x32.png?v=".$version."'>\n";
		$html .= "<link rel='icon' type='image/png' sizes='16x16' href='".$path."favicon-16x16.png?v=".$version."'>\n";
		$html .= "<link rel='manifest' href='".$path."site.webmanifest?v=".$version."'>\n";
		$html .= "<link rel='mask-icon' href='".$path."safari-pinned-tab.svg?v=".$version."' color='".htmlspecialchars($themeColor)."'>\n";
		$html .= "<link rel='shortcut icon' href='".$path."favicon.ico?v=".$version."'>\n";
		$html .= "<meta name='msapplication-TileColor' content='".htmlspecialchars($themeColor)."'>\n";
		$html .= "<meta name='msapplication-config' content='".$path."browserconfig.xml?v=".$version."'>\n";
		$html .= "<meta name='theme-color' content='".htmlspecialchars($themeColor)."'>\n";
	
		return $html;
	}
	
	public function css(): string {
    if (!$this->debug) {
    	$this->bundle('css');
      $path = '/'.ltrim($this->cssBundle, '/');
			$version = file_exists($this->cssBundle) ? filemtime($this->cssBundle) : time();
			return "<link rel='stylesheet' href='".$path."?v=".$version."'>";
    }

    $files = $this->collect('css', true);
    return $this->renderTags($files, 'css');
	}
	
	public function js(): string {
    if (!$this->debug) {
    	$this->bundle('js');
      $path = '/'.ltrim($this->jsBundle, '/');
			$version = file_exists($this->jsBundle) ? filemtime($this->jsBundle) : time();
			return "<script src='".$path."?v=".$version."'></script>";
    }

    $files = $this->collect('js', true);
    return $this->renderTags($files, 'js');
	}
	
	protected function bundle(string $type): void {
    $files = $this->collect($type, false);
    $bundleFile = $type === 'css' ? $this->cssBundle : $this->jsBundle;
    $bundlePath = $this->outputDir.'/'.basename($bundleFile);

    if (empty($files)) return;

    $newest = max(array_map('filemtime', $files));
    if (file_exists($bundlePath) && filemtime($bundlePath) >= $newest) return;

    $output = '';
    foreach ($files as $file) {
    	$output .= file_get_contents($file)."\n";
    }

    $output = $this->minify($output, $type);
    file_put_contents($bundlePath, $output);
	}
	
	protected function collect(string $ext, bool $forDebug): array {
    $dir = $ext;
    $files = [];

    if (!is_dir($dir)) return [];

    $rii = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($dir, FilesystemIterator::SKIP_DOTS));

    foreach ($rii as $file) {
      if (!$file->isFile()) continue;
      if (strtolower($file->getExtension()) !== $ext) continue;

      $path = $this->normalizePath($file->getPathname());

      if (in_array($path, $this->excludeFilesAll)) continue;

      foreach ($this->excludeDirs as $excl) {
		    $excl = rtrim($excl, '/');
		    if (strpos($path, $excl . '/') === 0 || strpos($path, '/' . $excl . '/') !== false) {
		    	continue 2;
		    }
			}

      if (!$forDebug && in_array($path, $this->excludeFilesBundle)) continue;

      $files[] = $path;
    }

    $getBaseName = function ($file) {
			$name = strtolower(basename($file));
			$name = preg_replace('/\.min\.js$|\.js$/', '', $name);
			$name = preg_replace('/-\d+(\.\d+)*$/', '', $name);
			return $name;
		};
		
		$sortPriority = $this->sortPriority;
		
		usort($files, function ($a, $b) use ($getBaseName, $sortPriority) {
			$score = function ($file) use ($getBaseName, $sortPriority) {
				$base = $getBaseName($file);
				foreach ($sortPriority as $i => $needle) {
					if ($base === strtolower($needle)) {
						return $i;
					}
				}
				return count($sortPriority);
			};
		
			$scoreA = $score($a);
			$scoreB = $score($b);
			return $scoreA <=> $scoreB ?: strcmp($a, $b);
		});
		
    return $files;
	}
	
	protected function renderTags(array $files, string $type): string {
    $html = '';
    foreach ($files AS $file) {
      $file = '/'.ltrim($file, '/');
      if ($type === 'css') {
      	$html .= "<link rel='stylesheet' href='".$file."'>\n";
      } elseif ($type === 'js') {
      	$html .= "<script src='".$file."'></script>\n";
      }
    }
    return $html;
	}
	
	protected function minify(string $content, string $type): string {
    if ($type === 'css') {
      $content = preg_replace('!/\*.*?\*/!s', '', $content);
      $content = preg_replace('/\s+/', ' ', $content);
      $content = str_replace([' {', '{ ', ' }', '; '], ['{', '{', '}', ';'], $content);
    } elseif ($type === 'js') {
      $content = preg_replace('#^\s*//.*$#m', '', $content);
			$content = preg_replace('#/\*.*?\*/#s', '', $content);
    }

    return trim($content);
	}
	
	protected function normalizePath(string $path): string {
		return str_replace('\\', '/', rtrim($path, '/'));
	}
}
