"""Render the additive planning dashboard from the two specification registers.

This is documentation tooling only. It does not implement a Minecraft feature.
"""
import json


def render_planning(root, esc, inline, badge, sectionhead, anchor, search):
    first = json.loads((root / 'first_playable_register.json').read_text())
    models = json.loads((root / 'tier1_model_register.json').read_text())
    art = json.loads((root / 'next_image_slice.json').read_text())
    inspection = json.loads((root / 'project_inspection.json').read_text())
    first_title = 'First Playable — Old Valley, the Quarry, and the First Workshop'
    models_title = 'Tier 1 — Model and Implementation Specifications'

    def list_html(items):
        if not items:
            return ''
        if isinstance(items, str):
            return '<p>' + esc(items) + '</p>'
        return '<ul>' + ''.join('<li>' + esc(v if isinstance(v, str) else json.dumps(v, ensure_ascii=False)) + '</li>' for v in items) + '</ul>'

    def readable(value):
        if isinstance(value, str):
            return value
        if isinstance(value, list):
            return '; '.join(readable(x) for x in value)
        if isinstance(value, dict):
            return '; '.join(str(k).replace('_', ' ') + ': ' + readable(v) for k, v in value.items())
        return str(value)

    def detail_html(value):
        """Expose the complete structured contract as ordinary readable HTML."""
        if isinstance(value, dict):
            return '<dl class="spec-fields">' + ''.join('<dt>' + esc(str(k).replace('_', ' ').capitalize()) + '</dt><dd>' + detail_html(v) + '</dd>' for k, v in value.items()) + '</dl>'
        if isinstance(value, list):
            if not value:
                return '<span>None specified</span>'
            if all(isinstance(v, (int, float)) for v in value):
                return esc('(' + ', '.join(str(v) for v in value) + ')')
            return '<ul>' + ''.join('<li>' + detail_html(v) + '</li>' for v in value) + '</ul>'
        if isinstance(value, bool):
            return 'Yes' if value else 'No'
        return inline(value) if value is not None else 'Not specified'

    def register_tasks(register):
        return register.get('tasks', register.get('build_tasks', register.get('backlog', [])))

    task_groups = [('first-playable', 'First Playable', first_title, register_tasks(first)),
                   ('tier1-models', 'Tier 1 models and implementation', models_title, register_tasks(models))]
    tasks = []
    for stream, label, title, records in task_groups:
        for task in records:
            task = dict(task)
            task.update(stream=stream, stream_label=label, chapter_title=title)
            if stream == 'tier1-models':
                task.setdefault('section_id', 'T1-SPEC-19')
            tasks.append(task)
    decisions = list(models.get('decisions', []))

    h = '<section class="section-wrap planning-intro" id="planning-slice">'
    h += sectionhead('New / Planning slice 02', 'A first playable. A precise Tier 1 brief.',
                     'Two complete additions connect the direction to reviewable build work. All previous writing and artwork remain in this edition.')
    h += '<div class="legend">' + badge('Planning and documentation', 'new') + badge('BUILD approval required', 'backlog') + badge('0 new images generated', 'confirmed') + '</div>'
    h += '<div class="comparison-grid"><article class="card"><span class="card-id">FIRST PLAYABLE</span><h3>Old Valley, the quarry, and the first workshop.</h3><p>Restore the guesthouse, repair the crossing, reopen the quarry, explore a bounded Copperwash Channels route, and turn early materials into a useful hoist improvement.</p><p>The opening keeps its authored chapter order. The first workshop is a limited service, with the full settlement network and first market still ahead.</p><div class="links"><a href="#' + anchor(first_title) + '">Read the complete First Playable specification →</a></div></article>'
    h += '<article class="card"><span class="card-id">TIER 1 PRODUCTION PLANNING</span><h3>From confirmed references to consistent assets.</h3><p>Proposed dimensions, component boundaries, connections, collision, assembly, animations, and operating states for the Explorer and its supporting systems.</p><p>Confirmed appearance is preserved. Geometry and production choices are explicitly proposed and remain subject to review and future in-game fit checks.</p><div class="links"><a href="#' + anchor(models_title) + '">Read the complete model specification →</a></div></article></div>'
    h += '<div class="intro-note"><strong>Implementation baseline.</strong> The inspected workspace contains the document sources, artwork, diagrams, and HTML tooling. No Minecraft mod source or build configuration was available there. Existing implementation, loader, version, save compatibility, and performance remain unverified. The first build prerequisite is a read-only audit of the actual mod project. No mod development has been performed in this slice.</div>'
    h += '<div class="hero-links"><a class="button" href="#planning-decisions">Recommended defaults and decisions</a><a class="button" href="#planning-build-backlog">Prioritised build backlog</a><a class="button" href="#next-image-slice">Next image slice · planned only</a></div></section>'

    h += '<section class="section-wrap" id="planning-decisions">' + sectionhead('Review / Production choices', 'The few choices that need your direction.', 'The specifications make practical defaults explicit. Approval of the documentation does not mark provisional dimensions as tested or authorize a mod build.')
    h += '<div class="comparison-grid">'
    for decision in decisions:
        ident = decision.get('id', '')
        title = decision.get('title', decision.get('question', ident))
        recommendation = decision.get('recommendation', decision.get('recommended_default', decision.get('recommended', 'See the complete specification.')))
        reason = decision.get('reason', decision.get('rationale', decision.get('why', '')))
        before = decision.get('needed_before', decision.get('decision_gate', decision.get('gate', 'Final model production')))
        h += '<article class="card" id="decision-' + ident.lower() + '"><span class="card-id">' + esc(ident) + '</span><h3>' + esc(title) + '</h3>' + badge('Recommended default · review needed', 'new')
        h += '<p><strong>Recommendation:</strong> ' + esc(readable(recommendation)) + '</p>'
        if reason:
            h += '<p>' + esc(readable(reason)) + '</p>'
        h += '<p><strong>Resolve before:</strong> ' + esc(readable(before)) + '</p><div class="links"><a href="#' + anchor(decision.get('section_id', 'T1-SPEC-02')) + '">Read the constraints and alternatives →</a></div></article>'
        search.append({'id': 'decision-' + ident.lower(), 'chapter': 'Planning decisions', 'title': ident + ' · ' + title, 'text': readable(decision), 'source': 'planning-decisions'})
    h += '</div><p class="endnote">Opening scope, initial recipe quantities, timing targets, material accounting, and task order are recommended playtest defaults in the First Playable specification. The actual project audit determines which implementation work is already present and which work remains.</p></section>'

    h += '<section class="section-wrap" id="tier1-model-matrix">' + sectionhead('Tier 1 / Asset specification index', 'Every asset has a production role.', 'Dimensions below are proposed block units. Body, fin, deck, and moving-service envelopes are distinguished where needed. The full specification governs collision, door sweeps, access, and fit checks.')
    h += '<div class="table-wrap model-matrix" tabindex="0" aria-label="Scrollable Tier 1 asset specification index"><table><thead><tr><th>Asset</th><th>Proposed dimensions</th><th>Confirmed visual context</th><th>Role and detailed specification</th></tr></thead><tbody>'
    for asset in models.get('assets', []):
        ident = asset['id']; row_id = 'model-asset-' + ident.lower()
        dims = asset.get('proposed_dimensions', {})
        footprint = dims.get('footprint_blocks')
        height = dims.get('height_blocks')
        body = dims.get('main_body_footprint_blocks')
        if isinstance(body, list) and len(body) == 2:
            dimension_text = str(body[0]) + ' × ' + str(body[1]) + ' body; ' + str(footprint[0]) + ' × ' + str(footprint[1]) + ' closed fin envelope; ' + str(height) + ' high'
        elif dims.get('deck_height_blocks') is not None:
            dimension_text = str(footprint[0]) + ' × ' + str(footprint[1]) + ' deck; ' + str(dims['deck_height_blocks']) + ' deck height; service pose up to ' + str(height)
        elif isinstance(footprint, list) and len(footprint) == 2 and height is not None:
            dimension_text = str(footprint[0]) + ' × ' + str(footprint[1]) + ' nominal; ' + str(height) + ' high'
        else:
            dimension_text = 'Item, worn, or service specification; see full dimensions'
        representation = {'connection': 'Placement cell; visible pipe body is approximately 0.25 blocks thick.',
                          'component': 'Loose component display envelope.',
                          'component_container': 'Portable container envelope.',
                          'control_item': 'Handheld item display envelope.',
                          'wearable': 'Art-review envelope; this does not change the player hitbox.',
                          'wearable_component': 'Wearable art envelope; fit to the supported player rig.',
                          'equipment_container': 'Portable equipment envelope.',
                          'service': 'Proposed service frontage or rack envelope.',
                          'structure_service': 'Structure envelope; terrain allowance is defined separately.',
                          'recovery_service': 'Existing beacon service mode; no extra mandatory machine.'}.get(asset.get('category'), '')
        installed = dims.get('installed_variant_bounds')
        if installed:
            extent = [round(b - a, 4) for a, b in zip(installed['min'], installed['max'])]
            representation += ' Installed assembly: ' + ' × '.join(str(v) for v in (extent[0], extent[2], extent[1])) + ' (width × depth × height).'
        evidence = asset.get('reference_evidence', [])
        refs = []
        for reference in evidence:
            ref = reference.get('ref', '')
            if isinstance(ref, str) and ref.startswith('REF'):
                refs.append('<a href="#ref-' + ref.lower() + '">' + esc(ref) + '</a>')
        h += '<tr id="' + row_id + '"><td><code>' + esc(ident) + '</code><br>' + esc(asset.get('name', ident)) + '</td><td>' + esc(dimension_text) + ('<p>' + esc(representation) + '</p>' if representation else '') + '<br>' + badge('Proposed · fit check required', 'new') + '</td><td>' + (' · '.join(dict.fromkeys(refs)) if refs else 'Planning extension from the existing system role') + '</td><td>' + esc(readable(asset.get('function', ''))) + '<br><a href="#' + anchor(asset.get('section_id', models_title)) + '">Read the complete asset specification →</a><details class="asset-contract"><summary>Inspect dimensions, ports, components, states, and acceptance</summary>' + detail_html(asset) + '</details></td></tr>'
        search.append({'id': row_id, 'chapter': 'Tier 1 asset specification index', 'title': ident + ' · ' + asset.get('name', ident), 'text': readable(asset), 'source': 'model-assets'})
    h += '</tbody></table></div></section>'

    h += '<section class="section-wrap" id="planning-build-backlog">' + sectionhead('Build preparation / Ordered work', 'A backlog with observable completion.', 'Every entry is proposed work. Dependencies identify what must be established first; acceptance criteria describe the evidence that a future build must produce.')
    h += '<div class="backlog-controls"><input id="plan-filter" type="search" placeholder="Find a task, asset, or acceptance criterion…" aria-label="Filter build planning tasks"><select id="plan-stream" aria-label="Planning stream"><option value="">Both planning streams</option><option value="first-playable">First Playable</option><option value="tier1-models">Tier 1 models and implementation</option></select><button id="export-plan" type="button">Export planning registers</button></div><p class="backlog-count" id="plan-count"></p>'
    h += '<div class="table-wrap backlog-table build-plan-table" tabindex="0" aria-label="Scrollable prioritised build planning backlog"><table><thead><tr><th>Task</th><th>Priority and scope</th><th>Depends on</th><th>Completion criteria</th></tr></thead><tbody>'
    for task in tasks:
        ident = task['id']; row_id = 'plan-task-' + ident.lower()
        title = task.get('title', task.get('name', ident))
        priority = task.get('priority', 'Planned')
        deps = task.get('depends_on', task.get('dependencies', []))
        external_deps = task.get('external_dependencies', [])
        decision_deps = task.get('decision_dependencies', [])
        dependency_html = ' · '.join('<a href="#plan-task-' + esc(d.lower()) + '">' + esc(d) + '</a>' for d in deps) if deps else 'No earlier task'
        if external_deps:
            dependency_html += '<p><strong>External prerequisite:</strong> ' + esc(readable(external_deps)) + '</p>'
        if decision_deps:
            dependency_html += '<p><strong>Final production review:</strong> ' + ' · '.join('<a href="#decision-' + esc(d.lower()) + '">' + esc(d) + '</a>' for d in decision_deps) + '</p><p>' + esc(task.get('decision_gate_note', 'Resolve before final production.')) + '</p>'
        criteria = task.get('acceptance', task.get('acceptance_criteria', task.get('completion_criteria', [])))
        scope = task.get('scope', task.get('description', ''))
        h += '<tr id="' + row_id + '" data-plan-row data-stream="' + esc(task['stream']) + '"><td><code>' + esc(ident) + '</code><br><a href="#' + anchor(task.get('section_id', task['chapter_title'])) + '">' + esc(title) + '</a></td>'
        h += '<td>' + badge(readable(priority), 'new') + '<br>' + esc(task['stream_label']) + ('<p>' + esc(readable(scope)) + '</p>' if scope else '') + '</td><td>' + dependency_html + '</td><td>' + list_html(criteria) + '</td></tr>'
        search.append({'id': row_id, 'chapter': 'Build planning · ' + task['stream_label'], 'title': ident + ' · ' + title, 'text': readable(task), 'source': 'build-plan'})
    h += '</tbody></table></div><p class="endnote">The registers preserve the full deliverables, asset mappings, and verification requirements. No task is marked implemented or tested from the existence of a design entry. Actual mod development remains behind explicit BUILD approval.</p></section>'

    h += '<section class="section-wrap" id="next-image-slice">' + sectionhead('Visual planning / No generation', 'Two future boards, with a defined job.', 'The proposed boards support the first playable. Your existing Tier 1 references continue to guide the model specification.')
    h += '<div class="comparison-grid">'
    for record in art['records']:
        ident = record['id']
        h += '<article class="card art-placeholder" id="planned-art-' + ident.lower() + '"><span class="card-id">' + esc(ident) + '</span>' + badge('Planned · image not generated', 'backlog') + '<h3>' + esc(record['title']) + '</h3><p>' + esc(record['purpose']) + '</p>'
        h += list_html(record['subjects']) + '<p><strong>Format:</strong> ' + esc(record['format']) + '</p><details><summary>Dependencies, acceptance, and future prompt</summary><h4>Before generation</h4>' + list_html(record['depends_on']) + '<h4>Acceptance</h4>' + list_html(record['acceptance']) + '<h4>Future prompt</h4><p>' + esc(record['future_prompt']) + '</p></details></article>'
        search.append({'id': 'planned-art-' + ident.lower(), 'chapter': 'Next image slice · planned only', 'title': ident + ' · ' + record['title'], 'text': readable(record), 'source': 'next-art'})
    h += '</div><div class="intro-note"><strong>Generation status: zero new calls.</strong> These are written briefs. A later image-generation instruction can approve the two-board scope. Detailed Tier 2 and Tier 3 production remains deferred.</div></section>'
    data = {'edition': 'Planning edition 02', 'implementation_status': inspection['implementation_status'],
            'build_authorization': inspection['build_authorization'], 'first_playable': first, 'tier1_models': models,
            'next_image_slice': art, 'task_count': len(tasks), 'decisions': decisions}
    return h, data
