"""backfill post.post_title from description first-line — 2026-05-27

Revision ID: 0024
Revises: 0023
Create Date: 2026-05-27

SubscribeStar gallery-dl always writes `title: ""` and embeds the leading
sentence inside `content` HTML. FC's sidecar parser was leaving
post_title NULL for every SubscribeStar post since FC-3 shipped. The
parser fix (sidecar._first_line_text fallback) now synthesizes a title
at parse time; this migration applies the same logic retroactively to
existing rows.

Operator-flagged 2026-05-27 after inspecting
/mnt/Data/Patreon/Cheunart/subscribestar/ sidecars.

Idempotent: only touches rows where post_title IS NULL or empty AND
description IS NOT NULL. Re-running the migration is a no-op.
"""
from __future__ import annotations

import re
from typing import Sequence, Union

from alembic import op
from sqlalchemy import text

revision: str = "0024"
down_revision: Union[str, None] = "0023"
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None


_TAG_RE = re.compile(r"<[^>]+>")
_WS_RE = re.compile(r"\s+")


def _first_line_text(body: str, limit: int = 120) -> str | None:
    """Mirror of sidecar._first_line_text. Kept inline so the migration
    doesn't carry a runtime import dependency from app code that may
    have moved by the time the migration is replayed years from now."""
    if not body:
        return None
    text_ = _TAG_RE.sub(" ", body)
    text_ = text_.replace("\xa0", " ")
    for line in text_.splitlines():
        line = _WS_RE.sub(" ", line).strip()
        if line:
            if len(line) > limit:
                return line[: limit - 1].rstrip() + "…"
            return line
    return None


def upgrade() -> None:
    bind = op.get_bind()
    rows = bind.execute(
        text(
            "SELECT id, description FROM post "
            "WHERE (post_title IS NULL OR post_title = '') "
            "AND description IS NOT NULL AND description <> ''"
        )
    ).fetchall()
    updated = 0
    for row in rows:
        derived = _first_line_text(row.description)
        if not derived:
            continue
        bind.execute(
            text("UPDATE post SET post_title = :t WHERE id = :id"),
            {"t": derived, "id": row.id},
        )
        updated += 1
    print(f"0024: backfilled post_title on {updated} row(s)")


def downgrade() -> None:
    # No safe restore — we can't tell which post_titles were derived vs
    # genuinely present. Leave the column alone on rollback.
    pass
