"""Teaching runner: scripted decisions and an in-memory read tool. No AI/API calls.

Time checks are cooperative: they cannot interrupt a blocked callback.
Do not expose real tools or arbitrary code through this demonstration.
"""
import argparse
import json
import math
import time


def scripted(scenario):
    """A stand-in decider; its outputs are authored fixtures, not model outputs."""
    def decide(events):
        if scenario == 'complete' and events:
            return {'action': 'done'}
        if scenario == 'denied':
            return {'action': 'send_email', 'key': 'Harbor'}
        key = str(len(events)) if scenario == 'step-limit' else 'Harbor'
        return {'action': 'read_note', 'key': key}
    return decide


def run(decide, *, max_steps=3, max_repeats=2, seconds=5.0, clock=time.monotonic):
    """Count decision attempts, gate dispatch, and return an inspectable stop report."""
    if any(type(n) is not int or n < 1 for n in (max_steps, max_repeats)):
        raise ValueError('Step and repeat limits must be positive integers.')
    if not isinstance(seconds, (int, float)) or not math.isfinite(seconds) or seconds <= 0:
        raise ValueError('seconds must be positive and finite.')
    events, seen = [], {}
    decisions = 0
    deadline = clock() + seconds

    def report(status):
        return {'status': status, 'decisions': decisions,
                'tool_calls': len(events), 'events': events}

    for _ in range(max_steps):
        if clock() >= deadline:
            return report('time_limit')
        decisions += 1
        # Pass copies so this teaching decider does not accidentally edit history.
        choice = decide([dict(event) for event in events])
        if clock() >= deadline:
            return report('time_limit')
        if not isinstance(choice, dict) or not isinstance(choice.get('action'), str):
            return report('invalid_decision')
        action = choice['action']
        if action == 'done':
            return report('complete')
        # Gate the action before any tool dispatch; no send tool exists here.
        if action != 'read_note':
            return report('denied_action')
        key = choice.get('key')
        if not isinstance(key, str) or not 1 <= len(key) <= 80:
            return report('invalid_decision')
        signature = (action, key)
        if seen.get(signature, 0) >= max_repeats:
            return report('repeat_limit')
        seen[signature] = seen.get(signature, 0) + 1
        # Fake tool: only a dictionary lookup, no filesystem/network access.
        notes = {'Harbor': 'Fictional Harbor note: draft needs review.'}
        result = notes.get(key, 'No fictional note for this key.')
        events.append({'action': action, 'key': key, 'result': result})
        if clock() >= deadline:
            return report('time_limit')
    return report('step_limit')


if __name__ == '__main__':
    parser = argparse.ArgumentParser(description=__doc__)
    parser.add_argument('scenario', choices=['complete', 'step-limit', 'repeat', 'denied'])
    args = parser.parse_args()
    result = run(scripted(args.scenario))
    print(json.dumps(result, indent=2))
    # Distinguish useful partial work from normal completion for shell callers.
    raise SystemExit(0 if result['status'] == 'complete' else 2)
