<?php

namespace Ignite\Core\Entities;

use ArrayAccess;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;
use Ignite\Core\Traits\AttributeToggle;
use Ignite\Core\Facades\Program;

class Page extends Model
{
    use AttributeToggle;

    const ACTIVE   = 'active';
    const INACTIVE = 'inactive';

    const MENU_VISIBLE   = 1;
    const MENU_INVISIBLE = 0;

    const PROGRAM_ANY = 'any';
    const PROGRAM_CASH = 'cash';
    const PROGRAM_MERCHANDISE = 'merchandise';

    /**
     * The database table name.
     * @var string
     */
    protected $table = 'core_page';

    /**
     * The fillable fields.
     * @var array
     */
    protected $fillable = [
        'locale', 'code', 'label', 'content', 'template',
        'created', 'modified', 'published_at',
        'status', 'display_in_menu', 'is_home', 'is_alias', 'position',
        'is_protected', 'allowed_groups', 'program_type',
        'meta_title', 'meta_keywords', 'meta_description',
    ];

    /**
     * An array of attribute names which can be toggled.
     * @var array
     */
    protected $toggleable = [
        'status', 'display_in_menu'
    ];

    /**
     * The accessors to append to the model's array form.
     *
     * @var array
     */
    protected $appends = [
        'published_ago'
    ];

    /**
     * Cast values coming from the database into PHP types.
     * @var array
     */
    protected $casts = [
        'is_home' => 'boolean',
        'is_alias' => 'boolean',
        'is_protected' => 'boolean',
        'display_in_menu' => 'boolean',
        'published_at' => 'datetime',
    ];

    /**
     * Allow programmatic overriding of the navigation menu canBeViewed check.
     * @var null|bool
     */
    protected $allowCanBeViewed;

    /**
     * The "booting" method of the model.
     *
     * @return void
     */
    public static function boot()
    {
        parent::boot();

        // When saving the homepage, exclude the code
        static::saving(function (Page $page) {
            if ($page->isHome()) {
                $page->attributes['code'] = "";
            }

            // Purify
            $purifier = app(\Ignite\Core\Models\Purifier::class);
            $page->attributes['content'] = $purifier->clean($page->attributes['content']);
        });

        // Add a default order scope
        static::addGlobalScope('order', function (Builder $builder) {
            $builder->orderBy(\DB::raw('CASE WHEN position is null THEN 1 ELSE 0 END'))
                ->orderBy('position', 'asc');
        });
    }

    /**
     * The url of the edit page.
     *
     * @return string
     */
    public function getEditUrl()
    {
        return url('/admin/cms/edit', ['id' => $this->getKey()]);
    }

    /**
     * The published at date in human readable form.
     *
     * @return string
     */
    public function getPublishedAgoAttribute()
    {
        return $this->attributes['published_ago'] = $this->published_at->diffForHumans();
    }

    // ----- Mutators

    /**
     * Set the allowed groups.
     *
     * @param mixed $value
     */
    public function setAllowedGroupsAttribute($value)
    {
        if (is_array($value) || $value instanceof ArrayAccess) {
            if (! in_array('1', $value)) {
                $value[] = '1';
            }
            $value = implode(';', $value);
        }

        // Always set the IT group as allowed.
        if (empty($value)) {
            $value = '1';
        }

        // Make sure there are no spaces.
        $value = trim($value);
        $value = preg_split('/[,;|\s]+/', trim($value));
        sort($value);
        $value = implode(';', $value);

        $this->attributes['allowed_groups'] = $value;
    }

    // ----- Scopes

    /**
     * Query for a URL code.
     *
     * @param  Builder $query
     * @param  string $code
     * @return Builder
     */
    public function scopeByCode(Builder $query, $code)
    {
        $query->where('code', $code);

        return $query;
    }

    /**
     * Query to find the page marked as home.
     *
     * @param  Builder $query
     * @return Builder
     */
    public function scopeIsHome(Builder $query)
    {
        $query->where('is_home', 1);

        return $query;
    }

    /**
     * Query for an active page only.
     *
     * @param  Builder $query
     * @return Builder
     */
    public function scopeOnlyActive(Builder $query)
    {
        $query->where('status', Page::ACTIVE);

        return $query;
    }

    /**
     * Query for a page visible in the menu.
     *
     * @param  Builder $query
     * @return Builder
     */
    public function scopeOnlyVisibleInMenu(Builder $query)
    {
        $query->where('display_in_menu', Page::MENU_VISIBLE);

        return $query;
    }

    /**
     * Query for a URL code.
     *
     * @param  Builder $query
     * @return Builder
     */
    public function scopeOnlyPublished(Builder $query)
    {
        $query->where('published_at', '<=', \Carbon\Carbon::now());

        return $query;
    }

    // -----

    /**
     * Switch status.
     *
     * @param  mixed $value
     * @return string
     */
    public function toggleStatus($value)
    {
        return strtolower($value) === static::ACTIVE
            ? static::INACTIVE
            : static::ACTIVE;
    }

    /**
     * Switch display in menu.
     *
     * @param  mixed $value
     * @return string
     */
    public function toggleDisplayInMenu($value)
    {
        return (int) $value === static::MENU_VISIBLE
            ? static::MENU_INVISIBLE
            : static::MENU_VISIBLE;
    }

    // -----

    /**
     * The url of the view page.
     *
     * @return string
     */
    public function getCode()
    {
        return $this->code;
    }

    /**
     * The url of the view page.
     *
     * @return string
     */
    public function getUrl()
    {
        return url('/' . $this->getCode());
    }

    /**
     * The page name label.
     *
     * @return string
     */
    public function getLabel()
    {
        return $this->label;
    }

    /**
     * Get the HTML content.
     *
     * @return string
     */
    public function getContent()
    {
        return $this->content;
    }

    /**
     * Determine if there is any content.
     *
     * @return bool
     */
    public function hasContent()
    {
        return ! empty(strip_tags($this->content));
    }

    /**
     * Is the page the homepage.
     *
     * @return bool
     */
    public function isHome()
    {
        return (bool) $this->is_home;
    }

    /**
     * Is the page an alias of a controller/route.
     *
     * @return bool
     */
    public function isAlias()
    {
        return (bool) $this->is_alias;
    }

    /**
     * Get the statuses.
     *
     * @return array
     */
    public function getStatuses()
    {
        return [
            static::INACTIVE => ucfirst(static::INACTIVE),
            static::ACTIVE   => ucfirst(static::ACTIVE),
        ];
    }

    /**
     * Is the page active.
     *
     * @return bool
     */
    public function isActive()
    {
        return $this->status === static::ACTIVE;
    }

    /**
     * Is the page inactive.
     *
     * @return bool
     */
    public function isInactive()
    {
        return $this->status === static::INACTIVE;
    }

    /**
     * Get the options for menu display on the frontend.
     *
     * @return array
     */
    public function getMenuDisplayOptions()
    {
        return [
            static::MENU_INVISIBLE => 'No',
            static::MENU_VISIBLE   => 'Yes',
        ];
    }

    /**
     * Get the options for program type.
     *
     * @return array
     */
    public function getProgramTypeOptions()
    {
        return [
            static::PROGRAM_ANY => 'Any',
            static::PROGRAM_CASH => 'Cash',
            static::PROGRAM_MERCHANDISE => 'Merchandise',
        ];
    }

    /**
     * Get the meta title.
     *
     * @return string
     */
    public function getMetaTitle()
    {
        $title = (string) $this->meta_title;

        if ($title) {
            return $title;
        }

        return $this->getLabel();
    }

    /**
     * Get the meta description.
     *
     * @return string
     */
    public function getMetaDescription()
    {
        return $this->meta_description;
    }

    /**
     * Get the meta keywords.
     *
     * @return string
     */
    public function getMetaKeywords()
    {
        $keywords = explode(',', $this->meta_keywords);
        $keywords = array_map('trim', $keywords);

        return implode(',', $keywords);
    }

    /**
     * The position of the page in the menu.
     *
     * @return int
     */
    public function getPosition()
    {
        return (int) $this->position;
    }


    /**
     * Should the page be displayed in the menu.
     *
     * @return bool
     */
    public function isDisplayedInMenu()
    {
        return !! $this->display_in_menu;
    }

    /**
     * Check if the page is protected from the public.
     *
     * @return bool
     */
    public function isProtected()
    {
        return $this->is_protected;
    }

    /**
     * Determine if the page should be display on the configured program type.
     *
     * @return bool
     */
    public function isConfiguredProgramType()
    {
        return $this->program_type === Program::getType();
    }

    /**
     * Determine if the page should be display on any program type.
     *
     * @return bool
     */
    public function isAnyProgramType()
    {
        return $this->program_type === static::PROGRAM_ANY;
    }

    /**
     * Can the page be viewed publicly.
     *
     * @return bool
     */
    public function canBeViewed()
    {
        // Allow programmatic override.
        if (! is_null($this->allowCanBeViewed)) {
            return $this->allowCanBeViewed;
        }

        if (! $this->isAnyProgramType() && ! $this->isConfiguredProgramType()) {
            return false;
        }

        if ($this->isProtected()) {
            if (! auth()->check()) {
                return false;
            }
            $groups = array_map(function ($group) {
                return (int) $group;
            }, explode(';', $this->allowed_groups));

            if (! empty($groups)) {
                return auth()->user()->hasGroup($groups);
            }
        }

        return true;
    }

    /**
     * Can the page not be viewed publicly.
     *
     * @return bool
     */
    public function cannotBeViewed()
    {
        return ! $this->canBeViewed();
    }

    /**
     * Check if the provided group ID is allowed to access to page.
     *
     * @param  int $groupId
     * @return bool
     */
    public function inAllowedGroups($groupId)
    {
        $groupIds = preg_split('/[,;|\s]+/', trim($this->allowed_groups));

        if (empty($groupIds)) {
            $groupIds = [];
        }

        return in_array($groupId, $groupIds);
    }

    /**
     * Convert the object into something JSON serializable.
     *
     * @return array
     */
    public function jsonSerialize()
    {
        $data = $this->toArray();

        unset($data['content']);

        return $data;
    }
}
