<?php

declare(strict_types=1);

namespace ViartasThemes\Protera\Database\Seeders;

use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Seeder;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Schema;
use ViartasModules\Faq\Models\Faq;
use ViartasModules\Faq\Models\FaqCategory;
use ViartasModules\Menus\Models\MenuItem;
use ViartasModules\News\Models\News;
use ViartasModules\NewsCategories\Models\NewsCategory;
use ViartasModules\Pages\Models\Page;
use ViartasThemes\Cleverphant\Database\Seeders\CleverphantContentWiper;
use ViartasThemes\Cleverphant\Database\Seeders\CleverphantPagesSeeder;

/**
 * Destructively clears the Cleverphant-specific web content so that, after the
 * tyre-domain demo seeders re-run, /duk and /tinklarastis surface ONLY the
 * Protera (tyre) FAQ + blog — the inverse of {@see CleverphantContentWiper}.
 *
 * Called EXCLUSIVELY by `protera:install --fresh`, BEFORE the demo Pages / News /
 * NewsCategories / Faq / Menus seeders. The additive default flow
 * (`protera:install` without `--fresh`) never touches this class — the Cleverphant
 * content is preserved there (both content sets coexist).
 *
 * Scope — WIPES only Cleverphant-owned web content:
 *   - `pages`          — every page whose LT slug is a Cleverphant-EXCLUSIVE slug
 *                        ({@see CLEVERPHANT_PAGE_SLUGS} minus {@see PROTERA_OVERLAP_SLUGS}).
 *                        The three overlap slugs (`kontaktai` / `pristatymas` /
 *                        `privatumo-politika`) also exist in the tyre demo, so they
 *                        are KEPT (task Risk #2 — deleting them would 404 the demo
 *                        links); the demo Pages seeder recreates any deleted rows.
 *   - `news` + `news_categories` — only the Cleverphant blog category
 *                        (`mokyklu-svetaines`) and its articles (the demo seeders
 *                        recreate the tyre categories + articles afterwards).
 *   - `faqs` + `faq_categories`  — only the Cleverphant FAQ category
 *                        (`bendrieji-klausimai`) and its questions.
 *   - `menu_items`     — `type='model'` items that point at a wiped row (dangling
 *                        Page/News/NewsCategory/Faq/FaqCategory link).
 *
 * NEVER TOUCHES: the shop catalog, users/roles, locales, the `themes` table, the
 * three overlap pages, or any tyre-demo News/Faq category.
 *
 * Deletion is per-model (`forceDelete()` on loaded rows, not a bulk builder
 * delete) so the model events fire — HasSlugs cleans the polymorphic `slugs`
 * lookup rows on force-delete, and Spatie Media removes its files. A bulk builder
 * delete would silently orphan those, breaking slug resolution when the demo
 * seeders recreate a row with the same slug. `withoutForeignKeyConstraints`
 * guards the ordering (children before parents) and the whole run is wrapped in a
 * transaction — a mid-wipe failure rolls back rather than leaving a half-emptied
 * site (task Risk #2).
 *
 * Reverse operation: `cleverphant:install --fresh` (task-29) wipes the inverse set.
 */
final class ProteraContentWiper extends Seeder
{
    /**
     * LT slugs of the pages the Cleverphant theme owns.
     *
     * ⚠️ KEEP IN SYNC with {@see CleverphantContentWiper::CLEVERPHANT_PAGE_SLUGS}
     * (task-29) AND {@see CleverphantPagesSeeder::pages()}
     * (same 10 slugs). Duplicated as an explicit constant — rather than a
     * cross-package `require` on the Cleverphant theme — so the two independent
     * theme packages share one stable, greppable contract without runtime
     * coupling (task Risk #1 / Risk #4).
     *
     * @var list<string>
     */
    public const CLEVERPHANT_PAGE_SLUGS = [
        'index',
        'mokyklos',
        'poziuris',
        'pradeti',
        'pristatymas',
        'kontaktai',
        'duk',
        'begalinis-ciklas',
        'privatumo-politika',
        'straipsnis-del-bendruju-reikalavimu',
    ];

    /**
     * LT slugs that exist in BOTH the Cleverphant theme AND the tyre demo.
     *
     * These are KEPT rather than deleted: the demo `PagesContentSeeder`
     * recreates/keeps them (firstOrCreate) and menu/custom-link references would
     * 404 if we removed them mid-restore (task Risk #2). Only the
     * Cleverphant-EXCLUSIVE slugs (CLEVERPHANT_PAGE_SLUGS minus these) are wiped.
     *
     * @var list<string>
     */
    private const PROTERA_OVERLAP_SLUGS = [
        'kontaktai',
        'pristatymas',
        'privatumo-politika',
    ];

    /**
     * LT slug of the Cleverphant blog (News) category — its articles + the
     * category itself are wiped. The tyre demo categories are left untouched.
     */
    private const CLEVERPHANT_NEWS_CATEGORY_SLUG = 'mokyklu-svetaines';

    /**
     * LT slug of the Cleverphant FAQ category — its questions + the category
     * itself are wiped. The tyre demo FAQ categories are left untouched.
     */
    private const CLEVERPHANT_FAQ_CATEGORY_SLUG = 'bendrieji-klausimai';

    /**
     * The linkable model classes whose menu items become orphaned by the wipe.
     *
     * @var list<class-string<Model>>
     */
    private const WIPED_LINKABLE_TYPES = [
        Page::class,
        News::class,
        NewsCategory::class,
        Faq::class,
        FaqCategory::class,
    ];

    /**
     * Dry-run mode — print the plan, never touch the DB.
     */
    protected bool $dryRun = false;

    /**
     * Enable dry-run mode (print what WOULD be wiped, write nothing).
     */
    public function dryRun(bool $dryRun = true): self
    {
        $this->dryRun = $dryRun;

        return $this;
    }

    /**
     * Seeder entry point — delegates to {@see wipeCleverphantContent()} so the
     * class can also be run via `db:seed`.
     */
    public function run(): void
    {
        $this->wipeCleverphantContent();
    }

    /**
     * Wipe every piece of Cleverphant-owned web content (Pages/News/Faq/Menus).
     *
     * In dry-run mode nothing is written — a plan is printed instead. Otherwise
     * the whole wipe runs inside a single transaction with FK checks disabled so
     * a failure rolls back cleanly and child/pivot rows never block the delete.
     */
    public function wipeCleverphantContent(): void
    {
        if ($this->dryRun) {
            $this->reportPlan();

            return;
        }

        DB::transaction(function (): void {
            Schema::withoutForeignKeyConstraints(function (): void {
                // Children before parents: news → news_categories,
                // faqs → faq_categories. Pages next, then the menu orphans that
                // any of the above left dangling.
                $this->wipeCleverphantNews();
                $this->wipeCleverphantFaqs();
                $this->wipeCleverphantPages();
                $this->wipeOrphanMenuItems();
            });
        });
    }

    /**
     * Force-delete the Cleverphant blog category (`mokyklu-svetaines`) plus its
     * articles. Tyre-demo News categories are left in place.
     */
    private function wipeCleverphantNews(): void
    {
        $categoryIds = NewsCategory::withTrashed()
            ->where('slug->lt', self::CLEVERPHANT_NEWS_CATEGORY_SLUG)
            ->pluck('id');

        $articles = News::withTrashed()
            ->whereIn('primary_category_id', $categoryIds)
            ->get();
        foreach ($articles as $article) {
            $article->forceDelete();
        }

        $categories = NewsCategory::withTrashed()->whereKey($categoryIds)->get();
        foreach ($categories as $category) {
            $category->forceDelete();
        }

        $this->line("Cleverphant News wiped: {$articles->count()} articles, {$categories->count()} category");
    }

    /**
     * Force-delete the Cleverphant FAQ category (`bendrieji-klausimai`) plus its
     * questions. Tyre-demo FAQ categories are left in place.
     */
    private function wipeCleverphantFaqs(): void
    {
        $categoryIds = FaqCategory::withTrashed()
            ->where('slug->lt', self::CLEVERPHANT_FAQ_CATEGORY_SLUG)
            ->pluck('id');

        $faqs = Faq::withTrashed()
            ->whereIn('faq_category_id', $categoryIds)
            ->get();
        foreach ($faqs as $faq) {
            $faq->forceDelete();
        }

        $categories = FaqCategory::withTrashed()->whereKey($categoryIds)->get();
        foreach ($categories as $category) {
            $category->forceDelete();
        }

        $this->line("Cleverphant FAQ wiped: {$faqs->count()} items, {$categories->count()} category");
    }

    /**
     * Force-delete every page whose LT slug is Cleverphant-exclusive.
     *
     * The three overlap slugs (kontaktai/pristatymas/privatumo-politika) share
     * the tyre demo and are KEPT — the demo Pages seeder owns them. Empty-slug
     * pages are structural/system rows and are never touched.
     */
    private function wipeCleverphantPages(): void
    {
        $exclusive = array_flip(array_diff(self::CLEVERPHANT_PAGE_SLUGS, self::PROTERA_OVERLAP_SLUGS));
        $deleted = 0;

        foreach (Page::withTrashed()->get() as $page) {
            $slug = (string) $page->getTranslation('slug', 'lt', false);

            if ($slug === '' || ! isset($exclusive[$slug])) {
                continue;
            }

            $page->forceDelete();
            $deleted++;
        }

        $this->line("Cleverphant pages wiped: {$deleted} (kept ".count(self::PROTERA_OVERLAP_SLUGS).' shared demo slugs + system rows)');
    }

    /**
     * Delete `type='model'` menu items whose linkable target no longer exists.
     *
     * The content wipe leaves menu items pointing at deleted Page / News /
     * NewsCategory / Faq / FaqCategory rows; those render as dead links. The demo
     * MenusSeeder (run after the restore) re-seeds `web.main` / `web.footer` when
     * empty, so pruning the orphans keeps the storefront nav clean.
     */
    private function wipeOrphanMenuItems(): void
    {
        $deleted = 0;

        $candidates = MenuItem::query()
            ->where('type', 'model')
            ->whereIn('linkable_type', self::WIPED_LINKABLE_TYPES)
            ->get();

        foreach ($candidates as $item) {
            $class = (string) $item->linkable_type;

            if (! class_exists($class)) {
                continue;
            }

            /** @var class-string<Model> $class */
            $stillExists = $class::withTrashed()
                ->whereKey($item->linkable_id)
                ->exists();

            if ($stillExists) {
                continue;
            }

            $item->delete();
            $deleted++;
        }

        $this->line("Menu items pruned: {$deleted} orphaned model links");
    }

    /**
     * Print the dry-run plan — counts of what a real wipe would remove.
     */
    private function reportPlan(): void
    {
        $exclusive = array_flip(array_diff(self::CLEVERPHANT_PAGE_SLUGS, self::PROTERA_OVERLAP_SLUGS));

        $pagesToWipe = Page::withTrashed()->get()->filter(function (Page $page) use ($exclusive): bool {
            $slug = (string) $page->getTranslation('slug', 'lt', false);

            return $slug !== '' && isset($exclusive[$slug]);
        })->count();

        $newsCategoryIds = NewsCategory::withTrashed()
            ->where('slug->lt', self::CLEVERPHANT_NEWS_CATEGORY_SLUG)
            ->pluck('id');
        $faqCategoryIds = FaqCategory::withTrashed()
            ->where('slug->lt', self::CLEVERPHANT_FAQ_CATEGORY_SLUG)
            ->pluck('id');

        $this->line('<fg=yellow>DRY</> would wipe: '
            .News::withTrashed()->whereIn('primary_category_id', $newsCategoryIds)->count().' cleverphant news, '
            .$newsCategoryIds->count().' news category, '
            .Faq::withTrashed()->whereIn('faq_category_id', $faqCategoryIds)->count().' cleverphant faqs, '
            .$faqCategoryIds->count().' faq category, '
            .$pagesToWipe.' cleverphant pages (+ their orphaned menu items)');
    }

    /**
     * Emit a console line when a command is attached (setCommand), else no-op.
     */
    private function line(string $message): void
    {
        $this->command?->line("  - {$message}");
    }
}
