<?php
if (!defined("ROOT_PATH")) {
    header("HTTP/1.1 403 Forbidden");
    exit;
}

/**
 * Post-install setup wizard: load or skip sample data for the shopping cart script.
 */
class pjAdminSetup extends pjAdmin
{
    const DEMO_CATEGORY_PREFIX = '[DEMO] ';
    const DEMO_ORDER_NOTE = '[DEMO] Sample order';
    const DEMO_SKU_PREFIX = 'DEMO-';

    /** @var int|null Cached English locale ID — avoids repeated DB queries */
    private $_demoLocaleId = null;

    /** @var array|null Cached gallery image settings — avoids repeated DB queries */
    private $_demoGallerySettings = null;

    public static function isDemoProductSku($sku)
    {
        return pjUtil::isDemoProductSku($sku);
    }

    public static function isDemoProductId($productId)
    {
        return pjUtil::isDemoProductId($productId);
    }

    public function beforeFilter()
    {
        pjAppController::beforeFilter();
        return true;
    }

    public function pjActionWelcome()
    {
        $this->checkLogin();

        $wizardCompleted = $this->isWizardCompleted();
        if (!$wizardCompleted) {
            $this->setLayout('pjAdminSetup');
            $this->appendCss('admin-datagrid-listings.css', PJ_CSS_PATH);
        }

        $hasDemoProducts = self::hasDemoData();

        $this->set('wizardCompleted', $wizardCompleted);
        $this->set('hasDemoProducts', $hasDemoProducts);
        $this->set('hasDemoData', $hasDemoProducts);
        $this->appendJs('pjAdminSetup.js');
    }

    public function pjActionLoadDemo()
    {
        $this->setAjax(true);

        if (!$this->isXHR() || !$this->isLoged()) {
            self::jsonResponse(array('status' => 'ERR', 'code' => 100, 'text' => __('setup_unauthorized', true)));
        }

        try {
            @set_time_limit(300);
            @ini_set('memory_limit', '256M');

            $results = $this->createDemoCatalog();
            $clientIds = $this->createDemoClients();
            $results['clients'] = count($clientIds);
            $results['profile'] = $this->createDemoInvoiceProfile();
            $results['orders'] = $this->createDemoOrders($clientIds);

            $this->markWizardCompleted();

            self::jsonResponse(array(
                'status' => 'OK',
                'code'   => 200,
                'text'   => __('setup_api_loaded', true),
                'data'   => $results,
            ));
        } catch (Exception $e) {
            self::jsonResponse(array(
                'status' => 'ERR',
                'code'   => 500,
                'text'   => __('setup_error_loading_demo', true) . ' ' . $e->getMessage(),
            ));
        }
        exit;
    }

    public function pjActionSkipDemo()
    {
        $this->setAjax(true);

        if (!$this->isXHR() || !$this->isLoged()) {
            self::jsonResponse(array('status' => 'ERR', 'code' => 100, 'text' => __('setup_unauthorized', true)));
        }

        $this->markWizardCompleted();

        self::jsonResponse(array(
            'status' => 'OK',
            'code'   => 200,
            'text'   => __('setup_api_completed', true),
        ));
        exit;
    }

    public function pjActionRemoveDemo()
    {
        $this->setAjax(true);

        if (!$this->isXHR() || !$this->isLoged()) {
            self::jsonResponse(array('status' => 'ERR', 'code' => 100, 'text' => __('setup_unauthorized', true)));
        }

        try {
            $results = array(
                'invoices'   => $this->removeDemoInvoices(),
                'orders'     => $this->removeDemoOrders(),
                'clients'    => $this->removeDemoClients(),
                'products'   => $this->removeDemoProducts(),
                'categories' => $this->removeDemoCategories(),
            );

            self::jsonResponse(array(
                'status' => 'OK',
                'code'   => 200,
                'text'   => __('setup_api_removed', true),
                'data'   => $results,
            ));
        } catch (Exception $e) {
            self::jsonResponse(array(
                'status' => 'ERR',
                'code'   => 500,
                'text'   => __('setup_error_removing_demo', true) . ' ' . $e->getMessage(),
            ));
        }
        exit;
    }

    public function pjActionHasDemoData()
    {
        $this->setAjax(true);

        if (!$this->isXHR() || !$this->isLoged()) {
            self::jsonResponse(array('status' => 'ERR', 'code' => 100));
        }

        $count = self::hasDemoData() ? 1 : 0;

        self::jsonResponse(array(
            'status'   => 'OK',
            'code'     => 200,
            'has_demo' => (int) $count > 0,
            'count'    => (int) $count,
        ));
        exit;
    }

    public static function hasDemoData()
    {
        try {
            $demoProducts = pjProductModel::factory()
                ->where('t1.sku LIKE', self::DEMO_SKU_PREFIX . '%')
                ->limit(1)
                ->findAll()
                ->getData();
            if (!empty($demoProducts)) {
                return true;
            }

            $demoClients = pjClientModel::factory()
                ->where('t1.email LIKE', 'demo-%@example.com')
                ->limit(1)
                ->findAll()
                ->getData();
            if (!empty($demoClients)) {
                return true;
            }

            $demoOrders = pjOrderModel::factory()
                ->where('t1.notes', self::DEMO_ORDER_NOTE)
                ->limit(1)
                ->findAll()
                ->getData();

            return !empty($demoOrders);
        } catch (Exception $e) {
            return false;
        }
    }

    private function isWizardCompleted()
    {
        try {
            $arr = pjOptionModel::factory()
                ->where("t1.`foreign_id` = 1 AND t1.`key` = 'o_setup_wizard_completed'")
                ->limit(1)
                ->findAll()
                ->getData();
            if (empty($arr)) {
                return false;
            }
            return $arr[0]['value'] == '1';
        } catch (Exception $e) {
            return false;
        }
    }

    private function markWizardCompleted()
    {
        $arr = pjOptionModel::factory()
            ->where("t1.`foreign_id` = 1 AND t1.`key` = 'o_setup_wizard_completed'")
            ->limit(1)
            ->findAll()
            ->getData();

        if (!empty($arr)) {
            pjOptionModel::factory()
                ->where("`foreign_id` = 1 AND `key` = 'o_setup_wizard_completed'")
                ->limit(1)
                ->modifyAll(array('value' => '1'));
        } else {
            pjOptionModel::factory()
                ->setAttributes(array(
                    'foreign_id' => 1,
                    'key'        => 'o_setup_wizard_completed',
                    'value'      => '1',
                    'type'       => 'int',
                    'label'      => 'Setup wizard completed',
                    'tab_id'     => 0,
                    'order'      => 0,
                    'is_visible' => 0,
                    'style'      => '',
                ))
                ->insert();
        }
    }

    private function getEnglishLocaleId()
    {
        if ($this->_demoLocaleId !== null) {
            return $this->_demoLocaleId;
        }
        $locales = pjLocaleModel::factory()->findAll()->getData();
        $result = 1;
        foreach ($locales as $row) {
            if (!empty($row['language_iso']) && in_array($row['language_iso'], array('en-GB', 'en', 'en-US'))) {
                $result = (int) $row['id'];
                break;
            }
        }
        if ($result === 1 && !empty($locales)) {
            foreach ($locales as $row) {
                if (!empty($row['is_default'])) {
                    $result = (int) $row['id'];
                    break;
                }
            }
            if ($result === 1) {
                $result = (int) $locales[0]['id'];
            }
        }
        $this->_demoLocaleId = $result;
        return $result;
    }

    private function demoSaveEnglishLang($foreignId, $model, array $fields)
    {
        $localeId = $this->getEnglishLocaleId();
        if ($localeId <= 0 || empty($fields)) {
            return;
        }

        pjMultiLangModel::factory()
            ->where('model', $model)
            ->where('foreign_id', $foreignId)
            ->where('locale !=', $localeId)
            ->eraseAll();

        $i18n = array($localeId => $fields);

        $hasLang = pjMultiLangModel::factory()
            ->where('t1.model', $model)
            ->where('t1.foreign_id', $foreignId)
            ->where('t1.locale', $localeId)
            ->limit(1)
            ->findAll()
            ->getData();

        if (!empty($hasLang)) {
            pjMultiLangModel::factory()->updateMultiLang($i18n, $foreignId, $model, 'data');
        } else {
            pjMultiLangModel::factory()->saveMultiLang($i18n, $foreignId, $model, 'data');
        }
    }

    private function createDemoCatalog()
    {
        $this->removeDemoProducts();
        $this->removeDemoCategories();

        $categoryId = $this->getOrCreateDemoCategory('Clothing');
        $productsCreated = 0;
        $stocksCreated = 0;
        $imagesCreated = 0;

        $demoProducts = array(
            array(
                'sku' => self::DEMO_SKU_PREFIX . 'TSHIRT-001',
                'name' => 'Classic Cotton T-Shirt',
                'short_desc' => 'Comfortable everyday cotton t-shirt.',
                'price' => 19.99,
                'qty' => 50,
                'size' => 'Medium',
                'color' => 'Blue',
            ),
            array(
                'sku' => self::DEMO_SKU_PREFIX . 'HOODIE-002',
                'name' => 'Premium Zip Hoodie',
                'short_desc' => 'Warm zip-up hoodie for casual wear.',
                'price' => 49.99,
                'qty' => 30,
                'size' => 'Large',
                'color' => 'Black',
            ),
            array(
                'sku' => self::DEMO_SKU_PREFIX . 'CAP-003',
                'name' => 'Adjustable Baseball Cap',
                'short_desc' => 'One-size adjustable cap with embroidered logo area.',
                'price' => 14.99,
                'qty' => 75,
                'size' => 'One Size',
                'color' => 'Navy',
            ),
            array(
                'sku' => self::DEMO_SKU_PREFIX . 'BAG-004',
                'name' => 'Canvas Tote Bag',
                'short_desc' => 'Reusable canvas tote bag for shopping or events.',
                'price' => 12.50,
                'qty' => 100,
                'size' => 'Standard',
                'color' => 'Natural',
            ),
            array(
                'sku' => self::DEMO_SKU_PREFIX . 'MUG-005',
                'name' => 'Ceramic Coffee Mug',
                'short_desc' => '11oz ceramic mug, dishwasher safe.',
                'price' => 8.99,
                'qty' => 120,
                'size' => '11oz',
                'color' => 'White',
            ),
        );

        foreach ($demoProducts as $item) {
            $productId = pjProductModel::factory()
                ->setAttributes(array(
                    'sku'         => $item['sku'],
                    'status'      => 1,
                    'is_featured' => 0,
                    'is_digital'  => 0,
                ))
                ->insert()
                ->getInsertId();

            if (!$productId) {
                continue;
            }

            $this->demoSaveEnglishLang($productId, 'pjProduct', array(
                'name'       => $item['name'],
                'short_desc' => $item['short_desc'],
                'full_desc'  => '<p>' . htmlspecialchars($item['short_desc'], ENT_QUOTES, 'UTF-8') . '</p>',
            ));

            pjProductCategoryModel::factory()
                ->set('product_id', $productId)
                ->set('category_id', $categoryId)
                ->insert();

            $sizeAttr = $this->getOrCreateAttribute($productId, 'Size', $item['size']);
            $colorAttr = $this->getOrCreateAttribute($productId, 'Color', $item['color']);

            $stockId = pjStockModel::factory()
                ->set('product_id', $productId)
                ->set('qty', (int) $item['qty'])
                ->set('price', (float) $item['price'])
                ->insert()
                ->getInsertId();

            if ($stockId && $sizeAttr && $colorAttr) {
                pjStockAttributeModel::factory()
                    ->set('stock_id', $stockId)
                    ->set('product_id', $productId)
                    ->set('attribute_parent_id', $sizeAttr['parent_id'])
                    ->set('attribute_id', $sizeAttr['id'])
                    ->insert();
                pjStockAttributeModel::factory()
                    ->set('stock_id', $stockId)
                    ->set('product_id', $productId)
                    ->set('attribute_parent_id', $colorAttr['parent_id'])
                    ->set('attribute_id', $colorAttr['id'])
                    ->insert();
                $stocksCreated++;
            }

            $galleryIds = $this->demoAttachProductDemoImages($productId, $item['name'], 3, $item['sku']);
            $imagesCreated += count($galleryIds);

            if ($stockId && !empty($galleryIds)) {
                pjStockModel::factory()
                    ->reset()
                    ->where('id', $stockId)
                    ->limit(1)
                    ->modifyAll(array('image_id' => (int) $galleryIds[0]));
            }

            $productsCreated++;
        }

        return array(
            'categories' => 1,
            'products'   => $productsCreated,
            'stocks'     => $stocksCreated,
            'images'     => $imagesCreated,
        );
    }

    /**
     * Load GD image helper (autoload is not triggered with class_exists(..., false)).
     */
    private function demoRequireImageLib()
    {
        if (!class_exists('pjImage')) {
            $componentFile = PJ_COMPONENTS_PATH . 'pjImage.component.php';
            if (is_file($componentFile)) {
                require_once $componentFile;
            }
        }

        return class_exists('pjImage');
    }

    /**
     * Images used when loading demo products (3 per product, rotated from pool).
     *
     * Primary source: app/web/img/demo/ — add or replace product-*.jpg|png there.
     * Falls back to bundled pjBase / frontend assets if the folder is empty.
     */
    private function demoGetImagePool()
    {
        $pool = array();
        $allowedExt = array('jpg', 'jpeg', 'png', 'gif', 'webp');

        $demoDirs = array(
            PJ_INSTALL_PATH . 'app/web/img/demo/',
            PJ_IMG_PATH . 'demo/',
        );

        // JPEG/WebP are preferred over PNG (same base name) — smaller files = faster GD processing
        $preferredExt = array('jpg', 'jpeg', 'webp');

        foreach ($demoDirs as $demoDir) {
            $realDir = realpath($demoDir);
            if ($realDir === false || !is_dir($realDir)) {
                continue;
            }
            $byBase = array(); // base name → best file path
            foreach (scandir($realDir) as $entry) {
                if ($entry === '.' || $entry === '..') {
                    continue;
                }
                $ext = strtolower(pathinfo($entry, PATHINFO_EXTENSION));
                if (!in_array($ext, $allowedExt, true)) {
                    continue;
                }
                $fullPath = realpath($realDir . '/' . $entry);
                if ($fullPath === false || !is_file($fullPath)) {
                    continue;
                }
                $base = strtolower(pathinfo($entry, PATHINFO_FILENAME));
                // Prefer JPEG/WebP over PNG: only replace existing entry if new file is preferred and existing is not
                if (!isset($byBase[$base])) {
                    $byBase[$base] = $fullPath;
                } elseif (in_array($ext, $preferredExt, true)) {
                    $existingExt = strtolower(pathinfo($byBase[$base], PATHINFO_EXTENSION));
                    if (!in_array($existingExt, $preferredExt, true)) {
                        $byBase[$base] = $fullPath; // upgrade PNG → JPEG
                    }
                }
            }
            if (!empty($byBase)) {
                $files = array_values($byBase);
                sort($files, SORT_NATURAL | SORT_FLAG_CASE);
                foreach ($files as $file) {
                    $pool[$file] = $file;
                }
                return array_values($pool);
            }
        }

        $fallback = array(
            PJ_PLUGINS_PATH . 'pjBase/web/img/logo.png',
            PJ_PLUGINS_PATH . 'pjBase/web/img/orange.png',
            PJ_PLUGINS_PATH . 'pjBase/web/img/phpjabbers-logo1.jpg',
            PJ_INSTALL_PATH . 'app/web/img/frontend/80x106.png',
            PJ_INSTALL_PATH . 'app/web/img/frontend/noimg.jpg',
        );

        foreach ($fallback as $path) {
            $real = realpath($path);
            if ($real !== false && is_file($real)) {
                $pool[$real] = $real;
            }
        }

        return array_values($pool);
    }

    private function demoEnsureUploadDirs()
    {
        foreach (array('source', 'small', 'medium', 'large') as $dir) {
            $folder = PJ_INSTALL_PATH . PJ_UPLOAD_PATH . $dir . '/';
            if (!is_dir($folder)) {
                @mkdir($folder, 0777, true);
            }
        }
    }

    /**
     * Attach multiple demo gallery rows per product (admin list + storefront).
     *
     * @return int[] Gallery IDs, oldest sort first
     */
    private function demoAttachProductDemoImages($productId, $productName, $count = 3, $sku = '')
    {
        $productId = (int) $productId;
        $count = max(1, (int) $count);
        $pool = $this->demoGetImagePool();
        if ($productId <= 0 || empty($pool)) {
            return array();
        }

        // Use the image that matches the product type (cap -> cap photo, tote ->
        // tote photo, etc.) so each product shows the correct picture. Previously
        // images were assigned by (productId % poolSize), which mismatched them.
        $matched = $this->demoMatchDemoImage($pool, $productName, $sku);
        if ($matched !== null) {
            $label = trim($productName) !== '' ? $productName : 'Demo product';
            $galleryId = $this->demoAttachGalleryImage($productId, $matched, $label, 1);
            return $galleryId > 0 ? array($galleryId) : array();
        }

        // Fallback (no type match): rotate through the pool.
        $ids = array();
        for ($i = 0; $i < $count; $i++) {
            $sourceFile = $pool[($productId + $i) % count($pool)];
            $label = trim($productName) !== '' ? $productName . ' (' . ($i + 1) . ')' : 'Demo product';
            $galleryId = $this->demoAttachGalleryImage($productId, $sourceFile, $label, $i + 1);
            if ($galleryId > 0) {
                $ids[] = $galleryId;
            }
        }

        return $ids;
    }

    /**
     * Pick the demo source image whose filename matches the product type.
     * Returns null when nothing matches (caller falls back to the pool).
     */
    private function demoMatchDemoImage($pool, $productName, $sku)
    {
        $hay = strtolower((string) $productName . ' ' . (string) $sku);
        // product keyword => image filename fragment
        $map = array(
            'hoodie'  => 'hoodie',
            'cap'     => 'cap',
            'tote'    => 'tote',
            'bag'     => 'tote',
            'mug'     => 'mug',
            't-shirt' => 'tshirt',
            'tshirt'  => 'tshirt',
            'shirt'   => 'tshirt',
        );
        foreach ($map as $keyword => $fragment) {
            if (strpos($hay, $keyword) !== false) {
                foreach ($pool as $file) {
                    if (strpos(strtolower(basename($file)), $fragment) !== false) {
                        return $file;
                    }
                }
            }
        }
        return null;
    }

    /**
     * Copy a file into the product gallery (model pjProduct) with admin-sized thumbs.
     *
     * Optimised flow: copy the source file first (no GD needed), validate with
     * getimagesize(), then build thumbnails with a fresh pjImage instance.
     * demoBuildGalleryThumbs() reloads from disk for each size anyway, so there
     * is no need to pre-load the source into GD before copying.
     */
    private function demoAttachGalleryImage($productId, $sourceFile, $label, $sort)
    {
        $productId = (int) $productId;
        $sort = (int) $sort;
        if ($productId <= 0 || !is_file($sourceFile)) {
            return 0;
        }

        $this->demoEnsureUploadDirs();

        $ext = strtolower(pathinfo($sourceFile, PATHINFO_EXTENSION));
        if ($ext === '') {
            $ext = 'jpg';
        }

        $hash = md5(uniqid((string) $productId . $sort, true));
        $source_path = PJ_UPLOAD_PATH . 'source/' . $productId . '_demo_' . $hash . '.' . $ext;
        $source_full = PJ_INSTALL_PATH . $source_path;

        // ── Step 1: copy source to uploads/source/ (no GD required) ──────────
        if (!@copy($sourceFile, $source_full)) {
            // copy() failed — fall back to GD save
            if (!$this->demoRequireImageLib()) {
                return $this->demoAttachGalleryImageFallback($productId, $sourceFile, $label, $sort);
            }
            $Image = new pjImage();
            if ($Image->getErrorCode() === 200 || !$Image->loadImage($sourceFile)) {
                return $this->demoAttachGalleryImageFallback($productId, $sourceFile, $label, $sort);
            }
            if (!$Image->saveImage($source_full)) {
                return $this->demoAttachGalleryImageFallback($productId, $sourceFile, $label, $sort);
            }
        }

        // ── Step 2: validate copied file (getimagesize — no GD pixel decode) ─
        $image_info = @getimagesize($source_full);
        if ($image_info === false) {
            @unlink($source_full);
            return 0;
        }

        $data_insert = array(
            'foreign_id'    => $productId,
            'model'         => 'pjProduct',
            'mime_type'     => $image_info['mime'],
            'source_path'   => $source_path,
            'source_size'   => filesize($source_full),
            'source_width'  => $image_info[0],
            'source_height' => $image_info[1],
            'name'          => $label,
            'sort'          => $sort > 0 ? $sort : 1,
            'created'       => date('Y-m-d H:i:s'),
        );

        // ── Step 3: build thumbnails with a fresh pjImage (loads inside loop) ─
        if (!$this->demoRequireImageLib()) {
            return $this->demoInsertGalleryRow($productId, $this->demoGalleryRowFromCopiedFile($source_full, $source_path, $label, $sort));
        }

        $Image = new pjImage();
        if ($Image->getErrorCode() === 200) {
            return $this->demoInsertGalleryRow($productId, $this->demoGalleryRowFromCopiedFile($source_full, $source_path, $label, $sort));
        }

        $data_insert = array_merge($data_insert, $this->demoBuildGalleryThumbs($Image, $data_insert));
        if (empty($data_insert['small_path'])) {
            return $this->demoInsertGalleryRow($productId, $this->demoGalleryRowFromCopiedFile($source_full, $source_path, $label, $sort));
        }

        return $this->demoInsertGalleryRow($productId, $data_insert);
    }

    /**
     * When GD is unavailable, copy the file into upload folders and reference it for all sizes.
     */
    private function demoAttachGalleryImageFallback($productId, $sourceFile, $label, $sort)
    {
        $ext = strtolower(pathinfo($sourceFile, PATHINFO_EXTENSION));
        if ($ext === '') {
            $ext = 'jpg';
        }

        $hash = md5(uniqid((string) $productId . $sort . 'fb', true));
        $basename = $productId . '_demo_' . $hash . '.' . $ext;
        $source_path = PJ_UPLOAD_PATH . 'source/' . $basename;
        $source_full = PJ_INSTALL_PATH . $source_path;

        if (!@copy($sourceFile, $source_full)) {
            return 0;
        }

        $row = $this->demoGalleryRowFromCopiedFile($source_full, $source_path, $label, $sort);

        foreach (array('small', 'medium', 'large') as $key) {
            $dst_rel = PJ_UPLOAD_PATH . $key . '/' . $basename;
            $dst_full = PJ_INSTALL_PATH . $dst_rel;
            if (@copy($source_full, $dst_full)) {
                $size = @getimagesize($dst_full);
                $row[$key . '_path'] = $dst_rel;
                $row[$key . '_size'] = filesize($dst_full);
                if ($size !== false) {
                    $row[$key . '_width'] = $size[0];
                    $row[$key . '_height'] = $size[1];
                }
            }
        }

        if (empty($row['small_path'])) {
            $row['small_path'] = $source_path;
            $row['medium_path'] = $source_path;
            $row['large_path'] = $source_path;
            $row['small_size'] = filesize($source_full);
            $row['medium_size'] = $row['small_size'];
            $row['large_size'] = $row['small_size'];
            $size = @getimagesize($source_full);
            if ($size !== false) {
                $row['small_width'] = $size[0];
                $row['small_height'] = $size[1];
                $row['medium_width'] = $size[0];
                $row['medium_height'] = $size[1];
                $row['large_width'] = $size[0];
                $row['large_height'] = $size[1];
            }
        }

        return $this->demoInsertGalleryRow($productId, $row);
    }

    private function demoGalleryRowFromCopiedFile($source_full, $source_path, $label, $sort)
    {
        $image_info = @getimagesize($source_full);
        if ($image_info === false) {
            return array();
        }

        return array(
            'mime_type'     => $image_info['mime'],
            'source_path'   => $source_path,
            'source_size'   => filesize($source_full),
            'source_width'  => $image_info[0],
            'source_height' => $image_info[1],
            'name'          => $label,
            'sort'          => $sort > 0 ? $sort : 1,
            'created'       => date('Y-m-d H:i:s'),
        );
    }

    private function demoInsertGalleryRow($productId, array $row)
    {
        if (empty($row['source_path']) || empty($row['small_path'])) {
            return 0;
        }

        $row['foreign_id'] = (int) $productId;
        $row['model'] = 'pjProduct';

        return (int) pjGalleryModel::factory()
            ->reset()
            ->setAttributes($row)
            ->insert()
            ->getInsertId();
    }

    private function demoGetGalleryImageSettings()
    {
        if ($this->_demoGallerySettings !== null) {
            return $this->_demoGallerySettings;
        }
        $options = class_exists('pjBaseOptionModel') ? pjBaseOptionModel::factory()->getPairs(1) : array();
        $getInt = function ($key, $fallback) use ($options) {
            return !empty($options[$key]) ? (int) $options[$key] : $fallback;
        };
        $parseMode = function ($key, $fallback) use ($options) {
            $value = isset($options[$key]) ? $options[$key] : $fallback;
            if (strpos($value, '::') !== false) {
                $parts = explode('::', $value);
                $value = end($parts);
            }
            return in_array($value, array('crop', 'resize'), true) ? $value : $fallback;
        };
        $this->_demoGallerySettings = array(
            'small'  => array($getInt('o_thumb_width', 90),   $getInt('o_thumb_height', 68),   $parseMode('o_thumb_crop', 'crop')),
            'medium' => array($getInt('o_medium_width', 215),  $getInt('o_medium_height', 161),  $parseMode('o_medium_crop', 'crop')),
            'large'  => array($getInt('o_large_width', 800),   $getInt('o_large_height', 800),   $parseMode('o_large_crop', 'resize')),
        );
        return $this->_demoGallerySettings;
    }

    /**
     * Demo derivatives follow the saved Images settings.
     */
    private function demoBuildGalleryThumbs($Image, $data)
    {
        $arr = array();
        if (empty($data['source_path'])) {
            return $arr;
        }

        $source_full = PJ_INSTALL_PATH . $data['source_path'];
        if (!is_file($source_full)) {
            return $arr;
        }

        $sizes = $this->demoGetGalleryImageSettings();

        foreach ($sizes as $key => $dim) {
            $dst_rel = str_replace(PJ_UPLOAD_PATH . 'source/', PJ_UPLOAD_PATH . $key . '/', $data['source_path']);
            $dst_full = PJ_INSTALL_PATH . $dst_rel;
            $Image->loadImage($source_full);
            if ($dim[2] === 'crop') {
                $Image->resizeSmart((int) $dim[0], (int) $dim[1]);
            } else {
                $Image->resizeToWidth((int) $dim[0]);
            }
            if ($Image->saveImage($dst_full)) {
                $size = @getimagesize($dst_full);
                if ($size !== false) {
                    $arr[$key . '_path']   = $dst_rel;
                    $arr[$key . '_size']   = filesize($dst_full);
                    $arr[$key . '_width']  = $size[0];
                    $arr[$key . '_height'] = $size[1];
                }
            }
        }

        if (empty($arr['small_path']) && is_file($source_full)) {
            $arr['small_path'] = $data['source_path'];
            $arr['medium_path'] = $data['source_path'];
            $arr['large_path'] = $data['source_path'];
            $size = @getimagesize($source_full);
            $arr['small_size'] = filesize($source_full);
            $arr['medium_size'] = $arr['small_size'];
            $arr['large_size'] = $arr['small_size'];
            if ($size !== false) {
                $arr['small_width'] = $size[0];
                $arr['small_height'] = $size[1];
                $arr['medium_width'] = $size[0];
                $arr['medium_height'] = $size[1];
                $arr['large_width'] = $size[0];
                $arr['large_height'] = $size[1];
            }
        }

        return $arr;
    }

    private function getOrCreateDemoCategory($name)
    {
        $categoryName = self::DEMO_CATEGORY_PREFIX . $name;
        $localeId = $this->getEnglishLocaleId();

        $category = pjCategoryModel::factory()
            ->join('pjMultiLang', 't2.foreign_id=t1.id AND t2.model="pjCategory"', 'inner')
            ->where('t2.field', 'name')
            ->where('t2.locale', $localeId)
            ->where('LOWER(t2.content)', strtolower($categoryName))
            ->limit(1)
            ->findAll()
            ->getData();

        if (!empty($category)) {
            return (int) $category[0]['id'];
        }

        $categoryId = pjCategoryModel::factory()->saveNode(array('parent_id' => 1), 1);
        $this->demoSaveEnglishLang($categoryId, 'pjCategory', array('name' => $categoryName));

        return (int) $categoryId;
    }

    private function getOrCreateAttribute($productId, $groupName, $itemName)
    {
        $group = pjAttributeModel::factory()
            ->select('t1.id')
            ->join('pjMultiLang', 't2.foreign_id=t1.id AND t2.model="pjAttribute"', 'inner')
            ->where('t2.field', 'name')
            ->where('t2.content', $groupName)
            ->where('t1.product_id', $productId)
            ->where('t1.parent_id IS NULL')
            ->limit(1)
            ->findAll()
            ->getData();

        if (empty($group)) {
            $groupId = pjAttributeModel::factory()
                ->setAttributes(array('product_id' => $productId, 'order_group' => 0))
                ->insert()
                ->getInsertId();
            $this->demoSaveEnglishLang($groupId, 'pjAttribute', array('name' => $groupName));
        } else {
            $groupId = $group[0]['id'];
        }

        $item = pjAttributeModel::factory()
            ->select('t1.id')
            ->join('pjMultiLang', 't2.foreign_id=t1.id AND t2.model="pjAttribute"', 'inner')
            ->where('t2.field', 'name')
            ->where('t2.content', $itemName)
            ->where('t1.parent_id', $groupId)
            ->where('t1.product_id', $productId)
            ->limit(1)
            ->findAll()
            ->getData();

        if (empty($item)) {
            $itemId = pjAttributeModel::factory()
                ->setAttributes(array(
                    'product_id'  => $productId,
                    'parent_id'   => $groupId,
                    'order_group' => 0,
                    'order_item'  => 0,
                ))
                ->insert()
                ->getInsertId();
            $this->demoSaveEnglishLang($itemId, 'pjAttribute', array('name' => $itemName));
        } else {
            $itemId = $item[0]['id'];
        }

        return array('parent_id' => $groupId, 'id' => $itemId);
    }

    private function getDemoClientData()
    {
        return array(
            array('email' => 'demo-alice@example.com', 'client_name' => 'Alice Johnson', 'phone' => '+1 555 0101'),
            array('email' => 'demo-bob@example.com', 'client_name' => 'Bob Smith', 'phone' => '+1 555 0102'),
            array('email' => 'demo-carol@example.com', 'client_name' => 'Carol Williams', 'phone' => '+1 555 0103'),
        );
    }

    private function createDemoClients()
    {
        $clientIds = array();

        foreach ($this->getDemoClientData() as $client) {
            $existing = pjClientModel::factory()
                ->where('t1.email', $client['email'])
                ->limit(1)
                ->findAll()
                ->getData();

            if (!empty($existing)) {
                $clientIds[] = (int) $existing[0]['id'];
                continue;
            }

            $clientId = pjClientModel::factory()
                ->setAttributes(array(
                    'email'       => $client['email'],
                    'password'    => 'demo123',
                    'client_name' => $client['client_name'],
                    'phone'       => $client['phone'],
                    'url'         => 'https://example.com',
                    'status'      => 'T',
                    'created'     => date('Y-m-d H:i:s'),
                ))
                ->insert()
                ->getInsertId();

            if ($clientId && (int) $clientId > 0) {
                $clientIds[] = (int) $clientId;
            }
        }

        return $clientIds;
    }

    private function getDemoPaymentMethod()
    {
        $methods = pjAppController::getActivePaymentMethods($this->getForeignId(), $this->getLocaleId());
        foreach (array('cash', 'bank') as $method) {
            if (isset($methods[$method])) {
                return $method;
            }
        }
        foreach ($methods as $method => $label) {
            return $method;
        }

        return 'cash';
    }

    private function createDemoOrders($clientIds)
    {
        if (empty($clientIds)) {
            return 0;
        }

        $this->removeDemoOrders();

        $localeId = $this->getEnglishLocaleId();
        $countryId = 0;

        try {
            $countries = pjBaseCountryModel::factory()
                ->where('t1.alpha_2', 'US')
                ->limit(1)
                ->findAll()
                ->getData();
            if (empty($countries)) {
                $countries = pjBaseCountryModel::factory()->limit(1)->findAll()->getData();
            }
            if (!empty($countries)) {
                $countryId = (int) $countries[0]['id'];
            }
        } catch (Exception $e) {
        }

        $stocks = pjStockModel::factory()
            ->join('pjProduct', 't2.id=t1.product_id', 'inner')
            ->where('t2.sku LIKE', self::DEMO_SKU_PREFIX . '%')
            ->findAll()
            ->getData();

        if (empty($stocks)) {
            return 0;
        }

        $orderDefs = array(
            array('client_idx' => 0, 'status' => 'new', 'days_ago' => 1, 'stock_indices' => array(0)),
            array('client_idx' => 1, 'status' => 'pending', 'days_ago' => 3, 'stock_indices' => array(1)),
            array('client_idx' => 0, 'status' => 'completed', 'days_ago' => 5, 'stock_indices' => array(2)),
            array('client_idx' => 2, 'status' => 'completed', 'days_ago' => 8, 'stock_indices' => array(3)),
            array('client_idx' => 1, 'status' => 'pending', 'days_ago' => 10, 'stock_indices' => array(0, 1)),
            array('client_idx' => 2, 'status' => 'new', 'days_ago' => 12, 'stock_indices' => array(4)),
        );

        $clients = $this->getDemoClientData();
        $orderCount = 0;
        $demoPaymentMethod = $this->getDemoPaymentMethod();

        foreach ($orderDefs as $def) {
            $clientIdx = $def['client_idx'] % count($clientIds);
            $clientId = $clientIds[$clientIdx];
            $client = $clients[$clientIdx];

            $lines = array();
            foreach ($def['stock_indices'] as $idx) {
                if (!isset($stocks[$idx])) {
                    continue;
                }
                $stock = $stocks[$idx];
                $qty = 2;
                $price = (float) $stock['price'];
                $lines[] = array('stock' => $stock, 'qty' => $qty, 'price' => $price);
            }
            if (empty($lines)) {
                continue;
            }

            $created = date('Y-m-d H:i:s', strtotime('-' . (int) $def['days_ago'] . ' days'));

            $orderId = pjOrderModel::factory()
                ->setAttributes(array(
                    'uuid'           => pjUtil::uuid(),
                    'client_id'      => $clientId,
                    'locale_id'      => $localeId,
                    'status'         => $def['status'],
                    'payment_method' => $demoPaymentMethod,
                    'price'          => 0,
                    'discount'       => 0,
                    'insurance'      => 0,
                    'shipping'       => 0,
                    'tax'            => 0,
                    'total'          => 0,
                    'notes'          => self::DEMO_ORDER_NOTE,
                    'created'        => $created,
                    'ip'             => '127.0.0.1',
                    'same_as'        => 1,
                    'b_name'         => $client['client_name'],
                    'b_country_id'   => $countryId,
                    'b_city'         => 'New York',
                    'b_zip'          => '10001',
                    'b_address_1'    => '123 Demo Street',
                    's_name'         => $client['client_name'],
                    's_country_id'   => $countryId,
                    's_city'         => 'New York',
                    's_zip'          => '10001',
                    's_address_1'    => '123 Demo Street',
                ))
                ->insert()
                ->getInsertId();

            if (!$orderId) {
                continue;
            }

            foreach ($lines as $line) {
                pjOrderStockModel::factory()
                    ->setAttributes(array(
                        'order_id'   => $orderId,
                        'stock_id'   => $line['stock']['id'],
                        'product_id' => $line['stock']['product_id'],
                        'price'      => $line['price'],
                        'qty'        => $line['qty'],
                    ))
                    ->insert();
            }

            $orderStockRows = pjOrderStockModel::factory()
                ->where('t1.order_id', $orderId)
                ->findAll()
                ->getData();
            $calcPrice = pjAppController::pjActionCalcPrices($orderId, array(), $orderStockRows, null, null, $this->option_arr, null, 'back');
            if (!empty($calcPrice)) {
                pjOrderModel::factory()
                    ->reset()
                    ->set('id', $orderId)
                    ->modify(array(
                        'price'     => round((float) $calcPrice['price'], 2),
                        'discount'  => round((float) $calcPrice['discount'], 2),
                        'insurance' => round((float) $calcPrice['insurance'], 2),
                        'shipping'  => round((float) $calcPrice['shipping'], 2),
                        'tax'       => round((float) $calcPrice['tax'], 2),
                        'total'     => max(0, round((float) $calcPrice['total'], 2)),
                    ));
            }

            $this->createDemoInvoiceFromOrder($orderId, $lines);
            $orderCount++;
        }

        return $orderCount;
    }

    private function createDemoInvoiceProfile()
    {
        try {
            $arr = pjInvoiceConfigModel::factory()->find(1)->getData();
            if (empty($arr)) {
                pjInvoiceConfigModel::factory()
                    ->setAttributes(array(
                        'id' => 1,
                        'invoice_number' => '100000',
                        'invoice_number_digits' => 5,
                        'invoice_number_reset_yearly' => 0,
                        'y_zip' => '10001',
                        'y_phone' => '+1 555 0199',
                        'y_email' => 'demo-store@example.com',
                        'y_url' => 'https://example.com',
                        'y_coc_number' => 'DEMO-REG-001',
                        'y_bank_iban' => 'DEMO BANK 0001',
                        'y_bank_name' => 'Demo Store',
                        'si_include' => 0,
                        'o_use_qty_unit_price' => 1
                    ))
                    ->insert();
            } else {
                pjInvoiceConfigModel::factory()
                    ->set('id', 1)
                    ->modify(array(
                        'invoice_number' => !empty($arr['invoice_number']) ? $arr['invoice_number'] : '100000',
                        'y_zip' => !empty($arr['y_zip']) ? $arr['y_zip'] : '10001',
                        'y_phone' => !empty($arr['y_phone']) ? $arr['y_phone'] : '+1 555 0199',
                        'y_email' => !empty($arr['y_email']) ? $arr['y_email'] : 'demo-store@example.com',
                        'y_url' => !empty($arr['y_url']) ? $arr['y_url'] : 'https://example.com',
                        'y_coc_number' => !empty($arr['y_coc_number']) ? $arr['y_coc_number'] : 'DEMO-REG-001',
                        'y_bank_iban' => !empty($arr['y_bank_iban']) ? $arr['y_bank_iban'] : 'DEMO BANK 0001',
                        'y_bank_name' => !empty($arr['y_bank_name']) ? $arr['y_bank_name'] : 'Demo Store'
                    ));
            }

            $this->demoSaveEnglishLang(1, 'pjInvoiceConfig', array(
                'y_company' => 'Demo Store',
                'y_name' => 'Store Administrator',
                'y_street_address' => '500 Demo Avenue',
                'y_city' => 'New York',
                'y_state' => 'NY',
                'y_template' => '',
            ));

            return 1;
        } catch (Exception $e) {
            return 0;
        }
    }

    private function createDemoInvoiceFromOrder($orderId, array $lines)
    {
        try {
            $order = pjOrderModel::factory()->find($orderId)->getData();
            if (empty($order)) {
                return 0;
            }

            pjInvoiceModel::factory()->where('order_id', $order['uuid'])->eraseAll();

            $invoiceId = pjInvoiceModel::factory()
                ->setAttributes(array(
                    'uuid' => (string) (100000 + (int) $orderId),
                    'order_id' => $order['uuid'],
                    'foreign_id' => $orderId,
                    'locale_id' => $order['locale_id'],
                    'issue_date' => date('Y-m-d', strtotime($order['created'])),
                    'due_date' => date('Y-m-d', strtotime($order['created'] . ' +14 days')),
                    'created' => $order['created'],
                    'modified' => date('Y-m-d H:i:s'),
                    'status' => $order['status'] === 'completed' ? 'paid' : 'not_paid',
                    'payment_method' => $order['payment_method'],
                    'price' => $order['price'],
                    'discount' => $order['discount'],
                    'insurance' => $order['insurance'],
                    'shipping' => $order['shipping'],
                    'tax' => $order['tax'],
                    'total' => $order['total'],
                    'currency' => isset($this->option_arr['o_currency']) ? $this->option_arr['o_currency'] : null,
                    'notes' => self::DEMO_ORDER_NOTE,
                    'b_name' => $order['b_name'],
                    'b_country_id' => $order['b_country_id'],
                    'b_city' => $order['b_city'],
                    'b_state' => isset($order['b_state']) ? $order['b_state'] : null,
                    'b_zip' => $order['b_zip'],
                    'b_address_1' => $order['b_address_1'],
                    'b_address_2' => isset($order['b_address_2']) ? $order['b_address_2'] : null,
                    's_name' => $order['s_name'],
                    's_country_id' => $order['s_country_id'],
                    's_city' => $order['s_city'],
                    's_state' => isset($order['s_state']) ? $order['s_state'] : null,
                    's_zip' => $order['s_zip'],
                    's_address_1' => $order['s_address_1'],
                    's_address_2' => isset($order['s_address_2']) ? $order['s_address_2'] : null,
                ))
                ->insert()
                ->getInsertId();

            if (!$invoiceId) {
                return 0;
            }

            foreach ($lines as $line) {
                $productName = $this->getDemoProductName((int) $line['stock']['product_id']);
                pjInvoiceItemModel::factory()
                    ->setAttributes(array(
                        'invoice_id' => $invoiceId,
                        'tmp' => 'demo',
                        'name' => $productName,
                        'description' => 'Demo order item',
                        'qty' => $line['qty'],
                        'unit_price' => $line['price'],
                        'amount' => round($line['qty'] * $line['price'], 2)
                    ))
                    ->insert();
            }

            return 1;
        } catch (Exception $e) {
            return 0;
        }
    }

    private function getDemoProductName($productId)
    {
        $localeId = $this->getEnglishLocaleId();
        $row = pjMultiLangModel::factory()
            ->where('t1.model', 'pjProduct')
            ->where('t1.foreign_id', (int) $productId)
            ->where('t1.locale', $localeId)
            ->where('t1.field', 'name')
            ->limit(1)
            ->findAll()
            ->getData();

        return !empty($row) && !empty($row[0]['content']) ? $row[0]['content'] : 'Demo product';
    }

    private function removeDemoCategories()
    {
        $count = 0;
        $localeId = $this->getEnglishLocaleId();
        $rows = pjMultiLangModel::factory()
            ->select('t1.foreign_id')
            ->where('t1.model', 'pjCategory')
            ->where('t1.field', 'name')
            ->where('t1.locale', $localeId)
            ->where("t1.content LIKE '" . self::DEMO_CATEGORY_PREFIX . "%'")
            ->findAll()
            ->getData();

        foreach ($rows as $row) {
            $catId = (int) $row['foreign_id'];
            if ($catId <= 1) {
                continue;
            }
            pjMultiLangModel::factory()->where('model', 'pjCategory')->where('foreign_id', $catId)->eraseAll();
            pjProductCategoryModel::factory()->where('category_id', $catId)->eraseAll();
            pjCategoryModel::factory()->deleteNode($catId);
            $count++;
        }

        if ($count > 0) {
            pjCategoryModel::factory()->rebuildTree(1, 1);
        }

        return $count;
    }

    private function removeDemoProducts()
    {
        $count = 0;
        $products = pjProductModel::factory()
            ->where('t1.sku LIKE', self::DEMO_SKU_PREFIX . '%')
            ->findAll()
            ->getData();

        foreach ($products as $product) {
            $pid = (int) $product['id'];

            pjStockAttributeModel::factory()->where('product_id', $pid)->eraseAll();
            pjStockModel::factory()->where('product_id', $pid)->eraseAll();
            pjProductCategoryModel::factory()->where('product_id', $pid)->eraseAll();
            pjMultiLangModel::factory()->where('model', 'pjProduct')->where('foreign_id', $pid)->eraseAll();

            try {
                pjAttributeModel::factory()->where('product_id', $pid)->eraseAll();
            } catch (Exception $e) {
            }

            try {
                $galleries = pjGalleryModel::factory()
                    ->where('foreign_id', $pid)
                    ->where('model', 'pjProduct')
                    ->findAll()
                    ->getData();
                foreach ($galleries as $gallery) {
                    foreach (array('small', 'medium', 'large', 'source') as $size) {
                        $key = $size . '_path';
                        if (!empty($gallery[$key])) {
                            $file = $gallery[$key];
                            $full = (strpos($file, '/') === 0) ? $file : PJ_INSTALL_PATH . $file;
                            if (is_file($full)) {
                                @unlink($full);
                            }
                        }
                    }
                    pjGalleryModel::factory()->set('id', $gallery['id'])->erase();
                }
            } catch (Exception $e) {
            }

            pjProductModel::factory()->set('id', $pid)->erase();
            $count++;
        }

        return $count;
    }

    private function removeDemoClients()
    {
        $count = 0;
        $clients = pjClientModel::factory()
            ->where('t1.email LIKE', 'demo-%@example.com')
            ->findAll()
            ->getData();

        foreach ($clients as $client) {
            $cid = (int) $client['id'];
            try {
                pjAddressModel::factory()->where('client_id', $cid)->eraseAll();
            } catch (Exception $e) {
            }
            pjClientModel::factory()->set('id', $cid)->erase();
            $count++;
        }

        return $count;
    }

    private function removeDemoOrders()
    {
        $count = 0;
        $orders = pjOrderModel::factory()
            ->where('t1.notes', self::DEMO_ORDER_NOTE)
            ->findAll()
            ->getData();

        foreach ($orders as $order) {
            $oid = (int) $order['id'];
            try {
                $invoices = pjInvoiceModel::factory()->where('t1.order_id', $order['uuid'])->findAll()->getData();
                foreach ($invoices as $invoice) {
                    pjInvoiceItemModel::factory()->where('invoice_id', (int) $invoice['id'])->eraseAll();
                    pjInvoiceModel::factory()->set('id', (int) $invoice['id'])->erase();
                }
            } catch (Exception $e) {
            }
            try {
                pjOrderExtraModel::factory()->where('order_id', $oid)->eraseAll();
            } catch (Exception $e) {
            }
            pjOrderStockModel::factory()->where('order_id', $oid)->eraseAll();
            pjOrderModel::factory()->set('id', $oid)->erase();
            $count++;
        }

        return $count;
    }

    private function removeDemoInvoices()
    {
        $count = 0;
        try {
            $invoices = pjInvoiceModel::factory()
                ->where('t1.notes', self::DEMO_ORDER_NOTE)
                ->findAll()
                ->getData();

            foreach ($invoices as $invoice) {
                pjInvoiceItemModel::factory()->where('invoice_id', (int) $invoice['id'])->eraseAll();
                pjInvoiceModel::factory()->set('id', (int) $invoice['id'])->erase();
                $count++;
            }
        } catch (Exception $e) {
        }
        return $count;
    }
}
