"""Compute the deliverables list and progress for a course plan.

A deliverable is a single concrete artefact the AI should eventually
produce. Each week's planned skills (from ``items_json``) maps to a
deliverable, and each generated text material can in turn produce
multimedia children:

* listening_script  -> 1 audio narration + 1 illustration + 1 video
* reading_text      -> 1 hero illustration
* speaking_prompt   -> 1 model-answer audio
* vocabulary_list   -> 1 image per term (capped at 8 per list)

The frontend uses this to draw the Deliverables Preview before
generation and the progress strip on the detail page after.
"""

from __future__ import annotations

import json


SKILL_TO_MATERIAL_TYPE = {
    'reading': 'reading_text',
    'writing': 'writing_prompt',
    'listening': 'listening_script',
    'speaking': 'speaking_prompt',
    'grammar': 'grammar_lesson',
    'vocabulary': 'vocabulary_list',
}

MATERIAL_MEDIA_PLAN = {
    'listening_script': [
        {'kind': 'audio', 'mandatory': True, 'note': 'Narrated MP3 of the script'},
        {'kind': 'image', 'mandatory': False, 'note': 'Illustration for the listening lesson'},
        {'kind': 'video', 'mandatory': False, 'note': 'Slide-style MP4 with audio'},
    ],
    'reading_text': [
        {'kind': 'image', 'mandatory': False, 'note': 'Hero illustration for the passage'},
    ],
    'speaking_prompt': [
        {'kind': 'audio', 'mandatory': False, 'note': 'Model-answer narration'},
    ],
    'vocabulary_list': [
        {'kind': 'image', 'mandatory': False, 'note': 'Flashcard image (one per term, capped)'},
    ],
}

VOCAB_IMAGE_CAP = 8


def _safe_loads(raw, default):
    if not raw:
        return default
    try:
        return json.loads(raw)
    except (TypeError, ValueError):
        return default


def compute_deliverables(plan):
    """Return ``{summary, weeks: [...]}`` describing what the plan owes.

    Status logic per deliverable:

    * ``planned``  — no material row yet for that {week, material_type}.
    * ``generated``— material row exists but no media, or media not
      applicable for the type.
    * ``ready``    — material has at least one ``ready`` media child for
      every mandatory-or-cap-of-1 modality and one ``ready`` media for
      vocab cap (we don't gate on every term).

    The frontend only needs the counts, but per-week breakdown is also
    returned so it can render a checklist.
    """
    weeks_payload = []

    materials_by_week = {}
    for m in plan.material_ids:
        materials_by_week.setdefault(m.week_id.id if m.week_id else 0, []).append(m)

    counts = {'planned': 0, 'generated': 0, 'ready': 0}
    media_counts = {'audio': 0, 'image': 0, 'video': 0}
    media_ready_counts = {'audio': 0, 'image': 0, 'video': 0}

    for week in plan.week_ids.sorted('week_number'):
        items = _safe_loads(week.items_json, [])
        ws_materials = materials_by_week.get(week.id, [])
        materials_by_type = {m.material_type: m for m in ws_materials}

        deliverables = []
        for item in items:
            skill = (item.get('skill') or '').lower()
            mtype = SKILL_TO_MATERIAL_TYPE.get(skill)
            if not mtype:
                continue
            material = materials_by_type.get(mtype)
            entry = {
                'skill': skill,
                'material_type': mtype,
                'material_id': material.id if material else None,
                'title': material.title if material else '',
                'media': [],
                'status': 'planned',
            }
            if material:
                entry['status'] = 'generated'
                planned_media = MATERIAL_MEDIA_PLAN.get(mtype, [])
                ready_for_each_kind = {p['kind']: False for p in planned_media}
                for child in material.media_ids:
                    media_counts[child.kind] = media_counts.get(child.kind, 0) + 1
                    if child.status == 'ready':
                        media_ready_counts[child.kind] = (
                            media_ready_counts.get(child.kind, 0) + 1
                        )
                        if child.kind in ready_for_each_kind:
                            ready_for_each_kind[child.kind] = True
                    entry['media'].append({
                        'id': child.id, 'kind': child.kind,
                        'status': child.status, 'provider': child.provider,
                    })
                if planned_media and all(
                    ready_for_each_kind.get(p['kind'], False)
                    for p in planned_media if p.get('mandatory')
                ):
                    entry['status'] = 'ready'
                elif not planned_media:
                    entry['status'] = 'ready'
            counts[entry['status']] += 1
            deliverables.append(entry)
        weeks_payload.append({
            'week_number': week.week_number,
            'date_label': week.date_label or '',
            'unit': week.unit or '',
            'focus': week.focus or '',
            'items_total': len(items),
            'deliverables': deliverables,
        })

    total = sum(counts.values())
    percent = round((counts['ready'] / total) * 100, 1) if total else 0.0
    return {
        'summary': {
            'total': total,
            'planned': counts['planned'],
            'generated': counts['generated'],
            'ready': counts['ready'],
            'percent_ready': percent,
            'media': {
                'audio': media_counts['audio'],
                'image': media_counts['image'],
                'video': media_counts['video'],
                'audio_ready': media_ready_counts['audio'],
                'image_ready': media_ready_counts['image'],
                'video_ready': media_ready_counts['video'],
            },
        },
        'weeks': weeks_payload,
    }
