import os
import json
import time
import random
import requests
import tkinter as tk
from tkinter import ttk, messagebox, filedialog
from ttkthemes import ThemedTk
import threading
from datetime import datetime, timedelta, UTC
import webbrowser
from http.server import BaseHTTPRequestHandler, HTTPServer
import urllib.parse
import pickle
from collections import defaultdict, Counter
import numpy as np
from sklearn.ensemble import RandomForestClassifier, RandomForestRegressor
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler
from PIL import Image

CREDENTIALS_FILE = "credentials.json"
CAPTION_MEMORY_FILE = "caption_storage.json"
ML_MODEL_FILE = "user_behavior_model.pkl"
USER_BEHAVIOR_DATA = "user_behavior.json"

# Cloudflare Worker Configuration - Update with your values
FACEBOOK_APP_ID = "715373697489068"
FACEBOOK_APP_SECRET = "c8707919b92f3b8f1836faa60ad4a33e"
CLOUDFLARE_REDIRECT_URI = "https://fb-page-poster.tricksduniyaemail.workers.dev/auth/facebook/callback"
LOCAL_SERVER_PORT = 8080

try:
    from moviepy.editor import VideoFileClip
except ImportError:
    VideoFileClip = None

# Local OAuth callback server
class LocalOAuthHandler(BaseHTTPRequestHandler):
    def do_POST(self):
        if self.path == '/oauth/callback':
            content_length = int(self.headers['Content-Length'])
            post_data = self.rfile.read(content_length)
            try:
                data = json.loads(post_data.decode('utf-8'))
                self.server.auth_code = data.get('code')
                self.server.auth_state = data.get('state')
                self.send_response(200)
                self.send_header('Content-Type', 'application/json')
                self.send_header('Access-Control-Allow-Origin', '*')
                self.end_headers()
                self.wfile.write(json.dumps({"status": "success"}).encode())
            except Exception as e:
                self.send_response(400)
                self.send_header('Content-Type', 'application/json')
                self.end_headers()
                self.wfile.write(json.dumps({"error": str(e)}).encode())
        else:
            self.send_response(404)
            self.end_headers()
    
    def do_OPTIONS(self):
        self.send_response(200)
        self.send_header('Access-Control-Allow-Origin', '*')
        self.send_header('Access-Control-Allow-Methods', 'POST, OPTIONS')
        self.send_header('Access-Control-Allow-Headers', 'Content-Type')
        self.end_headers()
    
    def log_message(self, format, *args):
        pass  # Silence logs

def run_local_oauth_server():
    try:
        server_address = ('localhost', LOCAL_SERVER_PORT)
        httpd = HTTPServer(server_address, LocalOAuthHandler)
        httpd.auth_code = None
        httpd.auth_state = None
        timeout_count = 0
        while httpd.auth_code is None and timeout_count < 120:
            httpd.handle_request()
            timeout_count += 1
            time.sleep(1)
        if httpd.auth_code:
            return httpd.auth_code
        return None
    except Exception:
        return None

def exchange_code_for_token(code):
    url = "https://graph.facebook.com/v18.0/oauth/access_token"
    params = {
        'client_id': FACEBOOK_APP_ID,
        'client_secret': FACEBOOK_APP_SECRET,
        'redirect_uri': CLOUDFLARE_REDIRECT_URI,
        'code': code
    }
    response = requests.post(url, params=params)
    data = response.json()
    if 'access_token' in data:
        return data['access_token']
    raise Exception(f"Failed to get access token: {data}")

def get_long_lived_token(short_lived_token):
    url = "https://graph.facebook.com/v18.0/oauth/access_token"
    params = {
        'grant_type': 'fb_exchange_token',
        'client_id': FACEBOOK_APP_ID,
        'client_secret': FACEBOOK_APP_SECRET,
        'fb_exchange_token': short_lived_token
    }
    response = requests.get(url, params=params)
    data = response.json()
    if 'access_token' in data:
        return data['access_token']
    return short_lived_token

def get_user_business_pages(token):
    try:
        if not token:
            raise Exception("No access token available")
        all_pages = []
        url = 'https://graph.facebook.com/v18.0/me/accounts'
        params = {
            'access_token': token,
            'fields': 'id,name,category,access_token,tasks',
            'limit': 100
        }
        while url:
            response = requests.get(url, params=params)
            if response.status_code == 200:
                data = response.json()
                pages = data.get('data', [])
                for page in pages:
                    if 'tasks' in page and 'MANAGE' in page.get('tasks', []):
                        page['can_post'] = True
                    else:
                        page['can_post'] = False
                    all_pages.append(page)
                paging = data.get('paging', {})
                url = paging.get('next')
                params = {}
                if url:
                    time.sleep(1)
            else:
                break
        return all_pages
    except Exception as e:
        raise Exception(f"Failed to get business pages: {str(e)}")

def get_user_info(token):
    try:
        url = f"https://graph.facebook.com/v18.0/me"
        params = {
            'access_token': token,
            'fields': 'id,name,email'
        }
        response = requests.get(url, params=params)
        return response.json()
    except:
        return {'id': 'me', 'name': 'Facebook User'}

def load_credentials():
    if not os.path.exists(CREDENTIALS_FILE):
        return []
    with open(CREDENTIALS_FILE, "r") as f:
        return json.load(f)

def save_credentials(creds):
    with open(CREDENTIALS_FILE, "w") as f:
        json.dump(creds, f, indent=2)

def load_captions():
    if not os.path.exists(CAPTION_MEMORY_FILE):
        return {}
    with open(CAPTION_MEMORY_FILE, "r") as f:
        return json.load(f)

def save_captions(captions):
    with open(CAPTION_MEMORY_FILE, "w") as f:
        json.dump(captions, f, indent=2)

def add_caption(media_type, caption):
    captions = load_captions()
    if media_type not in captions:
        captions[media_type] = []
    cap = caption.strip()
    if cap and cap not in captions[media_type]:
        captions[media_type].insert(0, cap)
        captions[media_type] = captions[media_type][:10]
        save_captions(captions)

def validate_reel_format(path):
    if not VideoFileClip:
        return True, None
    try:
        clip = VideoFileClip(path)
        w, h = clip.size
        dur = clip.duration
        if h < w:
            return False, "Reel must be vertical video (portrait)."
        if dur > 90:
            return False, "Reel length must be under 90 seconds."
        ext = os.path.splitext(path)[1].lower()
        if ext not in [".mp4", ".mov"]:
            return False, "Reel file must be .mp4 or .mov."
        return True, None
    except Exception:
        return True, None

def validate_video_format(path):
    if not VideoFileClip:
        return True, None
    try:
        clip = VideoFileClip(path)
        ext = os.path.splitext(path)[1].lower()
        if ext not in [".mp4", ".mov", ".avi"]:
            return False, "Video file must be standard format (.mp4, .mov, .avi)."
        return True, None
    except Exception:
        return True, None

def is_devotional(path):
    try:
        img = Image.open(path)
        img = img.resize((100,100))
        pixels = np.array(img.convert('RGB'))
        r_mean = np.mean(pixels[:,:,0])
        g_mean = np.mean(pixels[:,:,1])
        b_mean = np.mean(pixels[:,:,2])
        if r_mean > 140 and g_mean > 100 and b_mean < 150:
            return True
        return False
    except:
        return False

phases = {
    'Morning': (6, 12),
    'Afternoon': (12, 16),
    'Evening': (16, 20),
    'Night': (20, 24),
    'Late Night': (0, 6)
}

def get_random_time_in_phase(phase, base_date):
    start, end = phases[phase]
    hour = random.randint(start, end - 1)
    minute = random.randint(0, 59)
    dt = base_date.replace(hour=hour, minute=minute)
    if phase == 'Late Night':
        if dt.hour >= 6:
            dt += timedelta(days=1)
    return dt

class MLBehaviorPredictor:
    def __init__(self):
        self.model = RandomForestClassifier(n_estimators=100, random_state=42)
        self.ppd_model = RandomForestRegressor(n_estimators=100, random_state=42)
        self.scaler = StandardScaler()
        self.is_trained = False
        self.behavior_data = self.load_behavior_data()
        
    def load_behavior_data(self):
        if os.path.exists(USER_BEHAVIOR_DATA):
            with open(USER_BEHAVIOR_DATA, 'r') as f:
                return json.load(f)
        return {
            'schedule_patterns': [],
            'media_preferences': [],
            'posting_frequency': [],
            'time_preferences': [],
            'phase_preferences': []
        }
    
    def save_behavior_data(self):
        with open(USER_BEHAVIOR_DATA, 'w') as f:
            json.dump(self.behavior_data, f, indent=2)
    
    def get_phase(self, hour):
        if 0 <= hour < 6:
            return 'Late Night'
        elif 6 <= hour < 12:
            return 'Morning'
        elif 12 <= hour < 16:
            return 'Afternoon'
        elif 16 <= hour < 20:
            return 'Evening'
        else:
            return 'Night'
    
    def record_user_action(self, action_type, data):
        timestamp = datetime.now()
        hour = timestamp.hour
        weekday = timestamp.weekday()
        if action_type == 'schedule':
            self.behavior_data['schedule_patterns'].append({
                'hour': hour,
                'weekday': weekday,
                'interval_minutes': data.get('interval', 30),
                'media_count': data.get('media_count', 1),
                'phase': self.get_phase(hour),
                'posts_per_day': data.get('posts_per_day', 1)
            })
        elif action_type == 'media_preference':
            self.behavior_data['media_preferences'].append({
                'media_type': data.get('media_type', 'photo'),
                'hour': hour,
                'weekday': weekday
            })
        elif action_type == 'posting':
            self.behavior_data['posting_frequency'].append({
                'pages_count': data.get('pages_count', 1),
                'hour': hour,
                'weekday': weekday
            })
        self.save_behavior_data()
        self.train_model()
    
    def train_model(self):
        try:
            if len(self.behavior_data['schedule_patterns']) < 5:
                return
            features = []
            targets_interval = []
            targets_ppd = []
            for pattern in self.behavior_data['schedule_patterns']:
                features.append([
                    pattern['hour'],
                    pattern['weekday'],
                    pattern.get('media_count', 1)
                ])
                targets_interval.append(pattern.get('interval_minutes', 30))
                targets_ppd.append(pattern.get('posts_per_day', 1))
            if len(features) > 0:
                X = np.array(features)
                if len(X) > 1:
                    X_scaled = self.scaler.fit_transform(X)
                    self.model.fit(X_scaled, np.array(targets_interval))
                    self.ppd_model.fit(X_scaled, np.array(targets_ppd))
                    self.is_trained = True
                    with open(ML_MODEL_FILE, 'wb') as f:
                        pickle.dump({
                            'model': self.model,
                            'ppd_model': self.ppd_model,
                            'scaler': self.scaler
                        }, f)
        except Exception:
            pass
    
    def predict_schedule_interval(self, current_time, media_count):
        if not self.is_trained:
            return 30
        try:
            features = [[
                current_time.hour,
                current_time.weekday(),
                media_count
            ]]
            X_scaled = self.scaler.transform(features)
            prediction = self.model.predict(X_scaled)
            return max(5, min(180, int(prediction[0])))
        except Exception:
            return 30
    
    def predict_posts_per_day(self, media_count, weekday, hour=datetime.now().hour):
        if not self.is_trained:
            return 1.0
        try:
            features = [[hour, weekday, media_count]]
            X_scaled = self.scaler.transform(features)
            return max(1.0, self.ppd_model.predict(X_scaled)[0])
        except:
            return 1.0
    
    def get_recommended_times(self, media_count):
        if not self.behavior_data['schedule_patterns']:
            return [
                datetime.now() + timedelta(minutes=30),
                datetime.now() + timedelta(hours=2),
                datetime.now() + timedelta(hours=6)
            ]
        hours = [p['hour'] for p in self.behavior_data['schedule_patterns']]
        common_hours = Counter(hours).most_common(3)
        recommendations = []
        base_time = datetime.now()
        for hour, _ in common_hours:
            next_time = base_time.replace(hour=hour, minute=0, second=0)
            if next_time <= base_time:
                next_time += timedelta(days=1)
            recommendations.append(next_time)
        return recommendations[:3]
    
    def get_recommended_phases(self):
        phases = [p.get('phase', self.get_phase(p['hour'])) for p in self.behavior_data['schedule_patterns']]
        if not phases:
            return ['Morning', 'Afternoon', 'Evening']
        counter = Counter(phases)
        return [p for p, _ in counter.most_common(3)]

class BulkSchedulePopup(tk.Toplevel):
    def __init__(self, master, ml_predictor, on_save):
        self.master = master
        self.ml_predictor = ml_predictor
        self.on_save = on_save
        super().__init__(master)
        
        self.configure(bg="#1e3a8a")
        self.geometry("1000x750+150+50")
        self.title("Bulk Schedule Manager")
        self.resizable(True, True)
        self.transient(master)
        self.grab_set()
        
        self.media_files = []
        self.schedule_entries = []
        self.schedule_mode = tk.StringVar(value="Daily for Month")
        self.phase_vars = {}
        
        self.build_ui()
    
    def build_ui(self):
        style = ttk.Style()
        style.configure("TButton", font=("Segoe UI", 11))
        style.configure("Accent.TButton", font=("Segoe UI", 11, "bold"), 
                       foreground="white", background="#2563eb")
        
        # Main container with scrollable area
        main_container = tk.Frame(self, bg="#1e3a8a")
        main_container.pack(fill="both", expand=True, padx=15, pady=15)
        
        # Canvas for scrolling
        canvas = tk.Canvas(main_container, bg="white", highlightthickness=0)
        scrollbar = ttk.Scrollbar(main_container, orient="vertical", command=canvas.yview)
        self.scrollable_frame = tk.Frame(canvas, bg="white")
        
        self.scrollable_frame.bind(
            "<Configure>",
            lambda e: canvas.configure(scrollregion=canvas.bbox("all"))
        )
        
        canvas.create_window((0, 0), window=self.scrollable_frame, anchor="nw")
        canvas.configure(yscrollcommand=scrollbar.set)
        
        canvas.pack(side="left", fill="both", expand=True)
        scrollbar.pack(side="right", fill="y")
        
        # Main content frame
        main = tk.Frame(self.scrollable_frame, bg="white", bd=2, relief=tk.RIDGE)
        main.pack(fill="both", expand=True, padx=10, pady=10)
        
        # Title
        title_frame = tk.Frame(main, bg="#2563eb", height=60)
        title_frame.pack(fill="x", pady=(0, 15))
        title_frame.pack_propagate(False)
        
        tk.Label(title_frame, text="📅 Bulk Schedule Manager", 
                font=("Segoe UI", 18, "bold"), bg="#2563eb", fg="white").pack(pady=15)
        
        # Close button
        close_btn = tk.Button(self, text="✕", font=("Segoe UI", 12), 
                             bg="#dc2626", fg="white", relief="flat", 
                             command=self.close_popup, width=3)
        close_btn.place(x=950, y=10)
        
        # File selection frame
        file_frame = tk.LabelFrame(main, text="Media Selection", font=("Segoe UI", 14, "bold"), 
                                  bg="white", fg="#1e3a8a", padx=10, pady=10, labelanchor='n', relief=tk.GROOVE)
        file_frame.pack(fill="x", pady=(0, 15))
        
        btn_frame = tk.Frame(file_frame, bg="white")
        btn_frame.pack(fill="x", pady=5)
        
        ttk.Button(btn_frame, text="📁 Choose Files", 
                  command=self.choose_files, style="Accent.TButton").pack(side="left", padx=(0, 10))
        
        ttk.Button(btn_frame, text="📂 Choose Folder", 
                  command=self.choose_folder, style="Accent.TButton").pack(side="left", padx=(0, 10))
        
        ttk.Button(btn_frame, text="🤖 Auto Schedule", 
                  command=self.auto_schedule, style="Accent.TButton").pack(side="left", padx=(0, 10))
        
        self.files_label = tk.Label(file_frame, text="No files selected", 
                                   bg="white", fg="#4b5563", font=("Segoe UI", 11))
        self.files_label.pack(anchor="w", pady=5)
        
        # Schedule settings frame
        settings_frame = tk.LabelFrame(main, text="Schedule Settings", 
                                      font=("Segoe UI", 14, "bold"), 
                                      bg="white", fg="#1e3a8a", padx=10, pady=10, labelanchor='n', relief=tk.GROOVE)
        settings_frame.pack(fill="x", pady=(0, 15))
        
        # Schedule mode
        mode_frame = tk.Frame(settings_frame, bg="white")
        mode_frame.pack(fill="x", pady=5)
        tk.Label(mode_frame, text="Schedule Mode:", bg="white", font=("Segoe UI", 12, "bold")).pack(side="left")
        combo = ttk.Combobox(mode_frame, textvariable=self.schedule_mode, 
                             values=["Interval", "Daily for Month", "Phase Random"], state="readonly", width=20)
        combo.pack(side="left", padx=10)
        combo.bind("<<ComboboxSelected>>", self.update_schedule_controls)
        
        # Mode specific controls
        self.mode_controls_frame = tk.Frame(settings_frame, bg="white")
        self.mode_controls_frame.pack(fill="x", pady=5)
        
        # Schedule list frame
        list_frame = tk.LabelFrame(main, text="Schedule Preview", 
                                  font=("Segoe UI", 14, "bold"), 
                                  bg="white", fg="#1e3a8a", padx=5, pady=5, labelanchor='n', relief=tk.GROOVE)
        list_frame.pack(fill="both", expand=True, pady=(0, 15))
        
        # Scrollable frame for schedule entries
        entry_canvas = tk.Canvas(list_frame, bg="white", highlightthickness=0)
        entry_scrollbar = ttk.Scrollbar(list_frame, orient="vertical", command=entry_canvas.yview)
        self.entry_frame = tk.Frame(entry_canvas, bg="white")
        
        self.entry_frame.bind(
            "<Configure>", 
            lambda e: entry_canvas.configure(scrollregion=entry_canvas.bbox("all"))
        )
        
        entry_canvas.create_window((0, 0), window=self.entry_frame, anchor="nw")
        entry_canvas.configure(yscrollcommand=entry_scrollbar.set)
        
        entry_canvas.pack(side="left", fill="both", expand=True)
        entry_scrollbar.pack(side="right", fill="y")
        
        # Control buttons frame
        control_frame = tk.Frame(main, bg="white")
        control_frame.pack(side="bottom", fill="x", pady=10)
        
        ttk.Button(control_frame, text="🚀 Save & Schedule", 
                  command=self.save_schedule, 
                  style="Accent.TButton").pack(side="right", padx=(10, 20))
        
        ttk.Button(control_frame, text="❌ Cancel", 
                  command=self.close_popup, style="TButton").pack(side="right", padx=(0, 10))
        
        # Initial UI update
        self.update_schedule_controls()
    
    def update_schedule_controls(self, event=None):
        for widget in self.mode_controls_frame.winfo_children():
            widget.destroy()
        
        mode = self.schedule_mode.get()
        now = datetime.now()
        
        if mode == "Interval":
            interval_frame = tk.Frame(self.mode_controls_frame, bg="white")
            interval_frame.pack(fill="x", pady=5)
            tk.Label(interval_frame, text="Schedule Interval:", bg="white", font=("Segoe UI", 12)).pack(side="left")
            self.interval_var = tk.IntVar(value=30)
            tk.Spinbox(interval_frame, from_=5, to=180, width=8, textvariable=self.interval_var, font=("Segoe UI", 11)).pack(side="left", padx=(10, 5))
            tk.Label(interval_frame, text="minutes", bg="white", font=("Segoe UI", 12)).pack(side="left")
            
            start_frame = tk.Frame(self.mode_controls_frame, bg="white")
            start_frame.pack(fill="x", pady=5)
            tk.Label(start_frame, text="Start Time:", bg="white", font=("Segoe UI", 12)).pack(side="left")
            self.start_year = tk.IntVar(value=now.year)
            self.start_month = tk.IntVar(value=now.month)
            self.start_day = tk.IntVar(value=now.day)
            self.start_hour = tk.IntVar(value=now.hour)
            self.start_minute = tk.IntVar(value=now.minute)
            tk.Spinbox(start_frame, from_=now.year, to=now.year+2, width=5, textvariable=self.start_year).pack(side="left", padx=2)
            tk.Spinbox(start_frame, from_=1, to=12, width=3, textvariable=self.start_month).pack(side="left", padx=2)
            tk.Spinbox(start_frame, from_=1, to=31, width=3, textvariable=self.start_day).pack(side="left", padx=2)
            tk.Spinbox(start_frame, from_=0, to=23, width=3, textvariable=self.start_hour, format="%02.0f").pack(side="left", padx=2)
            tk.Spinbox(start_frame, from_=0, to=59, width=3, textvariable=self.start_minute, format="%02.0f").pack(side="left", padx=2)
            self.create_schedule_entries()
        
        elif mode == "Daily for Month":
            start_frame = tk.Frame(self.mode_controls_frame, bg="white")
            start_frame.pack(fill="x", pady=5)
            tk.Label(start_frame, text="Start Date:", bg="white", font=("Segoe UI", 12)).pack(side="left")
            self.start_year = tk.IntVar(value=now.year)
            self.start_month = tk.IntVar(value=now.month)
            self.start_day = tk.IntVar(value=now.day + 1)
            tk.Spinbox(start_frame, from_=now.year, to=now.year+2, width=5, textvariable=self.start_year).pack(side="left", padx=2)
            tk.Spinbox(start_frame, from_=1, to=12, width=3, textvariable=self.start_month).pack(side="left", padx=2)
            tk.Spinbox(start_frame, from_=1, to=31, width=3, textvariable=self.start_day).pack(side="left", padx=2)
            
            time_frame = tk.Frame(self.mode_controls_frame, bg="white")
            time_frame.pack(fill="x", pady=5)
            tk.Label(time_frame, text="Fixed Time:", bg="white", font=("Segoe UI", 12)).pack(side="left")
            self.start_hour = tk.IntVar(value=16)
            self.start_minute = tk.IntVar(value=30)
            tk.Spinbox(time_frame, from_=0, to=23, width=3, textvariable=self.start_hour, format="%02.0f").pack(side="left", padx=2)
            tk.Spinbox(time_frame, from_=0, to=59, width=3, textvariable=self.start_minute, format="%02.0f").pack(side="left", padx=2)
            
            days_frame = tk.Frame(self.mode_controls_frame, bg="white")
            days_frame.pack(fill="x", pady=5)
            tk.Label(days_frame, text="Number of Days:", bg="white", font=("Segoe UI", 12)).pack(side="left")
            self.num_days_var = tk.IntVar(value=30)
            tk.Spinbox(days_frame, from_=1, to=90, width=5, textvariable=self.num_days_var).pack(side="left", padx=10)
            self.create_schedule_entries()
        
        elif mode == "Phase Random":
            start_frame = tk.Frame(self.mode_controls_frame, bg="white")
            start_frame.pack(fill="x", pady=5)
            tk.Label(start_frame, text="Start Date:", bg="white", font=("Segoe UI", 12)).pack(side="left")
            self.start_year = tk.IntVar(value=now.year)
            self.start_month = tk.IntVar(value=now.month)
            self.start_day = tk.IntVar(value=now.day + 1)
            tk.Spinbox(start_frame, from_=now.year, to=now.year+2, width=5, textvariable=self.start_year).pack(side="left", padx=2)
            tk.Spinbox(start_frame, from_=1, to=12, width=3, textvariable=self.start_month).pack(side="left", padx=2)
            tk.Spinbox(start_frame, from_=1, to=31, width=3, textvariable=self.start_day).pack(side="left", padx=2)
            
            phase_frame = tk.Frame(self.mode_controls_frame, bg="white")
            phase_frame.pack(fill="x", pady=5)
            tk.Label(phase_frame, text="Select Phases:", bg="white", font=("Segoe UI", 12)).pack(anchor="w")
            self.phase_vars = {}
            for phase, (start, end) in phases.items():
                var = tk.BooleanVar(value=True)
                tk.Checkbutton(phase_frame, text=f"{phase} ({start:02d}:00 - {end:02d}:00)", variable=var, bg="white").pack(anchor="w")
                self.phase_vars[phase] = var
            
            days_frame = tk.Frame(self.mode_controls_frame, bg="white")
            days_frame.pack(fill="x", pady=5)
            tk.Label(days_frame, text="Number of Days:", bg="white", font=("Segoe UI", 12)).pack(side="left")
            self.num_days_var = tk.IntVar(value=30)
            tk.Spinbox(days_frame, from_=1, to=90, width=5, textvariable=self.num_days_var).pack(side="left", padx=10)
            self.create_schedule_entries()
    
    def choose_files(self):
        files = filedialog.askopenfilenames(
            title="Select Media Files",
            filetypes=[
                ("All Media", "*.jpg *.jpeg *.png *.gif *.bmp *.mp4 *.mov *.avi"),
                ("Images", "*.jpg *.jpeg *.png *.gif *.bmp"),
                ("Videos", "*.mp4 *.mov *.avi")
            ]
        )
        if files:
            self.media_files = list(files)
            self.update_files_display()
            self.create_schedule_entries()
        else:
            self.files_label.config(text="No files selected", fg="#4b5563")
    
    def choose_folder(self):
        folder = filedialog.askdirectory(title="Select Media Folder")
        if not folder:
            return
        media_extensions = ['.jpg', '.jpeg', '.png', '.gif', '.bmp', 
                           '.mp4', '.mov', '.avi']
        files = []
        for file in os.listdir(folder):
            if os.path.splitext(file)[1].lower() in media_extensions:
                files.append(os.path.join(folder, file))
        if files:
            self.media_files = files
            self.update_files_display()
            self.create_schedule_entries()
        else:
            messagebox.showinfo("No Media Found", "No valid media files found in the selected folder.")
            self.files_label.config(text="No files selected", fg="#4b5563")
    
    def update_files_display(self):
        count = len(self.media_files)
        if count > 0:
            display_files = [os.path.basename(f) for f in self.media_files[:3]]
            if count > 3:
                display_text = f"{count} files: {', '.join(display_files)}..."
            else:
                display_text = f"{count} files: {', '.join(display_files)}"
            self.files_label.config(text=display_text, fg="#15803d")
        else:
            self.files_label.config(text="No files selected", fg="#4b5563")
    
    def auto_schedule(self):
        if not self.media_files:
            messagebox.showwarning("No Files", "Please select media files first.")
            return
        current_time = datetime.now()
        weekday = current_time.weekday()
        media_count = len(self.media_files)
        predicted_interval = self.ml_predictor.predict_schedule_interval(current_time, media_count)
        predicted_ppd = self.ml_predictor.predict_posts_per_day(media_count, weekday)
        suggested_num_days = max(1, int(media_count / predicted_ppd)) if predicted_ppd > 0 else 30
        suggested_phases = self.ml_predictor.get_recommended_phases()
        devotional_files = sum(is_devotional(f) for f in self.media_files)
        dev_ratio = devotional_files / media_count if media_count > 0 else 0
        if dev_ratio > 0.5:
            suggested_phases = ['Morning', 'Evening', 'Night']
            self.schedule_mode.set("Phase Random")
            if predicted_ppd < 3:
                predicted_ppd = random.uniform(3, 4)
            suggested_num_days = max(1, int(media_count / predicted_ppd))
            msg = "Detected devotional content. Suggesting Morning, Evening, Night phases with ~3-4 posts per day."
        else:
            self.schedule_mode.set("Daily for Month")
            msg = "Suggesting daily schedule."
        self.update_schedule_controls()
        mode = self.schedule_mode.get()
        if mode in ["Daily for Month", "Phase Random"]:
            self.num_days_var.set(suggested_num_days)
            self.start_day.set(current_time.day + 1)
            if hasattr(self, 'start_hour'):
                self.start_hour.set(16)
            if hasattr(self, 'start_minute'):
                self.start_minute.set(30)
            if mode == "Phase Random" and hasattr(self, 'phase_vars'):
                for p, v in self.phase_vars.items():
                    v.set(p in suggested_phases)
        elif mode == "Interval":
            self.interval_var.set(predicted_interval)
            self.start_day.set(current_time.day + 1)
            self.start_hour.set(16)
            self.start_minute.set(30)
        self.create_schedule_entries()
        messagebox.showinfo("Auto Schedule", f"🤖 AI suggested based on your behavior!\n{msg}")
    
    def create_schedule_entries(self):
        for widget in self.entry_frame.winfo_children():
            widget.destroy()
        self.schedule_entries = []
        if not self.media_files:
            self.files_label.config(text="No files selected", fg="#4b5563")
            return
        mode = self.schedule_mode.get()
        try:
            start_year = self.start_year.get()
            start_month = self.start_month.get()
            start_day = self.start_day.get()
            start_hour = self.start_hour.get() if hasattr(self, 'start_hour') else 0
            start_minute = self.start_minute.get() if hasattr(self, 'start_minute') else 0
            start_time = datetime(start_year, start_month, start_day, start_hour, start_minute)
        except Exception as e:
            messagebox.showerror("Invalid Start Time", f"Error: {str(e)}")
            return
        media_count = len(self.media_files)
        if mode == "Interval":
            interval = self.interval_var.get() if hasattr(self, 'interval_var') else 30
            for idx, file_path in enumerate(self.media_files):
                schedule_time = start_time + timedelta(minutes=idx * interval)
                self.add_schedule_entry(idx + 1, file_path, schedule_time)
        elif mode == "Daily for Month":
            num_days = self.num_days_var.get()
            current_day = start_time.replace(hour=0, minute=0, second=0, microsecond=0)
            media_idx = 0
            for d in range(num_days):
                if media_idx >= media_count:
                    break
                day_time = current_day + timedelta(days=d)
                schedule_time = day_time.replace(hour=start_hour, minute=start_minute)
                self.add_schedule_entry(media_idx + 1, self.media_files[media_idx], schedule_time)
                media_idx += 1
        elif mode == "Phase Random":
            selected_phases = [p for p, v in self.phase_vars.items() if v.get()]
            if not selected_phases:
                selected_phases = list(phases.keys())
            num_days = self.num_days_var.get()
            current_day = start_time.replace(hour=0, minute=0, second=0, microsecond=0)
            media_idx = 0
            for d in range(num_days):
                if media_idx >= media_count:
                    break
                day_time = current_day + timedelta(days=d)
                phase = random.choice(selected_phases)
                schedule_time = get_random_time_in_phase(phase, day_time)
                self.add_schedule_entry(media_idx + 1, self.media_files[media_idx], schedule_time)
                media_idx += 1
    
    def add_schedule_entry(self, num, file_path, schedule_time):
        entry_frame = tk.Frame(self.entry_frame, bg="#f9fafb", 
                              bd=1, relief="solid", padx=10, pady=8)
        entry_frame.pack(fill="x", pady=2)
        file_info = tk.Frame(entry_frame, bg="#f9fafb")
        file_info.pack(fill="x")
        tk.Label(file_info, text=f"{num}. {os.path.basename(file_path)}", 
                bg="#f9fafb", font=("Segoe UI", 11, "bold"), 
                fg="#1e3a8a", anchor="w").pack(side="left")
        schedule_frame = tk.Frame(entry_frame, bg="#f9fafb")
        schedule_frame.pack(fill="x", pady=5)
        tk.Label(schedule_frame, text="📅 Schedule:", bg="#f9fafb", 
                font=("Segoe UI", 10)).pack(side="left", padx=(0, 5))
        year_var = tk.IntVar(value=schedule_time.year)
        month_var = tk.IntVar(value=schedule_time.month)
        day_var = tk.IntVar(value=schedule_time.day)
        hour_var = tk.IntVar(value=schedule_time.hour)
        minute_var = tk.IntVar(value=schedule_time.minute)
        tk.Spinbox(schedule_frame, from_=datetime.now().year, 
                  to=datetime.now().year+2, width=5, 
                  textvariable=year_var).pack(side="left", padx=1)
        tk.Spinbox(schedule_frame, from_=1, to=12, width=3, 
                  textvariable=month_var).pack(side="left", padx=1)
        tk.Spinbox(schedule_frame, from_=1, to=31, width=3, 
                  textvariable=day_var).pack(side="left", padx=1)
        tk.Spinbox(schedule_frame, from_=0, to=23, width=3, 
                  textvariable=hour_var, format="%02.0f").pack(side="left", padx=1)
        tk.Spinbox(schedule_frame, from_=0, to=59, width=3, 
                  textvariable=minute_var, format="%02.0f").pack(side="left", padx=1)
        caption_frame = tk.Frame(entry_frame, bg="#f9fafb")
        caption_frame.pack(fill="x", pady=2)
        tk.Label(caption_frame, text="💬 Caption:", bg="#f9fafb", 
                font=("Segoe UI", 10)).pack(side="left", padx=(0, 5))
        caption_var = tk.StringVar()
        caption_entry = tk.Entry(caption_frame, textvariable=caption_var, 
                               width=60, font=("Segoe UI", 10))
        caption_entry.pack(side="left", fill="x", expand=True)
        self.schedule_entries.append({
            'file': file_path,
            'year': year_var,
            'month': month_var,
            'day': day_var,
            'hour': hour_var,
            'minute': minute_var,
            'caption': caption_var
        })
    
    def save_schedule(self):
        if not self.schedule_entries:
            messagebox.showwarning("No Schedule", "Please select media files and create a schedule.")
            return
        mode = self.schedule_mode.get()
        interval = 30
        posts_per_day = 1.0
        if mode == "Interval":
            interval = self.interval_var.get() if hasattr(self, 'interval_var') else 30
        elif mode in ["Daily for Month", "Phase Random"]:
            num_days = self.num_days_var.get() if hasattr(self, 'num_days_var') else 30
            posts_per_day = len(self.schedule_entries) / num_days if num_days > 0 else 1.0
        self.ml_predictor.record_user_action('schedule', {
            'interval': interval,
            'media_count': len(self.media_files),
            'posts_per_day': posts_per_day
        })
        schedule_data = []
        for entry in self.schedule_entries:
            try:
                schedule_time = datetime(
                    entry['year'].get(),
                    entry['month'].get(),
                    entry['day'].get(),
                    entry['hour'].get(),
                    entry['minute'].get()
                )
                if schedule_time <= datetime.now():
                    messagebox.showerror("Invalid Time", 
                                       f"Schedule time for {os.path.basename(entry['file'])} must be in the future.")
                    return
                schedule_data.append({
                    'file': entry['file'],
                    'time': schedule_time,
                    'caption': entry['caption'].get()
                })
            except Exception as e:
                messagebox.showerror("Invalid Date", 
                                   f"Invalid date/time for {os.path.basename(entry['file'])}: {str(e)}")
                return
        self.on_save(schedule_data)
        self.close_popup()
    
    def close_popup(self):
        self.grab_release()
        self.destroy()

class AddUserPopup(tk.Toplevel):
    def __init__(self, master, on_save):
        self.on_save = on_save
        super().__init__(master)
        self.configure(bg="#1e3a8a")
        self.geometry("440x325+400+220")
        self.resizable(False, False)
        self.title("Add New User")
        self.transient(master)
        self.grab_set()
        main = tk.Frame(self, bg="white", bd=3, relief=tk.RIDGE)
        main.place(relx=0.5, rely=0.5, anchor="center", relwidth=0.97, relheight=0.97)
        close_btn = tk.Button(
            self, text="✕", font=("Segoe UI", 11), bg="#dc2626",
            fg="white", relief="flat", borderwidth=0,
            command=self._close
        )
        close_btn.place(relx=1, rely=0, anchor="ne", width=36, height=36)
        tk.Label(
            main, text="Add User", font=("Segoe UI", 16, "bold"),
            bg="white", fg="#1e3a8a"
        ).pack(pady=(6,12))
        self.vars = {}
        for label_text in ["Profile Name", "System User Token", "Business Manager ID"]:
            tk.Label(
                main, text=label_text, bg="white", fg="#1e3a8a",
                font=("Segoe UI", 11, "bold")
            ).pack(anchor="w", padx=24, pady=(6,2))
            sv = tk.StringVar()
            ent = tk.Entry(
                main, textvariable=sv, font=("Segoe UI", 13), bd=2,
                width=31, relief="groove", fg="black"
            )
            ent.pack(padx=17, pady=(0,7))
            self.vars[label_text] = sv
        btn_fr = tk.Frame(main, bg="white")
        btn_fr.pack(pady=(10,7))
        save_btn = ttk.Button(
            btn_fr, text="Save", width=13,
            style="Accent.TButton", command=self.save
        )
        save_btn.pack(side="left", padx=8, ipadx=7, ipady=3)
        cancel_btn = ttk.Button(
            btn_fr, text="Cancel", width=11, command=self._close
        )
        cancel_btn.pack(side="left", padx=10, ipady=3)
    
    def _close(self):
        self.grab_release()
        self.destroy()
    
    def save(self):
        pn = self.vars["Profile Name"].get().strip()
        tkn = self.vars["System User Token"].get().strip()
        bmid = self.vars["Business Manager ID"].get().strip()
        if not pn or not tkn or not bmid:
            messagebox.showerror(
                "Required!",
                "All fields must be filled with valid data!",
                parent=self
            )
            return
        self.on_save(pn, tkn, bmid)
        self._close()

class MultiPosterGUI(ThemedTk):
    def __init__(self):
        super().__init__(theme="azure")
        self.title("FB Multi Poster v2.0")
        self.geometry("1000x750")
        self.minsize(800, 550)
        self.configure(bg="#f8fafc")
        self.ml_predictor = MLBehaviorPredictor()
        self.credentials = load_credentials()
        self.selected_user = None
        self.pages = []
        self.media_type = tk.StringVar(value=None)
        self.caption_type = tk.StringVar(value="same")
        self.status_message = tk.StringVar()
        self.progress = tk.IntVar(value=0)
        self.delete_after_var = tk.BooleanVar(value=True)
        self.media_files = []
        self.media_folder = None
        self.schedule_var = tk.BooleanVar(value=False)
        self.bulk_schedule_var = tk.BooleanVar(value=False)
        self.bulk_schedule_data = []
        self.fb_access_token = None
        self.fb_business_pages = []
        self.login_type = "manual"
        now_utc = datetime.now(UTC)
        self.schedule_year_var = tk.IntVar(value=now_utc.year)
        self.schedule_month_var = tk.IntVar(value=now_utc.month)
        self.schedule_day_var = tk.IntVar(value=now_utc.day)
        self.schedule_hour_var = tk.IntVar(value=0)
        self.schedule_minute_var = tk.IntVar(value=0)
        self.build_layout()
        self._reset_filecap_frame()
    
    def build_layout(self):
        style = ttk.Style()
        style.configure("Accent.TButton", font=("Segoe UI", 12, "bold"), 
                       foreground="white", background="#2563eb", borderwidth=0)
        style.configure("TButton", font=("Segoe UI", 12))
        style.configure("TCheckbutton", font=("Segoe UI", 12))
        style.configure("TRadiobutton", font=("Segoe UI", 12))
        style.configure("Vertical.TScrollbar", background="#2563eb")
        
        # Header
        header_frame = tk.Frame(self, bg="#2563eb", height=60)
        header_frame.pack(fill="x")
        header_frame.pack_propagate(False)
        tk.Label(header_frame, text="☁️ Facebook Multi Poster v2.0", 
                font=("Segoe UI", 18, "bold"), fg="white", bg="#2563eb").pack(pady=15)
        
        # Top frame for user selection
        top_fr = tk.Frame(self, bg="#e6effa")
        top_fr.pack(fill="x", padx=15, pady=7)
        tk.Label(top_fr, text="Account:", font=("Segoe UI", 13, "bold"), 
                bg="#e6effa", fg="#1e3a8a").pack(side="left", padx=(10, 8))
        self.userbox = ttk.Combobox(top_fr, state="readonly", width=22, 
                                   font=("Segoe UI", 11), 
                                   values=[c["profile_name"] for c in self.credentials])
        self.userbox.pack(side="left")
        self.userbox.bind("<<ComboboxSelected>>", self.on_user_selected)
        ttk.Button(top_fr, text="+ Add New User", style="Accent.TButton", 
                  command=self.show_add_user).pack(side="left", padx=30)
        self.fb_login_btn = ttk.Button(top_fr, text="☁️ Login with Facebook", 
                                      style="Accent.TButton", 
                                      command=self.facebook_cloudflare_login)
        self.fb_login_btn.pack(side="left", padx=15)
        self.login_status = tk.Label(top_fr, text="Manual Login", 
                                   font=("Segoe UI", 10, "italic"), 
                                   bg="#e6effa", fg="#666")
        self.login_status.pack(side="right", padx=10)
        
        main_fr = tk.Frame(self, bg="#f8fafc")
        main_fr.pack(expand=True, fill="both", padx=20, pady=0)
        
        # Pages card (left side)
        pages_card = tk.Frame(main_fr, bg="#fff", bd=1, relief="solid")
        pages_card.place(relx=0.01, rely=0, relwidth=0.3, relheight=0.96)
        tk.Label(pages_card, text="Select Pages", font=("Segoe UI", 14, "bold"), 
                fg="#1e3a8a", bg="white").pack(pady=(14, 5))
        self.select_all_var = tk.BooleanVar(value=False)
        self.select_all_check = ttk.Checkbutton(pages_card, text="Select All", 
                                               variable=self.select_all_var, 
                                               command=self.select_all_pages)
        self.select_all_check.pack(anchor="w", padx=12, pady=(2, 4))
        page_canvas_frame = tk.Frame(pages_card, bg="white")
        page_canvas_frame.pack(fill="both", expand=True, padx=2, pady=(3, 0))
        self.page_canvas = tk.Canvas(page_canvas_frame, bg="white", 
                                    highlightthickness=0, borderwidth=0)
        self.page_scroll = ttk.Scrollbar(page_canvas_frame, orient="vertical", 
                                        command=self.page_canvas.yview)
        self.page_canvas.configure(yscrollcommand=self.page_scroll.set, height=310)
        self.page_inner = tk.Frame(self.page_canvas, bg="white")
        self.page_canvas.create_window((0, 0), window=self.page_inner, anchor="nw")
        self.page_canvas.pack(side="left", fill="both", expand=True)
        self.page_scroll.pack(side="right", fill="y")
        self.page_inner.bind("<Configure>", 
                           lambda e: self.page_canvas.configure(scrollregion=self.page_canvas.bbox("all")))
        
        # Right side content
        right = tk.Frame(main_fr, bg="#f8fafc")
        right.place(relx=0.33, rely=0, relwidth=0.67, relheight=0.99)
        
        # Post type selection
        ptype_fr = tk.Frame(right, bg="#fff")
        ptype_fr.pack(fill="x", pady=(14, 0))
        tk.Label(ptype_fr, text="What do you want to post?", font=("Segoe UI", 13, "bold"),
                fg="#1e3a8a", bg="white").pack(anchor="w", padx=18)
        for i, (label, ptype) in enumerate([("Text", "text"), ("Photo", "photo"), 
                                           ("Video", "video"), ("Reel", "reel")]):
            b = ttk.Radiobutton(ptype_fr, text=label, variable=self.media_type, 
                               value=ptype, command=self.post_type_changed)
            b.pack(side="left", padx=(14, 0), pady=6)
        
        # File and caption frame
        self.filecap_fr = tk.Frame(right, bg="white", padx=8, pady=7, bd=1, relief=tk.RIDGE)
        self.filecap_fr.pack(fill="both", expand=True, pady=(20, 4), padx=8)
        
        # Scheduler frame
        sched_fr = tk.Frame(right, bg="#fff", bd=1, relief=tk.RIDGE, padx=10, pady=8)
        sched_fr.pack(fill="x", pady=(4, 8), padx=8)
        schedule_options_frame = tk.Frame(sched_fr, bg="#fff")
        schedule_options_frame.pack(fill="x", pady=5)
        regular_sched = tk.Checkbutton(schedule_options_frame, text="📅 Schedule Post", 
                                      variable=self.schedule_var, bg="#fff", 
                                      font=("Segoe UI", 12, "bold"), 
                                      command=self.on_schedule_toggle)
        regular_sched.pack(side="left", padx=(0, 20))
        bulk_sched = tk.Checkbutton(schedule_options_frame, text="📋 Bulk Schedule", 
                                   variable=self.bulk_schedule_var, bg="#fff", 
                                   font=("Segoe UI", 12, "bold"), fg="#dc2626",
                                   command=self.on_bulk_schedule_toggle)
        bulk_sched.pack(side="left")
        self.regular_sched_frame = tk.Frame(sched_fr, bg="#fff")
        self.regular_sched_frame.pack(fill="x", pady=5)
        tk.Label(self.regular_sched_frame, text="Date (UTC):", bg="#fff", 
                font=("Segoe UI", 11)).grid(row=0, column=0, padx=(0,5))
        self.year_spin = tk.Spinbox(self.regular_sched_frame, 
                                   from_=datetime.now(UTC).year, 
                                   to=datetime.now(UTC).year+5, 
                                   width=5, textvariable=self.schedule_year_var, 
                                   state='disabled', font=("Segoe UI", 11))
        self.year_spin.grid(row=0, column=1)
        self.month_spin = tk.Spinbox(self.regular_sched_frame, from_=1, to=12, width=3, 
                                    textvariable=self.schedule_month_var, 
                                    state='disabled', font=("Segoe UI", 11))
        self.month_spin.grid(row=0, column=2)
        self.day_spin = tk.Spinbox(self.regular_sched_frame, from_=1, to=31, width=3, 
                                  textvariable=self.schedule_day_var, 
                                  state='disabled', font=("Segoe UI", 11))
        self.day_spin.grid(row=0, column=3)
        tk.Label(self.regular_sched_frame, text="Time (UTC):", bg="#fff", 
                font=("Segoe UI", 11)).grid(row=0, column=4, padx=(20,5))
        self.hour_spin = tk.Spinbox(self.regular_sched_frame, from_=0, to=23, width=3, 
                                   textvariable=self.schedule_hour_var, 
                                   state='disabled', font=("Segoe UI", 11), format="%02.0f")
        self.hour_spin.grid(row=0, column=5)
        self.minute_spin = tk.Spinbox(self.regular_sched_frame, from_=0, to=59, width=3, 
                                     textvariable=self.schedule_minute_var, 
                                     state='disabled', font=("Segoe UI", 11), format="%02.0f")
        self.minute_spin.grid(row=0, column=6)
        self.bulk_sched_frame = tk.Frame(sched_fr, bg="#fff")
        self.bulk_sched_frame.pack(fill="x", pady=5)
        self.bulk_schedule_btn = ttk.Button(self.bulk_sched_frame, 
                                           text="🚀 Open Bulk Schedule Manager", 
                                           command=self.open_bulk_scheduler, 
                                           state="disabled", style="Accent.TButton")
        self.bulk_schedule_btn.pack(side="left")
        self.bulk_status_label = tk.Label(self.bulk_sched_frame, 
                                         text="No bulk schedule configured", 
                                         bg="#fff", font=("Segoe UI", 10, "italic"), 
                                         fg="#666")
        self.bulk_status_label.pack(side="left", padx=(10, 0))
        tk.Label(sched_fr, text="ℹ️ AI learns your posting patterns to suggest optimal times", 
                bg="#fff", font=("Segoe UI", 9, "italic"), fg="#888").pack(pady=(4,0))
        
        # Bottom status bar
        bottom = tk.Frame(self, bg="#f8fafc")
        bottom.pack(fill="x", pady=(8, 0))
        self.status_label = tk.Label(bottom, textvariable=self.status_message, width=45, 
                                    font=("Segoe UI", 13, "bold"), fg="#1e3a8a", bg="#f8fafc")
        self.status_label.pack(side="left", padx=(22, 14))
        self.progressbar = ttk.Progressbar(bottom, orient="horizontal", length=320, 
                                          mode="determinate", variable=self.progress)
        self.progressbar.pack(side="left", padx=(8, 16), pady=10)
        self.stop_btn = ttk.Button(bottom, text="🛑 Stop", width=8, 
                                  style="Accent.TButton", command=self.stop_posting, 
                                  state="disabled")
        self.stop_btn.pack(side="left", padx=7)
    
    def facebook_cloudflare_login(self):
        def login_thread():
            try:
                self.status_message.set("Starting Cloudflare OAuth login...")
                self.fb_login_btn.config(text="Connecting...", state="disabled")
                oauth_url = (
                    f"https://www.facebook.com/v18.0/dialog/oauth?"
                    f"client_id={FACEBOOK_APP_ID}"
                    f"&redirect_uri={urllib.parse.quote(CLOUDFLARE_REDIRECT_URI)}"
                    f"&scope=public_profile,email,pages_show_list,pages_messaging,page_events,pages_manage_ads,pages_manage_cta,pages_manage_posts,pages_manage_metadata,pages_read_engagement,pages_manage_engagement,pages_read_user_content,pages_utility_messaging,pages_messaging_phone_number,pages_manage_instant_articles,read_page_mailboxes,publish_video"
                    f"&response_type=code"
                    f"&state=cloudflare_facebook_login"
                )
                webbrowser.open(oauth_url)
                self.status_message.set("Complete login in browser, then return...")
                auth_code = run_local_oauth_server()
                if not auth_code:
                    raise Exception("No authorization code received")
                self.status_message.set("Processing authentication...")
                short_token = exchange_code_for_token(auth_code)
                long_token = get_long_lived_token(short_token)
                self.fb_access_token = long_token
                self.status_message.set("Loading your Facebook pages...")
                business_pages = get_user_business_pages(self.fb_access_token)
                self.after(0, self.update_facebook_cloudflare_pages, business_pages)
            except Exception as e:
                error_msg = f"Cloudflare Facebook login failed: {str(e)}"
                self.after(0, self.facebook_login_error, error_msg)
        threading.Thread(target=login_thread, daemon=True).start()
    
    def update_facebook_cloudflare_pages(self, business_pages):
        try:
            for widget in self.page_inner.winfo_children():
                widget.destroy()
            self.pages = []
            self.fb_business_pages = business_pages
            for page in business_pages:
                page_data = {
                    'id': page['id'],
                    'name': f"{page['name']} {'✅' if page.get('can_post', False) else '⚠️'}",
                    'access_token': page.get('access_token', self.fb_access_token),
                    'can_post': page.get('can_post', False),
                    'var': tk.BooleanVar(value=False)
                }
                self.pages.append(page_data)
            self.update_pages_list()
            self.login_type = "facebook_cloudflare"
            self.login_status.config(text="✅ Cloudflare Facebook", fg="#15803d")
            self.fb_login_btn.config(text="☁️ Cloudflare Login Active", state="normal")
            self.userbox.set("")
            self.selected_user = None
            self.status_message.set(f"Cloudflare login successful! {len(self.pages)} pages loaded.")
        except Exception as e:
            self.facebook_login_error(f"Failed to update pages: {str(e)}")
    
    def facebook_login_error(self, error_msg):
        self.status_message.set(error_msg)
        self.fb_login_btn.config(text="☁️ Login with Facebook (Cloudflare)", state="normal")
        self.login_status.config(text="Login Failed", fg="#dc2626")
        messagebox.showerror("Facebook Cloudflare Login Error", error_msg)
    
    def on_schedule_toggle(self):
        enabled = self.schedule_var.get()
        if enabled:
            self.bulk_schedule_var.set(False)
            self.on_bulk_schedule_toggle()
        state_val = 'normal' if enabled else 'disabled'
        for widget in [self.year_spin, self.month_spin, self.day_spin, 
                      self.hour_spin, self.minute_spin]:
            widget.config(state=state_val)
    
    def on_bulk_schedule_toggle(self):
        enabled = self.bulk_schedule_var.get()
        if enabled:
            self.schedule_var.set(False)
            self.on_schedule_toggle()
            self.bulk_schedule_btn.config(state="normal")
            self.open_bulk_scheduler()
        else:
            self.bulk_schedule_btn.config(state="disabled")
            self.bulk_schedule_data = []
            self.bulk_status_label.config(text="No bulk schedule configured", fg="#666")
    
    def open_bulk_scheduler(self):
        def on_save_bulk_schedule(schedule_data):
            self.bulk_schedule_data = schedule_data
            count = len(schedule_data)
            self.bulk_status_label.config(
                text=f"✅ {count} items scheduled", 
                fg="#15803d"
            )
            self.on_page_selection()
        BulkSchedulePopup(self, self.ml_predictor, on_save_bulk_schedule)
    
    def show_add_user(self):
        def on_save_user(pn, tkn, bmid):
            creds = load_credentials()
            for c in creds:
                if c['profile_name'] == pn:
                    messagebox.showerror("Error", "Profile name already exists.")
                    return
            creds.append({
                "profile_name": pn,
                "system_user_token": tkn,
                "bm_id": bmid
            })
            save_credentials(creds)
            self.credentials = load_credentials()
            self.userbox['values'] = [c["profile_name"] for c in self.credentials]
            self.userbox.set(pn)
            self.selected_user = {"profile_name": pn, "system_user_token": tkn, "bm_id": bmid}
            self.on_user_selected()
        AddUserPopup(self, on_save_user)
    
    def on_user_selected(self, event=None):
        idx = self.userbox.current()
        if idx == -1 and self.selected_user:
            return
        if idx >= 0:
            self.selected_user = self.credentials[idx]
            self.fb_access_token = None
            self.fb_business_pages = []
            self.login_type = "manual"
            self.login_status.config(text="Manual Login", fg="#666")
            self.fb_login_btn.config(text="☁️ Login with Facebook (Cloudflare)")
        self.status_message.set("Loading pages...")
        self.after(180, self.load_pages)
    
    def load_pages(self):
        try:
            for widget in self.page_inner.winfo_children():
                widget.destroy()
            self.pages.clear()
            self.select_all_var.set(False)
            if self.selected_user and self.login_type == "manual":
                token = self.selected_user["system_user_token"]
                bm_id = self.selected_user["bm_id"]
                all_pages = []
                try:
                    url = f"https://graph.facebook.com/v18.0/{bm_id}/owned_pages"
                    params = {
                        'fields': 'name,id,link,access_token,tasks,category',
                        'access_token': token,
                        'limit': 100
                    }
                    while url:
                        resp = requests.get(url, params=params)
                        data = resp.json()
                        if "data" in data and data["data"]:
                            all_pages.extend(data["data"])
                            if 'paging' in data and 'next' in data['paging']:
                                url = data['paging']['next']
                                params = {}
                            else:
                                break
                        else:
                            break
                except Exception:
                    all_pages = []
                if len(all_pages) < 50:
                    try:
                        url = f"https://graph.facebook.com/v18.0/me/accounts"
                        backup_pages = []
                        params = {
                            'fields': 'name,id,link,access_token,tasks,category',
                            'access_token': token,
                            'limit': 100
                        }
                        while url:
                            resp = requests.get(url, params=params)
                            data = resp.json()
                            if "data" in data and data["data"]:
                                backup_pages.extend(data["data"])
                                if 'paging' in data and 'next' in data['paging']:
                                    url = data['paging']['next']
                                    params = {}
                                else:
                                    break
                            else:
                                break
                        existing_ids = {page['id'] for page in all_pages}
                        for page in backup_pages:
                            if page['id'] not in existing_ids:
                                all_pages.append(page)
                    except Exception:
                        pass
                if not all_pages:
                    self.status_message.set("❌ No pages found or invalid token.")
                    return
                self.pages = all_pages
                for page in self.pages:
                    page["var"] = tk.BooleanVar(value=False)
                    if 'name' not in page:
                        page['name'] = f"Page {page.get('id', 'Unknown')}"
                self.update_pages_list()
                self.status_message.set(f"✅ Loaded {len(self.pages)} pages")
            elif self.login_type == "facebook_cloudflare" and self.fb_access_token:
                self.status_message.set(f"☁️ Cloudflare: {len(self.pages)} pages active")
            else:
                self.status_message.set("⚠️ No user selected.")
        except Exception:
            self.status_message.set("❌ Error loading pages.")
    
    def update_pages_list(self):
        for widget in self.page_inner.winfo_children():
            widget.destroy()
        for idx, page in enumerate(self.pages):
            cb = ttk.Checkbutton(self.page_inner, text=page['name'], 
                               variable=page["var"], command=self.on_page_selection)
            cb.pack(anchor="w", padx=(14, 0), pady=(2, 4))
    
    def select_all_pages(self):
        v = self.select_all_var.get()
        for page in self.pages:
            page["var"].set(v)
        self.on_page_selection()
    
    def on_page_selection(self):
        count = sum(1 for pg in self.pages if pg["var"].get())
        if (self.media_type.get() and 
            (self.media_type.get() == "text" or 
             (self.media_files or self.media_folder or self.bulk_schedule_data) and 
             count > 0)):
            self.publish_btn_state("normal")
        else:
            self.publish_btn_state("disabled")
    
    def post_type_changed(self, *_):
        self.ml_predictor.record_user_action('media_preference', {
            'media_type': self.media_type.get()
        })
        self._reset_filecap_frame()
        self.on_page_selection()
    
    def _reset_filecap_frame(self):
        for widget in self.filecap_fr.winfo_children():
            widget.destroy()
        mt = self.media_type.get()
        if mt == "text":
            tk.Label(self.filecap_fr, text="Enter Text To Post:",
                    bg="white", fg="#1e3a8a", font=("Segoe UI", 13, "bold")).pack(anchor="w", padx=12, pady=(10, 6))
            self.text_post = tk.Text(self.filecap_fr, height=4, width=65, 
                                   font=("Segoe UI", 12), bg="#e4e7eb")
            self.text_post.pack(padx=8, pady=(2, 8))
            self.build_publish_btn()
        elif mt in ["photo", "video", "reel"]:
            area = tk.Frame(self.filecap_fr, bg="#f4faff")
            area.pack(fill="x", pady=(9, 2))
            ft_label = {"photo": "Photo", "video": "Video", "reel": "Reel"}[mt]
            tk.Label(area, text=f"Select {ft_label} Files or Folder:", 
                    font=("Segoe UI", 12, "bold"), fg="#1e3a8a", bg="#f4faff")\
                .grid(row=0, column=0, columnspan=4, sticky="w", padx=2, pady=3)
            self.file_label = tk.Label(area, text="No files selected.", fg="#444", 
                                      bg="#f4faff", font=("Segoe UI", 10, "italic"))
            self.file_label.grid(row=1, column=0, columnspan=2, sticky="w", padx=10)
            ttk.Button(area, text="Choose File(s)", style="Accent.TButton", 
                      command=self.select_media_files).grid(row=2, column=0, sticky="w", padx=1, pady=(0, 9))
            self.folder_label = tk.Label(area, text="No folder selected.", fg="#444", 
                                        bg="#f4faff", font=("Segoe UI", 10, "italic"))
            self.folder_label.grid(row=1, column=2, columnspan=2, sticky="w", padx=28)
            ttk.Button(area, text="Select Folder", style="Accent.TButton", 
                      command=self.select_media_folder).grid(row=2, column=2, sticky="w", padx=17, pady=(0, 9))
            tk.Checkbutton(area, text="Delete media after posting", 
                          variable=self.delete_after_var, bg="#f4faff", 
                          font=("Segoe UI", 11))\
                .grid(row=3, column=0, columnspan=4, sticky='w', padx=1, pady=(0, 8))
            cfrm = tk.Frame(self.filecap_fr, bg="#eaf7f4")
            cfrm.pack(fill="x", pady=(6, 0), padx=6)
            tk.Label(cfrm, text="Caption mode:", font=("Segoe UI", 11, "italic"), 
                    fg="#1e3a8a", bg="#eaf7f4").grid(row=0, column=0, sticky="w")
            ttk.Radiobutton(cfrm, text="Same caption for all", 
                           variable=self.caption_type, value="same", 
                           command=self.reset_caption_entry_frame)\
                .grid(row=1, column=0, sticky='w', padx=1)
            ttk.Radiobutton(cfrm, text="Different caption for each", 
                           variable=self.caption_type, value="multi", 
                           command=self.reset_caption_entry_frame)\
                .grid(row=1, column=1, sticky='w', padx=21)
            self.caption_entry_frame = tk.Frame(self.filecap_fr, bg="#eaf7f4")
            self.caption_entry_frame.pack(fill='x', padx=12, pady=(1, 3))
            self.reset_caption_entry_frame()
            self.build_publish_btn()
    
    def build_publish_btn(self):
        if hasattr(self, 'publish_btn') and self.publish_btn:
            self.publish_btn.destroy()
        btn_frame = tk.Frame(self.filecap_fr, bg="white")
        btn_frame.pack(anchor="e", padx=18, pady=(8, 3))
        if self.bulk_schedule_data:
            self.publish_btn = ttk.Button(btn_frame, text="🚀 Execute Bulk Schedule", 
                                         style="Accent.TButton", command=self.on_publish)
        else:
            self.publish_btn = ttk.Button(btn_frame, text="📤 Publish/Post", 
                                         style="Accent.TButton", command=self.on_publish)
        self.publish_btn.pack()
        self.on_page_selection()
    
    def select_media_files(self):
        mt = self.media_type.get()
        if mt == "photo":
            ftypes = [("Images", "*.jpg *.jpeg *.png *.gif *.bmp")]
        elif mt == "video":
            ftypes = [("Videos", "*.mp4 *.mov *.avi")]
        else:
            ftypes = [("Reel", "*.mp4 *.mov")]
        files = filedialog.askopenfilenames(title=f"Select {mt.capitalize()} files", 
                                          filetypes=ftypes)
        if files:
            self.media_files = list(files)
            self.file_label["text"] = f"{len(files)} file(s): {', '.join(os.path.basename(f) for f in files[:3])}..."
            self.folder_label["text"] = "No folder selected."
            self.media_folder = None
            self.reset_caption_entry_frame()
            if self.bulk_schedule_var.get():
                self.open_bulk_scheduler()
        else:
            self.media_files = []
            self.file_label["text"] = "No files selected."
        self.on_page_selection()
    
    def select_media_folder(self):
        mt = self.media_type.get()
        fdir = filedialog.askdirectory(title=f"Select folder containing {mt} files")
        if not fdir:
            return
        if mt == "photo":
            file_types = [".jpg", ".jpeg", ".png", ".gif", ".bmp"]
        elif mt == "video":
            file_types = [".mp4", ".mov", ".avi"]
        elif mt == "reel":
            file_types = [".mp4", ".mov"]
        else:
            file_types = []
        media = []
        for file in os.listdir(fdir):
            ext = os.path.splitext(file)[1].lower()
            if ext in file_types:
                media.append(os.path.join(fdir, file))
        self.media_files = media
        self.media_folder = fdir
        self.folder_label["text"] = f"{len(media)} file(s) in: {os.path.basename(fdir)}" if media else "No valid files in folder."
        self.file_label["text"] = "No files selected."
        self.reset_caption_entry_frame()
        if self.bulk_schedule_var.get():
            self.open_bulk_scheduler()
        self.on_page_selection()
    
    def reset_caption_entry_frame(self):
        for widget in self.caption_entry_frame.winfo_children():
            widget.destroy()
        mt = self.media_type.get()
        if mt not in ["photo", "video", "reel"]:
            return
        captions_hist = load_captions().get(mt, [])
        recent_caps = ",  ".join([f"{i+1}. {c}" for i, c in enumerate(captions_hist)])
        if self.caption_type.get() == "same":
            tk.Label(self.caption_entry_frame, text="Enter Caption (or recent #):", 
                    font=("Segoe UI", 11), bg="#eaf7f4").pack(anchor="w")
            self.same_caption_var = tk.StringVar()
            cap_entry = tk.Entry(self.caption_entry_frame, textvariable=self.same_caption_var, 
                               width=70, font=('Segoe UI', 11), bg="#f7fafc")
            cap_entry.pack(anchor="w", pady=(0, 2))
            if recent_caps:
                tk.Label(self.caption_entry_frame, text=f"Recent: {recent_caps}", 
                        font=("Segoe UI", 10, "italic"), fg="#1e3a8a", bg="#eaf7f4")\
                    .pack(anchor="w", padx=15)
        else:
            self.multi_caption_vars = []
            tk.Label(self.caption_entry_frame, text="Caption (or recent #) for each file:", 
                    font=("Segoe UI", 11), bg="#eaf7f4").pack(anchor="w")
            source_files = self.media_files
            if source_files:
                for i, f in enumerate(source_files[:22]):
                    cap = tk.StringVar()
                    self.multi_caption_vars.append(cap)
                    row = tk.Frame(self.caption_entry_frame, bg="#eaf7f4")
                    row.pack(anchor="w")
                    tk.Label(row, text=f"{i+1}. {os.path.basename(f)}: ", 
                            width=28, anchor="w", bg="#eaf7f4").pack(side="left")
                    tk.Entry(row, textvariable=cap, width=59).pack(side="left")
            if recent_caps:
                tk.Label(self.caption_entry_frame, text=f"Recent: {recent_caps}", 
                        font=("Segoe UI", 10, "italic"), fg="#1e3a8a", bg="#eaf7f4")\
                    .pack(anchor="w", padx=12)
    
    def publish_btn_state(self, state):
        if hasattr(self, 'publish_btn'):
            self.publish_btn["state"] = state
    
    def on_publish(self):
        try:
            sel_pages = [pg for pg in self.pages if pg["var"].get()]
            mt = self.media_type.get()
            if not sel_pages or not mt:
                self.status_message.set("Select all required options.")
                return
            self.ml_predictor.record_user_action('posting', {
                'pages_count': len(sel_pages)
            })
            if self.bulk_schedule_data and self.bulk_schedule_var.get():
                self.execute_bulk_schedule(sel_pages)
                return
            if mt == "text":
                txt = self.text_post.get("1.0", "end").strip()
                if not txt:
                    self.status_message.set("Enter text to post.")
                    return
            elif mt in ["photo", "video", "reel"]:
                if not self.media_files:
                    self.status_message.set(f"Select {mt} file(s) or folder.")
                    return
            if self.schedule_var.get():
                try:
                    sched_time = datetime(
                        year=self.schedule_year_var.get(),
                        month=self.schedule_month_var.get(),
                        day=self.schedule_day_var.get(),
                        hour=self.schedule_hour_var.get(),
                        minute=self.schedule_minute_var.get()
                    )
                    if sched_time <= datetime.now(UTC):
                        self.status_message.set("Scheduled time must be in the future (UTC).")
                        return
                    self.scheduled_time = sched_time
                except Exception:
                    self.status_message.set("Invalid schedule datetime selection.")
                    return
            else:
                self.scheduled_time = None
            
            self.publish_btn_state("disabled")
            self.progress.set(0)
            self.status_message.set("Posting...")
            self.stop_flag = False
            self.stop_btn["state"] = "normal"
            
            threading.Thread(target=self.run_publish, args=(sel_pages, mt), daemon=True).start()
            
        except Exception as ex:
            traceback.print_exc()
    
    def execute_bulk_schedule(self, sel_pages):
        """Execute bulk schedule with multiple files and times"""
        self.publish_btn_state("disabled")
        self.progress.set(0)
        self.status_message.set("Executing bulk schedule...")
        self.stop_flag = False
        self.stop_btn["state"] = "normal"
        
        threading.Thread(target=self.run_bulk_schedule, args=(sel_pages,), daemon=True).start()
    
    def run_bulk_schedule(self, sel_pages):
        """Run bulk schedule posting"""
        try:
            total_items = len(self.bulk_schedule_data) * len(sel_pages)
            progress_step = 100 // max(1, total_items)
            success_count = fail_count = completed_count = 0
            
            for item_idx, schedule_item in enumerate(self.bulk_schedule_data):
                if self.stop_flag:
                    break
                
                file_path = schedule_item['file']
                schedule_time = schedule_item['time']
                caption = schedule_item['caption']
                
                # Determine media type from file extension
                ext = os.path.splitext(file_path)[1].lower()
                if ext in ['.jpg', '.jpeg', '.png', '.gif', '.bmp']:
                    media_type = 'photo'
                elif ext in ['.mp4', '.mov', '.avi']:
                    # Check if it's a reel (vertical video)
                    if VideoFileClip:
                        try:
                            clip = VideoFileClip(file_path)
                            w, h = clip.size
                            if h > w and clip.duration <= 90:
                                media_type = 'reel'
                            else:
                                media_type = 'video'
                            clip.close()
                        except:
                            media_type = 'video'
                    else:
                        media_type = 'video'
                else:
                    media_type = 'video'
                
                for page_idx, page in enumerate(sel_pages):
                    if self.stop_flag:
                        break
                    
                    try:
                        page_id = page['id']
                        page_name = page['name']
                        
                        # Get page access token based on login type
                        if self.login_type == "facebook_cloudflare":
                            PAGE_ACCESS_TOKEN = page.get('access_token', self.fb_access_token)
                        else:
                            # Manual login - get page token using system user token
                            token = self.selected_user["system_user_token"]
                            page_token_resp = requests.get(
                                f"https://graph.facebook.com/v18.0/{page_id}?fields=access_token&access_token={token}"
                            ).json()
                            
                            if 'access_token' not in page_token_resp:
                                fail_count += 1
                                continue
                            
                            PAGE_ACCESS_TOKEN = page_token_resp['access_token']
                        
                        # Post based on media type
                        resp = {}
                        if media_type == "photo":
                            url = f"https://graph.facebook.com/v18.0/{page_id}/photos"
                            with open(file_path, "rb") as img:
                                filesrc = {"source": img}
                                data = {
                                    "access_token": PAGE_ACCESS_TOKEN,
                                    "published": "false",
                                    "scheduled_publish_time": int(schedule_time.timestamp())
                                }
                                if caption:
                                    data["caption"] = caption
                                resp = requests.post(url, files=filesrc, data=data).json()
                        
                        elif media_type == "video":
                            url = f"https://graph.facebook.com/v18.0/{page_id}/videos"
                            with open(file_path, "rb") as vid:
                                filesrc = {"source": vid}
                                data = {
                                    "access_token": PAGE_ACCESS_TOKEN,
                                    "published": "false",
                                    "scheduled_publish_time": int(schedule_time.timestamp())
                                }
                                if caption:
                                    data["description"] = caption
                                resp = requests.post(url, files=filesrc, data=data).json()
                        
                        elif media_type == "reel":
                            url = f"https://graph.facebook.com/v18.0/{page_id}/videos"
                            with open(file_path, "rb") as vid:
                                filesrc = {"source": vid}
                                data = {
                                    "access_token": PAGE_ACCESS_TOKEN,
                                    "is_clip": "true",
                                    "published": "false",
                                    "scheduled_publish_time": int(schedule_time.timestamp())
                                }
                                if caption:
                                    data["description"] = caption
                                resp = requests.post(url, files=filesrc, data=data).json()
                        
                        if "id" in resp:
                            success_count += 1
                            self.status_message.set(f'Scheduled on "{page_name}" - {os.path.basename(file_path)}')
                        else:
                            fail_count += 1
                            error_msg = resp.get("error", {}).get("message", "Unknown error")
                            self.status_message.set(f'Failed on "{page_name}": {error_msg}')
                        
                        completed_count += 1
                        self.progress.set(completed_count * progress_step)
                        
                    except Exception as e:
                        fail_count += 1
                        self.status_message.set(f'Exception on "{page_name}": {str(e)}')
                        traceback.print_exc()
                    
                    time.sleep(random.uniform(1, 1.5))  # Rate limiting
            
            self.status_label.config(fg="#176b82")
            self.status_message.set(f"Bulk Schedule Complete! Success: {success_count}, Failed: {fail_count}")
            
        except Exception as e:
            traceback.print_exc()
            self.status_label.config(fg="#e36040")
            self.status_message.set("Bulk schedule execution failed!")
        finally:
            self.progress.set(100)
            self.publish_btn_state("normal")
            self.stop_btn["state"] = "disabled"
    
    def stop_posting(self):
        self.stop_flag = True
        self.stop_btn["state"] = "disabled"
        self.status_message.set("Stopping...")
    
    def run_publish(self, sel_pages, mt):
        try:
            total_pages = len(sel_pages)
            files = self.media_files
            progress_pct = 100 // max(1, total_pages)
            success_count = fail_count = posted_count = 0
            
            if mt == "text":
                message = self.text_post.get("1.0", "end").strip()
            elif mt in ["photo", "video", "reel"]:
                captions_hist = load_captions().get(mt, [])
                caption_list = []
                box_val = self.same_caption_var.get().strip() if self.caption_type.get() == "same" else None
                
                if self.caption_type.get() == "same":
                    used_caption = ""
                    used_from_suggestion = False
                    if box_val and box_val.isdigit() and 1 <= int(box_val) <= len(captions_hist):
                        used_caption = captions_hist[int(box_val) - 1]
                        used_from_suggestion = True
                    else:
                        used_caption = box_val
                        used_from_suggestion = False
                    caption_list = [used_caption] * len(files)
                    if not used_from_suggestion:
                        add_caption(mt, used_caption)
                else:
                    caption_list = []
                    for idx, cap_var in enumerate(self.multi_caption_vars):
                        c = cap_var.get()
                        if c.isdigit() and 1 <= int(c) <= len(captions_hist):
                            c = captions_hist[int(c) - 1]
                        else:
                            add_caption(mt, c)
                        caption_list.append(c)
                
                for path in files:
                    if mt == "video":
                        valid, reason = validate_video_format(path)
                        if not valid:
                            self.status_label.config(fg="#e36040")
                            self.status_message.set(f"{os.path.basename(path)}: {reason}")
                            return self._run_end()
                    if mt == "reel":
                        valid, reason = validate_reel_format(path)
                        if not valid:
                            self.status_label.config(fg="#e36040")
                            self.status_message.set(f"{os.path.basename(path)}: {reason}")
                            return self._run_end()
            
            for idx, page in enumerate(sel_pages):
                if self.stop_flag:
                    self.status_label.config(fg="#e36040")
                    self.status_message.set(f"Stopped at {posted_count}/{total_pages}")
                    break
                
                page_id = page['id']
                page_name = page['name']
                try:
                    # Get page access token based on login type
                    if self.login_type == "facebook_cloudflare":
                        PAGE_ACCESS_TOKEN = page.get('access_token', self.fb_access_token)
                    else:
                        # Manual login
                        token = self.selected_user["system_user_token"]
                        page_token_resp = requests.get(
                            f"https://graph.facebook.com/v18.0/{page_id}?fields=access_token&access_token={token}"
                        ).json()
                        if 'access_token' not in page_token_resp:
                            fail_count += 1
                            self.status_message.set(f'Failed to get access token for "{page_name}".')
                            self.progress.set((idx + 1) * progress_pct)
                            continue
                        PAGE_ACCESS_TOKEN = page_token_resp['access_token']
                    
                    resp = {}
                    if mt == "text":
                        # For personal profile, use /me/feed, for pages use /page_id/feed
                        if page_id == 'me':
                            post_url = f"https://graph.facebook.com/v18.0/me/feed"
                        else:
                            post_url = f"https://graph.facebook.com/v18.0/{page_id}/feed"
                        
                        post_data = {'message': message, 'access_token': PAGE_ACCESS_TOKEN}
                        # Add scheduling if enabled
                        if self.scheduled_time:
                            post_data['published'] = 'false'
                            post_data['scheduled_publish_time'] = int(self.scheduled_time.timestamp())
                        resp = requests.post(post_url, data=post_data).json()
                    else:
                        media_idx = idx if len(files) >= total_pages else idx % len(files)
                        path = files[media_idx]
                        caption_val = caption_list[media_idx] if len(caption_list) > media_idx else caption_list
                        
                        if mt == "photo":
                            if page_id == 'me':
                                url = f"https://graph.facebook.com/v18.0/me/photos"
                            else:
                                url = f"https://graph.facebook.com/v18.0/{page_id}/photos"
                            
                            with open(path, "rb") as img:
                                filesrc = {"source": img}
                                data = {"access_token": PAGE_ACCESS_TOKEN}
                                if caption_val:
                                    data["caption"] = caption_val
                                # Schedule for photo posts
                                if self.scheduled_time:
                                    data['published'] = 'false'
                                    data['scheduled_publish_time'] = int(self.scheduled_time.timestamp())
                                resp = requests.post(url, files=filesrc, data=data).json()
                        
                        elif mt in ["video", "reel"]:
                            if page_id == 'me':
                                url = f"https://graph.facebook.com/v18.0/me/videos"
                            else:
                                url = f"https://graph.facebook.com/v18.0/{page_id}/videos"
                            
                            with open(path, "rb") as vid:
                                filesrc = {"source": vid}
                                data = {"access_token": PAGE_ACCESS_TOKEN}
                                if caption_val:
                                    data["description"] = caption_val
                                if mt == "reel":
                                    data["is_clip"] = "true"
                                if self.scheduled_time:
                                    data['published'] = 'false'
                                    data['scheduled_publish_time'] = int(self.scheduled_time.timestamp())
                                resp = requests.post(url, files=filesrc, data=data).json()
                    
                    # Check response and update status accordingly
                    if "id" in resp:
                        success_count += 1
                        self.status_message.set(f'Posted on "{page_name}"')
                    elif "error" in resp:
                        error_msg = resp["error"].get("message", "Unknown error")
                        fail_count += 1
                        self.status_message.set(f'Failed on "{page_name}": {error_msg}')
                    else:
                        fail_count += 1
                        self.status_message.set(f'Failed on "{page_name}": Unknown response')
                    
                    self.progress.set((idx + 1) * progress_pct)
                except Exception as e:
                    fail_count += 1
                    self.status_message.set(f'Exception on "{page_name}": {str(e)}')
                    traceback.print_exc()
                
                posted_count += 1
                # Prevent rapid-fire requests; slight delay to avoid rate limits
                time.sleep(random.uniform(1, 1.2))
            
            # Delete media files after posting, if enabled
            if mt in ["photo", "video", "reel"] and self.delete_after_var.get():
                paths_to_delete = []
                if len(files) >= total_pages:
                    paths_to_delete = files[:total_pages]
                else:
                    paths_to_delete = files
                for p in paths_to_delete:
                    try:
                        os.remove(p)
                    except Exception as e:
                        traceback.print_exc()
            
            self.status_label.config(fg="#176b82")
            self._run_end(success_count, fail_count, posted_count, total_pages)
        except Exception as e:
            traceback.print_exc()
            self.status_label.config(fg="#e36040")
            self._run_end()
    
    def _run_end(self, s=0, f=0, pc=0, tp=1):
        self.progress.set(100)
        self.publish_btn_state("normal")
        self.stop_btn["state"] = "disabled"
        self.status_message.set(f"Success: {s}, Fail: {f} (of {tp})")

if __name__ == "__main__":
    app = MultiPosterGUI()
    
    # Check configuration
    if (FACEBOOK_APP_ID == "YOUR_APP_ID" or 
        FACEBOOK_APP_SECRET == "YOUR_APP_SECRET" or 
        "your-worker" in CLOUDFLARE_REDIRECT_URI):
        messagebox.showwarning("Setup Required", 
                             "Please configure:\n"
                             "1. Facebook App ID & Secret\n"
                             "2. Cloudflare Worker URL in CLOUDFLARE_REDIRECT_URI\n\n"
                             "You can still use manual login method.")
    
    app.mainloop()