"""Render the source-preserving current-direction and worked-journey edition.

This module only renders local documentation and accepted assets. It never
invokes image generation, modifies the preserved text sources, or contacts a
website repository.
"""
from pathlib import Path
import hashlib
import json
import re
import shutil


NEW_SOURCES = [
    ('current-direction', 'current_direction_05.txt', 'Current direction'),
    ('worked-progression', 'progression_worked_example_05.txt', 'Worked progression'),
    ('occupied-spaces', 'occupied_spaces_05.txt', 'Occupied places and machinery'),
    ('lunar-expedition', 'lunar_expedition_05.txt', 'First lunar expedition'),
]
NEW_KEYS = {s[0] for s in NEW_SOURCES}


class CurrentDirectionEdition:
    def __init__(self, root, out, chapters, esc, inline, badge, anchor, search):
        self.root, self.out = Path(root), Path(out)
        self.portable = self.root.name == 'sources'
        self.chapters = chapters
        self.esc, self.inline, self.badge, self.anchor, self.search = esc, inline, badge, anchor, search
        self.data = json.loads((self.root/'edition05_integration.json').read_text())
        self.topics = self.data['topics']
        self.figures = {f['id']: dict(f) for f in self.data['figures']}
        self.chapter_by_pair = {(c['source'], c['title']): c for c in chapters}
        self.chapter_by_source = {c['source']: c for c in chapters if c['source'] in NEW_KEYS}
        self.rendered_figures = set()
        self.inline_source_refs = []
        for f in self.figures.values():
            f['target'] = 'figure-' + f['id'].lower()
            f['source_path'] = self.out/f['file'] if self.portable else self.root/f['source_file']
            if not f['source_path'].is_file():
                raise RuntimeError('Missing accepted edition05 figure: ' + str(f['source_path']))

    def _copy(self, source, target):
        source, target = Path(source), Path(target)
        target.parent.mkdir(parents=True, exist_ok=True)
        if source.resolve() != target.resolve():
            shutil.copy2(source, target)

    def copy_assets_and_sources(self):
        for f in self.figures.values():
            self._copy(f['source_path'], self.out/f['file'])
            if f['kind'] == 'concept':
                web_source = self.out/f['web'] if self.portable else self.root/f['web_source']
                self._copy(web_source, self.out/f['web'])
        for name in self.data['source_files']:
            self._copy(self.root/name, self.out/'sources'/name)
        for name in ('edition05_companion.py', 'edition05_integration.json'):
            self._copy(self.root/name, self.out/'sources'/name)

    def catalogue_records(self):
        return [{
            'id': f['id'], 'title': f['title'],
            'kind': 'Edition 05 concept board' if f['kind'] == 'concept' else 'Edition 05 editable diagram',
            'status': 'Included; design proposals remain labelled',
            'target': f['target'], 'caption': f['caption'],
            'source_ids': f.get('source_ids', []), 'coverage_note': f['coverage_note'],
            'file': f.get('web', f['file']), 'original': f['file'],
        } for f in self.figures.values()]

    def source_link(self, item):
        chapter = self.chapter_by_pair[(item['source_key'], item['chapter_title'])]
        return '<a href="#'+chapter['id']+'">'+self.esc(item['chapter_title'])+'</a>'

    def chapter_target(self, source):
        return self.chapter_by_source[source]['id']

    def section_target(self, source, prefix):
        chapter = self.chapter_by_source[source]
        return next((s['anchor'] for s in chapter['subheads'] if s['title'].startswith(prefix)), chapter['id'])

    def nav(self):
        entries = [('current-edition', 'Current direction · Edition 05')]
        entries += [(self.chapter_target(key), label) for key, _, label in NEW_SOURCES]
        entries += [('decision-review-05', 'Decisions and recommendations'), ('source-map-05', 'Current topics and full sources'), ('reconciliation-05', 'Reconciled edition history'), ('remaining-detail-05', 'Further detail by subject')]
        return entries

    def hub(self):
        e = self.esc
        h = '<section class="section-wrap current-edition-hub" id="current-edition"><div class="section-head"><div><span class="eyebrow">Current direction · Edition 05</span><h2>A connected plan, from home to the Moon.</h2></div><p>Four completed documentation slices bring the current direction together, follow a worked industrial chain, show occupied places, and describe one complete lunar expedition. All earlier writing and artwork remain available in full.</p></div>'
        h += '<div class="illustrated-stats"><div><b>4 / 4</b><span>documentation slices delivered</span></div><div><b>90,735</b><span>earlier source words preserved</span></div><div><b>3</b><span>focused concept boards</span></div><div><b>6</b><span>new editable diagrams</span></div></div><div class="illustrated-slice-grid">'
        for s in self.data['slices']:
            h += '<a class="illustrated-slice-card" href="#'+self.chapter_target(s['source_key'])+'"><span class="card-id">'+e(s['id'])+'</span><h3>'+e(s['title'])+'</h3><p>'+e(s['result'])+'</p>'+self.badge('Document slice complete', 'confirmed')+'</a>'
        h += '</div><div class="hero-links"><a class="button primary" href="#'+self.chapter_target('current-direction')+'">Read the current direction</a><a class="button" href="#decision-review-05">Review the few open choices</a><a class="button" href="#source-map-05">Find the full source detail</a></div><p class="endnote">Current reading guidance does not replace the complete sources. New quantitative examples, geometry and model choices retain their stated proposal status. The original approved text and confirmed artwork are preserved.</p></section>'
        return h

    def original_chapter_note(self, chapter):
        relevant = [t for t in self.topics if any(s['source_key']==chapter['source'] and s['chapter_title']==chapter['title'] for s in t['source_chapters'])]
        if not relevant:
            return ''
        return '<aside class="chapter-guide-note edition05-source-note"><span class="eyebrow">Current direction and source history</span><p>This complete source is preserved. Read its current synthesis and applicable clarifications in '+ ' · '.join('<a href="#'+self.section_target('current-direction', t['id'])+'">'+self.esc(t['title'])+'</a>' for t in relevant) +'.</p></aside>'

    def source_context(self, heading):
        topic = next((t for t in self.topics if heading.startswith(t['id'])), None)
        if not topic:
            return ''
        return '<aside class="chapter-guide-note edition05-source-context"><span class="eyebrow">Complete detail and evidence</span><p>'+self.esc(topic.get('source_note', 'The current account below is supported by these complete preserved chapters.'))+'</p><div class="related-chapters">'+' · '.join(self.source_link(s) for s in topic['source_chapters'])+'</div></aside>'

    def figure(self, ident):
        if ident in self.rendered_figures:
            f=self.figures[ident]
            return '<p class="figure-sources"><a href="#'+f['target']+'">'+self.esc(ident+' · '+f['title'])+'</a></p>'
        self.rendered_figures.add(ident)
        f=self.figures[ident];e=self.esc
        if f['kind']=='concept':
            h='<figure class="plate companion-figure" id="'+f['target']+'"><button type="button" data-image="'+f['web']+'" data-original="'+f['file']+'" data-caption="'+e(f['title'])+'" aria-label="Enlarge '+e(f['title'])+'"><img src="'+f['web']+'" alt="'+e(f['alt'])+'" loading="lazy" decoding="async"></button><figcaption><div class="plate-meta"><strong>'+e(f['title'])+'</strong><span>'+e(ident)+' · Concept board</span></div>'+e(f['caption'])
        else:
            svg=f['source_path'].read_text()
            svg=re.sub(r'<\?xml[^>]*>', '', svg)
            ids=re.findall(r'\bid="([^"]+)"', svg)
            mapping={i:ident.lower()+'-'+i for i in ids}
            svg=re.sub(r'\bid="([^"]+)"',lambda m:'id="'+mapping[m[1]]+'"',svg)
            svg=re.sub(r'url\(#([^\)]+)\)',lambda m:'url(#'+mapping.get(m[1],m[1])+')',svg)
            svg=re.sub(r'\bhref="#([^"]+)"',lambda m:'href="#'+mapping.get(m[1],m[1])+'"',svg)
            svg=re.sub(r'(aria-(?:labelledby|describedby)=")([^"]+)(")',lambda m:m[1]+' '.join(mapping.get(v,v) for v in m[2].split())+m[3],svg)
            h='<figure class="diagram companion-figure" id="'+f['target']+'">'+svg+'<figcaption><strong>'+e(f['title'])+'</strong><br>'+e(f['caption'])+' <a href="'+f['file']+'" target="_blank">Open full-size diagram</a>'
        h+='<p class="figure-coverage">'+e(f['coverage_note'])+'</p>'
        if f.get('proposed_notes'):
            h+='<p class="figure-coverage"><strong>Review status:</strong> '+e(f['proposed_notes'])+'</p>'
        refs=[]
        for source_id in f.get('source_ids',[]):
            if re.fullmatch(r'REF\d{2}',source_id):
                target='ref-'+source_id.lower()
            else:
                target=self.anchor(source_id)
            refs.append('<a href="#'+target+'">'+e(source_id)+'</a>' if target!='complete-text' else e(source_id))
        h+='<p class="figure-sources"><strong>Source context:</strong> '+' · '.join(refs)+'</p></figcaption></figure>'
        self.search.append({'id':f['target'],'chapter':'Current direction · Edition 05 figures','title':ident+' · '+f['title'],'text':f['caption']+' '+f['alt']+' '+f['coverage_note']+' '+' '.join(f.get('source_ids',[])),'source':'edition05-visuals'})
        return h

    def after_paragraph(self, source, heading):
        h=self.source_context(heading) if source=='current-direction' else ''
        for rule in self.data['figure_insertions']:
            if rule['source_key']==source and heading.startswith(rule['heading_prefix']):
                h+=''.join(self.figure(i) for i in rule['figures'])
        return h

    def review_and_maps(self):
        e=self.esc
        h='<section class="section-wrap" id="decision-review-05"><div class="section-head"><div><span class="eyebrow">Decision review</span><h2>Three choices, with their consequences.</h2></div><p>The first two affect final Tier 1 form; the third is an optional fictional ecology choice. These do not prevent the completed document or the Website agent from being used.</p></div><div class="card-grid">'
        for d in self.data['decisions']:
            h+='<article class="card" id="review-'+d['id'].lower()+'"><span class="card-id">'+e(d['id'])+'</span><h3>'+e(d['title'])+'</h3>'+self.badge(d['status'],'new')+'<p><strong>Recommendation:</strong> '+e(d['recommendation'])+'</p><p><strong>Why it matters:</strong> '+e(d['consequence'])+'</p><p>'+e(d['evidence_summary'])+'</p><p class="figure-sources">'+' · '.join('<a href="#'+self.anchor(i)+'">'+e(i)+'</a>' if self.anchor(i)!='complete-text' else e(i) for i in d.get('source_ids',[]))+'</p></article>'
        h+='</div><p class="endnote">All other documented dimensions, quantities and timings remain explicit working defaults where stated. This packet does not ask for blanket reconfirmation of the approved original direction.</p></section>'
        h+='<section class="section-wrap" id="source-map-05"><div class="section-head"><div><span class="eyebrow">Full-detail source map</span><h2>One current topic, every complete source.</h2></div><p>All 45 baseline chapters are mapped below. The current reading layer consolidates their direction; each linked chapter retains its complete writing, tables and artwork context.</p></div>'
        for t in self.topics:
            h+='<details class="current-source-group"><summary>'+e(t['id']+' · '+t['title'])+'</summary><p><a href="#'+self.section_target('current-direction',t['id'])+'">Read this current topic</a></p><ul>'
            for s in t['source_chapters']:
                h+='<li>'+self.source_link(s)+' <span class="source-key">'+e(s['source_key'])+'</span></li>'
            h+='</ul></details>'
        h+='</section><section class="section-wrap" id="reconciliation-05"><div class="section-head"><div><span class="eyebrow">Editorial reconciliation</span><h2>How the current direction relates to its history.</h2></div><p>These clarifications retain their evidence and preserve the earlier wording. Proposed design additions stay distinguishable from confirmed choices.</p></div><div class="table-wrap" tabindex="0"><table><thead><tr><th>Subject</th><th>Current reading</th><th>Evidence and retained context</th></tr></thead><tbody>'
        for r in self.data['reconciliation']:
            h+='<tr><td>'+e(r['id']+' · '+r['title'])+'</td><td>'+e(r['resolution'])+'</td><td>'+e(r['evidence_summary'])+'</td></tr>'
        h+='</tbody></table></div></section><section class="section-wrap" id="remaining-detail-05"><div class="section-head"><div><span class="eyebrow">Further detail</span><h2>Choose the next depth deliberately.</h2></div><p>The four current slices are delivered. These bounded subjects describe possible further depth and their completion criteria; they are not missing overview chapters or an instruction to generate every historical artwork record.</p></div><div class="table-wrap" tabindex="0"><table><thead><tr><th>Subject</th><th>Current coverage</th><th>Next bounded detail</th><th>Done when</th></tr></thead><tbody>'
        for r in self.data['remaining_expansions']:
            h+='<tr><td>'+e(r['id']+' · '+r['title'])+'</td><td>'+e(r['current_coverage'])+'</td><td>'+e(r['scope'])+'</td><td>'+e(r['completion'])+'</td></tr>'
        return h+'</tbody></table></div></section>'

    def verify_rendered(self):
        missing=set(self.figures)-self.rendered_figures
        if missing:
            raise RuntimeError('Figures not inserted in source context: '+', '.join(sorted(missing)))

    def add_manifest(self, manifest):
        manifest['edition']='Current direction and worked journeys 05'
        manifest['date']='2026-10-07'
        manifest['previous_edition']='Illustrated overview 03 + Reader update 04'
        manifest['prior_source_words_preserved']=90735
        manifest['edition05']={
            'document_slices':self.data['slices'], 'new_sources':[s[1] for s in NEW_SOURCES],
            'generated_concept_boards':3, 'generation_call_cap':3,
            'generation_calls_used':self.data['generation_calls_used'], 'rerolls':0,
            'new_editable_diagrams':6, 'baseline_chapters_preserved':45,
            'baseline_source_words_preserved':90735, 'baseline_art_files_preserved':81,
            'baseline_unique_visuals_preserved':50, 'current_topic_count':len(self.topics),
            'mod_development_authorized':False, 'browser_interaction_verified':False,
            'website_update':'Separate content/source update; no remote website was changed.',
        }
        for f in self.figures.values():
            if f['kind']=='concept':
                manifest['image_records'].append({'id':f['id'],'title':f['title'],'status':'Edition 05 layout/operation concept','original':f['file'],'web':f['web'],'sha256_original':hashlib.sha256((self.out/f['file']).read_bytes()).hexdigest()})
        manifest['raster_visuals']+=3
        manifest['diagrams']+=6
        manifest['total_distinct_visuals']=manifest['raster_visuals']+manifest['diagrams']
        manifest['new_overview_boards']=3
        manifest['new_generation_call_cap']=3
        manifest['new_generation_calls_used']=self.data['generation_calls_used']
        manifest['notes'][-1]='Current generation fields describe edition 05. Earlier generation records remain in the preserved edition metadata and illustrated_overview_03 field.'

