import asyncio
import logging
import sys
import html
import csv
import io
import json
import re
import aiosqlite
import os
from dotenv import load_dotenv
from datetime import datetime
from contextlib import suppress
from typing import Any, Dict, List, Union

from aiogram import Bot, Dispatcher, Router, F, BaseMiddleware
from aiogram.types import (
    ChatMemberUpdated,
    Message,
    InlineKeyboardMarkup,
    InlineKeyboardButton,
    CallbackQuery,
    BufferedInputFile,
    InputMediaPhoto,
    InputMediaVideo,
    InputMediaAudio,
    InputMediaDocument
)
from aiogram.filters import CommandStart, Command
from aiogram.filters.callback_data import CallbackData
from aiogram.enums import ChatMemberStatus, ParseMode, ChatType
from aiogram.exceptions import TelegramRetryAfter, TelegramForbiddenError, TelegramBadRequest
from aiogram.fsm.context import FSMContext
from aiogram.fsm.state import State, StatesGroup
from aiogram.fsm.storage.memory import MemoryStorage
from aiogram.utils.keyboard import InlineKeyboardBuilder

# ---------------------------------------------------------
# ⚙️ تنظیمات اصلی
# ---------------------------------------------------------
load_dotenv()

TOKEN = os.getenv("BOT_TOKEN", "7883213386:AAFa6RQjCMm9VMUwre_EHwdlDZXn5upg7yU")
ADMIN_ID = int(os.getenv("ADMIN_ID", 5605287290))
DB_NAME = os.getenv("DB_NAME", "channel_assist.db")

# لینک‌های تلگرام و وب‌سایت‌ها + آیدی‌ها
LINK_REGEX = re.compile(r'(https?://\S+|t\.me/\S+|@[a-zA-Z0-9_]+)', flags=re.IGNORECASE)

# ---------------------------------------------------------
# 🧩 ساختار کال‌بک دیتا
# ---------------------------------------------------------
class MenuCB(CallbackData, prefix="menu"):
    action: str
    page: int = 0
    id: int = 0
    
class ConfigCB(CallbackData, prefix="conf"):
    setting: str
    value: bool

class ChannelCB(CallbackData, prefix="ch"):
    action: str # manage, delete, edit, back
    id: int
    page: int = 0

class AlbumMiddleware(BaseMiddleware):
    def __init__(self, latency: float = 0.5):
        self.latency = latency
        self.album_data = {}

    async def __call__(self, handler, event: Message, data: Dict[str, Any]):
        if not event.media_group_id:
            return await handler(event, data)

        key = event.media_group_id
        if key not in self.album_data:
            self.album_data[key] = {"messages": [], "event": asyncio.Event()}
            self.album_data[key]["messages"].append(event)
            await asyncio.sleep(self.latency)
            
            if key in self.album_data:
                data["album"] = self.album_data[key]["messages"]
                await handler(event, data)
                del self.album_data[key]
            return
        else:
            self.album_data[key]["messages"].append(event)
            return

# ---------------------------------------------------------
# 🗄️ دیتابیس
# ---------------------------------------------------------
class AsyncDatabase:
    def __init__(self, db_name):
        self.db_name = db_name

    async def init(self):
        async with aiosqlite.connect(self.db_name) as db:
            await db.execute("PRAGMA journal_mode=WAL;")
            await db.execute("PRAGMA synchronous=NORMAL;")
            await db.execute('''CREATE TABLE IF NOT EXISTS channels 
                                (chat_id INTEGER PRIMARY KEY, title TEXT, status TEXT, type TEXT, joined_date TEXT)''')
            await db.execute('CREATE INDEX IF NOT EXISTS idx_status ON channels (status)')
            await db.execute('CREATE TABLE IF NOT EXISTS bot_config (key TEXT PRIMARY KEY, value TEXT)')
            await db.execute('''CREATE TABLE IF NOT EXISTS button_templates 
                                (id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT, content TEXT)''')
            await db.commit()

    async def execute(self, sql: str, params: tuple = (), fetchone: bool = False, fetchall: bool = False, commit: bool = False):
        async with aiosqlite.connect(self.db_name) as db:
            async with db.execute(sql, params) as cursor:
                result = None
                if fetchone:
                    result = await cursor.fetchone()
                elif fetchall:
                    result = await cursor.fetchall()
                if commit:
                    await db.commit()
                return result

    async def update_channel(self, chat_id, title, status, c_type):
        date_now = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
        existing = await self.execute("SELECT 1 FROM channels WHERE chat_id = ?", (chat_id,), fetchone=True)
        if existing:
            await self.execute('UPDATE channels SET title=?, status=?, type=? WHERE chat_id=?', (title, status, c_type, chat_id), commit=True)
        else:
            await self.execute('INSERT INTO channels (chat_id, title, status, type, joined_date) VALUES (?, ?, ?, ?, ?)', (chat_id, title, status, c_type, date_now), commit=True)

    async def get_channel(self, chat_id):
        return await self.execute("SELECT * FROM channels WHERE chat_id = ?", (chat_id,), fetchone=True)

    async def delete_channel(self, chat_id):
        await self.execute("DELETE FROM channels WHERE chat_id = ?", (chat_id,), commit=True)

    async def update_channel_title(self, chat_id, new_title):
        await self.execute("UPDATE channels SET title = ? WHERE chat_id = ?", (new_title, chat_id), commit=True)

    async def get_stats(self):
        ch = await self.execute("SELECT COUNT(*) FROM channels WHERE status='active' AND type='channel'", fetchone=True)
        gp = await self.execute("SELECT COUNT(*) FROM channels WHERE status='active' AND type IN ('group', 'supergroup')", fetchone=True)
        return ch[0], gp[0]

    async def get_active_targets(self, target_type=None):
        if target_type == 'channel':
            return await self.execute("SELECT title, chat_id FROM channels WHERE status='active' AND type='channel' ORDER BY joined_date DESC", fetchall=True)
        elif target_type == 'group':
            return await self.execute("SELECT title, chat_id FROM channels WHERE status='active' AND type IN ('group', 'supergroup') ORDER BY joined_date DESC", fetchall=True)
        return await self.execute("SELECT title, chat_id FROM channels WHERE status='active' ORDER BY joined_date DESC", fetchall=True)

    async def get_all_raw(self):
        return await self.execute("SELECT chat_id, title, status, type, joined_date FROM channels", fetchall=True)

    async def search(self, query):
        return await self.execute("SELECT title, chat_id, status FROM channels WHERE title LIKE ?", (f"%{query}%",), fetchall=True)

    async def set_last_msg(self, msg_id):
        await self.execute("INSERT OR REPLACE INTO bot_config (key, value) VALUES ('last_msg_id', ?)", (str(msg_id),), commit=True)

    async def get_last_msg(self):
        res = await self.execute("SELECT value FROM bot_config WHERE key = 'last_msg_id'", fetchone=True)
        return int(res[0]) if res else None

    async def add_template(self, name, content):
        await self.execute("INSERT INTO button_templates (name, content) VALUES (?, ?)", (name, content), commit=True)

    async def get_templates(self):
        return await self.execute("SELECT id, name, content FROM button_templates ORDER BY id DESC", fetchall=True)

    async def get_template(self, tpl_id):
        return await self.execute("SELECT content FROM button_templates WHERE id = ?", (tpl_id,), fetchone=True)

    async def delete_template(self, tpl_id):
        await self.execute("DELETE FROM button_templates WHERE id = ?", (tpl_id,), commit=True)

# ---------------------------------------------------------
# 🚦 وضعیت‌ها (States)
# ---------------------------------------------------------
class PostStates(StatesGroup):
    content = State()
    buttons = State()
    confirm = State()
    naming_template = State()
    editing_channel_title = State()
    btn_text = State()
    btn_link = State()
    btn_style = State()
    search_query = State()

# ---------------------------------------------------------
# 🔌 میدلور
# ---------------------------------------------------------
class DbMiddleware(BaseMiddleware):
    def __init__(self, db: AsyncDatabase):
        self.db = db

    async def __call__(self, handler, event, data):
        data["db"] = self.db
        return await handler(event, data)

class DebugLoggingMiddleware(BaseMiddleware):
    async def __call__(self, handler, event, data):
        state = data.get("state")
        state_str = await state.get_state() if state else "None"
        if event.message:
            logging.info(f"📥 MSG: '{event.message.text}' | User: {event.message.from_user.id} | State: {state_str}")
        elif event.callback_query:
            logging.info(f"📥 CB: '{event.callback_query.data}' | User: {event.callback_query.from_user.id} | State: {state_str}")
        elif event.my_chat_member:
            m = event.my_chat_member
            logging.info(f"📥 MY_CHAT_MEMBER: Chat: '{m.chat.title}' ({m.chat.id}) | Old: {m.old_chat_member.status} | New: {m.new_chat_member.status}")
        return await handler(event, data)

# ---------------------------------------------------------
# 🎨 مدیریت UI
# ---------------------------------------------------------
class UI:
    @staticmethod
    async def dashboard(db: AsyncDatabase):
        ch_count, gp_count = await db.get_stats()
        txt = (
            f"👑 <b>دستیار هوشمند کانال (Pro)</b>\n"
            f"📅 <code>{datetime.now().strftime('%Y/%m/%d')}</code>\n"
            f"━━━━━━━━━━━━━━━━\n"
            f"📢 <b>کانال‌ها:</b> {ch_count}\n"
            f"👥 <b>گروه‌ها:</b> {gp_count}\n"
            f"📊 <b>مجموع مخاطبین:</b> {ch_count + gp_count}\n"
            f"━━━━━━━━━━━━━━━━\n"
            f"👇 <i>لطفاً عملیات مورد نظر را انتخاب کنید:</i>"
        )
        kb = InlineKeyboardMarkup(inline_keyboard=[
            [InlineKeyboardButton(text="✨ ایجاد پست جدید", callback_data=MenuCB(action="new_post").pack(), style="success")],
            [InlineKeyboardButton(text="📂 مدیریت منابع", callback_data=MenuCB(action="list", page=0).pack(), style="primary"),
             InlineKeyboardButton(text="🔎 جستجو", callback_data=MenuCB(action="search_info").pack(), style="primary")],
            [InlineKeyboardButton(text="📥 خروجی اکسل", callback_data=MenuCB(action="export").pack()),
             InlineKeyboardButton(text="📚 راهنما", callback_data=MenuCB(action="help").pack())],
            [InlineKeyboardButton(text="🔄 بروزرسانی", callback_data=MenuCB(action="refresh").pack(), style="primary")]
        ])
        return txt, kb

    @staticmethod
    def config_keyboard(settings: dict, has_buttons: bool):
        pin_icon = "✅" if settings['pin'] else "❌"
        sound_icon = "🔔" if settings['sound'] else "🔕"
        clean_icon = "✅" if settings['clean'] else "❌"
        
        pin_style = "success" if settings['pin'] else "danger"
        sound_style = "success" if settings['sound'] else "danger"
        clean_style = "success" if settings['clean'] else "danger"
        
        btns = [
            [
                InlineKeyboardButton(text=f"📌 پین: {pin_icon}", callback_data=ConfigCB(setting="pin", value=not settings['pin']).pack(), style=pin_style),
                InlineKeyboardButton(text=f"🔊 صدا: {sound_icon}", callback_data=ConfigCB(setting="sound", value=not settings['sound']).pack(), style=sound_style)
            ],
            [
                InlineKeyboardButton(text=f"🧹 حذف لینک هوشمند: {clean_icon}", callback_data=ConfigCB(setting="clean", value=not settings['clean']).pack(), style=clean_style)
            ]
        ]
        
        if has_buttons:
            btns.append([InlineKeyboardButton(text="💾 ذخیره این دکمه‌ها", callback_data=MenuCB(action="save_tpl_ask").pack(), style="primary")])
            
        btns.append([InlineKeyboardButton(text="📤 ارسال به کانال‌ها", callback_data=MenuCB(action="send_ch").pack(), style="success")])
        btns.append([InlineKeyboardButton(text="📤 ارسال به گروه‌ها", callback_data=MenuCB(action="send_gp").pack(), style="success")])
        btns.append([InlineKeyboardButton(text="🔙 لغو و بازگشت", callback_data=MenuCB(action="home").pack(), style="danger")])
        
        return InlineKeyboardMarkup(inline_keyboard=btns)

    @staticmethod
    async def render(bot: Bot, chat_id: int, text: str, kb: InlineKeyboardMarkup, db: AsyncDatabase, loading: bool = True):
        last_id = await db.get_last_msg()
        if last_id:
            with suppress(Exception): await bot.delete_message(chat_id, last_id)
        
        if loading:
            with suppress(Exception):
                tmp = await bot.send_message(chat_id, "⏳")
                await asyncio.sleep(0.3)
                await tmp.delete()

        msg = await bot.send_message(chat_id, text, parse_mode=ParseMode.HTML, reply_markup=kb)
        await db.set_last_msg(msg.message_id)
        return msg

# ---------------------------------------------------------
# 🛣️ روتر
# ---------------------------------------------------------
router = Router()

@router.message(CommandStart())
async def start(msg: Message, db: AsyncDatabase, state: FSMContext):
    if msg.from_user.id != ADMIN_ID: return
    with suppress(Exception): await msg.delete()
    await state.clear()
    txt, kb = await UI.dashboard(db)
    await UI.render(msg.bot, msg.chat.id, txt, kb, db)

@router.message(Command("cancel"))
@router.message(F.text.casefold() == "cancel")
@router.message(F.text == "انصراف")
async def cmd_cancel(msg: Message, state: FSMContext, db: AsyncDatabase):
    if msg.from_user.id != ADMIN_ID: return
    
    current_state = await state.get_state()
    if current_state is None:
        with suppress(Exception): await msg.delete()
        txt, kb = await UI.dashboard(db)
        await UI.render(msg.bot, msg.chat.id, txt, kb, db)
        return
        
    data = await state.get_data()
    source_msg_ids = data.get("source_msg_ids", [])
    cid = data.get("cid")
    
    if source_msg_ids and cid:
        for mid in source_msg_ids:
            with suppress(Exception): await msg.bot.delete_message(cid, mid)
            
    await state.clear()
    with suppress(Exception): await msg.delete()
    txt, kb = await UI.dashboard(db)
    await UI.render(msg.bot, msg.chat.id, txt, kb, db)

@router.callback_query(MenuCB.filter(F.action == "home"))
async def go_home(call: CallbackQuery, db: AsyncDatabase, state: FSMContext):
    data = await state.get_data()
    source_msg_ids = data.get("source_msg_ids", [])
    cid = data.get("cid")
    
    if source_msg_ids and cid:
        for mid in source_msg_ids:
            with suppress(Exception): await call.bot.delete_message(cid, mid)
            
    await state.clear()
    txt, kb = await UI.dashboard(db)
    await UI.render(call.bot, call.message.chat.id, txt, kb, db, loading=False)
    await call.answer("🏠 داشبورد")

@router.callback_query(MenuCB.filter(F.action == "refresh"))
async def do_refresh(call: CallbackQuery, db: AsyncDatabase):
    txt, kb = await UI.dashboard(db)
    await UI.render(call.bot, call.message.chat.id, txt, kb, db, loading=False)
    await call.answer("✅")

# --- Manual Add via Forward ---
@router.message(F.forward_from_chat)
async def manual_add_channel(msg: Message, db: AsyncDatabase):
    if msg.from_user.id != ADMIN_ID: return
    
    chat = msg.forward_from_chat
    if chat.type not in [ChatType.CHANNEL, ChatType.SUPERGROUP, ChatType.GROUP]:
        with suppress(Exception): await msg.delete()
        txt, kb = await UI.dashboard(db)
        err_txt = "❌ <b>این چت قابل افزودن نیست (شاید کاربر باشد).</b>\n\n" + txt
        await UI.render(msg.bot, msg.chat.id, err_txt, kb, db, loading=False)
        return

    try:
        member = await msg.bot.get_chat_member(chat.id, msg.bot.id)
        if chat.type == ChatType.CHANNEL and member.status != ChatMemberStatus.ADMINISTRATOR:
             with suppress(Exception): await msg.delete()
             txt, kb = await UI.dashboard(db)
             err_txt = "⚠️ <b>ربات در این کانال ادمین نیست.</b>\n\n" + txt
             await UI.render(msg.bot, msg.chat.id, err_txt, kb, db, loading=False)
             return
    except Exception as e:
        with suppress(Exception): await msg.delete()
        txt, kb = await UI.dashboard(db)
        err_txt = f"❌ <b>خطا در بررسی دسترسی: {html.escape(str(e))}</b>\n\n" + txt
        await UI.render(msg.bot, msg.chat.id, err_txt, kb, db, loading=False)
        return

    # If successfully verified, save to database and notify user
    with suppress(Exception): await msg.delete()
    c_type = 'channel' if chat.type == ChatType.CHANNEL else 'group'
    await db.update_channel(chat.id, chat.title, 'active', c_type)
    
    txt, kb = await UI.dashboard(db)
    success_txt = f"🟢 <b>اتصال با موفقیت برقرار شد!</b>\n🏷 نام: <b>{html.escape(chat.title or '')}</b>\n\n" + txt
    await UI.render(msg.bot, msg.chat.id, success_txt, kb, db, loading=False)

@router.callback_query(MenuCB.filter(F.action == "list"))
async def show_list_interactive(call: CallbackQuery, callback_data: MenuCB, db: AsyncDatabase):
    page = callback_data.page
    targets = await db.get_active_targets()
    
    if not targets: 
        await call.answer("❌ لیستی وجود ندارد.", show_alert=True)
        return
 
    ipp = 10
    total = (len(targets) + ipp - 1) // ipp
    curr = targets[page*ipp : (page+1)*ipp]
    
    btns = []
    for title, cid in curr:
        channel_info = await db.get_channel(cid)
        c_type = channel_info[3] if channel_info else 'channel'
        icon = "📢" if c_type == 'channel' else "👥"
        btns.append([InlineKeyboardButton(text=f"{icon} {title}", callback_data=ChannelCB(action="manage", id=cid, page=page).pack(), style="primary")])
    
    nav = []
    if page > 0: nav.append(InlineKeyboardButton(text="◀️", callback_data=MenuCB(action="list", page=page-1).pack(), style="primary"))
    if (page+1)*ipp < len(targets): nav.append(InlineKeyboardButton(text="▶️", callback_data=MenuCB(action="list", page=page+1).pack(), style="primary"))
    
    if nav: btns.append(nav)
    btns.append([InlineKeyboardButton(text="🔙 بازگشت", callback_data=MenuCB(action="home").pack(), style="danger")])
    
    txt = (
        f"📂 <b>مدیریت منابع (صفحه {page+1} از {total})</b>\n\n"
        "لیست تمام کانال‌ها و گروه‌های متصل.\n"
        "برای <b>ویرایش</b> یا <b>حذف</b> روی نام مورد نظر کلیک کنید."
    )
    
    await UI.render(call.bot, call.message.chat.id, txt, InlineKeyboardMarkup(inline_keyboard=btns), db, loading=False)
    await call.answer()
 
async def show_manage_channel_menu(bot, chat_id, target_id, page, db: AsyncDatabase):
    channel = await db.get_channel(target_id)
    if not channel:
        return False
        
    t_id, title, status, c_type, date = channel
    
    txt = (
        f"🏷 <b>مشخصات:</b>\n\n"
        f"📌 نام: <b>{html.escape(title)}</b>\n"
        f"🆔 آیدی: <code>{t_id}</code>\n"
        f"💠 نوع: {c_type}\n"
        f"📅 تاریخ افزودن: {date}\n\n"
        "👇 عملیات:"
    )
    
    kb = InlineKeyboardMarkup(inline_keyboard=[
        [InlineKeyboardButton(text="✏️ ویرایش نام", callback_data=ChannelCB(action="edit", id=t_id, page=page).pack(), style="primary")],
        [InlineKeyboardButton(text="🗑 حذف از لیست", callback_data=ChannelCB(action="delete", id=t_id, page=page).pack(), style="danger")],
        [InlineKeyboardButton(text="🔙 بازگشت به لیست", callback_data=MenuCB(action="list", page=page).pack(), style="primary")]
    ])
    
    await UI.render(bot, chat_id, txt, kb, db, loading=False)
    return True

@router.callback_query(ChannelCB.filter(F.action == "manage"))
async def manage_channel(call: CallbackQuery, callback_data: ChannelCB, db: AsyncDatabase):
    success = await show_manage_channel_menu(call.bot, call.message.chat.id, callback_data.id, callback_data.page, db)
    if not success:
        await call.answer("❌ یافت نشد!", show_alert=True)
    else:
        await call.answer()
 
@router.callback_query(ChannelCB.filter(F.action == "delete"))
async def delete_channel_confirm(call: CallbackQuery, callback_data: ChannelCB, db: AsyncDatabase):
    await db.delete_channel(callback_data.id)
    await call.answer("🗑 حذف شد")
    await show_list_interactive(call, MenuCB(action="list", page=callback_data.page), db)
@router.callback_query(ChannelCB.filter(F.action == "edit"))
async def edit_channel_start(call: CallbackQuery, callback_data: ChannelCB, db: AsyncDatabase, state: FSMContext):
    await state.set_state(PostStates.editing_channel_title)
    await state.update_data(target_id=callback_data.id, page=callback_data.page)
    
    txt = "✏️ <b>نام جدید را ارسال کنید:</b>\n(برای انصراف /cancel بزنید)"
    kb = InlineKeyboardMarkup(inline_keyboard=[[InlineKeyboardButton(text="🔙 انصراف", callback_data=MenuCB(action="list", page=callback_data.page).pack(), style="danger")]])
    
    await UI.render(call.bot, call.message.chat.id, txt, kb, db, loading=False)
    await call.answer()

@router.message(PostStates.editing_channel_title)
async def edit_channel_save(msg: Message, db: AsyncDatabase, state: FSMContext):
    if msg.from_user.id != ADMIN_ID: return
    
    data = await state.get_data()
    new_title = msg.text
    target_id = data['target_id']
    page = data['page']
    
    with suppress(Exception): await msg.delete()
    
    await db.update_channel_title(target_id, new_title)
    await state.clear()
    
    await show_manage_channel_menu(msg.bot, msg.chat.id, target_id, page, db)

# --- Post Creator ---
@router.callback_query(MenuCB.filter(F.action == "new_post"))
async def post_start(call: CallbackQuery, db: AsyncDatabase, state: FSMContext):
    txt = "📝 <b>ایجاد پست جدید (مرحله ۱ از ۳)</b>\n━━━━━━━━━━━━━━━━\n🔸 لطفاً محتوای پست خود را بفرستید.\n🔹 می‌توانید متن، عکس، ویدیو، آلبوم یا فایل بفرستید."
    kb = InlineKeyboardMarkup(inline_keyboard=[[InlineKeyboardButton(text="🔙 انصراف", callback_data=MenuCB(action="home").pack())]])
    await UI.render(call.bot, call.message.chat.id, txt, kb, db)
    await state.set_state(PostStates.content)
    await state.update_data(settings={'pin': False, 'sound': True, 'clean': False}, raw_btns="", album_msgs=[], buttons_list=[])
    await call.answer()

@router.message(PostStates.content)
async def post_receive(msg: Message, db: AsyncDatabase, state: FSMContext, album: List[Message] = None):
    if msg.from_user.id != ADMIN_ID: return
    is_album = False
    messages_to_copy = [msg]
    if album:
        is_album = True
        messages_to_copy = album

    raw_content = ""
    for m in messages_to_copy:
        if m.caption:
            raw_content = m.caption
            break
        if m.text:
            raw_content = m.text
            break

    source_msg_ids = []
    caption_msg_id = None
    try:
        if is_album:
            media_group = []
            for m in messages_to_copy:
                if m.photo: media_group.append(InputMediaPhoto(media=m.photo[-1].file_id, caption=m.caption))
                elif m.video: media_group.append(InputMediaVideo(media=m.video.file_id, caption=m.caption))
                elif m.audio: media_group.append(InputMediaAudio(media=m.audio.file_id, caption=m.caption))
                elif m.document: media_group.append(InputMediaDocument(media=m.document.file_id, caption=m.caption))
            sent_msgs = await msg.bot.send_media_group(chat_id=msg.chat.id, media=media_group)
            source_msg_ids = [m.message_id for m in sent_msgs]
            
            # Find which sent message actually holds the caption
            for sent_m, orig_m in zip(sent_msgs, messages_to_copy):
                if orig_m.caption:
                    caption_msg_id = sent_m.message_id
                    break
            if not caption_msg_id and sent_msgs:
                caption_msg_id = sent_msgs[0].message_id
        else:
            copy = await msg.send_copy(msg.chat.id)
            source_msg_ids = [copy.message_id]
            caption_msg_id = copy.message_id

        await state.update_data(
            source_msg_ids=source_msg_ids,
            caption_msg_id=caption_msg_id,
            cid=msg.chat.id,
            is_album=is_album,
            raw_text=raw_content,
            is_text=bool(msg.text)
        )
    except Exception as e:
        await msg.reply(f"❌ خطا: {e}")
        return

    for m in messages_to_copy:
        with suppress(Exception): await m.delete()

    txt = (
        "✅ <b>محتوا دریافت شد!</b>\n\n"
        "🔹 <b>مرحله ۲: مدیریت دکمه‌های شیشه‌ای</b>\n\n"
        "✏️ <b>لطفاً متن روی دکمه اول را ارسال کنید:</b>\n"
        "اگر مایل به اضافه کردن دکمه نیستید، دکمه «⏭ بدون دکمه» را بزنید یا می‌توانید از قالب‌ها استفاده کنید."
    )
    kb = InlineKeyboardMarkup(inline_keyboard=[
        [InlineKeyboardButton(text="📂 دکمه‌های آماده (قالب‌ها)", callback_data=MenuCB(action="show_tpls").pack(), style="primary")],
        [InlineKeyboardButton(text="⏭ بدون دکمه (ادامه)", callback_data=MenuCB(action="skip_btn").pack(), style="success")],
        [InlineKeyboardButton(text="🔙 انصراف", callback_data=MenuCB(action="home").pack(), style="danger")]
    ])
    await UI.render(msg.bot, msg.chat.id, txt, kb, db, loading=False)
    await state.set_state(PostStates.btn_text)

# --- Helpers for step-by-step buttons ---
def parse_raw_buttons(raw_btns: str) -> List[tuple]:
    parsed = []
    if not raw_btns:
        return parsed
    for line in raw_btns.split('\n'):
        for part in line.split('|'):
            parts = re.split(r'\s+-\s*|\s*-\s+', part.strip(), maxsplit=2)
            if len(parts) >= 2:
                if len(parts) == 3:
                    l, u, s = parts
                    l, u, s = l.strip(), u.strip(), s.strip().lower()
                else:
                    l, u = parts
                    l, u, s = l.strip(), u.strip(), ""
                parsed.append((l, u, s))
    return parsed

def serialize_buttons(buttons: List[tuple]) -> str:
    lines = []
    for l, u, s in buttons:
        if s:
            lines.append(f"{l} - {u} - {s}")
        else:
            lines.append(f"{l} - {u}")
    return "\n".join(lines)

def build_keyboard_from_list(buttons: List[tuple]) -> InlineKeyboardMarkup:
    builder = InlineKeyboardBuilder()
    for l, u, s in buttons:
        btn_style = None
        text_with_emoji = l
        if s in ['danger', 'قرمز']:
            btn_style = 'danger'
            if not text_with_emoji.startswith("🔴"):
                text_with_emoji = f"🔴 {text_with_emoji}"
        elif s in ['primary', 'آبی']:
            btn_style = 'primary'
            if not text_with_emoji.startswith("🔵"):
                text_with_emoji = f"🔵 {text_with_emoji}"
        elif s in ['success', 'سبز']:
            btn_style = 'success'
            if not text_with_emoji.startswith("🟢"):
                text_with_emoji = f"🟢 {text_with_emoji}"
                
        try:
            if btn_style:
                builder.button(text=text_with_emoji, url=u, style=btn_style)
            else:
                builder.button(text=text_with_emoji, url=u)
        except TypeError:
            builder.button(text=text_with_emoji, url=u)
            
    builder.adjust(1)
    return builder.as_markup()

async def show_current_buttons_menu(bot, chat_id, state: FSMContext, db: AsyncDatabase):
    data = await state.get_data()
    buttons_list = data.get("buttons_list", [])
    
    if buttons_list:
        preview = "📋 <b>دکمه‌های اضافه شده تاکنون:</b>\n\n"
        for i, (l, u, s) in enumerate(buttons_list, 1):
            style_desc = "🔴 قرمز" if s == 'danger' else "🔵 آبی" if s == 'primary' else "🟢 سبز" if s == 'success' else "⚪️ معمولی"
            preview += f"{i}️⃣ {l} | استایل: {style_desc}\n🔗 {u}\n\n"
    else:
        preview = "❌ هنوز هیچ دکمه‌ای اضافه نشده است.\n\n"
        
    txt = preview + "👇 چه کاری می‌خواهید انجام دهید؟"
    
    kb_list = [
        [InlineKeyboardButton(text="➕ افزودن دکمه جدید", callback_data=MenuCB(action="add_new_btn").pack(), style="success")],
    ]
    if buttons_list:
        kb_list.append([InlineKeyboardButton(text="🗑 پاک کردن آخرین دکمه", callback_data=MenuCB(action="pop_btn").pack(), style="danger")])
        kb_list.append([InlineKeyboardButton(text="💾 ذخیره به عنوان قالب جدید", callback_data=MenuCB(action="save_tpl_ask").pack(), style="primary")])
        
    kb_list.append([InlineKeyboardButton(text="📂 دکمه‌های آماده (قالب‌ها)", callback_data=MenuCB(action="show_tpls").pack(), style="primary")])
    kb_list.append([InlineKeyboardButton(text="⏭ تایید و ادامه (مرحله ۳)", callback_data=MenuCB(action="confirm_btns").pack(), style="success")])
    kb_list.append([InlineKeyboardButton(text="🔙 انصراف و لغو پست", callback_data=MenuCB(action="home").pack(), style="danger")])
    
    await UI.render(bot, chat_id, txt, InlineKeyboardMarkup(inline_keyboard=kb_list), db, loading=False)
    await state.set_state(PostStates.buttons)

# --- Templates & Buttons Callback Handlers ---
@router.callback_query(MenuCB.filter(F.action == "add_new_btn"))
async def start_add_button(call: CallbackQuery, state: FSMContext, db: AsyncDatabase):
    txt = "✏️ <b>لطفاً متن روی دکمه را ارسال کنید:</b>\n\n(مثال: خرید محصولات)"
    kb = InlineKeyboardMarkup(inline_keyboard=[
        [InlineKeyboardButton(text="🔙 انصراف و بازگشت", callback_data=MenuCB(action="back_to_btn_menu").pack(), style="danger")]
    ])
    await UI.render(call.bot, call.message.chat.id, txt, kb, db, loading=False)
    await state.set_state(PostStates.btn_text)
    await call.answer()

@router.message(PostStates.buttons)
async def post_btns_type_text(msg: Message, db: AsyncDatabase, state: FSMContext):
    if msg.from_user.id != ADMIN_ID: return
    if not msg.text: return
    btn_text = msg.text.strip()
    with suppress(Exception): await msg.delete()
    if not btn_text:
        return
        
    await state.update_data(temp_btn_text=btn_text)
    txt = f"🔗 <b>لینک دکمه را ارسال کنید:</b>\n\nمتن دکمه: {btn_text}\n(مثال: https://google.com)"
    kb = InlineKeyboardMarkup(inline_keyboard=[
        [InlineKeyboardButton(text="🔙 انصراف", callback_data=MenuCB(action="back_to_btn_menu").pack(), style="danger")]
    ])
    await UI.render(msg.bot, msg.chat.id, txt, kb, db, loading=False)
    await state.set_state(PostStates.btn_link)

@router.message(PostStates.btn_text)
async def receive_btn_text(msg: Message, state: FSMContext, db: AsyncDatabase):
    if msg.from_user.id != ADMIN_ID: return
    if not msg.text: return
    btn_text = msg.text.strip()
    with suppress(Exception): await msg.delete()
    if not btn_text:
        return await msg.answer("⚠️ متن دکمه نمی‌تواند خالی باشد. مجدداً ارسال کنید:")
        
    await state.update_data(temp_btn_text=btn_text)
    txt = f"🔗 <b>لینک دکمه را ارسال کنید:</b>\n\nمتن دکمه: {btn_text}\n(مثال: https://google.com)"
    kb = InlineKeyboardMarkup(inline_keyboard=[
        [InlineKeyboardButton(text="🔙 انصراف", callback_data=MenuCB(action="back_to_btn_menu").pack(), style="danger")]
    ])
    await UI.render(msg.bot, msg.chat.id, txt, kb, db, loading=False)
    await state.set_state(PostStates.btn_link)

@router.message(PostStates.btn_link)
async def receive_btn_link(msg: Message, state: FSMContext, db: AsyncDatabase):
    if msg.from_user.id != ADMIN_ID: return
    if not msg.text: return
    btn_link = msg.text.strip()
    with suppress(Exception): await msg.delete()
    
    # Auto-format usernames and raw domains to valid URL formats
    if btn_link.startswith('@'):
        btn_link = f"https://t.me/{btn_link[1:]}"
    elif not btn_link.startswith(('http://', 'https://', 'tg://')):
        if '.' in btn_link:
            btn_link = f"https://{btn_link}"
            
    if not btn_link.startswith(('http://', 'https://', 'tg://')):
        return await msg.answer("⚠️ <b>لینک نامعتبر است!</b>\nلطفاً یک لینک معتبر بفرستید (مثال: <code>https://google.com</code> یا <code>@username</code>):", parse_mode=ParseMode.HTML)
        
    await state.update_data(temp_btn_link=btn_link)
    data = await state.get_data()
    btn_text = data.get("temp_btn_text")
    
    txt = f"🎨 <b>استایل (رنگ) دکمه را انتخاب کنید:</b>\n\nمتن دکمه: {btn_text}\nلینک دکمه: {btn_link}"
    kb = InlineKeyboardMarkup(inline_keyboard=[
        [InlineKeyboardButton(text="🔵 Primary (آبی)", callback_data="btn_style_primary", style="primary"),
         InlineKeyboardButton(text="🟢 Success (سبز)", callback_data="btn_style_success", style="success")],
        [InlineKeyboardButton(text="🔴 Danger (قرمز)", callback_data="btn_style_danger", style="danger"),
         InlineKeyboardButton(text="⚪️ معمولی", callback_data="btn_style_none")],
        [InlineKeyboardButton(text="🔙 انصراف", callback_data=MenuCB(action="back_to_btn_menu").pack(), style="danger")]
    ])
    await UI.render(msg.bot, msg.chat.id, txt, kb, db, loading=False)
    await state.set_state(PostStates.btn_style)

@router.callback_query(F.data.startswith("btn_style_"))
async def receive_btn_style(call: CallbackQuery, state: FSMContext, db: AsyncDatabase):
    style = call.data.split("_")[2]
    if style == "none":
        style = ""
        
    data = await state.get_data()
    btn_text = data.get("temp_btn_text")
    btn_link = data.get("temp_btn_link")
    buttons_list = data.get("buttons_list", [])
    
    buttons_list.append((btn_text, btn_link, style))
    await state.update_data(buttons_list=buttons_list)
    with suppress(Exception): await call.answer("✅ دکمه اضافه شد")
    await show_current_buttons_menu(call.bot, call.message.chat.id, state, db)

@router.callback_query(MenuCB.filter(F.action == "back_to_btn_menu"))
async def back_to_btn_menu_handler(call: CallbackQuery, state: FSMContext, db: AsyncDatabase):
    await show_current_buttons_menu(call.bot, call.message.chat.id, state, db)
    await call.answer()

@router.callback_query(MenuCB.filter(F.action == "pop_btn"))
async def pop_last_button(call: CallbackQuery, state: FSMContext, db: AsyncDatabase):
    data = await state.get_data()
    buttons_list = data.get("buttons_list", [])
    if buttons_list:
        buttons_list.pop()
        await state.update_data(buttons_list=buttons_list)
        await call.answer("🗑 آخرین دکمه حذف شد")
    else:
        await call.answer("دکمه‌ای برای حذف وجود ندارد", show_alert=True)
    await show_current_buttons_menu(call.bot, call.message.chat.id, state, db)

@router.callback_query(MenuCB.filter(F.action == "show_tpls"))
async def show_templates(call: CallbackQuery, db: AsyncDatabase):
    tpls = await db.get_templates()
    if not tpls: return await call.answer("❌ قالبی وجود ندارد", show_alert=True)
    kb_list = [[InlineKeyboardButton(text=f"📄 {n}", callback_data=MenuCB(action="use_tpl", id=i).pack(), style="primary"), InlineKeyboardButton(text="❌", callback_data=MenuCB(action="del_tpl", id=i).pack(), style="danger")] for i, n, c in tpls]
    kb_list.append([InlineKeyboardButton(text="🔙 بازگشت", callback_data=MenuCB(action="back_to_btn_menu").pack(), style="danger")])
    await UI.render(call.bot, call.message.chat.id, "📂 <b>انتخاب قالب:</b>", InlineKeyboardMarkup(inline_keyboard=kb_list), db, loading=False)
    await call.answer()

@router.callback_query(MenuCB.filter(F.action == "use_tpl"))
async def use_template(call: CallbackQuery, callback_data: MenuCB, db: AsyncDatabase, state: FSMContext):
    tpl = await db.get_template(callback_data.id)
    if tpl:
        raw_btns = tpl[0]
        buttons_list = parse_raw_buttons(raw_btns)
        await state.update_data(buttons_list=buttons_list)
        await call.answer("✅ قالب بارگذاری شد")
        await show_current_buttons_menu(call.bot, call.message.chat.id, state, db)
    else:
        await call.answer("❌ قالب یافت نشد", show_alert=True)

@router.callback_query(MenuCB.filter(F.action == "del_tpl"))
async def delete_template(call: CallbackQuery, callback_data: MenuCB, db: AsyncDatabase):
    await db.delete_template(callback_data.id)
    await call.answer("🗑 حذف شد")
    await show_templates(call, db)

@router.callback_query(MenuCB.filter(F.action == "skip_btn"))
async def post_skip_btns(call: CallbackQuery, db: AsyncDatabase, state: FSMContext):
    await state.update_data(buttons_list=[])
    await confirm_buttons_list(call, state, db)

@router.callback_query(MenuCB.filter(F.action == "confirm_btns"))
async def confirm_buttons_list(call: CallbackQuery, state: FSMContext, db: AsyncDatabase):
    data = await state.get_data()
    buttons_list = data.get("buttons_list", [])
    
    kb = build_keyboard_from_list(buttons_list) if buttons_list else None
    raw_btns = serialize_buttons(buttons_list)
    await state.update_data(kb_json=kb.model_dump_json() if kb else None, raw_btns=raw_btns)
    
    if kb:
        with suppress(Exception): 
            await call.bot.send_message(chat_id=call.message.chat.id, text="👁‍🗨 <b>پیش‌نمایش دکمه‌ها:</b>", reply_markup=kb, parse_mode=ParseMode.HTML)

    if not data['is_album'] and kb:
        try:
            await call.bot.edit_message_reply_markup(chat_id=call.message.chat.id, message_id=data['source_msg_ids'][0], reply_markup=kb)
        except Exception:
            pass

    txt = "⚙️ <b>پیکربندی نهایی (مرحله ۳)</b>\nتنظیمات را چک کنید:"
    kb_conf = UI.config_keyboard(data['settings'], has_buttons=bool(kb))
    await UI.render(call.bot, call.message.chat.id, txt, kb_conf, db, loading=False)
    await state.set_state(PostStates.confirm)

@router.callback_query(MenuCB.filter(F.action == "save_tpl_ask"))
async def save_template_ask(call: CallbackQuery, db: AsyncDatabase, state: FSMContext):
    await UI.render(call.bot, call.message.chat.id, "💾 نام قالب را بفرستید:", InlineKeyboardMarkup(inline_keyboard=[[InlineKeyboardButton(text="🔙 لغو", callback_data=MenuCB(action="back_to_conf").pack(), style="danger")]]), db, loading=False)
    await state.set_state(PostStates.naming_template)
    await call.answer()

@router.message(PostStates.naming_template)
async def save_template_perform(msg: Message, db: AsyncDatabase, state: FSMContext):
    if msg.from_user.id != ADMIN_ID: return
    name = msg.text
    with suppress(Exception): await msg.delete()
    data = await state.get_data()
    await db.add_template(name, data['raw_btns'])
    await UI.render(msg.bot, msg.chat.id, "✅ ذخیره شد.", UI.config_keyboard(data['settings'], has_buttons=True), db, loading=False)
    await state.set_state(PostStates.confirm)

@router.callback_query(MenuCB.filter(F.action == "back_to_conf"))
async def back_to_confirm(call: CallbackQuery, db: AsyncDatabase, state: FSMContext):
    data = await state.get_data()
    await UI.render(call.bot, call.message.chat.id, "⚙️ <b>پیکربندی</b>", UI.config_keyboard(data['settings'], has_buttons=bool(data.get('kb_json'))), db, loading=False)
    await state.set_state(PostStates.confirm)

@router.callback_query(ConfigCB.filter())
async def toggle_config(call: CallbackQuery, callback_data: ConfigCB, state: FSMContext, db: AsyncDatabase):
    data = await state.get_data()
    settings = data['settings']
    settings[callback_data.setting] = callback_data.value
    await state.update_data(settings=settings)
    has_btns = bool(data.get('kb_json'))
    kb = UI.config_keyboard(settings, has_buttons=has_btns)
    with suppress(Exception): await call.message.edit_reply_markup(reply_markup=kb)
    await call.answer()

# --- Executing ---
@router.callback_query(MenuCB.filter(F.action.startswith("send_")))
async def post_execute(call: CallbackQuery, callback_data: MenuCB, db: AsyncDatabase, state: FSMContext):
    data = await state.get_data()
    settings = data['settings']
    source_msg_ids = data['source_msg_ids']
    caption_msg_id = data.get('caption_msg_id')
    cid = data['cid']
    raw_text = data.get('raw_text', "")
    is_album = data['is_album']
    is_text = data.get('is_text', False)
    
    t_type = 'channel' if callback_data.action == 'send_ch' else 'group'
    targets = await db.get_active_targets(t_type)
    markup = InlineKeyboardMarkup(**json.loads(data['kb_json'])) if data.get('kb_json') else None
    
    if not targets: return await call.answer(f"❌ لیست {t_type} خالی است!", show_alert=True)
    
    final_text = raw_text
    if settings['clean']:
        final_text = LINK_REGEX.sub('', raw_text).strip()

    # Edit the preview message in the admin's chat if clean option is toggled and text changed
    if settings['clean'] and final_text != raw_text and caption_msg_id:
        try:
            if is_text:
                await call.bot.edit_message_text(chat_id=cid, message_id=caption_msg_id, text=final_text)
            else:
                await call.bot.edit_message_caption(chat_id=cid, message_id=caption_msg_id, caption=final_text)
        except Exception as e:
            logging.error(f"Error cleaning preview message: {e}")

    await UI.render(call.bot, call.message.chat.id, f"🚀 <b>ارسال به {len(targets)} هدف...</b>", None, db)
    
    s, f = 0, 0
    for i, (title, target_id) in enumerate(targets):
        try:
            sent_messages = []
            if is_album:
                # Copy the media group in one call
                copied_msgs = await call.bot.copy_messages(
                    chat_id=target_id,
                    from_chat_id=cid,
                    message_ids=source_msg_ids,
                    disable_notification=not settings['sound']
                )
                sent_messages = [m.message_id for m in copied_msgs]
                
                # If there are buttons, send a separate helper message under the album
                if markup:
                    m = await call.bot.send_message(
                        chat_id=target_id,
                        text="👇",
                        reply_markup=markup,
                        disable_notification=not settings['sound']
                    )
                    sent_messages.append(m.message_id)
            else:
                mid = source_msg_ids[0]
                sent = await call.bot.copy_message(
                    chat_id=target_id,
                    from_chat_id=cid,
                    message_id=mid,
                    reply_markup=markup,
                    disable_notification=not settings['sound']
                )
                sent_messages = [sent.message_id]

            if settings['pin'] and sent_messages:
                with suppress(Exception):
                    await call.bot.pin_chat_message(chat_id=target_id, message_id=sent_messages[0])
            s += 1
        except TelegramRetryAfter as e:
            await asyncio.sleep(e.retry_after)
            # Retry once
            try:
                if is_album:
                    copied_msgs = await call.bot.copy_messages(chat_id=target_id, from_chat_id=cid, message_ids=source_msg_ids, disable_notification=not settings['sound'])
                    sent_messages = [m.message_id for m in copied_msgs]
                    if markup:
                        m = await call.bot.send_message(chat_id=target_id, text="👇", reply_markup=markup, disable_notification=not settings['sound'])
                        sent_messages.append(m.message_id)
                else:
                    sent = await call.bot.copy_message(chat_id=target_id, from_chat_id=cid, message_id=source_msg_ids[0], reply_markup=markup, disable_notification=not settings['sound'])
                    sent_messages = [sent.message_id]
                if settings['pin'] and sent_messages:
                    with suppress(Exception): await call.bot.pin_chat_message(chat_id=target_id, message_id=sent_messages[0])
                s += 1
            except Exception:
                f += 1
        except TelegramForbiddenError:
            await db.update_channel(target_id, title, "left", "unknown")
            f += 1
        except Exception as e:
            logging.error(f"Error copying messages to {title} ({target_id}): {e}")
            f += 1
        await asyncio.sleep(0.04)

    res = f"✅ <b>پایان!</b>\n🎯 مقصد: {t_type}\n✅ موفق: {s}\n❌ ناموفق: {f}"
    kb = InlineKeyboardMarkup(inline_keyboard=[[InlineKeyboardButton(text="🏠 خانه", callback_data=MenuCB(action="home").pack(), style="success")]])
    await UI.render(call.bot, call.message.chat.id, res, kb, db)
    
    # Delete the preview messages from admin's chat
    for mid in source_msg_ids:
        with suppress(Exception): await call.bot.delete_message(cid, mid)
    await state.clear()

@router.callback_query(MenuCB.filter(F.action == "search_info"))
async def search_info(call: CallbackQuery, db: AsyncDatabase, state: FSMContext):
    await state.set_state(PostStates.search_query)
    txt = "🔍 <b>جستجوی کانال و گروه</b>\n\n✏️ لطفاً بخشی از نام هدف را ارسال کنید:"
    kb = InlineKeyboardMarkup(inline_keyboard=[
        [InlineKeyboardButton(text="🔙 انصراف", callback_data=MenuCB(action="home").pack(), style="danger")]
    ])
    await UI.render(call.bot, call.message.chat.id, txt, kb, db, loading=False)
    await call.answer()

@router.message(PostStates.search_query)
async def process_search_query(msg: Message, db: AsyncDatabase, state: FSMContext):
    if msg.from_user.id != ADMIN_ID: return
    q = msg.text.strip()
    with suppress(Exception): await msg.delete()
    if not q: return
    
    res = await db.search(q)
    if not res:
        txt = f"🔍 <b>جستجوی منابع:</b> «{html.escape(q)}»\n\n❌ موردی یافت نشد. لطفاً عبارت دیگری ارسال کنید:"
        kb = InlineKeyboardMarkup(inline_keyboard=[
            [InlineKeyboardButton(text="🔙 انصراف", callback_data=MenuCB(action="home").pack(), style="danger")]
        ])
        await UI.render(msg.bot, msg.chat.id, txt, kb, db, loading=False)
        return
        
    await state.clear()
    
    btns = []
    for title, cid, status in res[:10]:
        channel_info = await db.get_channel(cid)
        c_type = channel_info[3] if channel_info else 'channel'
        icon = "📢" if c_type == 'channel' else "👥"
        
        btns.append([InlineKeyboardButton(text=f"{icon} {title}", callback_data=ChannelCB(action="manage", id=cid, page=0).pack(), style="primary")])
        
    btns.append([InlineKeyboardButton(text="🔙 بازگشت به خانه", callback_data=MenuCB(action="home").pack(), style="danger")])
    
    txt = f"🔍 <b>نتایج جستجو برای:</b> «{html.escape(q)}»\n\n👇 برای مدیریت روی نام منبع کلیک کنید:"
    await UI.render(msg.bot, msg.chat.id, txt, InlineKeyboardMarkup(inline_keyboard=btns), db, loading=False)

@router.message(Command("search"))
async def cmd_search(msg: Message, db: AsyncDatabase):
    if msg.from_user.id != ADMIN_ID: return
    with suppress(Exception): await msg.delete()
    q = msg.text.replace("/search", "").strip()
    if not q: return
    
    res = await db.search(q)
    if not res:
        txt = f"🔍 <b>جستجوی منابع:</b> «{html.escape(q)}»\n\n❌ موردی یافت نشد."
        kb = InlineKeyboardMarkup(inline_keyboard=[
            [InlineKeyboardButton(text="🔙 بازگشت به خانه", callback_data=MenuCB(action="home").pack(), style="danger")]
        ])
        await UI.render(msg.bot, msg.chat.id, txt, kb, db, loading=False)
        return
        
    btns = []
    for title, cid, status in res[:10]:
        channel_info = await db.get_channel(cid)
        c_type = channel_info[3] if channel_info else 'channel'
        icon = "📢" if c_type == 'channel' else "👥"
        
        btns.append([InlineKeyboardButton(text=f"{icon} {title}", callback_data=ChannelCB(action="manage", id=cid, page=0).pack(), style="primary")])
        
    btns.append([InlineKeyboardButton(text="🔙 بازگشت به خانه", callback_data=MenuCB(action="home").pack(), style="danger")])
    
    txt = f"🔍 <b>نتایج جستجو برای:</b> «{html.escape(q)}»\n\n👇 برای مدیریت روی نام منبع کلیک کنید:"
    await UI.render(msg.bot, msg.chat.id, txt, InlineKeyboardMarkup(inline_keyboard=btns), db, loading=False)

@router.callback_query(MenuCB.filter(F.action == "export"))
async def do_export(call: CallbackQuery, db: AsyncDatabase):
    await call.answer("⏳")
    data = await db.get_all_raw()
    out = io.StringIO()
    csv.writer(out).writerows([['ID', 'Title', 'Status', 'Type', 'Date']] + data)
    out.seek(0)
    with suppress(Exception): await call.message.delete()
    await call.message.answer_document(BufferedInputFile(out.getvalue().encode('utf-8-sig'), filename="Channels.csv"))
    txt, kb = await UI.dashboard(db)
    msg = await call.message.answer(txt, parse_mode=ParseMode.HTML, reply_markup=kb)
    await db.set_last_msg(msg.message_id)

@router.callback_query(MenuCB.filter(F.action == "help"))
async def show_help(call: CallbackQuery, db: AsyncDatabase):
    txt = "📚 <b>راهنما</b>\n\n1️⃣ افزودن کانال: فوروارد پیام یا ادمین کردن ربات.\n2️⃣ پست جدید: دکمه «ایجاد پست»."
    kb = InlineKeyboardMarkup(inline_keyboard=[[InlineKeyboardButton(text="🔙", callback_data=MenuCB(action="home").pack(), style="danger")]])
    await UI.render(call.bot, call.message.chat.id, txt, kb, db, loading=False)
    await call.answer()

@router.my_chat_member()
async def monitor(ev: ChatMemberUpdated, db: AsyncDatabase):
    c, n, o = ev.chat, ev.new_chat_member.status, ev.old_chat_member.status
    c_type = 'channel' if c.type == ChatType.CHANNEL else 'group'
    act = None
    if n in ['administrator', 'member'] and o not in ['administrator', 'member']:
        act = "🟢 اتصال"
        await db.update_channel(c.id, c.title, 'active', c_type)
    elif n in ['left', 'kicked'] and o not in ['left', 'kicked']:
        act = "🔴 قطع"
        await db.update_channel(c.id, c.title, 'left', c_type)
    if act:
        kb = InlineKeyboardMarkup(inline_keyboard=[[InlineKeyboardButton(text="🔄 رفرش", callback_data=MenuCB(action="refresh").pack(), style="primary")]])
        await UI.render(ev.bot, ADMIN_ID, f"🔔 {act}\n🏷 {html.escape(c.title or 'Unknown')}", kb, db)

async def main():
    logging.basicConfig(level=logging.INFO, format="%(message)s")
    bot = Bot(token=TOKEN)
    dp = Dispatcher(storage=MemoryStorage())
    db = AsyncDatabase(DB_NAME)
    await db.init()
    dp.update.outer_middleware(DebugLoggingMiddleware())
    dp.message.middleware(AlbumMiddleware())
    dp.update.middleware(DbMiddleware(db))
    dp.include_router(router)
    print("🚀 Channel Assistant Pro Started...")
    await bot.delete_webhook(drop_pending_updates=True)
    await dp.start_polling(bot, allowed_updates=["message", "my_chat_member", "callback_query"])

if __name__ == "__main__":
    with suppress(KeyboardInterrupt): asyncio.run(main())