"""Single-owner documentation for shared and module-specific agent methods."""
from pathlib import Path
from html import escape
import json,os,re
from _paths import ARCH,WIKI,SITE,WORKSPACE,DOCS_URL,workspace_path,citation_link_path
HERE=Path(__file__).resolve().parent
CATALOG=json.loads((HERE/'agent-catalog.json').read_text(encoding='utf-8'))
ENTITIES={e['id']:e for e in CATALOG['entities']}
PROFILE_IDS={p:e['id'] for e in CATALOG['entities'] for p in e.get('runtime_profiles',[])}
PAGES={'common':'50 Agents/Common Capabilities.md','root':'40 Support/Root Agents and Skills.md','data':'40 Support/Agents and Skills.md','configuration':'20 Configuration/Agents and Skills.md','source':'30 Source/Agents and Skills.md','commercial':'45 Commercial/Agents and Skills.md','intake':'Intake.md'}
HEADS={'root':'40 Support/Support Module.md','data':'40 Support/Data and Information Module.md','configuration':'20 Configuration/Configuration Module.md','source':'30 Source/Source Module.md','commercial':'45 Commercial/Assessment and Offers.md'}
TITLES={'common':'Common agents and methods','root':'Support Root — agents and methods','data':'Data & Manipulation — agents and methods','configuration':'Configuration — agents and methods','source':'Customer-specific development — agents and methods','commercial':'Assessment & Offers — agents and methods','intake':'Intake — connector polling and push'}
def anchor(key):return 'cap-'+key.replace('.','-')
def owner(key):
    if key=='platform.intake':return 'intake'
    if key.startswith('common.'):return 'common'
    if key in ('support.root','support.triage','support.skill.classification','support.skill.channel-access'):return 'root'
    if key.startswith(('support.','data.')):return 'data'
    return key.split('.')[0]
def page_rel(key):return PAGES[owner(key)]
def page_name(key):return Path(page_rel(key)).stem
def slug_path(path):return '/'.join(re.sub('[^a-z0-9._-]','',s.lower().replace(' ','-')) for s in path.split('/')).replace('.md','.html')
def entity_url(key):return DOCS_URL+'10-architecture/'+slug_path(page_rel(key))+'#'+anchor(key)
def md_link(path,base):return os.path.relpath(ARCH/path,base).replace('\\','/').replace(' ','%20')
def entity_md(key,base=None):return '['+key+']('+md_link(page_rel(key),base or ARCH/'50 Agents')+'#'+anchor(key)+')'
EXEC_KIND={}
for _m in ('support','data','configuration','source','commercial'):
    for _p in json.loads((ARCH/'contracts/manifests'/(_m+'.json')).read_text(encoding='utf-8'))['profiles']:EXEC_KIND[_p['key']]=_p['execution_kind']
for _p in json.loads((ARCH/'contracts/platform-profiles.json').read_text(encoding='utf-8'))['profiles']:EXEC_KIND[_p['key']]=_p['execution_kind']

def runs_code(e):
    """Code with a contract: a helper a skill calls, or a task the platform runs without a model."""
    if e['kind'] in ('tool','mechanism'):return True
    rps=e.get('runtime_profiles') or []
    return bool(rps) and all(EXEC_KIND.get(r,'llm')=='deterministic' for r in rps)
def node_class(e):
    """Three words. An **agent** runs a model and holds a task. A **skill** is a procedure an agent follows.
    A **tool** is code with a contract — a helper a skill calls, or a task the platform runs on its own."""
    if e['kind']=='skill':return 'skill'
    return 'tool' if runs_code(e) else 'agent'
def by_profile(profile):return ENTITIES[PROFILE_IDS[profile]]
def stage_entities(module,sid):return [ENTITIES[k] for k in CATALOG['stages'][module+'/'+sid]['agents']]
def stage_brief(e):return e['purpose']+' Input: '+'; '.join(e['inputs'])+'. Output: '+'; '.join(e['outputs'])+'.'

def build_catalog_docs():
    for group,rel in PAGES.items():
        base=(ARCH/rel).parent;base.mkdir(parents=True,exist_ok=True)
        out=['# '+TITLES[group],'']
        if group in HEADS:out += ['[Module overview]('+md_link(HEADS[group],base)+') · [Common methods]('+md_link(PAGES['common'],base)+')','']
        out += ['**[P]** A skill is a complete procedure that can use LLM judgment and optional executable helpers. A tool is runnable source and/or a binary inside that procedure. Connector calls, state transitions, agent calls and permission checks are runtime access, not extra learned skills or imaginary binaries. Only the methods and segments selected for the actual case run.','']
        if group=='common':
            out += ['## Module-owned definitions','']
            for key,head in HEADS.items():out += ['- ['+TITLES[key].split(' — ')[0]+']('+md_link(head,base)+') — [agent and method subpage]('+md_link(PAGES[key],base)+').']
            out += ['', '**[P]** This catalogue defines only shared capabilities. Domain-specific content stays with the module that owns it. A common definition is reused through separately scoped tasks, not shared transcripts or authority.','']
        for kind,title in [('mechanism','Connector mechanism'),('agent','Agents'),('skill','Domain procedures'),('tool','Executable helpers used by procedures')]:
            rows=[e for e in CATALOG['entities'] if owner(e['id'])==group and e['kind']==kind]
            if not rows:continue
            out += ['## '+title,'']
            for e in rows:
                out += ['<a id="'+anchor(e['id'])+'"></a>','','### '+e['id']+' — '+e['label'],'','**[P] Function.** '+e['purpose'],'',
                        '| Contract | Content |','|---|---|']
                for field,label in [('inputs','Input brief'),('outputs','Output brief'),('boundaries','Scope and restrictions'),('memory','Memory / ownership'),('instructions','Role instructions'),('operations','Runtime / connector access')]:
                    if e.get(field):out.append('| '+label+' | '+'<br>'.join(escape(s).replace('|','&#124;') for s in e[field])+' |')
                if e.get('context_contract'):out.append('| Context and task continuity | '+escape(e['context_contract']).replace('|','&#124;')+' |')
                if e.get('consults'):out.append('| May consult | '+'<br>'.join(escape(c['domain'])+' — '+escape(c['asks'])+(' (a change is Hd)' if c.get('handover') else '') for c in e['consults'])+' |')
                if e.get('eval_set'):out.append('| Eval set | '+chr(96)+e['eval_set']+chr(96)+' — declared by the owning module manifest or platform-profiles.json; green before publication ([Agents § 0.1]('+md_link('50 Agents/Agents.md',base)+'#01-the-fields)) |')
                if e.get('experience_strategy'):out.append('| Experience strategy | '+e['experience_strategy']+'; [retrieval and index contract]('+md_link('50 Agents/Experience Strategies.md',base)+') |')
                if e.get('runtime_profiles'):out.append('| Runtime profile identity | '+', '.join(chr(96)+s+chr(96) for s in e['runtime_profiles'])+' |')
                out += ['']
                if e.get('skills'):out += ['**Methods used.** '+', '.join(entity_md(k,base) for k in e['skills'])+'.','']
                if e.get('skill_scopes'):out += ['**Selected segments.** '+' '.join(k+': '+v for k,v in e['skill_scopes'].items()),'']
                if e.get('method_selection'):
                    sel=e['method_selection'];out += ['**Case-specific method selection.** '+sel['purpose']+'. '+sel['rule'],'',
                    ', '.join(entity_md(k,base) for k in sel['candidates'])+'.','']
                if kind=='skill' and e.get('tools'):out += ['**Optional executable helpers.** '+', '.join(entity_md(k,base) for k in e['tools'])+'. Helper availability and role scope are checked separately.','']
                if kind=='agent' and e.get('tools'):out += ['**Executable helper scope.** '+', '.join(entity_md(k,base) for k in e['tools'])+'. These are used through the selected method, not an extra workflow.','']
                if e.get('executable'):
                    ex=e['executable'];out+=['**Executable evidence.** '+ex['status'],'']
                    for field in ('source','binary'):
                        if ex.get(field):out+=['- '+field.title()+': ['+Path(ex[field]).name+']('+os.path.relpath(citation_link_path(ex[field]),base).replace('\\','/').replace(' ','%20')+').']
                    if ex.get('invocation'):out+=['','Invocation: '+chr(96)+ex['invocation']+chr(96)+'.']
                    out+=['']
                out += ['**Source basis.** '+', '.join('['+s['label']+']('+os.path.relpath(citation_link_path(s['path']),base).replace('\\','/').replace(' ','%20')+')' for s in e['sources'])+'.','',
                        '**[P] Status.** '+e['evidence_status'],'']
                for old,new in CATALOG.get('aliases',{}).items():
                    if new==e['id'] and old not in ENTITIES:
                        out += ['<a id="'+anchor(old)+'"></a>','','Former reference '+chr(96)+old+chr(96)+' resolves to this responsibility. Existing task/profile revisions are not rewritten; new work uses '+chr(96)+e['id']+chr(96)+'.','']
        (ARCH/rel).write_text('\n'.join(out),encoding='utf-8')
    refresh_module_agent_details()
    base=ARCH/'50 Agents';out=['# Module capability index','','Module-specific definitions live on their module subpages. Shared definitions live in [Common agents and methods](Common%20Capabilities.md).','']
    for key,head in HEADS.items():out+=['- ['+TITLES[key]+']('+md_link(PAGES[key],base)+') — [module overview]('+md_link(head,base)+').']
    out+=['','## Existing direct links','']
    for e in CATALOG['entities']:out+=['<a id="'+anchor(e['id'])+'"></a>','',entity_md(e['id'],base),'']
    for old,new in CATALOG.get('aliases',{}).items():
        if old not in ENTITIES and new in ENTITIES:out+=['<a id="'+anchor(old)+'"></a>','',entity_md(new,base),'']
    (base/'Agent Catalogue.md').write_text('\n'.join(out),encoding='utf-8')

def refresh_module_agent_details():
    """Visible module entry points, generated from the same owned definitions."""
    for group,rel in HEADS.items():
        p=ARCH/rel;t=p.read_text(encoding='utf-8')
        rows=['<!-- BEGIN MODULE AGENT DETAILS -->','## Agents in this module','',
              '**[P]** Open an agent below for its function, input/output brief, task context, methods and restrictions. These descriptions are generated from the same catalogue as the [module agent subpage]('+md_link(PAGES[group],p.parent)+'). The memory domains listed later are knowledge stores, not additional agents.','',
              '**Task contract.** Start only when the selected work and prerequisites are ready. Pin the profile, request/plan and source revisions; provide the original objective, relevant evidence, decisions, uncertainties and dependencies. The agent proposes a plan to its immediate parent, waits for that confirmation before dependent work, and returns a result or a precise question. Parent confirmation does not replace an effect gate. [Runtime and context contract]('+md_link('50 Agents/Agent Runtime.md',p.parent)+').','']
        for i,e in enumerate(x for x in CATALOG['entities'] if x['kind']=='agent' and owner(x['id'])==group):
            rows += ['<details class="agent-detail"'+(' open' if i==0 else '')+' markdown="1">',
                     '<summary>'+escape(e['label'])+' · '+escape(e['id'])+'</summary>','',e['purpose'],'',
                     '| Agent contract | Detail |','|---|---|']
            for field,label in [('inputs','Input'),('outputs','Output'),('instructions','Task instructions'),('memory','Memory'),('boundaries','Restrictions'),('operations','Runtime access')]:
                if e.get(field):rows += ['| '+label+' | '+'<br>'.join(escape(s).replace('|','&#124;') for s in e[field])+' |']
            if e.get('context_contract'):rows+=['| Context continuity | '+escape(e['context_contract']).replace('|','&#124;')+' |']
            if e.get('consults'):rows+=['| May consult | '+'<br>'.join(escape(c['domain'])+' — '+escape(c['asks'])+(' (a change is Hd)' if c.get('handover') else '') for c in e['consults'])+' |']
            if e.get('eval_set'):rows+=['| Eval set | '+chr(96)+e['eval_set']+chr(96)+' |']
            rows+=['','**Methods.** '+(', '.join(entity_md(k,p.parent) for k in e.get('skills',[])) or 'Uses the selected case method and runtime protocol')+'.','']
            if e.get('skill_scopes'):rows+=['**Selected method segments.** '+' '.join(e['skill_scopes'].values()),'']
            if e.get('method_selection'):rows+=['**Method selection.** '+e['method_selection']['purpose']+'. '+e['method_selection']['rule'],'']
            rows+=['[Full technical definition, sources and executable bindings]('+md_link(page_rel(e['id']),p.parent)+'#'+anchor(e['id'])+').','','</details>','']
        if group=='data':rows+=['[Full recurring-operation inventory — investigation, methods and automation candidates](Frequent%20Operations.md#operation-inventory).','']
        rows+=['<!-- END MODULE AGENT DETAILS -->','']
        block='\n'.join(rows)
        if '<!-- BEGIN MODULE AGENT DETAILS -->' in t:
            t=re.sub(r'<!-- BEGIN MODULE AGENT DETAILS -->.*?<!-- END MODULE AGENT DETAILS -->\n?',lambda m:block,t,flags=re.S)
        else:
            index=t.find('## 0. The agents')
            if index<0:index=t.find('\n## ')
            if index<0:index=t.find('\n')+1
            t=t[:index]+block+'\n'+t[index:]
        p.write_text(t,encoding='utf-8')

def refresh_module_agent_tables():
    for module,folder in [('support','40 Support'),('configuration','20 Configuration'),('source','30 Source')]:
        p=ARCH/folder/'Components.md';t=p.read_text(encoding='utf-8')
        a=t.index('<!-- BEGIN CURRENT AGENT INDEX -->');b=t.index('<!-- END CURRENT AGENT INDEX -->',a)+len('<!-- END CURRENT AGENT INDEX -->')
        group='data' if module=='support' else module
        keys=[e for e in CATALOG['entities'] if e['kind']=='agent' and owner(e['id'])==group]
        rows=['<!-- BEGIN CURRENT AGENT INDEX -->','| Agent reference | Role |','|---|---|']
        for e in keys:rows+=['| '+entity_md(e['id'],p.parent)+' | '+e['label']+' |']
        rows+=['<!-- END CURRENT AGENT INDEX -->']
        t=t[:a]+'\n'.join(rows)+t[b:]
        a=t.index('## 4.');b=t.index('## 5.',a)
        t=t[:a]+'## 4. Methods and executable helpers\n\n**[P]** The [module method subpage](Agents%20and%20Skills.md) defines the procedures and their optional source/binary helpers. Runtime and connector operations are implementation mechanisms; they are not separately counted as learned skills or tools.\n\n'+t[b:]
        p.write_text(t,encoding='utf-8')
    for p in ARCH.rglob('*.md'):
        if p.name in ('Agent Catalogue.md','Common Capabilities.md','Agents and Skills.md','Root Agents and Skills.md','Intake.md'):continue
        t=p.read_text(encoding='utf-8')
        def repl(m):
            key=CATALOG.get('aliases',{}).get(m.group(1),m.group(1))
            return entity_md(key,p.parent) if key in ENTITIES else m.group(0)
        t=re.sub(r'\[(?:\x60)?([a-z][a-z0-9_.-]+)(?:\x60)?\]\([^)]*(?:Agent%20Catalogue|Common%20Capabilities)\.md#[^)]+\)',repl,t)
        p.write_text(t,encoding='utf-8')
