# ============================================================================
# Replanning Poetry HTN Domain
# Neuro-Symbolic Poetry Generation with Steering and Revision
# ============================================================================
#
# MOTIVATION:
# Anthropic's "Planning in Poems" (March 2025) discovered that injecting an
# alternative planned word causes Claude 3.5 Haiku to RESTRUCTURE THE ENTIRE
# LINE in 70% of test poems. The planned end-word acts as a "control knob"
# for the whole line — changing it doesn't just patch the ending, it cascades
# through the entire downstream text.
#
# This domain makes that revision behavior EXPLICIT by adding an evaluation
# step after initial generation. When evaluation fails (the line needs a
# better target), the planner backtracks and tries a revision path that
# steers to a new target word and completely regenerates the line.
#
# KEY DIFFERENCE FROM STRUCTURED_POETRY:
# Structured poetry uses a 3-action cycle per rhymed line:
#   a_select_rhyme_target -> a_generate_line -> a_verify_line
#
# This domain adds a 4th step (evaluation + optional revision):
#   a_select_rhyme_target -> a_generate_line -> a_verify_line
#     -> m_evaluate_and_replan (may steer + regenerate + re-verify)
#
# BACKTRACKING MECHANISM:
# m_evaluate_and_replan has TWO registered methods:
#   1. m_accept_line    (tried first, uses a_evaluate_line which may fail)
#   2. m_revise_line    (fallback, steers target + regenerates + re-verifies)
#
# When a_evaluate_line fails for lines requiring revision, the planner
# backtracks from m_accept_line to m_revise_line — the same mechanism
# used in backtracking_poetry but at the evaluation level.
#
# REVISION TRIGGER:
# a_initialize_poem computes lines_requiring_revision from the rhyme scheme:
# subsequent uses of each rhyme label require revision. This models the
# paper's finding that imposing a rhyme constraint causes restructuring.
#
# ARCHITECTURE:
#   HTN Planner (GTPyhop)     ->  structural plan (form, rhyme, meter)
#   LLM Server (MCP)          ->  text generation, line evaluation
#   Phonetics Server (MCP)    ->  rhyme target selection/steering, verification
#
# SUPPORTED FORMS:
#   - Rhyming couplet (AA)
#   - Limerick (AABBA, anapestic meter)
#   - Haiku (5-7-5 syllables, no rhyme)
#   - Shakespearean sonnet (ABAB CDCD EFEF GG, iambic pentameter)
#
# PLAN LENGTH BY FORM:
#   Lines without revision: 4 actions (select + generate + verify + evaluate)
#   Lines with revision:    6 actions (select + generate + verify + steer + generate + verify)
#   Free lines (haiku):     2 actions (generate + verify)
#
#   Couplet:  2 + (1x4 + 1x6) = 12 actions
#   Limerick: 2 + (2x4 + 3x6) = 28 actions
#   Haiku:    2 + (3x2)        = 8 actions
#   Sonnet:   2 + (7x4 + 7x6) = 72 actions
#
# ============================================================================

# ============================================================================
# FILE ORGANIZATION
# ----------------------------------------------------------------------------
# This file is organized into the following sections:
#   - Imports (with secure path handling)
#   - Domain (1)
#   - Constants
#   - State Property Map (Replanning Poetry Workflow)
#   - Actions (8)
#   - Methods (10)
# ============================================================================

# ============================================================================
# IMPORTS
# ============================================================================

import sys
import os
from typing import Optional, Union, List, Tuple, Dict

# ============================================================================
# GTPYHOP IMPORT (with graceful degradation for direct imports)
# ============================================================================

try:
    import gtpyhop
    from gtpyhop import Domain, State, set_current_domain, declare_actions, declare_task_methods
except ImportError:
    # Graceful degradation: supports direct domain.py import (unsupported but functional)
    sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', '..', '..', '..'))
    import gtpyhop
    from gtpyhop import Domain, State, set_current_domain, declare_actions, declare_task_methods

# ============================================================================
# DOMAIN
# ============================================================================
the_domain = Domain("replanning_poetry")
set_current_domain(the_domain)

# ============================================================================
# CONSTANTS -- Poetic Form Specifications
# ============================================================================

FORM_SPECS = {
    "couplet": {
        "num_lines": 2,
        "rhyme_scheme": ["A", "A"],
        "meter": "iambic_tetrameter",
        "syllables_per_line": [8, 8],
    },
    "limerick": {
        "num_lines": 5,
        "rhyme_scheme": ["A", "A", "B", "B", "A"],
        "meter": "anapestic",
        "syllables_per_line": [8, 8, 5, 5, 8],
    },
    "haiku": {
        "num_lines": 3,
        "rhyme_scheme": [None, None, None],
        "meter": "syllabic",
        "syllables_per_line": [5, 7, 5],
    },
    "sonnet": {
        "num_lines": 14,
        "rhyme_scheme": [
            "A", "B", "A", "B",   # quatrain 1
            "C", "D", "C", "D",   # quatrain 2
            "E", "F", "E", "F",   # quatrain 3
            "G", "G",             # couplet
        ],
        "meter": "iambic_pentameter",
        "syllables_per_line": [10] * 14,
    },
}

# ============================================================================
# STATE PROPERTY MAP (Replanning Poetry Workflow)
# ----------------------------------------------------------------------------
# Legend:
#  - (E) Created/modified by the action (Effects)
#  - (P) Consumed/checked by the action (Preconditions/State checks)
#  - [ENABLER] Property acts as a workflow gate for subsequent steps
#  - [DATA]    Informational/data container
#
# Server 1: phonetics-server (Rhyme Selection, Steering & Verification)
# Server 2: llm-server (Text Generation & Line Evaluation)
#
# --- INITIALIZATION ---
# a_initialize_poem
#  (P) poem_form: str [DATA] - poetic form name
#  (P) topic: str [DATA] - poem topic/theme
#  (E) form_spec: Dict [DATA] - full form specification
#  (E) rhyme_registry: Dict[str, str] [DATA] - maps rhyme labels to target words
#  (E) lines: List[str] [DATA] - generated lines (initially empty)
#  (E) line_targets: List[Optional[str]] [DATA] - planned end-words per line
#  (E) lines_requiring_revision: set [DATA] - computed from rhyme scheme
#  (E) poem_initialized: True [ENABLER]
#
# --- PHONETICS SERVER ACTIONS (Server 1) ---
# a_select_rhyme_target
#  (P) poem_initialized == True [ENABLER]
#  (P) rhyme_registry: Dict [DATA]
#  (E) line_targets[line_idx]: str [DATA] - selected rhyme target word
#  (E) rhyme_registry[label]: str [DATA] - registered rhyme word
#  (E) rhyme_target_selected[line_idx]: True [ENABLER]
#
# a_verify_line
#  (P) line_generated[line_idx] == True [ENABLER]
#  (E) line_verified[line_idx]: True [ENABLER]
#  (E) verification_errors[line_idx]: List[str] [DATA]
#
# a_steer_target
#  (P) line_verified[line_idx] == True [ENABLER] (first pass complete)
#  (E) line_targets[line_idx]: str [DATA] - replacement target word
#  (E) line_generated[line_idx]: False [DATA] - reset for regeneration
#  (E) line_verified[line_idx]: False [DATA] - reset for re-verification
#  (E) line_steered[line_idx]: True [ENABLER]
#
# --- LLM SERVER ACTIONS (Server 2) ---
# a_generate_line
#  (P) poem_initialized == True [ENABLER]
#  (P) rhyme_target_selected[line_idx] == True [ENABLER]
#  (E) lines[line_idx]: str [DATA] - generated line text
#  (E) line_generated[line_idx]: True [ENABLER]
#
# a_generate_line_no_rhyme
#  (P) poem_initialized == True [ENABLER]
#  (E) lines[line_idx]: str [DATA]
#  (E) line_generated[line_idx]: True [ENABLER]
#
# a_evaluate_line
#  (P) line_verified[line_idx] == True [ENABLER]
#  (E) line_evaluated[line_idx]: True [ENABLER]
#  NOTE: FAILS (returns False) when line_idx in lines_requiring_revision.
#        This triggers backtracking from m_accept_line to m_revise_line.
#
# --- ASSEMBLY ---
# a_assemble_poem
#  (P) all line_verified[i] == True [ENABLER]
#  (E) final_poem: str [DATA] - assembled poem text
#  (E) poem_complete: True [ENABLER]
# ============================================================================


# ============================================================================
# ACTIONS (8)
# ============================================================================

def a_initialize_poem(state: State, poem_form: str, topic: str) -> Union[State, bool]:
    """
    Class: Action

    MCP_Tool: local:initialize_poem

    Action signature:
        a_initialize_poem(state, poem_form, topic)

    Action parameters:
        poem_form: Poetic form ('couplet', 'limerick', 'haiku', 'sonnet')
        topic: Theme or subject of the poem (e.g., 'autumn', 'the sea')

    Action purpose:
        Initialize the poem state with form specification, empty line buffers,
        rhyme registry, and compute which lines require revision

    Preconditions:
        - poem_form must be a recognized form

    Effects:
        - Form specification loaded (state.form_spec) [DATA]
        - Rhyme registry initialized (state.rhyme_registry) [DATA]
        - Line buffers initialized (state.lines, state.line_targets) [DATA]
        - Lines requiring revision computed (state.lines_requiring_revision) [DATA]
        - Poem initialized flag set (state.poem_initialized) [ENABLER]

    Returns:
        Updated state if successful, False otherwise
    """
    # BEGIN: Type Checking
    if not isinstance(state, State): return False
    if not isinstance(poem_form, str): return False
    if not isinstance(topic, str): return False
    # END: Type Checking

    # BEGIN: State-Type Checks
    if not poem_form.strip(): return False
    if not topic.strip(): return False
    if poem_form not in FORM_SPECS: return False
    # END: State-Type Checks

    # BEGIN: Preconditions
    # No state preconditions -- this is the initialization action
    # END: Preconditions

    # BEGIN: Effects
    spec = FORM_SPECS[poem_form]

    # [DATA] Form specification
    state.poem_form = poem_form
    state.topic = topic
    state.form_spec = spec
    state.num_lines = spec["num_lines"]
    state.rhyme_scheme = spec["rhyme_scheme"]
    state.meter = spec["meter"]
    state.syllables_per_line = spec["syllables_per_line"]

    # [DATA] Rhyme registry: maps rhyme labels (A, B, C...) to target words
    state.rhyme_registry = {}

    # [DATA] Line buffers
    state.lines = [""] * spec["num_lines"]
    state.line_targets = [None] * spec["num_lines"]

    # [DATA] Compute which lines require revision from rhyme scheme:
    # subsequent uses of each rhyme label require revision
    lines_requiring_revision = set()
    label_first_seen = {}
    for i, label in enumerate(spec["rhyme_scheme"]):
        if label is not None:
            if label in label_first_seen:
                lines_requiring_revision.add(i)
            else:
                label_first_seen[label] = i
    state.lines_requiring_revision = lines_requiring_revision

    # [DATA] Tracking dictionaries
    state.rhyme_target_selected = {}
    state.line_generated = {}
    state.line_verified = {}
    state.verification_errors = {}
    state.line_evaluated = {}
    state.line_steered = {}

    # [ENABLER] Gates all subsequent actions
    state.poem_initialized = True
    # END: Effects

    return state


def a_select_rhyme_target(state: State, line_idx: int, rhyme_label: str) -> Union[State, bool]:
    """
    Class: Action

    MCP_Tool: phonetics_server:select_rhyme_target

    Action signature:
        a_select_rhyme_target(state, line_idx, rhyme_label)

    Action parameters:
        line_idx: Zero-based index of the line to plan a rhyme target for
        rhyme_label: Rhyme group label (e.g., 'A', 'B', 'C')

    Action purpose:
        Select or look up a rhyme target word for a line (forward planning).
        If the rhyme label already has a registered word, select a word that
        rhymes with it. If this is the first line with this label, select a
        semantically appropriate word and register it.

    Preconditions:
        - Poem must be initialized (state.poem_initialized)
        - line_idx must be within range

    Effects:
        - Target word stored (state.line_targets[line_idx]) [DATA]
        - Rhyme registry updated (state.rhyme_registry[label]) [DATA]
        - Rhyme target selected flag (state.rhyme_target_selected[line_idx]) [ENABLER]

    Returns:
        Updated state if successful, False otherwise
    """
    # BEGIN: Type Checking
    if not isinstance(state, State): return False
    if not isinstance(line_idx, int): return False
    if not isinstance(rhyme_label, str): return False
    # END: Type Checking

    # BEGIN: State-Type Checks
    if line_idx < 0: return False
    if not rhyme_label.strip(): return False
    # END: State-Type Checks

    # BEGIN: Preconditions
    if not (hasattr(state, 'poem_initialized') and state.poem_initialized):
        return False
    if line_idx >= state.num_lines:
        return False
    # END: Preconditions

    # BEGIN: Effects
    # [DATA] Determine if this is the first or subsequent use of the rhyme label.
    # At planning time, we store placeholder targets.
    # At execution time, the MCP phonetics server provides real words.
    if rhyme_label in state.rhyme_registry:
        # Subsequent line: must rhyme with the registered word
        existing_word = state.rhyme_registry[rhyme_label]
        # Placeholder: actual rhyming word selected by phonetics server
        state.line_targets[line_idx] = f"${{rhymes_with:{existing_word}}}"
    else:
        # First line with this label: select a topic-appropriate word
        state.line_targets[line_idx] = f"${{topic_word:{state.topic}:{rhyme_label}}}"
        state.rhyme_registry[rhyme_label] = f"${{registered:{rhyme_label}}}"

    # [ENABLER] Gates a_generate_line for this line
    state.rhyme_target_selected[line_idx] = True
    # END: Effects

    return state


def a_generate_line(state: State, line_idx: int, target_syllables: int) -> Union[State, bool]:
    """
    Class: Action

    MCP_Tool: llm_server:generate_line

    Action signature:
        a_generate_line(state, line_idx, target_syllables)

    Action parameters:
        line_idx: Zero-based index of the line to generate
        target_syllables: Target syllable count for the line

    Action purpose:
        Generate a line of poetry using an LLM, constrained by the planned
        rhyme target word and syllable count. The LLM receives the poem
        context (topic, preceding lines, target end-word) and produces
        text that naturally ends with the target word.

    Preconditions:
        - Poem must be initialized (state.poem_initialized)
        - Rhyme target must be selected for this line (state.rhyme_target_selected[line_idx])

    Effects:
        - Generated line text stored (state.lines[line_idx]) [DATA]
        - Line generated flag set (state.line_generated[line_idx]) [ENABLER]

    Returns:
        Updated state if successful, False otherwise
    """
    # BEGIN: Type Checking
    if not isinstance(state, State): return False
    if not isinstance(line_idx, int): return False
    if not isinstance(target_syllables, int): return False
    # END: Type Checking

    # BEGIN: State-Type Checks
    if line_idx < 0: return False
    if target_syllables <= 0: return False
    # END: State-Type Checks

    # BEGIN: Preconditions
    if not (hasattr(state, 'poem_initialized') and state.poem_initialized):
        return False
    if not (hasattr(state, 'rhyme_target_selected') and
            state.rhyme_target_selected.get(line_idx)):
        return False
    # END: Preconditions

    # BEGIN: Effects
    # [DATA] Line text (populated by LLM server at runtime)
    # The LLM receives: topic, preceding lines, target word, syllable count, meter
    state.lines[line_idx] = (
        f"${{llm_generate:topic={state.topic},"
        f"target={state.line_targets[line_idx]},"
        f"syllables={target_syllables},"
        f"meter={state.meter},"
        f"context={state.lines[:line_idx]}}}"
    )

    # [ENABLER] Gates a_verify_line for this line
    state.line_generated[line_idx] = True
    # END: Effects

    return state


def a_generate_line_no_rhyme(state: State, line_idx: int, target_syllables: int) -> Union[State, bool]:
    """
    Class: Action

    MCP_Tool: llm_server:generate_line_free

    Action signature:
        a_generate_line_no_rhyme(state, line_idx, target_syllables)

    Action parameters:
        line_idx: Zero-based index of the line to generate
        target_syllables: Target syllable count for the line

    Action purpose:
        Generate a line of poetry without a rhyme constraint (e.g., haiku lines).
        Only syllable count and thematic coherence are enforced. No evaluation
        or revision is needed for unrhymed lines.

    Preconditions:
        - Poem must be initialized (state.poem_initialized)

    Effects:
        - Generated line text stored (state.lines[line_idx]) [DATA]
        - Line generated flag set (state.line_generated[line_idx]) [ENABLER]

    Returns:
        Updated state if successful, False otherwise
    """
    # BEGIN: Type Checking
    if not isinstance(state, State): return False
    if not isinstance(line_idx, int): return False
    if not isinstance(target_syllables, int): return False
    # END: Type Checking

    # BEGIN: State-Type Checks
    if line_idx < 0: return False
    if target_syllables <= 0: return False
    # END: State-Type Checks

    # BEGIN: Preconditions
    if not (hasattr(state, 'poem_initialized') and state.poem_initialized):
        return False
    # END: Preconditions

    # BEGIN: Effects
    # [DATA] Line text (populated by LLM server at runtime)
    state.lines[line_idx] = (
        f"${{llm_generate_free:topic={state.topic},"
        f"syllables={target_syllables},"
        f"context={state.lines[:line_idx]}}}"
    )

    # [ENABLER] Gates a_verify_line for this line
    state.line_generated[line_idx] = True
    # END: Effects

    return state


def a_verify_line(state: State, line_idx: int) -> Union[State, bool]:
    """
    Class: Action

    MCP_Tool: phonetics_server:verify_line

    Action signature:
        a_verify_line(state, line_idx)

    Action parameters:
        line_idx: Zero-based index of the line to verify

    Action purpose:
        Verify that a generated line satisfies its constraints: syllable count,
        meter pattern, and rhyme (if applicable). Uses the phonetics server
        for syllable counting and rhyme checking. May be called twice on the
        same line: once after initial generation, once after revision.

    Preconditions:
        - Line must be generated (state.line_generated[line_idx])

    Effects:
        - Line verified flag set (state.line_verified[line_idx]) [ENABLER]
        - Any verification errors recorded (state.verification_errors[line_idx]) [DATA]

    Returns:
        Updated state if successful, False otherwise
    """
    # BEGIN: Type Checking
    if not isinstance(state, State): return False
    if not isinstance(line_idx, int): return False
    # END: Type Checking

    # BEGIN: State-Type Checks
    if line_idx < 0: return False
    # END: State-Type Checks

    # BEGIN: Preconditions
    if not (hasattr(state, 'line_generated') and
            state.line_generated.get(line_idx)):
        return False
    # END: Preconditions

    # BEGIN: Effects
    # [DATA] Verification result (populated by phonetics server at runtime)
    # Checks: syllable count, meter, rhyme match (if rhymed line)
    state.verification_errors[line_idx] = []  # Empty = passed

    # [ENABLER] Gates evaluation or assembly
    state.line_verified[line_idx] = True
    # END: Effects

    return state


def a_evaluate_line(state: State, line_idx: int) -> Union[State, bool]:
    """
    Class: Action

    MCP_Tool: llm_server:evaluate_line

    Action signature:
        a_evaluate_line(state, line_idx)

    Action parameters:
        line_idx: Zero-based index of the line to evaluate

    Action purpose:
        Evaluate whether a verified line's quality is acceptable. Lines that
        must rhyme with an existing word (subsequent uses of a rhyme label)
        are deemed to need steering to a better target. This models the
        paper's finding that imposing a rhyme constraint causes the model
        to restructure the line in 70% of cases.

        FAILS (returns False) when the line requires revision, triggering
        HTN backtracking from m_accept_line to m_revise_line.

    Preconditions:
        - Line must be verified (state.line_verified[line_idx])
        - Line must NOT require revision (line_idx not in lines_requiring_revision)

    Effects:
        - Line evaluated flag set (state.line_evaluated[line_idx]) [ENABLER]

    Returns:
        Updated state if line accepted, False if revision needed (triggers backtracking)
    """
    # BEGIN: Type Checking
    if not isinstance(state, State): return False
    if not isinstance(line_idx, int): return False
    # END: Type Checking

    # BEGIN: State-Type Checks
    if line_idx < 0: return False
    # END: State-Type Checks

    # BEGIN: Preconditions
    if not (hasattr(state, 'line_verified') and
            state.line_verified.get(line_idx)):
        return False

    # BACKTRACKING TRIGGER: fail if this line requires revision
    if (hasattr(state, 'lines_requiring_revision') and
            line_idx in state.lines_requiring_revision):
        return False  # Line needs revision -- triggers backtracking
    # END: Preconditions

    # BEGIN: Effects
    # [ENABLER] Line quality accepted
    state.line_evaluated[line_idx] = True
    # END: Effects

    return state


def a_steer_target(state: State, line_idx: int, rhyme_label: str) -> Union[State, bool]:
    """
    Class: Action

    MCP_Tool: phonetics_server:steer_target

    Action signature:
        a_steer_target(state, line_idx, rhyme_label)

    Action parameters:
        line_idx: Zero-based index of the line to steer
        rhyme_label: Rhyme group label (e.g., 'A', 'B', 'C')

    Action purpose:
        Replace the current rhyme target with an alternative word, then reset
        line generation and verification flags to enable complete regeneration.
        This models the paper's "control knob" finding: changing the planned
        end-word causes the entire line to be restructured, not just the ending.

    Preconditions:
        - Line must be verified from the first pass (state.line_verified[line_idx])

    Effects:
        - Target word replaced with alternative (state.line_targets[line_idx]) [DATA]
        - Line generated flag reset (state.line_generated[line_idx]) [DATA]
        - Line verified flag reset (state.line_verified[line_idx]) [DATA]
        - Line steered flag set (state.line_steered[line_idx]) [ENABLER]

    Returns:
        Updated state if successful, False otherwise
    """
    # BEGIN: Type Checking
    if not isinstance(state, State): return False
    if not isinstance(line_idx, int): return False
    if not isinstance(rhyme_label, str): return False
    # END: Type Checking

    # BEGIN: State-Type Checks
    if line_idx < 0: return False
    if not rhyme_label.strip(): return False
    # END: State-Type Checks

    # BEGIN: Preconditions
    if not (hasattr(state, 'line_verified') and
            state.line_verified.get(line_idx)):
        return False
    # END: Preconditions

    # BEGIN: Effects
    # [DATA] Replace target with an alternative rhyme word
    # At execution time, the phonetics server selects a different word
    # that still rhymes with the registered word for this label
    existing_word = state.rhyme_registry.get(rhyme_label, "")
    state.line_targets[line_idx] = (
        f"${{steered_rhyme:{existing_word}:topic={state.topic}}}"
    )

    # [DATA] Reset generation flags for complete regeneration
    state.line_generated[line_idx] = False
    state.line_verified[line_idx] = False

    # [ENABLER] Gates regeneration in the revision path
    state.line_steered[line_idx] = True
    # END: Effects

    return state


def a_assemble_poem(state: State) -> Union[State, bool]:
    """
    Class: Action

    MCP_Tool: local:assemble_poem

    Action signature:
        a_assemble_poem(state)

    Action parameters:
        None

    Action purpose:
        Assemble all verified lines into the final poem text

    Preconditions:
        - All lines must be verified

    Effects:
        - Final poem text assembled (state.final_poem) [DATA]
        - Poem complete flag set (state.poem_complete) [ENABLER]

    Returns:
        Updated state if successful, False otherwise
    """
    # BEGIN: Type Checking
    if not isinstance(state, State): return False
    # END: Type Checking

    # BEGIN: State-Type Checks
    # No additional parameters to validate
    # END: State-Type Checks

    # BEGIN: Preconditions
    if not hasattr(state, 'line_verified'):
        return False
    for i in range(state.num_lines):
        if not state.line_verified.get(i):
            return False
    # END: Preconditions

    # BEGIN: Effects
    # [DATA] Final assembled poem
    state.final_poem = "\n".join(state.lines)

    # [ENABLER] Workflow complete
    state.poem_complete = True
    # END: Effects

    return state


# ============================================================================
# METHODS (10)
# ============================================================================

# ============================================================================
# TOP-LEVEL ENTRY POINT
# ============================================================================

def m_write_poem(state: State, poem_form: str, topic: str) -> Union[List[Tuple], bool]:
    """
    Class: Method

    Method signature:
        m_write_poem(state, poem_form, topic)

    Method parameters:
        poem_form: Poetic form ('couplet', 'limerick', 'haiku', 'sonnet')
        topic: Theme or subject of the poem

    Method purpose:
        Top-level entry point. Initializes the poem, dispatches to the
        appropriate form-specific method, then assembles the result.

    Preconditions:
        - poem_form must be a recognized form

    Task decomposition:
        - a_initialize_poem: Set up form spec, buffers, rhyme registry, revision set
        - m_compose_<form>: Form-specific composition method
        - a_assemble_poem: Join verified lines into final text

    Returns:
        Task decomposition if successful, False otherwise

    Hierarchical Decomposition:
        m_write_poem
        +-- a_initialize_poem
        +-- m_compose_couplet | m_compose_limerick | m_compose_haiku | m_compose_sonnet
        +-- a_assemble_poem
    """
    # BEGIN: Type Checking
    if not isinstance(state, State): return False
    if not isinstance(poem_form, str): return False
    if not isinstance(topic, str): return False
    # END: Type Checking

    # BEGIN: State-Type Checks
    if poem_form not in FORM_SPECS: return False
    if not topic.strip(): return False
    # END: State-Type Checks

    # BEGIN: Preconditions
    # No state preconditions -- this is the entry point
    # END: Preconditions

    # BEGIN: Task Decomposition
    form_method = f"m_compose_{poem_form}"
    return [
        ("a_initialize_poem", poem_form, topic),
        (form_method,),
        ("a_assemble_poem",),
    ]
    # END: Task Decomposition


# ============================================================================
# FORM-SPECIFIC COMPOSITION METHODS
# ============================================================================

def m_compose_couplet(state: State) -> Union[List[Tuple], bool]:
    """
    Class: Method

    Method signature:
        m_compose_couplet(state)

    Method parameters:
        None (uses state.form_spec)

    Method purpose:
        Compose a rhyming couplet (AA scheme, 2 lines).

    Preconditions:
        - Poem must be initialized (state.poem_initialized)

    Task decomposition:
        - m_write_rhymed_line x 2

    Returns:
        Task decomposition if successful, False otherwise
    """
    # BEGIN: Type Checking
    if not isinstance(state, State): return False
    # END: Type Checking

    # BEGIN: State-Type Checks
    # No additional parameters to validate
    # END: State-Type Checks

    # BEGIN: Preconditions
    if not (hasattr(state, 'poem_initialized') and state.poem_initialized):
        return False
    # END: Preconditions

    # BEGIN: Task Decomposition
    return [
        ("m_write_rhymed_line", 0, "A", 8),
        ("m_write_rhymed_line", 1, "A", 8),
    ]
    # END: Task Decomposition


def m_compose_limerick(state: State) -> Union[List[Tuple], bool]:
    """
    Class: Method

    Method signature:
        m_compose_limerick(state)

    Method parameters:
        None (uses state.form_spec)

    Method purpose:
        Compose a limerick (AABBA scheme, 5 lines).
        Lines 1,2,5: 8 syllables, rhyme group A.
        Lines 3,4: 5 syllables, rhyme group B.

    Preconditions:
        - Poem must be initialized (state.poem_initialized)

    Task decomposition:
        - m_write_rhymed_line x 5 with appropriate labels and syllable counts

    Returns:
        Task decomposition if successful, False otherwise
    """
    # BEGIN: Type Checking
    if not isinstance(state, State): return False
    # END: Type Checking

    # BEGIN: State-Type Checks
    # No additional parameters to validate
    # END: State-Type Checks

    # BEGIN: Preconditions
    if not (hasattr(state, 'poem_initialized') and state.poem_initialized):
        return False
    # END: Preconditions

    # BEGIN: Task Decomposition
    # AABBA: lines 0,1,4 rhyme A (8 syl); lines 2,3 rhyme B (5 syl)
    return [
        ("m_write_rhymed_line", 0, "A", 8),
        ("m_write_rhymed_line", 1, "A", 8),
        ("m_write_rhymed_line", 2, "B", 5),
        ("m_write_rhymed_line", 3, "B", 5),
        ("m_write_rhymed_line", 4, "A", 8),
    ]
    # END: Task Decomposition


def m_compose_haiku(state: State) -> Union[List[Tuple], bool]:
    """
    Class: Method

    Method signature:
        m_compose_haiku(state)

    Method parameters:
        None (uses state.form_spec)

    Method purpose:
        Compose a haiku (5-7-5 syllables, no rhyme).
        Each line is generated freely with only syllable constraints.
        No evaluation or revision is needed for unrhymed lines.

    Preconditions:
        - Poem must be initialized (state.poem_initialized)

    Task decomposition:
        - m_write_free_line x 3 with syllable counts [5, 7, 5]

    Returns:
        Task decomposition if successful, False otherwise
    """
    # BEGIN: Type Checking
    if not isinstance(state, State): return False
    # END: Type Checking

    # BEGIN: State-Type Checks
    # No additional parameters to validate
    # END: State-Type Checks

    # BEGIN: Preconditions
    if not (hasattr(state, 'poem_initialized') and state.poem_initialized):
        return False
    # END: Preconditions

    # BEGIN: Task Decomposition
    return [
        ("m_write_free_line", 0, 5),
        ("m_write_free_line", 1, 7),
        ("m_write_free_line", 2, 5),
    ]
    # END: Task Decomposition


def m_compose_sonnet(state: State) -> Union[List[Tuple], bool]:
    """
    Class: Method

    Method signature:
        m_compose_sonnet(state)

    Method parameters:
        None (uses state.form_spec)

    Method purpose:
        Compose a Shakespearean sonnet (ABAB CDCD EFEF GG, 14 lines,
        iambic pentameter = 10 syllables per line).
        Decomposes into 3 quatrains + 1 couplet.

    Preconditions:
        - Poem must be initialized (state.poem_initialized)

    Task decomposition:
        - m_write_quatrain x 3 (lines 0-3, 4-7, 8-11)
        - m_write_rhymed_line x 2 (closing couplet, lines 12-13)

    Returns:
        Task decomposition if successful, False otherwise

    Hierarchical Decomposition:
        m_compose_sonnet
        +-- m_write_quatrain(0, "A", "B")   [lines 0-3: ABAB]
        +-- m_write_quatrain(4, "C", "D")   [lines 4-7: CDCD]
        +-- m_write_quatrain(8, "E", "F")   [lines 8-11: EFEF]
        +-- m_write_rhymed_line(12, "G", 10) [closing couplet]
        +-- m_write_rhymed_line(13, "G", 10)
    """
    # BEGIN: Type Checking
    if not isinstance(state, State): return False
    # END: Type Checking

    # BEGIN: State-Type Checks
    # No additional parameters to validate
    # END: State-Type Checks

    # BEGIN: Preconditions
    if not (hasattr(state, 'poem_initialized') and state.poem_initialized):
        return False
    # END: Preconditions

    # BEGIN: Task Decomposition
    return [
        ("m_write_quatrain", 0, "A", "B"),
        ("m_write_quatrain", 4, "C", "D"),
        ("m_write_quatrain", 8, "E", "F"),
        ("m_write_rhymed_line", 12, "G", 10),
        ("m_write_rhymed_line", 13, "G", 10),
    ]
    # END: Task Decomposition


# ============================================================================
# STRUCTURAL SUB-METHODS
# ============================================================================

def m_write_quatrain(state: State, start_line: int, label_1: str, label_2: str) -> Union[List[Tuple], bool]:
    """
    Class: Method

    Method signature:
        m_write_quatrain(state, start_line, label_1, label_2)

    Method parameters:
        start_line: Index of the first line in this quatrain
        label_1: Rhyme label for lines 1 and 3 (e.g., 'A')
        label_2: Rhyme label for lines 2 and 4 (e.g., 'B')

    Method purpose:
        Write a four-line quatrain with ABAB rhyme scheme (cross-rhyme).
        Each line is 10 syllables (iambic pentameter).

    Preconditions:
        - Poem must be initialized (state.poem_initialized)

    Task decomposition:
        - m_write_rhymed_line x 4 in ABAB pattern

    Returns:
        Task decomposition if successful, False otherwise
    """
    # BEGIN: Type Checking
    if not isinstance(state, State): return False
    if not isinstance(start_line, int): return False
    if not isinstance(label_1, str): return False
    if not isinstance(label_2, str): return False
    # END: Type Checking

    # BEGIN: State-Type Checks
    if start_line < 0: return False
    if not label_1.strip(): return False
    if not label_2.strip(): return False
    # END: State-Type Checks

    # BEGIN: Preconditions
    if not (hasattr(state, 'poem_initialized') and state.poem_initialized):
        return False
    # END: Preconditions

    # BEGIN: Task Decomposition
    # ABAB pattern within the quatrain
    return [
        ("m_write_rhymed_line", start_line + 0, label_1, 10),
        ("m_write_rhymed_line", start_line + 1, label_2, 10),
        ("m_write_rhymed_line", start_line + 2, label_1, 10),
        ("m_write_rhymed_line", start_line + 3, label_2, 10),
    ]
    # END: Task Decomposition


# ============================================================================
# LINE-LEVEL METHODS (with evaluation and optional revision)
# ============================================================================

def m_write_rhymed_line(state: State, line_idx: int, rhyme_label: str, target_syllables: int) -> Union[List[Tuple], bool]:
    """
    Class: Method

    Method signature:
        m_write_rhymed_line(state, line_idx, rhyme_label, target_syllables)

    Method parameters:
        line_idx: Zero-based line index
        rhyme_label: Rhyme group label ('A', 'B', etc.)
        target_syllables: Target syllable count for the line

    Method purpose:
        Write a single rhymed line with post-generation evaluation. After
        the initial select-generate-verify cycle, delegates to
        m_evaluate_and_replan which may accept the line or trigger a
        complete revision with a steered target word.

    Preconditions:
        - Poem must be initialized (state.poem_initialized)

    Task decomposition:
        - a_select_rhyme_target: Forward planning (choose end-word)
        - a_generate_line: Generate text toward target
        - a_verify_line: Check constraints (meter, rhyme, coherence)
        - m_evaluate_and_replan: Accept or revise (backtracking point)

    Returns:
        Task decomposition if successful, False otherwise
    """
    # BEGIN: Type Checking
    if not isinstance(state, State): return False
    if not isinstance(line_idx, int): return False
    if not isinstance(rhyme_label, str): return False
    if not isinstance(target_syllables, int): return False
    # END: Type Checking

    # BEGIN: State-Type Checks
    if line_idx < 0: return False
    if not rhyme_label.strip(): return False
    if target_syllables <= 0: return False
    # END: State-Type Checks

    # BEGIN: Preconditions
    if not (hasattr(state, 'poem_initialized') and state.poem_initialized):
        return False
    # END: Preconditions

    # BEGIN: Task Decomposition
    return [
        ("a_select_rhyme_target", line_idx, rhyme_label),
        ("a_generate_line", line_idx, target_syllables),
        ("a_verify_line", line_idx),
        ("m_evaluate_and_replan", line_idx, rhyme_label, target_syllables),
    ]
    # END: Task Decomposition


# ============================================================================
# EVALUATION AND REPLANNING METHODS (backtracking point)
# ============================================================================

def m_accept_line(state: State, line_idx: int, rhyme_label: str, target_syllables: int) -> Union[List[Tuple], bool]:
    """
    Class: Method

    Method signature:
        m_accept_line(state, line_idx, rhyme_label, target_syllables)

    Method parameters:
        line_idx: Zero-based line index
        rhyme_label: Rhyme group label ('A', 'B', etc.)
        target_syllables: Target syllable count for the line

    Method purpose:
        First method for m_evaluate_and_replan (tried first by the planner).
        Decomposes into a_evaluate_line which checks if the line quality
        is acceptable. If the line requires revision, a_evaluate_line fails,
        triggering backtracking to m_revise_line.

    Preconditions:
        - Line must be verified (state.line_verified[line_idx])

    Task decomposition:
        - a_evaluate_line: Check quality (may fail, triggering backtracking)

    Returns:
        Task decomposition if successful, False otherwise
    """
    # BEGIN: Type Checking
    if not isinstance(state, State): return False
    if not isinstance(line_idx, int): return False
    if not isinstance(rhyme_label, str): return False
    if not isinstance(target_syllables, int): return False
    # END: Type Checking

    # BEGIN: State-Type Checks
    if line_idx < 0: return False
    # END: State-Type Checks

    # BEGIN: Preconditions
    if not (hasattr(state, 'line_verified') and
            state.line_verified.get(line_idx)):
        return False
    # END: Preconditions

    # BEGIN: Task Decomposition
    return [
        ("a_evaluate_line", line_idx),
    ]
    # END: Task Decomposition


def m_revise_line(state: State, line_idx: int, rhyme_label: str, target_syllables: int) -> Union[List[Tuple], bool]:
    """
    Class: Method

    Method signature:
        m_revise_line(state, line_idx, rhyme_label, target_syllables)

    Method parameters:
        line_idx: Zero-based line index
        rhyme_label: Rhyme group label ('A', 'B', etc.)
        target_syllables: Target syllable count for the line

    Method purpose:
        Second method for m_evaluate_and_replan (fallback after m_accept_line
        fails). Steers the target to an alternative word, then completely
        regenerates and re-verifies the line. This models the paper's finding
        that changing the planned end-word restructures the entire line.

    Preconditions:
        - Line must be verified from first pass (state.line_verified[line_idx])

    Task decomposition:
        - a_steer_target: Replace target with alternative, reset generation flags
        - a_generate_line: Regenerate line with new target
        - a_verify_line: Re-verify the regenerated line

    Returns:
        Task decomposition if successful, False otherwise
    """
    # BEGIN: Type Checking
    if not isinstance(state, State): return False
    if not isinstance(line_idx, int): return False
    if not isinstance(rhyme_label, str): return False
    if not isinstance(target_syllables, int): return False
    # END: Type Checking

    # BEGIN: State-Type Checks
    if line_idx < 0: return False
    if not rhyme_label.strip(): return False
    if target_syllables <= 0: return False
    # END: State-Type Checks

    # BEGIN: Preconditions
    if not (hasattr(state, 'line_verified') and
            state.line_verified.get(line_idx)):
        return False
    # END: Preconditions

    # BEGIN: Task Decomposition
    return [
        ("a_steer_target", line_idx, rhyme_label),
        ("a_generate_line", line_idx, target_syllables),
        ("a_verify_line", line_idx),
    ]
    # END: Task Decomposition


def m_write_free_line(state: State, line_idx: int, target_syllables: int) -> Union[List[Tuple], bool]:
    """
    Class: Method

    Method signature:
        m_write_free_line(state, line_idx, target_syllables)

    Method parameters:
        line_idx: Zero-based line index
        target_syllables: Target syllable count for the line

    Method purpose:
        Write a single unrhymed line (e.g., haiku): generate text -> verify.
        No rhyme target selection, evaluation, or revision needed.

    Preconditions:
        - Poem must be initialized (state.poem_initialized)

    Task decomposition:
        - a_generate_line_no_rhyme: Free generation with syllable constraint
        - a_verify_line: Constraint checking (syllable count)

    Returns:
        Task decomposition if successful, False otherwise
    """
    # BEGIN: Type Checking
    if not isinstance(state, State): return False
    if not isinstance(line_idx, int): return False
    if not isinstance(target_syllables, int): return False
    # END: Type Checking

    # BEGIN: State-Type Checks
    if line_idx < 0: return False
    if target_syllables <= 0: return False
    # END: State-Type Checks

    # BEGIN: Preconditions
    if not (hasattr(state, 'poem_initialized') and state.poem_initialized):
        return False
    # END: Preconditions

    # BEGIN: Task Decomposition
    return [
        ("a_generate_line_no_rhyme", line_idx, target_syllables),
        ("a_verify_line", line_idx),
    ]
    # END: Task Decomposition


# ============================================================================
# DECLARE ACTIONS TO DOMAIN
# ============================================================================

declare_actions(
    a_initialize_poem,
    a_select_rhyme_target,
    a_generate_line,
    a_generate_line_no_rhyme,
    a_verify_line,
    a_evaluate_line,
    a_steer_target,
    a_assemble_poem,
)

# ============================================================================
# DECLARE METHODS TO DOMAIN
# ============================================================================

# Top-level entry point
declare_task_methods('m_write_poem', m_write_poem)

# Form-specific composition methods
declare_task_methods('m_compose_couplet', m_compose_couplet)
declare_task_methods('m_compose_limerick', m_compose_limerick)
declare_task_methods('m_compose_haiku', m_compose_haiku)
declare_task_methods('m_compose_sonnet', m_compose_sonnet)

# Structural sub-methods
declare_task_methods('m_write_quatrain', m_write_quatrain)

# Line-level methods
declare_task_methods('m_write_rhymed_line', m_write_rhymed_line)

# Evaluation and replanning -- TWO methods for m_evaluate_and_replan (backtracking point)
# The planner tries m_accept_line first. If its a_evaluate_line action fails
# (line requires revision), the planner backtracks and tries m_revise_line.
declare_task_methods('m_evaluate_and_replan', m_accept_line, m_revise_line)

declare_task_methods('m_write_free_line', m_write_free_line)

# ============================================================================
# END OF FILE
# ============================================================================
