# # """
# # Configuration et dictionnaires multilingues pour mkdocs-superquiz
# # """

# # import json
# # from pathlib import Path
# # from typing import Any, Dict, List, Optional


# # DEFAULTS_ALIASES = ("defaults", "global")


# # # ─────────────────────────────────────────────────────────────────────────────
# # # i18n helpers
# # # ─────────────────────────────────────────────────────────────────────────────

# # def load_i18n_config(language='en'):
# #     i18n_dir = Path(__file__).parent / 'i18n'
# #     i18n_file = i18n_dir / f'{language}.json'
# #     if not i18n_file.exists():
# #         i18n_file = i18n_dir / 'en.json'
# #     with open(i18n_file, 'r', encoding='utf-8') as f:
# #         return json.load(f)


# # def _merge_dedup_ci(primary_list, secondary_list):
# #     out = []
# #     seen = set()
# #     for src in (primary_list or []) + (secondary_list or []):
# #         s = str(src).strip()
# #         if not s:
# #             continue
# #         key = s.lower()
# #         if key in seen:
# #             continue
# #         seen.add(key)
# #         out.append(s)
# #     return out


# # def build_quiz_types_dict(language='en'):
# #     cfg_lang = load_i18n_config(language)
# #     cfg_en   = load_i18n_config('en')
# #     quiz_types = {}
# #     all_types = set((cfg_en.get('quiz_types', {}) or {}).keys()) | \
# #                 set((cfg_lang.get('quiz_types', {}) or {}).keys())
# #     for quiz_type in all_types:
# #         en_syn   = (cfg_en.get('quiz_types', {}).get(quiz_type, {}) or {}).get('synonyms', []) or []
# #         lang_syn = (cfg_lang.get('quiz_types', {}).get(quiz_type, {}) or {}).get('synonyms', []) or []
# #         quiz_types[quiz_type] = _merge_dedup_ci(lang_syn, en_syn)
# #     return quiz_types


# # def build_keywords_dict(language='en'):
# #     cfg_lang = load_i18n_config(language).get('quiz_configs', {}) or {}
# #     cfg_en   = load_i18n_config('en').get('quiz_configs', {}) or {}

# #     def merged(key_name, default_list):
# #         lang_list = cfg_lang.get(key_name, default_list) or []
# #         en_list   = cfg_en.get(key_name, default_list) or []
# #         return _merge_dedup_ci(lang_list, en_list)

# #     return {
# #         'preamble': merged('preamble_keywords', ['preamble', 'pre']),
# #         'question': merged('question_keywords',  ['question', 'q']),
# #         'answers':  merged('answers_keywords',   ['answers', 'ans']),
# #     }


# # QUIZ_TYPES = build_quiz_types_dict('en')
# # KEYWORDS   = build_keywords_dict('en')


# # # ─────────────────────────────────────────────────────────────────────────────
# # # compare_answers — new format only (no legacy)
# # # ─────────────────────────────────────────────────────────────────────────────

# # # Default compare_answers settings.
# # # Override at any level:
# # #   - globally in mkdocs.yml under plugins > superquiz > fillblanks > compare_answers
# # #   - per-page in the page front-matter (not yet implemented)
# # #   - per-quiz in the quiz front-matter (between --- delimiters)
# # #   - per-question in the question mini front-matter (between --- delimiters)
# # DEFAULT_COMPARE_ANSWERS: Dict[str, Any] = {
# #     "ignore": {
# #         "spaces":     "none",        # "none" | "outside" | "inside" | "all"
# #         "characters": "",            # string of characters to strip (e.g. "?,.;/:!¡-_")
# #         "case":       False,         # False = case-sensitive (default), True = case-insensitive
# #         "diacritics": False          # False = diacritics matter (default), True = ignore them
# #     },
# #     "equivalence": {}
# # }

# # _VALID_SPACES = frozenset(('all', 'inside', 'outside', 'none'))


# # def normalize_compare_answers(raw: Any) -> Dict[str, Any]:
# #     import copy

# #     if not raw or not isinstance(raw, dict):
# #         return copy.deepcopy(DEFAULT_COMPARE_ANSWERS)

# #     ignore = dict(DEFAULT_COMPARE_ANSWERS['ignore'])
# #     raw_ignore = raw.get('ignore', {})
# #     if isinstance(raw_ignore, dict):
# #         ignore.update(raw_ignore)

# #     if ignore.get('spaces') not in _VALID_SPACES:
# #         ignore['spaces'] = 'none'

# #     equivalence = _normalize_equivalence(raw.get('equivalence', {}))

# #     return {'ignore': ignore, 'equivalence': equivalence}


# # def _normalize_equivalence(raw_equiv: Any) -> Dict[str, List[str]]:
# #     if not isinstance(raw_equiv, dict):
# #         return {}
# #     out: Dict[str, List[str]] = {}
# #     for base, variants in raw_equiv.items():
# #         if isinstance(variants, str):
# #             stripped = variants.strip()
# #             if stripped.startswith('['):
# #                 try:
# #                     parsed = json.loads(stripped)
# #                     if isinstance(parsed, list):
# #                         out[str(base)] = [str(v) for v in parsed]
# #                         continue
# #                 except (json.JSONDecodeError, ValueError):
# #                     pass
# #             out[str(base)] = [variants]
# #         elif isinstance(variants, list):
# #             out[str(base)] = [str(v) for v in variants]
# #         else:
# #             out[str(base)] = [str(variants)]
# #     return out


# # # ─────────────────────────────────────────────────────────────────────────────
# # # DEFAULT_CONFIG
# # # ─────────────────────────────────────────────────────────────────────────────

# # DEFAULT_CONFIG = {
# #     'security_mode': 'obfuscated',
# #     'teacher_code': None,
# #     'encryption_key': 'default-key-change-me',
# #     'api_endpoint': None,
# #     'correction_granularity': 'exercise',

# #     'min_score': None,
# #     'clamp_max_score': True,

# #     'correction_locked_icon':   '❌',
# #     'correction_unlock_icon':   '🔐',
# #     'correction_unlocked_icon': '✅',

# #     'mcquiz': {
# #         'scoring_mode':        'proportional',
# #         'default_false':       0,
# #         'default_points':      1,
# #         'show_correction':     'on_demand',
# #         'save_answers':        True,
# #         'randomize_questions': False,
# #         'randomize_answers':   False,
# #         'synonyms':            []
# #     },

# #     'scquiz': {
# #         'scoring_mode':        'all_or_nothing',
# #         'default_false':       0,
# #         'default_points':      1,
# #         'show_correction':     'on_demand',
# #         'save_answers':        True,
# #         'randomize_questions': False,
# #         'randomize_answers':   False,
# #         'synonyms':            []
# #     },

# #     'fillblanks': {
# #         'scoring_mode':        'proportional',
# #         'default_false':       0,
# #         'default_points':      1,
# #         'show_correction':     'on_demand',
# #         'save_answers':        True,
# #         'randomize_questions': False,
# #         'synonyms':            [],
# #         # compare_answers defaults for fillblanks — override in mkdocs.yml, per-page,
# #         # per-quiz front-matter, or per-question front-matter.
# #         'compare_answers': {
# #             'ignore': {
# #                 'spaces':     'none',
# #                 'characters': '',
# #                 'case':       False,
# #                 'diacritics': False
# #             },
# #             'equivalence': {}
# #         }
# #     },

# #     'scdropdown': {
# #         'scoring_mode':        'all_or_nothing',
# #         'default_false':       0,
# #         'default_points':      1,
# #         'show_correction':     'on_demand',
# #         'save_answers':        True,
# #         'randomize_questions': False,
# #         'randomize_answers':   False,
# #         'synonyms':            []
# #     },

# #     'mcdropdown': {
# #         'scoring_mode':        'proportional',
# #         'default_false':       0,
# #         'default_points':      1,
# #         'show_correction':     'on_demand',
# #         'save_answers':        True,
# #         'randomize_questions': False,
# #         'randomize_answers':   False,
# #         'synonyms':            []
# #     },
# # }


# # # ─────────────────────────────────────────────────────────────────────────────
# # # Helpers
# # # ─────────────────────────────────────────────────────────────────────────────

# # def normalize_quiz_type(quiz_type_str: str, language: str = 'en') -> Optional[str]:
# #     quiz_types = build_quiz_types_dict(language)
# #     quiz_type_lower = quiz_type_str.lower()
# #     for canonical_type, synonyms in quiz_types.items():
# #         if quiz_type_lower in [s.lower() for s in synonyms]:
# #             return canonical_type
# #     return None


# # def normalize_keyword(keyword_str: str, language: str = 'en') -> Optional[str]:
# #     import re
# #     keywords = build_keywords_dict(language)
# #     keyword_base = re.sub(r'\d+$', '', keyword_str.lower())
# #     for canonical_keyword, synonyms in keywords.items():
# #         if keyword_base in [s.lower() for s in synonyms]:
# #             return canonical_keyword
# #     return None

# # =======================================================================================================
# # =======================================================================================================
# # =======================================================================================================

# """
# Configuration et dictionnaires multilingues pour mkdocs-superquiz
# """

# import json
# from pathlib import Path
# from typing import Any, Dict, List, Optional


# DEFAULTS_ALIASES = ("defaults", "global")


# # ─────────────────────────────────────────────────────────────────────────────
# # i18n helpers
# # ─────────────────────────────────────────────────────────────────────────────

# def load_i18n_config(language='en'):
#     i18n_dir = Path(__file__).parent / 'i18n'
#     i18n_file = i18n_dir / f'{language}.json'
#     if not i18n_file.exists():
#         i18n_file = i18n_dir / 'en.json'
#     with open(i18n_file, 'r', encoding='utf-8') as f:
#         return json.load(f)


# def _merge_dedup_ci(primary_list, secondary_list):
#     out = []
#     seen = set()
#     for src in (primary_list or []) + (secondary_list or []):
#         s = str(src).strip()
#         if not s:
#             continue
#         key = s.lower()
#         if key in seen:
#             continue
#         seen.add(key)
#         out.append(s)
#     return out


# def build_quiz_types_dict(language='en'):
#     cfg_lang = load_i18n_config(language)
#     cfg_en   = load_i18n_config('en')
#     quiz_types = {}
#     all_types = set((cfg_en.get('quiz_types', {}) or {}).keys()) | \
#                 set((cfg_lang.get('quiz_types', {}) or {}).keys())
#     for quiz_type in all_types:
#         en_syn   = (cfg_en.get('quiz_types', {}).get(quiz_type, {}) or {}).get('synonyms', []) or []
#         lang_syn = (cfg_lang.get('quiz_types', {}).get(quiz_type, {}) or {}).get('synonyms', []) or []
#         quiz_types[quiz_type] = _merge_dedup_ci(lang_syn, en_syn)
#     return quiz_types


# def build_keywords_dict(language='en'):
#     cfg_lang = load_i18n_config(language).get('quiz_configs', {}) or {}
#     cfg_en   = load_i18n_config('en').get('quiz_configs', {}) or {}

#     def merged(key_name, default_list):
#         lang_list = cfg_lang.get(key_name, default_list) or []
#         en_list   = cfg_en.get(key_name, default_list) or []
#         return _merge_dedup_ci(lang_list, en_list)

#     return {
#         'preamble': merged('preamble_keywords', ['preamble', 'pre']),
#         'question': merged('question_keywords',  ['question', 'q']),
#         'answers':  merged('answers_keywords',   ['answers', 'ans']),
#     }


# QUIZ_TYPES = build_quiz_types_dict('en')
# KEYWORDS   = build_keywords_dict('en')


# # ─────────────────────────────────────────────────────────────────────────────
# # compare_answers — new format only (no legacy)
# # ─────────────────────────────────────────────────────────────────────────────

# # Default compare_answers settings.
# # Override at any level:
# #   - globally in mkdocs.yml under plugins > superquiz > fillblanks > compare_answers
# #   - per-page in the page front-matter (not yet implemented)
# #   - per-quiz in the quiz front-matter (between --- delimiters)
# #   - per-question in the question mini front-matter (between --- delimiters)
# DEFAULT_COMPARE_ANSWERS: Dict[str, Any] = {
#     "ignore": {
#         "spaces":     "none",        # "none" | "outside" | "inside" | "all"
#         "characters": "",            # string of characters to strip (e.g. "?,.;/:!¡-_")
#         "case":       False,         # False = case-sensitive (default), True = case-insensitive
#         "diacritics": False          # False = diacritics matter (default), True = ignore them
#     },
#     "equivalence": {}
# }

# _VALID_SPACES = frozenset(('all', 'inside', 'outside', 'none'))


# def normalize_compare_answers(raw: Any) -> Dict[str, Any]:
#     import copy

#     if not raw or not isinstance(raw, dict):
#         return copy.deepcopy(DEFAULT_COMPARE_ANSWERS)

#     ignore = dict(DEFAULT_COMPARE_ANSWERS['ignore'])
#     raw_ignore = raw.get('ignore', {})
#     if isinstance(raw_ignore, dict):
#         ignore.update(raw_ignore)

#     if ignore.get('spaces') not in _VALID_SPACES:
#         ignore['spaces'] = 'none'

#     equivalence = _normalize_equivalence(raw.get('equivalence', {}))

#     return {'ignore': ignore, 'equivalence': equivalence}


# def _normalize_equivalence(raw_equiv: Any) -> Dict[str, List[str]]:
#     if not isinstance(raw_equiv, dict):
#         return {}
#     out: Dict[str, List[str]] = {}
#     for base, variants in raw_equiv.items():
#         if isinstance(variants, str):
#             stripped = variants.strip()
#             if stripped.startswith('['):
#                 try:
#                     parsed = json.loads(stripped)
#                     if isinstance(parsed, list):
#                         out[str(base)] = [str(v) for v in parsed]
#                         continue
#                 except (json.JSONDecodeError, ValueError):
#                     pass
#             out[str(base)] = [variants]
#         elif isinstance(variants, list):
#             out[str(base)] = [str(v) for v in variants]
#         else:
#             out[str(base)] = [str(variants)]
#     return out


# # ─────────────────────────────────────────────────────────────────────────────
# # DEFAULT_CONFIG
# # ─────────────────────────────────────────────────────────────────────────────

# # DEFAULT_CONFIG = {
# #     'security_mode': 'obfuscated',
# #     'teacher_code': None,
# #     'encryption_key': 'default-key-change-me',
# #     'api_endpoint': None,
# #     'correction_granularity': 'exercise',

# #     'min_score': None,
# #     'clamp_max_score': True,

# #     'correction_locked_icon':   '❌',
# #     'correction_unlock_icon':   '🔐',
# #     'correction_unlocked_icon': '✅',

# #     'mcquiz': {
# #         'scoring_mode':        'proportional',
# #         'default_false':       0,
# #         'default_points':      1,
# #         'show_correction':     'on_demand',
# #         'save_answers':        True,
# #         'randomize':           'none',
# #         'synonyms':            []
# #     },

# #     'scquiz': {
# #         'scoring_mode':        'all_or_nothing',
# #         'default_false':       0,
# #         'default_points':      1,
# #         'show_correction':     'on_demand',
# #         'save_answers':        True,
# #         'randomize':           'none',
# #         'synonyms':            []
# #     },

# #     'fillblanks': {
# #         'scoring_mode':        'proportional',
# #         'default_false':       0,
# #         'default_points':      1,
# #         'show_correction':     'on_demand',
# #         'save_answers':        True,
# #         'randomize':           'none',
# #         'synonyms':            [],
# #         # compare_answers defaults for fillblanks — override in mkdocs.yml, per-page,
# #         # per-quiz front-matter, or per-question front-matter.
# #         'compare_answers': {
# #             'ignore': {
# #                 'spaces':     'none',
# #                 'characters': '',
# #                 'case':       False,
# #                 'diacritics': False
# #             },
# #             'equivalence': {}
# #         }
# #     },

# #     'scdropdown': {
# #         'scoring_mode':        'all_or_nothing',
# #         'default_false':       0,
# #         'default_points':      1,
# #         'show_correction':     'on_demand',
# #         'save_answers':        True,
# #         'randomize':           'none',
# #         'synonyms':            []
# #     },

# #     'mcdropdown': {
# #         'scoring_mode':        'proportional',
# #         'default_false':       0,
# #         'default_points':      1,
# #         'show_correction':     'on_demand',
# #         'save_answers':        True,
# #         'randomize':           'none',
# #         'synonyms':            []
# #     },
# # }

# DEFAULT_CONFIG = {
#     'security_mode': 'obfuscated',
#     'teacher_code': None,
#     'encryption_key': 'default-key-change-me',
#     'api_endpoint': None,
#     'correction_granularity': 'exercise',

#     'min_score': None,
#     'clamp_max_score': True,

#     'correction_locked_icon':   '❌',
#     'correction_unlock_icon':   '🔐',
#     'correction_unlocked_icon': '✅',

#     'mcquiz': {
#         'scoring_mode':        'proportional',
#         'default_false':       0,
#         'default_points':      1,
#         'show_correction':     'on_demand',
#         'save_answers':        True,
#         'randomize_answers':   False,
#         'randomize_questions': False,
#         'synonyms':            []
#     },

#     'scquiz': {
#         'scoring_mode':        'all_or_nothing',
#         'default_false':       0,
#         'default_points':      1,
#         'show_correction':     'on_demand',
#         'save_answers':        True,
#         'randomize_answers':   False,
#         'randomize_questions': False,
#         'synonyms':            []
#     },

#     'fillblanks': {
#         'scoring_mode':        'proportional',
#         'default_false':       0,
#         'default_points':      1,
#         'show_correction':     'on_demand',
#         'save_answers':        True,
#         'randomize_answers':   False,
#         'randomize_questions': False,
#         'synonyms':            [],
#         'compare_answers': {
#             'ignore': {
#                 'spaces':     'none',
#                 'characters': '',
#                 'case':       False,
#                 'diacritics': False
#             },
#             'equivalence': {}
#         }
#     },

#     'scdropdown': {
#         'scoring_mode':        'all_or_nothing',
#         'default_false':       0,
#         'default_points':      1,
#         'show_correction':     'on_demand',
#         'save_answers':        True,
#         'randomize_answers':   False,
#         'randomize_questions': False,
#         'synonyms':            []
#     },

#     'mcdropdown': {
#         'scoring_mode':        'proportional',
#         'default_false':       0,
#         'default_points':      1,
#         'show_correction':     'on_demand',
#         'save_answers':        True,
#         'randomize_answers':   False,
#         'randomize_questions': False,
#         'synonyms':            []
#     },
#     'orderquiz': {
#     'scoring_mode':        'proportional',   # Kendall tau par défaut
#     'default_points':      1,
#     'show_correction':     'on_demand',
#     'save_answers':        True,
#     'randomize_answers':   False,            # n/a — toujours False
#     'randomize_questions': True,             # mélange les items au chargement
#     'synonyms':            []
# },
# }


# # ─────────────────────────────────────────────────────────────────────────────
# # Helpers
# # ─────────────────────────────────────────────────────────────────────────────

# def normalize_quiz_type(quiz_type_str: str, language: str = 'en') -> Optional[str]:
#     quiz_types = build_quiz_types_dict(language)
#     quiz_type_lower = quiz_type_str.lower()
#     for canonical_type, synonyms in quiz_types.items():
#         if quiz_type_lower in [s.lower() for s in synonyms]:
#             return canonical_type
#     return None


# def normalize_keyword(keyword_str: str, language: str = 'en') -> Optional[str]:
#     import re
#     keywords = build_keywords_dict(language)
#     keyword_base = re.sub(r'\d+$', '', keyword_str.lower())
#     for canonical_keyword, synonyms in keywords.items():
#         if keyword_base in [s.lower() for s in synonyms]:
#             return canonical_keyword
#     return None

# ===============================================================================================
# ===============================================================================================
# ===============================================================================================

"""
Configuration et dictionnaires multilingues pour mkdocs-superquiz
"""

import json
from pathlib import Path
from typing import Any, Dict, List, Optional


DEFAULTS_ALIASES = ("defaults", "global")


# ─────────────────────────────────────────────────────────────────────────────
# i18n helpers
# ─────────────────────────────────────────────────────────────────────────────

def load_i18n_config(language='en'):
    i18n_dir = Path(__file__).parent / 'i18n'
    i18n_file = i18n_dir / f'{language}.json'
    if not i18n_file.exists():
        i18n_file = i18n_dir / 'en.json'
    with open(i18n_file, 'r', encoding='utf-8') as f:
        return json.load(f)


def _merge_dedup_ci(primary_list, secondary_list):
    out = []
    seen = set()
    for src in (primary_list or []) + (secondary_list or []):
        s = str(src).strip()
        if not s:
            continue
        key = s.lower()
        if key in seen:
            continue
        seen.add(key)
        out.append(s)
    return out


def build_quiz_types_dict(language='en'):
    cfg_lang = load_i18n_config(language)
    cfg_en   = load_i18n_config('en')
    quiz_types = {}
    all_types = set((cfg_en.get('quiz_types', {}) or {}).keys()) | \
                set((cfg_lang.get('quiz_types', {}) or {}).keys())
    for quiz_type in all_types:
        en_syn   = (cfg_en.get('quiz_types', {}).get(quiz_type, {}) or {}).get('synonyms', []) or []
        lang_syn = (cfg_lang.get('quiz_types', {}).get(quiz_type, {}) or {}).get('synonyms', []) or []
        quiz_types[quiz_type] = _merge_dedup_ci(lang_syn, en_syn)
    return quiz_types


def build_keywords_dict(language='en'):
    cfg_lang = load_i18n_config(language).get('quiz_configs', {}) or {}
    cfg_en   = load_i18n_config('en').get('quiz_configs', {}) or {}

    def merged(key_name, default_list):
        lang_list = cfg_lang.get(key_name, default_list) or []
        en_list   = cfg_en.get(key_name, default_list) or []
        return _merge_dedup_ci(lang_list, en_list)

    return {
        'preamble': merged('preamble_keywords', ['preamble', 'pre']),
        'question': merged('question_keywords',  ['question', 'q']),
        'answers':  merged('answers_keywords',   ['answers', 'ans']),
    }


QUIZ_TYPES = build_quiz_types_dict('en')
KEYWORDS   = build_keywords_dict('en')


# ─────────────────────────────────────────────────────────────────────────────
# compare_answers — new format only (no legacy)
# ─────────────────────────────────────────────────────────────────────────────

DEFAULT_COMPARE_ANSWERS: Dict[str, Any] = {
    "ignore": {
        "spaces":     "none",        # "none" | "outside" | "inside" | "all"
        "characters": "",            # string of characters to strip (e.g. "?,.;/:!¡-_")
        "case":       False,         # False = case-sensitive (default), True = case-insensitive
        "diacritics": False          # False = diacritics matter (default), True = ignore them
    },
    "equivalence": {}
}

_VALID_SPACES = frozenset(('all', 'inside', 'outside', 'none'))


def normalize_compare_answers(raw: Any) -> Dict[str, Any]:
    import copy

    if not raw or not isinstance(raw, dict):
        return copy.deepcopy(DEFAULT_COMPARE_ANSWERS)

    ignore = dict(DEFAULT_COMPARE_ANSWERS['ignore'])
    raw_ignore = raw.get('ignore', {})
    if isinstance(raw_ignore, dict):
        ignore.update(raw_ignore)

    if ignore.get('spaces') not in _VALID_SPACES:
        ignore['spaces'] = 'none'

    equivalence = _normalize_equivalence(raw.get('equivalence', {}))

    return {'ignore': ignore, 'equivalence': equivalence}


def _normalize_equivalence(raw_equiv: Any) -> Dict[str, List[str]]:
    if not isinstance(raw_equiv, dict):
        return {}
    out: Dict[str, List[str]] = {}
    for base, variants in raw_equiv.items():
        if isinstance(variants, str):
            stripped = variants.strip()
            if stripped.startswith('['):
                try:
                    parsed = json.loads(stripped)
                    if isinstance(parsed, list):
                        out[str(base)] = [str(v) for v in parsed]
                        continue
                except (json.JSONDecodeError, ValueError):
                    pass
            out[str(base)] = [variants]
        elif isinstance(variants, list):
            out[str(base)] = [str(v) for v in variants]
        else:
            out[str(base)] = [str(variants)]
    return out


# ─────────────────────────────────────────────────────────────────────────────
# DEFAULT_CONFIG
# ─────────────────────────────────────────────────────────────────────────────

DEFAULT_CONFIG = {
    'security_mode': 'obfuscated',
    'teacher_code': None,
    'encryption_key': 'default-key-change-me',
    'api_endpoint': None,
    'correction_granularity': 'exercise',

    'min_score': None,
    'clamp_max_score': True,

    'correction_locked_icon':   '❌',
    'correction_unlock_icon':   '🔐',
    'correction_unlocked_icon': '✅',

    'mcquiz': {
        'scoring_mode':        'proportional',
        'default_false':       0,
        'default_points':      1,
        'show_correction':     'on_demand',
        'save_answers':        True,
        'randomize_answers':   False,
        'randomize_questions': False,
        'synonyms':            []
    },

    'scquiz': {
        'scoring_mode':        'all_or_nothing',
        'default_false':       0,
        'default_points':      1,
        'show_correction':     'on_demand',
        'save_answers':        True,
        'randomize_answers':   False,
        'randomize_questions': False,
        'synonyms':            []
    },

    'fillblanks': {
        'scoring_mode':        'proportional',
        'default_false':       0,
        'default_points':      1,
        'show_correction':     'on_demand',
        'save_answers':        True,
        'randomize_answers':   False,
        'randomize_questions': False,
        'synonyms':            [],
        'compare_answers': {
            'ignore': {
                'spaces':     'none',
                'characters': '',
                'case':       False,
                'diacritics': False
            },
            'equivalence': {}
        }
    },

    'scdropdown': {
        'scoring_mode':        'all_or_nothing',
        'default_false':       0,
        'default_points':      1,
        'show_correction':     'on_demand',
        'save_answers':        True,
        'randomize_answers':   False,
        'randomize_questions': False,
        'synonyms':            []
    },

    'mcdropdown': {
        'scoring_mode':        'proportional',
        'default_false':       0,
        'default_points':      1,
        'show_correction':     'on_demand',
        'save_answers':        True,
        'randomize_answers':   False,
        'randomize_questions': False,
        'synonyms':            []
    },

    # ── orderquiz ─────────────────────────────────────────────────────────────
    # Pas de randomize_answers (les items sont des blocs, pas des choix).
    # randomize_questions = True par défaut : l'ordre initial est mélangé au
    # chargement, ce qui constitue le défi de l'exercice.
    # scoring_mode : 'proportional' (Kendall tau) ou 'all_or_nothing'.
    'orderquiz': {
        'scoring_mode':        'proportional',
        'default_points':      1,
        'show_correction':     'on_demand',
        'save_answers':        True,
        'randomize_answers':   False,   # n/a pour orderquiz — toujours False
        'randomize_questions': True,    # mélange les items au chargement
        'synonyms':            []
    },
}


# ─────────────────────────────────────────────────────────────────────────────
# Helpers
# ─────────────────────────────────────────────────────────────────────────────

def normalize_quiz_type(quiz_type_str: str, language: str = 'en') -> Optional[str]:
    quiz_types = build_quiz_types_dict(language)
    quiz_type_lower = quiz_type_str.lower()
    for canonical_type, synonyms in quiz_types.items():
        if quiz_type_lower in [s.lower() for s in synonyms]:
            return canonical_type
    return None


def normalize_keyword(keyword_str: str, language: str = 'en') -> Optional[str]:
    import re
    keywords = build_keywords_dict(language)
    keyword_base = re.sub(r'\d+$', '', keyword_str.lower())
    for canonical_keyword, synonyms in keywords.items():
        if keyword_base in [s.lower() for s in synonyms]:
            return canonical_keyword
    return None
