"""
BSS Skill: Requirements to Stories
Parses a requirements document (BRD, PRD, specs, or bullet list) and outputs
structured user stories grouped by epic, with acceptance criteria extracted
where available.
"""
import re

text = PROMPT  # injected by BIBIVIDI sandbox wrapper

# ── Already-written stories detection ─────────────────────────────────────────

def extract_already_written_stories(txt):
    """Detect pre-existing user stories in the text."""
    stories = []
    pattern = re.compile(
        r'As an?\s+([^,]+),\s+I\s+(?:want|need|can)\s+([^\.]+?)(?:\s+so\s+that\s+([^\.]+))?\.?'
        r'|En tant que\s+([^,]+),\s+je\s+(?:veux|souhaite|peux)\s+([^\.]+?)(?:\s+afin\s+de\s+([^\.]+)|\s+pour\s+([^\.]+))?\.?',
        re.IGNORECASE,
    )
    for m in pattern.finditer(txt):
        if m.group(1):  # English
            stories.append({
                'persona': m.group(1).strip(),
                'goal': m.group(2).strip(),
                'benefit': (m.group(3) or '').strip(),
            })
        else:  # French
            stories.append({
                'persona': m.group(4).strip(),
                'goal': m.group(5).strip(),
                'benefit': (m.group(6) or m.group(7) or '').strip(),
            })
    return stories


# ── Requirement splitting ──────────────────────────────────────────────────────

def split_requirements(txt):
    """Split text into individual requirement items."""
    lines = txt.splitlines()
    items = []

    # AC / Given-When-Then lines — skip entirely (handled by extract_acceptance_criteria)
    _AC_LINE = re.compile(r'^(?:given|when|then|ac\s*:|acceptance\s+criteria|critères?\s+d)', re.IGNORECASE)

    # Try numbered / bullet / SHALL patterns first
    for line in lines:
        line = line.strip()
        if not line:
            continue
        # Numbered: 1. / 1.1 / REQ-001
        if re.match(r'^(?:\d+(?:\.\d+)*\.?\s+|REQ-\d+\s*[:\-]?\s*)', line, re.IGNORECASE):
            content = re.sub(r'^(?:\d+(?:\.\d+)*\.?\s+|REQ-\d+\s*[:\-]?\s*)', '', line).strip()
            if len(content) >= 15 and not _AC_LINE.match(content):
                items.append(content)
        # Bullet: - / • / *
        elif re.match(r'^[-•*]\s+', line):
            content = re.sub(r'^[-•*]\s+', '', line).strip()
            if len(content) >= 15 and not _AC_LINE.match(content):
                items.append(content)
        # SHALL / MUST / SHOULD lines (English)
        elif re.match(r'(?:the\s+)?(?:system|users?\s+must|app(?:lication)?)\s+(?:shall|must|should|will)\b', line, re.IGNORECASE):
            if len(line) >= 15 and not _AC_LINE.match(line):
                items.append(line)
        # French: Le système doit / L'utilisateur doit pouvoir
        elif re.match(r"(?:le\s+système|l'utilisateur|les\s+utilisateurs)\s+(?:doit|doivent|devra|devront)\b", line, re.IGNORECASE):
            if len(line) >= 15:
                items.append(line)

    # Paragraph fallback when no structured items found
    if not items:
        paragraphs = re.split(r'\n{2,}', txt)
        for para in paragraphs:
            para = para.strip()
            if len(para) >= 15:
                items.append(para[:300])

    return items[:30]


# ── Persona inference ──────────────────────────────────────────────────────────

def infer_persona(req_text):
    """Guess the user persona from keywords in the requirement text."""
    t = req_text.lower()
    if re.search(r'\b(?:admins?|administrators?|managers?)\b', t):
        return 'admin'
    if re.search(r'\b(?:developer|engineer|dev)\b', t):
        return 'developer'
    if re.search(r'\b(?:viewer|reader|guest|visiteur)\b', t):
        return 'visitor'
    if re.search(r'\b(?:owner|operator)\b', t):
        return 'operator'
    if re.search(r'\b(?:user|customer|client|utilisateur)\b', t):
        return 'user'
    return 'user'


# ── Requirement → story conversion ────────────────────────────────────────────

_STRIP_PREFIXES = re.compile(
    r'^(?:'
    # "The system/app/platform shall/must/will [be able to] [allow users to]"
    r'(?:the\s+)?(?:system|app(?:lication)?|platform|service|solution)\s+(?:shall|must|should|will)\s+(?:be\s+able\s+to\s+|allow\s+\w+\s+to\s+)?'
    # "The <noun> shall/must/will" — e.g. "The dashboard shall display"
    r'|the\s+\w+\s+(?:shall|must|should|will)\s+'
    # "<Noun/Plural> must be able to" — catches "Users/Admins/Members must be able to"
    r'|\w+s?\s+must\s+be\s+able\s+to\s+'
    # "<Noun> shall/should/can/may" — "Users shall", "Admins should"
    r'|\w+s?\s+(?:shall|should|can|may)\s+'
    r'|le\s+système\s+(?:doit|devra|doit\s+pouvoir)\s+'
    r"|l'utilisateur\s+(?:doit\s+pouvoir|doit|devra)\s+"
    r'|les\s+utilisateurs\s+(?:doivent|devront)\s+(?:pouvoir\s+)?'
    r')',
    re.IGNORECASE,
)

_STRIP_BULLETS = re.compile(r'^(?:\d+(?:\.\d+)*\.?\s+|REQ-\d+\s*[:\-]?\s*|[-•*]\s+)')


def req_to_story(req):
    """Convert a requirement sentence to user story format."""
    goal = _STRIP_BULLETS.sub('', req).strip()
    goal = _STRIP_PREFIXES.sub('', goal).strip()
    # Strip residual "be able to" at the start
    goal = re.sub(r'^be\s+able\s+to\s+', '', goal, flags=re.IGNORECASE).strip()
    # Lowercase first char for natural flow
    if goal and goal[0].isupper() and not goal[:2].isupper():
        goal = goal[0].lower() + goal[1:]
    persona = infer_persona(req)
    article = 'an' if persona[0] in 'aeiou' else 'a'
    return f'As {article} {persona}, I want to {goal[:100]}'


# ── Acceptance criteria extraction ────────────────────────────────────────────

def extract_acceptance_criteria(txt):
    """Find acceptance criteria blocks."""
    criteria = []

    # Section-based extraction
    section_m = re.search(
        r'(?:acceptance\s+criteria|critères?\s+d[\'e]acceptation|AC\s*:)\s*\n((?:.+\n?){1,20})',
        txt, re.IGNORECASE,
    )
    if section_m:
        block = section_m.group(1)
        for line in block.splitlines():
            line = line.strip()
            if line and len(line) > 5:
                criteria.append(re.sub(r'^[-•*\d.]+\s*', '', line).strip())
                if len(criteria) >= 8:
                    break
        return criteria[:8]

    # Given/When/Then / AC: inline lines
    for line in txt.splitlines():
        line = line.strip()
        if re.match(r'^(?:given|when|then|ac\s*:)\s+.{5,}', line, re.IGNORECASE):
            criteria.append(line)
            if len(criteria) >= 8:
                break

    return criteria[:8]


# ── Epic detection ─────────────────────────────────────────────────────────────

_EPIC_KEYWORDS = [
    ('Authentication',   r'login|auth(?:entication)?|password|sign[- ]in|connexion|mot\s+de\s+passe|register|inscription'),
    ('Dashboard',        r'dashboard|overview|chart|graph|tableau\s+de\s+bord|visualis'),
    ('Notifications',    r'notif(?:ication)?|alert|email|sms|push'),
    ('Search',           r'search|filter|sort|find|recherche|filtrer'),
    ('Reports',          r'report|export|download|pdf|csv|rapport'),
    ('Profile',          r'profile|account|settings|preferences|profil|compte'),
    ('Payments',         r'payment|invoice|billing|subscription|paiement|facture'),
]


def detect_epics(requirements):
    """Group requirements by feature area using keyword detection."""
    buckets = {}
    for req in requirements:
        assigned = False
        t = req.lower()
        for epic, pattern in _EPIC_KEYWORDS:
            if re.search(pattern, t):
                buckets.setdefault(epic, []).append(req)
                assigned = True
                break
        if not assigned:
            buckets.setdefault('General', []).append(req)

    # Keep top 6 epics by size; merge overflow into General
    if len(buckets) > 6:
        sorted_epics = sorted(buckets.items(), key=lambda kv: len(kv[1]), reverse=True)
        top = dict(sorted_epics[:5])
        overflow = [r for name, reqs in sorted_epics[5:] for r in reqs]
        if overflow:
            top.setdefault('General', []).extend(overflow)
        buckets = top

    return buckets


# ── Parse & output ────────────────────────────────────────────────────────────

already_written = extract_already_written_stories(text)

lines = ['=== BIBIVIDI: USER STORIES ===']

if already_written:
    lines.append(f'Requirements: {len(already_written)}')
    lines.append(f'Epics       : 1')
    lines.append('')
    lines.append('[USER STORIES]')
    for idx, s in enumerate(already_written, 1):
        story_line = f'As a {s["persona"]}, I want {s["goal"]}'
        if s['benefit']:
            story_line += f' so that {s["benefit"]}'
        lines.append(f'  US-{idx}: {story_line}')
else:
    raw_reqs = split_requirements(text)
    epics = detect_epics(raw_reqs) if raw_reqs else {}
    ac_items = extract_acceptance_criteria(text)

    total_reqs = sum(len(v) for v in epics.values())
    lines.append(f'Requirements: {total_reqs}')
    lines.append(f'Epics       : {len(epics)}')

    story_counter = 0
    for epic_name, reqs in epics.items():
        lines.append('')
        lines.append(f'[{epic_name.upper()}]')
        for req in reqs:
            story_counter += 1
            story = req_to_story(req)
            lines.append(f'  US-{story_counter}: {story}')
            # Attach up to 3 AC items to the first story that has them
            if ac_items and story_counter == 1:
                for ac in ac_items[:3]:
                    lines.append(f'        AC: {ac}')

lines.append(f'\n=== END (condensed from {len(text)} chars) ===')

print('\n'.join(lines))
