import sqlite3
import asyncio
import os
import json
import re
import urllib.request
import urllib.error
import urllib.parse
from pyrogram import Client, filters, idle, enums
from pyrogram.types import (
    InlineKeyboardMarkup,
    InlineKeyboardButton,
    ReplyKeyboardMarkup,
    BotCommand,
    KeyboardButton,
    ChatMemberUpdated,
    ChatJoinRequest
)
from pyrogram.enums import ParseMode, ChatMemberStatus

# --- 🛑 CONFIGURATION 🛑 ---
API_ID = 23231578
API_HASH = "6016ef2dc043865f26667d89719b2c7e"
BOT_TOKEN = "8959939991:AAEn8QUFnKyAO8mtbko--jw6y3-F0GlZHEY".strip() # APNA TOKEN DAALEIN

# --- 🔐 ADMIN IDs (Plain Text as requested) ---
OWNER_ID = 6742282042
SUDO_ID  = 7204275439
ADMINS = [OWNER_ID, SUDO_ID]

app = Client("auto_button_bot", api_id=API_ID, api_hash=API_HASH, bot_token=BOT_TOKEN)

# --- DATABASE ---
DB_NAME = "button_bot.db"

def init_db():
    conn = sqlite3.connect(DB_NAME)
    c = conn.cursor()
    c.execute('CREATE TABLE IF NOT EXISTS users (user_id INTEGER PRIMARY KEY)')
    c.execute('CREATE TABLE IF NOT EXISTS settings (key TEXT PRIMARY KEY, value TEXT)')
    c.execute('CREATE TABLE IF NOT EXISTS custom_texts (key TEXT PRIMARY KEY, value TEXT)')
    c.execute('CREATE TABLE IF NOT EXISTS broadcast_logs (user_id INTEGER, message_id INTEGER, PRIMARY KEY(user_id, message_id))')
    c.execute('CREATE TABLE IF NOT EXISTS user_channels (user_id INTEGER, chat_id INTEGER, chat_title TEXT, UNIQUE(user_id, chat_id))')
    c.execute('CREATE TABLE IF NOT EXISTS saved_posts (post_id INTEGER PRIMARY KEY AUTOINCREMENT, user_id INTEGER, message_id INTEGER, keyboard_data TEXT, timestamp DATETIME DEFAULT CURRENT_TIMESTAMP)')
    c.execute('CREATE TABLE IF NOT EXISTS auto_projects_v2 (user_id INTEGER, chat_id INTEGER, chat_title TEXT, keyboard_data TEXT, PRIMARY KEY(user_id, chat_id))')
    conn.commit()
    
    # Load backup from JSON if exists on startup
    if os.path.exists("channels_backup.json"):
        try:
            with open("channels_backup.json", "r", encoding="utf-8") as f:
                data = json.load(f)
            for ch in data:
                c.execute("INSERT OR IGNORE INTO user_channels (user_id, chat_id, chat_title) VALUES (?, ?, ?)", (OWNER_ID, ch["chat_id"], ch.get("chat_title", "")))
            conn.commit()
        except: pass
    conn.close()

# --- DATABASE METHODS ---
def get_user(uid):
    conn = sqlite3.connect(DB_NAME)
    c = conn.cursor()
    c.execute('SELECT user_id FROM users WHERE user_id = ?', (uid,))
    res = c.fetchone()
    conn.close()
    return res

def add_user(user_id):
    conn = sqlite3.connect(DB_NAME)
    c = conn.cursor()
    try: c.execute('INSERT INTO users (user_id) VALUES (?)', (user_id,))
    except: pass
    conn.commit()
    conn.close()

def get_all_users():
    conn = sqlite3.connect(DB_NAME)
    c = conn.cursor()
    c.execute('SELECT user_id FROM users')
    rows = [r[0] for r in c.fetchall()]
    conn.close()
    return rows

def set_custom_text(key, value):
    conn = sqlite3.connect(DB_NAME)
    c = conn.cursor()
    c.execute('INSERT OR REPLACE INTO custom_texts (key, value) VALUES (?, ?)', (key, value))
    conn.commit()
    conn.close()

def get_custom_text(key, default_val):
    conn = sqlite3.connect(DB_NAME)
    c = conn.cursor()
    c.execute('SELECT value FROM custom_texts WHERE key = ?', (key,))
    res = c.fetchone()
    conn.close()
    return res[0] if res else default_val

def set_config(key, value):
    conn = sqlite3.connect(DB_NAME)
    c = conn.cursor()
    c.execute('INSERT OR REPLACE INTO settings (key, value) VALUES (?, ?)', (key, value))
    conn.commit()
    conn.close()

def get_config(key):
    conn = sqlite3.connect(DB_NAME)
    c = conn.cursor()
    c.execute('SELECT value FROM settings WHERE key = ?', (key,))
    res = c.fetchone()
    conn.close()
    return res[0] if res else None

def get_user_channels(uid):
    conn = sqlite3.connect(DB_NAME)
    c = conn.cursor()
    c.execute("SELECT chat_id, chat_title FROM user_channels WHERE user_id = ?", (uid,))
    res = c.fetchall()
    conn.close()
    return res

def save_broadcast_log(user_id, message_id):
    conn = sqlite3.connect(DB_NAME)
    c = conn.cursor()
    c.execute('INSERT OR REPLACE INTO broadcast_logs (user_id, message_id) VALUES (?, ?)', (user_id, message_id))
    conn.commit()
    conn.close()

def get_broadcast_logs():
    conn = sqlite3.connect(DB_NAME)
    c = conn.cursor()
    c.execute('SELECT user_id, message_id FROM broadcast_logs')
    logs = c.fetchall()
    conn.close()
    return logs

def clear_broadcast_logs():
    conn = sqlite3.connect(DB_NAME)
    c = conn.cursor()
    c.execute('DELETE FROM broadcast_logs')
    conn.commit()
    conn.close()

def clean_old_posts():
    conn = sqlite3.connect(DB_NAME)
    c = conn.cursor()
    c.execute("DELETE FROM saved_posts WHERE timestamp <= datetime('now', '-1 day')")
    conn.commit()
    conn.close()

# JSON Channel Sync Backup
def sync_channels_json():
    conn = sqlite3.connect(DB_NAME)
    c = conn.cursor()
    c.execute('SELECT chat_id, chat_title FROM user_channels')
    rows = c.fetchall()
    conn.close()
    data = [{"chat_id": r[0], "chat_title": r[1]} for r in rows]
    try:
        with open("channels_backup.json", "w", encoding="utf-8") as f:
            json.dump(data, f, indent=4, ensure_ascii=False)
    except: pass

init_db()

# --- DEFAULT TEXTS & CONSTANTS ---
START_PHOTO = "https://t.me/HackWithd/101"

DEFAULT_START_MSG = """<b>❓ Hey I'm Button Bot — I can help you create posts with inline buttons!</b>\n\n<b>✔️ My Features:</b>\n\n✔️ Create posts with inline button.\n✔️ Add buttons in channel existing posts.\n✔️ Auto buttons adding in channel new posts.\n✔️ Premium emojis supported.\n✔️ Colorful buttons can be added."""

DEFAULT_CREATE_POST_MSG = """🖼 <b>Send me your post content...</b>\n\nℹ️ You can send text, photo, video, audio, document, sticker, GIF, or voice note etc."""
DEFAULT_AUTO_ADDER_MSG = """✨ <b>AUTO BUTTON ADDER</b>\n___________________________\n\n<i>Automatically add custom buttons to your channel posts!</i>\n\n📌 <b>Choose an option below:</b>\n• ➕ Auto Button Adder Project - Create a new project\n• 🔗 Add Button to a Channel Post - Add buttons to a single post\n• 📁 My Projects - View/Edit your projects"""
AUTO_PROJECT_MSG = """📢 CHANNEL ADDITION\n\n🎯 To add buttons to channel posts:\n\nStep 1: Add @auto_button_robot as an administrator to your channel with all permissions\n\nStep 2: Tell me which channel — either way works:\n\n📬 **Forward a post**\n• Forward ANY post from your channel to this bot\n• It MUST be forwarded (not copy/pasted)\n• Make sure "Show sender's name" is ENABLED\n\n🆔 **Or just send the channel ID / link**\n• `-1001234567890` — with or without the -100\n• `@yourchannel`\n• `https://t.me/yourchannel`\n\n⚡️ Ready? Forward a post or send your channel ID! 👇"""

# --- STATES ---
TEXT_EDIT_STATE = {}
BUTTON_EDIT_STATE = {}
PHOTO_EDIT_STATE = {}
CREATOR_STATE = {}
BROADCAST_STATE = {}
IS_BROADCASTING = False
STOP_BROADCAST = False

# 🚀 BULLETPROOF TELEGRAM API BYPASS ENGINE FOR REPLY KEYBOARDS 
async def send_modded_reply_keyboard(client, chat_id, text, keyboard_raw, photo_url_or_id=None):
    if text: text = text.replace("<emoji id=", "<tg-emoji emoji-id=").replace("</emoji>", "</tg-emoji>")
    url = f"https://api.telegram.org/bot{BOT_TOKEN}/"
    keyboard = []
    for row in keyboard_raw:
        kb_row = []
        for btn in row:
            kb_btn = {"text": btn["text"]}
            if btn.get("style"): kb_btn["style"] = btn["style"]
            if btn.get("icon_custom_emoji_id"): kb_btn["icon_custom_emoji_id"] = btn["icon_custom_emoji_id"]
            kb_row.append(kb_btn)
        keyboard.append(kb_row)
        
    payload = {
        "chat_id": chat_id, "parse_mode": "HTML",
        "reply_markup": {"keyboard": keyboard, "resize_keyboard": True, "is_persistent": True}
    }
    
    if photo_url_or_id:
        url += "sendPhoto"
        payload["photo"] = photo_url_or_id
        payload["caption"] = text
    else:
        url += "sendMessage"
        payload["text"] = text
        
    headers = {'Content-Type': 'application/json'}
    req = urllib.request.Request(url, data=json.dumps(payload).encode('utf-8'), headers=headers)
    def fetch():
        try:
            with urllib.request.urlopen(req, timeout=10) as response: return json.loads(response.read().decode())
        except: return {"ok": False}
    res = await asyncio.to_thread(fetch)
    
    if not res or not res.get("ok"):
        pyro_kb = [[KeyboardButton(b["text"]) for b in row] for row in keyboard_raw]
        markup = ReplyKeyboardMarkup(pyro_kb, resize_keyboard=True)
        pyro_text = text.replace("<tg-emoji emoji-id=", "<emoji id=").replace("</tg-emoji>", "</emoji>")
        try:
            if photo_url_or_id: await client.send_photo(chat_id, photo_url_or_id, caption=pyro_text, reply_markup=markup, parse_mode=ParseMode.HTML)
            else: await client.send_message(chat_id, pyro_text, reply_markup=markup, parse_mode=ParseMode.HTML)
        except: pass

async def send_start_message_with_inline(client, chat_id, text, photo_url_or_id):
    if text: text = text.replace("<emoji id=", "<tg-emoji emoji-id=").replace("</emoji>", "</tg-emoji>")
    bot_info = await client.get_me()
    bot_username = bot_info.username
    
    ch_link = f"https://t.me/{bot_username}?startchannel=true&admin=change_info+post_messages+edit_messages+delete_messages+restrict_members+invite_users+pin_messages+promote_members+manage_video_chats+anonymous+manage_chat+manage_topics+post_stories+edit_stories+delete_stories"
    grp_link = f"https://t.me/{bot_username}?startgroup=true&admin=change_info+post_messages+edit_messages+delete_messages+restrict_members+invite_users+pin_messages+promote_members+manage_video_chats+anonymous+manage_chat+manage_topics+post_stories+edit_stories+delete_stories"
    
    btn_ch_text = get_config("btn_start_addch_text") or "📢 Add New Channel"
    btn_grp_text = get_config("btn_start_addgrp_text") or "👥 Add New Group"
    
    style_ch = get_config("s_start_addch") or "success"
    style_grp = get_config("s_start_addgrp") or "primary"
    
    emoji_ch = get_config("e_start_addch")
    emoji_grp = get_config("e_start_addgrp")
    
    kb_row = []
    btn_ch = {"text": btn_ch_text, "url": ch_link, "style": style_ch}
    if emoji_ch: btn_ch["icon_custom_emoji_id"] = emoji_ch
    kb_row.append(btn_ch)
    
    btn_grp = {"text": btn_grp_text, "url": grp_link, "style": style_grp}
    if emoji_grp: btn_grp["icon_custom_emoji_id"] = emoji_grp
    kb_row.append(btn_grp)
    
    keyboard_raw = [kb_row]
    
    url = f"https://api.telegram.org/bot{BOT_TOKEN}/"
    payload = {
        "chat_id": chat_id, "parse_mode": "HTML",
        "reply_markup": {"inline_keyboard": keyboard_raw}
    }
    
    if photo_url_or_id:
        url += "sendPhoto"
        payload["photo"] = photo_url_or_id
        payload["caption"] = text
    else:
        url += "sendMessage"
        payload["text"] = text
        
    headers = {'Content-Type': 'application/json'}
    req = urllib.request.Request(url, data=json.dumps(payload).encode('utf-8'), headers=headers)
    def fetch():
        try:
            with urllib.request.urlopen(req, timeout=10) as response: return json.loads(response.read().decode())
        except: return {"ok": False}
    res = await asyncio.to_thread(fetch)
    
    if not res or not res.get("ok"):
        pyro_kb = InlineKeyboardMarkup([[InlineKeyboardButton(btn_ch_text, url=ch_link), InlineKeyboardButton(btn_grp_text, url=grp_link)]])
        pyro_text = text.replace("<tg-emoji emoji-id=", "<emoji id=").replace("</tg-emoji>", "</emoji>")
        try:
            if photo_url_or_id: await client.send_photo(chat_id, photo_url_or_id, caption=pyro_text, reply_markup=pyro_kb, parse_mode=ParseMode.HTML)
            else: await client.send_message(chat_id, pyro_text, reply_markup=pyro_kb, parse_mode=ParseMode.HTML)
        except: pass

def resolve_photo(menu_name):
    p = get_config(f"photo_{menu_name}")
    if p == "none": return None
    return p or (get_config("start_photo") if get_config("start_photo") != "none" else None) or START_PHOTO

# 🌟 DYNAMIC BOTTOM MENUS 
def get_start_bottom_raw():
    return [
        [{"text": get_config("btn_start_1_text") or "Create Post with Buttons", "style": get_config("s_start_1") or "primary", "icon_custom_emoji_id": get_config("e_start_1") or "5803077308881309574"}],
        [{"text": get_config("btn_start_2_text") or "Auto Button Adder in Channel", "style": get_config("s_start_2") or "success", "icon_custom_emoji_id": get_config("e_start_2") or "5803077308881309574"}]
    ]

def get_aa_bottom_raw():
    return [
        [{"text": get_config("btn_aa_1_text") or "AUTO BUTTON ADDER PROJECT", "style": get_config("s_aa_1") or "primary", "icon_custom_emoji_id": get_config("e_aa_1") or "5803077308881309574"}],
        [{"text": get_config("btn_aa_2_text") or "ADD BUTTON TO A CHANNEL POST", "style": get_config("s_aa_2") or "primary", "icon_custom_emoji_id": get_config("e_aa_2") or "5803077308881309574"}],
        [{"text": get_config("btn_aa_3_text") or "MY PROJECTS", "style": get_config("s_aa_3") or "success", "icon_custom_emoji_id": get_config("e_aa_3") or "5803077308881309574"},
         {"text": get_config("btn_aa_4_text") or "BACK", "style": get_config("s_aa_4") or "danger", "icon_custom_emoji_id": get_config("e_aa_4") or "6093624063974971924"}]
    ]

def get_builder_bottom_raw():
    return [
        [{"text": "✅ DONE", "style": "success", "icon_custom_emoji_id": "5803077308881309574"}, 
         {"text": "❌ CANCEL", "style": "danger", "icon_custom_emoji_id": "6093624063974971924"}],
        [{"text": "↩️ UNDO LAST BUTTON", "style": "secondary", "icon_custom_emoji_id": "6093624063974971924"}]
    ]

def get_color_bottom_raw():
    return [
        [{"text": "⬜ Default", "style": "secondary", "icon_custom_emoji_id": "5803077308881309574"}],
        [{"text": "🔴 Red", "style": "danger", "icon_custom_emoji_id": "5803077308881309574"}, 
         {"text": "🔵 Blue", "style": "primary", "icon_custom_emoji_id": "5803077308881309574"}, 
         {"text": "🟢 Green", "style": "success", "icon_custom_emoji_id": "5803077308881309574"}]
    ]

# ==========================================
# 🚀 RAW API INLINE BUILDER ENGINE
# ==========================================
async def raw_copy_and_send_preview(chat_id, from_chat_id, msg_id, layout, add_preview_buttons=True):
    keyboard = []
    for r_idx, row in enumerate(layout):
        kb_row = []
        for btn in row:
            b = {"text": btn["text"], "url": btn["url"]}
            if btn.get("color") and btn["color"] != "secondary":
                b["style"] = btn["color"] 
            if btn.get("emoji_id"):
                b["icon_custom_emoji_id"] = btn["emoji_id"]
            kb_row.append(b)
        
        if add_preview_buttons and len(row) < 3:
            kb_row.append({"text": "➕", "callback_data": f"addbtn_{r_idx}"})
        keyboard.append(kb_row)
        
    if add_preview_buttons:
        keyboard.append([{"text": "➕", "callback_data": "addbtn_new"}])
        
    payload = {
        "chat_id": chat_id,
        "from_chat_id": from_chat_id,
        "message_id": msg_id
    }
    if keyboard:
        payload["reply_markup"] = {"inline_keyboard": keyboard}
        
    url = f"https://api.telegram.org/bot{BOT_TOKEN}/copyMessage"
    headers = {'Content-Type': 'application/json'}
    req = urllib.request.Request(url, data=json.dumps(payload).encode('utf-8'), headers=headers)
    
    def fetch():
        try:
            with urllib.request.urlopen(req, timeout=10) as response: return json.loads(response.read().decode())
        except Exception as e: return None
            
    res = await asyncio.to_thread(fetch)
    if res and res.get("ok"):
        return res["result"]["message_id"]
    return None

async def raw_edit_message_reply_markup(chat_id, message_id, layout):
    keyboard = []
    for row in layout:
        kb_row = []
        for btn in row:
            b = {"text": btn["text"], "url": btn["url"]}
            if btn.get("color") and btn["color"] != "secondary":
                b["style"] = btn["color"] 
            if btn.get("emoji_id"):
                b["icon_custom_emoji_id"] = btn["emoji_id"]
            kb_row.append(b)
        keyboard.append(kb_row)
        
    payload = {
        "chat_id": chat_id,
        "message_id": message_id,
        "reply_markup": {"inline_keyboard": keyboard} if keyboard else None
    }
        
    url = f"https://api.telegram.org/bot{BOT_TOKEN}/editMessageReplyMarkup"
    headers = {'Content-Type': 'application/json'}
    req = urllib.request.Request(url, data=json.dumps(payload).encode('utf-8'), headers=headers)
    
    def fetch():
        try:
            with urllib.request.urlopen(req, timeout=10) as response: return json.loads(response.read().decode())
        except Exception: return None
            
    res = await asyncio.to_thread(fetch)
    if res and res.get("ok"):
        return True
    return False

# ==========================================
# 🚨 CHANNEL AUTO-ADDER & JOIN TRACKER
# ==========================================
@app.on_message(filters.channel)
async def auto_button_adder(client, message):
    if message.edit_date: return # Avoid looping on edits
    chat_id = message.chat.id
    
    conn = sqlite3.connect(DB_NAME)
    c = conn.cursor()
    c.execute("SELECT keyboard_data FROM auto_projects_v2 WHERE chat_id = ?", (chat_id,))
    res = c.fetchone()
    conn.close()
    
    if res:
        try:
            layout = json.loads(res[0])
            await raw_edit_message_reply_markup(chat_id, message.id, layout)
        except Exception:
            pass

@app.on_chat_member_updated()
async def on_bot_permission_change(client, event: ChatMemberUpdated):
    chat = event.chat
    user = event.new_chat_member.user if event.new_chat_member else (event.old_chat_member.user if event.old_chat_member else None)
    if not user or user.id != client.me.id: return
    
    is_active = event.new_chat_member and event.new_chat_member.status == ChatMemberStatus.ADMINISTRATOR
    adder_id = event.from_user.id if event.from_user else None

    conn = sqlite3.connect(DB_NAME)
    c = conn.cursor()
    if is_active and adder_id:
        c.execute("INSERT OR IGNORE INTO user_channels (user_id, chat_id, chat_title) VALUES (?, ?, ?)", (adder_id, chat.id, chat.title))
        try: await client.send_message(adder_id, f"✅ **Success!**\nI'm now an admin in `{chat.title}`. You can now publish posts there!")
        except: pass
    else:
        c.execute("DELETE FROM user_channels WHERE chat_id = ?", (chat.id,))
        try: await client.send_message(adder_id, f"⚠️ **Alert:** I was removed as admin from `{chat.title}`. It has been removed from your list.")
        except: pass
    conn.commit()
    conn.close()
    
    # Sync JSON Backup
    sync_channels_json()

# Listen to ChatJoinRequests for Auto DM
@app.on_chat_join_request()
async def handle_auto_dm_join(client, request: ChatJoinRequest):
    chat_id = get_config("autodm_chat_id")
    msg_id = get_config("autodm_msg_id")
    layout_str = get_config("autodm_layout")
    
    if chat_id and msg_id and layout_str:
        try:
            layout = json.loads(layout_str)
            await raw_copy_and_send_preview(request.from_user.id, int(chat_id), int(msg_id), layout, add_preview_buttons=False)
        except: pass


# ==========================================
# 🎛 /admin - ADMIN PANEL COMMANDS
# ==========================================
@app.on_message(filters.command("admin") & filters.user(ADMINS))
async def admin_help_command(client, message):
    msg = """👑 **ADMIN PANEL (BUTTON BOT)** 👑

**🛠 General Commands:**
/start - Start the bot & show the main menu
/stats - View total users and connected channels

**⚙️ Bot Customization:**
/text - Edit text for any menu interface
/button - Rename & add premium emoji to menu buttons
/colur or /color - Change the color/style of any menu button
/photo - Change the bot's menu photos

**🤖 Auto DM on Join Request:**
/autodm - Set an Auto DM message for users sending join requests to your channels

**📣 Broadcast Commands:**
/broadcast - Broadcast a message to all users
/stopBroadcast - Stop a currently running broadcast
/BroadMessageDelete - Delete the last broadcasted messages"""
    await message.reply(msg, parse_mode=ParseMode.MARKDOWN)

@app.on_message(filters.command("stats") & filters.user(ADMINS))
async def stats_cmd(client, message):
    conn = sqlite3.connect(DB_NAME)
    c = conn.cursor()
    c.execute('SELECT COUNT(*) FROM users')
    users_count = c.fetchone()[0]
    c.execute('SELECT COUNT(DISTINCT chat_id) FROM user_channels')
    channels_count = c.fetchone()[0]
    conn.close()
    
    text = f"📊 **𝗕𝗢𝗧 𝗦𝗧𝗔𝗧𝗜𝗦𝗧𝗜𝗖𝗦** 📊\n\n👤 Total Users: `{users_count}`\n📢 Connected Channels/Groups: `{channels_count}`"
    await message.reply(text)

@app.on_message(filters.command("autodm") & filters.user(ADMINS))
async def autodm_setup_cmd(client, message):
    CREATOR_STATE[message.from_user.id] = {"step": "WAIT_MEDIA", "mode": "autodm"}
    kb = [[{"text": "❌ CANCEL", "style": "danger", "icon_custom_emoji_id": "6093624063974971924"}]]
    await send_modded_reply_keyboard(client, message.chat.id, "⚙️ **Auto DM Setup (Join Request)**\n\nSend the text, photo, or video for your welcome Auto DM message:", kb)

@app.on_message(filters.command("photo") & filters.user(ADMINS))
async def photo_editor_cmd(client, message):
    kb = InlineKeyboardMarkup([[InlineKeyboardButton("Start Menu", callback_data="photom_start")], [InlineKeyboardButton("Auto Adder Menu", callback_data="photom_aa")]])
    await message.reply("🖼 **Menu Photo Editor**\nSelect the menu whose photo you want to change:", reply_markup=kb)

@app.on_callback_query(filters.regex(r"^photom_(.*)"))
async def cb_photo_editor(client, cb):
    menu = cb.data.replace("photom_", "")
    PHOTO_EDIT_STATE[cb.from_user.id] = {"menu": menu}
    kb = InlineKeyboardMarkup([[InlineKeyboardButton("🗑 Remove Photo", callback_data=f"photodel_{menu}")]])
    await cb.message.edit_text(f"🖼 **Send the NEW PHOTO** for `{menu.upper()}` menu now.\nOr click the button below to remove the photo completely:", reply_markup=kb)
    
@app.on_callback_query(filters.regex(r"^photodel_(.*)"))
async def cb_photo_delete(client, cb):
    menu = cb.data.replace("photodel_", "")
    set_config(f"photo_{menu}", "none")
    if cb.from_user.id in PHOTO_EDIT_STATE: del PHOTO_EDIT_STATE[cb.from_user.id]
    await cb.message.edit_text(f"✅ Photo for `{menu.upper()}` menu has been removed!")

@app.on_message(filters.command("text") & filters.user(ADMINS))
async def text_editor_cmd(client, message):
    kb = InlineKeyboardMarkup([
        [InlineKeyboardButton("Start Menu Msg", callback_data="txt_start_msg")],
        [InlineKeyboardButton("Create Post Msg", callback_data="txt_create_msg")],
        [InlineKeyboardButton("Auto Adder Msg", callback_data="txt_aa_msg")]
    ])
    await message.reply("📝 **Text Content Editor**\nSelect the menu whose text you want to change:", reply_markup=kb)

@app.on_callback_query(filters.regex(r"^txt_(.*)"))
async def cb_text_editor(client, cb):
    key = cb.data.replace("txt_", "")
    TEXT_EDIT_STATE[cb.from_user.id] = {"key": key}
    await cb.message.edit_text(f"📝 Send the new text for `{key.upper()}` now.\n✅ **You can use HTML tags and Premium Emojis!**")

@app.on_message(filters.command("button") & filters.user(ADMINS))
async def button_editor_cmd(client, message):
    kb = InlineKeyboardMarkup([[InlineKeyboardButton("Start Menu Buttons", callback_data="btnm_start")], [InlineKeyboardButton("Auto Adder Menu", callback_data="btnm_aa")]])
    await message.reply("⚙️ **Button Editor Wizard**\nSelect a menu to modify its buttons:", reply_markup=kb)

@app.on_callback_query(filters.regex(r"^btnm_(.*)"))
async def cb_button_menu(client, cb):
    menu = cb.data.replace("btnm_", "")
    if menu == "start": 
        kb = [
            [InlineKeyboardButton("Create Post (Bottom)", callback_data="btn_start_1"), InlineKeyboardButton("Auto Adder (Bottom)", callback_data="btn_start_2")],
            [InlineKeyboardButton("Add Channel (Inline)", callback_data="btn_start_addch"), InlineKeyboardButton("Add Group (Inline)", callback_data="btn_start_addgrp")]
        ]
    elif menu == "aa": 
        kb = [[InlineKeyboardButton("Auto Project", callback_data="btn_aa_1")], [InlineKeyboardButton("Add to Post", callback_data="btn_aa_2")], [InlineKeyboardButton("My Projects", callback_data="btn_aa_3"), InlineKeyboardButton("Back", callback_data="btn_aa_4")]]
    await cb.message.edit_text(f"⚙️ **Select button to edit in {menu.upper()}:**", reply_markup=InlineKeyboardMarkup(kb))

@app.on_callback_query(filters.regex(r"^btn_(.*)"))
async def cb_button_edit_start(client, cb):
    data = cb.data.replace("btn_", "")
    parts = data.split("_")
    menu, idx = parts[0], parts[1]
    BUTTON_EDIT_STATE[cb.from_user.id] = {"menu": menu, "idx": idx}
    await cb.message.edit_text("✏️ **Send the NEW NAME for this button.**\n*(Send a Premium Emoji alongside text, the bot will extract and set it perfectly!)*")

@app.on_message(filters.command(["colur", "color"]) & filters.user(ADMINS))
async def color_cmd_handler(client, message):
    kb = InlineKeyboardMarkup([[InlineKeyboardButton("Start Menu", callback_data="colm_start")], [InlineKeyboardButton("Auto Adder Menu", callback_data="colm_aa")]])
    await message.reply("🎨 **Button Style Wizard**\nSelect a menu to customize its color:", reply_markup=kb)

@app.on_callback_query(filters.regex(r"^colm_(.*)"))
async def colur_menu_cb_handler(client, cb):
    menu = cb.data.replace("colm_", "")
    if menu == "start": 
        kb = [
            [InlineKeyboardButton("Create Post (Bottom)", callback_data="colur_a_c_start_1"), InlineKeyboardButton("Auto Adder (Bottom)", callback_data="colur_a_c_start_2")],
            [InlineKeyboardButton("Add Channel (Inline)", callback_data="colur_a_c_start_addch"), InlineKeyboardButton("Add Group (Inline)", callback_data="colur_a_c_start_addgrp")]
        ]
    elif menu == "aa": 
        kb = [[InlineKeyboardButton("Auto Project", callback_data="colur_a_c_aa_1")], [InlineKeyboardButton("Add to Post", callback_data="colur_a_c_aa_2")], [InlineKeyboardButton("My Projects", callback_data="colur_a_c_aa_3"), InlineKeyboardButton("Back", callback_data="colur_a_c_aa_4")]]
    await cb.message.edit_text(f"🎨 **Select button in {menu.upper()}:**", reply_markup=InlineKeyboardMarkup(kb))

@app.on_callback_query(filters.regex(r"^colur_a_c_(.*)"))
async def colur_color_sel_handler(client, cb):
    data = cb.data.replace("colur_a_c_", "")
    parts = data.split("_")
    menu, idx = parts[0], parts[1]
    kb = InlineKeyboardMarkup([
        [InlineKeyboardButton("🔵 Blue (Primary)", callback_data=f"colur_s_{menu}_{idx}_primary")],
        [InlineKeyboardButton("🟢 Green (Success)", callback_data=f"colur_s_{menu}_{idx}_success")],
        [InlineKeyboardButton("🔴 Red (Danger)", callback_data=f"colur_s_{menu}_{idx}_danger")],
        [InlineKeyboardButton("⚪ Default (Secondary)", callback_data=f"colur_s_{menu}_{idx}_secondary")]
    ])
    await cb.message.edit_text("Select the new color:", reply_markup=kb)

@app.on_callback_query(filters.regex(r"^colur_s_(.*)"))
async def colur_save_handler(client, cb):
    data = cb.data.replace("colur_s_", "")
    parts = data.split("_")
    menu, idx, color = parts[0], parts[1], parts[2]
    set_config(f"s_{menu}_{idx}", color)
    await cb.message.edit_text(f"✅ Color updated to **{color.upper()}** successfully!\nCheck the menu to see changes.")


# ==========================================
# 🟢 START & MENUS COMMANDS
# ==========================================
@app.on_message(filters.command("start") & filters.private)
async def start_private(client, message):
    uid = message.from_user.id
    if uid in CREATOR_STATE: del CREATOR_STATE[uid]
    if uid in BROADCAST_STATE: del BROADCAST_STATE[uid]
    if not get_user(uid): add_user(uid)
    await send_home_menu(client, message.chat.id, uid)

async def send_home_menu(client, chat_id, uid):
    caption = get_custom_text("start_msg", DEFAULT_START_MSG)
    
    # 1. Start Photo Ke Sath - Add Channel & Group (Inline Buttons side-by-side)
    await send_start_message_with_inline(client, chat_id, caption, resolve_photo("start"))
    
    # 2. Main Bot Menu - Create Post & Auto Adder (Bottom Menu) (Uses invisible character 'ᅠ' for zero visual clutter)
    kb = get_start_bottom_raw()
    await send_modded_reply_keyboard(client, chat_id, "Hey 👋", kb, None)

async def send_aa_menu(client, chat_id, uid):
    caption = get_custom_text("aa_msg", DEFAULT_AUTO_ADDER_MSG)
    kb = get_aa_bottom_raw()
    await send_modded_reply_keyboard(client, chat_id, caption, kb, resolve_photo("aa"))


# ==========================================
# 🎙️ BROADCAST SYSTEM
# ==========================================
@app.on_message(filters.command("broadcast") & filters.user(ADMINS))
async def broadcast_init(client, message):
    global IS_BROADCASTING
    if IS_BROADCASTING: return await message.reply_text("⚠️ A broadcast is already running!")
    BROADCAST_STATE[message.from_user.id] = {"step": "WAIT_MSG"}
    await message.reply_text("📢 **Broadcast Setup**\nPlease send the message to broadcast.\n👇 **Drop it below:**")

@app.on_message(filters.command("stopBroadcast") & filters.user(ADMINS))
async def stop_broadcast_command(client, message):
    global STOP_BROADCAST, IS_BROADCASTING
    if not IS_BROADCASTING: return await message.reply_text("❌ No broadcast running.")
    STOP_BROADCAST = True
    await message.reply_text("🛑 **Stopping Broadcast...**")

@app.on_message(filters.command("BroadMessageDelete") & filters.user(ADMINS))
async def delete_broadcast_messages(client, message):
    logs = get_broadcast_logs()
    if not logs: return await message.reply_text("⚠️ No recent broadcast messages found to delete.")
    msg = await message.reply_text("🗑 **Deleting last broadcast messages... Please wait.**")
    deleted_count = 0
    for uid, mid in logs:
        try:
            await client.delete_messages(chat_id=uid, message_ids=mid)
            deleted_count += 1
            await asyncio.sleep(0.05)
        except: pass
    clear_broadcast_logs()
    await msg.edit_text(f"✅ **Broadcast Deletion Complete!**\n🗑 Deleted from `{deleted_count}` chats.")

async def show_broadcast_preview(client, user_id):
    data = BROADCAST_STATE[user_id]
    buttons = []
    if data.get("btn_name") and data.get("btn_url"):
        buttons.append([InlineKeyboardButton(data["btn_name"], url=data["btn_url"])])
    buttons.append([InlineKeyboardButton("✅ SEND", callback_data="broadcast_confirm"), InlineKeyboardButton("❌ CANCEL", callback_data="broadcast_cancel")])
    markup = InlineKeyboardMarkup(buttons)
    await client.send_message(user_id, "👁‍🗨 **PREVIEW OF BROADCAST** 👇")
    if data["msg_type"] == "photo": await client.send_photo(user_id, data["msg_file_id"], caption=data.get("caption", ""), reply_markup=markup, parse_mode=ParseMode.HTML)
    elif data["msg_type"] == "video": await client.send_video(user_id, data["msg_file_id"], caption=data.get("caption", ""), reply_markup=markup, parse_mode=ParseMode.HTML)
    else: await client.send_message(user_id, data.get("caption", "Broadcast"), reply_markup=markup, parse_mode=ParseMode.HTML)

@app.on_callback_query(filters.regex("broadcast_confirm"))
async def start_broadcasting(client, callback_query):
    global IS_BROADCASTING, STOP_BROADCAST
    user_id = callback_query.from_user.id
    if user_id not in BROADCAST_STATE: return await callback_query.answer("Session expired.", show_alert=True)
    data = BROADCAST_STATE[user_id]
    del BROADCAST_STATE[user_id]
    try: await callback_query.message.edit_reply_markup(None)
    except: pass
    await client.send_message(user_id, "🚀 **Broadcast Started!**")
    IS_BROADCASTING = True
    STOP_BROADCAST = False
    clear_broadcast_logs()
    users = get_all_users()
    sent = 0
    markup = None
    if data.get("btn_name") and data.get("btn_url"):
        markup = InlineKeyboardMarkup([[InlineKeyboardButton(data["btn_name"], url=data["btn_url"])]])
        
    for uid in users:
        if STOP_BROADCAST: break
        try:
            msg = None
            if data["msg_type"] == "photo": msg = await client.send_photo(uid, data["msg_file_id"], caption=data.get("caption", ""), reply_markup=markup, parse_mode=ParseMode.HTML)
            elif data["msg_type"] == "video": msg = await client.send_video(uid, data["msg_file_id"], caption=data.get("caption", ""), reply_markup=markup, parse_mode=ParseMode.HTML)
            else: msg = await client.send_message(uid, data.get("caption", "Broadcast"), reply_markup=markup, parse_mode=ParseMode.HTML)
            if msg: save_broadcast_log(uid, msg.id)
            sent += 1
            await asyncio.sleep(0.05)
        except: pass
    IS_BROADCASTING = False
    await client.send_message(user_id, f"📣 **Broadcast Completed!**\n✅ Sent: `{sent}`")

@app.on_callback_query(filters.regex("broadcast_cancel"))
async def cancel_broadcasting(client, callback_query):
    user_id = callback_query.from_user.id
    if user_id in BROADCAST_STATE: del BROADCAST_STATE[user_id]
    try: await callback_query.message.delete()
    except: pass
    await client.send_message(user_id, "❌ Broadcast Cancelled.")


# ==========================================
# 🔄 MASTER ROUTER (Handles States & Logic)
# ==========================================
@app.on_message(filters.private & ~filters.command(["start", "admin", "stats", "autodm", "broadcast", "stopBroadcast", "BroadMessageDelete", "button", "text", "photo", "color", "colur"]))
async def master_router(client, message):
    user_id = message.from_user.id
    chat_id = message.chat.id
    text = message.text.strip() if message.text else ""

    # --- ADMIN STATES ---
    if user_id in ADMINS:
        if user_id in PHOTO_EDIT_STATE:
            if not message.photo: return await message.reply("❌ Please send a valid **Photo**.")
            menu = PHOTO_EDIT_STATE[user_id].get("menu", "start")
            set_config(f"photo_{menu}", message.photo.file_id)
            del PHOTO_EDIT_STATE[user_id]
            await message.reply(f"✅ **{menu.upper()} Menu Photo Updated Successfully!**")
            return

        if user_id in TEXT_EDIT_STATE:
            key = TEXT_EDIT_STATE[user_id]["key"]
            if message.text: new_text = message.text.html
            elif message.caption: new_text = message.caption.html
            else: return await message.reply("❌ Please send text or media with a caption!")
            set_custom_text(key, new_text)
            await message.reply(f"✅ Text for `{key}` updated successfully!\n\n**Live Preview:**\n{new_text}", parse_mode=ParseMode.HTML)
            del TEXT_EDIT_STATE[user_id]
            return

        if user_id in BUTTON_EDIT_STATE:
            menu = BUTTON_EDIT_STATE[user_id]["menu"]
            idx = BUTTON_EDIT_STATE[user_id]["idx"]
            new_text = message.text
            emoji_id = None
            if message.entities:
                for ent in sorted(message.entities, key=lambda x: x.offset, reverse=True):
                    if ent.type == enums.MessageEntityType.CUSTOM_EMOJI:
                        if not emoji_id: emoji_id = str(ent.custom_emoji_id)
                        new_text = new_text[:ent.offset] + new_text[ent.offset + ent.length:]
            new_text = new_text.strip()
            if new_text: set_config(f"btn_{menu}_{idx}_text", new_text)
            if emoji_id: set_config(f"e_{menu}_{idx}", emoji_id)
            del BUTTON_EDIT_STATE[user_id]
            success_msg = f"✅ **Button Updated!**\nName: `{new_text}`"
            if emoji_id: success_msg += "\n✨ **Premium Emoji Detected & Saved Perfectly!**"
            await message.reply(success_msg)
            return

        if user_id in BROADCAST_STATE:
            state = BROADCAST_STATE[user_id]
            step = state["step"]
            if step == "WAIT_MSG":
                file_id, file_type, caption = None, "text", message.caption.html if message.caption else message.text.html if message.text else ""
                if message.photo: file_id, file_type = message.photo.file_id, "photo"
                elif message.video: file_type = message.video.file_id, "video"
                BROADCAST_STATE[user_id].update({"msg_file_id": file_id, "msg_type": file_type, "caption": caption, "step": "WAIT_BTN_NAME"})
                await message.reply_text("✅ Message Received!\n⌨️ **Enter Button Name** (or 'skip')")
                return
            elif step == "WAIT_BTN_NAME":
                if message.text.lower() == "skip":
                    BROADCAST_STATE[user_id]["btn_name"] = None
                    BROADCAST_STATE[user_id]["step"] = "CONFIRM"
                    await show_broadcast_preview(client, user_id)
                else:
                    BROADCAST_STATE[user_id]["btn_name"] = message.text
                    BROADCAST_STATE[user_id]["step"] = "WAIT_BTN_URL"
                    await message.reply_text("🔗 **Now Enter Button Link** (URL):")
                return
            elif step == "WAIT_BTN_URL":
                BROADCAST_STATE[user_id]["btn_url"] = message.text
                BROADCAST_STATE[user_id]["step"] = "CONFIRM"
                await show_broadcast_preview(client, user_id)
                return

    # --- MENU NAVIGATION (Processed FIRST to avoid state hanging) ---
    b_st_1 = get_config("btn_start_1_text") or "Create Post with Buttons"
    b_st_2 = get_config("btn_start_2_text") or "Auto Button Adder in Channel"
    b_aa_1 = get_config("btn_aa_1_text") or "AUTO BUTTON ADDER PROJECT"
    b_aa_2 = get_config("btn_aa_2_text") or "ADD BUTTON TO A CHANNEL POST"
    b_aa_3 = get_config("btn_aa_3_text") or "MY PROJECTS"
    b_aa_4 = get_config("btn_aa_4_text") or "BACK"

    nav_triggers = [b_st_1, b_st_2, b_aa_1, b_aa_2, b_aa_3, b_aa_4, "AUTO BUTTON ADDER PROJECT", "ADD BUTTON TO A CHANNEL POST", "BACK", "MY PROJECTS", "Create Post with Buttons", "Auto Button Adder in Channel"]

    if text and any(nav.lower() in text.lower() for nav in nav_triggers) and text not in ["❌ CANCEL", "✅ DONE", "↩️ UNDO LAST BUTTON"]:
        if user_id in CREATOR_STATE:
            del CREATOR_STATE[user_id]

        if text == b_st_1 or "Create Post" in text:
            CREATOR_STATE[user_id] = {"step": "WAIT_MEDIA", "mode": "create"}
            kb = [[{"text": "❌ CANCEL", "style": "danger", "icon_custom_emoji_id": "6093624063974971924"}]]
            create_msg = get_custom_text("create_msg", DEFAULT_CREATE_POST_MSG)
            await send_modded_reply_keyboard(client, chat_id, create_msg, kb)
            return

        elif text == b_st_2 or "Auto Button Adder" in text:
            await send_aa_menu(client, chat_id, user_id)
            return
            
        elif text == b_aa_1 or "AUTO BUTTON ADDER PROJECT" in text.upper():
            CREATOR_STATE[user_id] = {"step": "WAIT_CHANNEL", "mode": "auto_project"}
            kb = [[{"text": "❌ CANCEL", "style": "danger", "icon_custom_emoji_id": "6093624063974971924"}]]
            await send_modded_reply_keyboard(client, chat_id, AUTO_PROJECT_MSG, kb)
            return

        elif text == b_aa_2 or "ADD BUTTON TO A CHANNEL POST" in text.upper():
            CREATOR_STATE[user_id] = {"step": "WAIT_POST_LINK", "mode": "edit"}
            kb = [[{"text": "❌ CANCEL", "style": "danger", "icon_custom_emoji_id": "6093624063974971924"}]]
            msg_text = "🔗 Send the link to the channel post you want to add buttons to.\n\n📋 Format: `https://t.me/channel/123` or `https://t.me/c/123456/789`"
            await send_modded_reply_keyboard(client, chat_id, msg_text, kb)
            return

        elif text == b_aa_4 or "BACK" in text.upper():
            await send_home_menu(client, chat_id, user_id)
            return

        elif text == b_aa_3 or "MY PROJECTS" in text.upper():
            clean_old_posts()
            conn = sqlite3.connect(DB_NAME)
            c = conn.cursor()
            
            c.execute("SELECT chat_title, chat_id FROM auto_projects_v2 WHERE user_id = ?", (user_id,))
            auto_projects = c.fetchall()
            
            c.execute("SELECT post_id, timestamp FROM saved_posts WHERE user_id = ? ORDER BY post_id DESC", (user_id,))
            posts = c.fetchall()
            conn.close()

            if not posts and not auto_projects:
                await message.reply("📁 **You have no saved projects.**\n(Remember: Custom posts are automatically deleted after 24 hours)")
                return

            kb = []
            for ch_title, ch_id in auto_projects:
                kb.append([InlineKeyboardButton(f"🤖 Auto: {ch_title}", callback_data=f"viewauto_{ch_id}")])
                
            for pid, time in posts:
                time_short = time.split()[1][:5]
                kb.append([InlineKeyboardButton(f"📄 Post - {time_short}", callback_data=f"viewpost_{pid}")])

            await message.reply("📁 **My Projects:**", reply_markup=InlineKeyboardMarkup(kb))
            return


    # --- BUTTON CREATOR LOGIC (Advanced Live Preview & Extraction) ---
    if user_id in CREATOR_STATE:
        state = CREATOR_STATE[user_id]
        
        if text == "❌ CANCEL":
            preview_msg_id = state.get("preview_msg_id")
            del CREATOR_STATE[user_id]
            
            if preview_msg_id:
                async def silent_delete():
                    try: await client.delete_messages(chat_id, preview_msg_id)
                    except: pass
                asyncio.create_task(silent_delete())
                
            await send_home_menu(client, chat_id, user_id)
            return
            
        elif text == "↩️ UNDO LAST BUTTON":
            if state["step"] == "BUILDING" and len(state["layout"]) > 0:
                if len(state["layout"][-1]) > 1:
                    state["layout"][-1].pop()
                else:
                    state["layout"].pop()
                
                try: await client.delete_messages(chat_id, state["preview_msg_id"])
                except: pass
                
                src_chat = state["target_chat_id"] if state.get("mode") not in ["auto_project", "autodm"] else chat_id
                src_msg = state["target_msg_id"]
                new_msg_id = await raw_copy_and_send_preview(chat_id, src_chat, src_msg, state["layout"])
                CREATOR_STATE[user_id]["preview_msg_id"] = new_msg_id
                await message.reply("↩️ Last button removed.")
            else:
                await message.reply("⚠️ No buttons to undo or finish current button first.")
            return
            
        elif text == "✅ DONE":
            if state["step"] not in ["WAIT_MEDIA", "WAIT_POST_LINK", "WAIT_CHANNEL", "WAIT_BTN_NAME", "WAIT_URL", "WAIT_COLOR"]:
                mode = state.get("mode", "create")
                
                if mode == "create":
                    conn = sqlite3.connect(DB_NAME)
                    c = conn.cursor()
                    c.execute("INSERT INTO saved_posts (user_id, message_id, keyboard_data) VALUES (?, ?, ?)", 
                              (user_id, state["target_msg_id"], json.dumps(state["layout"])))
                    conn.commit()
                    conn.close()
                    
                    try: await client.delete_messages(chat_id, state["preview_msg_id"])
                    except: pass
                    del CREATOR_STATE[user_id]
                    await message.reply("✅ **Post Saved to 'My Projects'!**\n(Note: Automatically expires in 24 hours)")
                    await send_aa_menu(client, chat_id, user_id)
                    
                elif mode == "edit":
                    success = await raw_edit_message_reply_markup(state["target_chat_id"], state["target_msg_id"], state["layout"])
                    if success:
                        btn_count = sum(len(row) for row in state["layout"])
                        text_msg = f"✅ **Buttons Added Successfully!**\n\n📢 Channel: {state.get('chat_title', str(state['target_chat_id']))}\n📝 Message ID: {state['target_msg_id']}\n🔘 Buttons: {btn_count} buttons added\n\nThe buttons have been applied to the channel post!"
                        
                        view_kb = InlineKeyboardMarkup([[InlineKeyboardButton("👁 View Post", url=state["target_link"])]])
                        await message.reply(text_msg, reply_markup=view_kb)
                    else:
                        await message.reply("❌ **Failed to add buttons.** Make sure I have admin rights to edit messages in that channel.")
                        
                    try: await client.delete_messages(chat_id, state["preview_msg_id"])
                    except: pass
                    del CREATOR_STATE[user_id]
                    kb = get_aa_bottom_raw()
                    await send_modded_reply_keyboard(client, chat_id, "🏠 Returned to main menu.", kb)
                    
                elif mode == "auto_project":
                    conn = sqlite3.connect(DB_NAME)
                    c = conn.cursor()
                    c.execute("INSERT OR REPLACE INTO auto_projects_v2 (user_id, chat_id, chat_title, keyboard_data) VALUES (?, ?, ?, ?)", 
                              (user_id, state["target_chat_id"], state["chat_title"], json.dumps(state["layout"])))
                    conn.commit()
                    conn.close()
                    
                    try: await client.delete_messages(chat_id, state["preview_msg_id"])
                    except: pass
                    del CREATOR_STATE[user_id]
                    
                    await message.reply("✅ **Project saved!**\n\nButtons will be automatically added to new posts in this channel.")
                    kb = get_aa_bottom_raw()
                    await send_modded_reply_keyboard(client, chat_id, "🏠 Returned to main menu.", kb)
                    
                elif mode == "autodm":
                    set_config("autodm_chat_id", str(chat_id))
                    set_config("autodm_msg_id", str(state["target_msg_id"]))
                    set_config("autodm_layout", json.dumps(state["layout"]))
                    
                    try: await client.delete_messages(chat_id, state["preview_msg_id"])
                    except: pass
                    del CREATOR_STATE[user_id]
                    
                    await message.reply("✅ **Auto DM Setup Complete!**\nUsers sending join requests will now receive this message automatically.")
                    await send_home_menu(client, chat_id, user_id)
            else:
                await message.reply("⚠️ Please complete the current setup first!")
            return

        if state["step"] == "WAIT_MEDIA":
            CREATOR_STATE[user_id].update({
                "target_chat_id": chat_id,
                "target_msg_id": message.id,
                "layout": [],
                "step": "BUILDING"
            })
            
            kb = get_builder_bottom_raw()
            await send_modded_reply_keyboard(client, chat_id, "👇 Tap ➕ on the message below to add a button.", kb)
            
            new_msg_id = await raw_copy_and_send_preview(chat_id, chat_id, message.id, [])
            CREATOR_STATE[user_id]["preview_msg_id"] = new_msg_id
            return
            
        elif state["step"] == "WAIT_CHANNEL":
            target_chat_id, chat_title = None, None
            
            if message.forward_from_chat and message.forward_from_chat.type == enums.ChatType.CHANNEL:
                target_chat_id = message.forward_from_chat.id
                chat_title = message.forward_from_chat.title
            elif text:
                link = text.strip()
                match_private = re.search(r"t\.me/c/(\d+)", link)
                match_public = re.search(r"t\.me/([^/]+)", link)
                
                if match_private: target_chat_id = int("-100" + match_private.group(1))
                elif match_public:
                    target_chat_id = match_public.group(1)
                    if not target_chat_id.startswith("@"): target_chat_id = "@" + target_chat_id
                elif link.startswith("-100") and link[1:].isdigit():
                    target_chat_id = int(link)
                    
            if not target_chat_id:
                state["err_count"] = state.get("err_count", 0) + 1
                if state["err_count"] >= 2:
                    del CREATOR_STATE[user_id]
                    kb = get_aa_bottom_raw()
                    await send_modded_reply_keyboard(client, chat_id, "❌ Cancelled due to multiple invalid attempts.", kb)
                    return
                return await message.reply("❌ Invalid format. Please forward a post or send a valid channel link/ID.")
                
            try:
                ch_info = await client.get_chat(target_chat_id)
                chat_title = ch_info.title
                
                demo_msg = await client.send_message(chat_id, "📝 **DEMO POST**\n\nThis is a preview of how your channel posts will look with buttons.")
                
                CREATOR_STATE[user_id].update({
                    "target_chat_id": target_chat_id,
                    "chat_title": chat_title,
                    "target_msg_id": demo_msg.id,
                    "layout": [],
                    "step": "BUILDING"
                })
                
                await message.reply(f"✅ Channel detected: **{chat_title}**\n\n🎯 Now configure your buttons below.")
                
                kb = get_builder_bottom_raw()
                await send_modded_reply_keyboard(client, chat_id, "Click ➕ to add buttons below!", kb)
                
                new_msg_id = await raw_copy_and_send_preview(chat_id, chat_id, demo_msg.id, [])
                CREATOR_STATE[user_id]["preview_msg_id"] = new_msg_id
            except Exception as e:
                state["err_count"] = state.get("err_count", 0) + 1
                if state["err_count"] >= 2:
                    del CREATOR_STATE[user_id]
                    kb = get_aa_bottom_raw()
                    await send_modded_reply_keyboard(client, chat_id, "❌ Cancelled due to multiple invalid attempts.", kb)
                    return
                return await message.reply("❌ Error accessing channel: Make sure I am an admin in that channel.")
            return

        elif state["step"] == "WAIT_POST_LINK":
            link = message.text.strip() if message.text else ""
            match_private = re.search(r"t\.me/c/(\d+)/(\d+)", link)
            match_public = re.search(r"t\.me/([^/]+)/(\d+)", link)
            
            target_chat_id = None
            target_msg_id = None
            
            if match_private:
                target_chat_id = int("-100" + match_private.group(1))
                target_msg_id = int(match_private.group(2))
            elif match_public:
                target_chat_id = match_public.group(1)
                if not target_chat_id.startswith("@"): target_chat_id = "@" + target_chat_id
                target_msg_id = int(match_public.group(2))
            
            if not target_chat_id or not target_msg_id:
                state["err_count"] = state.get("err_count", 0) + 1
                if state["err_count"] >= 2:
                    del CREATOR_STATE[user_id]
                    kb = get_aa_bottom_raw()
                    await send_modded_reply_keyboard(client, chat_id, "❌ Cancelled due to multiple invalid attempts.", kb)
                    return
                return await message.reply("❌ Invalid link format. Please try again.")
                
            try:
                msg_obj = await client.get_messages(target_chat_id, target_msg_id)
                if not msg_obj or msg_obj.empty:
                    state["err_count"] = state.get("err_count", 0) + 1
                    if state["err_count"] >= 2:
                        del CREATOR_STATE[user_id]
                        kb = get_aa_bottom_raw()
                        await send_modded_reply_keyboard(client, chat_id, "❌ Cancelled due to multiple invalid attempts.", kb)
                        return
                    return await message.reply("❌ Could not find the post. Make sure I am an admin in the channel.")
                
                chat_title = msg_obj.chat.title if msg_obj.chat else str(target_chat_id)
                
                CREATOR_STATE[user_id].update({
                    "target_chat_id": target_chat_id,
                    "target_msg_id": target_msg_id,
                    "target_link": link,
                    "chat_title": chat_title,
                    "layout": [],
                    "step": "BUILDING"
                })
                
                msg_text = f"✅ **Post found!**\n\n📢 Channel: {chat_title}\n📝 Message ID: {target_msg_id}\n\nClick ➕ to add buttons:"
                await message.reply(msg_text)
                
                kb = get_builder_bottom_raw()
                await send_modded_reply_keyboard(client, chat_id, "👇 Tap ➕ on the message below to add a button.", kb)
                
                new_msg_id = await raw_copy_and_send_preview(chat_id, target_chat_id, target_msg_id, [])
                if not new_msg_id:
                    return await message.reply("❌ Failed to copy preview. Ensure bot has permissions in that channel.")
                CREATOR_STATE[user_id]["preview_msg_id"] = new_msg_id
            except Exception as e:
                state["err_count"] = state.get("err_count", 0) + 1
                if state["err_count"] >= 2:
                    del CREATOR_STATE[user_id]
                    kb = get_aa_bottom_raw()
                    await send_modded_reply_keyboard(client, chat_id, "❌ Cancelled due to multiple invalid attempts.", kb)
                    return
                return await message.reply(f"❌ Error accessing post: Make sure I am an admin in that channel.")
            return

        elif state["step"] == "WAIT_BTN_NAME":
            temp_name = message.text
            emoji_id = None
            if message.entities:
                for ent in sorted(message.entities, key=lambda x: x.offset, reverse=True):
                    if ent.type == enums.MessageEntityType.CUSTOM_EMOJI:
                        emoji_id = str(ent.custom_emoji_id)
                        temp_name = temp_name[:ent.offset] + temp_name[ent.offset + ent.length:]
                        break 
                        
            CREATOR_STATE[user_id]["temp_btn_name"] = temp_name.strip()
            CREATOR_STATE[user_id]["temp_emoji_id"] = emoji_id
            CREATOR_STATE[user_id]["step"] = "WAIT_URL"
            await message.reply(f"✅ Button text: {temp_name.strip()}\n\n🔗 *Now send the button URL:*", parse_mode=ParseMode.MARKDOWN)
            return
            
        elif state["step"] == "WAIT_URL":
            CREATOR_STATE[user_id]["temp_btn_url"] = message.text.strip()
            CREATOR_STATE[user_id]["step"] = "WAIT_COLOR"
            kb = get_color_bottom_raw()
            await send_modded_reply_keyboard(client, chat_id, "🎨 Please choose button color:", kb)
            return
            
        elif state["step"] == "WAIT_COLOR":
            color_choice = "secondary"
            if "Red" in text: color_choice = "danger"
            elif "Blue" in text: color_choice = "primary"
            elif "Green" in text: color_choice = "success"
            
            btn_data = {
                "text": CREATOR_STATE[user_id]["temp_btn_name"],
                "url": CREATOR_STATE[user_id]["temp_btn_url"],
                "color": color_choice,
                "emoji_id": CREATOR_STATE[user_id].get("temp_emoji_id")
            }
            
            row_idx = CREATOR_STATE[user_id]["target_row"]
            layout = CREATOR_STATE[user_id]["layout"]
            
            if row_idx == "new": layout.append([btn_data])
            else: layout[int(row_idx)].append(btn_data)
                
            CREATOR_STATE[user_id]["layout"] = layout
            CREATOR_STATE[user_id]["step"] = "BUILDING"
            
            try: await client.delete_messages(chat_id, CREATOR_STATE[user_id]["preview_msg_id"])
            except: pass
            
            kb = get_builder_bottom_raw()
            await send_modded_reply_keyboard(client, chat_id, f"✅ Button added: {CREATOR_STATE[user_id]['temp_btn_name']} (Color: {color_choice.capitalize()})", kb)
            
            src_chat = CREATOR_STATE[user_id]["target_chat_id"] if CREATOR_STATE[user_id].get("mode") not in ["auto_project", "autodm"] else chat_id
            src_msg = CREATOR_STATE[user_id]["target_msg_id"]
            
            new_msg_id = await raw_copy_and_send_preview(chat_id, src_chat, src_msg, layout)
            CREATOR_STATE[user_id]["preview_msg_id"] = new_msg_id
            return


# ==========================================
# 🛠 INLINE BUTTON CALLBACKS
# ==========================================
@app.on_callback_query(filters.regex(r"^addbtn_(.*)"))
async def cb_add_button(client, cb):
    uid = cb.from_user.id
    if uid not in CREATOR_STATE:
        return await cb.answer("Session expired. Please start over.", show_alert=True)
        
    target_row = cb.matches[0].group(1) if hasattr(cb, 'matches') and cb.matches else cb.data.split("_")[1]
    CREATOR_STATE[uid]["target_row"] = target_row
    CREATOR_STATE[uid]["step"] = "WAIT_BTN_NAME"
    
    await cb.message.reply("📤 Please send the **text** for your button 👇")
    await cb.answer()

@app.on_callback_query(filters.regex(r"^viewauto_(.*)"))
async def cb_view_auto(client, cb):
    uid = cb.from_user.id
    chat_id = cb.data.split("_")[1]
    
    conn = sqlite3.connect(DB_NAME)
    c = conn.cursor()
    c.execute("SELECT chat_title, keyboard_data FROM auto_projects_v2 WHERE user_id = ? AND chat_id = ?", (uid, chat_id))
    res = c.fetchone()
    conn.close()
    
    if not res: return await cb.answer("Auto Project expired or deleted.", show_alert=True)
    ch_title, layout = res[0], json.loads(res[1])
    
    demo_msg = await client.send_message(cb.message.chat.id, f"📝 **PREVIEW: {ch_title}**\n\nThese buttons are automatically added to all new posts.")
    preview_msg_id = await raw_copy_and_send_preview(cb.message.chat.id, cb.message.chat.id, demo_msg.id, layout, add_preview_buttons=False)
    
    control_kb = InlineKeyboardMarkup([
        [InlineKeyboardButton("✏️ Edit Auto Project", callback_data=f"editauto_{chat_id}_{preview_msg_id}_{demo_msg.id}")],
        [InlineKeyboardButton("🗑 Delete Auto Project", callback_data=f"delauto_{chat_id}_{preview_msg_id}_{demo_msg.id}")],
        [InlineKeyboardButton("❌ Close Preview", callback_data=f"closeauto_{preview_msg_id}_{demo_msg.id}")]
    ])
    
    await client.send_message(cb.message.chat.id, "⚙️ **Auto Project Control:**", reply_markup=control_kb)
    await cb.answer()

@app.on_callback_query(filters.regex(r"^editauto_(.*)"))
async def cb_edit_auto(client, cb):
    parts = cb.data.split("_")
    chat_id, preview_msg_id, demo_msg_id = parts[1], parts[2], parts[3]
    uid = cb.from_user.id
    
    try: await client.delete_messages(cb.message.chat.id, int(preview_msg_id))
    except: pass
    try: await client.delete_messages(cb.message.chat.id, int(demo_msg_id))
    except: pass
    try: await cb.message.delete()
    except: pass
    
    conn = sqlite3.connect(DB_NAME)
    c = conn.cursor()
    c.execute("SELECT chat_title, keyboard_data FROM auto_projects_v2 WHERE user_id = ? AND chat_id = ?", (uid, chat_id))
    res = c.fetchone()
    conn.close()
    
    if not res: return await cb.answer("Project not found.", show_alert=True)
    ch_title, layout = res[0], json.loads(res[1])
    
    new_demo = await client.send_message(cb.message.chat.id, f"📝 **EDITING DEMO POST ({ch_title})**\n\nAdd new buttons or click ✅ DONE to save changes.")
    
    CREATOR_STATE[uid] = {
        "step": "BUILDING",
        "mode": "auto_project",
        "target_chat_id": int(chat_id),
        "chat_title": ch_title,
        "target_msg_id": new_demo.id,
        "layout": layout
    }
    
    kb = get_builder_bottom_raw()
    await send_modded_reply_keyboard(client, cb.message.chat.id, "👇 Edit your buttons below:", kb)
    
    new_preview_msg_id = await raw_copy_and_send_preview(cb.message.chat.id, cb.message.chat.id, new_demo.id, layout)
    CREATOR_STATE[uid]["preview_msg_id"] = new_preview_msg_id
    await cb.answer()

@app.on_callback_query(filters.regex(r"^delauto_(.*)"))
async def cb_del_auto(client, cb):
    parts = cb.data.split("_")
    chat_id, preview_msg_id, demo_msg_id = parts[1], parts[2], parts[3]
    
    conn = sqlite3.connect(DB_NAME)
    c = conn.cursor()
    c.execute("DELETE FROM auto_projects_v2 WHERE user_id = ? AND chat_id = ?", (cb.from_user.id, chat_id))
    conn.commit()
    conn.close()
    
    try: await client.delete_messages(cb.message.chat.id, int(preview_msg_id))
    except: pass
    try: await client.delete_messages(cb.message.chat.id, int(demo_msg_id))
    except: pass
    try: await cb.message.delete()
    except: pass
    
    await client.send_message(cb.message.chat.id, "✅ **Auto Project Deleted successfully!**")
    await cb.answer()

@app.on_callback_query(filters.regex(r"^closeauto_(.*)"))
async def cb_close_auto(client, cb):
    parts = cb.data.split("_")
    preview_msg_id, demo_msg_id = parts[1], parts[2]
    
    try: await cb.message.delete()
    except: pass
    try: await client.delete_messages(cb.message.chat.id, int(preview_msg_id))
    except: pass
    try: await client.delete_messages(cb.message.chat.id, int(demo_msg_id))
    except: pass
    await cb.answer()

@app.on_callback_query(filters.regex(r"^viewpost_(.*)"))
async def cb_view_post(client, cb):
    pid = cb.data.split("_")[1]
    conn = sqlite3.connect(DB_NAME)
    c = conn.cursor()
    c.execute("SELECT message_id, keyboard_data FROM saved_posts WHERE post_id = ?", (pid,))
    res = c.fetchone()
    conn.close()
    
    if not res: return await cb.answer("Post expired or deleted.", show_alert=True)
    msg_id, layout = res[0], json.loads(res[1])
    
    preview_msg_id = await raw_copy_and_send_preview(cb.message.chat.id, cb.message.chat.id, msg_id, layout, add_preview_buttons=False)
    
    control_kb = InlineKeyboardMarkup([
        [InlineKeyboardButton("🚀 Ready For Upload", callback_data=f"publist_{pid}_{preview_msg_id}")],
        [InlineKeyboardButton("❌ Cancel", callback_data=f"delprev_{preview_msg_id}")]
    ])
    
    await client.send_message(cb.message.chat.id, "⚙️ **Post Control Menu:**\nSelect an option below to manage this post.", reply_markup=control_kb)
    await cb.answer()

@app.on_callback_query(filters.regex(r"^delprev_(.*)"))
async def cb_del_prev(client, cb):
    preview_msg_id = cb.data.split("_")[1]
    try: await cb.message.delete()
    except: pass
    try: await client.delete_messages(cb.message.chat.id, int(preview_msg_id))
    except: pass

@app.on_callback_query(filters.regex(r"^publist_(.*)"))
async def cb_publish_list(client, cb):
    parts = cb.data.split("_")
    pid, preview_msg_id = parts[1], parts[2]
    uid = cb.from_user.id
    
    channels = get_user_channels(uid)
    if not channels:
        return await cb.answer("⚠️ You haven't added me as Admin in any channels yet!", show_alert=True)
        
    kb = []
    for ch_id, ch_title in channels:
        kb.append([InlineKeyboardButton(f"📢 {ch_title}", callback_data=f"pub_{pid}_{ch_id}_{preview_msg_id}")])
    kb.append([InlineKeyboardButton("🔙 Back", callback_data=f"delprev_{preview_msg_id}")])
    
    await cb.message.edit_text("Select a channel to publish to:", reply_markup=InlineKeyboardMarkup(kb))

@app.on_callback_query(filters.regex(r"^pub_(.*)"))
async def cb_publish_execute(client, cb):
    parts = cb.data.split("_")
    pid, ch_id, preview_msg_id = parts[1], parts[2], parts[3]
    
    conn = sqlite3.connect(DB_NAME)
    c = conn.cursor()
    c.execute("SELECT message_id, keyboard_data FROM saved_posts WHERE post_id = ?", (pid,))
    res = c.fetchone()
    conn.close()
    
    if not res: return await cb.answer("Post expired.", show_alert=True)
    msg_id, layout = res[0], json.loads(res[1])
    
    sent_id = await raw_copy_and_send_preview(int(ch_id), cb.message.chat.id, msg_id, layout, add_preview_buttons=False)
    
    if sent_id:
        await cb.answer("✅ Successfully Published to Channel!", show_alert=True)
        try: await cb.message.delete()
        except: pass
        try: await client.delete_messages(cb.message.chat.id, int(preview_msg_id))
        except: pass
    else:
        await cb.answer("❌ Failed: Ensure I have admin rights in that channel.", show_alert=True)

async def main():
    await app.start()
    try: await app.set_bot_commands([BotCommand("start", "Start Bot")])
    except: pass
    print("🔥 Advanced Button Builder Bot Started Successfully!")
    await idle()
    await app.stop()

if __name__ == "__main__":
    app.run(main())