<?php

declare(strict_types=1);

namespace App\Models;

use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\SoftDeletes;

class World extends Model
{
    use HasFactory;
    use SoftDeletes;

    public const STATUS_DRAFT = 'draft';
    public const STATUS_PUBLISHED = 'published';
    public const STATUS_ARCHIVED = 'archived';

    public const TYPE_SEASONAL = 'seasonal';
    public const TYPE_EVENT = 'event';
    public const TYPE_CAMPAIGN = 'campaign';
    public const TYPE_TRIBUTE = 'tribute';

    public const PARTICIPATION_MODE_MANUAL_APPROVAL = 'manual_approval';
    public const PARTICIPATION_MODE_AUTO_ADD = 'auto_add';
    public const PARTICIPATION_MODE_CLOSED = 'closed';

    protected $fillable = [
        'title',
        'slug',
        'tagline',
        'summary',
        'description',
        'cover_path',
        'theme_key',
        'accent_color',
        'accent_color_secondary',
        'background_motif',
        'icon_name',
        'status',
        'type',
        'starts_at',
        'ends_at',
        'submission_starts_at',
        'submission_ends_at',
        'is_featured',
        'accepts_submissions',
        'participation_mode',
        'submission_note_enabled',
        'community_section_enabled',
        'allow_readd_after_removal',
        'is_recurring',
        'recurrence_key',
        'recurrence_rule',
        'edition_year',
        'cta_label',
        'cta_url',
        'badge_label',
        'badge_description',
        'submission_guidelines',
        'badge_url',
        'seo_title',
        'seo_description',
        'og_image_path',
        'related_tags_json',
        'section_order_json',
        'section_visibility_json',
        'parent_world_id',
        'created_by_user_id',
        'published_at',
    ];

    protected $casts = [
        'starts_at' => 'datetime',
        'ends_at' => 'datetime',
        'submission_starts_at' => 'datetime',
        'submission_ends_at' => 'datetime',
        'published_at' => 'datetime',
        'is_featured' => 'boolean',
        'accepts_submissions' => 'boolean',
        'allow_readd_after_removal' => 'boolean',
        'submission_note_enabled' => 'boolean',
        'community_section_enabled' => 'boolean',
        'is_recurring' => 'boolean',
        'edition_year' => 'integer',
        'related_tags_json' => 'array',
        'section_order_json' => 'array',
        'section_visibility_json' => 'array',
    ];

    public function createdBy(): BelongsTo
    {
        return $this->belongsTo(User::class, 'created_by_user_id');
    }

    public function parentWorld(): BelongsTo
    {
        return $this->belongsTo(self::class, 'parent_world_id');
    }

    public function archiveEditions(): HasMany
    {
        return $this->hasMany(self::class, 'parent_world_id')->orderByDesc('edition_year')->orderByDesc('starts_at');
    }

    public function worldRelations(): HasMany
    {
        return $this->hasMany(WorldRelation::class)->orderBy('section_key')->orderBy('sort_order')->orderBy('id');
    }

    public function worldSubmissions(): HasMany
    {
        return $this->hasMany(WorldSubmission::class)->orderByDesc('reviewed_at')->orderByDesc('created_at');
    }

    public function scopePublished(Builder $query): Builder
    {
        return $query
            ->where('status', self::STATUS_PUBLISHED)
            ->where(function (Builder $builder): void {
                $builder->whereNull('published_at')
                    ->orWhere('published_at', '<=', now());
            });
    }

    public function scopePubliclyVisible(Builder $query): Builder
    {
        return $query
            ->whereIn('status', [self::STATUS_PUBLISHED, self::STATUS_ARCHIVED])
            ->where(function (Builder $builder): void {
                $builder->whereNull('published_at')
                    ->orWhere('published_at', '<=', now());
            });
    }

    public function scopeCurrent(Builder $query): Builder
    {
        return $query
            ->published()
            ->where(function (Builder $builder): void {
                $builder->whereNull('starts_at')
                    ->orWhere('starts_at', '<=', now());
            })
            ->where(function (Builder $builder): void {
                $builder->whereNull('ends_at')
                    ->orWhere('ends_at', '>=', now());
            });
    }

    public function scopeUpcoming(Builder $query): Builder
    {
        return $query
            ->published()
            ->whereNotNull('starts_at')
            ->where('starts_at', '>', now());
    }

    public function scopeArchive(Builder $query): Builder
    {
        return $query
            ->publiclyVisible()
            ->where(function (Builder $builder): void {
                $builder->where('status', self::STATUS_ARCHIVED)
                    ->orWhere(function (Builder $expired): void {
                        $expired->whereNotNull('ends_at')
                            ->where('ends_at', '<', now());
                    });
            });
    }

    public function isPubliclyVisible(): bool
    {
        if (! in_array($this->status, [self::STATUS_PUBLISHED, self::STATUS_ARCHIVED], true)) {
            return false;
        }

        if ($this->published_at && $this->published_at->isFuture()) {
            return false;
        }

        return true;
    }

    public function isCurrent(): bool
    {
        if (! $this->isPubliclyVisible()) {
            return false;
        }

        if ($this->starts_at && $this->starts_at->isFuture()) {
            return false;
        }

        if ($this->ends_at && $this->ends_at->isPast()) {
            return false;
        }

        return true;
    }

    public function isAcceptingSubmissions(): bool
    {
        if (! $this->isPubliclyVisible() || ! $this->accepts_submissions || ! $this->allowsCreatorParticipation()) {
            return false;
        }

        if ($this->submission_starts_at && $this->submission_starts_at->isFuture()) {
            return false;
        }

        if ($this->submission_ends_at && $this->submission_ends_at->isPast()) {
            return false;
        }

        return true;
    }

    public function allowsCreatorParticipation(): bool
    {
        return in_array((string) $this->participation_mode, [
            self::PARTICIPATION_MODE_MANUAL_APPROVAL,
            self::PARTICIPATION_MODE_AUTO_ADD,
        ], true);
    }

    public function submissionStartsAsLive(): bool
    {
        return (string) $this->participation_mode === self::PARTICIPATION_MODE_AUTO_ADD;
    }

    public function coverUrl(): ?string
    {
        $path = trim((string) $this->cover_path);

        if ($path === '') {
            return null;
        }

        if (str_starts_with($path, 'http://') || str_starts_with($path, 'https://')) {
            return $path;
        }

        return rtrim((string) config('cdn.files_url', 'https://files.skinbase.org'), '/') . '/' . ltrim($path, '/');
    }

    public function ogImageUrl(): ?string
    {
        $path = trim((string) ($this->og_image_path ?: $this->cover_path ?: ''));

        if ($path === '') {
            return null;
        }

        if (str_starts_with($path, 'http://') || str_starts_with($path, 'https://')) {
            return $path;
        }

        return rtrim((string) config('cdn.files_url', 'https://files.skinbase.org'), '/') . '/' . ltrim($path, '/');
    }

    public function publicUrl(): string
    {
        return route('worlds.show', ['world' => $this->slug]);
    }

    public function sectionOrder(): array
    {
        $defaults = array_values(array_filter(config('worlds.default_section_order', []), 'is_string'));
        $custom = array_values(array_filter($this->section_order_json ?? [], 'is_string'));

        return array_values(array_unique(array_merge($custom, $defaults)));
    }

    public function sectionVisibility(): array
    {
        $defaults = collect(array_keys((array) config('worlds.sections', [])))
            ->mapWithKeys(fn (string $key): array => [$key => true])
            ->all();

        $custom = collect((array) $this->section_visibility_json)
            ->mapWithKeys(fn ($value, $key): array => [(string) $key => (bool) $value])
            ->all();

        return array_merge($defaults, $custom);
    }
}