"""
Production game views for the play screen.

Provides the main gameplay interface with 3-column layout:
- Left: Character stats + action buttons
- Middle: Narrative + location context
- Right: Accordions for history, quests, NPCs, map
"""

from flask import Blueprint, render_template, request
import structlog

from ..utils.api_client import get_api_client, APIError, APINotFoundError
from ..utils.auth import require_auth_web as require_auth, get_current_user

logger = structlog.get_logger(__name__)

game_bp = Blueprint('game', __name__, url_prefix='/play')


# ===== Action Definitions =====
# Actions organized by tier - context filtering happens in template
# These are static definitions, available actions come from API session state

DEFAULT_ACTIONS = {
    'free': [
        {'prompt_id': 'ask_locals', 'display_text': 'Ask Locals for Information', 'icon': 'chat', 'context': ['town', 'tavern']},
        {'prompt_id': 'explore_area', 'display_text': 'Explore the Area', 'icon': 'compass', 'context': ['wilderness', 'dungeon']},
        {'prompt_id': 'search_supplies', 'display_text': 'Search for Supplies', 'icon': 'search', 'context': ['any'], 'cooldown': 2},
        {'prompt_id': 'rest_recover', 'display_text': 'Rest and Recover', 'icon': 'bed', 'context': ['town', 'tavern', 'safe_area'], 'cooldown': 3}
    ],
    'premium': [
        {'prompt_id': 'investigate_suspicious', 'display_text': 'Investigate Suspicious Activity', 'icon': 'magnifying_glass', 'context': ['any']},
        {'prompt_id': 'follow_lead', 'display_text': 'Follow a Lead', 'icon': 'footprints', 'context': ['any']},
        {'prompt_id': 'make_camp', 'display_text': 'Make Camp', 'icon': 'campfire', 'context': ['wilderness'], 'cooldown': 5}
    ],
    'elite': [
        {'prompt_id': 'consult_texts', 'display_text': 'Consult Ancient Texts', 'icon': 'book', 'context': ['library', 'town'], 'cooldown': 3},
        {'prompt_id': 'commune_nature', 'display_text': 'Commune with Nature', 'icon': 'leaf', 'context': ['wilderness'], 'cooldown': 4},
        {'prompt_id': 'seek_audience', 'display_text': 'Seek Audience with Authorities', 'icon': 'crown', 'context': ['town'], 'cooldown': 5}
    ]
}


def _get_user_tier(client) -> str:
    """Get user's subscription tier from API or session."""
    try:
        # Try to get user info which includes tier
        user_response = client.get('/api/v1/auth/me')
        user_data = user_response.get('result', {})
        return user_data.get('tier', 'free')
    except (APIError, APINotFoundError):
        # Default to free tier if we can't determine
        return 'free'


def _build_location_from_game_state(game_state: dict) -> dict:
    """Build location dict from game_state data."""
    return {
        'location_id': game_state.get('current_location_id') or game_state.get('current_location'),
        'name': game_state.get('current_location_name', game_state.get('current_location', 'Unknown')),
        'location_type': game_state.get('location_type', 'unknown'),
        'region': game_state.get('region', ''),
        'description': game_state.get('location_description', ''),
        'ambient_description': game_state.get('ambient_description', '')
    }


def _build_character_from_api(char_data: dict) -> dict:
    """
    Build character dict from API character response.

    Always returns a dict with all required fields, using sensible defaults
    if the API data is incomplete or empty.
    """
    if not char_data:
        char_data = {}

    # Extract stats from base_stats or stats, with defaults
    stats = char_data.get('base_stats', char_data.get('stats', {}))
    if not stats:
        stats = {
            'strength': 10,
            'dexterity': 10,
            'constitution': 10,
            'intelligence': 10,
            'wisdom': 10,
            'charisma': 10
        }

    # Calculate HP/MP - these may come from different places
    # For now use defaults based on level/constitution
    level = char_data.get('level', 1)
    constitution = stats.get('constitution', 10)
    intelligence = stats.get('intelligence', 10)

    # Simple HP/MP calculation (can be refined based on game rules)
    max_hp = max(1, 50 + (level * 10) + ((constitution - 10) * level))
    max_mp = max(1, 20 + (level * 5) + ((intelligence - 10) * level // 2))

    # Get class name from various possible locations
    class_name = 'Unknown'
    if char_data.get('player_class'):
        class_name = char_data['player_class'].get('name', 'Unknown')
    elif char_data.get('class_name'):
        class_name = char_data['class_name']
    elif char_data.get('class'):
        class_name = char_data['class'].replace('_', ' ').title()

    return {
        'character_id': char_data.get('character_id', ''),
        'name': char_data.get('name', 'Unknown Hero'),
        'class_name': class_name,
        'level': level,
        'current_hp': char_data.get('current_hp', max_hp),
        'max_hp': char_data.get('max_hp', max_hp),
        'current_mp': char_data.get('current_mp', max_mp),
        'max_mp': char_data.get('max_mp', max_mp),
        'stats': stats,
        'equipped': char_data.get('equipped', {}),
        'inventory': char_data.get('inventory', []),
        'gold': char_data.get('gold', 0),
        'experience': char_data.get('experience', 0)
    }


# ===== Main Routes =====

@game_bp.route('/session/<session_id>')
@require_auth
def play_session(session_id: str):
    """
    Production play screen for a game session.

    Displays 3-column layout with character panel, narrative area,
    and sidebar accordions for history/quests/NPCs/map.
    """
    client = get_api_client()

    try:
        # Get session state (includes game_state with location info)
        session_response = client.get(f'/api/v1/sessions/{session_id}')
        session_data = session_response.get('result', {})

        # Extract game state and build location info
        game_state = session_data.get('game_state', {})
        location = _build_location_from_game_state(game_state)

        # Get character details - always build a valid character dict
        character_id = session_data.get('character_id')
        char_data = {}
        if character_id:
            try:
                char_response = client.get(f'/api/v1/characters/{character_id}')
                char_data = char_response.get('result', {})
            except (APINotFoundError, APIError) as e:
                logger.warning("failed_to_load_character", character_id=character_id, error=str(e))

        # Always build character with defaults for any missing fields
        character = _build_character_from_api(char_data)

        # Get session history (last DM response for display)
        history = []
        dm_response = "Your adventure awaits..."
        try:
            history_response = client.get(f'/api/v1/sessions/{session_id}/history?limit=10')
            history_data = history_response.get('result', {})
            history = history_data.get('history', [])
            # Get the most recent DM response for the main narrative panel
            if history:
                dm_response = history[0].get('dm_response', dm_response)
        except (APINotFoundError, APIError) as e:
            logger.warning("failed_to_load_history", session_id=session_id, error=str(e))

        # Get NPCs at current location
        npcs = []
        current_location_id = location.get('location_id')
        if current_location_id:
            try:
                npcs_response = client.get(f'/api/v1/npcs/at-location/{current_location_id}')
                npcs = npcs_response.get('result', {}).get('npcs', [])
            except (APINotFoundError, APIError) as e:
                logger.debug("no_npcs_at_location", location_id=current_location_id, error=str(e))

        # Get available travel destinations (discovered locations)
        discovered_locations = []
        try:
            travel_response = client.get(f'/api/v1/travel/available?session_id={session_id}')
            travel_result = travel_response.get('result', {})
            discovered_locations = travel_result.get('available_locations', [])
            # Mark current location
            for loc in discovered_locations:
                loc['is_current'] = loc.get('location_id') == current_location_id
        except (APINotFoundError, APIError) as e:
            logger.debug("failed_to_load_travel_destinations", session_id=session_id, error=str(e))

        # Get quests (from character's active_quests or session)
        quests = game_state.get('active_quests', [])
        # If quests are just IDs, we could expand them, but for now use what we have

        # Get user tier
        user_tier = _get_user_tier(client)

        # Build session object for template
        session = {
            'session_id': session_id,
            'turn_number': session_data.get('turn_number', 0),
            'status': session_data.get('status', 'active')
        }

        return render_template(
            'game/play.html',
            session_id=session_id,
            session=session,
            character=character,
            location=location,
            dm_response=dm_response,
            history=history,
            quests=quests,
            npcs=npcs,
            discovered_locations=discovered_locations,
            actions=DEFAULT_ACTIONS,
            user_tier=user_tier
        )

    except APINotFoundError:
        logger.warning("session_not_found", session_id=session_id)
        return render_template('errors/404.html', message=f"Session {session_id} not found"), 404
    except APIError as e:
        logger.error("failed_to_load_play_session", session_id=session_id, error=str(e))
        return render_template('errors/500.html', message=str(e)), 500


# ===== Partial Refresh Routes =====

@game_bp.route('/session/<session_id>/character-panel')
@require_auth
def character_panel(session_id: str):
    """Refresh character stats and actions panel."""
    client = get_api_client()

    try:
        # Get session to find character and location
        session_response = client.get(f'/api/v1/sessions/{session_id}')
        session_data = session_response.get('result', {})
        game_state = session_data.get('game_state', {})
        location = _build_location_from_game_state(game_state)

        # Get character - always build valid character dict
        char_data = {}
        character_id = session_data.get('character_id')
        if character_id:
            try:
                char_response = client.get(f'/api/v1/characters/{character_id}')
                char_data = char_response.get('result', {})
            except (APINotFoundError, APIError):
                pass

        character = _build_character_from_api(char_data)
        user_tier = _get_user_tier(client)

        return render_template(
            'game/partials/character_panel.html',
            session_id=session_id,
            character=character,
            location=location,
            actions=DEFAULT_ACTIONS,
            user_tier=user_tier
        )
    except APIError as e:
        logger.error("failed_to_refresh_character_panel", session_id=session_id, error=str(e))
        return f'<div class="error">Failed to load character panel: {e}</div>', 500


@game_bp.route('/session/<session_id>/narrative')
@require_auth
def narrative_panel(session_id: str):
    """Refresh narrative content panel."""
    client = get_api_client()

    try:
        # Get session state
        session_response = client.get(f'/api/v1/sessions/{session_id}')
        session_data = session_response.get('result', {})
        game_state = session_data.get('game_state', {})
        location = _build_location_from_game_state(game_state)

        # Get latest DM response from history
        dm_response = "Your adventure awaits..."
        try:
            history_response = client.get(f'/api/v1/sessions/{session_id}/history?limit=1')
            history_data = history_response.get('result', {})
            history = history_data.get('history', [])
            if history:
                dm_response = history[0].get('dm_response', dm_response)
        except (APINotFoundError, APIError):
            pass

        session = {
            'session_id': session_id,
            'turn_number': session_data.get('turn_number', 0),
            'status': session_data.get('status', 'active')
        }

        return render_template(
            'game/partials/narrative_panel.html',
            session_id=session_id,
            session=session,
            location=location,
            dm_response=dm_response
        )
    except APIError as e:
        logger.error("failed_to_refresh_narrative", session_id=session_id, error=str(e))
        return f'<div class="error">Failed to load narrative: {e}</div>', 500


@game_bp.route('/session/<session_id>/history')
@require_auth
def history_accordion(session_id: str):
    """Refresh history accordion content."""
    client = get_api_client()

    try:
        history_response = client.get(f'/api/v1/sessions/{session_id}/history?limit=20')
        history_data = history_response.get('result', {})
        history = history_data.get('history', [])

        return render_template(
            'game/partials/sidebar_history.html',
            session_id=session_id,
            history=history
        )
    except APIError as e:
        logger.error("failed_to_refresh_history", session_id=session_id, error=str(e))
        return f'<div class="error">Failed to load history: {e}</div>', 500


@game_bp.route('/session/<session_id>/quests')
@require_auth
def quests_accordion(session_id: str):
    """Refresh quests accordion content."""
    client = get_api_client()

    try:
        # Get session to access game_state.active_quests
        session_response = client.get(f'/api/v1/sessions/{session_id}')
        session_data = session_response.get('result', {})
        game_state = session_data.get('game_state', {})
        quests = game_state.get('active_quests', [])

        return render_template(
            'game/partials/sidebar_quests.html',
            session_id=session_id,
            quests=quests
        )
    except APIError as e:
        logger.error("failed_to_refresh_quests", session_id=session_id, error=str(e))
        return f'<div class="error">Failed to load quests: {e}</div>', 500


@game_bp.route('/session/<session_id>/npcs')
@require_auth
def npcs_accordion(session_id: str):
    """Refresh NPCs accordion content."""
    client = get_api_client()

    try:
        # Get session to find current location
        session_response = client.get(f'/api/v1/sessions/{session_id}')
        session_data = session_response.get('result', {})
        game_state = session_data.get('game_state', {})
        current_location_id = game_state.get('current_location_id') or game_state.get('current_location')

        # Get NPCs at location
        npcs = []
        if current_location_id:
            try:
                npcs_response = client.get(f'/api/v1/npcs/at-location/{current_location_id}')
                npcs = npcs_response.get('result', {}).get('npcs', [])
            except (APINotFoundError, APIError):
                pass

        return render_template(
            'game/partials/sidebar_npcs.html',
            session_id=session_id,
            npcs=npcs
        )
    except APIError as e:
        logger.error("failed_to_refresh_npcs", session_id=session_id, error=str(e))
        return f'<div class="error">Failed to load NPCs: {e}</div>', 500


@game_bp.route('/session/<session_id>/map')
@require_auth
def map_accordion(session_id: str):
    """Refresh map accordion content."""
    client = get_api_client()

    try:
        # Get session for current location
        session_response = client.get(f'/api/v1/sessions/{session_id}')
        session_data = session_response.get('result', {})
        game_state = session_data.get('game_state', {})
        current_location = _build_location_from_game_state(game_state)
        current_location_id = current_location.get('location_id')

        # Get available travel destinations
        discovered_locations = []
        try:
            travel_response = client.get(f'/api/v1/travel/available?session_id={session_id}')
            travel_result = travel_response.get('result', {})
            discovered_locations = travel_result.get('available_locations', [])
            # Mark current location
            for loc in discovered_locations:
                loc['is_current'] = loc.get('location_id') == current_location_id
        except (APINotFoundError, APIError):
            pass

        return render_template(
            'game/partials/sidebar_map.html',
            session_id=session_id,
            discovered_locations=discovered_locations,
            current_location=current_location
        )
    except APIError as e:
        logger.error("failed_to_refresh_map", session_id=session_id, error=str(e))
        return f'<div class="error">Failed to load map: {e}</div>', 500


# ===== Action Routes =====

@game_bp.route('/session/<session_id>/action', methods=['POST'])
@require_auth
def take_action(session_id: str):
    """
    Submit an action - returns job polling partial.

    Handles two action types:
    - 'button': Predefined action via prompt_id
    - 'custom': Free-form player text input
    """
    client = get_api_client()
    action_type = request.form.get('action_type', 'button')

    try:
        # Build payload based on action type
        payload = {'action_type': action_type}

        if action_type == 'text' or action_type == 'custom':
            # Free-form text action from player input
            action_text = request.form.get('action_text', request.form.get('custom_text', '')).strip()
            if not action_text:
                return '<div class="dm-response error">Please enter an action.</div>', 400

            logger.info("Player text action submitted",
                       session_id=session_id,
                       action_text=action_text[:100])

            payload['action_type'] = 'custom'
            payload['custom_text'] = action_text
            player_action = action_text
        else:
            # Button action via prompt_id
            prompt_id = request.form.get('prompt_id')
            if not prompt_id:
                return '<div class="dm-response error">No action selected.</div>', 400

            logger.info("Player button action submitted",
                       session_id=session_id,
                       prompt_id=prompt_id)

            payload['prompt_id'] = prompt_id
            player_action = None  # Will display prompt_id display text

        # POST to API
        response = client.post(f'/api/v1/sessions/{session_id}/action', payload)
        result = response.get('result', {})
        job_id = result.get('job_id')

        if not job_id:
            # Immediate response (shouldn't happen, but handle it)
            dm_response = result.get('dm_response', 'Action completed.')
            return render_template(
                'game/partials/dm_response.html',
                session_id=session_id,
                dm_response=dm_response
            )

        # Return polling partial
        return render_template(
            'game/partials/job_polling.html',
            session_id=session_id,
            job_id=job_id,
            status=result.get('status', 'queued'),
            player_action=player_action
        )

    except APIError as e:
        logger.error("failed_to_take_action", session_id=session_id, error=str(e))
        return f'<div class="dm-response error">Action failed: {e}</div>', 500


@game_bp.route('/session/<session_id>/job/<job_id>')
@require_auth
def poll_job(session_id: str, job_id: str):
    """Poll job status - returns updated partial."""
    client = get_api_client()

    try:
        response = client.get(f'/api/v1/jobs/{job_id}/status')
        result = response.get('result', {})
        status = result.get('status', 'unknown')

        if status == 'completed':
            # Job done - check for NPC dialogue vs story action
            nested_result = result.get('result', {})
            if nested_result.get('context_type') == 'npc_dialogue':
                # NPC dialogue response - return dialogue partial
                return render_template(
                    'game/partials/npc_dialogue_response.html',
                    npc_name=nested_result.get('npc_name', 'NPC'),
                    character_name=nested_result.get('character_name', 'You'),
                    conversation_history=nested_result.get('conversation_history', []),
                    player_line=nested_result.get('player_line', ''),
                    dialogue=nested_result.get('dialogue', 'No response'),
                    session_id=session_id
                )
            else:
                # Standard DM response
                dm_response = result.get('dm_response', nested_result.get('dm_response', 'No response'))
                return render_template(
                    'game/partials/dm_response.html',
                    session_id=session_id,
                    dm_response=dm_response
                )

        elif status in ('failed', 'error'):
            error_msg = result.get('error', 'Unknown error occurred')
            return f'<div class="dm-response error">Action failed: {error_msg}</div>'

        else:
            # Still processing - return polling partial to continue
            return render_template(
                'game/partials/job_polling.html',
                session_id=session_id,
                job_id=job_id,
                status=status
            )

    except APIError as e:
        logger.error("failed_to_poll_job", job_id=job_id, session_id=session_id, error=str(e))
        return f'<div class="dm-response error">Failed to check job status: {e}</div>', 500


# ===== Modal Routes =====

@game_bp.route('/session/<session_id>/equipment-modal')
@require_auth
def equipment_modal(session_id: str):
    """Get equipment modal with character's gear."""
    client = get_api_client()

    try:
        # Get session to find character
        session_response = client.get(f'/api/v1/sessions/{session_id}')
        session_data = session_response.get('result', {})
        character_id = session_data.get('character_id')

        character = {}
        if character_id:
            char_response = client.get(f'/api/v1/characters/{character_id}')
            char_data = char_response.get('result', {})
            character = _build_character_from_api(char_data)

        return render_template(
            'game/partials/equipment_modal.html',
            session_id=session_id,
            character=character
        )
    except APIError as e:
        logger.error("failed_to_load_equipment_modal", session_id=session_id, error=str(e))
        return f'''
        <div class="modal-overlay" onclick="this.remove()">
            <div class="modal-content" onclick="event.stopPropagation()">
                <h3>Equipment</h3>
                <div class="error">Failed to load equipment: {e}</div>
                <button class="modal-close" onclick="this.closest('.modal-overlay').remove()">Close</button>
            </div>
        </div>
        '''


@game_bp.route('/session/<session_id>/travel-modal')
@require_auth
def travel_modal(session_id: str):
    """Get travel modal with available destinations."""
    client = get_api_client()

    try:
        # Get available travel destinations
        response = client.get(f'/api/v1/travel/available?session_id={session_id}')
        result = response.get('result', {})
        available_locations = result.get('available_locations', [])
        current_location_id = result.get('current_location')

        # Get current location details from session
        session_response = client.get(f'/api/v1/sessions/{session_id}')
        session_data = session_response.get('result', {})
        game_state = session_data.get('game_state', {})
        current_location = _build_location_from_game_state(game_state)

        # Filter out current location from destinations
        destinations = [loc for loc in available_locations if loc.get('location_id') != current_location_id]

        return render_template(
            'game/partials/travel_modal.html',
            session_id=session_id,
            destinations=destinations,
            current_location=current_location
        )
    except APIError as e:
        logger.error("failed_to_load_travel_modal", session_id=session_id, error=str(e))
        return f'''
        <div class="modal-overlay" onclick="this.remove()">
            <div class="modal-content" onclick="event.stopPropagation()">
                <h3>Travel</h3>
                <div class="error">Failed to load travel options: {e}</div>
                <button class="modal-close" onclick="this.closest('.modal-overlay').remove()">Close</button>
            </div>
        </div>
        '''


@game_bp.route('/session/<session_id>/travel', methods=['POST'])
@require_auth
def do_travel(session_id: str):
    """Execute travel to location - returns job polling partial or immediate response."""
    client = get_api_client()
    location_id = request.form.get('location_id')

    if not location_id:
        return '<div class="error">No destination selected.</div>', 400

    try:
        response = client.post('/api/v1/travel', {
            'session_id': session_id,
            'location_id': location_id
        })
        result = response.get('result', {})

        # Check if travel triggers a job (narrative generation)
        job_id = result.get('job_id')
        if job_id:
            # Close modal and return job polling partial
            return f'''
            <script>document.querySelector('.modal-overlay')?.remove();</script>
            ''' + render_template(
                'game/partials/job_polling.html',
                job_id=job_id,
                session_id=session_id,
                status='queued'
            )

        # Immediate response (no AI generation)
        narrative = result.get('narrative', result.get('description', 'You arrive at your destination.'))
        location_name = result.get('location_name', 'Unknown Location')

        # Close modal and update response area
        return f'''
        <script>document.querySelector('.modal-overlay')?.remove();</script>
        <div class="dm-response">
            <strong>Arrived at {location_name}</strong><br><br>
            {narrative}
        </div>
        ''' + render_template(
            'game/partials/dm_response.html',
            session_id=session_id,
            dm_response=f"**Arrived at {location_name}**\n\n{narrative}"
        )

    except APIError as e:
        logger.error("failed_to_travel", session_id=session_id, location_id=location_id, error=str(e))
        return f'<div class="error">Travel failed: {e}</div>', 500


@game_bp.route('/session/<session_id>/npc/<npc_id>/chat')
@require_auth
def npc_chat_modal(session_id: str, npc_id: str):
    """Get NPC chat modal with conversation history."""
    client = get_api_client()

    try:
        # Get session to find character
        session_response = client.get(f'/api/v1/sessions/{session_id}')
        session_data = session_response.get('result', {})
        character_id = session_data.get('character_id')

        # Get NPC details with relationship info
        npc_response = client.get(f'/api/v1/npcs/{npc_id}?character_id={character_id}')
        npc_data = npc_response.get('result', {})

        npc = {
            'npc_id': npc_data.get('npc_id'),
            'name': npc_data.get('name'),
            'role': npc_data.get('role'),
            'appearance': npc_data.get('appearance', {}).get('brief', ''),
            'tags': npc_data.get('tags', [])
        }

        # Get relationship info
        interaction_summary = npc_data.get('interaction_summary', {})
        relationship_level = interaction_summary.get('relationship_level', 50)
        interaction_count = interaction_summary.get('interaction_count', 0)

        # Conversation history would come from character's npc_interactions
        # For now, we'll leave it empty - the API returns it in dialogue responses
        conversation_history = []

        return render_template(
            'game/partials/npc_chat_modal.html',
            session_id=session_id,
            npc=npc,
            conversation_history=conversation_history,
            relationship_level=relationship_level,
            interaction_count=interaction_count
        )

    except APINotFoundError:
        return '<div class="error">NPC not found</div>', 404
    except APIError as e:
        logger.error("failed_to_load_npc_chat", session_id=session_id, npc_id=npc_id, error=str(e))
        return f'''
        <div class="modal-overlay" onclick="this.remove()">
            <div class="modal-content" onclick="event.stopPropagation()">
                <h3>Talk to NPC</h3>
                <div class="error">Failed to load NPC info: {e}</div>
                <button class="modal-close" onclick="this.closest('.modal-overlay').remove()">Close</button>
            </div>
        </div>
        '''


@game_bp.route('/session/<session_id>/npc/<npc_id>/talk', methods=['POST'])
@require_auth
def talk_to_npc(session_id: str, npc_id: str):
    """Send message to NPC - returns dialogue response or job polling partial."""
    client = get_api_client()

    # Support both topic (initial greeting) and player_response (conversation)
    player_response = request.form.get('player_response')
    topic = request.form.get('topic', 'greeting')

    try:
        payload = {'session_id': session_id}
        if player_response:
            # Player typed a custom response
            payload['player_response'] = player_response
        else:
            # Initial greeting click
            payload['topic'] = topic

        response = client.post(f'/api/v1/npcs/{npc_id}/talk', payload)
        result = response.get('result', {})

        # Check if it's a job-based response (async) or immediate
        job_id = result.get('job_id')
        if job_id:
            # Return job polling partial for the chat area
            return render_template(
                'game/partials/job_polling.html',
                job_id=job_id,
                session_id=session_id,
                status='queued',
                is_npc_dialogue=True
            )

        # Immediate response (if AI is sync or cached)
        dialogue = result.get('dialogue', result.get('response', 'The NPC has nothing to say.'))
        npc_name = result.get('npc_name', 'NPC')

        # Return dialogue in chat format
        player_display = player_response if player_response else f"[{topic}]"
        return f'''
        <div class="chat-message chat-message--player">
            <strong>You:</strong> {player_display}
        </div>
        <div class="chat-message chat-message--npc">
            <strong>{npc_name}:</strong> {dialogue}
        </div>
        '''

    except APINotFoundError:
        return '<div class="error">NPC not found.</div>', 404
    except APIError as e:
        logger.error("failed_to_talk_to_npc", session_id=session_id, npc_id=npc_id, error=str(e))
        return f'<div class="error">Failed to talk to NPC: {e}</div>', 500
