"""Turn written notes into walkthrough beats.

The designer writes the walkthrough as prose long before easel sees it — Adi's
`designer-notes-pins-homepage.md` carries a dozen-plus beats as title + one
sentence + the element they point at. Entering those one at a time by dragging
boxes over a mockup is retyping a document that already exists, so this parses
the document instead.

The format is deliberately forgiving and close to how the notes are already
written — one beat per blank-line-separated block:

    Your number, in your face
    @.phone
    Front and centre on every page, real size, tap-to-call.

    !Two decisions, that's all
    @.triage
    Pick an opening and a brand level.

  * first line is the TITLE; a leading `!` marks the beat as requiring the
    client's approval
  * a line starting `@` is the CSS SELECTOR into the mockup (optional — a beat
    with no target still reads, it just has no spotlight)
  * a line starting `>` is a DEMO CLICK: `>.injuries` clicks that element when
    the beat opens, and `>.injuries 5` holds it open five seconds before handing
    the design back. `0` means leave it open.
  * everything else is the body, joined into one paragraph

Parsing lives on the server so there is one implementation of the format and it
is testable; the UI only has to offer a textarea.
"""

import re

_BLOCK = re.compile(r"\n\s*\n")

MAX_TITLE = 200
MAX_BODY = 8000
MAX_SELECTOR = 255


class ImportError_(ValueError):
    """Raised with a human sentence — it is shown to whoever pasted the text."""


def parse(text: str) -> list[dict]:
    beats: list[dict] = []
    for raw in _BLOCK.split((text or "").strip()):
        lines = [line.strip() for line in raw.splitlines() if line.strip()]
        if not lines:
            continue
        title = lines[0]
        requires_approval = title.startswith("!")
        if requires_approval:
            title = title[1:].strip()
        if not title:
            raise ImportError_(
                "One of the blocks has no title on its first line.")
        selector = ""
        click = ""
        dismiss = 3
        body_parts: list[str] = []
        for line in lines[1:]:
            if line.startswith("@") and not selector:
                selector = line[1:].strip()
            elif line.startswith(">") and not click:
                spec = line[1:].strip()
                # Trailing integer is the hold, if present.
                bits = spec.rsplit(" ", 1)
                if len(bits) == 2 and bits[1].isdigit():
                    click, dismiss = bits[0].strip(), int(bits[1])
                else:
                    click = spec
            else:
                body_parts.append(line)
        beats.append({
            "title": title[:MAX_TITLE],
            "body_md": " ".join(body_parts)[:MAX_BODY],
            "target_selector": selector[:MAX_SELECTOR],
            "requires_approval": requires_approval,
            "click_selector": click[:MAX_SELECTOR],
            "click_dismiss_seconds": max(0, min(600, dismiss)),
        })
    if not beats:
        raise ImportError_("Nothing to import — the text had no beats in it.")
    return beats
