<?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\ThemePostType;
use APD\Repositories\ProductConfigRepository;
use APD\Repositories\TemplateRepository;
use APD\Support\AssetCatalog;
use APD\Support\MetaKeys;
use APD\Support\RuntimeDataSchema;
use APD\Theme\DesignerTheme;

defined('ABSPATH') || exit;

final class ProductPackageExporter
{
    public function export(int $configId, array $options = []) : array|\WP_Error
    {
        $options = wp_parse_args($options, [
            'includeAssets' => true,
            'includeTemplate' => true,
            'includeThemes' => true,
        ]);

        $repository = new ProductConfigRepository();
        $product = $repository->export($configId);
        if ($product === []) {
            return new \WP_Error('package_product_missing', __('The selected custom product could not be found.', 'asuka-product-designer'));
        }

        $packageRoot = ProductPackageArchive::temporaryDirectory('apd-package-build');
        $outputRoot = ProductPackageArchive::temporaryDirectory('apd-package-output');
        if (is_wp_error($packageRoot) || is_wp_error($outputRoot)) {
            if (is_string($packageRoot)) {
                ProductPackageArchive::removeDirectory($packageRoot);
            }
            return is_wp_error($packageRoot) ? $packageRoot : $outputRoot;
        }

        try {
            $assets = new PortableAssetRegistry();
            $codec = new PortableProductDocument();
            $product['meta'] = $this->hydratePortableDependencies(
                $this->portableMeta((array) ($product['meta'] ?? []), $assets, false)
            );
            $template = null;
            $templateId = $this->templateId((array) ($product['meta'] ?? []));
            if ((bool) $options['includeTemplate'] && $templateId > 0) {
                $template = (new TemplateRepository())->export($templateId);
                if ($template !== []) {
                    $template['meta'] = $this->portableMeta((array) ($template['meta'] ?? []), $assets, false);
                } else {
                    $template = null;
                }
            }

            $themeIds = [];
            if ((bool) $options['includeThemes']) {
                $themeIds = array_values(array_unique(array_merge(
                    $this->themeIds((array) ($product['meta'] ?? [])),
                    $this->themeIds(is_array($template['meta'] ?? null) ? $template['meta'] : [])
                )));
            }
            $themeReferences = $this->themeReferences($themeIds);
            $themes = $this->exportThemes($themeIds, $themeReferences, $codec);
            $templateReference = $template !== null
                ? PortableProductDocument::templateReference((string) ($template['post']['slug'] ?? ''))
                : '';
            $productDocument = $codec->productFromWordPress(
                $product,
                $templateReference !== '' ? $templateReference : null,
                $themeReferences
            );
            $templateDocument = $template !== null
                ? $codec->templateFromWordPress($template, $themeReferences)
                : null;
            if ((bool) $options['includeAssets']) {
                $productDocument = $assets->rewrite($productDocument);
                $templateDocument = is_array($templateDocument) ? $assets->rewrite($templateDocument) : null;
                $themes = array_map(static fn(array $theme) : array => $assets->rewrite($theme), $themes);
            }

            $write = ProductPackageArchive::writeJson($packageRoot . '/product.json', $productDocument);
            if (is_wp_error($write)) {
                return $write;
            }
            if ($templateDocument !== null) {
                $write = ProductPackageArchive::writeJson($packageRoot . '/dependencies/template.json', $templateDocument);
                if (is_wp_error($write)) {
                    return $write;
                }
            }
            foreach ($themes as $theme) {
                $themeSlug = sanitize_file_name((string) ($theme['metadata']['slug'] ?? 'theme'));
                $themePath = $packageRoot . '/dependencies/themes/' . ($themeSlug !== '' ? $themeSlug : 'theme') . '.json';
                $write = ProductPackageArchive::writeJson($themePath, $theme);
                if (is_wp_error($write)) {
                    return $write;
                }
            }
            if ((bool) $options['includeAssets']) {
                $write = $assets->write($packageRoot);
                if (is_wp_error($write)) {
                    return $write;
                }
            }

            $assetManifest = (bool) $options['includeAssets'] ? $assets->manifest() : [];
            $manifest = [
                'schema' => ProductPackageArchive::SCHEMA,
                'schemaVersion' => ProductPackageArchive::SCHEMA_VERSION,
                'designerVersion' => defined('ASUKA_PRODUCT_DESIGNER_JS_VERSION') ? ASUKA_PRODUCT_DESIGNER_JS_VERSION : '',
                'generatedAt' => gmdate('c'),
                'exporter' => [
                    'name' => 'Asuka Designer',
                    'version' => defined('ASUKA_PRODUCT_DESIGNER_VERSION') ? ASUKA_PRODUCT_DESIGNER_VERSION : '',
                ],
                'product' => [
                    'file' => 'product.json',
                    'name' => (string) ($productDocument['metadata']['name'] ?? ''),
                    'slug' => (string) ($productDocument['metadata']['slug'] ?? ''),
                ],
                'dependencies' => [
                    'template' => $templateDocument !== null ? [
                        'file' => 'dependencies/template.json',
                        'reference' => $templateReference,
                        'name' => (string) ($templateDocument['metadata']['name'] ?? ''),
                    ] : null,
                    'themes' => array_map(static fn(array $theme) : array => [
                        'file' => 'dependencies/themes/' . sanitize_file_name((string) ($theme['metadata']['slug'] ?? 'theme')) . '.json',
                        'reference' => (string) ($theme['reference'] ?? ''),
                        'name' => (string) ($theme['metadata']['name'] ?? ''),
                    ], $themes),
                ],
                'assets' => $assetManifest,
                'externalHosts' => $assets->externalHosts(),
                'options' => [
                    'assets' => (bool) $options['includeAssets'],
                    'template' => $template !== null,
                    'themes' => $themes !== [],
                ],
            ];
            $write = ProductPackageArchive::writeJson($packageRoot . '/' . ProductPackageArchive::MANIFEST_FILE, $manifest);
            if (is_wp_error($write)) {
                return $write;
            }
            $write = ProductPackageArchive::writeChecksums($packageRoot);
            if (is_wp_error($write)) {
                return $write;
            }

            $slug = sanitize_title((string) ($productDocument['metadata']['slug'] ?? $productDocument['metadata']['name'] ?? 'custom-product'));
            $filename = ($slug !== '' ? $slug : 'custom-product') . '-' . gmdate('Ymd-His') . '.asuka.zip';
            $archivePath = $outputRoot . '/' . $filename;
            $created = ProductPackageArchive::create($packageRoot, $archivePath);
            if (is_wp_error($created)) {
                return $created;
            }

            return [
                'path' => $archivePath,
                'filename' => $filename,
                'bytes' => (int) filesize($archivePath),
                'manifest' => $manifest,
                'cleanup' => [$packageRoot, $outputRoot],
            ];
        } finally {
            // The caller owns successful archive cleanup; failed builds are removed here.
            if (! isset($archivePath) || ! is_file($archivePath)) {
                ProductPackageArchive::removeDirectory($packageRoot);
                ProductPackageArchive::removeDirectory($outputRoot);
            } else {
                ProductPackageArchive::removeDirectory($packageRoot);
            }
        }
    }

    private function portableMeta(array $meta, PortableAssetRegistry $assets, bool $includeAssets) : array
    {
        $jsonKeys = array_merge(MetaKeys::jsonKeys(), ['_apd_ui']);
        foreach ($meta as $key => $value) {
            if (in_array($key, $jsonKeys, true) && is_string($value) && trim($value) !== '') {
                $decoded = json_decode($value, true);
                if (is_array($decoded) && json_last_error() === JSON_ERROR_NONE) {
                    $meta[$key] = $includeAssets ? $assets->rewrite($decoded) : $decoded;
                    continue;
                }
            }
            $meta[$key] = $includeAssets ? $assets->rewrite($value, (string) $key) : $value;
        }
        return $meta;
    }

    /**
     * Replace WordPress catalog selections with their complete portable data.
     */
    private function hydratePortableDependencies(array $meta) : array
    {
        $catalogService = new AssetCatalog();
        $filters = RuntimeDataSchema::sanitizeProductAssetFilters(
            is_array($meta[MetaKeys::LEGACY_ASSET_FILTERS] ?? null)
                ? $meta[MetaKeys::LEGACY_ASSET_FILTERS]
                : []
        );
        $catalog = $catalogService->buildPayload($filters);

        $fonts = RuntimeDataSchema::sanitizeProductFontsConfig(
            is_array($meta[MetaKeys::LEGACY_FONTS] ?? null)
                ? $meta[MetaKeys::LEGACY_FONTS]
                : []
        );
        foreach ((array) ($catalog['fonts'] ?? []) as $font) {
            if (! is_array($font)) {
                continue;
            }
            $source = sanitize_key((string) ($font['source'] ?? 'system'));
            if (! in_array($source, ['system', 'google', 'custom'], true)) {
                $source = 'system';
            }
            $fonts[$source] = $this->mergeById((array) ($fonts[$source] ?? []), [$font]);
        }
        $meta[MetaKeys::LEGACY_FONTS] = RuntimeDataSchema::sanitizeProductFontsConfig($fonts);

        $colors = RuntimeDataSchema::sanitizeProductColorsConfig(
            is_array($meta[MetaKeys::LEGACY_COLORS] ?? null)
                ? $meta[MetaKeys::LEGACY_COLORS]
                : []
        );
        $colors['items'] = $this->mergeById((array) ($catalog['colors'] ?? []), (array) ($colors['items'] ?? []));
        $colors['printingMethods'] = $this->mergeById(
            (array) ($catalog['printingMethods'] ?? []),
            (array) ($colors['printingMethods'] ?? [])
        );
        $meta[MetaKeys::LEGACY_COLORS] = RuntimeDataSchema::sanitizeProductColorsConfig($colors);

        $pricing = RuntimeDataSchema::sanitizeProductPricingConfig(
            is_array($meta[MetaKeys::LEGACY_PRICING_RULES] ?? null)
                ? $meta[MetaKeys::LEGACY_PRICING_RULES]
                : []
        );
        $selectedRules = $catalogService->getSelectedPricingRules(
            is_array($meta[MetaKeys::LEGACY_PRICING_RULE_IDS] ?? null)
                ? $meta[MetaKeys::LEGACY_PRICING_RULE_IDS]
                : []
        );
        $pricing['pricingRules'] = $this->mergeById((array) ($pricing['pricingRules'] ?? []), $selectedRules);
        $pricing['rules'] = $pricing['pricingRules'];
        $pricing['enabled'] = (bool) ($pricing['enabled'] ?? false) || $pricing['pricingRules'] !== [];
        $meta[MetaKeys::LEGACY_PRICING_RULES] = RuntimeDataSchema::sanitizeProductPricingConfig($pricing);

        $modules = is_array($meta[MetaKeys::LEGACY_MODULES] ?? null) ? $meta[MetaKeys::LEGACY_MODULES] : [];
        $modules['designs'] = is_array($modules['designs'] ?? null) ? $modules['designs'] : [];
        $modules['designs']['items'] = $this->mergeById(
            (array) ($catalog['designs'] ?? []),
            (array) ($modules['designs']['items'] ?? [])
        );
        $modules['shapes'] = is_array($modules['shapes'] ?? null) ? $modules['shapes'] : [];
        $modules['shapes']['svgAssets'] = $this->mergeById(
            (array) ($catalog['shapes'] ?? []),
            (array) ($modules['shapes']['svgAssets'] ?? [])
        );
        $meta[MetaKeys::LEGACY_MODULES] = $modules;

        $initialState = is_array($meta[MetaKeys::LEGACY_INITIAL_STATE] ?? null)
            ? $meta[MetaKeys::LEGACY_INITIAL_STATE]
            : [];
        $initialState['catalog'] = is_array($initialState['catalog'] ?? null) ? $initialState['catalog'] : [];
        $initialState['catalog']['designs'] = $this->mergeById(
            (array) ($catalog['designs'] ?? []),
            (array) ($initialState['catalog']['designs'] ?? [])
        );
        $initialState['catalog']['colors'] = $this->mergeById(
            (array) ($colors['items'] ?? []),
            (array) ($initialState['catalog']['colors'] ?? [])
        );
        $initialState['catalog']['printingMethods'] = $this->mergeById(
            (array) ($colors['printingMethods'] ?? []),
            (array) ($initialState['catalog']['printingMethods'] ?? [])
        );
        $meta[MetaKeys::LEGACY_INITIAL_STATE] = $initialState;

        unset($meta[MetaKeys::LEGACY_PRICING_RULE_IDS], $meta[MetaKeys::LEGACY_ASSET_FILTERS]);
        return $meta;
    }

    private function mergeById(array $first, array $second) : array
    {
        $keyed = [];
        $loose = [];
        foreach ([$first, $second] as $items) {
            foreach ($items as $item) {
                if (! is_array($item)) {
                    continue;
                }
                $id = sanitize_key((string) ($item['id'] ?? ''));
                if ($id === '') {
                    $loose[] = $item;
                    continue;
                }
                $keyed[$id] = isset($keyed[$id])
                    ? array_replace($keyed[$id], $item)
                    : $item;
            }
        }
        return array_values(array_merge($loose, array_values($keyed)));
    }

    private function templateId(array $meta) : int
    {
        return absint($meta[MetaKeys::PRODUCT_CONFIG_TEMPLATE_ID] ?? $meta[MetaKeys::LEGACY_TEMPLATE_ID] ?? 0);
    }

    private function themeIds(array $payload) : array
    {
        $ids = [];
        $walk = function (mixed $value, string $key = '') use (&$walk, &$ids) : void {
            if (is_array($value)) {
                foreach ($value as $childKey => $childValue) {
                    $walk($childValue, (string) $childKey);
                }
                return;
            }
            if (in_array(strtolower($key), ['themeid', 'theme_id'], true)) {
                $themeId = absint($value);
                if ($themeId > 0 && get_post_type($themeId) === ThemePostType::TYPE) {
                    $ids[] = $themeId;
                }
            }
        };
        $walk($payload);
        return array_values(array_unique($ids));
    }

    private function themeReferences(array $themeIds) : array
    {
        $references = [];
        foreach ($themeIds as $themeId) {
            $post = get_post((int) $themeId);
            if (! $post instanceof \WP_Post || $post->post_type !== ThemePostType::TYPE) {
                continue;
            }
            $reference = PortableProductDocument::themeReference((string) $post->post_name);
            if ($reference !== '') {
                $references[(int) $themeId] = $reference;
            }
        }
        return $references;
    }

    private function exportThemes(array $themeIds, array $themeReferences, PortableProductDocument $codec) : array
    {
        $service = new DesignerTheme();
        $themes = [];
        foreach ($themeIds as $themeId) {
            $post = get_post((int) $themeId);
            if (! $post instanceof \WP_Post || $post->post_type !== ThemePostType::TYPE) {
                continue;
            }
            $payload = $service->published((string) $themeId);
            $reference = (string) ($themeReferences[(int) $themeId] ?? '');
            if ($reference === '') {
                continue;
            }
            $themes[] = $codec->themeFromWordPress([
                'title' => (string) $post->post_title,
                'slug' => (string) $post->post_name,
            ], $payload, $reference);
        }
        return $themes;
    }

}
