<?php

namespace Ignite\Core\Http\Responses;

use Illuminate\Contracts\Support\Arrayable;
use Illuminate\Contracts\Support\Jsonable;
use Illuminate\Contracts\Support\Responsable;
use Illuminate\Support\Arr;
use JsonSerializable;

/**
 * Class JsonResponse
 *
 * Usage:
 *  $response = JsonResponse::success('The resource was updated', 201, [
 *    'data' => $model->toArray()
 *  ]);
 */
final class JsonResponse implements Responsable, Arrayable, Jsonable, JsonSerializable
{
    public const STATUS_SUCCESS = 'success';
    public const STATUS_FAILURE = 'failure';

    /**
     * The status to respond.
     *
     * @var string
     */
    private $status;

    /**
     * The message to respond.
     *
     * @var string
     */
    private $message;

    /**
     * The status code to respond.
     *
     * @var int
     */
    private $code;

    /**
     * The associated data or errors.
     *
     * @var array
     */
    private $context;

    /**
     * JsonResponse constructor.
     *
     * @param string $status
     * @param string $message
     * @param int $code
     * @param array $context
     */
    public function __construct($status, $message, $code, $context = [])
    {
        $this->status = $status;
        $this->message = $message;
        $this->code = $code;
        $this->context = $context;
    }

    /**
     * Create a success response.
     *
     * @param  string $message
     * @param  int $code
     * @param  array$context
     * @return self
     */
    public static function success($message, $code = 200, $context = null)
    {
        return app(static::class, [
            'status' => static::STATUS_SUCCESS,
            'message' => $message,
            'code' => $code,
            'context' => $context
        ]);
    }

    /**
     * Create a failure response.
     *
     * @param  string $message
     * @param  int $code
     * @param  array $context
     * @return self
     */
    public static function failure($message, $code = 422, $context = null)
    {
        return app(static::class, [
            'status' => static::STATUS_FAILURE,
            'message' => $message,
            'code' => $code,
            'context' => $context
        ]);
    }

    /**
     * The response status.
     *
     * @return string
     */
    public function getStatus()
    {
        return $this->status;
    }

    /**
     * The response message.
     *
     * @return string
     */
    public function getMessage()
    {
        return $this->message;
    }

    /**
     * The response code.
     *
     * @return int
     */
    public function getCode()
    {
        return $this->code;
    }

    /**
     * The response context.
     *
     * @return array
     */
    public function getContext()
    {
        return $this->context;
    }

    /**
     * Get the instance as an array.
     *
     * @return array
     */
    public function toArray()
    {
        return [
            'status' => $this->status,
            'message' => $this->message,
            'context' => Arr::forget($this->context, [
                'headers', 'options'
            ]),
        ];
    }

    /**
     * Convert the object to its JSON representation.
     *
     * @param  int $options
     *
     * @return string
     */
    public function toJson($options = 0)
    {
        return json_encode($this->toArray(), $options);
    }

    /**
     * Specify data which should be serialized to JSON
     * @link http://php.net/manual/en/jsonserializable.jsonserialize.php
     * @return mixed data which can be serialized by <b>json_encode</b>,
     * which is a value of any type other than a resource.
     * @since 5.4.0
     */
    public function jsonSerialize()
    {
        return $this->toArray();
    }

    /**
     * Create an HTTP response that represents the object.
     *
     * @param  \Illuminate\Http\Request $request
     * @return \Illuminate\Http\Response
     */
    public function toResponse($request)
    {
        $headers = $this->context['headers'] ?? [];
        $options = $this->context['options'] ?? 0;

        return app(\Illuminate\Http\JsonResponse::class, [
            'data' => $this->toArray(),
            'status' => $this->code,
            'headers' => $headers,
            'options' => $options,
        ]);
    }
}
