<?php

namespace App\Support;

use Illuminate\Support\HtmlString;

class FormBuilder
{
    protected $model;

    public function model($model, $options = [])
    {
        $this->model = $model;
        return $this->open($options ?? []);
    }

    public function open($options = [])
    {
        $options = $options ?? [];
        $method = strtoupper($options['method'] ?? 'POST');
        $action = $options['url'] ?? ($options['action'] ?? '');
        $files = isset($options['files']) && $options['files'] ? ' enctype="multipart/form-data"' : '';
        $class = isset($options['class']) ? ' class="' . e($options['class']) . '"' : '';
        $id = isset($options['id']) ? ' id="' . e($options['id']) . '"' : '';

        $html = '<form method="' . ($method === 'GET' ? 'GET' : 'POST') . '" action="' . e($action) . '"' . $files . $class . $id . '>';
        $html .= csrf_field();

        if (!in_array($method, ['GET', 'POST'])) {
            $html .= method_field($method);
        }

        return new HtmlString($html);
    }

    public function close()
    {
        $this->model = null;
        return new HtmlString('</form>');
    }

    public function token()
    {
        return csrf_field();
    }

    public function label($name, $value = null, $options = [], $escapeHtml = true)
    {
        $options = $options ?? [];
        $value = $value ?? ucfirst(str_replace('_', ' ', $name));
        $for = ' for="' . e($name) . '"';
        $attributes = $this->attributes($options);

        return new HtmlString('<label' . $for . $attributes . '>' . ($escapeHtml ? e($value) : $value) . '</label>');
    }

    public function text($name, $value = null, $options = [])
    {
        return $this->input('text', $name, $value, $options ?? []);
    }

    public function email($name, $value = null, $options = [])
    {
        return $this->input('email', $name, $value, $options ?? []);
    }

    public function password($name, $options = [])
    {
        return $this->input('password', $name, null, $options ?? []);
    }

    public function hidden($name, $value = null, $options = [])
    {
        return $this->input('hidden', $name, $value, $options ?? []);
    }

    public function number($name, $value = null, $options = [])
    {
        return $this->input('number', $name, $value, $options ?? []);
    }

    public function file($name, $options = [])
    {
        return $this->input('file', $name, null, $options ?? []);
    }

    public function textarea($name, $value = null, $options = [])
    {
        $options = $options ?? [];
        $value = $this->getValueAttribute($name, $value);
        $attributes = $this->attributes(array_merge(['name' => $name, 'id' => $name], $options));

        return new HtmlString('<textarea' . $attributes . '>' . e($value ?? '') . '</textarea>');
    }

    public function select($name, $list = [], $selected = null, $options = [], $optionsAttributes = [], $optgroupsAttributes = [])
    {
        $options = $options ?? [];
        $list = $list ?? [];
        $selected = $this->getValueAttribute($name, $selected);
        $attributes = $this->attributes(array_merge(['name' => $name, 'id' => $name], $options));

        $html = '<select' . $attributes . '>';

        foreach ($list as $key => $value) {
            if (is_array($value)) {
                $html .= '<optgroup label="' . e($key) . '">';
                foreach ($value as $optKey => $optValue) {
                    $html .= $this->option($optKey, $optValue, $selected);
                }
                $html .= '</optgroup>';
            } else {
                $html .= $this->option($key, $value, $selected);
            }
        }

        $html .= '</select>';

        return new HtmlString($html);
    }

    protected function option($key, $value, $selected)
    {
        $isSelected = $this->isSelected($key, $selected) ? ' selected' : '';
        return '<option value="' . e($key) . '"' . $isSelected . '>' . e($value) . '</option>';
    }

    protected function isSelected($key, $selected)
    {
        if (is_array($selected)) {
            return in_array($key, $selected);
        }
        return (string) $key === (string) $selected;
    }

    public function checkbox($name, $value = 1, $checked = null, $options = [])
    {
        $options = $options ?? [];
        $checked = $this->getCheckedState($name, $value, $checked) ? ' checked' : '';
        $attributes = $this->attributes(array_merge([
            'name' => $name,
            'id' => $options['id'] ?? $name,
            'value' => $value,
            'type' => 'checkbox'
        ], $options));

        return new HtmlString('<input' . $attributes . $checked . '>');
    }

    public function radio($name, $value = null, $checked = null, $options = [])
    {
        $options = $options ?? [];
        $checked = $this->getCheckedState($name, $value, $checked) ? ' checked' : '';
        $attributes = $this->attributes(array_merge([
            'name' => $name,
            'id' => $options['id'] ?? $name . '_' . $value,
            'value' => $value,
            'type' => 'radio'
        ], $options));

        return new HtmlString('<input' . $attributes . $checked . '>');
    }

    public function submit($value = null, $options = [])
    {
        return $this->input('submit', null, $value, $options ?? []);
    }

    public function button($value = null, $options = [])
    {
        $options = $options ?? [];
        $attributes = $this->attributes(array_merge(['type' => 'button'], $options));
        return new HtmlString('<button' . $attributes . '>' . e($value ?? '') . '</button>');
    }

    public function input($type, $name, $value = null, $options = [])
    {
        $options = $options ?? [];

        if ($type !== 'password' && $type !== 'file') {
            $value = $this->getValueAttribute($name, $value);
        }

        $attributes = $this->attributes(array_merge([
            'type' => $type,
            'name' => $name,
            'id' => $options['id'] ?? $name,
            'value' => $value
        ], $options));

        return new HtmlString('<input' . $attributes . '>');
    }

    public function date($name, $value = null, $options = [])
    {
        return $this->input('date', $name, $value, $options ?? []);
    }

    public function time($name, $value = null, $options = [])
    {
        return $this->input('time', $name, $value, $options ?? []);
    }

    public function datetime($name, $value = null, $options = [])
    {
        return $this->input('datetime-local', $name, $value, $options ?? []);
    }

    protected function getValueAttribute($name, $value = null)
    {
        if (is_null($name)) {
            return $value;
        }

        $old = old($name);
        if (!is_null($old)) {
            return $old;
        }

        if (!is_null($value)) {
            return $value;
        }

        if ($this->model && isset($this->model->{$name})) {
            return $this->model->{$name};
        }

        return null;
    }

    protected function getCheckedState($name, $value, $checked)
    {
        $old = old($name);
        if (!is_null($old)) {
            return $old == $value;
        }

        if (!is_null($checked)) {
            return $checked;
        }

        if ($this->model && isset($this->model->{$name})) {
            return $this->model->{$name} == $value;
        }

        return false;
    }

    protected function attributes($attributes)
    {
        $attributes = $attributes ?? [];
        $html = '';
        foreach ($attributes as $key => $value) {
            if (is_null($value)) {
                continue;
            }
            if (is_numeric($key)) {
                $html .= ' ' . $value;
            } else {
                $html .= ' ' . $key . '="' . e($value) . '"';
            }
        }
        return $html;
    }
}
