"""
Plugin principal MkDocs pour mkdocs-superquiz

Features in this version:
1) ✅ Fix repeated quiz titles:
   - If the same admonition title appears multiple times on a page,
     map title -> queue (deque) and consume quizzes in order.

2) ✅ Preamble markers are NOT "Q0":
   - Synthetic meta question 0 (is_meta=True / number==0) is rendered/extracted
     using dedicated PREAMBLE markers:
       <!-- MQS_{quiz_num}_QUIZ_PREAMBLE_START -->
       ...
       <!-- MQS_{quiz_num}_QUIZ_PREAMBLE_END -->

3) ✅ Config override levels:
   - Global (mkdocs.yml plugin config)   [lowest priority]
   - Page (front-matter `superquiz:` or `quiz:` block)   [overrides global]
   - Exercise (mini front-matter inside the quiz) [overrides page + global]

4) ✅ support "defaults" / "global" block in mkdocs.yml with rule:
   - NESTED > FLAT (at the same level)
   - i.e. defaults/global overrides root keys, including language.

5) ✅ on_post_page replaces on_page_context for reliable asset injection
   - Works with MkDocs Material and any theme
   - Depth-aware relative paths for nested pages

6) ✅ min_score semantics:
   - Default = 0 (no negative scores)
   - min_score: none / "none" / "null" → no minimum (negative scores allowed)
   - min_score: N → minimum N, but ignored if N >= points_max (aberrant case)
"""

import os
import re
import shutil
import json
import posixpath
from pathlib import Path
from collections import defaultdict, deque
from typing import Any, Dict

from mkdocs.config import config_options
from mkdocs.plugins import BasePlugin
from mkdocs.structure.files import Files
from mkdocs.structure.pages import Page
from mkdocs.config.defaults import MkDocsConfig

from .parser import QuizParser
from .renderer import QuizRenderer
from .database import QuizDatabase
from .security import prepare_teacher_code_hash
from .config import DEFAULT_CONFIG, DEFAULTS_ALIASES
from .locale import QuizI18n


# ─────────────────────────────────────────────────────────────────────────────
# Admonition body-line detector  (must stay in sync with parser.py)
# ─────────────────────────────────────────────────────────────────────────────

_HTML_BLOCK_TAG_RE = re.compile(
    r'^<(?:iframe|div|figure|figcaption|table|thead|tbody|tr|th|td|'
    r'blockquote|pre|video|audio|script|style|details|summary)\b',
    re.IGNORECASE
)


def _is_admonition_body_line(line: str) -> bool:
    return (
        line == '' or
        line.startswith('    ') or
        line.startswith('\t') or
        bool(_HTML_BLOCK_TAG_RE.match(line))
    )


# ─────────────────────────────────────────────────────────────────────────────
# Deep merge helpers
# ─────────────────────────────────────────────────────────────────────────────

def _deep_merge(base: Dict[str, Any], override: Dict[str, Any]) -> Dict[str, Any]:
    """
    Recursively merge `override` into `base` and return a new dict.
    - dict values are merged recursively
    - other values replace the base value
    """
    if not isinstance(base, dict):
        base = {}
    if not isinstance(override, dict):
        override = {}

    out: Dict[str, Any] = dict(base)
    for k, v in override.items():
        if isinstance(v, dict) and isinstance(out.get(k), dict):
            out[k] = _deep_merge(out[k], v)
        else:
            out[k] = v
    return out


def _pick_quiz_type_config(cfg: Dict[str, Any], quiz_type: str) -> Dict[str, Any]:
    """
    Return a dict that represents ONLY the config for `quiz_type`, merged with
    common/global keys present at top-level.
    """
    if not isinstance(cfg, dict):
        cfg = {}

    result: Dict[str, Any] = dict(cfg)

    qt_cfg = cfg.get(quiz_type, {})
    if not isinstance(qt_cfg, dict):
        qt_cfg = {}

    for qt in ('mcquiz', 'scquiz', 'fillblanks', 'scdropdown'):
        if qt not in result or not isinstance(result.get(qt), dict):
            result[qt] = {}

    result[quiz_type] = qt_cfg
    return result


def _normalize_exercise_config(ex_cfg: Dict[str, Any], quiz_type: str) -> Dict[str, Any]:
    """
    Accept both styles for exercise config:

    Flat:
        show_correction: on_demand
        scoring_mode: proportional
        default_false: 0
        compare_answers: { ... }   (fillblanks)

    Nested:
        mcquiz:
          show_correction: on_demand
    """
    if not isinstance(ex_cfg, dict):
        return {}

    if isinstance(ex_cfg.get(quiz_type), dict):
        return ex_cfg

    flat = dict(ex_cfg)
    out: Dict[str, Any] = {}

    global_keys = {
        'security_mode',
        'encryption_key',
        'api_endpoint',
        'correction_granularity',
        'correction_locked_icon',
        'correction_unlock_icon',
        'correction_unlocked_icon',
        'language',
        'min_score',
        'clamp_max_score',
    }
    for k in list(flat.keys()):
        if k in global_keys:
            out[k] = flat.pop(k)

    out[quiz_type] = flat
    return out


# ─────────────────────────────────────────────────────────────────────────────
# min_score normalization
# ─────────────────────────────────────────────────────────────────────────────

def _normalize_min_score(value: Any) -> Any:
    """
    Normalize min_score:
    - None (YAML `none` without quotes, or not set at quiz/page level)
                             → None  (no minimum, negative scores allowed)
    - 'none' / 'None' / 'null' / 'NULL' (YAML `none` with quotes)
                             → None  (no minimum, negative scores allowed)
    - any number             → that number (kept as-is; JS clamp handles aberrant values)

    Note: when not set at all in mkdocs.yml, config_scheme default=0 applies,
    so the global default is 0 (no negative scores).
    """
    if value is None:
        return None  # none YAML sans guillemets → pas de minimum
    if isinstance(value, str) and value.lower() in ('none', 'null'):
        return None  # "none"/"null" avec guillemets → pas de minimum
    return value     # number as-is



# ─────────────────────────────────────────────────────────────────────────────
# Plugin
# ─────────────────────────────────────────────────────────────────────────────

class QuizPlugin(BasePlugin):
    # NOTE: MkDocs requires config_scheme to be static at import time.
    config_scheme = (
        ('language', config_options.Type(str, default='en')),

        # ✅ optional defaults blocks (nested > flat)
        ('defaults', config_options.Type(dict, default={})),
        ('global',   config_options.Type(dict, default={})),

        ('security_mode', config_options.Type(str, default='obfuscated')),
        ('teacher_code', config_options.Type(str, default=None)),
        ('encryption_key', config_options.Type(str, default='default-key-change-me')),
        ('api_endpoint', config_options.Type(str, default=None)),
        ('correction_granularity', config_options.Type(str, default='all')),

        # ('min_score', config_options.Type((int, float, str), default=None)),
        ('min_score', config_options.Type((int, float, str), default=0)),
        ('clamp_max_score', config_options.Type(bool, default=True)),

        ('mcquiz', config_options.Type(dict, default={})),
        ('scquiz', config_options.Type(dict, default={})),
        ('fillblanks', config_options.Type(dict, default={})),
        ('scdropdown', config_options.Type(dict, default={})),
        ('correction_locked_icon', config_options.Type(str, default='❌')),
        ('correction_unlock_icon', config_options.Type(str, default='🔐')),
        ('correction_unlocked_icon', config_options.Type(str, default='✅')),
    )

    def __init__(self):
        super().__init__()
        self.db = None
        self.teacher_code_hash = None
        self.i18n = None
        self.language = 'en'

        plugin_dir = Path(__file__).parent
        self.assets_dir = plugin_dir / 'assets'
        self.templates_dir = plugin_dir / 'templates'

    # ─────────────────────────────────────────────────────────────────────────
    # defaults/global handling (nested > flat)
    # ─────────────────────────────────────────────────────────────────────────

    def _extract_defaults_block(self, cfg: Dict[str, Any]) -> Dict[str, Any]:
        out: Dict[str, Any] = {}
        for k in DEFAULTS_ALIASES:
            block = cfg.get(k)
            if isinstance(block, dict) and block:
                out = _deep_merge(out, block)
        return out

    def _apply_nested_over_flat(self, raw_cfg: Dict[str, Any]) -> Dict[str, Any]:
        flat = dict(raw_cfg)
        for k in DEFAULTS_ALIASES:
            if k in flat:
                flat.pop(k, None)
        defaults_block = self._extract_defaults_block(raw_cfg)
        merged = _deep_merge(flat, defaults_block)
        return merged

    # ─────────────────────────────────────────────────────────────────────────
    # on_config
    # ─────────────────────────────────────────────────────────────────────────

    def on_config(self, config: MkDocsConfig) -> MkDocsConfig:
        merged_cfg = self._apply_nested_over_flat(dict(self.config))
        self.config = merged_cfg

        # ✅ Normalize min_score at global level
        self.config['min_score'] = _normalize_min_score(self.config.get('min_score'))

        self.language = self.config.get('language', 'en')

        for quiz_type in ['mcquiz', 'scquiz', 'fillblanks', 'scdropdown']:
            default_type_config = DEFAULT_CONFIG.get(quiz_type, {})
            user_type_config = self.config.get(quiz_type, {})
            if not isinstance(user_type_config, dict):
                user_type_config = {}
            self.config[quiz_type] = {**default_type_config, **user_type_config}

        custom_synonyms = {qt: self.config.get(qt, {}) for qt in ['mcquiz', 'scquiz', 'fillblanks', 'scdropdown']}
        self.i18n = QuizI18n(self.language, custom_synonyms)

        print(f"🌍 Language: {self.language}")
        print(f"   Recognized quiz type synonyms: {', '.join(self.i18n.get_all_quiz_type_synonyms())}")

        self.teacher_code_hash = prepare_teacher_code_hash(
            self.config.get('teacher_code') or 'CHANGE_ME'
        )

        db_path = os.path.join(config['docs_dir'], 'quizzes.db')
        self.db = QuizDatabase(db_path)

        return config

    # ─────────────────────────────────────────────────────────────────────────
    # Page-level overrides
    # ─────────────────────────────────────────────────────────────────────────

    def _get_page_overrides(self, page: Page) -> Dict[str, Any]:
        if not hasattr(page, 'meta') or not page.meta:
            return {}
        q = page.meta.get('superquiz') or page.meta.get('quiz')  # both accepted
        return q if isinstance(q, dict) else {}

    def _effective_config_for_quiz(self, page: Page, quiz: Dict[str, Any]) -> Dict[str, Any]:
            global_cfg = dict(self.config)
            page_cfg_raw = self._get_page_overrides(page)

            # ✅ i18n: normalize page-level overrides (front-matter `superquiz:`)
            page_cfg = self.i18n.normalize_config_dict(page_cfg_raw) if page_cfg_raw else {}

            merged = _deep_merge(global_cfg, page_cfg)

            quiz_type = quiz.get('type', '')
            ex_cfg_raw = quiz.get('config') or {}
            # ex_cfg is already normalized by the parser — no need to re-normalize
            ex_cfg = _normalize_exercise_config(ex_cfg_raw, quiz_type)
            merged = _deep_merge(merged, ex_cfg)
            merged = _pick_quiz_type_config(merged, quiz_type)

            # ✅ Normalize min_score at effective config level
            merged['min_score'] = _normalize_min_score(merged.get('min_score'))

            return merged


    # ─────────────────────────────────────────────────────────────────────────
    # on_page_markdown
    # ─────────────────────────────────────────────────────────────────────────

    def on_page_markdown(self, markdown: str, page: Page, config: MkDocsConfig, files: Files) -> str:
        parser = QuizParser(self.i18n, self.language)
        quizzes = parser.parse_markdown(markdown)

        if not quizzes:
            return markdown

        page_path = page.file.src_path
        page_title = page.title or page_path

        corrections_locked = False
        if hasattr(page, 'meta') and page.meta:
            show_correction = page.meta.get('quiz', {}).get('show_correction', 'on_demand')
            corrections_locked = (show_correction == 'on_demand')

        page_uuid = self.db.get_or_create_page(page_path, page_title, corrections_locked)
        self.db._clean_page_quizzes(page_uuid)

        for quiz in quizzes:
            quiz_db_id = self.db.add_quiz(page_uuid, quiz['number'], quiz['type'], quiz['title'])

            for question in quiz.get('questions', []):
                if question.get('is_meta') or question.get('number') == 0:
                    continue

                qid = self.db.add_question(quiz_db_id, question['number'], question.get('text', ''))

                if quiz['type'] in ['mcquiz', 'scquiz']:
                    for idx, answer in enumerate(question.get('answers', [])):
                        self.db.add_answer(qid, answer['text'], answer.get('is_correct', False), idx)

                elif quiz['type'] == 'fillblanks':
                    for blank_idx, blank in enumerate(question.get('blanks', [])):
                        for answer in blank.get('answers', []):
                            self.db.add_answer(qid, answer, True, blank_idx)

                elif quiz['type'] == 'scdropdown':
                    scdropdowns = question.get('scdropdowns') or []
                    if not scdropdowns:
                        dd = question.get('scdropdown')
                        scdropdowns = [dd] if dd else []
                    if scdropdowns:
                        dd = scdropdowns[0]
                        ci = dd.get('correct_index', 0)
                        for idx, option in enumerate(dd.get('options', [])):
                            self.db.add_answer(qid, option, idx == ci, idx)

        modified_markdown = self._rewrite_admonitions(markdown, quizzes)

        if not hasattr(page, '_mkdocs_quiz_data'):
            page._mkdocs_quiz_data = {}
        page._mkdocs_quiz_data['quizzes'] = quizzes
        page._mkdocs_quiz_data['page_uuid'] = page_uuid
        page._mkdocs_quiz_data['page_path'] = page_path

        return modified_markdown

    # ─────────────────────────────────────────────────────────────────────────
    # Admonition rewriter
    # ─────────────────────────────────────────────────────────────────────────

    def _rewrite_admonitions(self, markdown: str, quizzes: list) -> str:
        all_synonyms = self.i18n.get_all_quiz_type_synonyms()
        synonym_set = {s.lower() for s in all_synonyms}
        header_re = re.compile(r'^!!!\s+(\S+)\s+"([^"]+)"\s*$', re.IGNORECASE)

        quizzes_by_title = defaultdict(deque)
        for q in quizzes:
            quizzes_by_title[q['title']].append(q)

        src_lines = markdown.split('\n')
        out_lines = []
        i = 0

        while i < len(src_lines):
            line = src_lines[i]
            hm = header_re.match(line)

            if hm and hm.group(1).lower() in synonym_set:
                title = hm.group(2)

                i += 1
                while i < len(src_lines) and _is_admonition_body_line(src_lines[i]):
                    i += 1

                out_lines.append(line)

                quiz = quizzes_by_title[title].popleft() if quizzes_by_title[title] else None
                if quiz:
                    out_lines.append(self._build_admonition_body(quiz))
            else:
                out_lines.append(line)
                i += 1

        return '\n'.join(out_lines)

    def _build_admonition_body(self, quiz: dict) -> str:
        quiz_num = quiz['number']
        quiz_type = quiz.get('type', '')
        indent = '    '
        blocks = []

        def strip_source_indent(raw: str) -> str:
            lines = raw.split('\n')
            indents = [
                len(ln) - len(ln.lstrip())
                for ln in lines
                if ln.strip() and ln[0] in (' ', '\t')
            ]
            min_indent = min(indents) if indents else 0
            result = []
            for ln in lines:
                if not ln.strip():
                    result.append('')
                elif ln[0] in (' ', '\t'):
                    result.append(ln[min_indent:])
                else:
                    result.append(ln)
            return '\n'.join(result)

        def ensure_blank(text: str) -> str:
            block_tags = (r'(?:div|iframe|figure|figcaption|table|thead|tbody|'
                          r'tr|th|td|blockquote|pre|p|h[1-6]|video|audio|details|summary)')
            self_closing = r'(?:img|br|hr|input|source)'

            text = re.sub(
                rf'(</{block_tags}\s*>)([ \t]*)$',
                r'\1\2\n', text, flags=re.MULTILINE | re.IGNORECASE
            )
            text = re.sub(
                rf'(<{self_closing}[^>]*/?>)([ \t]*)$',
                r'\1\2\n', text, flags=re.MULTILINE | re.IGNORECASE
            )

            lines = text.split('\n')
            result = []
            for line in lines:
                stripped = line.strip()
                is_list = (
                    (stripped and stripped[0] in ('*', '-', '+')
                     and len(stripped) > 1 and stripped[1] == ' ') or
                    bool(stripped and re.match(r'^\d+\.\s', stripped))
                )
                if is_list and result:
                    prev = result[-1].strip()
                    prev_is_list = (
                        (prev and prev[0] in ('*', '-', '+')
                         and len(prev) > 1 and prev[1] == ' ') or
                        bool(prev and re.match(r'^\d+\.\s', prev))
                    )
                    if prev and not prev_is_list:
                        result.append('')
                result.append(line)
            return '\n'.join(result)

        def build_rich_block(raw: str, start_marker: str, end_marker: str) -> str:
            cleaned = strip_source_indent(raw)
            cleaned = ensure_blank(cleaned)
            content_lines = [
                '' if ln.strip() == '' else f'{indent}{ln}'
                for ln in cleaned.split('\n')
            ]
            parts = ['', f'{indent}{start_marker}', ''] + content_lines + ['', f'{indent}{end_marker}', '']
            return '\n'.join(parts)

        for q in quiz.get('questions', []):
            q_num = q.get('number', 0)
            is_meta = bool(q.get('is_meta')) or (q_num == 0)

            if (quiz_type in ('mcquiz', 'scquiz')) and (not is_meta):
                q_text = (q.get('text') or '').strip()
                if q_text:
                    blocks.append('\n'.join([
                        '',
                        f'{indent}<!-- MQS_{quiz_num}_Q{q_num}_TEXT_START -->',
                        '',
                        f'{indent}{q_text}',
                        '',
                        f'{indent}<!-- MQS_{quiz_num}_Q{q_num}_TEXT_END -->',
                        '',
                    ]))

            rich = q.get('rich_content', '') or ''
            rlines = rich.split('\n')
            while rlines and not rlines[0].strip():
                rlines.pop(0)
            while rlines and not rlines[-1].strip():
                rlines.pop()

            if not rlines:
                continue

            if is_meta:
                blocks.append(build_rich_block(
                    '\n'.join(rlines),
                    f'<!-- MQS_{quiz_num}_QUIZ_PREAMBLE_START -->',
                    f'<!-- MQS_{quiz_num}_QUIZ_PREAMBLE_END -->'
                ))
            else:
                blocks.append(build_rich_block(
                    '\n'.join(rlines),
                    f'<!-- MQS_{quiz_num}_Q{q_num}_START -->',
                    f'<!-- MQS_{quiz_num}_Q{q_num}_END -->'
                ))

        if not blocks:
            return f'{indent}<!-- QUIZ_EMPTY_{quiz_num} -->\n'

        return '\n'.join(blocks) + '\n'

    # ─────────────────────────────────────────────────────────────────────────
    # on_page_content
    # ─────────────────────────────────────────────────────────────────────────

    def on_page_content(self, html: str, page: Page, config: MkDocsConfig, files: Files) -> str:
        if not hasattr(page, '_mkdocs_quiz_data'):
            return html

        quiz_data = page._mkdocs_quiz_data
        quizzes = quiz_data.get('quizzes', [])
        page_uuid = quiz_data.get('page_uuid')
        page_path = quiz_data.get('page_path', '')

        if not quizzes:
            return html

        modified_html = html

        for quiz in quizzes:
            quiz_num = quiz['number']

            for q in quiz.get('questions', []):
                q_num = q.get('number', 0)
                is_meta = bool(q.get('is_meta')) or (q_num == 0)

                if (quiz.get('type') in ('mcquiz', 'scquiz')) and (not is_meta):
                    text_html, modified_html = self._extract_marked(
                        modified_html,
                        f'<!-- MQS_{quiz_num}_Q{q_num}_TEXT_START -->',
                        f'<!-- MQS_{quiz_num}_Q{q_num}_TEXT_END -->'
                    )
                    if text_html is not None:
                        p_count = len(re.findall(r'<p>', text_html, re.IGNORECASE))
                        if p_count == 1:
                            text_html = re.sub(
                                r'^\s*<p>(.*?)</p>\s*$',
                                r'\1',
                                text_html,
                                flags=re.DOTALL | re.IGNORECASE
                            )
                        q['text_html'] = self._fix_image_paths(text_html, page_path)

                if is_meta:
                    rich_html, modified_html = self._extract_marked(
                        modified_html,
                        f'<!-- MQS_{quiz_num}_QUIZ_PREAMBLE_START -->',
                        f'<!-- MQS_{quiz_num}_QUIZ_PREAMBLE_END -->'
                    )
                else:
                    rich_html, modified_html = self._extract_marked(
                        modified_html,
                        f'<!-- MQS_{quiz_num}_Q{q_num}_START -->',
                        f'<!-- MQS_{quiz_num}_Q{q_num}_END -->'
                    )

                if rich_html is not None:
                    q['rich_content'] = self._fix_image_paths(rich_html, page_path)

            effective_cfg = self._effective_config_for_quiz(page, quiz)
            renderer = QuizRenderer(effective_cfg, page_uuid, self.teacher_code_hash, self.i18n, self.language)

            html_class = quiz.get('original_synonym', quiz['type']) or quiz['type']
            quiz_title = re.escape(quiz['title'])
            quiz_html = renderer.render_quiz(quiz)
            modified_html = self._replace_admonition(modified_html, html_class, quiz_title, quiz_html)

        return modified_html

    # ─────────────────────────────────────────────────────────────────────────
    # Image path fix
    # ─────────────────────────────────────────────────────────────────────────

    def _fix_image_paths(self, html_fragment: str, page_src_path: str) -> str:
        if not page_src_path:
            return html_fragment

        page_dir = posixpath.dirname(page_src_path)
        page_stem = posixpath.splitext(posixpath.basename(page_src_path))[0]
        page_out_dir = posixpath.join(page_dir, page_stem) if page_dir else page_stem

        def rewrite(m):
            attr, quote, path = m.group(1), m.group(2), m.group(3)
            if path.startswith(('http://', 'https://', '//', '/', 'data:', '#', 'mailto:')):
                return m.group(0)
            if path.startswith('./'):
                docs_rel = posixpath.join(page_dir, path[2:]) if page_dir else path[2:]
            elif path.startswith('../'):
                parent = posixpath.dirname(page_dir) if page_dir else ''
                docs_rel = posixpath.join(parent, path[3:]) if parent else path[3:]
            else:
                docs_rel = posixpath.join(page_dir, path) if page_dir else path
            site_rel = posixpath.relpath(docs_rel, page_out_dir)
            return f'{attr}={quote}{site_rel}{quote}'

        return re.sub(r'(src|href)=(["\'])([^"\']+)\2', rewrite, html_fragment, flags=re.IGNORECASE)

    # ─────────────────────────────────────────────────────────────────────────
    # HTML helpers
    # ─────────────────────────────────────────────────────────────────────────

    def _extract_marked(self, html: str, start_tag: str, end_tag: str):
        start_re = re.compile(r'(?:<p>\s*)?' + re.escape(start_tag) + r'\s*(?:</p>)?', re.IGNORECASE)
        end_re = re.compile(r'(?:<p>\s*)?' + re.escape(end_tag) + r'\s*(?:</p>)?', re.IGNORECASE)

        sm = start_re.search(html)
        if not sm:
            return None, html
        em = end_re.search(html, sm.end())
        if not em:
            return None, html

        content = html[sm.end():em.start()].strip()
        html_without = html[:sm.start()] + html[em.end():]
        return (content or ''), html_without

    def _replace_admonition(self, html: str, html_class: str, escaped_title: str, replacement: str) -> str:
        open_pattern = re.compile(rf'<div\s+class="admonition\s+{html_class}"[^>]*>', re.IGNORECASE)
        title_pattern = re.compile(rf'<p\s+class="admonition-title"[^>]*>\s*{escaped_title}\s*</p>', re.IGNORECASE)
        div_token = re.compile(r'<(/?)div\b[^>]*>', re.IGNORECASE)

        search_start = 0
        while True:
            open_m = open_pattern.search(html, search_start)
            if not open_m:
                return html

            after_open = html[open_m.end():open_m.end() + 300]
            if not title_pattern.search(after_open):
                search_start = open_m.end()
                continue

            adm_start = open_m.start()
            depth = 1
            pos = open_m.end()

            while pos < len(html) and depth > 0:
                m = div_token.search(html, pos)
                if not m:
                    break
                depth += -1 if m.group(1) == '/' else 1
                pos = m.end()

            if depth != 0:
                return html

            return html[:adm_start] + replacement + html[pos:]

    # ─────────────────────────────────────────────────────────────────────────
    # on_post_build
    # ─────────────────────────────────────────────────────────────────────────

    def on_post_build(self, config: MkDocsConfig) -> None:
        site_dir = config['site_dir']
        assets_output_dir = os.path.join(site_dir, 'assets', 'quiz')
        os.makedirs(assets_output_dir, exist_ok=True)

        for css_file in ['quiz-light.css', 'quiz-dark.css', 'unlock-page.css']:
            src = self.assets_dir / css_file
            if src.exists():
                shutil.copy(str(src), os.path.join(assets_output_dir, css_file))
                print(f"✅ Copied {css_file}")
            else:
                print(f"❌ WARNING: {css_file} not found at {src}")

        qrcode_src = self.assets_dir / 'qrcode.min.js'
        if qrcode_src.exists():
            shutil.copy(str(qrcode_src), os.path.join(assets_output_dir, 'qrcode.min.js'))
            print(f"✅ Copied qrcode.min.js")
        else:
            print(f"❌ WARNING: qrcode.min.js not found")

        quiz_js_src = self.assets_dir / 'quiz.js'
        if not quiz_js_src.exists():
            print(f"❌ ERROR: quiz.js not found at {quiz_js_src}")
            return

        with open(quiz_js_src, 'r', encoding='utf-8') as f:
            quiz_js_content = f.read()

        # ✅ min_score already normalized in on_config: None = no minimum, 0 = default
        min_score_value = self.config.get('min_score')  # None or number

        config_injection = f'''// Quiz configuration (injected by plugin)
window.QUIZ_CONFIG = window.QUIZ_CONFIG || {{}};
window.QUIZ_CONFIG.teacher_code_hash = '{self.teacher_code_hash}';
window.QUIZ_CONFIG.language = '{self.language}';
window.QUIZ_CONFIG.i18n = {json.dumps(self.i18n.translations)};
window.QUIZ_CONFIG.encryption_key = '{self.config.get('encryption_key', 'default-key-change-me')}';
window.QUIZ_CONFIG.min_score = {json.dumps(min_score_value)};
window.QUIZ_CONFIG.clamp_max_score = {json.dumps(self.config.get('clamp_max_score', True))};
window.QUIZ_CONFIG.mcquiz = {json.dumps(self.config.get('mcquiz', {}))};
window.QUIZ_CONFIG.scquiz = {json.dumps(self.config.get('scquiz', {}))};
window.QUIZ_CONFIG.fillblanks = {json.dumps(self.config.get('fillblanks', {}))};
window.QUIZ_CONFIG.scdropdown = {json.dumps(self.config.get('scdropdown', {}))};
console.log('✅ QUIZ_CONFIG loaded:', window.QUIZ_CONFIG);

'''
        with open(os.path.join(assets_output_dir, 'quiz.js'), 'w', encoding='utf-8') as f:
            f.write(config_injection + quiz_js_content)
        print(f"✅ Created quiz.js with inline config")

        quiz_unlock_src = self.assets_dir / 'quiz-unlock.js'
        if quiz_unlock_src.exists():
            shutil.copy(str(quiz_unlock_src), os.path.join(assets_output_dir, 'quiz-unlock.js'))
            print(f"✅ Copied quiz-unlock.js")
        else:
            print(f"❌ WARNING: quiz-unlock.js not found")

        unlock_page_src = self.templates_dir / 'quiz-unlock.html'
        if unlock_page_src.exists():
            with open(unlock_page_src, 'r', encoding='utf-8') as f:
                unlock_html = f.read()
            unlock_html = unlock_html.replace('{{TEACHER_CODE_HASH}}', self.teacher_code_hash)
            unlock_html = unlock_html.replace('{{I18N_TRANSLATIONS}}', json.dumps(self.i18n.translations))
            with open(os.path.join(site_dir, 'quiz-unlock.html'), 'w', encoding='utf-8') as f:
                f.write(unlock_html)
            print(f"✅ Created quiz-unlock.html")

        if self.db:
            self.db.close()

    # ─────────────────────────────────────────────────────────────────────────
    # on_post_page — inject CSS + JS directly into HTML output
    # ─────────────────────────────────────────────────────────────────────────

    def on_post_page(self, output: str, page: Page, config: MkDocsConfig) -> str:
        if not hasattr(page, '_mkdocs_quiz_data'):
            return output

        page_url = (page.url or '').strip('/')
        depth = page_url.count('/') + 1 if page_url else 0
        prefix = '../' * depth

        css_files = [
            'assets/quiz/quiz-light.css',
            'assets/quiz/quiz-dark.css',
        ]
        js_files = [
            'assets/quiz/qrcode.min.js',
            'assets/quiz/quiz.js',
            'assets/quiz/quiz-unlock.js',
        ]

        css_tags = '\n'.join(
            f'<link rel="stylesheet" href="{prefix}{f}">'
            for f in css_files
        )
        js_tags = '\n'.join(
            f'<script src="{prefix}{f}"></script>'
            for f in js_files
        )

        if '</head>' in output and '</body>' in output:
            output = output.replace('</head>', css_tags + '\n</head>', 1)
            output = output.replace('</body>', js_tags + '\n</body>', 1)
        elif '</body>' in output:
            output = output.replace('</body>', css_tags + '\n' + js_tags + '\n</body>', 1)

        return output



