# retoor <retoor@molodetz.nl>

import dataset
import logging
from pathlib import Path
from sqlalchemy import or_
from collections import defaultdict
from datetime import datetime, timedelta, timezone
from devplacepy.cache import TTLCache
from devplacepy.config import (
    DATABASE_URL,
    DEFAULT_CORRECTION_PROMPT,
    DEFAULT_MODIFIER_PROMPT,
    INTERNAL_GATEWAY_URL,
    ensure_data_dirs,
)

logger = logging.getLogger(__name__)

ensure_data_dirs()
if DATABASE_URL.startswith("sqlite:///"):
    _db_file = DATABASE_URL[len("sqlite:///") :]
    if _db_file and _db_file != ":memory:":
        Path(_db_file).parent.mkdir(parents=True, exist_ok=True)

db = dataset.connect(
    DATABASE_URL,
    engine_kwargs={
        "connect_args": {
            "timeout": 30,
            "check_same_thread": False,
        },
    },
    on_connect_statements=[
        "PRAGMA journal_mode=WAL",
        "PRAGMA synchronous=NORMAL",
        "PRAGMA busy_timeout=30000",
        "PRAGMA cache_size=-8000",
        "PRAGMA temp_store=MEMORY",
        "PRAGMA mmap_size=268435456",
    ],
)


def refresh_snapshot() -> None:
    connection = db.executable
    if connection.in_transaction() and not db.in_transaction:
        connection.commit()


_local_cache_versions: dict = {}
_cache_version_cache = TTLCache(ttl=1)
_cache_state_ready = False


def _ensure_cache_state() -> None:
    global _cache_state_ready
    if _cache_state_ready:
        return
    db.query(
        "CREATE TABLE IF NOT EXISTS cache_state "
        "(name TEXT PRIMARY KEY, version INTEGER NOT NULL DEFAULT 0)"
    )
    _cache_state_ready = True


def get_cache_version(name: str) -> int:
    cached = _cache_version_cache.get(name)
    if cached is not None:
        return cached
    try:
        _ensure_cache_state()
        row = next(
            iter(
                db.query(
                    "SELECT version FROM cache_state WHERE name = :name", name=name
                )
            ),
            None,
        )
        version = int(row["version"]) if row else 0
    except Exception as e:
        logger.warning(f"Could not read cache version {name}: {e}")
        return 0
    _cache_version_cache.set(name, version)
    return version


def bump_cache_version(name: str) -> None:
    try:
        _ensure_cache_state()
        db.query(
            "INSERT OR IGNORE INTO cache_state (name, version) VALUES (:name, 0)",
            name=name,
        )
        db.query(
            "UPDATE cache_state SET version = version + 1 WHERE name = :name", name=name
        )
        connection = db.executable
        if connection.in_transaction():
            connection.commit()
        _cache_version_cache.pop(name)
    except Exception as e:
        logger.warning(f"Could not bump cache version {name}: {e}")


def sync_local_cache(name: str, cache) -> None:
    current = get_cache_version(name)
    if name not in _local_cache_versions:
        _local_cache_versions[name] = current
        return
    if _local_cache_versions[name] != current:
        cache.clear()
        _local_cache_versions[name] = current


def _index(db, table, name, columns):
    try:
        if table in db.tables:
            cols = ", ".join(columns)
            db.query(f"CREATE INDEX IF NOT EXISTS {name} ON {table} ({cols})")
    except Exception as e:
        logger.warning(f"Could not create index {name} on {table}: {e}")


def _add_usage(usage_table: str, user_uid: str, totals: dict) -> None:
    if not user_uid or int(totals.get("calls") or 0) <= 0 or usage_table not in db.tables:
        return
    with db:
        db.query(
            f"INSERT INTO {usage_table} "
            "(user_uid, calls, prompt_tokens, completion_tokens, total_tokens, cost_usd, "
            "upstream_latency_ms, total_latency_ms, updated_at) "
            "VALUES (:user_uid, :calls, :prompt, :completion, :total, :cost, :ulat, :tlat, :now) "
            "ON CONFLICT(user_uid) DO UPDATE SET "
            "calls = calls + excluded.calls, "
            "prompt_tokens = prompt_tokens + excluded.prompt_tokens, "
            "completion_tokens = completion_tokens + excluded.completion_tokens, "
            "total_tokens = total_tokens + excluded.total_tokens, "
            "cost_usd = cost_usd + excluded.cost_usd, "
            "upstream_latency_ms = upstream_latency_ms + excluded.upstream_latency_ms, "
            "total_latency_ms = total_latency_ms + excluded.total_latency_ms, "
            "updated_at = excluded.updated_at",
            user_uid=user_uid,
            calls=int(totals.get("calls") or 0),
            prompt=int(totals.get("prompt_tokens") or 0),
            completion=int(totals.get("completion_tokens") or 0),
            total=int(totals.get("total_tokens") or 0),
            cost=float(totals.get("cost_usd") or 0.0),
            ulat=float(totals.get("upstream_latency_ms") or 0.0),
            tlat=float(totals.get("total_latency_ms") or 0.0),
            now=datetime.now(timezone.utc).isoformat(),
        )


def _get_usage(usage_table: str, user_uid: str) -> dict:
    empty = {
        "calls": 0,
        "prompt_tokens": 0,
        "completion_tokens": 0,
        "total_tokens": 0,
        "cost_usd": 0.0,
        "upstream_latency_ms": 0.0,
        "total_latency_ms": 0.0,
        "avg_tokens": 0.0,
        "avg_upstream_latency_ms": 0.0,
        "avg_total_latency_ms": 0.0,
        "avg_tokens_per_second": 0.0,
        "avg_cost_usd": 0.0,
        "updated_at": None,
    }
    if not user_uid or usage_table not in db.tables:
        return empty
    row = get_table(usage_table).find_one(user_uid=user_uid)
    if not row:
        return empty
    calls = int(row.get("calls") or 0)
    completion = int(row.get("completion_tokens") or 0)
    total_tokens = int(row.get("total_tokens") or 0)
    cost = float(row.get("cost_usd") or 0.0)
    upstream_ms = float(row.get("upstream_latency_ms") or 0.0)
    total_ms = float(row.get("total_latency_ms") or 0.0)
    return {
        "calls": calls,
        "prompt_tokens": int(row.get("prompt_tokens") or 0),
        "completion_tokens": completion,
        "total_tokens": total_tokens,
        "cost_usd": cost,
        "upstream_latency_ms": upstream_ms,
        "total_latency_ms": total_ms,
        "avg_tokens": round(total_tokens / calls, 1) if calls else 0.0,
        "avg_upstream_latency_ms": round(upstream_ms / calls, 1) if calls else 0.0,
        "avg_total_latency_ms": round(total_ms / calls, 1) if calls else 0.0,
        "avg_tokens_per_second": round(completion / (upstream_ms / 1000.0), 1)
        if upstream_ms > 0
        else 0.0,
        "avg_cost_usd": (cost / calls) if calls else 0.0,
        "updated_at": row.get("updated_at"),
    }


def add_correction_usage(user_uid: str, totals: dict) -> None:
    _add_usage("correction_usage", user_uid, totals)


def get_correction_usage(user_uid: str) -> dict:
    return _get_usage("correction_usage", user_uid)


def add_modifier_usage(user_uid: str, totals: dict) -> None:
    _add_usage("modifier_usage", user_uid, totals)


def get_modifier_usage(user_uid: str) -> dict:
    return _get_usage("modifier_usage", user_uid)


def record_activity(user_uid: str, action: str) -> int:
    if not user_uid or not action or "user_activity" not in db.tables:
        return 0
    now = datetime.now(timezone.utc).isoformat()
    with db:
        db.query(
            "INSERT INTO user_activity (user_uid, action, count, first_at, last_at) "
            "VALUES (:u, :a, 1, :now, :now) "
            "ON CONFLICT(user_uid, action) DO UPDATE SET "
            "count = count + 1, last_at = excluded.last_at",
            u=user_uid,
            a=action,
            now=now,
        )
    rows = list(
        db.query(
            "SELECT count FROM user_activity WHERE user_uid = :u AND action = :a",
            u=user_uid,
            a=action,
        )
    )
    return int(rows[0]["count"]) if rows else 0


def record_unique_activity(user_uid: str, action: str, target: str) -> int | None:
    if not user_uid or not action or "user_activity_seen" not in db.tables:
        return None
    now = datetime.now(timezone.utc).isoformat()
    with db:
        db.query(
            "INSERT OR IGNORE INTO user_activity_seen "
            "(user_uid, action, target, created_at) VALUES (:u, :a, :t, :now)",
            u=user_uid,
            a=action,
            t=str(target),
            now=now,
        )
        changed = list(db.query("SELECT changes() AS c"))
    if not changed or not changed[0]["c"]:
        return None
    rows = list(
        db.query(
            "SELECT COUNT(*) AS c FROM user_activity_seen "
            "WHERE user_uid = :u AND action = :a",
            u=user_uid,
            a=action,
        )
    )
    return int(rows[0]["c"]) if rows else 0


def get_user_activity(user_uid: str) -> dict:
    if not user_uid or "user_activity" not in db.tables:
        return {}
    rows = db.query(
        "SELECT action, count FROM user_activity WHERE user_uid = :u",
        u=user_uid,
    )
    return {row["action"]: int(row["count"]) for row in rows}


SOFT_DELETE_TABLES = [
    "posts",
    "comments",
    "gists",
    "projects",
    "news",
    "news_images",
    "project_files",
    "attachments",
    "votes",
    "reactions",
    "bookmarks",
    "follows",
    "poll_votes",
    "polls",
    "poll_options",
    "sessions",
    "instances",
    "instance_schedules",
    "backup_schedules",
    "devii_conversations",
    "devii_tasks",
    "devii_lessons",
    "devii_virtual_tools",
    "user_customizations",
    "project_forks",
    "issue_tickets",
    "issue_comment_authors",
    "notification_preferences",
    "deepsearch_sessions",
    "deepsearch_messages",
    "devrant_tokens",
]


def ensure_soft_delete_columns(table, *, db_handle=None):
    handle = db_handle or db
    if table not in handle.tables:
        return
    target = handle[table]
    if not target.has_column("deleted_at"):
        target.create_column_by_example("deleted_at", "")
    if not target.has_column("deleted_by"):
        target.create_column_by_example("deleted_by", "")
    _index(handle, table, f"idx_{table}_deleted", ["deleted_at"])


BUG_TABLE_RENAMES = (
    ("bug_tickets", "issue_tickets"),
    ("bug_comment_authors", "issue_comment_authors"),
)


def migrate_bug_tables_to_issue_tables() -> None:
    for source_name, destination_name in BUG_TABLE_RENAMES:
        if source_name not in db.tables:
            continue
        source = db[source_name]
        destination = get_table(destination_name)
        copied = 0
        for row in source.all():
            payload = {key: value for key, value in row.items() if key != "id"}
            destination.insert_ignore(payload, ["uid"])
            copied += 1
        logger.info(
            "Migrated %s rows from %s into %s", copied, source_name, destination_name
        )
        source.drop()
        logger.info("Dropped table %s after migration", source_name)


def init_db():
    tables = db.tables
    _index(db, "users", "idx_users_username", ["username"])
    _index(db, "users", "idx_users_email", ["email"])
    _index(db, "users", "idx_users_api_key", ["api_key"])
    _index(db, "posts", "idx_posts_user_uid", ["user_uid"])
    _index(db, "posts", "idx_posts_created_at", ["created_at"])
    _index(db, "posts", "idx_posts_topic", ["topic"])
    if "posts" in tables:
        posts_table = get_table("posts")
        if not posts_table.has_column("tags"):
            posts_table.create_column_by_example("tags", "")
    if "devrant_tokens" in tables:
        devrant_tokens = get_table("devrant_tokens")
        for column, example in (
            ("uid", ""),
            ("key", ""),
            ("user_uid", ""),
            ("user_id", 0),
            ("expire_time", 0),
            ("created_at", ""),
        ):
            if not devrant_tokens.has_column(column):
                devrant_tokens.create_column_by_example(column, example)
        _index(db, "devrant_tokens", "idx_devrant_tokens_key", ["key"])
        _index(db, "devrant_tokens", "idx_devrant_tokens_user", ["user_uid"])
    _index(db, "comments", "idx_comments_post_uid", ["post_uid"])
    _index(db, "comments", "idx_comments_target", ["target_type", "target_uid"])
    _index(db, "comments", "idx_comments_user_uid", ["user_uid"])
    _index(db, "comments", "idx_comments_created_at", ["created_at"])
    _index(db, "votes", "idx_votes_target", ["target_uid", "target_type"])
    _index(db, "messages", "idx_messages_sender", ["sender_uid"])
    _index(db, "messages", "idx_messages_receiver", ["receiver_uid"])
    _index(db, "notifications", "idx_notifications_user", ["user_uid"])
    _index(db, "notifications", "idx_notifications_user_read", ["user_uid", "read"])
    _index(db, "push_registration", "idx_push_registration_user", ["user_uid"])
    _index(db, "sessions", "idx_sessions_token", ["session_token"])
    _index(db, "projects", "idx_projects_user", ["user_uid"])
    _index(db, "projects", "idx_projects_private", ["is_private"])
    _index(db, "project_files", "idx_project_files_path", ["project_uid", "path"])
    _index(
        db, "project_files", "idx_project_files_parent", ["project_uid", "parent_path"]
    )
    _index(db, "badges", "idx_badges_user", ["user_uid"])
    _index(db, "follows", "idx_follows_follower", ["follower_uid"])
    _index(db, "follows", "idx_follows_following", ["following_uid"])
    _index(db, "password_resets", "idx_password_resets_token", ["token"])
    _index(db, "gists", "idx_gists_user_uid", ["user_uid"])
    _index(db, "gists", "idx_gists_language", ["language"])
    attachments = get_table("attachments")
    for column, example in (
        ("uid", ""),
        ("target_type", ""),
        ("target_uid", ""),
        ("resource_type", ""),
        ("resource_uid", ""),
        ("user_uid", ""),
        ("original_filename", ""),
        ("stored_name", ""),
        ("directory", ""),
        ("file_size", 0),
        ("mime_type", ""),
        ("image_width", 0),
        ("image_height", 0),
        ("has_thumbnail", 0),
        ("thumbnail_name", ""),
        ("created_at", ""),
        ("deleted_at", ""),
    ):
        if not attachments.has_column(column):
            attachments.create_column_by_example(column, example)

    _index(
        db, "attachments", "idx_attachments_resource", ["resource_type", "resource_uid"]
    )
    _index(db, "attachments", "idx_attachments_target", ["target_type", "target_uid"])
    _index(db, "attachments", "idx_attachments_user_created", ["user_uid", "created_at"])
    _index(db, "reactions", "idx_reactions_target", ["target_type", "target_uid"])
    _index(
        db,
        "reactions",
        "idx_reactions_user_target",
        ["user_uid", "target_type", "target_uid"],
    )
    _index(db, "bookmarks", "idx_bookmarks_user", ["user_uid"])
    _index(db, "bookmarks", "idx_bookmarks_target", ["target_type", "target_uid"])
    _index(db, "polls", "idx_polls_post", ["post_uid"])
    _index(db, "poll_options", "idx_poll_options_poll", ["poll_uid"])
    _index(db, "poll_votes", "idx_poll_votes_poll", ["poll_uid"])
    _index(db, "poll_votes", "idx_poll_votes_user", ["poll_uid", "user_uid"])

    if "site_settings" in tables:
        defaults = {
            "site_name": "DevPlace",
            "site_description": "The Developer Social Network",
            "site_tagline": "Track industry shifts. Discover bold releases. Share what you are building in an open, uncensored environment.",
        }
        for key, value in defaults.items():
            existing = db["site_settings"].find_one(key=key)
            if not existing:
                db["site_settings"].insert(
                    {"uid": f"default_{key}", "key": key, "value": value}
                )

    news = get_table("news")
    for column, example in (
        ("uid", ""),
        ("slug", ""),
        ("title", ""),
        ("external_id", ""),
        ("status", ""),
        ("source_name", ""),
        ("url", ""),
        ("description", ""),
        ("content", ""),
        ("synced_at", ""),
        ("grade", 0),
        ("show_on_landing", 0),
    ):
        if not news.has_column(column):
            news.create_column_by_example(column, example)

    _index(db, "news", "idx_news_external_id", ["external_id"])
    _index(db, "news", "idx_news_synced_at", ["synced_at"])
    _index(db, "news", "idx_news_status", ["status"])

    news_images = get_table("news_images")
    if not news_images.has_column("news_uid"):
        news_images.create_column_by_example("news_uid", "")
    if not news_images.has_column("url"):
        news_images.create_column_by_example("url", "")

    news_sync = get_table("news_sync")
    if not news_sync.has_column("external_id"):
        news_sync.create_column_by_example("external_id", "")

    _index(db, "news_images", "idx_news_images_news_uid", ["news_uid"])
    _index(db, "news_sync", "idx_news_sync_external_id", ["external_id"])

    issue_tickets = get_table("issue_tickets")
    for column, example in (
        ("uid", ""),
        ("gitea_number", 0),
        ("author_uid", ""),
        ("original_title", ""),
        ("original_description", ""),
        ("enhanced_title", ""),
        ("html_url", ""),
        ("last_status", ""),
        ("last_comment_count", 0),
        ("created_at", ""),
        ("updated_at", ""),
        ("deleted_at", ""),
        ("deleted_by", ""),
    ):
        if not issue_tickets.has_column(column):
            issue_tickets.create_column_by_example(column, example)

    issue_comment_authors = get_table("issue_comment_authors")
    for column, example in (
        ("uid", ""),
        ("gitea_comment_id", 0),
        ("gitea_number", 0),
        ("author_uid", ""),
        ("created_at", ""),
        ("deleted_at", ""),
        ("deleted_by", ""),
    ):
        if not issue_comment_authors.has_column(column):
            issue_comment_authors.create_column_by_example(column, example)

    _index(db, "issue_tickets", "idx_issue_tickets_number", ["gitea_number"])
    _index(db, "issue_tickets", "idx_issue_tickets_author", ["author_uid"])
    _index(
        db,
        "issue_comment_authors",
        "idx_issue_comment_authors_comment",
        ["gitea_comment_id"],
    )
    _index(
        db, "issue_comment_authors", "idx_issue_comment_authors_number", ["gitea_number"]
    )
    migrate_bug_tables_to_issue_tables()
    _index(db, "service_state", "idx_service_state_name", ["name"])
    if "devii_conversations" in db.tables:
        conversations = get_table("devii_conversations")
        if not conversations.has_column("channel"):
            conversations.create_column_by_example("channel", "main")
        try:
            db.query(
                "UPDATE devii_conversations SET channel='main' WHERE channel IS NULL"
            )
        except Exception as e:  # noqa: BLE001
            logger.warning(f"Could not backfill devii_conversations.channel: {e}")
    _index(
        db,
        "devii_conversations",
        "idx_devii_conv_owner",
        ["owner_kind", "owner_id", "channel"],
    )
    _index(
        db,
        "devii_usage_ledger",
        "idx_devii_ledger_owner_time",
        ["owner_kind", "owner_id", "created_at"],
    )
    _index(
        db,
        "devii_turns",
        "idx_devii_turns_owner_time",
        ["owner_kind", "owner_id", "started_at"],
    )
    _index(db, "devii_tasks", "idx_devii_tasks_owner", ["owner_kind", "owner_id"])
    _index(
        db, "devii_tasks", "idx_devii_tasks_due", ["enabled", "status", "next_run_at"]
    )
    _index(db, "devii_lessons", "idx_devii_lessons_owner", ["owner_kind", "owner_id"])
    _index(
        db, "devii_virtual_tools", "idx_devii_vtools_owner", ["owner_kind", "owner_id"]
    )
    _index(
        db,
        "devii_virtual_tools",
        "idx_devii_vtools_name",
        ["owner_kind", "owner_id", "name"],
    )
    _index(db, "devii_behavior", "idx_devii_behavior_owner", ["owner_kind", "owner_id"])
    _index(db, "user_customizations", "idx_user_cust_owner", ["owner_kind", "owner_id"])
    _index(
        db,
        "user_customizations",
        "idx_user_cust_scope",
        ["owner_kind", "owner_id", "scope", "lang"],
    )
    _index(db, "gateway_usage_ledger", "idx_gw_usage_time", ["created_at"])
    _index(
        db,
        "gateway_usage_ledger",
        "idx_gw_usage_backend_time",
        ["backend", "created_at"],
    )
    _index(
        db, "gateway_usage_ledger", "idx_gw_usage_model_time", ["model", "created_at"]
    )
    _index(
        db,
        "gateway_usage_ledger",
        "idx_gw_usage_owner_time",
        ["owner_kind", "owner_id", "created_at"],
    )
    _index(
        db,
        "gateway_usage_ledger",
        "idx_gw_usage_endpoint_time",
        ["endpoint", "created_at"],
    )
    _index(db, "gateway_concurrency_samples", "idx_gw_conc_time", ["created_at"])
    _index(db, "jobs", "idx_jobs_kind_status", ["kind", "status"])
    _index(db, "jobs", "idx_jobs_owner", ["owner_kind", "owner_id"])
    _index(db, "jobs", "idx_jobs_expires", ["expires_at"])
    _index(db, "project_forks", "idx_project_forks_source", ["source_project_uid"])
    _index(db, "project_forks", "idx_project_forks_forked", ["forked_project_uid"])
    if "instances" in db.tables:
        instances = get_table("instances")
        for column, example in (
            ("run_as_uid", ""),
            ("boot_language", "none"),
            ("boot_script", ""),
            ("start_on_boot", 0),
        ):
            if not instances.has_column(column):
                instances.create_column_by_example(column, example)

    _index(db, "instances", "idx_instances_project", ["project_uid"])
    _index(db, "instances", "idx_instances_state", ["desired_state", "status"])
    _index(db, "instances", "idx_instances_container", ["container_id"])
    _index(db, "instances", "idx_instances_ingress", ["ingress_slug"])
    _index(
        db,
        "instance_events",
        "idx_instance_events_instance",
        ["instance_uid", "created_at"],
    )
    _index(
        db,
        "instance_metrics",
        "idx_instance_metrics_instance_ts",
        ["instance_uid", "ts"],
    )
    _index(
        db,
        "instance_schedules",
        "idx_instance_schedules_due",
        ["enabled", "next_run_at"],
    )

    from devplacepy.services.audit import store as audit_store

    audit_store.ensure_tables()

    from devplacepy.services.backup import store as backup_store

    backup_store.ensure_tables()
    _index(db, "audit_log", "idx_audit_created_at", ["created_at"])
    _index(db, "audit_log", "idx_audit_event_key", ["event_key"])
    _index(db, "audit_log", "idx_audit_category", ["category"])
    _index(db, "audit_log", "idx_audit_actor_uid", ["actor_uid"])
    _index(db, "audit_log", "idx_audit_actor_role", ["actor_role"])
    _index(db, "audit_log", "idx_audit_origin", ["origin"])
    _index(db, "audit_log", "idx_audit_target", ["target_type", "target_uid"])
    _index(db, "audit_log", "idx_audit_result", ["result"])
    _index(db, "audit_log_links", "idx_audit_links_audit", ["audit_uid"])
    _index(db, "audit_log_links", "idx_audit_links_object", ["object_type", "object_uid"])
    _index(db, "audit_log_links", "idx_audit_links_relation", ["relation"])

    notification_preferences = get_table("notification_preferences")
    for column, example in (
        ("uid", ""),
        ("user_uid", ""),
        ("notification_type", ""),
        ("in_app_enabled", 1),
        ("push_enabled", 1),
        ("created_at", ""),
        ("updated_at", ""),
        ("deleted_at", ""),
        ("deleted_by", ""),
    ):
        if not notification_preferences.has_column(column):
            notification_preferences.create_column_by_example(column, example)
    _index(db, "notification_preferences", "idx_notif_prefs_user", ["user_uid"])
    _index(
        db,
        "notification_preferences",
        "idx_notif_prefs_user_type",
        ["user_uid", "notification_type"],
    )

    correction_usage = get_table("correction_usage")
    for column, example in (
        ("user_uid", ""),
        ("calls", 0),
        ("prompt_tokens", 0),
        ("completion_tokens", 0),
        ("total_tokens", 0),
        ("cost_usd", 0.0),
        ("upstream_latency_ms", 0.0),
        ("total_latency_ms", 0.0),
        ("updated_at", ""),
    ):
        if not correction_usage.has_column(column):
            correction_usage.create_column_by_example(column, example)
    try:
        if "correction_usage" in db.tables:
            db.query(
                "CREATE UNIQUE INDEX IF NOT EXISTS idx_correction_usage_user "
                "ON correction_usage (user_uid)"
            )
    except Exception as e:
        logger.warning(f"Could not create unique index on correction_usage: {e}")

    modifier_usage = get_table("modifier_usage")
    for column, example in (
        ("user_uid", ""),
        ("calls", 0),
        ("prompt_tokens", 0),
        ("completion_tokens", 0),
        ("total_tokens", 0),
        ("cost_usd", 0.0),
        ("upstream_latency_ms", 0.0),
        ("total_latency_ms", 0.0),
        ("updated_at", ""),
    ):
        if not modifier_usage.has_column(column):
            modifier_usage.create_column_by_example(column, example)
    try:
        if "modifier_usage" in db.tables:
            db.query(
                "CREATE UNIQUE INDEX IF NOT EXISTS idx_modifier_usage_user "
                "ON modifier_usage (user_uid)"
            )
    except Exception as e:
        logger.warning(f"Could not create unique index on modifier_usage: {e}")

    user_activity = get_table("user_activity")
    for column, example in (
        ("user_uid", ""),
        ("action", ""),
        ("count", 0),
        ("first_at", ""),
        ("last_at", ""),
    ):
        if not user_activity.has_column(column):
            user_activity.create_column_by_example(column, example)
    try:
        if "user_activity" in db.tables:
            db.query(
                "CREATE UNIQUE INDEX IF NOT EXISTS idx_user_activity_user_action "
                "ON user_activity (user_uid, action)"
            )
    except Exception as e:
        logger.warning(f"Could not create unique index on user_activity: {e}")

    user_activity_seen = get_table("user_activity_seen")
    for column, example in (
        ("user_uid", ""),
        ("action", ""),
        ("target", ""),
        ("created_at", ""),
    ):
        if not user_activity_seen.has_column(column):
            user_activity_seen.create_column_by_example(column, example)
    try:
        if "user_activity_seen" in db.tables:
            db.query(
                "CREATE UNIQUE INDEX IF NOT EXISTS idx_user_activity_seen_unique "
                "ON user_activity_seen (user_uid, action, target)"
            )
    except Exception as e:
        logger.warning(f"Could not create unique index on user_activity_seen: {e}")

    deepsearch_sessions = get_table("deepsearch_sessions")
    for column, example in (
        ("uid", ""),
        ("owner_kind", ""),
        ("owner_id", ""),
        ("query", ""),
        ("status", ""),
        ("depth", 0),
        ("max_pages", 0),
        ("score", 0),
        ("confidence", 0.0),
        ("source_diversity", 0.0),
        ("page_count", 0),
        ("chunk_count", 0),
        ("collection", ""),
        ("summary", ""),
        ("created_at", ""),
        ("completed_at", ""),
        ("deleted_at", ""),
        ("deleted_by", ""),
    ):
        if not deepsearch_sessions.has_column(column):
            deepsearch_sessions.create_column_by_example(column, example)
    _index(db, "deepsearch_sessions", "idx_deepsearch_sessions_owner", ["owner_kind", "owner_id"])
    _index(db, "deepsearch_sessions", "idx_deepsearch_sessions_status", ["status"])
    _index(db, "deepsearch_sessions", "idx_deepsearch_sessions_created", ["created_at"])

    deepsearch_messages = get_table("deepsearch_messages")
    for column, example in (
        ("uid", ""),
        ("session_uid", ""),
        ("role", ""),
        ("content", ""),
        ("citations", ""),
        ("created_at", ""),
        ("deleted_at", ""),
        ("deleted_by", ""),
    ):
        if not deepsearch_messages.has_column(column):
            deepsearch_messages.create_column_by_example(column, example)
    _index(db, "deepsearch_messages", "idx_deepsearch_messages_session", ["session_uid"])

    deepsearch_url_cache = get_table("deepsearch_url_cache")
    for column, example in (
        ("uid", ""),
        ("url_hash", ""),
        ("url", ""),
        ("title", ""),
        ("content_hash", ""),
        ("status", 0),
        ("byte_size", 0),
        ("fetched_at", ""),
    ):
        if not deepsearch_url_cache.has_column(column):
            deepsearch_url_cache.create_column_by_example(column, example)
    _index(db, "deepsearch_url_cache", "idx_deepsearch_url_cache_hash", ["url_hash"])

    for table in SOFT_DELETE_TABLES:
        ensure_soft_delete_columns(table)

    if "news" in tables:
        for article in db["news"].find(status=None):
            was_featured = article.get("featured", 0)
            db["news"].update(
                {
                    "uid": article["uid"],
                    "status": "published" if was_featured else "draft",
                },
                ["uid"],
            )
        for article in db["news"].find(show_on_landing=None):
            db["news"].update(
                {
                    "uid": article["uid"],
                    "show_on_landing": 0,
                },
                ["uid"],
            )
        for article in db["news"].find(slug=None):
            from devplacepy.utils import make_combined_slug

            slug = make_combined_slug(
                article.get("title", "") or "news", article["uid"]
            )
            db["news"].update(
                {
                    "uid": article["uid"],
                    "slug": slug,
                },
                ["uid"],
            )

    if "news_sync" in tables:
        for entry in db["news_sync"].find():
            current = entry.get("status", "")
            if current in ("below_threshold", ""):
                db["news_sync"].update(
                    {
                        "id": entry["id"],
                        "status": "graded",
                    },
                    ["id"],
                )

    if "site_settings" in tables:
        news_defaults = {
            "news_grade_threshold": "7",
            "news_api_url": "https://news.app.molodetz.nl/api",
            "news_ai_url": INTERNAL_GATEWAY_URL,
            "news_ai_model": "molodetz",
        }
        for key, value in news_defaults.items():
            existing = db["site_settings"].find_one(key=key)
            if not existing:
                db["site_settings"].insert(
                    {"uid": f"default_{key}", "key": key, "value": value}
                )

        upload_defaults = {
            "max_upload_size_mb": "10",
            "allowed_file_types": "",
            "max_attachments_per_resource": "10",
        }
        for key, value in upload_defaults.items():
            existing = db["site_settings"].find_one(key=key)
            if not existing:
                db["site_settings"].insert(
                    {"uid": f"default_{key}", "key": key, "value": value}
                )

        operational_defaults = {
            "rate_limit_per_minute": "60",
            "rate_limit_window_seconds": "60",
            "news_service_interval": "3600",
            "session_max_age_days": "7",
            "session_remember_days": "30",
            "registration_open": "1",
            "maintenance_mode": "0",
            "maintenance_message": "DevPlace is undergoing scheduled maintenance. Please check back shortly.",
            "customization_enabled": "1",
            "customization_js_enabled": "1",
            "audit_log_retention_days": "90",
            "docs_search_mode": "agent",
        }
        for key, value in operational_defaults.items():
            existing = db["site_settings"].find_one(key=key)
            if not existing:
                db["site_settings"].insert(
                    {"uid": f"default_{key}", "key": key, "value": value}
                )

    _backfill_gamification()
    backfill_api_keys()
    migrate_ai_gateway_settings()

    logger.info("Database initialized")


def internal_gateway_key() -> str:
    return get_setting("gateway_internal_key", "")


OLD_GATEWAY_URL = "https://openai.app.molodetz.nl/v1/chat/completions"


def migrate_ai_gateway_settings() -> None:
    import os
    import uuid

    if not get_setting("gateway_internal_key", ""):
        set_setting("gateway_internal_key", str(uuid.uuid4()))
        logger.info("Generated internal gateway key")
    deepseek = os.environ.get("DEEPSEEK_API_KEY", "")
    openrouter = os.environ.get("OPENROUTER_API_KEY", "")
    if not get_setting("gateway_api_key", "") and (deepseek or openrouter):
        set_setting("gateway_api_key", deepseek or openrouter)
        logger.info("Migrated gateway upstream key from environment")
    if not get_setting("gateway_vision_key", "") and openrouter:
        set_setting("gateway_vision_key", openrouter)
        logger.info("Migrated gateway vision key from environment")
    for key in ("news_ai_url", "bot_api_url", "devii_ai_url"):
        if get_setting(key, "") == OLD_GATEWAY_URL:
            set_setting(key, INTERNAL_GATEWAY_URL)
            logger.info(f"Migrated {key} to the internal gateway")
    if get_setting("bot_model", "") == "deepseek-chat":
        set_setting("bot_model", "molodetz")
        logger.info("Migrated bot_model to molodetz")


def backfill_api_keys() -> int:
    if "users" not in db.tables:
        return 0
    users = db["users"]
    if not users.has_column("api_key"):
        users.create_column_by_example("api_key", "")
    if not users.has_column("cust_disable_global"):
        users.create_column_by_example("cust_disable_global", 0)
    if not users.has_column("cust_disable_pagetype"):
        users.create_column_by_example("cust_disable_pagetype", 0)
    if not users.has_column("ai_correction_enabled"):
        users.create_column_by_example("ai_correction_enabled", 0)
    if not users.has_column("ai_correction_prompt"):
        users.create_column_by_example("ai_correction_prompt", DEFAULT_CORRECTION_PROMPT)
    if not users.has_column("ai_correction_sync"):
        users.create_column_by_example("ai_correction_sync", 0)
    if not users.has_column("ai_modifier_enabled"):
        users.create_column_by_example("ai_modifier_enabled", 1)
    if not users.has_column("ai_modifier_sync"):
        users.create_column_by_example("ai_modifier_sync", 1)
    if not users.has_column("ai_modifier_prompt"):
        users.create_column_by_example("ai_modifier_prompt", DEFAULT_MODIFIER_PROMPT)
    with db:
        db.query(
            "UPDATE users SET ai_modifier_enabled = 1 WHERE ai_modifier_enabled IS NULL"
        )
        db.query("UPDATE users SET ai_modifier_sync = 1 WHERE ai_modifier_sync IS NULL")
        db.query(
            "UPDATE users SET ai_modifier_prompt = :prompt "
            "WHERE ai_modifier_prompt IS NULL OR ai_modifier_prompt = ''",
            prompt=DEFAULT_MODIFIER_PROMPT,
        )
    import uuid_utils

    updated = 0
    for user in users.find():
        if not user.get("api_key"):
            users.update(
                {"uid": user["uid"], "api_key": str(uuid_utils.uuid7())}, ["uid"]
            )
            updated += 1
    return updated


def _backfill_gamification():
    if "users" not in db.tables:
        return
    from devplacepy.utils import (
        level_for_xp,
        check_milestone_badges,
        XP_POST,
        XP_COMMENT,
        XP_PROJECT,
        XP_GIST,
        XP_UPVOTE,
        XP_FOLLOW,
    )

    pending = list(db["users"].find(xp=0))
    if not pending:
        return

    xp_by_user = defaultdict(int)

    def add_counts(table, column, points):
        if table not in db.tables:
            return
        for row in db.query(
            f"SELECT {column} AS uid, COUNT(*) AS c FROM {table} GROUP BY {column}"
        ):
            if row["uid"]:
                xp_by_user[row["uid"]] += row["c"] * points

    add_counts("posts", "user_uid", XP_POST)
    add_counts("comments", "user_uid", XP_COMMENT)
    add_counts("projects", "user_uid", XP_PROJECT)
    add_counts("gists", "user_uid", XP_GIST)
    add_counts("follows", "following_uid", XP_FOLLOW)

    if "votes" in db.tables:
        for content_table, target_type in (
            ("posts", "post"),
            ("projects", "project"),
            ("gists", "gist"),
            ("comments", "comment"),
        ):
            if content_table not in db.tables:
                continue
            rows = db.query(
                f"SELECT c.user_uid AS uid, COUNT(*) AS c "
                f"FROM votes v JOIN {content_table} c ON v.target_uid = c.uid "
                f"WHERE v.target_type = :t AND v.value = 1 GROUP BY c.user_uid",
                t=target_type,
            )
            for row in rows:
                if row["uid"]:
                    xp_by_user[row["uid"]] += row["c"] * XP_UPVOTE

    for user in pending:
        xp = xp_by_user.get(user["uid"], 0)
        if xp <= 0:
            continue
        db["users"].update(
            {"uid": user["uid"], "xp": xp, "level": level_for_xp(xp)}, ["uid"]
        )

    _authors_cache.clear()
    for user in pending:
        check_milestone_badges(user["uid"])
    logger.info(f"Gamification backfill processed {len(pending)} users")


def get_table(name):
    return db[name]


def _in_clause(uids, prefix="p"):
    placeholders = ", ".join(f":{prefix}{i}" for i in range(len(uids)))
    params = {f"{prefix}{i}": uid for i, uid in enumerate(uids)}
    return placeholders, params


def _now_iso() -> str:
    return datetime.now(timezone.utc).isoformat()


def soft_delete(table_name, deleted_by, *, stamp=None, **criteria):
    if table_name not in db.tables:
        return 0
    table = db[table_name]
    if not table.has_column("deleted_at"):
        return 0
    rows = list(table.find(deleted_at=None, **criteria))
    if not rows:
        return 0
    stamp = stamp or _now_iso()
    for row in rows:
        table.update(
            {"id": row["id"], "deleted_at": stamp, "deleted_by": deleted_by}, ["id"]
        )
    return len(rows)


def soft_delete_in(table_name, column, uids, deleted_by, *, stamp=None, **extra):
    uids = [uid for uid in (uids or []) if uid]
    if not uids or table_name not in db.tables:
        return 0
    if not db[table_name].has_column("deleted_at"):
        return 0
    placeholders, params = _in_clause(uids)
    params["dat"] = stamp or _now_iso()
    params["dby"] = deleted_by
    extra_sql = ""
    for index, (key, value) in enumerate(extra.items()):
        params[f"x{index}"] = value
        extra_sql += f" AND {key} = :x{index}"
    db.query(
        f"UPDATE {table_name} SET deleted_at = :dat, deleted_by = :dby "
        f"WHERE {column} IN ({placeholders}) AND deleted_at IS NULL{extra_sql}",
        **params,
    )
    return len(uids)


def restore(table_name, **criteria):
    if table_name not in db.tables:
        return 0
    table = db[table_name]
    if not table.has_column("deleted_at"):
        return 0
    rows = [row for row in table.find(**criteria) if row.get("deleted_at")]
    for row in rows:
        table.update({"id": row["id"], "deleted_at": None, "deleted_by": None}, ["id"])
    return len(rows)


def purge(table_name, **criteria):
    if table_name not in db.tables:
        return 0
    table = db[table_name]
    count = table.count(**criteria)
    table.delete(**criteria)
    return int(count)


def list_deleted(table_name, page=1, per_page=25):
    if table_name not in db.tables or not db[table_name].has_column("deleted_at"):
        return [], build_pagination(page, 0, per_page)
    table = db[table_name]
    column = table.table.columns.deleted_at
    total = table.count(column.isnot(None))
    pagination = build_pagination(page, total, per_page)
    offset = (pagination["page"] - 1) * pagination["per_page"]
    rows = list(
        table.find(
            column.isnot(None),
            order_by=["-deleted_at"],
            _limit=pagination["per_page"],
            _offset=offset,
        )
    )
    return rows, pagination


def count_deleted(table_name):
    if table_name not in db.tables or not db[table_name].has_column("deleted_at"):
        return 0
    table = db[table_name]
    return int(table.count(table.table.columns.deleted_at.isnot(None)))


def restore_event(stamp):
    if not stamp:
        return 0
    restored = 0
    for table_name in SOFT_DELETE_TABLES:
        if table_name in db.tables and db[table_name].has_column("deleted_at"):
            restored += int(
                db.query(
                    f"SELECT COUNT(*) AS n FROM {table_name} WHERE deleted_at = :s",
                    s=stamp,
                ).__next__()["n"]
            )
            db.query(
                f"UPDATE {table_name} SET deleted_at = NULL, deleted_by = NULL "
                f"WHERE deleted_at = :s",
                s=stamp,
            )
    return restored


def purge_event(stamp):
    if not stamp:
        return []
    purged = []
    for table_name in SOFT_DELETE_TABLES:
        if table_name in db.tables and db[table_name].has_column("deleted_at"):
            rows = list(
                db.query(
                    f"SELECT * FROM {table_name} WHERE deleted_at = :s", s=stamp
                )
            )
            if rows:
                purged.append((table_name, rows))
                db.query(
                    f"DELETE FROM {table_name} WHERE deleted_at = :s", s=stamp
                )
    return purged


def get_users_by_uids(uids):
    if not uids or "users" not in db.tables:
        return {}
    users = db["users"]
    if "uid" not in users.columns:
        return {}
    seen = set()
    unique = [u for u in uids if u not in seen and not seen.add(u)]
    return {u["uid"]: u for u in users.find(users.table.columns.uid.in_(unique))}


def search_users_by_username(q, *, exclude_uid=None, limit=10):
    if not q or "users" not in db.tables:
        return []
    if exclude_uid is not None:
        rows = db.query(
            "SELECT uid, username FROM users WHERE username LIKE :q AND uid != :me LIMIT :limit",
            q=f"%{q}%",
            me=exclude_uid,
            limit=limit,
        )
    else:
        rows = db.query(
            "SELECT uid, username FROM users WHERE username LIKE :q LIMIT :limit",
            q=f"%{q}%",
            limit=limit,
        )
    return [{"uid": r["uid"], "username": r["username"]} for r in rows]


def get_comment_counts_by_post_uids(post_uids):
    if not post_uids or "comments" not in db.tables:
        return {}
    placeholders, params = _in_clause(post_uids)
    rows = db.query(
        f"SELECT target_uid, COUNT(*) as c FROM comments WHERE target_type='post' AND target_uid IN ({placeholders}) AND deleted_at IS NULL GROUP BY target_uid",
        **params,
    )
    return {r["target_uid"]: r["c"] for r in rows}


def get_post_counts_by_user_uids(user_uids):
    if not user_uids or "posts" not in db.tables:
        return {}
    placeholders, params = _in_clause(user_uids)
    rows = db.query(
        f"SELECT user_uid, COUNT(*) as c FROM posts WHERE user_uid IN ({placeholders}) AND deleted_at IS NULL GROUP BY user_uid",
        **params,
    )
    return {r["user_uid"]: r["c"] for r in rows}


def get_vote_counts(target_uids):
    if not target_uids or "votes" not in db.tables:
        return {}, {}
    placeholders, params = _in_clause(target_uids)
    rows = db.query(
        f"SELECT target_uid, value, COUNT(*) as c FROM votes WHERE target_uid IN ({placeholders}) AND deleted_at IS NULL GROUP BY target_uid, value",
        **params,
    )
    ups = {}
    downs = {}
    for r in rows:
        if r["value"] == 1:
            ups[r["target_uid"]] = r["c"]
        else:
            downs[r["target_uid"]] = r["c"]
    return ups, downs


def get_user_votes(user_uid, target_uids):
    if not user_uid or not target_uids or "votes" not in db.tables:
        return {}
    placeholders, params = _in_clause(target_uids)
    params["uid"] = user_uid
    rows = db.query(
        f"SELECT target_uid, value FROM votes WHERE user_uid = :uid AND target_uid IN ({placeholders}) AND deleted_at IS NULL",
        **params,
    )
    return {r["target_uid"]: r["value"] for r in rows}


def get_reactions_by_targets(target_type, target_uids, user=None):
    if not target_uids or "reactions" not in db.tables:
        return {}
    placeholders, params = _in_clause(target_uids)
    params["tt"] = target_type
    rows = db.query(
        f"SELECT target_uid, emoji, COUNT(*) as c FROM reactions WHERE target_type=:tt AND target_uid IN ({placeholders}) AND deleted_at IS NULL GROUP BY target_uid, emoji",
        **params,
    )
    counts = defaultdict(dict)
    for row in rows:
        counts[row["target_uid"]][row["emoji"]] = row["c"]
    mine = defaultdict(list)
    if user:
        placeholders, params = _in_clause(target_uids, prefix="m")
        params["tt"] = target_type
        params["u"] = user["uid"]
        for row in db.query(
            f"SELECT target_uid, emoji FROM reactions WHERE user_uid=:u AND target_type=:tt AND target_uid IN ({placeholders}) AND deleted_at IS NULL",
            **params,
        ):
            mine[row["target_uid"]].append(row["emoji"])
    result = {}
    for uid in target_uids:
        result[uid] = {
            "counts": dict(counts.get(uid, {})),
            "mine": list(mine.get(uid, [])),
        }
    return result


def get_user_bookmarks(user_uid, target_type, target_uids):
    if not user_uid or not target_uids or "bookmarks" not in db.tables:
        return set()
    placeholders, params = _in_clause(target_uids)
    params["u"] = user_uid
    params["tt"] = target_type
    rows = db.query(
        f"SELECT target_uid FROM bookmarks WHERE user_uid=:u AND target_type=:tt AND target_uid IN ({placeholders}) AND deleted_at IS NULL",
        **params,
    )
    return {row["target_uid"] for row in rows}


def get_polls_by_post_uids(post_uids, user=None):
    if not post_uids or "polls" not in db.tables:
        return {}
    placeholders, params = _in_clause(post_uids)
    polls = list(
        db.query(
            f"SELECT * FROM polls WHERE post_uid IN ({placeholders}) AND deleted_at IS NULL",
            **params,
        )
    )
    if not polls:
        return {}
    poll_uids = [poll["uid"] for poll in polls]
    option_placeholders, option_params = _in_clause(poll_uids, prefix="o")
    options = list(
        db.query(
            f"SELECT * FROM poll_options WHERE poll_uid IN ({option_placeholders}) AND deleted_at IS NULL ORDER BY position",
            **option_params,
        )
    )
    counts = defaultdict(dict)
    totals = defaultdict(int)
    if "poll_votes" in db.tables:
        vote_placeholders, vote_params = _in_clause(poll_uids, prefix="v")
        for row in db.query(
            f"SELECT poll_uid, option_uid, COUNT(*) as c FROM poll_votes WHERE poll_uid IN ({vote_placeholders}) AND deleted_at IS NULL GROUP BY poll_uid, option_uid",
            **vote_params,
        ):
            counts[row["poll_uid"]][row["option_uid"]] = row["c"]
            totals[row["poll_uid"]] += row["c"]
    user_choice = {}
    if user and "poll_votes" in db.tables:
        placeholders, params = _in_clause(poll_uids, prefix="m")
        params["u"] = user["uid"]
        for row in db.query(
            f"SELECT poll_uid, option_uid FROM poll_votes WHERE user_uid=:u AND poll_uid IN ({placeholders}) AND deleted_at IS NULL",
            **params,
        ):
            user_choice[row["poll_uid"]] = row["option_uid"]
    options_by_poll = defaultdict(list)
    for option in options:
        options_by_poll[option["poll_uid"]].append(option)
    result = {}
    for poll in polls:
        poll_uid = poll["uid"]
        total = totals.get(poll_uid, 0)
        rendered = []
        for option in options_by_poll.get(poll_uid, []):
            count = counts.get(poll_uid, {}).get(option["uid"], 0)
            rendered.append(
                {
                    "uid": option["uid"],
                    "label": option["label"],
                    "count": count,
                    "pct": round(count * 100 / total) if total else 0,
                }
            )
        result[poll["post_uid"]] = {
            "uid": poll_uid,
            "question": poll["question"],
            "options": rendered,
            "total": total,
            "my_choice": user_choice.get(poll_uid),
        }
    return result


def get_poll_for_post(post_uid, user=None):
    return get_polls_by_post_uids([post_uid], user).get(post_uid)


def _build_comment_items(raw, user=None):
    uids = [c["user_uid"] for c in raw]
    cids = [c["uid"] for c in raw]
    users = get_users_by_uids(uids)
    ups, downs = get_vote_counts(cids)
    user_votes = get_user_votes(user["uid"], cids) if user else {}
    reactions = get_reactions_by_targets("comment", cids, user)
    from devplacepy.utils import time_ago
    from devplacepy.attachments import get_attachments_batch as _gab

    atts_map = _gab("comment", cids) if "attachments" in db.tables else {}
    items = {}
    for c in raw:
        items[c["uid"]] = {
            "comment": c,
            "author": users.get(c["user_uid"]),
            "time_ago": time_ago(c["created_at"]),
            "votes": {"up": ups.get(c["uid"], 0), "down": downs.get(c["uid"], 0)},
            "my_vote": user_votes.get(c["uid"], 0),
            "children": [],
            "attachments": atts_map.get(c["uid"], []),
            "reactions": reactions.get(c["uid"], {"counts": {}, "mine": []}),
        }
    return items


def load_comments(target_type, target_uid, user=None):
    if "comments" not in db.tables:
        return []
    comments_table = db["comments"]
    raw = list(
        comments_table.find(
            target_type=target_type,
            target_uid=target_uid,
            deleted_at=None,
            order_by=["created_at"],
        )
    )
    if not raw and target_type == "post":
        raw = list(
            comments_table.find(
                post_uid=target_uid, deleted_at=None, order_by=["created_at"]
            )
        )
    if not raw:
        return []
    cmap = _build_comment_items(raw, user)
    top = []
    for item in cmap.values():
        parent = item["comment"].get("parent_uid")
        if parent and parent in cmap:
            cmap[parent]["children"].append(item)
        else:
            top.append(item)
    return top

def get_recent_comments_by_target_uids(target_type, target_uids, limit=3, user=None):
    if not target_uids or "comments" not in db.tables:
        return {}
    placeholders, params = _in_clause(target_uids)
    params["tt"] = target_type
    params["lim"] = limit
    raw = list(
        db.query(
            f"SELECT * FROM ("
            f"  SELECT *, ROW_NUMBER() OVER ("
            f"    PARTITION BY target_uid ORDER BY created_at DESC, id DESC"
            f"  ) AS rn FROM comments"
            f"  WHERE target_type=:tt AND target_uid IN ({placeholders}) AND deleted_at IS NULL"
            f") WHERE rn <= :lim ORDER BY target_uid, created_at ASC",
            **params,
        )
    )
    if not raw:
        return {}
    items = _build_comment_items(raw, user)
    by_target = defaultdict(list)
    for c in raw:
        by_target[c["target_uid"]].append(c)
    result = {}
    for target_uid, group in by_target.items():
        in_group = {c["uid"] for c in group}
        top = []
        for c in group:
            item = items[c["uid"]]
            item["children"] = []
        for c in group:
            item = items[c["uid"]]
            parent = c.get("parent_uid")
            if parent and parent in in_group:
                items[parent]["children"].append(item)
            else:
                top.append(item)
        result[target_uid] = top
    return result


def get_recent_comments_by_post_uids(post_uids, limit=3, user=None):
    return get_recent_comments_by_target_uids("post", post_uids, limit, user)


def load_comments_by_target_uids(target_type, target_uids, user=None):
    if not target_uids or "comments" not in db.tables:
        return {}
    placeholders, params = _in_clause(target_uids)
    params["tt"] = target_type
    raw = list(
        db.query(
            f"SELECT * FROM comments WHERE target_type=:tt AND target_uid IN ({placeholders}) ORDER BY created_at",
            **params,
        )
    )
    if not raw:
        return {}
    from collections import defaultdict
    by_uid = defaultdict(list)
    for c in raw:
        by_uid[c["target_uid"]].append(c)
    result = {}
    for uid in target_uids:
        group = by_uid.get(uid, [])
        if not group:
            result[uid] = []
            continue
        cmap = _build_comment_items(group, user)
        tree = []
        for item in cmap.values():
            parent = item["comment"].get("parent_uid")
            if parent and parent in cmap:
                cmap[parent]["children"].append(item)
            else:
                tree.append(item)
        result[uid] = tree
    return result


def get_attachments(resource_type: str, resource_uid: str) -> list:
    if "attachments" not in db.tables:
        return []
    return list(
        db["attachments"].find(
            resource_type=resource_type,
            resource_uid=resource_uid,
            deleted_at=None,
            order_by=["created_at"],
        )
    )


def get_attachments_by_type(resource_type: str, resource_uids: list) -> dict:
    if not resource_uids or "attachments" not in db.tables:
        return {}
    rows = list(
        db["attachments"].find(
            db["attachments"].table.columns.resource_uid.in_(resource_uids),
            db["attachments"].table.columns.deleted_at.is_(None),
            resource_type=resource_type,
        )
    )
    result = {}
    for a in rows:
        key = a["resource_uid"]
        if key not in result:
            result[key] = []
        result[key].append(a)
    return result


def get_news_images_by_uids(news_uids: list) -> dict:
    if not news_uids or "news_images" not in db.tables:
        return {}
    images_table = db["news_images"]
    if not images_table.has_column("news_uid"):
        return {}
    rows = images_table.find(
        images_table.table.columns.news_uid.in_(news_uids),
        images_table.table.columns.deleted_at.is_(None),
        order_by=["uid"],
    )
    result = {}
    for r in rows:
        result.setdefault(r["news_uid"], r["url"])
    return result


def delete_attachment_record(uid: str) -> None:
    if "attachments" not in db.tables:
        return
    att = db["attachments"].find_one(uid=uid)
    if att:
        _delete_attachment_file(att.get("storage_path", ""))
        db["attachments"].delete(id=att["id"])


def delete_attachments(resource_type: str, resource_uid: str) -> None:
    if "attachments" not in db.tables:
        return
    for a in db["attachments"].find(
        resource_type=resource_type, resource_uid=resource_uid
    ):
        _delete_attachment_file(a.get("storage_path", ""))
    db["attachments"].delete(resource_type=resource_type, resource_uid=resource_uid)


def _delete_attachment_file(storage_path: str) -> None:
    if not storage_path:
        return
    from devplacepy.config import UPLOADS_DIR

    file_path = UPLOADS_DIR / storage_path
    try:
        file_path.unlink(missing_ok=True)
        parent = file_path.parent
        if parent.exists() and not any(parent.iterdir()):
            parent.rmdir()
        grandparent = parent.parent
        if grandparent.exists() and not any(grandparent.iterdir()):
            grandparent.rmdir()
    except Exception as e:
        logger.warning(f"Failed to delete attachment file {storage_path}: {e}")


_settings_cache = TTLCache(ttl=60, max_size=100)


def get_setting(key: str, default: str = "") -> str:
    sync_local_cache("settings", _settings_cache)
    cached = _settings_cache.get(key)
    if cached is not None:
        return cached
    if "site_settings" not in db.tables:
        return default
    entry = db["site_settings"].find_one(key=key)
    if entry is None:
        return default
    _settings_cache.set(key, entry["value"])
    return entry["value"]


def get_int_setting(key: str, default: int) -> int:
    raw = get_setting(key, str(default))
    try:
        return int(raw)
    except (TypeError, ValueError):
        return default


def set_setting(key: str, value: str) -> None:
    settings = get_table("site_settings")
    existing = settings.find_one(key=key)
    if existing:
        settings.update({"id": existing["id"], "key": key, "value": value}, ["id"])
    else:
        settings.insert({"uid": f"setting_{key}", "key": key, "value": value})
    _settings_cache.set(key, value)
    bump_cache_version("settings")


def clear_settings_cache() -> None:
    _settings_cache.clear()
    bump_cache_version("settings")


CUSTOMIZATION_GLOBAL_SCOPE = "global"
CUSTOMIZATION_LANGS = ("css", "js")

_customizations_cache = TTLCache(ttl=300, max_size=100)


def _customization_key(owner_kind: str, owner_id: str, page_type: str) -> str:
    return f"{owner_kind}\x1f{owner_id}\x1f{page_type}"


CUSTOMIZATION_PREF_COLUMNS = {
    "global": "cust_disable_global",
    "pagetype": "cust_disable_pagetype",
}


def get_customization_prefs(owner_kind: str, owner_id: str) -> dict:
    if owner_kind != "user" or "users" not in db.tables:
        return {"disable_global": False, "disable_pagetype": False}
    user = db["users"].find_one(uid=owner_id)
    if user is None:
        return {"disable_global": False, "disable_pagetype": False}
    return {
        "disable_global": bool(user.get("cust_disable_global", 0)),
        "disable_pagetype": bool(user.get("cust_disable_pagetype", 0)),
    }


def set_customization_pref(owner_id: str, category: str, disabled: bool) -> None:
    column = CUSTOMIZATION_PREF_COLUMNS.get(category)
    if column is None:
        raise ValueError(f"Unknown customization category: {category}")
    from devplacepy.utils import clear_user_cache

    get_table("users").update(
        {"uid": owner_id, column: 1 if disabled else 0}, ["uid"]
    )
    clear_user_cache(owner_id)
    bump_cache_version("customizations")


def get_custom_overrides(owner_kind: str, owner_id: str, page_type: str) -> dict:
    sync_local_cache("customizations", _customizations_cache)
    key = _customization_key(owner_kind, owner_id, page_type)
    cached = _customizations_cache.get(key)
    if cached is not None:
        return cached
    result = {"css": "", "js": ""}
    if "user_customizations" in db.tables:
        prefs = get_customization_prefs(owner_kind, owner_id)
        scopes = (CUSTOMIZATION_GLOBAL_SCOPE, page_type)
        rows = db["user_customizations"].find(
            owner_kind=owner_kind,
            owner_id=owner_id,
            enabled=1,
            deleted_at=None,
        )
        pieces: dict[str, dict[str, str]] = {lang: {} for lang in CUSTOMIZATION_LANGS}
        for row in rows:
            lang = row.get("lang")
            scope = row.get("scope")
            if lang not in pieces or scope not in scopes:
                continue
            if scope == CUSTOMIZATION_GLOBAL_SCOPE and prefs["disable_global"]:
                continue
            if scope != CUSTOMIZATION_GLOBAL_SCOPE and prefs["disable_pagetype"]:
                continue
            pieces[lang][scope] = row.get("code") or ""
        for lang in CUSTOMIZATION_LANGS:
            ordered = [pieces[lang][scope] for scope in scopes if scope in pieces[lang]]
            result[lang] = "\n".join(part for part in ordered if part.strip())
    _customizations_cache.set(key, result)
    return result


def get_custom_override(
    owner_kind: str, owner_id: str, scope: str, lang: str
) -> dict | None:
    if "user_customizations" not in db.tables:
        return None
    return db["user_customizations"].find_one(
        owner_kind=owner_kind,
        owner_id=owner_id,
        scope=scope,
        lang=lang,
        deleted_at=None,
    )


def list_custom_overrides(owner_kind: str, owner_id: str) -> list:
    if "user_customizations" not in db.tables:
        return []
    return list(
        db["user_customizations"].find(
            owner_kind=owner_kind, owner_id=owner_id, deleted_at=None
        )
    )


def set_custom_override(
    owner_kind: str, owner_id: str, scope: str, lang: str, code: str
) -> dict:
    from devplacepy.utils import generate_uid

    table = get_table("user_customizations")
    now = datetime.now(timezone.utc).isoformat()
    existing = table.find_one(
        owner_kind=owner_kind, owner_id=owner_id, scope=scope, lang=lang
    )
    if existing:
        record = {
            "id": existing["id"],
            "code": code,
            "enabled": 1,
            "updated_at": now,
            "deleted_at": None,
            "deleted_by": None,
        }
        table.update(record, ["id"])
        result = {**existing, **record}
    else:
        result = {
            "uid": generate_uid(),
            "owner_kind": owner_kind,
            "owner_id": owner_id,
            "scope": scope,
            "lang": lang,
            "code": code,
            "enabled": 1,
            "created_at": now,
            "updated_at": now,
            "deleted_at": None,
            "deleted_by": None,
        }
        table.insert(result)
    bump_cache_version("customizations")
    return result


def delete_custom_override(
    owner_kind: str,
    owner_id: str,
    scope: str | None = None,
    lang: str | None = None,
    deleted_by: str | None = None,
) -> int:
    if "user_customizations" not in db.tables:
        return 0
    criteria: dict = {"owner_kind": owner_kind, "owner_id": owner_id}
    if scope is not None:
        criteria["scope"] = scope
    if lang is not None:
        criteria["lang"] = lang
    count = soft_delete(
        "user_customizations", deleted_by or f"{owner_kind}:{owner_id}", **criteria
    )
    bump_cache_version("customizations")
    return int(count)


NOTIFICATION_TYPES = [
    {"key": "comment", "label": "Comments", "description": "Someone comments on your post"},
    {"key": "reply", "label": "Replies", "description": "Someone replies to your comment"},
    {"key": "mention", "label": "Mentions", "description": "Someone mentions you with @username"},
    {"key": "vote", "label": "Upvotes", "description": "Someone ++'d your content"},
    {"key": "follow", "label": "Followers", "description": "Someone starts following you"},
    {"key": "message", "label": "Direct messages", "description": "Someone sends you a message"},
    {"key": "badge", "label": "Badges", "description": "You earn a badge"},
    {"key": "level", "label": "Level-ups", "description": "You reach a new level"},
    {"key": "issue", "label": "Issue tracker", "description": "Updates on issue reports you filed"},
]

NOTIFICATION_CHANNELS = ("in_app", "push")
_NOTIFICATION_CHANNEL_COLUMNS = {"in_app": "in_app_enabled", "push": "push_enabled"}
_NOTIFICATION_TYPE_KEYS = {entry["key"] for entry in NOTIFICATION_TYPES}

_notification_prefs_cache = TTLCache(ttl=300, max_size=500)


def _notification_default(notification_type: str, channel: str) -> bool:
    return get_int_setting(f"notif_default_{notification_type}_{channel}", 1) != 0


def get_notification_default(notification_type: str, channel: str) -> bool:
    return _notification_default(notification_type, channel)


def set_notification_default(
    notification_type: str, channel: str, enabled: bool
) -> None:
    if channel not in _NOTIFICATION_CHANNEL_COLUMNS:
        raise ValueError(f"Unknown notification channel: {channel}")
    set_setting(f"notif_default_{notification_type}_{channel}", "1" if enabled else "0")


def _notification_overrides(user_uid: str) -> dict:
    sync_local_cache("notif_prefs", _notification_prefs_cache)
    cached = _notification_prefs_cache.get(user_uid)
    if cached is not None:
        return cached
    overrides: dict = {}
    if "notification_preferences" in db.tables:
        for row in db["notification_preferences"].find(
            user_uid=user_uid, deleted_at=None
        ):
            overrides[row["notification_type"]] = {
                "in_app": bool(row.get("in_app_enabled", 1)),
                "push": bool(row.get("push_enabled", 1)),
            }
    _notification_prefs_cache.set(user_uid, overrides)
    return overrides


def notification_enabled(user_uid: str, notification_type: str, channel: str) -> bool:
    if channel not in _NOTIFICATION_CHANNEL_COLUMNS:
        return True
    override = _notification_overrides(user_uid).get(notification_type)
    if override is not None:
        return bool(override[channel])
    return _notification_default(notification_type, channel)


def get_notification_prefs(user_uid: str) -> list:
    overrides = _notification_overrides(user_uid)
    result = []
    for entry in NOTIFICATION_TYPES:
        key = entry["key"]
        override = overrides.get(key)
        in_app = (
            bool(override["in_app"]) if override else _notification_default(key, "in_app")
        )
        push = bool(override["push"]) if override else _notification_default(key, "push")
        result.append(
            {
                "key": key,
                "label": entry["label"],
                "description": entry["description"],
                "in_app": in_app,
                "push": push,
                "customized": override is not None,
            }
        )
    return result


def set_notification_pref(
    user_uid: str, notification_type: str, channel: str, enabled: bool
) -> dict:
    if notification_type not in _NOTIFICATION_TYPE_KEYS:
        raise ValueError(f"Unknown notification type: {notification_type}")
    if channel not in _NOTIFICATION_CHANNEL_COLUMNS:
        raise ValueError(f"Unknown notification channel: {channel}")
    from devplacepy.utils import generate_uid

    table = get_table("notification_preferences")
    now = datetime.now(timezone.utc).isoformat()
    existing = table.find_one(user_uid=user_uid, notification_type=notification_type)
    if existing:
        values = {
            "in_app": bool(existing.get("in_app_enabled", 1)),
            "push": bool(existing.get("push_enabled", 1)),
        }
    else:
        values = {
            "in_app": _notification_default(notification_type, "in_app"),
            "push": _notification_default(notification_type, "push"),
        }
    values[channel] = enabled
    if existing:
        record = {
            "id": existing["id"],
            "in_app_enabled": 1 if values["in_app"] else 0,
            "push_enabled": 1 if values["push"] else 0,
            "updated_at": now,
            "deleted_at": None,
            "deleted_by": None,
        }
        table.update(record, ["id"])
        result = {**existing, **record}
    else:
        result = {
            "uid": generate_uid(),
            "user_uid": user_uid,
            "notification_type": notification_type,
            "in_app_enabled": 1 if values["in_app"] else 0,
            "push_enabled": 1 if values["push"] else 0,
            "created_at": now,
            "updated_at": now,
            "deleted_at": None,
            "deleted_by": None,
        }
        table.insert(result)
    bump_cache_version("notif_prefs")
    return result


def reset_notification_prefs(user_uid: str, deleted_by: str | None = None) -> int:
    if "notification_preferences" not in db.tables:
        return 0
    count = soft_delete(
        "notification_preferences", deleted_by or f"user:{user_uid}", user_uid=user_uid
    )
    bump_cache_version("notif_prefs")
    return int(count)


_stats_cache = TTLCache(ttl=30, max_size=50)


def get_site_stats() -> dict:
    cached = _stats_cache.get("site")
    if cached is not None:
        return cached
    today_start = (
        datetime.now(timezone.utc)
        .replace(hour=0, minute=0, second=0, microsecond=0)
        .isoformat()
    )
    stats = {
        "total_members": db["users"].count() if "users" in db.tables else 0,
        "posts_today": db["posts"].count(
            created_at={">=": today_start}, deleted_at=None
        )
        if "posts" in db.tables
        else 0,
        "total_projects": db["projects"].count(deleted_at=None)
        if "projects" in db.tables
        else 0,
        "total_gists": db["gists"].count(deleted_at=None)
        if "gists" in db.tables
        else 0,
    }
    _stats_cache.set("site", stats)
    return stats


_analytics_cache = TTLCache(ttl=300, max_size=50)


def get_platform_analytics(top_n: int = 10) -> dict:
    top_n = max(1, min(int(top_n or 10), 50))
    cache_key = f"analytics:{top_n}"
    cached = _analytics_cache.get(cache_key)
    if cached is not None:
        return cached

    now = datetime.now(timezone.utc)
    d1 = (now - timedelta(days=1)).isoformat()
    d7 = (now - timedelta(days=7)).isoformat()
    d30 = (now - timedelta(days=30)).isoformat()

    active_24h = active_7d = active_30d = 0
    sources = [
        table
        for table in ("posts", "comments", "gists", "projects")
        if table in db.tables
    ]
    if sources:
        union = " UNION ALL ".join(
            f"SELECT user_uid, created_at FROM {table} WHERE deleted_at IS NULL"
            for table in sources
        )
        rows = db.query(
            "SELECT "
            "COUNT(DISTINCT CASE WHEN created_at >= :d1 THEN user_uid END) AS a1, "
            "COUNT(DISTINCT CASE WHEN created_at >= :d7 THEN user_uid END) AS a7, "
            "COUNT(DISTINCT CASE WHEN created_at >= :d30 THEN user_uid END) AS a30 "
            f"FROM ({union})",
            d1=d1,
            d7=d7,
            d30=d30,
        )
        for row in rows:
            active_24h = row["a1"] or 0
            active_7d = row["a7"] or 0
            active_30d = row["a30"] or 0

    signed_in_now = 0
    if "sessions" in db.tables:
        for row in db.query(
            "SELECT COUNT(DISTINCT user_uid) AS n FROM sessions WHERE expires_at > :now AND deleted_at IS NULL",
            now=now.isoformat(),
        ):
            signed_in_now = row["n"] or 0

    def _users_created_since(cutoff: str) -> int:
        return (
            db["users"].count(created_at={">=": cutoff}) if "users" in db.tables else 0
        )

    site = get_site_stats()
    totals = {
        "posts": db["posts"].count(deleted_at=None) if "posts" in db.tables else 0,
        "comments": db["comments"].count(deleted_at=None)
        if "comments" in db.tables
        else 0,
        "gists": site["total_gists"],
        "projects": site["total_projects"],
        "news": db["news"].count(deleted_at=None) if "news" in db.tables else 0,
    }
    top_authors = [
        {"username": author.get("username", ""), "stars": author.get("stars", 0)}
        for author in get_top_authors(top_n)
    ]

    result = {
        "total_members": site["total_members"],
        "active_24h": active_24h,
        "active_7d": active_7d,
        "active_30d": active_30d,
        "signed_in_now": signed_in_now,
        "new_24h": _users_created_since(d1),
        "new_7d": _users_created_since(d7),
        "new_30d": _users_created_since(d30),
        "posts_today": site["posts_today"],
        "totals": totals,
        "top_authors": top_authors,
        "active_definition": (
            "Active = created a post, comment, gist, or project within the window. "
            "Automated/bot accounts are not separately flagged."
        ),
        "signed_in_definition": (
            "signed_in_now = distinct members holding an unexpired session cookie, i.e. who "
            "logged in within the session lifetime (default 7 days, up to 30 with remember-me) "
            "and have not logged out. It is NOT a real-time presence/online count - the platform "
            "does not track per-request last-seen activity - so it is normally a large fraction "
            "of recently active members. Do not describe it as 'currently online' or 'logged in "
            "right now'."
        ),
    }
    _analytics_cache.set(cache_key, result)
    return result


_activity_cache = TTLCache(ttl=300, max_size=1000)

_ACTIVITY_TABLES = ("posts", "comments", "gists", "projects")


def get_activity_calendar(user_uid: str) -> dict:
    cached = _activity_cache.get(user_uid)
    if cached is not None:
        return cached
    sources = [table for table in _ACTIVITY_TABLES if table in db.tables]
    calendar: dict[str, int] = {}
    if sources:
        cutoff = (datetime.now(timezone.utc) - timedelta(days=364)).date().isoformat()
        union = " UNION ALL ".join(
            f"SELECT created_at FROM {table} WHERE user_uid = :u AND deleted_at IS NULL"
            for table in sources
        )
        rows = db.query(
            f"SELECT date(created_at) AS day, COUNT(*) AS c FROM ({union}) WHERE date(created_at) >= :cutoff GROUP BY day",
            u=user_uid,
            cutoff=cutoff,
        )
        for row in rows:
            if row["day"]:
                calendar[row["day"]] = row["c"]
    _activity_cache.set(user_uid, calendar)
    return calendar


def _activity_level(count: int) -> int:
    if count <= 0:
        return 0
    if count == 1:
        return 1
    if count <= 3:
        return 2
    if count <= 6:
        return 3
    return 4


def get_first_activity_date(user_uid: str):
    sources = [table for table in _ACTIVITY_TABLES if table in db.tables]
    if not sources:
        return None
    union = " UNION ALL ".join(
        f"SELECT MIN(created_at) AS m FROM {table} WHERE user_uid = :u AND deleted_at IS NULL"
        for table in sources
    )
    for row in db.query(f"SELECT MIN(m) AS first FROM ({union})", u=user_uid):
        if row["first"]:
            return datetime.fromisoformat(row["first"]).date()
    return None


HEATMAP_WEEKS = 53


def get_activity_heatmap(user_uid: str) -> list:
    calendar = get_activity_calendar(user_uid)
    today = datetime.now(timezone.utc).date()
    week_start = today - timedelta(days=today.weekday())
    start = week_start - timedelta(weeks=HEATMAP_WEEKS - 1)
    first = get_first_activity_date(user_uid)
    if first:
        first_week = first - timedelta(days=first.weekday())
        if first_week > start:
            start = first_week
    weeks = []
    for w in range(HEATMAP_WEEKS):
        week = []
        for d in range(7):
            day = start + timedelta(days=w * 7 + d)
            iso = day.isoformat()
            count = calendar.get(iso, 0)
            week.append({"date": iso, "count": count, "level": _activity_level(count)})
        weeks.append(week)
    return weeks


def get_activity_months(weeks: list) -> list:
    if not weeks:
        return []
    last = len(weeks) - 1
    labels = []
    for i in range(6):
        column = round(i * last / 5)
        iso = weeks[column][0]["date"]
        labels.append(datetime.fromisoformat(iso).strftime("%b"))
    return labels


def get_streaks(user_uid: str) -> dict:
    calendar = get_activity_calendar(user_uid)
    if not calendar:
        return {"current": 0, "longest": 0}
    dates = sorted(datetime.fromisoformat(day).date() for day in calendar)
    date_set = set(dates)
    longest = 1
    run = 1
    for index in range(1, len(dates)):
        if (dates[index] - dates[index - 1]).days == 1:
            run += 1
        else:
            run = 1
        longest = max(longest, run)
    today = datetime.now(timezone.utc).date()
    cursor = today
    if today not in date_set and (today - timedelta(days=1)) in date_set:
        cursor = today - timedelta(days=1)
    current = 0
    while cursor in date_set:
        current += 1
        cursor = cursor - timedelta(days=1)
    return {"current": current, "longest": longest}


_gist_languages_cache = TTLCache(ttl=60, max_size=200)


def get_gist_languages() -> set[str]:
    cached = _gist_languages_cache.get("codes")
    if cached is not None:
        return cached
    codes: set[str] = set()
    if "gists" in db.tables:
        for row in db.query(
            "SELECT DISTINCT language FROM gists WHERE deleted_at IS NULL"
        ):
            language = row.get("language")
            if language:
                codes.add(language)
    _gist_languages_cache.set("codes", codes)
    return codes


VOTABLE_TARGETS: dict[str, str] = {
    "post": "posts",
    "project": "projects",
    "gist": "gists",
    "comment": "comments",
}

STAR_TARGETS: set[str] = {"post", "project", "gist"}

_authors_cache = TTLCache(ttl=60, max_size=200)


def _ranked_authors() -> list:
    cached = _authors_cache.get("ranked")
    if cached is not None:
        return cached
    sources = [
        (target_type, table_name)
        for target_type, table_name in VOTABLE_TARGETS.items()
        if table_name in db.tables
    ]
    if "votes" not in db.tables or not sources:
        _authors_cache.set("ranked", [])
        return []
    target_union = " UNION ALL ".join(
        f"SELECT uid, user_uid, '{target_type}' AS target_type FROM {table_name} WHERE deleted_at IS NULL"
        for target_type, table_name in sources
    )
    rows = db.query(
        f"SELECT t.user_uid, SUM(v.value) AS total "
        f"FROM votes v JOIN ({target_union}) t ON v.target_uid = t.uid AND v.target_type = t.target_type "
        f"WHERE v.deleted_at IS NULL "
        f"GROUP BY t.user_uid HAVING SUM(v.value) > 0 ORDER BY total DESC"
    )
    ranked = [(row["user_uid"], row["total"]) for row in rows]
    users_map = get_users_by_uids([uid for uid, _ in ranked])
    authors = []
    for uid, total in ranked:
        user = users_map.get(uid)
        if user:
            author = dict(user)
            author["stars"] = total
            authors.append(author)
    _authors_cache.set("ranked", authors)
    return authors


def get_top_authors(limit: int = 5) -> list:
    return _ranked_authors()[:limit]


def get_leaderboard(limit: int = 50, offset: int = 0) -> list:
    sliced = _ranked_authors()[offset : offset + limit]
    leaderboard = []
    for position, author in enumerate(sliced, start=offset + 1):
        entry = dict(author)
        entry["rank"] = position
        leaderboard.append(entry)
    return leaderboard


def get_user_rank(user_uid: str):
    for position, author in enumerate(_ranked_authors(), start=1):
        if author["uid"] == user_uid:
            return position
    return None


def get_user_stars(user_uid: str) -> int:
    if "votes" not in db.tables:
        return 0
    target_union = " UNION ALL ".join(
        f"SELECT uid, '{target_type}' AS target_type FROM {table_name} WHERE user_uid = :u AND deleted_at IS NULL"
        for target_type, table_name in VOTABLE_TARGETS.items()
        if table_name in db.tables
    )
    if not target_union:
        return 0
    for row in db.query(
        f"SELECT COALESCE(SUM(v.value), 0) AS s "
        f"FROM votes v JOIN ({target_union}) t ON v.target_uid = t.uid AND v.target_type = t.target_type "
        f"WHERE v.deleted_at IS NULL",
        u=user_uid,
    ):
        return row["s"] or 0
    return 0


def resolve_by_slug(table, slug, include_deleted=False):
    has_soft_delete = table.has_column("deleted_at")
    flt = {} if include_deleted or not has_soft_delete else {"deleted_at": None}
    entry = table.find_one(slug=slug, **flt)
    if not entry:
        entry = table.find_one(uid=slug, **flt)
    return entry


def record_fork(
    source_project_uid: str, forked_project_uid: str, forked_by_uid: str
) -> None:
    from devplacepy.utils import generate_uid

    get_table("project_forks").insert(
        {
            "uid": generate_uid(),
            "source_project_uid": source_project_uid,
            "forked_project_uid": forked_project_uid,
            "forked_by_uid": forked_by_uid,
            "created_at": datetime.now(timezone.utc).isoformat(),
            "deleted_at": None,
            "deleted_by": None,
        }
    )


def get_fork_parent(forked_project_uid: str) -> dict | None:
    if "project_forks" not in db.tables:
        return None
    relation = get_table("project_forks").find_one(
        forked_project_uid=forked_project_uid, deleted_at=None
    )
    if not relation:
        return None
    return get_table("projects").find_one(
        uid=relation["source_project_uid"], deleted_at=None
    )


def count_forks(source_project_uid: str) -> int:
    if "project_forks" not in db.tables:
        return 0
    return get_table("project_forks").count(
        source_project_uid=source_project_uid, deleted_at=None
    )


def soft_delete_fork_relations(project_uid: str, deleted_by: str) -> None:
    if "project_forks" not in db.tables:
        return
    stamp = _now_iso()
    soft_delete("project_forks", deleted_by, stamp=stamp, forked_project_uid=project_uid)
    soft_delete("project_forks", deleted_by, stamp=stamp, source_project_uid=project_uid)


def delete_fork_relations(project_uid: str) -> None:
    if "project_forks" not in db.tables:
        return
    forks = get_table("project_forks")
    forks.delete(forked_project_uid=project_uid)
    forks.delete(source_project_uid=project_uid)


def resolve_object_url(target_type: str, target_uid: str) -> str:
    if target_type == "post":
        post = resolve_by_slug(get_table("posts"), target_uid)
        return f"/posts/{post['slug'] or post['uid']}" if post else "/feed"
    if target_type == "project":
        project = resolve_by_slug(get_table("projects"), target_uid)
        return (
            f"/projects/{project['slug'] or project['uid']}" if project else "/projects"
        )
    if target_type == "news":
        article = resolve_by_slug(get_table("news"), target_uid)
        if article:
            return f"/news/{article.get('slug', '') or article['uid']}"
        return "/news"
    if target_type == "issue":
        return f"/issues?highlight={target_uid}"
    if target_type == "gist":
        gist = resolve_by_slug(get_table("gists"), target_uid)
        return f"/gists/{gist['slug'] or gist['uid']}" if gist else "/gists"
    if target_type == "comment":
        comment = get_table("comments").find_one(uid=target_uid, deleted_at=None)
        if not comment:
            return "/feed"
        parent_url = resolve_object_url(
            comment.get("target_type", "post"),
            comment.get("target_uid") or comment.get("post_uid", ""),
        )
        return f"{parent_url}#comment-{target_uid}"
    return "/feed"


def update_target_stars(target_type: str, target_uid: str, net_stars: int) -> None:
    table_name = VOTABLE_TARGETS.get(target_type)
    if not table_name:
        return
    if target_type in STAR_TARGETS:
        get_table(table_name).update({"uid": target_uid, "stars": net_stars}, ["uid"])
    _authors_cache.clear()


def soft_delete_engagement(target_type: str, target_uids: list, deleted_by: str) -> None:
    uids = [uid for uid in (target_uids or []) if uid]
    if not uids:
        return
    stamp = _now_iso()
    soft_delete_in(
        "reactions", "target_uid", uids, deleted_by, stamp=stamp, target_type=target_type
    )
    soft_delete_in(
        "bookmarks", "target_uid", uids, deleted_by, stamp=stamp, target_type=target_type
    )
    if target_type == "post" and "polls" in db.tables:
        for uid in uids:
            for poll in db["polls"].find(post_uid=uid, deleted_at=None):
                soft_delete("poll_votes", deleted_by, stamp=stamp, poll_uid=poll["uid"])
                soft_delete("poll_options", deleted_by, stamp=stamp, poll_uid=poll["uid"])
            soft_delete("polls", deleted_by, stamp=stamp, post_uid=uid)


def delete_engagement(target_type: str, target_uids: list) -> None:
    uids = [uid for uid in (target_uids or []) if uid]
    if not uids:
        return
    if "reactions" in db.tables:
        placeholders, params = _in_clause(uids)
        params["tt"] = target_type
        db.query(
            f"DELETE FROM reactions WHERE target_type=:tt AND target_uid IN ({placeholders})",
            **params,
        )
    if "bookmarks" in db.tables:
        placeholders, params = _in_clause(uids)
        params["tt"] = target_type
        db.query(
            f"DELETE FROM bookmarks WHERE target_type=:tt AND target_uid IN ({placeholders})",
            **params,
        )
    if target_type == "post" and "polls" in db.tables:
        for uid in uids:
            for poll in db["polls"].find(post_uid=uid):
                if "poll_votes" in db.tables:
                    db["poll_votes"].delete(poll_uid=poll["uid"])
                if "poll_options" in db.tables:
                    db["poll_options"].delete(poll_uid=poll["uid"])
            db["polls"].delete(post_uid=uid)


def get_target_owner_uid(target_type: str, target_uid: str) -> str | None:
    table_name = VOTABLE_TARGETS.get(target_type)
    if not table_name:
        return None
    row = get_table(table_name).find_one(uid=target_uid, deleted_at=None)
    return row["user_uid"] if row else None


PAGE_SIZE = 25


def text_search_clause(table, search, fields=("title", "description")):
    if not search or not search.strip() or not table.exists:
        return None
    columns = table.table.columns
    like = f"%{search.strip()}%"
    matches = [columns[field].ilike(like) for field in fields if field in columns]
    return or_(*matches) if matches else None


def paginate(
    table, *clauses, before=None, order=None, cursor_field="created_at", **filters
):
    order = order or ["-" + cursor_field]
    clauses = list(clauses)
    if table.has_column("deleted_at") and "deleted_at" not in filters:
        clauses.append(table.table.columns.deleted_at.is_(None))
    if before:
        clauses.append(table.table.columns[cursor_field] < before)
    rows = list(table.find(*clauses, **filters, order_by=order, _limit=PAGE_SIZE + 1))
    has_more = len(rows) > PAGE_SIZE
    rows = rows[:PAGE_SIZE]
    next_cursor = rows[-1][cursor_field] if has_more and rows else None
    return rows, next_cursor


def interleave_by_author(rows, uid_key="user_uid"):
    remaining = list(rows)
    spread = []
    last_owner = object()
    while remaining:
        pick = next(
            (
                index
                for index, row in enumerate(remaining)
                if row.get(uid_key) != last_owner
            ),
            0,
        )
        row = remaining.pop(pick)
        spread.append(row)
        last_owner = row.get(uid_key)
    return spread


def paginate_diverse(
    table,
    *clauses,
    before=None,
    order=None,
    cursor_field="created_at",
    uid_key="user_uid",
    **filters,
):
    rows, next_cursor = paginate(
        table,
        *clauses,
        before=before,
        order=order,
        cursor_field=cursor_field,
        **filters,
    )
    return interleave_by_author(rows, uid_key=uid_key), next_cursor


def get_user_post_count(user_uid: str) -> int:
    if "posts" not in db.tables:
        return 0
    return get_table("posts").count(user_uid=user_uid, deleted_at=None)


def build_pagination(page, total, per_page=25):
    total_pages = max(1, __import__("math").ceil(total / per_page))
    page = max(1, min(page, total_pages))
    return {
        "page": page,
        "per_page": per_page,
        "total": total,
        "total_pages": total_pages,
        "has_prev": page > 1,
        "has_next": page < total_pages,
        "prev_page": page - 1,
        "next_page": page + 1,
    }


def get_user_media(user_uid: str, page: int = 1, per_page: int = 24) -> tuple:
    if "attachments" not in db.tables:
        return [], build_pagination(page, 0, per_page)
    from devplacepy.attachments import _row_to_attachment

    total = list(
        db.query(
            "SELECT COUNT(*) AS n FROM attachments "
            "WHERE user_uid=:u AND target_type != '' AND deleted_at IS NULL",
            u=user_uid,
        )
    )[0]["n"]
    pagination = build_pagination(page, total, per_page)
    offset = (pagination["page"] - 1) * pagination["per_page"]
    rows = db.query(
        "SELECT * FROM attachments "
        "WHERE user_uid=:u AND target_type != '' AND deleted_at IS NULL "
        "ORDER BY created_at DESC LIMIT :limit OFFSET :offset",
        u=user_uid,
        limit=pagination["per_page"],
        offset=offset,
    )
    items = []
    for row in rows:
        item = _row_to_attachment(row)
        item["target_url"] = resolve_object_url(item["target_type"], item["target_uid"])
        items.append(item)
    return items, pagination


def get_deleted_media(page: int = 1, per_page: int = 24) -> tuple:
    if "attachments" not in db.tables:
        return [], build_pagination(page, 0, per_page)
    from devplacepy.attachments import _row_to_attachment

    total = list(
        db.query("SELECT COUNT(*) AS n FROM attachments WHERE deleted_at IS NOT NULL")
    )[0]["n"]
    pagination = build_pagination(page, total, per_page)
    offset = (pagination["page"] - 1) * pagination["per_page"]
    rows = db.query(
        "SELECT * FROM attachments WHERE deleted_at IS NOT NULL "
        "ORDER BY deleted_at DESC LIMIT :limit OFFSET :offset",
        limit=pagination["per_page"],
        offset=offset,
    )
    usernames = {u["uid"]: u["username"] for u in get_table("users").find()}
    items = []
    for row in rows:
        item = _row_to_attachment(row)
        item["target_url"] = resolve_object_url(item["target_type"], item["target_uid"])
        item["deleted_at"] = row.get("deleted_at", "")
        item["uploader"] = usernames.get(row.get("user_uid"), "unknown")
        items.append(item)
    return items, pagination


def get_follow_counts(user_uid: str) -> dict:
    if "follows" not in db.tables:
        return {"followers": 0, "following": 0}
    follows = get_table("follows")
    return {
        "followers": follows.count(following_uid=user_uid, deleted_at=None),
        "following": follows.count(follower_uid=user_uid, deleted_at=None),
    }


def get_follow_list(
    user_uid: str, mode: str, page: int = 1, per_page: int = 25
) -> tuple:
    if "follows" not in db.tables:
        return [], build_pagination(page, 0, per_page)
    follows = get_table("follows")
    key = "following_uid" if mode == "followers" else "follower_uid"
    other = "follower_uid" if mode == "followers" else "following_uid"
    total = follows.count(deleted_at=None, **{key: user_uid})
    pagination = build_pagination(page, total, per_page)
    offset = (pagination["page"] - 1) * pagination["per_page"]
    rows = list(
        follows.find(
            order_by=["-created_at"],
            _limit=pagination["per_page"],
            _offset=offset,
            deleted_at=None,
            **{key: user_uid},
        )
    )
    users_map = get_users_by_uids([row[other] for row in rows])
    people = []
    for row in rows:
        person = users_map.get(row[other])
        if person:
            people.append(
                {
                    "uid": person["uid"],
                    "username": person["username"],
                    "bio": (person.get("bio") or "")[:140],
                    "followed_at": row.get("created_at"),
                }
            )
    return people, pagination


def get_following_among(follower_uid: str, target_uids: list) -> set:
    if not follower_uid or not target_uids or "follows" not in db.tables:
        return set()
    placeholders, params = _in_clause(target_uids)
    params["f"] = follower_uid
    rows = db.query(
        f"SELECT following_uid FROM follows WHERE follower_uid = :f AND following_uid IN ({placeholders}) AND deleted_at IS NULL",
        **params,
    )
    return {row["following_uid"] for row in rows}


def get_daily_topic():
    if "news" in db.tables:
        article = db["news"].find_one(
            status="published", deleted_at=None, order_by=["-synced_at"]
        )
        if article:
            desc = (article.get("description") or "")[:200] or (
                article.get("content") or ""
            )[:200]
            return {
                "title": article.get("title", ""),
                "summary": desc,
                "slug": article.get("slug", ""),
                "url": article.get("url", ""),
                "image_url": article.get("image_url", ""),
            }
    return {
        "title": "Welcome to DevPlace",
        "summary": "Stay tuned for the latest dev news.",
    }


def get_featured_news(limit=5):
    if "news" not in db.tables:
        return []
    from devplacepy.utils import time_ago

    rows = list(
        db["news"].find(
            show_on_landing=1, deleted_at=None, order_by=["-synced_at"], _limit=limit
        )
    )
    articles = []
    for article in rows:
        summary = (article.get("description") or "")[:120] or (
            article.get("content") or ""
        )[:120]
        articles.append(
            {
                "title": article.get("title", ""),
                "summary": summary,
                "slug": article.get("slug", ""),
                "url": article.get("url", ""),
                "source_name": article.get("source_name", ""),
                "time_ago": time_ago(article["synced_at"])
                if article.get("synced_at")
                else "",
            }
        )
    return articles


def _ds_now() -> str:
    return datetime.now(timezone.utc).isoformat()


def create_deepsearch_session(
    uid: str,
    owner_kind: str,
    owner_id: str,
    query: str,
    depth: int,
    max_pages: int,
    collection: str,
) -> None:
    get_table("deepsearch_sessions").insert(
        {
            "uid": uid,
            "owner_kind": owner_kind,
            "owner_id": owner_id,
            "query": query,
            "status": "pending",
            "depth": depth,
            "max_pages": max_pages,
            "score": 0,
            "confidence": 0.0,
            "source_diversity": 0.0,
            "page_count": 0,
            "chunk_count": 0,
            "collection": collection,
            "summary": "",
            "created_at": _ds_now(),
            "completed_at": "",
            "deleted_at": None,
            "deleted_by": None,
        }
    )


def update_deepsearch_session(uid: str, fields: dict) -> None:
    if "deepsearch_sessions" not in db.tables:
        return
    payload = dict(fields)
    payload["uid"] = uid
    get_table("deepsearch_sessions").update(payload, ["uid"])


def get_deepsearch_session(uid: str) -> dict | None:
    if "deepsearch_sessions" not in db.tables:
        return None
    return get_table("deepsearch_sessions").find_one(uid=uid, deleted_at=None)


def add_deepsearch_message(
    uid: str, session_uid: str, role: str, content: str, citations: str = ""
) -> None:
    get_table("deepsearch_messages").insert(
        {
            "uid": uid,
            "session_uid": session_uid,
            "role": role,
            "content": content,
            "citations": citations,
            "created_at": _ds_now(),
            "deleted_at": None,
            "deleted_by": None,
        }
    )


def get_deepsearch_messages(session_uid: str, limit: int = 50) -> list[dict]:
    if "deepsearch_messages" not in db.tables:
        return []
    return list(
        get_table("deepsearch_messages").find(
            session_uid=session_uid,
            deleted_at=None,
            order_by=["created_at"],
            _limit=limit,
        )
    )


def get_cached_deepsearch_url(url_hash: str) -> dict | None:
    if "deepsearch_url_cache" not in db.tables:
        return None
    return get_table("deepsearch_url_cache").find_one(url_hash=url_hash)


def upsert_deepsearch_url_cache(
    url_hash: str,
    url: str,
    title: str,
    content_hash: str,
    status: int,
    byte_size: int,
) -> None:
    table = get_table("deepsearch_url_cache")
    existing = table.find_one(url_hash=url_hash)
    row = {
        "url_hash": url_hash,
        "url": url,
        "title": title,
        "content_hash": content_hash,
        "status": status,
        "byte_size": byte_size,
        "fetched_at": _ds_now(),
    }
    if existing:
        row["uid"] = existing["uid"]
        table.update(row, ["uid"])
    else:
        from devplacepy.utils import generate_uid

        row["uid"] = generate_uid()
        table.insert(row)
