<?php

namespace Kirby\Filesystem;

use Kirby\Toolkit\Str;

/**
 * The `Filename` class handles complex
 * mapping of file attributes (i.e for thumbnails)
 * into human readable filenames.
 *
 * ```php
 * $filename = new Filename('some-file.jpg', '{{ name }}-{{ attributes }}.{{ extension }}', [
 *   'crop'    => 'top left',
 *   'width'   => 300,
 *   'height'  => 200
 *   'quality' => 80
 * ]);
 *
 * echo $filename->toString();
 * // result: some-file-300x200-crop-top-left-q80.jpg
 *
 * @package   Kirby Filesystem
 * @author    Bastian Allgeier <bastian@getkirby.com>
 * @link      https://getkirby.com
 * @copyright Bastian Allgeier GmbH
 * @license   https://getkirby.com/license
 */
class Filename
{
    /**
     * List of all applicable attributes
     *
     * @var array
     */
    protected $attributes;

    /**
     * The sanitized file extension
     *
     * @var string
     */
    protected $extension;

    /**
     * The source original filename
     *
     * @var string
     */
    protected $filename;

    /**
     * The sanitized file name
     *
     * @var string
     */
    protected $name;

    /**
     * The template for the final name
     *
     * @var string
     */
    protected $template;

    /**
     * Creates a new Filename object
     *
     * @param string $filename
     * @param string $template
     * @param array $attributes
     */
    public function __construct(string $filename, string $template, array $attributes = [])
    {
        $this->filename   = $filename;
        $this->template   = $template;
        $this->attributes = $attributes;
        $this->extension  = $this->sanitizeExtension(
            $attributes['format'] ??
            pathinfo($filename, PATHINFO_EXTENSION)
        );
        $this->name       = $this->sanitizeName(pathinfo($filename, PATHINFO_FILENAME));
    }

    /**
     * Converts the entire object to a string
     *
     * @return string
     */
    public function __toString(): string
    {
        return $this->toString();
    }

    /**
     * Converts all processed attributes
     * to an array. The array keys are already
     * the shortened versions for the filename
     *
     * @return array
     */
    public function attributesToArray(): array
    {
        $array = [
            'dimensions' => implode('x', $this->dimensions()),
            'crop'       => $this->crop(),
            'blur'       => $this->blur(),
            'bw'         => $this->grayscale(),
            'q'          => $this->quality(),
        ];

        $array = array_filter(
            $array,
            fn ($item) => $item !== null && $item !== false && $item !== ''
        );

        return $array;
    }

    /**
     * Converts all processed attributes
     * to a string, that can be used in the
     * new filename
     *
     * @param string|null $prefix The prefix will be used in the filename creation
     * @return string
     */
    public function attributesToString(string $prefix = null): string
    {
        $array  = $this->attributesToArray();
        $result = [];

        foreach ($array as $key => $value) {
            if ($value === true) {
                $value = '';
            }

            switch ($key) {
                case 'dimensions':
                    $result[] = $value;
                    break;
                case 'crop':
                    $result[] = ($value === 'center') ? 'crop' : $key . '-' . $value;
                    break;
                default:
                    $result[] = $key . $value;
            }
        }

        $result     = array_filter($result);
        $attributes = implode('-', $result);

        if (empty($attributes) === true) {
            return '';
        }

        return $prefix . $attributes;
    }

    /**
     * Normalizes the blur option value
     *
     * @return false|int
     */
    public function blur()
    {
        $value = $this->attributes['blur'] ?? false;

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

        return (int)$value;
    }

    /**
     * Normalizes the crop option value
     *
     * @return false|string
     */
    public function crop()
    {
        // get the crop value
        $crop = $this->attributes['crop'] ?? false;

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

        return Str::slug($crop);
    }

    /**
     * Returns a normalized array
     * with width and height values
     * if available
     *
     * @return array
     */
    public function dimensions()
    {
        if (empty($this->attributes['width']) === true && empty($this->attributes['height']) === true) {
            return [];
        }

        return [
            'width'  => $this->attributes['width']  ?? null,
            'height' => $this->attributes['height'] ?? null
        ];
    }

    /**
     * Returns the sanitized extension
     *
     * @return string
     */
    public function extension(): string
    {
        return $this->extension;
    }

    /**
     * Normalizes the grayscale option value
     * and also the available ways to write
     * the option. You can use `grayscale`,
     * `greyscale` or simply `bw`. The function
     * will always return `grayscale`
     *
     * @return bool
     */
    public function grayscale(): bool
    {
        // normalize options
        $value = $this->attributes['grayscale'] ?? $this->attributes['greyscale'] ?? $this->attributes['bw'] ?? false;

        // turn anything into boolean
        return filter_var($value, FILTER_VALIDATE_BOOLEAN);
    }

    /**
     * Returns the filename without extension
     *
     * @return string
     */
    public function name(): string
    {
        return $this->name;
    }

    /**
     * Normalizes the quality option value
     *
     * @return false|int
     */
    public function quality()
    {
        $value = $this->attributes['quality'] ?? false;

        if ($value === false || $value === true) {
            return false;
        }

        return (int)$value;
    }

    /**
     * Sanitizes the file extension.
     * The extension will be converted
     * to lowercase and `jpeg` will be
     * replaced with `jpg`
     *
     * @param string $extension
     * @return string
     */
    protected function sanitizeExtension(string $extension): string
    {
        $extension = strtolower($extension);
        $extension = str_replace('jpeg', 'jpg', $extension);
        return $extension;
    }

    /**
     * Sanitizes the name with Kirby's
     * Str::slug function
     *
     * @param string $name
     * @return string
     */
    protected function sanitizeName(string $name): string
    {
        return Str::slug($name);
    }

    /**
     * Returns the converted filename as string
     *
     * @return string
     */
    public function toString(): string
    {
        return Str::template($this->template, [
            'name'       => $this->name(),
            'attributes' => $this->attributesToString('-'),
            'extension'  => $this->extension()
        ], ['fallback' => '']);
    }
}
