<?php

// phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedNamespaceFound -- APD is the established namespace shared with the Pro add-on and retained for backward compatibility.
namespace APD\Portability;

use APD\PostTypes\ProductConfigPostType;
use APD\PostTypes\TemplatePostType;
use APD\PostTypes\ThemePostType;
use APD\Repositories\ProductConfigRepository;
use APD\Repositories\TemplateRepository;
use APD\Support\MetaKeys;
use APD\Support\ModelUploads;
use APD\Support\SvgSanitizer;
use APD\Theme\DesignerTheme;

defined('ABSPATH') || exit;

final class ProductPackageImporter
{
    private const STAGE_TTL = 1800;

    private array $createdPosts = [];
    private array $createdAttachments = [];

    public function stageUpload(array $file) : array|\WP_Error
    {
        $error = (int) ($file['error'] ?? UPLOAD_ERR_NO_FILE);
        $temporaryPath = (string) ($file['tmp_name'] ?? '');
        $filename = sanitize_file_name((string) ($file['name'] ?? ''));
        if ($error !== UPLOAD_ERR_OK || $temporaryPath === '' || ! is_uploaded_file($temporaryPath) && PHP_SAPI !== 'cli') {
            return new \WP_Error('package_upload_failed', __('The package upload could not be read.', 'asuka-product-designer'));
        }
        if (! str_ends_with(strtolower($filename), '.zip')) {
            return new \WP_Error('package_extension_invalid', __('Choose an .asuka.zip package.', 'asuka-product-designer'));
        }

        $extracted = ProductPackageArchive::extract($temporaryPath);
        if (is_wp_error($extracted)) {
            return $extracted;
        }
        $preview = $this->previewExtracted($extracted);
        ProductPackageArchive::removeDirectory((string) $extracted['directory']);
        if (is_wp_error($preview)) {
            return $preview;
        }

        $stageDirectory = ProductPackageArchive::temporaryDirectory('apd-package-stage');
        if (is_wp_error($stageDirectory)) {
            return $stageDirectory;
        }
        $token = strtolower(wp_generate_password(40, false, false));
        $stagedPath = $stageDirectory . '/' . $token . '.asuka.zip';
        if (! copy($temporaryPath, $stagedPath)) {
            ProductPackageArchive::removeDirectory($stageDirectory);
            return new \WP_Error('package_stage_failed', __('The package could not be staged for review.', 'asuka-product-designer'));
        }

        set_transient($this->transientKey($token), [
            'userId' => get_current_user_id(),
            'path' => $stagedPath,
            'directory' => $stageDirectory,
            'filename' => $filename,
        ], self::STAGE_TTL);

        return ['token' => $token, 'preview' => $preview];
    }

    public function previewToken(string $token) : array|\WP_Error
    {
        $stage = $this->stage($token);
        if (is_wp_error($stage)) {
            return $stage;
        }
        $extracted = ProductPackageArchive::extract((string) $stage['path']);
        if (is_wp_error($extracted)) {
            return $extracted;
        }
        try {
            return $this->previewExtracted($extracted);
        } finally {
            ProductPackageArchive::removeDirectory((string) $extracted['directory']);
        }
    }

    public function importToken(string $token, array $options = []) : array|\WP_Error
    {
        $stage = $this->stage($token);
        if (is_wp_error($stage)) {
            return $stage;
        }
        $extracted = ProductPackageArchive::extract((string) $stage['path']);
        if (is_wp_error($extracted)) {
            return $extracted;
        }

        $this->createdPosts = [];
        $this->createdAttachments = [];
        try {
            $result = $this->importExtracted($extracted, $options);
            if (is_wp_error($result)) {
                $this->rollbackCreatedContent();
                return $result;
            }
            $this->discard($token, $stage);
            return $result;
        } catch (\Throwable $exception) {
            $this->rollbackCreatedContent();
            return new \WP_Error('package_import_exception', sanitize_text_field($exception->getMessage()));
        } finally {
            ProductPackageArchive::removeDirectory((string) $extracted['directory']);
        }
    }

    public function discardToken(string $token) : void
    {
        $stage = $this->stage($token);
        if (! is_wp_error($stage)) {
            $this->discard($token, $stage);
        }
    }

    private function importExtracted(array $extracted, array $options) : array|\WP_Error
    {
        $options = wp_parse_args($options, [
            'existingConfigId' => 0,
            'dependencyPolicy' => 'copy',
        ]);
        $policy = sanitize_key((string) $options['dependencyPolicy']);
        if (! in_array($policy, ['copy', 'reuse', 'replace'], true)) {
            $policy = 'copy';
        }

        $root = (string) $extracted['directory'];
        $manifest = (array) $extracted['manifest'];
        $codec = new PortableProductDocument();
        $product = ProductPackageArchive::readJson($root . '/product.json');
        if (is_wp_error($product) || ! $codec->isDocument($product, PortableProductDocument::PRODUCT_FORMAT)) {
            return new \WP_Error('package_product_invalid', __('The custom product document is missing or invalid.', 'asuka-product-designer'));
        }

        $assetMap = $this->importAssets($root, (array) ($manifest['assets'] ?? []));
        if (is_wp_error($assetMap)) {
            return $assetMap;
        }

        $themeMap = [];
        foreach ((array) ($manifest['dependencies']['themes'] ?? []) as $themeReference) {
            $path = $this->dependencyPath($root, (string) ($themeReference['file'] ?? ''));
            if (is_wp_error($path)) {
                return $path;
            }
            $theme = ProductPackageArchive::readJson($path);
            if (is_wp_error($theme) || ! $codec->isDocument($theme, PortableProductDocument::THEME_FORMAT)) {
                return new \WP_Error('package_theme_invalid', __('A designer theme inside the package is invalid.', 'asuka-product-designer'));
            }
            $reference = (string) ($theme['reference'] ?? $themeReference['reference'] ?? '');
            $theme['theme'] = $this->restoreReferences((array) ($theme['theme'] ?? []), $assetMap, [], []);
            $themeId = $this->importTheme($theme, $policy);
            if ($themeId <= 0) {
                return new \WP_Error('package_theme_import_failed', __('A designer theme could not be imported.', 'asuka-product-designer'));
            }
            if ($reference !== '') {
                $themeMap[$reference] = $themeId;
            }
        }

        $templateMap = [];
        $templateReference = $manifest['dependencies']['template'] ?? null;
        if (is_array($templateReference) && ! empty($templateReference['file'])) {
            $path = $this->dependencyPath($root, (string) $templateReference['file']);
            if (is_wp_error($path)) {
                return $path;
            }
            $template = ProductPackageArchive::readJson($path);
            if (is_wp_error($template) || ! $codec->isDocument($template, PortableProductDocument::TEMPLATE_FORMAT)) {
                return new \WP_Error('package_template_invalid', __('The template inside the package is invalid.', 'asuka-product-designer'));
            }
            $template = $this->restoreReferences($template, $assetMap, $themeMap, []);
            $wordpressTemplate = $codec->templateToWordPress($template, $themeMap);
            if (is_wp_error($wordpressTemplate)) {
                return $wordpressTemplate;
            }
            $templateId = $this->importTemplate($wordpressTemplate, $policy);
            if ($templateId <= 0) {
                return new \WP_Error('package_template_import_failed', __('The template could not be imported.', 'asuka-product-designer'));
            }
            $reference = (string) ($templateReference['reference'] ?? PortableProductDocument::templateReference((string) ($template['metadata']['slug'] ?? '')));
            if ($reference !== '') {
                $templateMap[$reference] = $templateId;
            }
        }

        $product = $this->restoreReferences($product, $assetMap, $themeMap, $templateMap);
        $wordpressProduct = $codec->productToWordPress($product, $themeMap, $templateMap);
        if (is_wp_error($wordpressProduct)) {
            return $wordpressProduct;
        }
        $existingConfigId = absint($options['existingConfigId']);
        if ($existingConfigId > 0 && get_post_type($existingConfigId) !== ProductConfigPostType::TYPE) {
            $existingConfigId = 0;
        }
        $configId = (new ProductConfigRepository())->import($wordpressProduct, $existingConfigId);
        if ($configId <= 0) {
            return new \WP_Error('package_product_import_failed', __('The custom product could not be imported.', 'asuka-product-designer'));
        }
        if ($existingConfigId <= 0) {
            $this->createdPosts[] = $configId;
        }

        return [
            'configId' => $configId,
            'assetsImported' => count($this->createdAttachments),
            'assetsResolved' => count($assetMap),
            'themesImported' => count($themeMap),
            'templateImported' => $templateMap !== [],
        ];
    }

    private function previewExtracted(array $extracted) : array|\WP_Error
    {
        $root = (string) $extracted['directory'];
        $manifest = (array) $extracted['manifest'];
        $codec = new PortableProductDocument();
        $product = ProductPackageArchive::readJson($root . '/product.json');
        if (is_wp_error($product) || ! $codec->isDocument($product, PortableProductDocument::PRODUCT_FORMAT)) {
            return new \WP_Error('package_product_invalid', __('The custom product document is missing or invalid.', 'asuka-product-designer'));
        }
        $productSlug = sanitize_title((string) ($product['metadata']['slug'] ?? ''));
        $existingProduct = $productSlug !== '' ? get_page_by_path($productSlug, OBJECT, ProductConfigPostType::TYPE) : null;
        $templateReference = $manifest['dependencies']['template'] ?? null;
        $themeReferences = (array) ($manifest['dependencies']['themes'] ?? []);
        $assetBytes = array_sum(array_map(static fn(array $asset) : int => max(0, (int) ($asset['bytes'] ?? 0)), (array) ($manifest['assets'] ?? [])));

        return [
            'title' => (string) ($product['metadata']['name'] ?? __('Imported custom product', 'asuka-product-designer')),
            'slug' => $productSlug,
            'exporter' => (string) ($manifest['exporter']['name'] ?? ''),
            'exporterVersion' => (string) ($manifest['exporter']['version'] ?? ''),
            'designerVersion' => (string) ($manifest['designerVersion'] ?? ''),
            'generatedAt' => (string) ($manifest['generatedAt'] ?? ''),
            'assets' => count((array) ($manifest['assets'] ?? [])),
            'assetBytes' => $assetBytes,
            'template' => is_array($templateReference) ? (string) ($templateReference['name'] ?? '') : '',
            'themes' => array_values(array_filter(array_map(static fn(array $theme) : string => (string) ($theme['name'] ?? ''), $themeReferences))),
            'externalHosts' => (array) ($manifest['externalHosts'] ?? []),
            'existingProductId' => $existingProduct instanceof \WP_Post ? (int) $existingProduct->ID : 0,
            'totalBytes' => (int) ($extracted['totalBytes'] ?? 0),
        ];
    }

    private function importAssets(string $root, array $assets) : array|\WP_Error
    {
        $map = [];
        $pending = [];
        $dependencyIds = [];
        foreach ($assets as $asset) {
            if (! is_array($asset) || ! preg_match('/^[a-f0-9]{24}$/', (string) ($asset['id'] ?? ''))) {
                return new \WP_Error('package_asset_manifest_invalid', __('The package media manifest is invalid.', 'asuka-product-designer'));
            }
            $pending[(string) $asset['id']] = $asset;
            foreach ((array) ($asset['dependencies'] ?? []) as $dependencyId) {
                $dependencyIds[(string) $dependencyId] = true;
            }
        }

        while ($pending !== []) {
            $progress = false;
            foreach ($pending as $assetId => $asset) {
                $dependencies = (array) ($asset['dependencies'] ?? []);
                if (array_diff($dependencies, array_keys($map)) !== []) {
                    continue;
                }
                $path = $this->dependencyPath($root, (string) ($asset['path'] ?? ''));
                if (is_wp_error($path) || ! str_starts_with(wp_normalize_path($path), wp_normalize_path($root . '/assets/'))) {
                    return new \WP_Error('package_asset_path_invalid', __('A package media path is invalid.', 'asuka-product-designer'));
                }
                $imported = $this->importAsset($path, $asset, $map, isset($dependencyIds[$assetId]));
                if (is_wp_error($imported)) {
                    return $imported;
                }
                $map[$assetId] = $imported;
                unset($pending[$assetId]);
                $progress = true;
            }
            if (! $progress) {
                return new \WP_Error('package_asset_dependency_invalid', __('The package contains unresolved media dependencies.', 'asuka-product-designer'));
            }
        }

        return $map;
    }

    private function importAsset(string $path, array $asset, array $assetMap, bool $isDeclaredDependency) : array|\WP_Error
    {
        $expectedHash = strtolower((string) ($asset['sha256'] ?? ''));
        $actualHash = hash_file('sha256', $path);
        if (! is_string($actualHash) || ! preg_match('/^[a-f0-9]{64}$/', $expectedHash) || ! hash_equals($expectedHash, $actualHash)) {
            return new \WP_Error('package_asset_checksum_invalid', __('A package media file is damaged.', 'asuka-product-designer'));
        }

        $existing = get_posts([
            'post_type' => 'attachment',
            'post_status' => 'inherit',
            'posts_per_page' => 1,
            'fields' => 'ids',
            'meta_key' => '_apd_package_asset_sha256', // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_key -- Package deduplication intentionally resolves an attachment by its indexed content fingerprint.
            'meta_value' => $expectedHash, // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_value -- The SHA-256 lookup prevents duplicate media imports.
            'no_found_rows' => true,
        ]);
        if (! empty($existing[0])) {
            $url = wp_get_attachment_url((int) $existing[0]);
            if (is_string($url) && $url !== '') {
                return ['attachmentId' => (int) $existing[0], 'url' => $url];
            }
        }

        $extension = strtolower((string) ($asset['extension'] ?? pathinfo($path, PATHINFO_EXTENSION)));
        $allowed = ['jpg', 'jpeg', 'png', 'gif', 'webp', 'avif', 'svg', 'glb', 'gltf', 'bin', 'woff2', 'woff', 'ttf', 'otf'];
        if (! in_array($extension, $allowed, true)) {
            return new \WP_Error('package_asset_type_invalid', __('A package media type is not allowed.', 'asuka-product-designer'));
        }
        if ($extension === 'bin' && ! $isDeclaredDependency) {
            return new \WP_Error('package_asset_type_invalid', __('A standalone binary package asset is not allowed.', 'asuka-product-designer'));
        }

        $temporary = wp_tempnam((string) ($asset['filename'] ?? ('asset.' . $extension)));
        if (! is_string($temporary) || ! copy($path, $temporary)) {
            return new \WP_Error('package_asset_temp_failed', __('A package media file could not be prepared.', 'asuka-product-designer'));
        }
        $filename = sanitize_file_name((string) ($asset['filename'] ?? basename($path)));
        if ($filename === '' || strtolower((string) pathinfo($filename, PATHINFO_EXTENSION)) !== $extension) {
            $filename = 'asuka-asset-' . substr($expectedHash, 0, 12) . '.' . $extension;
        }

        if ($extension === 'gltf') {
            $raw = file_get_contents($temporary);
            if (is_string($raw)) {
                $raw = $this->replaceAssetReferencesInString($raw, $assetMap);
                file_put_contents($temporary, $raw, LOCK_EX);
            }
        }
        $validation = $this->validateAssetContents($temporary, $extension, $filename);
        if (is_wp_error($validation)) {
            wp_delete_file($temporary);
            return $validation;
        }

        $uploads = wp_upload_dir();
        $baseDirectory = (string) ($uploads['basedir'] ?? '');
        $storagePath = trailingslashit($baseDirectory) . 'asuka-product-designer/imports';
        if (! empty($uploads['error']) || $baseDirectory === '' || ! wp_mkdir_p($storagePath)) {
            wp_delete_file($temporary);
            return new \WP_Error('package_asset_storage_failed', __('The WordPress media directory is not writable.', 'asuka-product-designer'));
        }
        $storedFilename = wp_unique_filename($storagePath, $filename);
        $storedPath = trailingslashit($storagePath) . $storedFilename;
        if (! copy($temporary, $storedPath)) {
            wp_delete_file($temporary);
            return new \WP_Error('package_asset_storage_failed', __('A package media file could not be stored.', 'asuka-product-designer'));
        }
        wp_delete_file($temporary);

        $mime = sanitize_mime_type((string) ($asset['mime'] ?? 'application/octet-stream'));
        $attachmentId = wp_insert_attachment([
            'post_mime_type' => $mime !== '' ? $mime : 'application/octet-stream',
            'post_title' => sanitize_text_field((string) pathinfo($storedFilename, PATHINFO_FILENAME)),
            'post_status' => 'inherit',
        ], $storedPath, 0, true);
        if (is_wp_error($attachmentId) || (int) $attachmentId <= 0) {
            wp_delete_file($storedPath);
            return new \WP_Error('package_asset_attachment_failed', __('A package media attachment could not be created.', 'asuka-product-designer'));
        }

        $attachmentId = (int) $attachmentId;
        $this->createdAttachments[] = $attachmentId;
        update_attached_file($attachmentId, $storedPath);
        update_post_meta($attachmentId, '_apd_package_asset_sha256', $expectedHash);
        update_post_meta($attachmentId, '_apd_package_asset_id', sanitize_key((string) ($asset['id'] ?? '')));
        if (str_starts_with($mime, 'image/') && $extension !== 'svg') {
            if (! function_exists('wp_generate_attachment_metadata')) {
                require_once ABSPATH . 'wp-admin/includes/image.php';
            }
            $metadata = wp_generate_attachment_metadata($attachmentId, $storedPath);
            if (is_array($metadata)) {
                wp_update_attachment_metadata($attachmentId, $metadata);
            }
        }
        $url = wp_get_attachment_url($attachmentId);
        if (! is_string($url) || $url === '') {
            return new \WP_Error('package_asset_url_failed', __('A package media URL could not be generated.', 'asuka-product-designer'));
        }
        return ['attachmentId' => $attachmentId, 'url' => $url];
    }

    private function validateAssetContents(string $path, string $extension, string $filename) : true|\WP_Error
    {
        if (in_array($extension, ['jpg', 'jpeg', 'png', 'gif', 'webp', 'avif'], true)) {
            $mime = function_exists('wp_get_image_mime') ? wp_get_image_mime($path) : false;
            if (! is_string($mime) || $mime === '') {
                return new \WP_Error('package_asset_content_invalid', __('A package image is invalid.', 'asuka-product-designer'));
            }
            return true;
        }
        if ($extension === 'svg') {
            $raw = file_get_contents($path);
            if (is_string($raw) && ! preg_match('/<!ENTITY/i', $raw)) {
                $raw = preg_replace('/<!DOCTYPE[^>]*(?:\[[\s\S]*?\]\s*)?>/i', '', $raw) ?? '';
            }
            $sanitized = is_string($raw) ? SvgSanitizer::sanitize($raw) : '';
            if ($sanitized === '' || file_put_contents($path, $sanitized, LOCK_EX) === false) {
                return new \WP_Error(
                    'package_asset_content_invalid',
                    sprintf(/* translators: Placeholder values are populated from the current Asuka Designer record. */ __('The package SVG "%s" is unsafe or invalid.', 'asuka-product-designer'), sanitize_file_name($filename))
                );
            }
            return true;
        }
        if (in_array($extension, ['glb', 'gltf'], true)) {
            $checked = (new ModelUploads())->validateUpload([
                'name' => $filename,
                'tmp_name' => $path,
                'type' => $extension === 'glb' ? 'model/gltf-binary' : 'model/gltf+json',
                'error' => 0,
            ]);
            if (! empty($checked['error'])) {
                return new \WP_Error('package_asset_content_invalid', sanitize_text_field((string) $checked['error']));
            }
            return true;
        }
        if (in_array($extension, ['woff2', 'woff', 'ttf', 'otf'], true)) {
            $header = file_get_contents($path, false, null, 0, 4);
            $valid = match ($extension) {
                'woff2' => $header === 'wOF2',
                'woff' => $header === 'wOFF',
                'otf' => $header === 'OTTO',
                default => $header === "\x00\x01\x00\x00" || $header === 'true',
            };
            if (! $valid) {
                return new \WP_Error('package_asset_content_invalid', __('A package font is invalid.', 'asuka-product-designer'));
            }
        }
        return true;
    }

    private function importTheme(array $theme, string $policy) : int
    {
        $metadata = (array) ($theme['metadata'] ?? []);
        $slug = sanitize_title((string) ($metadata['slug'] ?? ''));
        $existing = $slug !== '' ? get_page_by_path($slug, OBJECT, ThemePostType::TYPE) : null;
        if ($existing instanceof \WP_Post && $policy === 'reuse') {
            return (int) $existing->ID;
        }

        $postId = $existing instanceof \WP_Post && $policy === 'replace' ? (int) $existing->ID : 0;
        $postarr = [
            'post_type' => ThemePostType::TYPE,
            'post_status' => 'publish',
            'post_title' => sanitize_text_field((string) ($metadata['name'] ?? __('Imported theme', 'asuka-product-designer'))),
            'post_name' => $slug,
        ];
        if ($postId > 0) {
            $postarr['ID'] = $postId;
            $result = wp_update_post(wp_slash($postarr), true);
        } else {
            $result = wp_insert_post(wp_slash($postarr), true);
        }
        if (is_wp_error($result) || (int) $result <= 0) {
            return 0;
        }
        $postId = (int) $result;
        if (! ($existing instanceof \WP_Post && $policy === 'replace')) {
            $this->createdPosts[] = $postId;
        }
        (new DesignerTheme())->savePostPayload($postId, (array) ($theme['theme'] ?? []));
        return $postId;
    }

    private function importTemplate(array $template, string $policy) : int
    {
        $post = (array) ($template['post'] ?? []);
        $slug = sanitize_title((string) ($post['slug'] ?? ''));
        $existing = $slug !== '' ? get_page_by_path($slug, OBJECT, TemplatePostType::TYPE) : null;
        if ($existing instanceof \WP_Post && $policy === 'reuse') {
            return (int) $existing->ID;
        }
        $existingId = $existing instanceof \WP_Post && $policy === 'replace' ? (int) $existing->ID : 0;
        $templateId = (new TemplateRepository())->import($template, $existingId);
        if ($templateId > 0 && $existingId <= 0) {
            $this->createdPosts[] = $templateId;
        }
        return $templateId;
    }

    private function restoreReferences(mixed $value, array $assetMap, array $themeMap, array $templateMap, string $key = '') : mixed
    {
        if (is_array($value)) {
            $restored = [];
            foreach ($value as $childKey => $childValue) {
                $restored[$childKey] = $this->restoreReferences($childValue, $assetMap, $themeMap, $templateMap, (string) $childKey);
            }
            return $restored;
        }
        if (! is_string($value)) {
            return $value;
        }

        if (preg_match('/^asuka-attachment:\/\/([a-f0-9]{24})$/', $value, $matches) === 1) {
            return (int) ($assetMap[$matches[1]]['attachmentId'] ?? 0);
        }
        if (preg_match('/^asuka-asset:\/\/([a-f0-9]{24})$/', $value, $matches) === 1) {
            return (string) ($assetMap[$matches[1]]['url'] ?? '');
        }
        if (in_array(strtolower($key), ['themeid', 'theme_id'], true)) {
            return isset($themeMap[$value]) ? (string) $themeMap[$value] : $value;
        }
        if (in_array($key, [MetaKeys::LEGACY_TEMPLATE_ID, MetaKeys::PRODUCT_CONFIG_TEMPLATE_ID], true)) {
            return isset($templateMap[$value]) ? (string) $templateMap[$value] : $value;
        }
        return $this->replaceAssetReferencesInString($value, $assetMap);
    }

    private function replaceAssetReferencesInString(string $value, array $assetMap) : string
    {
        return preg_replace_callback(
            '/asuka-(?:asset|attachment):\/\/([a-f0-9]{24})/',
            static fn(array $matches) : string => (string) ($assetMap[$matches[1]]['url'] ?? ''),
            $value
        ) ?? $value;
    }

    private function dependencyPath(string $root, string $relativePath) : string|\WP_Error
    {
        $relativePath = trim(str_replace('\\', '/', $relativePath), '/');
        if ($relativePath === '' || str_contains($relativePath, '..') || preg_match('/^[a-zA-Z]:/', $relativePath)) {
            return new \WP_Error('package_dependency_path_invalid', __('A package dependency path is invalid.', 'asuka-product-designer'));
        }
        $path = realpath(untrailingslashit($root) . '/' . $relativePath);
        $normalizedRoot = untrailingslashit(wp_normalize_path($root));
        if (! is_string($path) || ! str_starts_with(wp_normalize_path($path), $normalizedRoot . '/')) {
            return new \WP_Error('package_dependency_missing', __('A package dependency is missing.', 'asuka-product-designer'));
        }
        return wp_normalize_path($path);
    }

    private function stage(string $token) : array|\WP_Error
    {
        $token = sanitize_key($token);
        if ($token === '') {
            return new \WP_Error('package_stage_missing', __('The package review session has expired.', 'asuka-product-designer'));
        }
        $stage = get_transient($this->transientKey($token));
        if (
            ! is_array($stage)
            || (int) ($stage['userId'] ?? 0) !== get_current_user_id()
            || ! is_file((string) ($stage['path'] ?? ''))
        ) {
            return new \WP_Error('package_stage_missing', __('The package review session has expired.', 'asuka-product-designer'));
        }
        return $stage;
    }

    private function discard(string $token, array $stage) : void
    {
        delete_transient($this->transientKey($token));
        ProductPackageArchive::removeDirectory((string) ($stage['directory'] ?? ''));
    }

    private function transientKey(string $token) : string
    {
        return 'apd_product_package_' . get_current_user_id() . '_' . substr(hash('sha256', sanitize_key($token)), 0, 24);
    }

    private function rollbackCreatedContent() : void
    {
        foreach (array_reverse(array_unique($this->createdPosts)) as $postId) {
            wp_delete_post((int) $postId, true);
        }
        foreach (array_reverse(array_unique($this->createdAttachments)) as $attachmentId) {
            wp_delete_attachment((int) $attachmentId, true);
        }
    }
}
