<?php

namespace App\Http\Livewire\Posts;

use App\Models\Category;
use App\Models\Locations\Location;
use App\Models\Meeting;
use App\Models\Post;
use App\Models\PostTranslation;
use App\Models\TransactionType;
use App\Rules\MaxLengthWithoutHtml;
use App\Http\Livewire\Traits\RequiresAdminAuthorization;
use App\Traits\FormHelpersTrait;
use Cviebrock\EloquentSluggable\Services\SlugService;
use Illuminate\Auth\Access\HandlesAuthorization;
use Illuminate\Support\Facades\App;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\DB;
use Illuminate\Validation\Rule;
use Livewire\Component;
use Livewire\WithFileUploads;
use Livewire\WithPagination;
use WireUi\Traits\WireUiActions;

class Manage extends Component
{
    use WithPagination;
    use WithFileUploads;
    use WireUiActions;
    use HandlesAuthorization;
    use FormHelpersTrait;
    use RequiresAdminAuthorization;

    public $search;
    public $postTypeFilter = '';
    public $categoryFilter = '';
    public $languageFilter = '';
    public $publicationStatusFilter = '';
    public bool $showModal = false;
    public $createTranslation;
    public $postId;
    public $bulkSelected = [];
    public bool $bulkDisabled = true;
    public bool $selectAll = false;
    public $categoryId;
    public $post = ['title' => '', 'excerpt' => '', 'content' => '', 'slug' => '', 'translation_id' => null];   // In case fields are left empty (concept post)

    public $localeInit;
    public $locale;
    public $localesOptions = [];
    public $language;
    public bool $localeIsLocked = false;  // True when category type defines specific locale

    public $title;
    public $content;
    public $from;   // x-date-time-picker and x-select do not entangle if they do not exist beforehand
    public $till;     // x-date-time-picker and x-select do not entangle if they do not exist beforehand
    public $modalStopPublication = false;
    public $modalStartPublication = false;
    public $selectedTranslationId = null;    // Needed for the stop/startPublicationModal
    public string $confirmString = '';
    public bool $isPrinciplesPost = false;

    public $image;
    public bool $imagePreviewable;
    public $mediaOwner;
    public $mediaCaption;
    public $media;

    public $meetingShow = false;
    public $meeting;
    public $meetingVenue;
    public $meetingAddress;
    public $meetingDistrict;
    public $meetingCity;
    public $meetingCountry;
    public $meetingFrom;
    public $meetingTill;
    public $amount;
    public $hours;
    public $minutes;
    public $basedOnQuantity;
    public $transactionTypeId;
    public $organizerOptions;
    public $organizer = ['id' => null, 'type' => null]; // In case fields are left empty (concept post)
    public $author = ['id' => null, 'type' => null]; // In case fields are left empty (concept post)

    public $perPage = 10;
    public $page;

    public string $sortField = 'updated_at'; // Default sort field
    public string $sortDirection = 'desc'; // Default sort direction

    protected $listeners = [
        'categorySelected',
        'localeSelected',
        'quillEditor',
        'uploadImage',
        'organizerSelected',
        'authorSelected',
        'amount',
        'openCreateModal' => 'create',
        'countryToParent',
        'cityToParent',
        'districtToParent',
        'refreshPostsTable' => '$refresh',
    ];

    public function mount()
    {
        // Admin Authorization - Prevent IDOR attacks and cross-guard access
        $activeProfileType = session('activeProfileType');
        $activeProfileId = session('activeProfileId');

        if (!$activeProfileType || !$activeProfileId) {
            abort(403, __('No active profile selected'));
        }

        $profile = $activeProfileType::find($activeProfileId);

        if (!$profile) {
            abort(403, __('Active profile not found'));
        }

        // Validate profile ownership using ProfileAuthorizationHelper (prevents cross-guard attacks)
        \App\Helpers\ProfileAuthorizationHelper::authorize($profile);

        // Verify admin or central bank permissions
        if ($profile instanceof \App\Models\Admin) {
            // Admin access OK
        } elseif ($profile instanceof \App\Models\Bank) {
            // Only central bank (level 0) can access post management
            if ($profile->level !== 0) {
                abort(403, __('Central bank access required for post management'));
            }
        } else {
            abort(403, __('Admin or central bank access required'));
        }

        // Log admin access for security monitoring
        \Log::info('Posts management access', [
            'component' => 'Posts\\Manage',
            'profile_id' => $profile->id,
            'profile_type' => get_class($profile),
            'authenticated_guard' => \Auth::getDefaultDriver(),
            'ip_address' => request()->ip(),
        ]);

        // Load post type filter from session if not set via URL
        if (empty($this->postTypeFilter) && !request()->has('postTypeFilter')) {
            $this->postTypeFilter = session('posts.manage.typeFilter', '');
        }
    }

    public function updatedPostTypeFilter($value)
    {
        // Save post type filter to session
        session(['posts.manage.typeFilter' => $value]);
        $this->resetPage();
    }

    protected function rules()
    {
        //  Note that most fields are not required, this is to store concept posts
        return [
            'categoryId' => 'required|integer',
            'locale' => 'required|string|min:2|max:3',
            'post.slug' => array_merge(
                timebank_config('posts.slug_rule'),
                [Rule::unique('post_translations', 'slug')->ignore($this->post['translation_id'] ?? null, 'id')]
            ),
            'post.title' => timebank_config('posts.title_rule'),
            'post.excerpt' =>  timebank_config('posts.excerpt_rule'),
            'content' => ['nullable', 'string', new MaxLengthWithoutHtml(timebank_config('posts.content_max_input', 2000))],
            'from' => 'date|nullable',
            'till' => 'date|nullable',
            'image' =>  timebank_config('posts.image_rule'),
            'mediaOwner' =>  timebank_config('posts.media_owner_rule'),
            'mediaCaption' =>  timebank_config('posts.media_caption_rule'),
            'meetingFrom' =>  'date|nullable',
            'meetingTill' =>  'date|nullable',
            'meeting.venue' =>  timebank_config('posts.meeting_venue_rule'),
            'meeting.address' =>  timebank_config('posts.meeting_address_rule'),
            'organizer.id' => 'integer|nullable',
            'organizer.type' => 'string|nullable',
            'amount' => 'integer|nullable|min:0',
            'basedOnQuantity' => 'integer|nullable|min:0',
            'transactionTypeId' => 'integer|nullable|exists:transaction_types,id',
            'confirmString' => [
                'required_if:isPrinciplesPost,true',
                function ($attribute, $value, $fail) {
                    // Only check if value matches expected when isPrinciplesPost is true
                    if ($this->isPrinciplesPost && !empty($value)) {
                        $expected = __('messages.confirm_input_string');
                        if ($value !== $expected) {
                            $fail(__('The confirmation keyword is incorrect.'));
                        }
                    }
                },
            ],
        ];
    }


    /**
     * Check if the current post/category is a Principles post
     *
     * @return bool
     */
    protected function checkIfPrinciplesPost(): bool
    {
        if (!$this->categoryId) {
            return false;
        }

        $category = Category::find($this->categoryId);
        if (!$category) {
            return false;
        }

        return $category->type === 'SiteContents\Static\Principles';
    }

    // Add the queryString property to sync pagination with URL
    protected $queryString = [
        'search' => ['except' => ''],
        'postTypeFilter' => ['except' => ''],
        'categoryFilter' => ['except' => ''],
        'languageFilter' => ['except' => ''],
        'publicationStatusFilter' => ['except' => ''],
        'perPage' => ['except' => 10],
        'page' => ['except' => 1]
    ];


    public function categorySelected($categoryId)
    {
        $this->categoryId = $categoryId;

        // Check if category type has locale-specific suffix
        $this->checkAndSetLocaleFromCategory();

        $this->getLocalesOptions();
        $isMeeting = Category::where('id', $this->categoryId)->where('type', Meeting::class)->exists();
        if ($isMeeting) {
            if ($this->postId) {    // Check if we are editing an existing post or if we are creating a new one
                $this->getMeeting();
                $this->meetingShow = true;
            }
            $this->meetingShow = true;
        } else {
            $this->meetingShow = false;
        }

        // Check if this is a Principles post to show warning in modal
        $this->isPrinciplesPost = $this->checkIfPrinciplesPost();
    }

    /**
     * Check if category type ends with a locale suffix and lock locale if it does
     *
     * @return void
     */
    protected function checkAndSetLocaleFromCategory()
    {
        if (!$this->categoryId) {
            $this->localeIsLocked = false;
            return;
        }

        $category = Category::find($this->categoryId);
        if (!$category) {
            $this->localeIsLocked = false;
            return;
        }

        // Get supported locales from Laravel Localization config
        $supportedLocales = array_keys(config('laravellocalization.supportedLocales', []));

        // Check if category type ends with \locale (e.g., App\Models\ImagePost\en)
        $categoryType = $category->type;
        foreach ($supportedLocales as $locale) {
            if (str_ends_with($categoryType, '\\' . $locale)) {
                // Lock locale to this specific language
                $this->locale = $locale;
                $this->localeIsLocked = true;
                $this->setLanguageName();
                return;
            }
        }

        // Category type doesn't end with locale - allow free selection
        $this->localeIsLocked = false;
    }


    /**
     * Get available language options for the language select-box
     *
     * @return void
     */
    public function getLocalesOptions()
    {
        // Ensure categoryId is set
        if (!$this->categoryId) {
            $this->localesOptions = [];
            return;
        }

        // Get available translations for the selected category
        $localesOptions = Category::with(['translations' => function ($query) {
            $query->select('category_id', 'locale');
        }])->find($this->categoryId);

        // Edit a post: exclude existing translations but include initial locale
        if ($this->postId) {
            $localesExclude = Post::find($this->postId)->translations()->whereNot('locale', $this->localeInit)->pluck('locale');
        } else {
            // Create a post
            $localesExclude = [];
        }


        $localesExclude = collect($localesExclude);

        if ($localesOptions) {
            $localesOptions = $localesOptions->translations()->pluck('locale');
            $this->localesOptions = $localesOptions->diff($localesExclude);
        } else {
            $this->localesOptions = [];
        }

        $this->dispatch('updateLocalesOptions', $this->localesOptions);
    }


    public function localeSelected($locale)
    {
        // Prevent locale changes when locked by category
        if ($this->localeIsLocked) {
            return;
        }

        // Edit the initial post
        if ($locale === $this->localeInit) {
            $this->locale = $locale;
            $this->post['translation_id'] = Post::find($this->postId)->translations->first()->id;     // No new post, so restore post['translation_id] to ignore unique slug validation
            $this->createTranslation = false;
        } elseif ($locale !== $this->localeInit) {
            // Add a new translation to the initial post
            $this->locale = $locale;
            $this->post['translation_id'] = null;   // New post, so reset post['translation_id'] for unique slug validation
            $this->createTranslation = true;
        }
        $this->setLanguageName();
    }


    public function setLanguageName()
    {
        if ($this->locale) {
            $this->language = DB::table('languages')->where('lang_code', $this->locale)->first()->name;
        }
    }


    public function organizerSelected($value)
    {
        $this->organizer = $value;
    }

    public function authorSelected($value)
    {
        $this->author = $value;
    }

    public function amount($value)
    {
        $this->amount = $value;
    }


    public function updatedTitle($value)
    {
        $this->post['title'] = $value;
        $this->post['slug'] = SlugService::createSlug(PostTranslation::class, 'slug', $value);
    }


    public function edit($translationId)
    {
        // CRITICAL: Authorize admin access for editing posts
        $this->authorizeAdminAccess();

        // Reset validation errors from previous edits
        $this->resetValidation();
        $this->resetErrorBag();

        $this->meetingShow = false;     // Hide the event details unless an event category is selected
        $this->createTranslation = false;
        $this->postId = PostTranslation::find($translationId)->post_id;

        $post = Post::with([
            'translations' => function ($query) use ($translationId) {
                $query->where('id', $translationId);
            },
            'category' => function ($query) {
                $query->with('translations');
            },
            'meeting',
            'author',
        ])->find($this->postId);

        $this->post = [
            'category_id' => $post->category_id,
            'translation_id' => $post->translations->first()->id,
            'locale' => $post->translations->first()->locale,
            'title' => $post->translations->first()->title,
            'slug' => $post->translations->first()->slug,
            'excerpt' => $post->translations->first()->excerpt,
            'content' => $post->translations->first()->content,
        ];
        if ($post->meeting) {
            $this->getMeeting();
            $this->dispatchLocationToChildren();
        }

        $this->title = $this->post['title'];
        $this->content = $this->post['content'];

        $this->localeInit = $this->post['locale'];
        $this->locale = $this->post['locale'];
        $this->setLanguageName();

        $this->categoryId = $post->category_id;

        // Check if category type has locale-specific suffix
        $this->checkAndSetLocaleFromCategory();

        $this->getLocalesOptions();
        $this->meetingShow = Category::where('id', $post->category_id)->where('type', Meeting::class)->exists();    // Toggle meeting section based on category type

        // Check if this is a Principles post to show warning in modal
        $this->isPrinciplesPost = $this->checkIfPrinciplesPost();

        $this->from = $post->translations->first()->from;   // x-date-time-picker and x-select need a separate public property, see start of this file
        $this->till = $post->translations->first()->till; // x-date-time-picker and x-select need a separate public property, see start of this file

        // Dispatch event to notify Quill editor to reload content
        $this->dispatch('postLoaded');

        // if ($post->media->count() > 0) {
        //     $this->media = $post->getFirstMediaUrl('posts');    // Do not use responsive media in livewire pages that have multiple update cycles as the placeholder img show after an update
        // }

        // Retrieve existing media caption
        $mediaItem = $post->getFirstMedia('posts');
        if ($mediaItem) {
            $this->media = $post->getFirstMediaUrl('posts');    // Do not use responsive media in livewire pages that have multiple update cycles as the placeholder img show after an update
            $this->mediaOwner = $mediaItem->getCustomProperty('owner');
            $this->mediaCaption = $mediaItem->getCustomProperty('caption-' . $this->locale);
        } else {
            $this->mediaOwner = null;
            $this->mediaCaption = null;
        }

        // Load author data if it exists
        if ($post->author_id) {
            $this->author['id'] = $post->author_id;
            $this->author['type'] = $post->author_model;
        }

        $this->showModal = true;
    }

    public function dispatchLocationToChildren()
    {
        $this->dispatch('countryToChildren', $this->meetingCountry);
        $this->dispatch('cityToChildren', $this->meetingCity);
        $this->dispatch('districtToChildren', $this->meetingDistrict);
    }


    public function countryToParent($value)
    {
        $this->meetingCountry = $value;
    }


    public function cityToParent($value)
    {
        $this->meetingCity = $value;
    }


    public function districtToParent($value)
    {
        $this->meetingDistrict = $value;
    }

    public function create()
    {
        // CRITICAL: Authorize admin access for creating posts
        $this->authorizeAdminAccess();

        $this->reset();
        $this->resetValidation();
        $this->resetErrorBag();
        $this->dispatch('showModal');
        $this->showModal = true;
    }


    public function save()
    {
        // CRITICAL: Authorize admin access for saving posts
        $this->authorizeAdminAccess();

        // Debug: Log the content being saved
        \Log::info('Save called with content:', ['content' => $this->content, 'length' => strlen($this->content ?? '')]);

        // Check if this is a Principles post for validation
        $this->isPrinciplesPost = $this->checkIfPrinciplesPost();

        // Add translation to post
        if (!is_null($this->postId)) {

            $this->validate();

            if ($this->createTranslation === true) {

                $post = Post::find($this->postId);

                $postTranslation = new PostTranslation([
                    'slug' => $this->post['slug'],
                    'locale' => $this->locale,
                    'title' => $this->post['title'],
                    'excerpt' => $this->post['excerpt'],
                    'content' => $this->content,
                    'updated_by_user_id' => Auth::guard('web')->id(),
                    'from' => $this->from,
                    'till' => $this->till,
                ]);
                $post->translations()->save($postTranslation);

                // Save author data to the post
                $post->author_id = $this->author['id'] ?? null;
                $post->author_model = $this->author['type'] ?? null;
                $post->save();

                if ($this->meetingShow) {

                    $postMeeting = [
                        'post_id' => $this->postId,
                        'venue' => $this->meetingVenue ?? null,
                        'address' => $this->meetingAddress ?? null,
                        'price' => $this->amount ?? null,
                        'based_on_quantity' => $this->basedOnQuantity ?? null,
                        'transaction_type_id' => $this->transactionTypeId ?? null,
                        'meetingable_id' => $this->organizer['id'],
                        'meetingable_type' => $this->organizer['type'],
                        'from' => $this->meetingFrom,
                        'till' => $this->meetingTill
                    ];
                    $meeting = Meeting::updateOrCreate(['post_id' =>  $this->postId], $postMeeting);

                    // Update or create the location for the meeting
                    $location = $meeting->location;
                    if ($location) {
                        // Update existing location
                        $location->update([
                            'country_id' => $this->meetingCountry,
                            'city_id' => $this->meetingCity,
                            'district_id' => $this->meetingDistrict,
                        ]);
                    } else {
                        // Create new location and associate with meeting
                        $location = new Location([
                            'country_id' => $this->meetingCountry,
                            'city_id' => $this->meetingCity,
                            'district_id' => $this->meetingDistrict,
                        ]);
                        $meeting->location()->save($location);
                    }
                }

                $this->saveMedia($post);

                // WireUI notification
                if ($post) {
                    $this->notification()->success(
                        $title = __('Saved'),
                        $description = __('Post') . ' ' . __('is saved successfully')
                    );
                } else {
                    $this->notification()->error(
                        $title = __('Error!'),
                        $description = __('Oops, we have an error: the post was not saved!')
                    );
                    return back();
                }
            } else {
                // Update a post
                $this->validate();

                $post = Post::find($this->postId);
                $postTranslation = [
                    'title' => $this->post['title'],
                    'slug' => $this->post['slug'],
                    'excerpt' => $this->post['excerpt'] ?? null,
                    'content' => $this->content ?? null,
                    'updated_by_user_id' => Auth::guard('web')->id(),
                    'from' => $this->from ?? null,
                    'till' => $this->till ?? null,
                ];

                // Update locale if it's locked by category
                if ($this->localeIsLocked) {
                    $postTranslation['locale'] = $this->locale;
                }

                $post->translations()->where('id', $this->post['translation_id'])->update($postTranslation);
                $post->category_id = $this->categoryId;
                $post->postable_id = Session('activeProfileId');
                $post->postable_type = Session('activeProfileType');

                // Save author data to the post
                $post->author_id = $this->author['id'] ?? null;
                $post->author_model = $this->author['type'] ?? null;
                $post->save();

                if ($this->meetingShow) {
                    $postMeeting = [
                        'post_id' => $this->postId,
                        'venue' => $this->meetingVenue ?? null,
                        'address' => $this->meetingAddress ?? null,
                        'price' => $this->amount ?? null,
                        'based_on_quantity' => $this->basedOnQuantity ?? null,
                        'transaction_type_id' => $this->transactionTypeId ?? null,
                        'meetingable_id' => $this->organizer['id'] ?? null,
                        'meetingable_type' => $this->organizer['type'] ?? null,
                        'from' => $this->meetingFrom ?? null,
                        'till' => $this->meetingTill ?? null
                    ];
                    $meeting = Meeting::updateOrCreate(['post_id' =>  $this->postId], $postMeeting);

                    // Update or create the location for the meeting
                    $location = $meeting->location;
                    if ($location) {
                        // Update existing location
                        $location->update([
                            'country_id' => $this->meetingCountry,
                            'city_id' => $this->meetingCity,
                            'district_id' => $this->meetingDistrict,
                        ]);
                    } else {
                        // Create new location and associate with meeting
                        $location = new Location([
                            'country_id' => $this->meetingCountry,
                            'city_id' => $this->meetingCity,
                            'district_id' => $this->meetingDistrict,
                        ]);
                        $meeting->location()->save($location);
                    }
                }

                $post->save();
                $post->searchable();

                $this->saveMedia($post);

                // WireUI notification
                if ($post) {
                    $this->notification()->success(
                        $title = __('Saved'),
                        $description = __('Post is saved successfully')
                    );
                } else {
                    $this->notification()->error(
                        $title = __('Error!'),
                        $description = __('Oops, we have an error: the post was not saved!')
                    );
                    return back();
                }
            }
        } else {
            // Create a new post
            $this->post['translation_id'] = 0;   // for unique validation on slug: do not ignore non-existing translation_id
            $this->validate();

            if (timebank_config('posts.postable_is_auth_user')) {
                // Authenicated users are stored as postables
                $post = new Post([
                    'postable_id' => Auth::guard('web')->id(),   // Store creator (article writer) id
                    'postable_type' => get_class(Auth::guard('web')->user()),   // Store creator (article writer) type. I.e. "App\Models\User"
                ]);
            } else {
                // Active profiles are stored as postables
                $post = new Post([
                    'postable_id' => session('activeProfileId'),
                    'postable_type' => session('activeProfileType'),
                ]);
            }
            $post['category_id'] = $this->categoryId;
            $post->save();

            $translation = new PostTranslation([
                'slug' => $this->post['slug'],
                'locale' => $this->locale,
                'title' => $this->post['title'],
                'excerpt' => $this->post['excerpt'],
                'content' => $this->content,
                'updated_by_user_id' => Auth::guard('web')->id(),
                'from' => $this->from,
                'till' => $this->till,
            ]);
            $post->translations()->save($translation);

            // Save author data to the post
            $post->author_id = $this->author['id'] ?? null;
            $post->author_model = $this->author['type'] ?? null;
            $post->save();

            if ($this->meetingShow) {
                $postMeeting = [
                    'venue' => $this->meetingVenue ?? null,
                    'address' => $this->meetingAddress ?? null,
                    'price' => $this->amount ?? null,
                    'based_on_quantity' => $this->basedOnQuantity ?? null,
                    'transaction_type_id' => $this->transactionTypeId ?? null,
                    'meetingable_id' => $this->organizer['id'],
                    'meetingable_type' => $this->organizer['type'],
                    'from' => $this->meetingFrom,
                    'till' => $this->meetingTill
                ];

                $meeting = Meeting::updateOrCreate(['post_id' =>  $post->id], $postMeeting);

                // Create new location and associate with meeting
                $location = new Location([
                    'country_id' => $this->meetingCountry,
                    'city_id' => $this->meetingCity,
                    'district_id' => $this->meetingDistrict,
                ]);
                $meeting->location()->save($location);
            }

            $this->saveMedia($post);

            // WireUI notification
            if ($post) {
                $this->notification()->success(
                    $title = __('Saved'),
                    $description = __('Post is saved successfully!')
                );
            } else {
                $this->notification()->error(
                    $title = __('Error!'),
                    $description = __('Oops, we have an error: the post was not saved!')
                );
                return back();
            }
        }
        $this->close();
    }


    public function saveMedia($post)
    {
        if ($this->image) {
            // If a new image is uploaded
            $post->clearMediaCollection('posts');
            $post->addMedia($this->image->getRealPath())
                ->withCustomProperties([
                    'owner' => $this->mediaOwner,
                    'caption-' . $this->locale => $this->mediaCaption,
                ])
                ->toMediaCollection('posts');
        } else {
            // No new image uploaded – update the caption of existing media
            $mediaItem = $post->getFirstMedia('posts');
            if ($mediaItem) {
                $mediaItem->setCustomProperty('owner', $this->mediaOwner);
                $mediaItem->setCustomProperty('caption-' . $this->locale, $this->mediaCaption);
                $mediaItem->save();
            }
        }
    }


    /**
     * Receives value from livewire quill-editor component
     *
     * @param  mixed $value
     * @return void
     */
    public function quillEditor($content = null)
    {
        $this->content = $content;
    }


    public function updatedImage()
    {
        $this->validateOnly('image');
    }

    public function updatingImage($newValue)
    {
        // If there's no file, just return
        if (!$newValue) {
            return;
        }
        // Check extension before storing it in $this->image
        $ext = strtolower($newValue->getClientOriginalExtension() ?? '');
        // Disallow non-image extensions
        if (!in_array($ext, ['jpg', 'jpeg', 'png', 'gif', 'webp'])) {
            $this->image = null;
            $this->media = null;
            $this->addError('image', 'Unsupported file type: ' . $ext);
            $this->imagePreviewable = false;
        } else {
            $this->imagePreviewable = true;
        }
    }

    public function removeImage()
    {
        // Clear the current upload preview
        $this->image = null;

        // If editing an existing post, remove any saved media
        if ($this->postId) {
            $post = Post::find($this->postId);
            if ($post) {
                $post->clearMediaCollection('posts');
                // update the preview in the modal
                $this->image = null;
                $this->media = null;
            }
        }
    }


    public function updatedBulkSelected()
    {
        if (count($this->bulkSelected) > 0) {
            $this->bulkDisabled = false;
        } else {
            $this->bulkDisabled = true;
        }

        // Update selectAll state based on current selection
        $this->updateSelectAllState();

        // Notify BackupRestore component of selection change
        $this->dispatch('updateSelectedTranslationIds', $this->bulkSelected)->to('posts.backup-restore');
    }

    /**
     * Handle select all checkbox toggle.
     */
    public function updatedSelectAll($value)
    {
        if ($value) {
            // Select all translation IDs on current page
            $this->bulkSelected = $this->getCurrentPageTranslationIds();
        } else {
            // Deselect all
            $this->bulkSelected = [];
        }

        $this->bulkDisabled = count($this->bulkSelected) === 0;

        // Notify BackupRestore component of selection change
        $this->dispatch('updateSelectedTranslationIds', $this->bulkSelected)->to('posts.backup-restore');
    }

    /**
     * Get all translation IDs on the current page.
     */
    private function getCurrentPageTranslationIds(): array
    {
        // Re-run the same query logic from render() to get current page posts
        $query = Post::with(['translations', 'category.translations']);

        if ($this->publicationStatusFilter === 'deleted') {
            $query->onlyTrashed()->with(['translations' => fn($q) => $q->onlyTrashed()]);
        }

        if ($this->postTypeFilter) {
            $query->whereHas('category', fn($q) => $q->where('type', $this->postTypeFilter));
        }

        if ($this->categoryFilter) {
            $query->where('category_id', $this->categoryFilter);
        }

        if ($this->languageFilter) {
            $query->whereHas('translations', fn($q) => $q->where('locale', $this->languageFilter));
        }

        if ($this->publicationStatusFilter && $this->publicationStatusFilter !== 'deleted') {
            $now = now();
            if ($this->publicationStatusFilter === 'published') {
                $query->whereHas('translations', fn($q) => $q->where(function ($q) use ($now) {
                    $q->where('from', '<=', $now)->where(fn($q) => $q->whereNull('till')->orWhere('till', '>', $now));
                }));
            } elseif ($this->publicationStatusFilter === 'scheduled') {
                $query->whereHas('translations', fn($q) => $q->where('from', '>', $now));
            } elseif ($this->publicationStatusFilter === 'ended') {
                $query->whereHas('translations', fn($q) => $q->where('till', '<=', $now));
            } elseif ($this->publicationStatusFilter === 'draft') {
                $query->whereHas('translations', fn($q) => $q->whereNull('from'));
            }
        }

        if ($this->search) {
            $searchTerm = '%' . $this->search . '%';
            $query->where(function ($q) use ($searchTerm) {
                $q->whereHas('translations', fn($q) => $q->where('title', 'like', $searchTerm)
                    ->orWhere('excerpt', 'like', $searchTerm)
                    ->orWhere('content', 'like', $searchTerm));
            });
        }

        $posts = $query->orderBy($this->sortField, $this->sortDirection)
            ->paginate($this->perPage);

        $ids = [];
        foreach ($posts as $post) {
            foreach ($post->translations as $translation) {
                $ids[] = (string) $translation->id;
            }
        }

        return $ids;
    }

    /**
     * Update the selectAll checkbox state based on current selection.
     */
    private function updateSelectAllState(): void
    {
        $currentPageIds = $this->getCurrentPageTranslationIds();

        if (empty($currentPageIds)) {
            $this->selectAll = false;
            return;
        }

        // Check if all items on current page are selected
        $allSelected = count(array_intersect($this->bulkSelected, $currentPageIds)) === count($currentPageIds);
        $this->selectAll = $allSelected;
    }

    public function deleteSelected()
    {
        // CRITICAL: Authorize admin access for deleting posts
        $this->authorizeAdminAccess();

        // Get the selected translations
        $selectedTranslations = PostTranslation::whereIn('id', $this->bulkSelected)->get();

        // Update the 'till' attribute to prevent immediate publication of restored posts
        $selectedTranslations->each(function ($translation) {
            $translation->update(['updated_by_user_id' => Auth::guard('web')->id(), 'till' => now()]);
        });

        // Delete the selected translations
        PostTranslation::whereIn('id', $this->bulkSelected)->delete();

        // Check if any posts have no remaining translations and if so, delete those posts
        $postIds = $selectedTranslations->pluck('post_id')->unique();
        foreach ($postIds as $postId) {
            $post = Post::withTrashed()->find($postId);
            if ($post && $post->translations()->count() === 0) {
                $post->delete();
                $post->searchable(); // re-index search
            }
        }


        $this->resetPage();

        // Reset the bulk selection
        $this->bulkSelected = [];
        $this->bulkDisabled = true;
        $this->selectAll = false;
    }

    public function undeleteSelected()
    {
        // CRITICAL: Authorize admin access for restoring posts
        $this->authorizeAdminAccess();

        // Get the selected translations from soft-deleted posts
        $selectedTranslations = PostTranslation::onlyTrashed()->whereIn('id', $this->bulkSelected)->get();

        // Update the 'till' attribute for translations that have till > now() or null
        $selectedTranslations->each(function ($translation) {
            $now = now();
            if (is_null($translation->till) || $translation->till > $now) {
                $translation->till = $now;
                $translation->updated_by_user_id = Auth::guard('web')->id();
                $translation->save();
            }
        });

        // Restore the selected translations
        PostTranslation::onlyTrashed()->whereIn('id', $this->bulkSelected)->restore();

        // Check if any posts need to be restored and restore them
        $postIds = $selectedTranslations->pluck('post_id')->unique();
        foreach ($postIds as $postId) {
            $post = Post::onlyTrashed()->find($postId);
            if ($post) {
                $post->restore();
                $post->searchable(); // re-index search
            }
        }

        $this->resetPage();

        // Reset the bulk selection
        $this->bulkSelected = [];
        $this->bulkDisabled = true;
        $this->selectAll = false;
    }


    /**
     * Close the Edit post modal
     *
     * @return void
     */
    public function close()
    {
        $this->showModal = false;
        $this->resetForm();
    }


    public function resetForm()
    {
        $this->reset();
        $this->resetValidation();
        $this->resetErrorBag();
    }


    /**
     * Get meeting details for the post
     *
     * @return void
     */
    public function getMeeting()
    {
        $meeting = Meeting::where('post_id', $this->postId)
            ->with('location')
            ->first();

        if ($meeting) {
            $this->meeting = $meeting;
            $this->meetingVenue = $meeting->venue;
            $this->meetingAddress = $meeting->address;
            $this->amount = $meeting->price;
            $this->basedOnQuantity = $meeting->based_on_quantity;
            $this->transactionTypeId = $meeting->transaction_type_id;

            // Calculate hours and minutes from amount (price in minutes)
            if ($meeting->price) {
                $this->hours = intdiv($meeting->price, 60);
                $this->minutes = $meeting->price % 60;
            }

            // Get the location (if any)
            $location = $meeting->location;
            $this->meetingDistrict = $location ? $location->district_id : null;
            $this->meetingCity = $location ? $location->city_id : null;
            $this->meetingCountry = $location ? $location->country_id : null;

            $this->meetingFrom = $meeting->from;
            $this->meetingTill = $meeting->till;
            $this->organizer['id'] = $meeting->meetingable_id;
            $this->organizer['type'] = $meeting->meetingable_type;
            if ($this->organizer['id']) {
                $this->dispatch('organizerExists', $meeting);
            }
        }

    }


    public function openStopPublicationModal($translationId)
    {
        $this->selectedTranslationId = $translationId;
        $this->modalStopPublication = true;
    }


    /**
     * Stop publication of the post
     *
     * @param  mixed $translationId
     * @return void
     */
    public function stopPublication($translationId)
    {
        // CRITICAL: Authorize admin access for unpublishing posts
        $this->authorizeAdminAccess();

        $translation = PostTranslation::find($translationId);
        if ($translation) {
            $translation->till = now();
            $translation->save();
            // Get a fresh Post instance and force synchronous update
            $freshPost = Post::with(['translations', 'category.translations'])
                ->find($translation->post_id);

            if ($freshPost) {
                // Force remove from index first
                $freshPost->unsearchable();

                // Then add back with fresh data
                $freshPost->searchable();
            }
            $this->resetForm();
        }
        $this->modalStopPublication = false;
    }

    /**
     * Start publication of the post
     *
     * @param  mixed $translationId
     * @return void
     */
    public function startPublication($translationId)
    {
        // CRITICAL: Authorize admin access for publishing posts
        $this->authorizeAdminAccess();

        $translation = PostTranslation::find($translationId);
        if ($translation) {
            // Set the start date to now and ensure the end date is cleared.
            $translation->from = now();
            $translation->till = null;
            $translation->save();
            // Get a fresh Post instance and force synchronous update
            $freshPost = Post::with(['translations', 'category.translations'])
                ->find($translation->post_id);

            if ($freshPost) {
                // Force remove from index first
                $freshPost->unsearchable();

                // Then add back with fresh data
                $freshPost->searchable();
            }
            $this->resetForm();
        }
        $this->modalStartPublication = false;
    }

    public function openStartPublicationModal($translationId)
    {
        $this->selectedTranslationId = $translationId;
        $this->modalStartPublication = true;
    }



    public function handleSearchEnter()
    {
        if (!$this->showModal) {
            $this->searchPosts();
        }
    }



    public function updatedPage()
    {
        $this->dispatch('scroll-to-top');
    }


    public function updatedPerPage($value)
    {
        $this->resetPage();
        $this->resetBulkSelection();
        $this->dispatch('scroll-to-top');
    }


    public function updatingSearch()
    {
        $this->resetPage();
        $this->resetBulkSelection();
    }

    public function searchPosts()
    {
        $this->resetPage();
        $this->resetBulkSelection();
        $this->dispatch('scroll-to-top');
    }

    public function resetSearch()
    {
        $this->search = '';
        $this->resetPage();
        $this->resetBulkSelection();
        $this->dispatch('scroll-to-top');
    }

    public function resetCategoryFilter()
    {
        $this->categoryFilter = '';
        $this->resetPage();
        $this->resetBulkSelection();
        $this->dispatch('scroll-to-top');
    }

    /**
     * Reset bulk selection state.
     */
    private function resetBulkSelection(): void
    {
        $this->bulkSelected = [];
        $this->bulkDisabled = true;
        $this->selectAll = false;

        // Notify BackupRestore component of selection change
        $this->dispatch('updateSelectedTranslationIds', [])->to('posts.backup-restore');
    }

    public function getCategoriesProperty()
    {
        // Load all translations so the translation accessor can fallback to base locale if needed
        return Category::with('translations')
            ->where('type', '!=', 'App\Models\Tag')
            ->get()
            ->map(function ($category) {
                $name = $category->translation ? $category->translation->name : __('Untitled category');
                // Remove any newlines/carriage returns that could break JavaScript
                $name = str_replace(["\r\n", "\r", "\n"], ' ', $name);
                return [
                    'id' => $category->id,
                    'name' => trim($name)
                ];
            })->sortBy('name');
    }

    public function resetLanguageFilter()
    {
        $this->languageFilter = '';
        $this->resetPage();
        $this->resetBulkSelection();
        $this->dispatch('scroll-to-top');
    }

    public function getLanguagesProperty()
    {
        return DB::table('post_translations')
            ->select('locale')
            ->distinct()
            ->orderBy('locale')
            ->get()
            ->map(function ($item) {
                return [
                    'id' => $item->locale,
                    'name' => __('messages.' . $item->locale) // Using Laravel's language translation
                ];
            });
    }

    public function resetPublicationStatusFilter()
    {
        $this->publicationStatusFilter = '';
        $this->resetPage();
        $this->resetBulkSelection();
        $this->dispatch('scroll-to-top');
    }

    public function getPublicationStatusOptionsProperty()
    {
        return collect([
            [
                'id' => 'draft',
                'name' => __('Drafts')
            ],
            [
                'id' => 'scheduled',
                'name' => __('Scheduled')
            ],
            [
                'id' => 'published',
                'name' => __('Published')
            ],
            [
                'id' => 'due',
                'name' => __('Due')
            ],
            [
                'id' => 'deleted',
                'name' => __('Deleted')
            ]
        ]);
    }

    public function resetPostTypeFilter()
    {
        $this->postTypeFilter = '';
        session(['posts.manage.typeFilter' => '']);
        $this->resetPage();
        $this->resetBulkSelection();
        $this->dispatch('scroll-to-top');
    }

    public function getPostTypeOptionsProperty()
    {
        return collect([
            [
                'id' => 'other',
                'name' => __('Posts')
            ],
            [
                'id' => 'site_contents',
                'name' => __('Site content')
            ],
            [
                'id' => 'admin_contents',
                'name' => __('Admin content')
            ]
        ]);
    }

    public function sortBy(string $field): void
    {
        if ($this->sortField === $field) {
            $this->sortDirection = $this->sortDirection === 'asc' ? 'desc' : 'asc';
        } else {
            $this->sortDirection = 'asc';
        }

        $this->sortField = $field;
        $this->resetPage();
    }

    public function render()
    {
        $posts = Post::when($this->publicationStatusFilter === 'deleted', function ($query) {
            // Include soft-deleted posts when showing deleted filter
            $query->withTrashed();
        })
            ->whereHas('category', function ($query) {
                // Always exclude Tag posts
                $query->where('type', '!=', 'App\Models\Tag');
            })
            ->with([
                'postable:id,name,email',
                'category.translations' => function ($query) {
                    // CategoryTranslation doesn't use SoftDeletes, so just filter by locale
                    $query->where('locale', App::getLocale());
                },
                'translations' => function ($query) {
                    $query->with('updated_by_user:id,name,full_name,profile_photo_path');

                    // Include trashed translations when showing deleted posts
                    if ($this->publicationStatusFilter === 'deleted') {
                        $query->withTrashed();
                    }

                    // Filter translations by language if language filter is active
                    if ($this->languageFilter) {
                        $query->where('locale', $this->languageFilter);
                    }
                },
            ])
            ->when($this->search, function ($query) {
                $query->where(function ($query) {
                    // For deleted posts, search in trashed translations too
                    if ($this->publicationStatusFilter === 'deleted') {
                        $query->whereHas('translations', function ($query) {
                            $query->withTrashed()
                                ->where(function ($query) {
                                    $query->where('title', 'like', '%' . $this->search . '%')
                                        ->orWhere('content', 'like', '%' . $this->search . '%');
                                });
                        })
                            ->orWhereHas('category.translations', function ($query) {
                                $query->where('name', 'like', '%' . $this->search . '%');
                            })
                            ->orWhereHas('translations.updated_by_user', function ($query) {
                                $query->withTrashed()->where('name', 'like', '%' . $this->search . '%');
                            })
                            ->orWhere('id', $this->search);
                    } else {
                        $query->whereHas('translations', function ($query) {
                            $query
                                ->where(function ($query) {
                                    $query->where('title', 'like', '%' . $this->search . '%')
                                        ->orWhere('content', 'like', '%' . $this->search . '%');
                                });
                        })
                            ->orWhereHas('category.translations', function ($query) {
                                $query
                                    ->where('name', 'like', '%' . $this->search . '%');
                            })
                            ->orWhereHas('translations.updated_by_user', function ($query) {
                                $query->where('name', 'like', '%' . $this->search . '%');
                            })
                            ->orWhere('id', $this->search);
                    }
                });
            })
            ->when($this->postTypeFilter, function ($query) {
                $query->whereHas('category', function ($q) {
                    if ($this->postTypeFilter === 'site_contents') {
                        // Show only SiteContents posts (excluding Manage)
                        $q->where('type', 'like', 'SiteContents\\\\%')
                            ->where('type', 'not like', 'SiteContents\\\\Manage\\\\%');
                    } elseif ($this->postTypeFilter === 'admin_contents') {
                        // Show only SiteContents\Manage posts
                        $q->where('type', 'like', 'SiteContents\\\\Manage\\\\%');
                    } elseif ($this->postTypeFilter === 'other') {
                        // Show other posts (excluding SiteContents and Tag)
                        $q->where('type', 'not like', 'SiteContents\\\\%')
                            ->where('type', '!=', 'App\Models\Tag');
                    }
                });
            })
            ->when($this->categoryFilter, function ($query) {
                $query->where('category_id', $this->categoryFilter);
            })
            ->when($this->languageFilter, function ($query) {
                if ($this->publicationStatusFilter === 'deleted') {
                    $query->whereHas('translations', function ($query) {
                        $query->withTrashed()->where('locale', $this->languageFilter);
                    });
                } else {
                    $query->whereHas('translations', function ($query) {
                        $query->where('locale', $this->languageFilter);
                    });
                }
            })
            ->when($this->publicationStatusFilter, function ($query) {
                $now = now();

                switch ($this->publicationStatusFilter) {
                    case 'draft':
                        $query->whereHas('translations', function ($query) {
                            $query->whereNull('from');
                        });
                        break;

                    case 'scheduled':
                        $query->whereHas('translations', function ($query) use ($now) {
                            $query->where('from', '>', $now);
                        });
                        break;

                    case 'published':
                        $query->whereHas('translations', function ($query) use ($now) {
                            $query->where('from', '<', $now)
                                ->where(function ($query) use ($now) {
                                    $query->where('till', '>', $now)
                                        ->orWhereNull('till');
                                });
                        });
                        break;

                    case 'due':
                        $query->whereHas('translations', function ($query) use ($now) {
                            $query->whereNotNull('till')->where('till', '<', $now);
                        });
                        break;

                    case 'deleted':
                        $query->where(function ($query) {
                            // Posts that are soft-deleted themselves
                            $query->whereNotNull('posts.deleted_at')
                                // OR posts that have soft-deleted translations
                                ->orWhereHas('translations', function ($subQuery) {
                                    $subQuery->onlyTrashed();
                                });
                        });
                        break;
                }
            });

        // Apply sorting based on selected field
        switch ($this->sortField) {
            case 'id':
                $posts->orderBy('posts.id', $this->sortDirection);
                break;

            case 'category_id':
                $posts->join('categories', 'posts.category_id', '=', 'categories.id')
                    ->leftJoin('category_translations', function ($join) {
                        $join->on('categories.id', '=', 'category_translations.category_id')
                            ->where('category_translations.locale', App::getLocale());
                    })
                    ->orderBy('category_translations.name', $this->sortDirection)
                    ->select('posts.*');
                break;

            case 'locale':
            case 'title':
            case 'from':
            case 'till':
            case 'deleted_at':
                // For translation fields, we need to join and order by the translation table
                $posts->join('post_translations', 'posts.id', '=', 'post_translations.post_id')
                    ->when($this->publicationStatusFilter === 'deleted', function ($query) {
                        $query->withTrashed();
                    })
                    ->when($this->languageFilter, function ($query) {
                        $query->where('post_translations.locale', $this->languageFilter);
                    })
                    ->orderBy('post_translations.' . $this->sortField, $this->sortDirection)
                    ->select('posts.*')
                    ->distinct();
                break;

            case 'updated_at':
            default:
                $posts->orderBy('posts.updated_at', $this->sortDirection);
                break;
        }

        $posts = $posts->paginate($this->perPage);

        return view('livewire.posts.manage', [
            'posts' => $posts
        ]);
    }

    /**
     * Gets the label for the "title" input field, including a character counter if applicable.
     * The character counter needs to use the `characterLeftCounter` method from the `FormHelpersTrait`.
     *
     * @return string The label for the "about" field, optionally including the remaining character count.
     */
    public function getTitleLabelProperty()
    {
        $maxInput = timebank_config('posts.title_max_input');
        $baseLabel = __('Title') . ' ';
        $counter = $this->characterLeftCounter($this->post['title'] ?? '', $maxInput);

        return $counter ? $baseLabel . ' (' . $counter . ')' : $baseLabel;
    }


    /**
     * Gets the label for the "Intro" input field, including a character counter if applicable.
     * The character counter needs to use the `characterLeftCounter` method from the `FormHelpersTrait`.
     *
     * @return string The label for the "about" field, optionally including the remaining character count.
     */
    public function getIntroLabelProperty()
    {
        $maxInput = timebank_config('posts.excerpt_max_input', 500);
        $baseLabel = __('Intro') . ' ';
        $counter = $this->characterLeftCounter($this->post['excerpt'] ?? '', $maxInput);

        return $counter ? $baseLabel . ' (' . $counter . ')' : $baseLabel;
    }

    /**
     * Gets the label for the "Content" input field, including a character counter if applicable.
     * The character counter uses the `characterLeftCounterWithoutHtml` method from the `FormHelpersTrait`
     * to count only visible text characters (HTML tags are stripped before counting).
     *
     * @return string The label for the "Content" field, optionally including the remaining character count.
     */
    public function getContentLabelProperty()
    {
        $maxInput = timebank_config('posts.content_max_input', 1000);
        $baseLabel = __('Content') . ' ';
        $counter = $this->characterLeftCounterWithoutHtml($this->content ?? '', $maxInput);

        return $counter ? $baseLabel . ' (' . $counter . ')' : $baseLabel;
    }

    /**
     * Gets the label for the "Media caption" input field, including a character counter if applicable.
     * The character counter needs to use the `characterLeftCounter` method from the `FormHelpersTrait`.
     *
     * @return string The label for the "about" field, optionally including the remaining character count.
     */
    public function getMediaCaptionLabelProperty()
    {
        $maxInput = timebank_config('posts.media_caption_max_input', 300);
        $baseLabel = __('Image caption') . ' ';
        $counter = $this->characterLeftCounter($this->mediaCaption ?? '', $maxInput);

        return $counter ? $baseLabel . ' (' . $counter . ')' : $baseLabel;
    }

    /**
     * Get transaction types for dropdown
     *
     * @return array
     */
    public function getTransactionTypesProperty()
    {
        $allowedTypeIds = timebank_config('posts.meeting_transaction_types', []);

        return TransactionType::whereIn('id', $allowedTypeIds)->get()->map(function ($type) {
            return [
                'id' => $type->id,
                'name' => ucfirst(__('messages.posts.transaction_types.' . strtolower($type->name)))
            ];
        })->toArray();
    }

    /**
     * Get cached localized URLs for view post buttons.
     * Pre-computes URLs for all locales to avoid repeated getLocalizedURL() calls in blade.
     *
     * @return array
     */
    public function getLocalizedUrlsProperty()
    {
        $locales = array_keys(config('laravellocalization.supportedLocales', []));
        $urls = [];

        foreach ($locales as $locale) {
            $urls[$locale] = [
                'pay' => \LaravelLocalization::getLocalizedURL($locale, route('pay', [], false)),
                'transactions' => \LaravelLocalization::getLocalizedURL($locale, route('transactions', [], false)),
                'account_usage' => \LaravelLocalization::getLocalizedURL($locale, route('transactions', ['openAccountUsageInfo' => 'true'], false)),
                'search_info' => \LaravelLocalization::getLocalizedURL($locale, route('search.show', ['openSearchInfo' => 'true'], false)),
                'reports' => \LaravelLocalization::getLocalizedURL($locale, route('reports', [], false)),
                'events' => \LaravelLocalization::getLocalizedURL($locale, route('static-events', [], false)),
                'principles' => \LaravelLocalization::getLocalizedURL($locale, route('static-principles', [], false)),
            ];
        }

        return $urls;
    }

    /**
     * Get localized URL for a post based on its category type.
     *
     * @param string $locale
     * @param string|null $categoryType
     * @param int $postId
     * @return string|null
     */
    public function getPostViewUrl($locale, $categoryType, $postId)
    {
        $urls = $this->localizedUrls;

        if (!isset($urls[$locale])) {
            return null;
        }

        if ($categoryType && str_starts_with($categoryType, 'SiteContents\Pay')) {
            return $urls[$locale]['pay'];
        } elseif ($categoryType && str_starts_with($categoryType, 'SiteContents\Transactions')) {
            return $urls[$locale]['transactions'];
        } elseif ($categoryType && str_starts_with($categoryType, 'SiteContents\AccountUsage')) {
            return $urls[$locale]['account_usage'];
        } elseif ($categoryType && str_starts_with($categoryType, 'SiteContents\Search\Info')) {
            return $urls[$locale]['search_info'];
        } elseif ($categoryType && str_starts_with($categoryType, 'SiteContents\Manage\Reports')) {
            return $urls[$locale]['reports'];
        } elseif ($categoryType && str_starts_with($categoryType, 'SiteContents\Static\Events')) {
            return $urls[$locale]['events'];
        } elseif ($categoryType && str_starts_with($categoryType, 'SiteContents\Static\Principles')) {
            return $urls[$locale]['principles'];
        } else {
            // Generic post URL
            return \LaravelLocalization::getLocalizedURL(
                $locale,
                str_replace('{id}', $postId, trans('routes.post.show', [], $locale))
            );
        }
    }
}
