from __future__ import annotations

import html
import logging
import time

from telebot.apihelper import ApiTelegramException

from src import keyboards, state
from src.config import settings
from src.repositories import repo
from src.services import media_service
from src.services import log_service
from src.telegram_text import text_with_custom_emoji
from src.validation import validate_chat_id, validate_html, validate_url

logger = logging.getLogger(__name__)


def is_admin(message) -> bool:
    return message.from_user.id in settings.admin_ids


def _delete_input_message(bot, message) -> None:
    """Rapikan chat admin dengan menghapus pesan input setelah berhasil dibaca."""
    try:
        bot.delete_message(message.chat.id, message.message_id)
    except Exception:
        pass


def _panel_message_id(chat_id: int) -> int | None:
    session = state.get(chat_id)
    if not session:
        return None
    return session.get("data", {}).get("panel_message_id")


def _edit_panel(bot, chat_id: int, text: str, reply_markup=None):
    """Edit panel asal yang digunakan saat tombol proses ditekan."""
    message_id = _panel_message_id(chat_id)
    if not message_id:
        return bot.send_message(
            chat_id,
            text,
            parse_mode="HTML",
            reply_markup=reply_markup,
            disable_web_page_preview=True,
        )

    try:
        return bot.edit_message_text(
            text=text,
            chat_id=chat_id,
            message_id=message_id,
            parse_mode="HTML",
            reply_markup=reply_markup,
            disable_web_page_preview=True,
        )
    except ApiTelegramException as exc:
        if "message is not modified" in str(exc).lower():
            return None
        logger.warning("Gagal mengedit panel input: %s", exc)
        return bot.send_message(
            chat_id,
            text,
            parse_mode="HTML",
            reply_markup=reply_markup,
            disable_web_page_preview=True,
        )


def _finish_and_clear(bot, chat_id: int, text: str, reply_markup=None):
    result = _edit_panel(bot, chat_id, text, reply_markup)
    state.clear(chat_id)
    return result


def _finish_upload(bot, chat_id: int, category_id: int):
    session = state.get(chat_id)
    if not session or session.get("flow") != "upload":
        return bot.send_message(
            chat_id,
            "❌ Sesi upload sudah berakhir. Silakan mulai upload kembali.",
            reply_markup=keyboards.back(),
        )

    data = session.get("data", {})

    # Caption boleh berupa string kosong ketika admin mengirim tanda "-".
    # Karena itu, validasi caption tidak boleh memakai pengecekan truthy/falsy.
    required_non_empty = ("file_id", "thumb", "title", "filename")
    missing = [key for key in required_non_empty if not data.get(key)]

    # Pastikan tahap caption memang sudah dilewati.
    if not data.get("caption_set"):
        missing.append("caption")
    if missing:
        return _finish_and_clear(
            bot,
            chat_id,
            "❌ Data upload tidak lengkap: " + ", ".join(missing),
            keyboards.back(),
        )

    code = media_service.make_code()
    media_id = repo.add_media(
        code,
        data["file_id"],
        data["thumb"],
        data["title"],
        data["caption"],
        data["filename"],
        category_id,
    )

    publish_status = "✅ Berhasil diposting ke channel."
    try:
        posted = media_service.publish(bot, media_id)
        if not posted:
            raise RuntimeError("Posting channel tidak menghasilkan pesan")
    except Exception as exc:
        logger.exception("Media tersimpan tetapi gagal diposting")
        publish_status = (
            "⚠️ Media tersimpan, tetapi gagal diposting ke channel.\n"
            f"<code>{html.escape(str(exc))}</code>"
        )

    return _finish_and_clear(
        bot,
        chat_id,
        (
            "✅ <b>Upload berhasil.</b>\n\n"
            f"Kode: <code>{html.escape(code)}</code>\n"
            f"Link: {media_service.deep_link(bot, code)}\n\n"
            f"{publish_status}"
        ),
        keyboards.back("adm:media"),
    )


def _extract_media(message):
    """Ekstrak content_type dan file_id dari pesan media (untuk step waiting_media)."""
    if message.video:
        return "video", message.video.file_id
    if message.photo:
        return "photo", message.photo[-1].file_id
    if message.document:
        return "document", message.document.file_id
    return None, None


def _is_owner(user_id: int) -> bool:
    return bool(settings.admin_ids) and user_id == min(settings.admin_ids)


def _validate_button_url(url: str, user_id: int) -> str:
    """
    Non-owner hanya boleh @username.
    Owner boleh URL lengkap (http/https).
    """
    url = url.strip()
    if url.startswith("@"):
        return f"https://t.me/{url.lstrip('@')}"
    if not _is_owner(user_id):
        raise ValueError(
            "Admin hanya boleh memasukkan username @channel.\n"
            "Contoh: <code>@namasaluran</code>"
        )
    if not url.startswith(("http://", "https://")):
        raise ValueError("URL tidak valid. Gunakan format <code>https://</code> atau <code>@username</code>.")
    return url


def _save_broadcast_template(data: dict, tpl_id: int | None = None) -> None:
    """Simpan atau perbarui template dari data sesi."""
    content_type = data.get("content_type", "text")
    file_id = data.get("file_id") or ""
    caption = data.get("caption", "")
    button_text = data.get("button_text", "")
    button_url = data.get("button_url", "")
    if tpl_id is None:
        repo.add_broadcast_template(data["tpl_name"], content_type, file_id, caption, button_text, button_url)
    else:
        repo.update_broadcast_template_content(tpl_id, content_type, file_id, caption, button_text, button_url)


def register(bot):
    @bot.message_handler(
        content_types=["video", "photo", "document", "text"],
        func=lambda message: is_admin(message) and state.get(message.chat.id) is not None,
    )
    def input_handler(message):
        session = state.get(message.chat.id)
        if not session:
            return

        chat_id = message.chat.id
        flow = session["flow"]
        step = session["step"]
        data = session["data"]

        try:
            if flow == "upload":
                if step == "video":
                    if not message.video:
                        return _edit_panel(
                            bot,
                            chat_id,
                            "❌ File tidak valid.\n\nKirim file <b>video</b>.",
                            keyboards.cancel(),
                        )
                    state.update(
                        chat_id,
                        "thumbnail",
                        file_id=message.video.file_id,
                        filename=message.video.file_name or "video.mp4",
                    )
                    _delete_input_message(bot, message)
                    return _edit_panel(
                        bot,
                        chat_id,
                        "📤 <b>Upload Media</b>\n\nKirim foto thumbnail.",
                        keyboards.cancel(),
                    )

                if step == "thumbnail":
                    if not message.photo:
                        return _edit_panel(
                            bot,
                            chat_id,
                            "❌ Thumbnail tidak valid.\n\nKirim thumbnail sebagai <b>foto</b>.",
                            keyboards.cancel(),
                        )
                    state.update(chat_id, "title", thumb=message.photo[-1].file_id)
                    _delete_input_message(bot, message)
                    return _edit_panel(
                        bot,
                        chat_id,
                        "📤 <b>Upload Media</b>\n\nKirim judul media.",
                        keyboards.cancel(),
                    )

                if step == "title":
                    title = (message.text or "").strip()
                    if not title:
                        return _edit_panel(
                            bot,
                            chat_id,
                            "❌ Judul tidak boleh kosong.\n\nKirim judul media.",
                            keyboards.cancel(),
                        )
                    state.update(chat_id, "caption", title=title)
                    _delete_input_message(bot, message)
                    return _edit_panel(
                        bot,
                        chat_id,
                        (
                            "📤 <b>Upload Media</b>\n\n"
                            "Kirim caption video.\n"
                            "Kirim <code>-</code> untuk memakai template default."
                        ),
                        keyboards.cancel(),
                    )

                if step == "caption":
                    caption_input = text_with_custom_emoji(message).strip()

                    if caption_input == "-":
                        caption = ""
                        use_default_caption = True
                    else:
                        caption = caption_input
                        use_default_caption = False

                    # Caption kosong karena tanda "-" tidak perlu divalidasi sebagai HTML.
                    if caption:
                        validate_html(caption)

                    state.update(
                        chat_id,
                        "category",
                        caption=caption,
                        caption_set=True,
                        use_default_caption=use_default_caption,
                    )

                    _delete_input_message(bot, message)

                    rows = repo.categories()

                    return _edit_panel(
                        bot,
                        chat_id,
                        "📂 <b>Upload Media</b>\n\nPilih kategori:",
                        keyboards.kb(
                            [[(row["name"], f"media:uploadcat:{row['id']}")] for row in rows]
                            + [
                                [("➕ Buat Kategori Baru", "cat:add_upload")],
                                [("❌ Batal", "adm:cancel")],
                            ]
                        ),
                    )

                if step == "new_category":
                    category_name = (message.text or "").strip()
                    if not category_name:
                        return _edit_panel(
                            bot,
                            chat_id,
                            "❌ Nama kategori tidak boleh kosong.\n\nKirim nama kategori baru.",
                            keyboards.cancel(),
                        )
                    category_id = repo.add_category(category_name)
                    _delete_input_message(bot, message)
                    return _finish_upload(bot, chat_id, category_id)

            if flow == "edit_media":
                field, media_id = data["field"], data["mid"]
                if field == "thumbnail_file_id":
                    if not message.photo:
                        return _edit_panel(
                            bot, chat_id,
                            "❌ Kirim thumbnail baru sebagai <b>foto</b>.",
                            keyboards.cancel(),
                        )
                    value = message.photo[-1].file_id
                else:
                    value = (message.text or "").strip()
                    if not value:
                        return _edit_panel(
                            bot, chat_id,
                            "❌ Nilai tidak boleh kosong. Kirim teks baru.",
                            keyboards.cancel(),
                        )
                if field == "user_caption":
                    value = text_with_custom_emoji(message)
                    if not value.strip():
                        return _edit_panel(
                            bot, chat_id,
                            "❌ Caption tidak boleh kosong. Kirim caption baru.",
                            keyboards.cancel(),
                        )
                    validate_html(value)

                if field == "filename":
                    media_service.replace_file_name(bot, media_id, value, chat_id)
                else:
                    repo.update_media(media_id, field, value)
                media_service.publish(bot, media_id, True)
                _delete_input_message(bot, message)
                return _finish_and_clear(
                    bot,
                    chat_id,
                    "✅ <b>Media diperbarui</b> dan postingan channel dibuat ulang.",
                    keyboards.media_actions(media_id),
                )

            if flow == "category":
                value = (message.text or "").strip()
                if not value:
                    return _edit_panel(bot, chat_id, "❌ Nama kategori tidak boleh kosong.", keyboards.cancel())
                if step == "add":
                    repo.add_category(value)
                    text = "✅ <b>Kategori berhasil ditambahkan.</b>"
                else:
                    repo.rename_category(data["cid"], value)
                    text = "✅ <b>Nama kategori berhasil diubah.</b>"
                _delete_input_message(bot, message)
                return _finish_and_clear(bot, chat_id, text, keyboards.back("adm:categories"))

            if flow == "template":
                value = (message.text or "").strip()
                if not value:
                    return _edit_panel(bot, chat_id, "❌ Data tidak boleh kosong.", keyboards.cancel())
                if step == "name":
                    state.update(chat_id, "content", name=value)
                    _delete_input_message(bot, message)
                    return _edit_panel(
                        bot, chat_id,
                        "📝 <b>Tambah Template</b>\n\nKirim isi caption template.",
                        keyboards.cancel(),
                    )
                template_content = text_with_custom_emoji(message)
                validate_html(template_content)
                repo.add_template(data["name"], template_content)
                _delete_input_message(bot, message)
                return _finish_and_clear(
                    bot, chat_id,
                    "✅ <b>Template berhasil ditambahkan.</b>",
                    keyboards.back("adm:templates"),
                )


            if flow == "template_edit":
                value = text_with_custom_emoji(message)
                if not value.strip():
                    return _edit_panel(bot, chat_id, "❌ Isi template tidak boleh kosong.", keyboards.cancel())
                validate_html(value)
                template_id = int(data["template_id"])
                repo.update_template(template_id, value)
                template = repo.template(template_id)
                if template and template["is_default"]:
                    repo.set_setting("default_video_caption", value)
                _delete_input_message(bot, message)
                return _finish_and_clear(
                    bot, chat_id,
                    "✅ <b>Template berhasil diperbarui dan langsung diterapkan.</b>",
                    keyboards.back("adm:templates"),
                )

            if flow == "fsub":
                value = (message.text or "").strip()
                if not value:
                    return _edit_panel(bot, chat_id, "❌ Data tidak boleh kosong.", keyboards.cancel())
                if step == "chat_id":
                    value = validate_chat_id(value)
                    try:
                        bot.get_chat(value)
                    except Exception as exc:
                        raise ValueError("Bot tidak dapat mengakses channel tersebut. Pastikan bot sudah menjadi admin.") from exc
                    state.update(chat_id, "title", fsub_chat_id=value)
                    next_text = "📢 <b>Tambah Force Subscribe</b>\n\nKirim nama channel."
                elif step == "title":
                    state.update(chat_id, "link", title=value)
                    next_text = "📢 <b>Tambah Force Subscribe</b>\n\nKirim invite link channel."
                elif step == "link":
                    value = validate_url(value)
                    state.update(chat_id, "button", link=value)
                    next_text = "📢 <b>Tambah Force Subscribe</b>\n\nKirim teks tombol join."
                else:
                    repo.add_fsub(data["fsub_chat_id"], data["title"], data["link"], value)
                    _delete_input_message(bot, message)
                    return _finish_and_clear(
                        bot, chat_id,
                        "✅ <b>Force-sub berhasil ditambahkan.</b>",
                        keyboards.back("adm:fsubs"),
                    )
                _delete_input_message(bot, message)
                return _edit_panel(bot, chat_id, next_text, keyboards.cancel())


            if flow == "fsub_edit":
                value = (message.text or "").strip()
                if not value:
                    return _edit_panel(bot, chat_id, "❌ Data tidak boleh kosong.", keyboards.cancel())
                field = data["field"]
                if field == "chat_id":
                    value = validate_chat_id(value)
                    try:
                        bot.get_chat(value)
                    except Exception as exc:
                        raise ValueError("Bot tidak dapat mengakses channel tersebut.") from exc
                elif field == "invite_link":
                    value = validate_url(value)
                repo.update_fsub(int(data["fsub_id"]), field, value)
                _delete_input_message(bot, message)
                return _finish_and_clear(
                    bot, chat_id,
                    "✅ <b>Force-sub berhasil diperbarui.</b>",
                    keyboards.back("adm:fsubs"),
                )

            if flow == "setting":
                key = data["key"]
                value = text_with_custom_emoji(message)
                validate_html(value)
                if key == "channel_caption_template":
                    media_service.validate_channel_template(value)
                    if "<tg-emoji" in value:
                        # Channel post tidak mendukung custom emoji dari bot.
                        # Lihat: https://core.telegram.org/bots/api-changelog#february-9-2026
                        logger.info("channel_caption_template berisi tg-emoji yang akan di-strip Telegram")
                repo.set_setting(key, value)
                if key == "default_video_caption":
                    default_tpl = repo.default_template()
                    if default_tpl and default_tpl["is_system"]:
                        repo.update_template(default_tpl["id"], value)
                _delete_input_message(bot, message)
                return _finish_and_clear(
                    bot, chat_id,
                    "✅ <b>Pengaturan berhasil disimpan dan langsung diterapkan.</b>",
                    keyboards.back("adm:settings"),
                )

            if flow == "links":
                value = (message.text or "").strip()
                order = [
                    ("main", "main_channel_link", "owner", "🔗 <b>Ubah Link</b>\n\nKirim link owner."),
                    ("owner", "owner_link", "help", "🔗 <b>Ubah Link</b>\n\nKirim link bantuan."),
                    ("help", "help_link", None, None),
                ]
                current = next(item for item in order if item[0] == step)
                value = validate_url(value)
                repo.set_setting(current[1], value)
                _delete_input_message(bot, message)
                if current[2]:
                    state.update(chat_id, current[2])
                    return _edit_panel(bot, chat_id, current[3], keyboards.cancel())
                return _finish_and_clear(
                    bot, chat_id,
                    "✅ <b>Semua link berhasil disimpan.</b>",
                    keyboards.back("adm:settings"),
                )

            if flow == "button":
                value = (message.text or "").strip()
                if not value:
                    return _edit_panel(bot, chat_id, "❌ Data tidak boleh kosong.", keyboards.cancel())
                if step == "label":
                    state.update(chat_id, "url", label=value)
                    _delete_input_message(bot, message)
                    return _edit_panel(
                        bot, chat_id,
                        "➕ <b>Tambah Tombol Welcome</b>\n\nKirim URL tombol.",
                        keyboards.cancel(),
                    )
                value = validate_url(value)
                repo.add_button(data["label"], value)
                _delete_input_message(bot, message)
                return _finish_and_clear(
                    bot, chat_id,
                    "✅ <b>Tombol berhasil ditambahkan.</b>",
                    keyboards.back("adm:settings"),
                )


            if flow == "button_edit":
                value = (message.text or "").strip()
                if not value:
                    return _edit_panel(bot, chat_id, "❌ Data tidak boleh kosong.", keyboards.cancel())
                button_id = int(data["button_id"])
                if step == "label":
                    repo.update_button_label(button_id, value)
                elif step == "url":
                    value = validate_url(value)
                    repo.update_button_url(button_id, value)
                else:
                    raise ValueError("Tahap edit tombol tidak valid")
                _delete_input_message(bot, message)
                return _finish_and_clear(
                    bot, chat_id,
                    "✅ <b>Tombol berhasil diperbarui dan langsung diterapkan.</b>",
                    keyboards.back("btn:list"),
                )

            if flow == "broadcast":
                success = failed = blocked = 0
                _delete_input_message(bot, message)
                for user in repo.users():
                    if user["is_banned"]:
                        continue
                    attempts = 0
                    while attempts < 2:
                        attempts += 1
                        try:
                            bot.copy_message(
                                user["id"],
                                message.chat.id,
                                message.message_id,
                                protect_content=False,
                            )
                            success += 1
                            break
                        except ApiTelegramException as exc:
                            payload = getattr(exc, "result_json", {}) or {}
                            retry_after = (payload.get("parameters") or {}).get("retry_after")
                            if retry_after and attempts < 2:
                                time.sleep(float(retry_after) + 0.2)
                                continue
                            error_text = str(exc).lower()
                            if any(marker in error_text for marker in (
                                "bot was blocked", "chat not found", "user is deactivated"
                            )):
                                repo.ban_user(user["id"], True)
                                blocked += 1
                            failed += 1
                            break
                        except Exception:
                            failed += 1
                            break
                    time.sleep(settings.broadcast_delay)
                _delete_input_message(bot, message)
                log_service.send(
                    bot,
                    "📣 Broadcast selesai",
                    f"Berhasil: {success}\nGagal: {failed}\nUser tidak aktif: {blocked}",
                )
                return _finish_and_clear(
                    bot,
                    chat_id,
                    f"✅ <b>Broadcast selesai.</b>\n\nBerhasil: {success}\nGagal: {failed}\nUser tidak aktif: {blocked}",
                    keyboards.back(),
                )

            if flow in ("broadcast_tpl_add", "broadcast_tpl_edit_content"):
                is_add = flow == "broadcast_tpl_add"
                tpl_id = None if is_add else int(data.get("tpl_id", 0))

                if step == "waiting_name":
                    name = (message.text or "").strip()
                    if not name:
                        return _edit_panel(bot, chat_id, "❌ Nama tidak boleh kosong.", keyboards.cancel())
                    if len(name) > 64:
                        return _edit_panel(bot, chat_id, "❌ Nama terlalu panjang (maks 64 karakter).", keyboards.cancel())
                    state.update(chat_id, "waiting_media", tpl_name=name)
                    _delete_input_message(bot, message)
                    return _edit_panel(
                        bot, chat_id,
                        f"➕ <b>Template: {html.escape(name)}</b>\n\nKirim foto/video untuk template ini, atau lewati.",
                        keyboards.broadcast_skip_media(),
                    )

                if step == "waiting_media":
                    content_type, file_id = _extract_media(message)
                    if not content_type:
                        return _edit_panel(
                            bot, chat_id,
                            "❌ Kirim foto atau video, atau tekan <b>Lewati</b>.",
                            keyboards.broadcast_skip_media(),
                        )
                    state.update(chat_id, "waiting_caption", content_type=content_type, file_id=file_id)
                    _delete_input_message(bot, message)
                    return _edit_panel(
                        bot, chat_id,
                        "📝 <b>Template Broadcast</b>\n\nKirim teks/caption broadcast.",
                        keyboards.cancel(),
                    )

                if step == "waiting_caption":
                    caption = text_with_custom_emoji(message).strip()
                    if not caption:
                        return _edit_panel(bot, chat_id, "❌ Caption tidak boleh kosong.", keyboards.cancel())
                    state.update(chat_id, "waiting_button_text", caption=caption)
                    _delete_input_message(bot, message)
                    return _edit_panel(
                        bot, chat_id,
                        "🔘 <b>Template Broadcast</b>\n\nKirim <b>label tombol</b>, atau lewati jika tidak perlu tombol.",
                        keyboards.broadcast_skip_button(),
                    )

                if step == "waiting_button_text":
                    btn_text = (message.text or "").strip()
                    if not btn_text:
                        return _edit_panel(
                            bot, chat_id,
                            "❌ Label tidak boleh kosong. Kirim label tombol atau tekan <b>Lewati</b>.",
                            keyboards.broadcast_skip_button(),
                        )
                    state.update(chat_id, "waiting_button_url", button_text=btn_text)
                    _delete_input_message(bot, message)
                    hint = (
                        "Gunakan format <code>@username</code> atau <code>https://</code>."
                        if _is_owner(message.from_user.id)
                        else "Hanya boleh <code>@username</code> channel Telegram."
                    )
                    return _edit_panel(
                        bot, chat_id,
                        f"🔗 <b>Template Broadcast</b>\n\nKirim URL atau username tombol.\n\n{hint}",
                        keyboards.cancel(),
                    )

                if step == "waiting_button_url":
                    raw_url = (message.text or "").strip()
                    try:
                        button_url = _validate_button_url(raw_url, message.from_user.id)
                    except ValueError as exc:
                        return _edit_panel(bot, chat_id, f"❌ {exc}", keyboards.cancel())
                    state.update(chat_id, None, button_url=button_url)
                    _delete_input_message(bot, message)
                    _save_broadcast_template(state.get(chat_id)["data"], tpl_id)
                    tpl_name = data.get("tpl_name", "")
                    msg = (
                        f"✅ <b>Template <i>{html.escape(tpl_name)}</i> berhasil disimpan.</b>"
                        if is_add else
                        "✅ <b>Konten template berhasil diperbarui.</b>"
                    )
                    return _finish_and_clear(bot, chat_id, msg, keyboards.back("bct:home"))

            if flow == "broadcast_tpl_edit_name":
                name = (message.text or "").strip()
                if not name:
                    return _edit_panel(bot, chat_id, "❌ Nama tidak boleh kosong.", keyboards.cancel())
                if len(name) > 64:
                    return _edit_panel(bot, chat_id, "❌ Nama terlalu panjang (maks 64 karakter).", keyboards.cancel())
                repo.update_broadcast_template_name(int(data["tpl_id"]), name)
                _delete_input_message(bot, message)
                return _finish_and_clear(
                    bot, chat_id,
                    f"✅ <b>Nama template berhasil diubah menjadi <i>{html.escape(name)}</i>.</b>",
                    keyboards.back("bct:home"),
                )

        except Exception as exc:
            logger.exception("Gagal memproses input admin")
            return _edit_panel(
                bot,
                chat_id,
                f"❌ Error: <code>{html.escape(str(exc))}</code>",
                keyboards.cancel(),
            )

    @bot.callback_query_handler(func=lambda call: call.data.startswith("media:uploadcat:"))
    def finish_upload(call):
        if call.from_user.id not in settings.admin_ids:
            return bot.answer_callback_query(call.id, "Bukan admin.", show_alert=True)
        session = state.get(call.message.chat.id)
        if not session or session["flow"] != "upload":
            return bot.answer_callback_query(call.id, "Sesi upload berakhir", show_alert=True)
        bot.answer_callback_query(call.id)
        return _finish_upload(bot, call.message.chat.id, int(call.data.rsplit(":", 1)[1]))

    @bot.callback_query_handler(func=lambda call: call.data == "cat:add_upload")
    def add_upload_category(call):
        if call.from_user.id not in settings.admin_ids:
            return bot.answer_callback_query(call.id, "Bukan admin.", show_alert=True)
        session = state.get(call.message.chat.id)
        if not session or session["flow"] != "upload":
            return bot.answer_callback_query(call.id, "Sesi upload berakhir", show_alert=True)
        bot.answer_callback_query(call.id)
        state.update(call.message.chat.id, "new_category")
        return _edit_panel(
            bot,
            call.message.chat.id,
            "📂 <b>Buat Kategori Baru</b>\n\nKirim nama kategori baru.",
            keyboards.cancel(),
        )
