<?php

namespace Ignite\Core\Models\Form;

use Closure;
use Exception;
use Ignite\Core\Models\Source\Manager as SourceManager;
use Illuminate\Contracts\Encryption\DecryptException;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Http\Request;
use Illuminate\Support\Arr;

class SchemaFieldDecorator
{
    /**
     * @var array|Model
     */
    protected $model;

    /**
     * @var Request
     */
    protected $request;

    /**
     * SchemaFieldDecorator constructor.
     *
     * @param $model
     * @param Request $request
     */
    public function __construct($model, Request $request)
    {
        $this->model = $model;
        $this->request = $request;
    }

    /**
     * Decorate the field data for use in the form.
     *
     * @param  array $field
     *
     * @return array
     */
    public function decorate(array $field)
    {
        $name = Arr::get($field, 'name');

        if (in_array($field['frontend_type'], ['toggle', 'checkbox'])) {
            $value = Arr::get($field, 'value', null);
        } else {
            $value = $this->getValueFromAvailableSource($name);
        }

        Arr::set($field, 'label', __($field['label']));
        Arr::set($field, 'label_attr', ['class' => 'control-label col-sm-3']);
        Arr::set($field, 'form_horizontal', true);
        Arr::set($field, 'error_messages', collect(Arr::get($field, 'error_messages', []))->map(function ($message) {
            return __($message);
        })->toArray());

        if (Arr::has($field, 'choices')) {
            Arr::set($field, 'choices', $this->convertChoices($field['choices']));
            Arr::set($field, 'selected', $value ?? Arr::get($field, 'selected'));
        }

        Arr::set($field, 'attr.data-initial', !isset($value) || is_scalar($value) ? $value : json_encode($value));
        Arr::set($field, 'value', $value);

        // Should be the last decorator
        if (Arr::has($field, 'sensitive')) {
            Arr::set($field, 'value', $this->getEncryptValueClosure());
        }

        return $field;
    }

    /**
     * Try to get a value for the given field from one of the available sources.
     *
     * @param string $name
     * @param mixed $default
     *
     * @return mixed
     */
    public function getValueFromAvailableSource(string $name, $default = null)
    {
        if (! empty($old = $this->request->old($name))) {
            return $old;
        }

        if (! is_null($this->model) && isset($this->model[$name])) {
            return $this->model[$name];
        }

        return $this->request->get($name, $default);
    }

    /**
     * The closure to pass when encrypting form value for a sensitive field.
     *
     * @return Closure
     */
    public function getEncryptValueClosure()
    {
        return Closure::fromCallable([$this, 'encryptValue']);
    }

    /**
     * Encrypt a value before displaying it in the form.
     *
     * $characterCount default needs to be 9 so nine asterisks can pass the
     * format validation for SSN.
     *
     * @param string $value
     * @param string $default
     * @param int $characterCount
     *
     * @return string
     */
    public function encryptValue($value, $default = '', $characterCount = 9)
    {
        $value = trim($value ?? '');

        if (empty($value)) {
            return $default;
        }

        try {
            $value = decrypt($value);
            return str_repeat('*', strlen($value));
        } catch (DecryptException $e) {
            return str_repeat('*', $characterCount);
        }
    }

    /**
     * Convert the provided choice sources.
     *
     * @param  array|string $options
     *
     * @return array
     */
    protected function convertChoices($options)
    {
        try {
            $model = $this->model instanceof Model ? $this->model : null;
            return app(SourceManager::class)->interpolate($options, $model);
        } catch (Exception $exception) {
            logger()->error($exception->getMessage(), compact('exception'));
            return [];
        }
    }
}
