"""Compile the user's local prologue into reviewable dialogue and staging data."""
from pathlib import Path
import json
import re

ROOT = Path(__file__).resolve().parents[1]
SOURCE = ROOT / 'content/prologue_source.txt'
PARAGRAPHS = [p.strip() for p in SOURCE.read_text().split('\n\n') if p.strip()]
QUOTES = re.compile(r"(?<!\w)'(?!\s)(.*?)(?<!\s)'(?!\w)")
SPEAKERS = {
    10:'commander',13:'ganoes',14:'commander',15:'ganoes',16:'commander',
    17:'ganoes',18:'commander',19:'ganoes',20:'commander',21:'ganoes',
    22:'commander',23:'ganoes',24:'commander',27:'fiddler',28:'commander',
    29:'fiddler',30:'commander',31:'fiddler',32:'commander',33:'fiddler',
    34:'commander',35:'fiddler',37:'fiddler',38:'commander',39:'fiddler',
    40:'commander',41:'fiddler',42:'ganoes',44:'ganoes',45:'surly',
    46:'commander',49:'commander',51:'surly',52:'commander',53:'surly',
    54:'commander',55:'surly',56:'commander',57:'surly',58:'commander',
    59:'surly',62:'ganoes',63:'commander',64:'ganoes',65:'commander',
    66:'ganoes',69:'ganoes',70:'commander',
}
EXPRESSIONS = {16:'grimace',23:'proud',35:'wry',42:'proud',50:'surprised',
               52:'grimace',59:'cold_smile',64:'scowl',66:'proud',
               68:'proud',69:'surprised',70:'grimace'}

def cue(kind, **kwargs):
    return {'kind':kind, **kwargs}

BEFORE = {
    10:[cue('move',actor='commander',points=[[560,184],[496,184],[496,164],[368,164]],speed=84),cue('face',actor='commander',direction='north')],
    13:[cue('face',actor='ganoes',direction='east')],
    20:[cue('face',actor='commander',direction='west')],
    25:[cue('smoke',strength=0.06,duration=1.0)],
    26:[cue('move',actor='fiddler',points=[[560,184],[496,184],[496,164],[432,164]],speed=124,gait='run'),cue('face',actor='fiddler',direction='north'),cue('face',actor='commander',direction='north')],
    36:[cue('face',actor='commander',direction='east'),cue('face',actor='fiddler',direction='west')],
    43:[cue('face',actor='commander',direction='west'),cue('face',actor='fiddler',direction='west')],
    45:[cue('arrival'),cue('face',actor='commander',direction='south'),cue('face',actor='fiddler',direction='south'),cue('face',actor='ganoes',direction='south')],
    47:[cue('move',actor='fiddler',points=[[496,164],[496,184],[560,184],[560,176]],speed=144,gait='run'),cue('hide',actor='fiddler')],
    50:[cue('expression',actor='ganoes',expression='surprised')],
    60:[cue('departure')],
    61:[cue('face',actor='commander',direction='north'),cue('face',actor='ganoes',direction='north'),cue('pose',actor='ganoes',pose='tiptoe')],
    62:[cue('face',actor='ganoes',direction='east')],
    67:[cue('face',actor='commander',direction='west'),cue('expression',actor='commander',expression='grimace'),cue('wait',seconds=0.8)],
    68:[cue('face',actor='ganoes',direction='north'),cue('pose',actor='ganoes',pose='tiptoe'),cue('expression',actor='ganoes',expression='proud')],
    69:[cue('smoke',strength=0.9,duration=2.5)],
    70:[cue('wait',seconds=1.0),cue('face',actor='commander',direction='north')],
}

# Editorial punctuation only: removing prose dialogue tags changes sentence joins.
# Keys are source paragraph and zero-based quoted-fragment index; all words stay intact.
PUNCTUATION_EDITS = {
    (16,0): (',', '.', 'Terminal dialogue tag removed'),
    (27,0): (',', '.', 'A complete sentence precedes the next spoken sentence'),
    (31,0): (',', '.', 'A complete sentence precedes the next spoken sentence'),
    (33,0): (',', '.', 'A complete sentence precedes the next spoken sentence'),
    (44,0): (',', '.', 'Terminal dialogue tag removed'),
    (53,0): (',', '.', 'A complete sentence precedes the next spoken sentence'),
    (54,0): (',', '.', 'A complete sentence precedes the next spoken sentence'),
    (55,0): (',', '.', 'A complete sentence precedes the next spoken sentence'),
    (62,0): (',', '.', 'Terminal dialogue tag removed'),
    (65,0): (',', '.', 'Terminal dialogue tag removed'),
    (66,0): (',', '', 'Rejoin subject and predicate around a removed dialogue tag'),
    (69,0): (',', '.', 'A complete sentence precedes the next spoken sentence'),
}
corrections=[]

def dialogue_text(index, fragments):
    display=[]
    for number,fragment in enumerate(fragments):
        edit=PUNCTUATION_EDITS.get((index,number))
        if edit:
            old,new,reason=edit
            assert fragment.endswith(old), (index,number,fragment)
            revised=fragment[:-len(old)]+new
            corrections.append({'source_paragraph':index,'fragment':number,'before':fragment,'after':revised,'reason':reason})
            display.append(revised)
        else:
            display.append(fragment)
    return ' '.join(display)

events=[cue('establish'),cue('pose',actor='ganoes',pose='tiptoe')]
spoken=[]
for index,paragraph in enumerate(PARAGRAPHS):
    if index<4:
        continue
    # Source beats drive sound and staging without putting prose on screen.
    events.append(cue('beat',source_paragraph=index))
    events.extend(BEFORE.get(index,[]))
    fragments=QUOTES.findall(paragraph)
    if fragments:
        assert index in SPEAKERS, (index,paragraph)
        spoken.extend(fragments)
        speaker=SPEAKERS[index]
        expression=EXPRESSIONS.get(index,'neutral')
        events.append(cue('say',speaker=speaker,text=dialogue_text(index,fragments),quote_fragments=fragments,expression=expression,source_paragraph=index))
events.append(cue('end'))

payload={'title':'Gardens of the Moon — Prologue', 'source':'prologue_source.txt',
         'paragraphs':PARAGRAPHS,'events':events,'quoted_fragments':len(spoken),
         'notes':'Dialogue only. Spoken words and their order are preserved; punctuation is adapted where prose dialogue tags were removed. Exact source fragments remain in quote_fragments. Non-spoken prose drives staging only. Blocking is interpretive.',
         'punctuation_corrections':corrections}
(ROOT/'content/prologue.json').write_text(json.dumps(payload,ensure_ascii=False,indent=2)+'\n')
(ROOT/'content/dialogue_punctuation_audit.json').write_text(json.dumps({'source':'prologue_source.txt','scope':'All 48 speaking paragraphs / 58 quoted fragments reviewed','policy':'No narrator or internal-monologue boxes. Preserve spoken words, interruptions, and internal commas. Adapt punctuation only at removed prose tags.','corrections':corrections},ensure_ascii=False,indent=2)+'\n')
print(f'Compiled {len(events)} cues, {len(SPEAKERS)} speaking paragraphs, {len(spoken)} exact spoken fragments.')

tiles=[]
for y in range(11):
    row=[]
    for x in range(20):
        if y<3: tile=-1
        elif y==3: tile=(6 if x in (2,18) else 5) if x%2==0 else -1
        elif y==4: tile=4
        else: tile=1 if (x+y)%4==0 else 0
        if x>=16 and y==1: tile=5
        elif x>=16 and 2<=y<=4: tile=4
        row.append(tile)
    tiles.append(row)
layout={'tile_size':32,'columns':20,'rows':11,'tiles':tiles,
        'tile_names':['floor_a','floor_b','floor_c','shadow_floor','wall_face','merlon','broken_merlon','wall_shadow','left_end','right_end','stairs_up','stairs_down','cliff','dark_masonry','chipped_floor','deep_shadow_floor'],
        'stops':{'ganoes':[9,5],'commander':[11,5],'fiddler':[13,5],'surly':[11,7],'claw_left':[10,7],'claw_right':[12,7]},
        'pixel_stops':{'ganoes':[304,164],'commander':[368,164],'fiddler':[432,164],'surly':[368,232],'claw_left':[336,240],'claw_right':[400,240]},
        'soldier_entry':[560,176], 'portal_position':[512,48], 'door_foot':[560,176],
        'notes':'Zero-based columns/rows, origin upper left. Stops identify the tile containing each pixel foot position; characters are not grid locked. Stage positions and routes are art direction, not book-specified geography. No collision map. Surly and her acolytes form up facing the Commander, with Surly centred on his column.'}
(ROOT/'content/prologue_map.json').write_text(json.dumps(layout,indent=2)+'\n')
