<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\{BelongsTo, HasMany, BelongsToMany, HasOne};
use Illuminate\Database\Eloquent\SoftDeletes;

class Category extends Model
{
    use SoftDeletes;

    protected $fillable = [
        'content_type_id','parent_id','name','slug',
        'description','image','is_active','sort_order'
    ];

    protected $casts = ['is_active' => 'boolean'];

    public function getNameAttribute(?string $value): ?string
    {
        return self::decodeHtmlEntities($value);
    }

    public function setNameAttribute(?string $value): void
    {
        $normalized = self::decodeHtmlEntities($value);
        $this->attributes['name'] = $normalized !== null ? trim($normalized) : null;
    }

    public function getDescriptionAttribute(?string $value): ?string
    {
        return self::decodeHtmlEntities($value);
    }

    public function setDescriptionAttribute(?string $value): void
    {
        $this->attributes['description'] = self::decodeHtmlEntities($value);
    }

    /**
     * Ensure slug is always lowercase and valid before saving.
     */
    protected static function boot()
    {
        parent::boot();

        static::saving(function (Category $model) {
            if (isset($model->slug)) {
                $model->slug = strtolower($model->slug);
                if (!preg_match('/^[a-z0-9-]+$/', $model->slug)) {
                    throw new \InvalidArgumentException('Category slug must be lowercase and contain only a-z, 0-9, and dashes.');
                }
            }
        });
    }

    public function contentType(): BelongsTo
    {
        return $this->belongsTo(ContentType::class);
    }

    public function parent(): BelongsTo
    {
        return $this->belongsTo(Category::class, 'parent_id');
    }

    public function children(): HasMany
    {
        return $this->hasMany(Category::class, 'parent_id')
            ->orderBy('sort_order')->orderBy('name');
    }

    public function descendants(): HasMany
    {
        return $this->children()->with('descendants');
    }

    public function seo(): HasOne
    {
        return $this->hasOne(CategorySeo::class);
    }

    public function artworks(): BelongsToMany
    {
        return $this->belongsToMany(Artwork::class, 'artwork_category');
    }

    public function scopeActive($query)
    {
        return $query->where('is_active', true);
    }

    public function scopeRoots($query)
    {
        return $query->whereNull('parent_id');
    }

    public function getFullSlugPathAttribute(): string
    {
        return $this->parent
            ? $this->parent->full_slug_path . '/' . $this->slug
            : $this->slug;
    }

    /**
     * Get the full public URL for this category (authoritative spec).
     * Example: /photography/abstract/dark
     */
    public function getUrlAttribute(): string
    {
        $contentTypeSlug = strtolower($this->contentType->slug);
        $path = strtolower($this->full_slug_path);
        return '/' . $contentTypeSlug . ($path ? '/' . $path : '');
    }

    /**
     * Get the canonical URL for SEO (authoritative spec).
     * Example: https://skinbase.org/photography/abstract/dark
     */
    public function getCanonicalUrlAttribute(): string
    {
        return 'https://skinbase.org' . $this->url;
    }

    public function getBreadcrumbsAttribute(): array
    {
        return $this->parent
            ? array_merge($this->parent->breadcrumbs, [$this])
            : [$this];
    }

    public function getRouteKeyName(): string
    {
        return 'slug';
    }

    /**
     * Resolve a category by a content-type slug and a category path (e.g. "audio/winamp").
     * This will locate the category with the final slug and verify its parent chain
     * matches the provided path and that the category belongs to the given content type.
     *
     * @param  string  $contentTypeSlug
     * @param  string|array  $categoryPath
     * @return Category|null
     */
    public static function findByPath(string $contentTypeSlug, $categoryPath): ?Category
    {
        $parts = is_array($categoryPath)
            ? array_values(array_map('strtolower', array_filter($categoryPath)))
            : array_values(array_map('strtolower', array_filter(explode('/', (string) $categoryPath))));

        if (empty($parts)) {
            return null;
        }

        $last = end($parts);

        $category = static::where('slug', $last)
            ->whereHas('contentType', function ($q) use ($contentTypeSlug) {
                $q->where('slug', strtolower($contentTypeSlug));
            })
            ->first();

        if (! $category) {
            return null;
        }

        // Verify parent chain matches the preceding parts in the path
        $idx = count($parts) - 2;
        $current = $category;
        while ($idx >= 0) {
            $parent = $current->parent;
            if (! $parent || $parent->slug !== $parts[$idx]) {
                return null;
            }
            $current = $parent;
            $idx--;
        }

        return $category;
    }

    private static function decodeHtmlEntities(?string $value): ?string
    {
        if ($value === null) {
            return null;
        }

        $decoded = $value;

        for ($index = 0; $index < 5; $index++) {
            $next = html_entity_decode($decoded, ENT_QUOTES | ENT_HTML5, 'UTF-8');

            if ($next === $decoded) {
                break;
            }

            $decoded = $next;
        }

        return $decoded;
    }
}
