<?php
namespace Components\Models;

use Components\Data\LegacyStoreData;
use Symfony\Component\Yaml\Yaml;

/**
 * Coupons model supporting multiple platforms.
 * - Table structure differs by underlying store system.
 * - Runtime schema introspection keeps editing/save behavior compatible across variants.
 */
class Coupon extends BaseModel
{
    protected static string $table = 'TABLE_COUPONS';
    protected static string $primaryKey = 'coupon_id';

    // Optional model-level blueprint (resolved by CrudController)
    protected static string $blueprint = 'coupon.yaml';

    /** @var array<string, array<string, mixed>>|null */
    protected static ?array $couponColumnsMetaCache = null;

    /** @var array<int, array{group_id:string, group_name:string}>|null */
    protected static ?array $customerGroupDataCache = null;

    /** @var array<int, array{id:string,name:string}>|null */
    protected static ?array $segmentationCategoryItemsCache = null;

    /** @var array<string, string>|null */
    protected static ?array $segmentationCategoryNamesByIdCache = null;

    /** @var string[] */
    protected static array $uiOnlyFields = [
        'prefill_assistant',
        'coupon_amount_percent',
        'coupon_amount_fixed',
        'coupon_shipping_hint',
        'coupon_code_template',
        'is_template',
        'coupon_name',
        'coupon_description',
        'coupon_description_restriction_hint',
    ];

    /**
     * Build runtime coupon schema by combining the static blueprint with the actual DB schema.
     * - Keeps known fields from coupon.yaml.
     * - Removes main-table fields not present in the active store schema.
     * - Adds generic editable fields for unknown addon columns.
     */
    public static function buildBlueprintSchema(string $blueprintPath, array $context = []): array
    {
        $schema = [];
        if (is_file($blueprintPath)) {
            try {
                $schema = Yaml::parseFile($blueprintPath) ?: [];
            } catch (\Throwable $e) {
                $schema = [];
            }
        }

        if (!is_array($schema)) {
            $schema = [];
        }
        if (!isset($schema['form']) || !is_array($schema['form'])) {
            $schema['form'] = [];
        }

        $form = $schema['form'];
        $fields = is_array($form['fields'] ?? null) ? $form['fields'] : [];
        $groups = is_array($form['groups'] ?? null) ? $form['groups'] : [];

        $fields = static::augmentRestrictionFieldDefinitions($fields);

        $columnMeta = static::couponColumnsMeta();
        if (empty($columnMeta)) {
            $schema['form']['fields'] = $fields;
            $schema['form']['groups'] = $groups;
            return $schema;
        }

        // Remove fields for columns that don't exist in this store schema.
        foreach ($fields as $fieldName => $_def) {
            $name = (string)$fieldName;
            if (isset($columnMeta[$name])) {
                continue;
            }
            if (in_array($name, static::$uiOnlyFields, true)) {
                continue;
            }
            unset($fields[$name]);
        }

        $knownColumns = [];
        foreach ($fields as $fieldName => $_def) {
            $name = (string)$fieldName;
            if (isset($columnMeta[$name])) {
                $knownColumns[$name] = true;
            }
        }

        $unknownFields = [];
        foreach ($columnMeta as $columnName => $meta) {
            if ($columnName === static::$primaryKey) {
                continue;
            }
            if (isset($knownColumns[$columnName])) {
                continue;
            }
            $unknownFields[] = $columnName;
            $fields[$columnName] = static::buildUnknownFieldDefinition($columnName, $meta);
        }

        if (!empty($unknownFields)) {
            $noticeField = '__coupon_unknown_fields_notice';
            $fields[$noticeField] = [
                'type' => 'notification',
                'label' => '',
                'variant' => 'warning',
                'dismissible' => false,
                'message' => 'Unknown field(s) detected in coupon schema. These fields are shown in generic mode. Contact MailBeez for dedicated support of this field.',
            ];

            // Remove existing unknown group if present, then append rebuilt group at the end.
            $filteredGroups = [];
            foreach ($groups as $group) {
                if (!is_array($group)) {
                    continue;
                }
                if ((string)($group['id'] ?? '') === 'advanced_unknown') {
                    continue;
                }
                $filteredGroups[] = $group;
            }
            $filteredGroups[] = [
                'id' => 'advanced_unknown',
                'label' => 'Advanced / Unknown fields',
                'fields' => array_merge([$noticeField], $unknownFields),
            ];
            $groups = $filteredGroups;
        }

        $schema['form']['fields'] = $fields;
        $schema['form']['groups'] = $groups;
        return $schema;
    }

    /**
     * Returns table column metadata keyed by column name.
     *
     * @return array<string, array<string, mixed>>
     */
    protected static function couponColumnsMeta(): array
    {
        if (static::$couponColumnsMetaCache !== null) {
            return static::$couponColumnsMetaCache;
        }

        $meta = [];
        if (!function_exists('mh_db_query') || !function_exists('mh_db_fetch_array')) {
            static::$couponColumnsMetaCache = $meta;
            return $meta;
        }

        $table = static::table();
        if ($table === '') {
            static::$couponColumnsMetaCache = $meta;
            return $meta;
        }

        $tableSql = '`' . str_replace('`', '', $table) . '`';

        try {
            $q = \mh_db_query("SHOW FULL COLUMNS FROM {$tableSql}");
            while ($row = \mh_db_fetch_array($q)) {
                $field = (string)($row['Field'] ?? '');
                if ($field === '') {
                    continue;
                }

                $typeRaw = strtolower((string)($row['Type'] ?? ''));
                $type = $typeRaw;
                if (preg_match('/^([a-z]+)/', $typeRaw, $m)) {
                    $type = strtolower((string)$m[1]);
                }

                $length = null;
                $precision = null;
                $scale = null;
                if (preg_match('/\((\d+)(?:\s*,\s*(\d+))?\)/', $typeRaw, $mm)) {
                    $length = (int)$mm[1];
                    if (isset($mm[2]) && $mm[2] !== '') {
                        $precision = (int)$mm[1];
                        $scale = (int)$mm[2];
                    }
                }

                $meta[$field] = [
                    'name' => $field,
                    'type' => $type,
                    'type_raw' => $typeRaw,
                    'nullable' => strtoupper((string)($row['Null'] ?? 'YES')) === 'YES',
                    'default' => $row['Default'] ?? null,
                    'length' => $length,
                    'precision' => $precision,
                    'scale' => $scale,
                    'unsigned' => false !== strpos($typeRaw, 'unsigned'),
                    'extra' => strtolower((string)($row['Extra'] ?? '')),
                ];
            }
            if (function_exists('mh_db_free_result')) {
                \mh_db_free_result($q);
            }
        } catch (\Throwable $e) {
            $meta = [];
        }

        static::$couponColumnsMetaCache = $meta;
        return $meta;
    }

    /**
     * Build a generic field definition for unknown coupon columns.
     *
     * @param array<string,mixed> $meta
     * @return array<string,mixed>
     */
    protected static function buildUnknownFieldDefinition(string $columnName, array $meta): array
    {
        $type = (string)($meta['type'] ?? '');
        $typeRaw = (string)($meta['type_raw'] ?? '');

        $def = [
            'type' => 'input',
            'label' => 'Unknown field: ' . $columnName,
            'validate' => [
                'type' => 'string',
                'required' => !(bool)($meta['nullable'] ?? true) && (($meta['default'] ?? null) === null),
            ],
        ];

        if (isset($meta['default']) && $meta['default'] !== null && $meta['default'] !== '') {
            $def['default'] = (string)$meta['default'];
        }

        switch ($type) {
            case 'datetime':
            case 'timestamp':
                $def['type'] = 'date_time';
                $def['validate']['type'] = 'date_time';
                break;
            case 'date':
                $def['type'] = 'date';
                $def['validate']['type'] = 'date';
                break;
            case 'text':
            case 'mediumtext':
            case 'longtext':
            case 'tinytext':
                $def['type'] = 'textarea';
                $def['validate']['type'] = 'string';
                break;
            case 'decimal':
            case 'double':
            case 'float':
                $def['type'] = 'input';
                $def['validate']['type'] = 'number';
                break;
            case 'tinyint':
            case 'smallint':
            case 'mediumint':
            case 'int':
            case 'integer':
            case 'bigint':
                $def['type'] = 'input';
                $def['validate']['type'] = 'integer';
                if (false !== strpos($typeRaw, 'tinyint(1)')) {
                    $def['validate']['min'] = '0';
                    $def['validate']['max'] = '1';
                }
                break;
            case 'enum':
                $options = static::extractEnumOptions($typeRaw);
                if (!empty($options)) {
                    $def['type'] = 'select';
                    $def['options'] = [];
                    foreach ($options as $opt) {
                        $def['options'][$opt] = $opt;
                    }
                    $def['validate']['type'] = 'select';
                    $def['values'] = array_values($options);
                }
                break;
            default:
                $def['type'] = 'input';
                $def['validate']['type'] = 'string';
                break;
        }

        $length = isset($meta['length']) ? (int)$meta['length'] : 0;
        if ($length > 0 && in_array($def['validate']['type'], ['string', 'email'], true)) {
            $def['validate']['max'] = (string)$length;
        }

        return $def;
    }

    /**
     * @return string[]
     */
    protected static function extractEnumOptions(string $typeRaw): array
    {
        if (!preg_match('/^enum\((.*)\)$/i', trim($typeRaw), $m)) {
            return [];
        }

        $inside = (string)$m[1];
        if ($inside === '') {
            return [];
        }

        $options = str_getcsv($inside, ',', "'", '\\');
        $out = [];
        foreach ($options as $opt) {
            $v = trim((string)$opt);
            if ($v === '') {
                continue;
            }
            $out[] = $v;
        }
        return $out;
    }

    /**
     * Customer group data for coupon restriction controls.
     *
     * @return array<int, array{group_id:string, group_name:string}>
     */
    public static function getCustomerGroupData(): array
    {
        if (static::$customerGroupDataCache !== null) {
            return static::$customerGroupDataCache;
        }

        try {
            $out = LegacyStoreData::getCustomerGroupData();
        } catch (\Throwable $e) {
            $out = [];
        }

        static::$customerGroupDataCache = is_array($out) ? $out : [];
        return static::$customerGroupDataCache;
    }

    /**
     * @return array<string, string>
     */
    public static function getCustomerGroupOptions(): array
    {
        $options = [];
        foreach (static::getCustomerGroupData() as $group) {
            $groupId = trim((string)($group['group_id'] ?? ''));
            $groupName = trim((string)($group['group_name'] ?? ''));
            if ($groupId === '' || $groupName === '') {
                continue;
            }
            $options[$groupId] = $groupName;
        }
        return $options;
    }

    /**
     * Enrich coupon restriction fields for BeezUI pillbox usage.
     *
     * @param array<string, mixed> $fields
     * @return array<string, mixed>
     */
    protected static function augmentRestrictionFieldDefinitions(array $fields): array
    {
        if (isset($fields['restrict_to_categories']) && is_array($fields['restrict_to_categories'])) {
            $categoryField = $fields['restrict_to_categories'];
            $categoryField['type'] = 'pillbox';
            $categoryField['mode'] = 'local';
            $categoryField['multiple'] = true;
            $categoryField['min_chars'] = 0;
            $categoryField['items'] = static::getSegmentationCategoryPillboxItems();
            $fields['restrict_to_categories'] = $categoryField;
        }

        if (isset($fields['restrict_to_products']) && is_array($fields['restrict_to_products'])) {
            $productField = $fields['restrict_to_products'];
            $productField['type'] = 'pillbox';
            $productField['mode'] = 'remote';
            $productField['multiple'] = true;
            $productField['min_chars'] = 2;
            $productField['delay'] = 250;
            try {
                $searchUrl = \Components\Legacy\LegacyRouter::url('segmentationProductSearch');
                if ($searchUrl !== '') {
                    $productField['search_url'] = $searchUrl;
                }
            } catch (\Throwable $e) {
                // keep field functional without remote URL
            }
            $fields['restrict_to_products'] = $productField;
        }

        return $fields;
    }

    /**
     * @return array<int, array{id:string,name:string}>
     */
    protected static function getSegmentationCategoryPillboxItems(): array
    {
        if (static::$segmentationCategoryItemsCache !== null) {
            return static::$segmentationCategoryItemsCache;
        }

        try {
            $raw = LegacyStoreData::getSegmentationCategoriesData();
        } catch (\Throwable $e) {
            $raw = [];
        }

        $items = [];
        $seen = [];

        if (is_array($raw)) {
            foreach ($raw as $key => $entry) {
                $id = '';
                $name = '';

                if (is_array($entry)) {
                    $id = trim((string)($entry['id'] ?? $entry['value'] ?? $entry['key'] ?? $key));
                    $name = trim((string)($entry['name'] ?? $entry['label'] ?? $entry['text'] ?? $id));
                } elseif ((is_int($key) || ctype_digit((string)$key))) {
                    $id = trim((string)$entry);
                    $name = trim((string)($raw['label' . (string)$key] ?? ''));
                } else {
                    if (is_string($key) && 0 === strpos($key, 'label')) {
                        continue;
                    }
                    $id = trim((string)$key);
                    $name = trim((string)$entry);
                }

                if ($id === '' || !preg_match('/^\d+$/', $id)) {
                    continue;
                }
                $id = (string)((int)$id);
                if ($name === '') {
                    $name = $id;
                }
                if (isset($seen[$id])) {
                    continue;
                }

                $seen[$id] = true;
                $items[] = ['id' => $id, 'name' => $name];
            }
        }

        static::$segmentationCategoryItemsCache = $items;
        return static::$segmentationCategoryItemsCache;
    }

    /**
     * @return array<string, string>
     */
    protected static function getSegmentationCategoryNamesById(): array
    {
        if (static::$segmentationCategoryNamesByIdCache !== null) {
            return static::$segmentationCategoryNamesByIdCache;
        }

        $map = [];
        foreach (static::getSegmentationCategoryPillboxItems() as $item) {
            $id = trim((string)($item['id'] ?? ''));
            $name = trim((string)($item['name'] ?? ''));
            if ($id === '' || $name === '') {
                continue;
            }
            $map[$id] = $name;
        }

        static::$segmentationCategoryNamesByIdCache = $map;
        return static::$segmentationCategoryNamesByIdCache;
    }

    /**
     * @param mixed $value
     * @return array<string, string>
     */
    protected static function getSegmentationProductNamesById($value): array
    {
        $ids = static::normalizeRestrictCsvIdsValue($value);
        if (empty($ids)) {
            return [];
        }

        try {
            $rows = LegacyStoreData::loadProductsForConfig($ids);
        } catch (\Throwable $e) {
            $rows = [];
        }

        $map = [];
        foreach ($rows as $row) {
            if (!is_array($row)) {
                continue;
            }
            $id = trim((string)($row['id'] ?? ''));
            $name = trim((string)($row['name'] ?? ''));
            if ($id === '' || $name === '') {
                continue;
            }
            $map[$id] = $name;
        }

        return $map;
    }

    /**
     * @param mixed $value
     * @return string[]
     */
    protected static function normalizeRestrictCsvIdsValue($value): array
    {
        $tokens = [];

        $collect = function ($entry) use (&$collect, &$tokens): void {
            if (is_array($entry)) {
                if (isset($entry['id']) || isset($entry['value']) || isset($entry['key'])) {
                    $tokens[] = (string)($entry['id'] ?? $entry['value'] ?? $entry['key']);
                    return;
                }
                foreach ($entry as $nested) {
                    $collect($nested);
                }
                return;
            }

            if ($entry === null) {
                return;
            }

            $scalar = trim((string)$entry);
            if ($scalar === '') {
                return;
            }

            $decoded = json_decode($scalar, true);
            if (json_last_error() === JSON_ERROR_NONE && is_array($decoded)) {
                $collect($decoded);
                return;
            }

            foreach (explode(',', $scalar) as $chunk) {
                $tokens[] = (string)$chunk;
            }
        };

        $collect($value);

        $ids = [];
        foreach ($tokens as $token) {
            $id = trim((string)$token);
            if ($id === '' || !preg_match('/^\d+$/', $id)) {
                continue;
            }
            $ids[(string)((int)$id)] = true;
        }

        return array_values(array_map('strval', array_keys($ids)));
    }

    /**
     * @param mixed $value
     */
    protected static function normalizeRestrictCsv($value): string
    {
        $ids = static::normalizeRestrictCsvIdsValue($value);
        return empty($ids) ? '' : implode(',', $ids);
    }

    /**
     * @param mixed $value
     * @param array<string, string> $namesById
     * @return array<int, array{id:string,name:string}>
     */
    protected static function normalizeRestrictPillBoxValue($value, array $namesById = []): array
    {
        $ids = static::normalizeRestrictCsvIdsValue($value);
        $out = [];
        foreach ($ids as $id) {
            $name = trim((string)($namesById[$id] ?? ''));
            if ($name === '') {
                $name = $id;
            }
            $out[] = ['id' => $id, 'name' => $name];
        }
        return $out;
    }


    /**
     * @param mixed $value
     * @return string[]
     */
    protected static function normalizeRestrictToCustomersValue($value): array
    {
        $tokens = [];

        if (is_array($value)) {
            foreach ($value as $entry) {
                if (is_array($entry)) {
                    foreach ($entry as $nested) {
                        $tokens[] = (string)$nested;
                    }
                    continue;
                }
                $tokens[] = (string)$entry;
            }
        } else {
            $tokens[] = (string)$value;
        }

        $ids = [];
        foreach ($tokens as $token) {
            foreach (explode(',', $token) as $piece) {
                $id = trim($piece);
                if ($id === '' || !preg_match('/^\d+$/', $id)) {
                    continue;
                }
                $ids[(string)((int)$id)] = true;
            }
        }

        return array_keys($ids);
    }

    /**
     * @param mixed $value
     */
    protected static function normalizeRestrictToCustomersCsv($value): string
    {
        $ids = static::normalizeRestrictToCustomersValue($value);
        return empty($ids) ? '' : implode(',', $ids);
    }

    protected static function normalizeTemplateCouponCodeForSave(array $data): string
    {
        $isTemplate = (string)($data['is_template'] ?? '0');
        if ($isTemplate !== '1') {
            return '';
        }

        $code = trim((string)($data['coupon_code_template'] ?? $data['coupon_code'] ?? ''));
        if ($code === '') {
            return '';
        }

        if (0 !== strpos($code, 'template_')) {
            $code = 'template_' . $code;
        }

        return $code;
    }

    public function validateBeforeSave(array $data, array $submitted = [], array $context = []): array
    {
        $normalizedCode = static::normalizeTemplateCouponCodeForSave($data);
        if ($normalizedCode === '') {
            return [];
        }

        $currentId = (int)($context['id'] ?? ($this->{static::$primaryKey} ?? 0));

        try {
            $query = \mh_db_query(
                "SELECT " . static::$primaryKey . " AS coupon_id FROM " . static::table() .
                " WHERE coupon_code = '" . \mh_db_input($normalizedCode) . "' LIMIT 1"
            );
            if (!$query) {
                return [];
            }

            $row = \mh_db_fetch_array($query);
            \mh_db_free_result($query);

            if (!is_array($row) || empty($row)) {
                return [];
            }

            $existingId = (int)($row['coupon_id'] ?? 0);
            if ($existingId > 0 && $currentId > 0 && $existingId === $currentId) {
                return [];
            }

            return [
                'coupon_code_template' => [
                    (string)mh_lng('UI_COUPON_MODAL_CODE_ALREADY_EXISTS', 'This coupon code already exists. Please choose another code.'),
                ],
            ];
        } catch (\Throwable $e) {
            return [];
        }
    }

    /**
     * Hook to sanitize data before saving.
     * - Persist only real table columns (plus required UI transformations).
     * - Keep unknown/addon columns if present in active schema.
     * - Strip i18n/helper fields that do not belong to TABLE_COUPONS.
     */
    protected function filterAttributesForSave(array $data): array
    {
        $columnMeta = static::couponColumnsMeta();
        $knownColumns = array_fill_keys(array_keys($columnMeta), true);

        // Keep only DB columns + explicit helper fields used for mapping.
        $helperFields = [
            'coupon_amount_percent' => true,
            'coupon_amount_fixed' => true,
            'coupon_code_template' => true,
            'is_template' => true,
        ];
        foreach (array_keys($data) as $key) {
            if (isset($knownColumns[$key]) || isset($helperFields[$key])) {
                continue;
            }
            unset($data[$key]);
        }

        // Remove translation arrays from main table payload
        foreach (['coupon_name','coupon_description'] as $tx) {
            if (array_key_exists($tx, $data)) { unset($data[$tx]); }
        }

        // Map UI helper fields to canonical coupon_amount based on coupon_type
        $type = strtoupper(trim((string)($data['coupon_type'] ?? '')));
        $amtPercent = isset($data['coupon_amount_percent']) ? (string)$data['coupon_amount_percent'] : '';
        $amtFixed = isset($data['coupon_amount_fixed']) ? (string)$data['coupon_amount_fixed'] : '';
        $setAmount = null;
        if ($type === 'P') {
            // percentage as-is (e.g., 10 => 10%)
            if ($amtPercent === '' && isset($data['coupon_amount'])) { $amtPercent = (string)$data['coupon_amount']; }
            if ($amtPercent !== '') {
                $num = is_numeric($amtPercent) ? (float)$amtPercent : (float)preg_replace('/[^0-9.\-]/', '', $amtPercent);
                $setAmount = number_format($num, 4, '.', '');
            } else {
                $setAmount = '0.00';
            }
        } elseif ($type === 'F') {
            // fixed currency amount
            if ($amtFixed === '' && isset($data['coupon_amount'])) { $amtFixed = (string)$data['coupon_amount']; }
            if ($amtFixed !== '') {
                $num = is_numeric($amtFixed) ? (float)$amtFixed : (float)preg_replace('/[^0-9.\-]/', '', $amtFixed);
                $setAmount = number_format($num, 4, '.', '');
            } else {
                $setAmount = '0.00';
            }
        } elseif ($type === 'S') {
            // free shipping -> amount not used
            $setAmount = '0.00';
        }
        if ($setAmount !== null) {
            $data['coupon_amount'] = $setAmount;
        }
        // Drop UI-only fields
        unset($data['coupon_amount_percent'], $data['coupon_amount_fixed']);

        if (array_key_exists('restrict_to_customers', $data)) {
            $data['restrict_to_customers'] = static::normalizeRestrictToCustomersCsv($data['restrict_to_customers']);
        }

        foreach (['restrict_to_categories', 'restrict_to_products'] as $restrictionField) {
            if (!array_key_exists($restrictionField, $data)) {
                continue;
            }
            $data[$restrictionField] = static::normalizeRestrictCsv($data[$restrictionField]);
        }

        // Normalize empty values against the live schema to avoid type errors
        // (e.g. "Incorrect integer value: '' for column ...").
        // Table columns differ by underlying store system, so we rely on the
        // runtime-introspected column metadata rather than hard-coded fields.
        $dateTypes = ['date', 'datetime', 'timestamp'];
        $integerTypes = ['tinyint', 'smallint', 'mediumint', 'int', 'integer', 'bigint'];
        $decimalTypes = ['decimal', 'numeric', 'double', 'float', 'real'];

        foreach ($columnMeta as $columnName => $meta) {
            $colType = (string)($meta['type'] ?? '');

            $isDate = in_array($colType, $dateTypes, true);
            $isInteger = in_array($colType, $integerTypes, true);
            $isDecimal = in_array($colType, $decimalTypes, true);
            if (!$isDate && !$isInteger && !$isDecimal) {
                continue;
            }
            if (!array_key_exists($columnName, $data)) {
                continue;
            }
            if ($data[$columnName] !== '' && $data[$columnName] !== null) {
                continue;
            }

            $default = $meta['default'] ?? null;
            if (is_string($default) && $default !== '') {
                $data[$columnName] = $default;
                continue;
            }

            if (($meta['nullable'] ?? false) === true) {
                $data[$columnName] = null;
                continue;
            }

            if ($isInteger) {
                $data[$columnName] = '0';
                continue;
            }

            if ($isDecimal) {
                $scale = isset($meta['scale']) ? (int)$meta['scale'] : 0;
                $data[$columnName] = $scale > 0 ? number_format(0, $scale, '.', '') : '0';
                continue;
            }

            $data[$columnName] = ($colType === 'date') ? '1000-01-01' : '1000-01-01 00:00:00';
        }

        if (isset($data['is_template']) && $data['is_template'] === '1') {
            $data['coupon_code'] = static::normalizeTemplateCouponCodeForSave($data);
        }
        unset($data['is_template'], $data['coupon_code_template']);

        return $data;
    }

    public function icon() {
        return self::getIcon($this->attributes['coupon_type'] ?? '');
    }

    public static function getIcon(string $type): array
    {
        switch (strtoupper($type)) {
            case 'F':
                return ['svg' => \Components\Base::SVG_OUTLINE_MONEY, 'color' => self::getCouponTypeColor('F'), 'size' => 'sm', 'variant' => 'solid'];
            case 'P':
                return ['svg' => \Components\Base::SVG_VOUCHER, 'color' => self::getCouponTypeColor('P'), 'size' => 'sm', 'variant' => 'solid'];
            case 'S':
                return ['svg' => \Components\Base::SVG_OUTLINE_TRUCK, 'color' => self::getCouponTypeColor('S'), 'size' => 'sm', 'variant' => 'solid'];
            default:
                return ['svg' => \Components\Base::SVG_MINUS, 'color' => 'gray', 'size' => 'sm', 'variant' => 'solid'];
        }
    }

    static public function getCouponTypeColor($type) {
        $map = [
            'F' => 'green',
            'P' => 'orange',
            'S' => 'blue',
        ];
        return $map[$type] ?? 'gray';
    }

    /**
     * Default values for new coupon records (used by CrudController on GET/new).
     */
    public static function getNewDefaults(): array
    {
        $defaults = [
            'coupon_active' => 'Y',
            'coupon_type' => 'P',
            'coupon_amount' => '0',
            'coupon_minimum_order' => '0.00',
            'uses_per_coupon' => 1,
            'uses_per_user' => 1,
        ];

        $presetKeys = [
            'coupon_type',
            'coupon_code_template',
            'coupon_amount',
            'coupon_amount_percent',
            'coupon_amount_fixed',
            'coupon_minimum_order',
            'coupon_name',
            'coupon_description',
            'uses_per_coupon',
            'uses_per_user',
        ];

        // Prefer preset_id lookup (current flow), then allow direct GET overrides for backwards compatibility.
        $presetId = strtolower(trim((string)mh_get('preset_id', '', 'string')));

        if ($presetId !== '' && $presetId !== 'empty') {
            $preset = self::getPresetById($presetId);
            if (is_array($preset) && isset($preset['data']) && is_array($preset['data'])) {
                foreach ($presetKeys as $key) {
                    if (array_key_exists($key, $preset['data'])) {
                        $defaults[$key] = $preset['data'][$key];
                    }
                }
            }
        }

        // Merge parameters from GET to support legacy preset transport
        foreach ($presetKeys as $key) {
            if (isset($_GET[$key])) {
                $defaults[$key] = $_GET[$key];
            }
        }
        try {
            $now = new \DateTime('now');
            $defaults['coupon_start_date'] = $now->format('Y-m-d H:i:s');
            $exp = clone $now;
            $exp->modify('+14 days');
            $defaults['coupon_expire_date'] = $exp->format('Y-m-d H:i:s');

            $isTemplate = (string)mh_get('is_template', '0', 'int');
            $defaults['is_template'] = $isTemplate;

            if ($isTemplate === '1') {
                if (isset($defaults['coupon_code_template']) && is_string($defaults['coupon_code_template']) && 0 === strpos($defaults['coupon_code_template'], 'template_')) {
                    $defaults['coupon_code_template'] = substr($defaults['coupon_code_template'], 9);
                }

                $type = strtoupper(trim((string)($defaults['coupon_type'] ?? '')));
                if ($type === '') {
                    $type = 'P';
                    $defaults['coupon_type'] = 'P';
                }

                if ($type === 'P') {
                    if (!isset($defaults['coupon_amount_percent']) || trim((string)$defaults['coupon_amount_percent']) === '') {
                        if (isset($defaults['coupon_amount'])) {
                            $defaults['coupon_amount_percent'] = (string)$defaults['coupon_amount'];
                        }
                    }
                    if (!isset($defaults['coupon_amount_percent']) || trim((string)$defaults['coupon_amount_percent']) === '') {
                        $defaults['coupon_amount_percent'] = '10';
                    }
                } elseif ($type === 'F') {
                    if ((!isset($defaults['coupon_amount_fixed']) || trim((string)$defaults['coupon_amount_fixed']) === '') && isset($defaults['coupon_amount'])) {
                        $defaults['coupon_amount_fixed'] = (string)$defaults['coupon_amount'];
                    }
                }

                if (!isset($defaults['uses_per_coupon']) || trim((string)$defaults['uses_per_coupon']) === '') {
                    $defaults['uses_per_coupon'] = 1;
                }
                if (!isset($defaults['uses_per_user']) || trim((string)$defaults['uses_per_user']) === '') {
                    $defaults['uses_per_user'] = 1;
                }
            }
        } catch (\Throwable $e) { /* ignore */ }


        return $defaults;
    }

    public static function getPresets(string $context = ''): array
    {
        $normalizeUtf8 = static function (string $value): string {
            if ($value === '' || preg_match('//u', $value) === 1) {
                return $value;
            }

            $sourceEncodings = ['Windows-1252', 'ISO-8859-1', 'ISO-8859-15'];

            if (function_exists('mb_convert_encoding')) {
                foreach ($sourceEncodings as $sourceEncoding) {
                    $converted = @mb_convert_encoding($value, 'UTF-8', $sourceEncoding);
                    if (is_string($converted) && $converted !== '' && preg_match('//u', $converted) === 1) {
                        return $converted;
                    }
                }
            }

            if (function_exists('iconv')) {
                foreach ($sourceEncodings as $sourceEncoding) {
                    $converted = @iconv($sourceEncoding, 'UTF-8//IGNORE', $value);
                    if (is_string($converted) && $converted !== '' && preg_match('//u', $converted) === 1) {
                        return $converted;
                    }
                }
            }

            return $value;
        };

        $langList = [];
        if (function_exists('mh_get_languages')) {
            try { $langList = (array)\mh_get_languages(); } catch (\Throwable $e) { }
        }

        $presetData = [];
        foreach ($langList as $lg) {
            $id = (int)($lg['id'] ?? 0);
            $directory = strtolower(trim((string)($lg['directory'] ?? $lg['code'] ?? 'english')));
            if ($directory === '') {
                $directory = 'english';
            }
            $confPath = MH_DIR_FS_CATALOG . MH_ROOT_PATH . 'configbeez/config_coupon_engine/languages/' . $directory . '.conf';
            if (!file_exists($confPath)) {
                $confPath = MH_DIR_FS_CATALOG . MH_ROOT_PATH . 'configbeez/config_coupon_engine/languages/english.conf';
            }

            if (file_exists($confPath)) {
                $content = file_get_contents($confPath);
                if (is_string($content) && $content !== '') {
                    $content = $normalizeUtf8($content);
                }
                if (is_string($content) && preg_match_all('/^(\w+)\s*=\s*[\'"](.*?)[\'"]\s*$/m', $content, $matches)) {
                    foreach ($matches[1] as $index => $key) {
                        $presetData[$id][$key] = $normalizeUtf8((string)$matches[2][$index]);
                    }
                }
            }
        }

        $buildI18n = function($keyName) use ($langList, $presetData) {
            $res = [];
            $firstVal = '';
            foreach ($presetData as $data) {
                if (isset($data[$keyName])) {
                    $firstVal = $data[$keyName];
                    break;
                }
            }
            foreach ($langList as $lg) {
                $id = (int)$lg['id'];
                $res[$id] = $presetData[$id][$keyName] ?? $firstVal;
            }
            return $res;
        };

        $presets = [
            [
                'group' => \mh_lng('UI_PRESET_GROUP_PERCENT', 'Discount (%)'),
                'items' => [
                    [
                        'label' => '5% ' . \mh_lng('UI_PRESET_LABEL_DISCOUNT', 'Discount'),
                        'data' => [
                            'coupon_code_template' => '5pct',
                            'coupon_type' => 'P',
                            'coupon_amount_percent' => '5',
                            'coupon_name' => $buildI18n('preset_5pct_name'),
                            'coupon_description' => $buildI18n('preset_5pct_desc'),
                        ]
                    ],
                    [
                        'label' => '10% ' . \mh_lng('UI_PRESET_LABEL_DISCOUNT', 'Discount'),
                        'data' => [
                            'coupon_code_template' => '10pct',
                            'coupon_type' => 'P',
                            'coupon_amount_percent' => '10',
                            'coupon_name' => $buildI18n('preset_10pct_name'),
                            'coupon_description' => $buildI18n('preset_10pct_desc'),
                        ]
                    ],
                    [
                        'label' => '15% ' . \mh_lng('UI_PRESET_LABEL_DISCOUNT', 'Discount'),
                        'data' => [
                            'coupon_code_template' => '15pct',
                            'coupon_type' => 'P',
                            'coupon_amount_percent' => '15',
                            'coupon_name' => $buildI18n('preset_15pct_name'),
                            'coupon_description' => $buildI18n('preset_15pct_desc'),
                        ]
                    ],
                ]
            ],
            [
                'group' => \mh_lng('UI_PRESET_GROUP_FIXED', 'Fixed Amount'),
                'items' => [
                    [
                        'label' => \Components\Base::priceLabel(5) . ' ' . \mh_lng('UI_PRESET_LABEL_DISCOUNT', 'Discount') . ' (' . \mh_lng('UI_LABEL_AB', 'ab') . ' ' . \Components\Base::priceLabel(50) . ')',
                        'data' => [
                            'coupon_code_template' => '5fixed',
                            'coupon_type' => 'F',
                            'coupon_amount_fixed' => '5',
                            'coupon_minimum_order' => '50',
                            'coupon_name' => $buildI18n('preset_5fixed_name'),
                            'coupon_description' => $buildI18n('preset_5fixed_desc'),
                        ]
                    ],
                    [
                        'label' => \Components\Base::priceLabel(10) . ' ' . \mh_lng('UI_PRESET_LABEL_DISCOUNT', 'Discount') . ' (' . \mh_lng('UI_LABEL_AB', 'ab') . ' ' . \Components\Base::priceLabel(100) . ')',
                        'data' => [
                            'coupon_code_template' => '10fixed',
                            'coupon_type' => 'F',
                            'coupon_amount_fixed' => '10',
                            'coupon_minimum_order' => '100',
                            'coupon_name' => $buildI18n('preset_10fixed_name'),
                            'coupon_description' => $buildI18n('preset_10fixed_desc'),
                        ]
                    ],
                    [
                        'label' => \Components\Base::priceLabel(50) . ' ' . \mh_lng('UI_PRESET_LABEL_DISCOUNT', 'Discount') . ' (' . \mh_lng('UI_LABEL_AB', 'ab') . ' ' . \Components\Base::priceLabel(500) . ')',
                        'data' => [
                            'coupon_code_template' => '50fixed',
                            'coupon_type' => 'F',
                            'coupon_amount_fixed' => '50',
                            'coupon_minimum_order' => '500',
                            'coupon_name' => $buildI18n('preset_50fixed_name'),
                            'coupon_description' => $buildI18n('preset_50fixed_desc'),
                        ]
                    ],
                ]
            ],
            [
                'group' => \mh_lng('UI_PRESET_GROUP_SHIPPING', 'Shipping'),
                'items' => [
                    [
                        'label' => \mh_lng('UI_PRESET_LABEL_FREE_SHIPPING', 'Free Shipping'),
                        'data' => [
                            'coupon_code_template' => 'freeshipping',
                            'coupon_type' => 'S',
                            'coupon_name' => $buildI18n('preset_shipping_name'),
                            'coupon_description' => $buildI18n('preset_shipping_desc'),
                        ]
                    ]
                ]
            ]
        ];

        return self::applyPresetContext($presets, $context);
    }

    protected static function applyPresetContext(array $presets, string $context): array
    {
        if (strtolower(trim($context)) !== 'prefill') {
            return $presets;
        }

        foreach ($presets as $groupIndex => $group) {
            if (!isset($group['items']) || !is_array($group['items'])) {
                continue;
            }

            foreach ($group['items'] as $itemIndex => $item) {
                $data = $item['data'] ?? [];
                if (!is_array($data)) {
                    $data = [];
                }

                $type = strtoupper(trim((string)($data['coupon_type'] ?? '')));
                $amountPercent = '';
                $amountFixed = '';
                $amount = (string)($data['coupon_amount'] ?? '');
                if ($type === 'P') {
                    $amountPercent = (string)($data['coupon_amount_percent'] ?? $amount);
                    $amount = $amountPercent;
                } elseif ($type === 'F') {
                    $amountFixed = (string)($data['coupon_amount_fixed'] ?? $amount);
                    $amount = $amountFixed;
                } else {
                    $amountPercent = (string)($data['coupon_amount_percent'] ?? '');
                    $amountFixed = (string)($data['coupon_amount_fixed'] ?? '');
                    if ($amount === '') {
                        $amount = ($amountFixed !== '') ? $amountFixed : $amountPercent;
                    }
                }

                $presets[$groupIndex]['items'][$itemIndex]['data'] = [
                    'is_template' => '1',
                    'coupon_code_template' => (string)($data['coupon_code_template'] ?? ''),
                    'coupon_type' => (string)($data['coupon_type'] ?? ''),
                    'coupon_amount' => $amount,
                    'coupon_amount_percent' => $amountPercent,
                    'coupon_amount_fixed' => $amountFixed,
                    'coupon_minimum_order' => (string)($data['coupon_minimum_order'] ?? ''),
                ];
            }
        }

        return $presets;
    }

    public static function getPresetItemId(array $item, int $groupIndex = 0, int $itemIndex = 0): string
    {
        $presetId = (string)($item['id'] ?? '');
        if ($presetId === '') {
            $presetId = (string)($item['data']['coupon_code_template'] ?? '');
        }
        if ($presetId === '') {
            $presetId = 'preset_' . ($groupIndex + 1) . '_' . ($itemIndex + 1);
        }

        $presetId = strtolower(trim($presetId));
        $presetId = (string)preg_replace('/[^a-z0-9_-]+/', '_', $presetId);
        $presetId = trim($presetId, '_');

        if ($presetId === '') {
            $presetId = 'preset_' . ($groupIndex + 1) . '_' . ($itemIndex + 1);
        }

        return $presetId;
    }

    public static function getPresetsWithIds(): array
    {
        $presets = self::getPresets();

        foreach ($presets as $groupIndex => $group) {
            if (!isset($group['items']) || !is_array($group['items'])) {
                continue;
            }

            foreach ($group['items'] as $itemIndex => $item) {
                $presets[$groupIndex]['items'][$itemIndex]['id'] = self::getPresetItemId((array)$item, (int)$groupIndex, (int)$itemIndex);
            }
        }

        return $presets;
    }

    public static function getPresetById(string $presetId): ?array
    {
        $needle = strtolower(trim($presetId));
        if ($needle === '' || $needle === 'empty') {
            return null;
        }

        $presets = self::getPresetsWithIds();
        foreach ($presets as $group) {
            if (!isset($group['items']) || !is_array($group['items'])) {
                continue;
            }

            foreach ($group['items'] as $item) {
                $itemId = strtolower(trim((string)($item['id'] ?? '')));
                if ($itemId === $needle) {
                    return is_array($item) ? $item : null;
                }
            }
        }

        return null;
    }

    /**
     * Augment form values with i18n translations from coupons_description so they can be edited in the same dialog.
     * @param array $values
     * @return array
     */
    public function augmentFormValues(array $values): array
    {
        $id = (int)($values[static::$primaryKey] ?? $this->{static::$primaryKey} ?? 0);
        if ($id > 0) {
            try {
                $all = CouponDescription::forCoupon($id);
                $names = [];
                $descs = [];
                foreach ($all as $lid => $row) {
                    $names[$lid] = (string)($row['coupon_name'] ?? '');
                    $descs[$lid] = (string)($row['coupon_description'] ?? '');
                }
                $values['coupon_name'] = $names;
                $values['coupon_description'] = $descs;
            } catch (\Throwable $e) { /* ignore */ }
        }

        $isTemplate = (int)mh_get('is_template', ($values['is_template'] ?? 0), 'int');
        $code = (string)($values['coupon_code'] ?? '');
        if ($isTemplate === 1 || 0 === strpos($code, 'template_')) {
            $values['is_template'] = '1';
            if (0 === strpos($code, 'template_')) {
                $values['coupon_code_template'] = substr($code, 9);
            } elseif ($code !== '') {
                $values['coupon_code_template'] = $code;
            }
        } else {
            $values['is_template'] = '0';
        }

        if (array_key_exists('coupon_active', $values)) {
            $activeRaw = strtolower(trim((string)$values['coupon_active']));
            $values['coupon_active'] = in_array($activeRaw, ['y', '1', 'true', 'on', 'yes'], true) ? 'Y' : '';
        }

        // Ensure template prefix is stripped from helper field (e.g. from presets)
        if (!empty($values['coupon_code_template']) && 0 === strpos($values['coupon_code_template'], 'template_')) {
            $values['coupon_code_template'] = substr($values['coupon_code_template'], 9);
        }

        // Prefill UI helper fields for amount based on coupon_type
        try {
            $type = strtoupper(trim((string)($values['coupon_type'] ?? $this->coupon_type ?? '')));
            $amt = (string)($values['coupon_amount'] ?? $this->coupon_amount ?? '');
            if ($amt !== '') {
                if ($type === 'P' && ($id > 0 || !isset($values['coupon_amount_percent']))) {
                    $values['coupon_amount_percent'] = $amt;
                } elseif ($type === 'F' && ($id > 0 || !isset($values['coupon_amount_fixed']))) {
                    $values['coupon_amount_fixed'] = $amt;
                }
            }
        } catch (\Throwable $e) { /* ignore */ }

        if (array_key_exists('restrict_to_customers', $values)) {
            $values['restrict_to_customers'] = static::normalizeRestrictToCustomersValue($values['restrict_to_customers']);
        }

        if (array_key_exists('restrict_to_categories', $values)) {
            $values['restrict_to_categories'] = static::normalizeRestrictPillBoxValue(
                $values['restrict_to_categories'],
                static::getSegmentationCategoryNamesById()
            );
        }

        if (array_key_exists('restrict_to_products', $values)) {
            $values['restrict_to_products'] = static::normalizeRestrictPillBoxValue(
                $values['restrict_to_products'],
                static::getSegmentationProductNamesById($values['restrict_to_products'])
            );
        }

        return $values;
    }

    /**
     * Persist translations posted as coupon_name[langId] and coupon_description[langId].
     */
    public function afterSave(array $post): void
    {
        $id = (int)($this->{static::$primaryKey} ?? 0);
        if ($id <= 0) return;
        $names = isset($post['coupon_name']) && is_array($post['coupon_name']) ? $post['coupon_name'] : [];
        $descs = isset($post['coupon_description']) && is_array($post['coupon_description']) ? $post['coupon_description'] : [];
        $map = [];
        // Prefer live env language list for stable ids
        $langIds = [];
        if (function_exists('mh_get_language_list')) {
            try {
                foreach ((array)\mh_get_language_list() as $lg) {
                    if (is_array($lg) && isset($lg['id'])) { $langIds[] = (int)$lg['id']; }
                }
            } catch (\Throwable $e) { $langIds = []; }
        }
        // If not available, derive from submitted keys
        if (empty($langIds)) {
            $langIds = array_unique(array_map('intval', array_merge(array_keys($names), array_keys($descs))));
        }
        foreach ($langIds as $lid) {
            $lid = (int)$lid; if ($lid <= 0) continue;
            $nm = isset($names[$lid]) ? trim((string)$names[$lid]) : '';
            $dc = isset($descs[$lid]) ? (string)$descs[$lid] : '';
            if ($nm === '' && $dc === '') continue; // nothing to store
            $map[$lid] = [ 'coupon_name' => $nm, 'coupon_description' => $dc ];
        }
        if (!empty($map)) {
            try { CouponDescription::syncForCoupon($id, $map); } catch (\Throwable $e) { /* ignore */ }
        }
    }

    public static function paginate(int $perPage = 10, int $page = 1, string $search = '', array $searchCols = [], array $filters = []): array
    {
        if (self::isDev()) return ['items' => [], 'total' => 0];

        $table = static::table();
        $where = $table . ".coupon_code NOT LIKE 'template_%'";
        $join = "";

        if (defined('TABLE_MAILBEEZ_TRACKING_COUPONS')) {
            if (isset($filters['template_coupon_id']) && (int)$filters['template_coupon_id'] > 0) {
                $templateId = (int)$filters['template_coupon_id'];
                $join = " JOIN " . TABLE_MAILBEEZ_TRACKING_COUPONS . " t ON " . $table . ".coupon_id = t.coupon_id";
                $where .= " AND t.template_coupon_id = " . $templateId;
            }
            if (isset($filters['batch_id']) && (int)$filters['batch_id'] > 0) {
                if ($join === "") {
                    $join = " JOIN " . TABLE_MAILBEEZ_TRACKING_COUPONS . " t ON " . $table . ".coupon_id = t.coupon_id";
                }
                $where .= " AND t.batch_id = " . (int)$filters['batch_id'];
            }
        }

        if (isset($filters['status']) && $filters['status'] !== '') {
            $statusFilter = (string)$filters['status'];
            $now = "NOW()";
            $activeSub = $table . ".coupon_active IN ('Y', '1')";
            $inactiveSub = $table . ".coupon_active NOT IN ('Y', '1')";
            $isStartValid = "(" . $table . ".coupon_start_date IS NOT NULL AND " . $table . ".coupon_start_date > '1001-01-01')";
            $isExpireValid = "(" . $table . ".coupon_expire_date IS NOT NULL AND " . $table . ".coupon_expire_date > '1001-01-01')";

            if ($statusFilter === 'inactive') {
                $where .= " AND " . $inactiveSub;
            } elseif ($statusFilter === 'expired') {
                $where .= " AND " . $activeSub . " AND " . $isExpireValid . " AND " . $table . ".coupon_expire_date < " . $now;
            } elseif ($statusFilter === 'scheduled') {
                $where .= " AND " . $activeSub . " AND " . $isStartValid . " AND " . $table . ".coupon_start_date > " . $now;
            } elseif ($statusFilter === 'active') {
                $where .= " AND " . $activeSub . " AND (NOT " . $isExpireValid . " OR " . $table . ".coupon_expire_date >= " . $now . ") AND (NOT " . $isStartValid . " OR " . $table . ".coupon_start_date <= " . $now . ")";
            }
        }

        if ($search !== '' && !empty($searchCols)) {
            $clauses = [];
            foreach ($searchCols as $col) {
                $clauses[] = $table . "." . $col . " LIKE '%" . \mh_db_input($search) . "%'";
            }
            $where .= " AND (" . implode(" OR ", $clauses) . ")";
        }

        $countSql = "SELECT COUNT(*) as total FROM " . $table . $join . " WHERE " . $where;
        $cq = \mh_db_query($countSql);
        $res = \mh_db_fetch_array($cq);
        $total = (int)($res['total'] ?? 0);
        \mh_db_free_result($cq);

        $offset = max(0, ($page - 1) * $perPage);

        $sql = "SELECT " . $table . ".* FROM " . $table . $join . " WHERE " . $where . " ORDER BY " . $table . "." . static::pk() . " DESC LIMIT " . (int)$offset . ", " . (int)$perPage;
        $q = \mh_db_query($sql);

        $items = [];
        while ($row = \mh_db_fetch_array($q)) {
            $items[] = new static($row);
        }
        \mh_db_free_result($q);

        return [
            'items' => $items,
            'total' => $total,
        ];
    }

    /**
     * Determine the current status of a coupon based on its active flag and date ranges.
     *
     * @param array|\Components\Models\Coupon $item Coupon record or object
     * @return string 'active', 'scheduled', 'expired', or 'inactive'
     */
    public static function getStatus($item): string
    {
        $attrs = ($item instanceof self) ? $item->attributes : $item;
        $active = ($attrs['coupon_active'] ?? '') === 'Y' || ($attrs['coupon_active'] ?? '') === '1';
        if (!$active) {
            return 'inactive';
        }

        $now = time();
        $start = $attrs['coupon_start_date'] ?? '';
        $expire = $attrs['coupon_expire_date'] ?? '';

        $isStartValid = ($start && substr($start, 0, 4) !== '0000' && substr($start, 0, 4) !== '0001' && substr($start, 0, 4) !== '1000');
        $isExpireValid = ($expire && substr($expire, 0, 4) !== '0000' && substr($expire, 0, 4) !== '0001' && substr($expire, 0, 4) !== '1000');

        if ($isExpireValid && strtotime($expire) < $now) {
            return 'expired';
        }

        if ($isStartValid && strtotime($start) > $now) {
            return 'scheduled';
        }

        return 'active';
    }

    /**
     * Render the status badge for a coupon.
     *
     * @param array|\Components\Models\Coupon $item
     * @param array $options Optional overrides for Badge::render
     * @return string HTML
     */
    public static function renderStatusBadge($item, array $options = []): string
    {
        $status = self::getStatus($item);
        return self::renderStatusBadgeByStatus($status, $options);
    }

    /**
     * Render a status badge by status key.
     *
     * @param string $status 'active', 'scheduled', 'expired', or 'inactive'
     * @param array $options Optional overrides for Badge::render
     * @return string HTML
     */
    public static function renderStatusBadgeByStatus(string $status, array $options = []): string
    {
        $all = self::getStatusConfig();
        $cfg = $all[$status] ?? $all['inactive'];

        $badgeOptions = array_merge([
            'class' => \Components\Support\Palette::newsletterStateBadgeClass($status),
//            'icon'  => $cfg['identity']['svg'] ?? null,
            'icon_class' => 'size-3.5',
        ], $options);

        return \Components\Ui\Badge::render($status, $cfg['label'], $badgeOptions);
    }

    /**
     * Get rich status options for UI components like rich_select.
     *
     * @return array<string, array{label: string, identity: array, label_html: string}>
     */
    public static function getStatusOptions(): array
    {
        $options = self::getStatusConfig();
        foreach ($options as $status => &$opt) {
            $opt['label_html'] = self::renderStatusBadgeByStatus($status);
        }
        return $options;
    }

    /**
     * Internal status configuration.
     * @return array
     */
    protected static function getStatusConfig(): array
    {
        return [
            'active' => [
                'label' => \mh_lng('UI_TEXT_STATUS_ACTIVE', 'Active'),
                'identity' => [ 'color' => 'green', 'size' => 'sm', 'variant' => 'soft']
            ],
//            'scheduled' => [
//                'label' => \mh_lng('UI_TEXT_STATUS_SCHEDULED', 'Scheduled'),
//                'identity' => [ 'color' => 'blue', 'size' => 'sm', 'variant' => 'soft']
//            ],
            'expired' => [
                'label' => \mh_lng('UI_TEXT_STATUS_EXPIRED', 'Expired'),
                'identity' => [ 'color' => 'red', 'size' => 'sm', 'variant' => 'soft']
            ],
            'inactive' => [
                'label' => \mh_lng('UI_TEXT_STATUS_INACTIVE', 'Inactive'),
                'identity' => ['color' => 'gray', 'size' => 'sm', 'variant' => 'soft']
            ],
        ];
    }

    /**
     * Dynamic select options for coupon_id (Coupon)
     * Returns rich option data including identity for CollectionIdentity rendering.
     *
     * @param mixed $includeNone Include "no coupon" option (if not boolean, defaults to true)
     * @return array<int|string, array{label: string, identity: array}>
     */
    public static function listOptions($includeNone = true): array
    {
        if (!is_bool($includeNone)) {
            $includeNone = true;
        }

        $options = [];

        try {
            $prov = \Components\Data\DataRegistry::templateCoupons();
            $rows = $prov ? $prov->templates($includeNone) : [];
        } catch (\Throwable $e) {
            $rows = [];
        }

        foreach ((array)$rows as $row) {
            if (!is_array($row)) continue;

            $id = $row['id'] ?? null;
            if ($id === null) continue;

            $code = $row['code'] ?? '';
            $codeMain = $row['codeMain'] ?? str_replace('template_', '', $code);
            $typeLabel = $row['typeLabel'] ?? '';
            $type = $row['type'] ?? '';

            $label = $id < 1 ? \mh_lng('MAILBEEZ_COUPON_TYPE_NONE', 'no coupon') : $codeMain . ' - ' . $typeLabel;

            $options[$id] = [
                'label'    => $label,
                'identity' => self::getIcon($type),
            ];
        }

        return $options;
    }
}
