<?php

declare(strict_types=1);

namespace Arcanedev\Html;

use Arcanedev\Html\Contracts\Html as HtmlContract;
use Arcanedev\Html\Elements\A;
use Arcanedev\Html\Elements\Button;
use Arcanedev\Html\Elements\Div;
use Arcanedev\Html\Elements\Dl;
use Arcanedev\Html\Elements\Element;
use Arcanedev\Html\Elements\Fieldset;
use Arcanedev\Html\Elements\File;
use Arcanedev\Html\Elements\Form;
use Arcanedev\Html\Elements\I;
use Arcanedev\Html\Elements\Img;
use Arcanedev\Html\Elements\Input;
use Arcanedev\Html\Elements\Label;
use Arcanedev\Html\Elements\Legend;
use Arcanedev\Html\Elements\Option;
use Arcanedev\Html\Elements\Select;
use Arcanedev\Html\Elements\Span;
use Arcanedev\Html\Elements\Textarea;
use Arcanedev\Html\Elements\Ul;
use Arcanedev\Html\Entities\Attributes\ClassAttribute;
use DateTimeImmutable;
use Illuminate\Support\Str;

/**
 * Class     Html
 *
 * @author   ARCANEDEV <arcanedev.maroc@gmail.com>
 */
class Html implements HtmlContract
{
    /* -----------------------------------------------------------------
     |  Constants
     | -----------------------------------------------------------------
     */

    const HTML_DATE_FORMAT = 'Y-m-d';
    const HTML_TIME_FORMAT = 'H:i:s';

    /* -----------------------------------------------------------------
     |  Main Methods
     | -----------------------------------------------------------------
     */

    /**
     * Make an `a` tag.
     *
     * @param  string|null  $href
     * @param  string|null  $value
     *
     * @return \Arcanedev\Html\Elements\A
     */
    public function a($href = null, $value = null)
    {
        return A::make()
                ->attributeIfNotNull($href, 'href', $href)
                ->html($value);
    }

    /**
     * Make a `button` tag.
     *
     * @param  string|null  $content
     * @param  string|null  $type
     *
     * @return \Arcanedev\Html\Elements\Button
     */
    public function button($content = null, $type = null)
    {
        return Button::make()
                     ->attributeIfNotNull($type, 'type', $type)
                     ->html($content);
    }

    /**
     * Make a checkbox input.
     *
     * @param  string|null  $name
     * @param  bool|null    $checked
     * @param  string|null  $value
     *
     * @return \Arcanedev\Html\Elements\Input
     */
    public function checkbox($name = null, $checked = null, $value = '1')
    {
        return Input::make()
                    ->attribute('type', 'checkbox')
                    ->attributeIfNotNull($name, 'name', $name)
                    ->attributeIfNotNull($name, 'id', $name)
                    ->attributeIfNotNull($value, 'value', $value)
                    ->attributeIf((bool) $checked, 'checked');
    }

    /**
     * Parse and render `class` attribute.
     *
     * @param  iterable|string  $classes
     *
     * @return string
     */
    public function class($classes): string
    {
        return ClassAttribute::make($classes)->render();
    }

    /**
     * Make a date input.
     *
     * @param  string|null  $name
     * @param  string|null  $value
     * @param  bool         $format
     *
     * @return \Arcanedev\Html\Elements\Input
     */
    public function date($name = null, $value = null, bool $format = true)
    {
        $input = $this->input('date', $name, $value);

        if (
            ! $format ||
            ! $input->hasAttribute('value') ||
            empty($value = $input->getAttribute('value')->value())
        ) {
            return $input;
        }

        return $input->value(static::formatDateTime($value, static::HTML_DATE_FORMAT));;
    }

    /**
     * Make a datetime input.
     *
     * @param  string|null  $name
     * @param  string|null  $value
     * @param  bool         $format
     *
     * @return \Arcanedev\Html\Elements\Input
     */
    public function datetime($name = null, $value = null, $format = true)
    {
        $input = $this->input('datetime-local', $name, $value);

        if (
            ! $format ||
            ! $input->hasAttribute('value') ||
            empty($value = $input->getAttribute('value')->value())
        ) {
            return $input;
        }

        return $input->value(
            $this->formatDateTime($value, static::HTML_DATE_FORMAT.'\T'.static::HTML_TIME_FORMAT)
        );
    }

    /**
     * Make a div element.
     *
     * @param  \Arcanedev\Html\Elements\HtmlElement|string|null  $content
     *
     * @return \Arcanedev\Html\Elements\Div
     */
    public function div($content = null)
    {
        return Div::make()->addChild($content);
    }

    /**
     * Make a custom tag element.
     *
     * @param  string  $tag
     *
     * @return \Arcanedev\Html\Elements\Element
     */
    public function element($tag)
    {
        return Element::withTag($tag);
    }

    /**
     * Make an email input.
     *
     * @param  string|null  $name
     * @param  string|null  $value
     *
     * @return \Arcanedev\Html\Elements\Input
     */
    public function email($name = null, $value = null)
    {
        return $this->input('email', $name, $value);
    }

    /**
     * Make a fieldset tag.
     *
     * @param  \Arcanedev\Html\Elements\HtmlElement|string|null  $legend
     *
     * @return \Arcanedev\Html\Elements\Fieldset
     */
    public function fieldset($legend = null)
    {
        return is_null($legend)
            ? Fieldset::make()
            : Fieldset::make()->legend($legend);
    }

    /**
     * Make a file input.
     *
     * @param  string|null  $name
     *
     * @return \Arcanedev\Html\Elements\File
     */
    public function file($name = null)
    {
        return File::make()
                   ->attributeIfNotNull($name, 'name', $name)
                   ->attributeIfNotNull($name, 'id', $name);
    }

    /**
     * Make a form input.
     *
     * @param  string       $method
     * @param  string|null  $action
     *
     * @return \Arcanedev\Html\Elements\Form
     */
    public function form($method = 'POST', $action = null)
    {
        return Form::make()
                   ->method($method)
                   ->attributeIfNotNull($action, 'action', $action);
    }

    /**
     * Make a hidden input.
     *
     * @param  string|null  $name
     * @param  string|null  $value
     *
     * @return \Arcanedev\Html\Elements\Input
     */
    public function hidden($name = null, $value = null)
    {
        return $this->input('hidden', $name, $value);
    }

    /**
     * Make an i tag.
     *
     * @param  string|null  $content
     *
     * @return \Arcanedev\Html\Elements\I
     */
    public function i($content = null)
    {
        return I::make()->html($content);
    }

    /**
     * Make an input tag.
     *
     * @param  string|null  $type
     * @param  string|null  $name
     * @param  string|null  $value
     *
     * @return \Arcanedev\Html\Elements\Input
     */
    public function input($type = null, $name = null, $value = null)
    {
        $hasValue = $name && ! is_null($value) && $type !== 'password';

        return Input::make()
                    ->attributeIfNotNull($type, 'type', $type)
                    ->attributeIfNotNull($name, 'name', $name)
                    ->attributeIfNotNull($name, 'id', $name)
                    ->attributeIf($hasValue, 'value', $value);
    }

    /**
     * Make an image tag.
     *
     * @param  string|null  $src
     * @param  string|null  $alt
     *
     * @return \Arcanedev\Html\Elements\Img
     */
    public function img($src = null, $alt = null)
    {
        return Img::make()
                  ->attributeIfNotNull($src, 'src', $src)
                  ->attributeIfNotNull($alt, 'alt', $alt);
    }

    /**
     * Make a label tag.
     *
     * @param  \Arcanedev\Html\Elements\HtmlElement|iterable|string|null  $content
     * @param  string|null                                                $for
     *
     * @return \Arcanedev\Html\Elements\Label
     */
    public function label($content = null, $for = null)
    {
        return Label::make()
                    ->attributeIfNotNull($for, 'for', $for)
                    ->children($content);
    }

    /**
     * Make a legend tag.
     *
     * @param  \Arcanedev\Html\Elements\HtmlElement|string|null  $content
     *
     * @return \Arcanedev\Html\Elements\Legend
     */
    public function legend($content = null)
    {
        return Legend::make()->html($content);
    }

    /**
     * Make a mailto link.
     *
     * @param  string       $email
     * @param  string|null  $content
     *
     * @return \Arcanedev\Html\Elements\A
     */
    public function mailto($email, $content = null)
    {
        return $this->a("mailto:{$email}", $content ?: $email);
    }

    /**
     * Make an option tag.
     *
     * @param  string|null  $text
     * @param  string|null  $value
     * @param  bool         $selected
     *
     * @return \Arcanedev\Html\Elements\Option
     */
    public function option($text = null, $value = null, $selected = false)
    {
        return Option::make()
                     ->text($text)
                     ->value($value)
                     ->selectedIf($selected);
    }

    /**
     * Make a password input.
     *
     * @param  string|null  $name
     *
     * @return \Arcanedev\Html\Elements\Input
     */
    public function password($name = null)
    {
        return $this->input('password', $name);
    }

    /**
     * Make a radio input.
     *
     * @param  string|null  $name
     * @param  bool|null    $checked
     * @param  string|null  $value
     *
     * @return \Arcanedev\Html\Elements\Input
     */
    public function radio($name = null, $checked = null, $value = null)
    {
        return $this->input('radio', $name, $value)
                    ->attributeIfNotNull($name, 'id', $value === null ? $name : ($name.'_'.Str::slug($value)))
                    ->attributeIf(( ! is_null($value)) || $checked, 'checked');
    }

    /**
     * Make a range input.
     *
     * @param  string|null  $name
     * @param  string|null  $value
     * @param  string|null  $min
     * @param  string|null  $max
     * @param  string|null  $step
     *
     * @return \Arcanedev\Html\Elements\Input
     */
    public function range($name = null, $value = null, $min = null, $max = null, $step = null)
    {
        return $this->input('range', $name, $value)
                    ->attributeIfNotNull($min, 'min', $min)
                    ->attributeIfNotNull($max, 'max', $max)
                    ->attributeIfNotNull($step, 'step', $step);
    }

    /**
     * Make a reset button.
     *
     * @param  string|null  $text
     *
     * @return \Arcanedev\Html\Elements\Button
     */
    public function reset($text = null)
    {
        return $this->button($text, 'reset');
    }

    /**
     * Make a select tag.
     *
     * @param  string|null           $name
     * @param  array|iterable        $options
     * @param  string|iterable|null  $value
     *
     * @return \Arcanedev\Html\Elements\Select
     */
    public function select($name = null, $options = [], $value = null)
    {
        return Select::make()
                     ->attributeIfNotNull($name, 'name', $name)
                     ->attributeIfNotNull($name, 'id', $name)
                     ->options($options)
                     ->value($value);
    }

    /**
     * Make a span tag.
     *
     * @param  \Arcanedev\Html\Elements\HtmlElement|string|null  $content
     *
     * @return \Arcanedev\Html\Elements\Span
     */
    public function span($content = null)
    {
        return Span::make()->children($content);
    }

    /**
     * Make a submit button.
     *
     * @param  string|null  $text
     *
     * @return \Arcanedev\Html\Elements\Button
     */
    public function submit($text = null)
    {
        return $this->button($text, 'submit');
    }

    /**
     * Make a tel link.
     *
     * @param  string       $phoneNumber
     * @param  string|null  $text
     *
     * @return \Arcanedev\Html\Elements\A
     */
    public function telLink($phoneNumber, $text = null)
    {
        return $this->a("tel:{$phoneNumber}", $text ?: $phoneNumber);
    }

    /**
     * Make a text input.
     *
     * @param  string       $name
     * @param  string|null  $value
     *
     * @return \Arcanedev\Html\Elements\Input
     */
    public function text($name, $value = null)
    {
        return $this->input('text', $name, $value);
    }

    /**
     * Make a textarea tag.
     *
     * @param  string|null  $name
     * @param  string|null  $value
     *
     * @return \Arcanedev\Html\Elements\Textarea
     */
    public function textarea($name = null, $value = null)
    {
        return Textarea::make()
                       ->attributeIfNotNull($name, 'name', $name)
                       ->attributeIfNotNull($name, 'id', $name)
                       ->value($value);
    }

    /**
     * Make a time input.
     *
     * @param  string|null  $name
     * @param  string|null  $value
     * @param  bool         $format
     *
     * @return \Arcanedev\Html\Elements\Input
     */
    public function time($name = null, $value = null, $format = true)
    {
        $input = $this->input('time', $name, $value);

        return $format
            && $input->hasAttribute('value')
            && ! empty($value = $input->getAttribute('value')->value())
            ? $input->value(static::formatDateTime($value, self::HTML_TIME_FORMAT))
            : $input;
    }

    /**
     * Make a number input.
     *
     * @param  string|null            $name
     * @param  string|null            $value
     * @param  string|float|int|null  $min
     * @param  string|float|int|null  $max
     * @param  string|float|int|null  $step
     *
     * @return \Arcanedev\Html\Elements\Input
     */
    public function number($name = null, $value = null, $min = null, $max = null, $step = null)
    {
        return $this->input('number', $name, $value)
                    ->attributeIfNotNull($min, 'min', $min)
                    ->attributeIfNotNull($max, 'max', $max)
                    ->attributeIfNotNull($step, 'step', $step);
    }

    /**
     * Make an ordered list.
     *
     * @param  array  $attributes
     *
     * @return \Arcanedev\Html\Elements\Ol
     */
    public function ol(array $attributes = [])
    {
        return Elements\Ol::make()->attributes($attributes);
    }

    /**
     * Make an unordered list.
     *
     * @param  array  $attributes
     *
     * @return \Arcanedev\Html\Elements\Ul
     */
    public function ul(array $attributes = [])
    {
        return Ul::make()->attributes($attributes);
    }

    /**
     * Make a description list.
     *
     * @param  array  $attributes
     *
     * @return \Arcanedev\Html\Elements\Dl
     */
    public function dl(array $attributes = [])
    {
        return Dl::make()->attributes($attributes);
    }

    /* -----------------------------------------------------------------
     |  Other Methods
     | -----------------------------------------------------------------
     */

    /**
     * Format the date/time value.
     *
     * @param  string  $value
     * @param  string  $format
     *
     * @return string
     */
    protected static function formatDateTime(string $value, string $format): string
    {
        try {
            if ( ! empty($value))
                $value = (new DateTimeImmutable($value))->format($format);
        }
        catch (\Exception $e) {
            // Do nothing...
        }

        return $value;
    }
}
