#!/usr/bin/env python3
"""Backfill connections journal + proposals from insights blocks that missed routing.

Use when consolidation wrote ## Connections & Links to insights.md but did not
replace the section with the journal pointer (usually: backend had not restarted
after personal-noticing code landed).
"""

from __future__ import annotations

import argparse
import re
import sys

from memory_consolidator import (
    _CONNECTIONS_HEADING,
    _CONNECTIONS_POINTER,
    _extract_consolidation_sections,
    _get_consolidation_config,
    _process_connections_and_links,
    _section_bullets,
)
from utils.personal_pattern_observations import maybe_emit_consolidation_pattern_observation
from utils.user_model import BackgroundProposalBudget, list_proposals


def _insights_path(workspace: str) -> str:
    from memory_consolidator import _get_insights_path

    return _get_insights_path(workspace)


def _unprocessed_blocks(insights: str) -> list[str]:
    """Return consolidation bodies whose Connections section was not routed yet."""
    blocks: list[str] = []
    for match in re.finditer(
        r"## Consolidation —[^\n]*\n\n(.*?)(?=\n---\n|\Z)",
        insights,
        re.DOTALL,
    ):
        body = match.group(1).strip()
        if not body:
            continue
        sections = _extract_consolidation_sections(body)
        conn_body = sections.get(_CONNECTIONS_HEADING, "").strip()
        if not conn_body or _CONNECTIONS_POINTER.strip() in conn_body:
            continue
        if not _section_bullets(sections, _CONNECTIONS_HEADING):
            continue
        blocks.append(body)
    return blocks


def backfill(workspace: str, *, latest_only: bool = True, emit_patterns: bool = True) -> dict:
    path = _insights_path(workspace)
    with open(path, encoding="utf-8") as f:
        insights = f.read()

    pending = _unprocessed_blocks(insights)
    if not pending:
        return {"blocks": 0, "proposals_before": len(list_proposals(workspace))}

    if latest_only:
        pending = [pending[-1]]

    proposals_before = len(list_proposals(workspace))
    cfg = _get_consolidation_config()
    budget = BackgroundProposalBudget(max_proposals=20)

    for body in pending:
        _process_connections_and_links(
            workspace,
            body,
            budget=budget,
            cfg=cfg,
            on_log=lambda level, msg: print(f"{level}: {msg}", file=sys.stderr),
        )
        if emit_patterns:
            pattern_bullets = _section_bullets(
                _extract_consolidation_sections(body),
                "Recurring Patterns",
            )
            maybe_emit_consolidation_pattern_observation(
                workspace,
                pattern_bullets,
                on_log=lambda level, msg: print(f"{level}: {msg}", file=sys.stderr),
            )

    proposals_after = len(list_proposals(workspace))
    return {
        "blocks": len(pending),
        "proposals_before": proposals_before,
        "proposals_after": proposals_after,
        "proposals_added": proposals_after - proposals_before,
    }


def main() -> int:
    parser = argparse.ArgumentParser(description=__doc__)
    parser.add_argument(
        "--workspace",
        default="",
        help="Workspace path (default: active Mugi workspace)",
    )
    parser.add_argument(
        "--all-blocks",
        action="store_true",
        help="Process every unprocessed consolidation block, not just the latest",
    )
    parser.add_argument(
        "--no-patterns",
        action="store_true",
        help="Skip weekly pattern observation emit",
    )
    args = parser.parse_args()

    workspace = (args.workspace or "").strip()
    if not workspace:
        from core import get_workspace_path

        workspace = get_workspace_path() or ""
    if not workspace:
        print("No workspace available", file=sys.stderr)
        return 2

    result = backfill(
        workspace,
        latest_only=not args.all_blocks,
        emit_patterns=not args.no_patterns,
    )
    print(result)
    return 0


if __name__ == "__main__":
    raise SystemExit(main())
