<?php
require_once __DIR__ . '/../config/config.php';

/* =========================================================
 *  SOURCES  (dynamic — admin panel থেকে add/edit/delete হয়)
 * ========================================================= */

function get_sources() {
    if (!file_exists(SOURCES_JSON)) {
        $defaults = require BASE_PATH . '/config/sources.php';
        save_sources($defaults);
        return $defaults;
    }
    $json = file_get_contents(SOURCES_JSON);
    $data = json_decode($json, true);
    return is_array($data) ? $data : [];
}

function save_sources($sources) {
    return file_put_contents(SOURCES_JSON, json_encode($sources, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES), LOCK_EX);
}

function add_source($id, $name, $category, $url) {
    $sources = get_sources();
    foreach ($sources as $s) {
        if ($s['id'] === $id) return false; // already exists
    }
    $sources[] = ['id' => $id, 'name' => $name, 'category' => $category, 'url' => $url, 'active' => true];
    return save_sources($sources);
}

function delete_source($id) {
    $sources = get_sources();
    $sources = array_values(array_filter($sources, fn($s) => $s['id'] !== $id));
    return save_sources($sources);
}

function toggle_source($id, $active) {
    $sources = get_sources();
    foreach ($sources as &$s) {
        if ($s['id'] === $id) {
            $s['active'] = (bool)$active;
            if ($active) { $s['consecutive_fails'] = 0; unset($s['paused_by_health']); }
        }
    }
    return save_sources($sources);
}

/* =========================================================
 *  SCRAPE SOURCES (dynamic — RSS নেই এমন সাইটের হোমপেজ/ক্যাটাগরি
 *  লিস্টিং URL, যেখান থেকে অটো আর্টিকেল লিংক ডিসকভার করা হয়)
 * ========================================================= */

function get_scrape_sources() {
    if (!file_exists(SCRAPE_SOURCES_JSON)) {
        save_scrape_sources([]);
        return [];
    }
    $data = json_decode(file_get_contents(SCRAPE_SOURCES_JSON), true);
    return is_array($data) ? $data : [];
}

function save_scrape_sources($sources) {
    return file_put_contents(SCRAPE_SOURCES_JSON, json_encode($sources, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES), LOCK_EX);
}

function add_scrape_source($id, $name, $category, $url, $maxPerRun = 8) {
    $sources = get_scrape_sources();
    foreach ($sources as $s) {
        if ($s['id'] === $id) return false; // already exists
    }
    $sources[] = [
        'id' => $id, 'name' => $name, 'category' => $category, 'url' => $url,
        'active' => true, 'max_per_run' => max(1, min(30, (int)$maxPerRun)),
    ];
    return save_scrape_sources($sources);
}

function delete_scrape_source($id) {
    $sources = get_scrape_sources();
    $sources = array_values(array_filter($sources, fn($s) => $s['id'] !== $id));
    return save_scrape_sources($sources);
}

function toggle_scrape_source($id, $active) {
    $sources = get_scrape_sources();
    foreach ($sources as &$s) {
        if ($s['id'] === $id) {
            $s['active'] = (bool)$active;
            if ($active) { $s['consecutive_fails'] = 0; unset($s['paused_by_health']); }
        }
    }
    return save_scrape_sources($sources);
}

/* =========================================================
 *  SCRAPED-LINK INDEX (URL-লেভেল dedup)
 *  news_hash (link+title) ইনডেক্সের চেয়ে আগে চেক হয় — একবার
 *  যে লিংক চেক করা হয়ে গেছে সেটা বারবার fetch করে সাইটে লোড
 *  না বাড়ানোর জন্য। (advanced: bandwidth/rate-limit বাঁচায়)
 * ========================================================= */

function load_scraped_link_index() {
    if (!file_exists(SCRAPED_LINKS_INDEX_JSON)) return [];
    $data = json_decode(file_get_contents(SCRAPED_LINKS_INDEX_JSON), true);
    return is_array($data) ? $data : [];
}

function save_scraped_link_index($index) {
    return file_put_contents(SCRAPED_LINKS_INDEX_JSON, json_encode($index, JSON_UNESCAPED_UNICODE), LOCK_EX);
}

function link_hash($url) {
    return md5(strtolower(trim($url)));
}

/* =========================================================
 *  CATEGORY LABELS (কাস্টম ক্যাটাগরি — সাইট-উইজার্ড অটো-ডিটেক্ট
 *  করা ক্যাটাগরির slug => আসল লেবেল ম্যাপিং সংরক্ষণ করে, যাতে
 *  api/categories.php তে সঠিক নাম দেখানো যায়)
 * ========================================================= */

function get_category_labels() {
    if (!file_exists(CATEGORY_LABELS_JSON)) return [];
    $data = json_decode(file_get_contents(CATEGORY_LABELS_JSON), true);
    return is_array($data) ? $data : [];
}

function save_category_labels($labels) {
    return file_put_contents(CATEGORY_LABELS_JSON, json_encode($labels, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES), LOCK_EX);
}

/**
 * টেক্সট (মেনু/নেভিগেশন লিংকের নাম) থেকে ক্যাটাগরি slug বের করে —
 * প্রথমে পরিচিত ট্যাক্সোনমি (জাতীয়/খেলা/বিনোদন ইত্যাদি) এর কীওয়ার্ডের
 * সাথে মিলিয়ে দেখে, না মিললে নতুন কাস্টম slug তৈরি করে (একই লেবেল
 * আগে থেকে থাকলে সেই slug-ই আবার ব্যবহার করে, ডুপ্লিকেট এড়াতে)।
 * রিটার্ন করে: [slug, label]
 */
function resolve_category($text) {
    $text = trim(clean_text($text));
    if ($text === '') return ['national', 'জাতীয়'];
    $norm = mb_strtolower($text, 'UTF-8');

    $knownMap = [
        'national'      => ['keywords' => ['জাতীয়', 'national', 'bangladesh', 'দেশ', 'home', 'headline', 'প্রথম পাতা', 'ফ্রন্ট পেজ'], 'label' => 'জাতীয়'],
        'international' => ['keywords' => ['আন্তর্জাতিক', 'বিশ্ব', 'world', 'international'], 'label' => 'আন্তর্জাতিক'],
        'sports'        => ['keywords' => ['খেলা', 'খেলাধুলা', 'sport', 'cricket', 'football'], 'label' => 'খেলা'],
        'entertainment' => ['keywords' => ['বিনোদন', 'entertainment', 'showbiz', 'সিনেমা'], 'label' => 'বিনোদন'],
        'technology'    => ['keywords' => ['প্রযুক্তি', 'technology', 'tech', 'বিজ্ঞান', 'science'], 'label' => 'প্রযুক্তি'],
        'business'      => ['keywords' => ['অর্থনীতি', 'ব্যবসা', 'business', 'economy', 'trade', 'finance', 'বাণিজ্য'], 'label' => 'অর্থনীতি'],
        'politics'      => ['keywords' => ['রাজনীতি', 'politics'], 'label' => 'রাজনীতি'],
        'opinion'       => ['keywords' => ['মতামত', 'opinion', 'editorial', 'সম্পাদকীয়'], 'label' => 'মতামত'],
        'lifestyle'     => ['keywords' => ['লাইফস্টাইল', 'lifestyle', 'জীবনযাপন'], 'label' => 'লাইফস্টাইল'],
        'education'     => ['keywords' => ['শিক্ষা', 'education', 'campus', 'ক্যাম্পাস'], 'label' => 'শিক্ষা'],
        'health'        => ['keywords' => ['স্বাস্থ্য', 'health'], 'label' => 'স্বাস্থ্য'],
        'english'       => ['keywords' => ['english'], 'label' => 'English'],
    ];
    foreach ($knownMap as $slug => $info) {
        foreach ($info['keywords'] as $kw) {
            if ($kw !== '' && mb_stripos($norm, $kw, 0, 'UTF-8') !== false) {
                return [$slug, $info['label']];
            }
        }
    }

    // কাস্টম ক্যাটাগরি — একই লেবেল আগে সেভ থাকলে সেই slug পুনরায় ব্যবহার করি
    $labels = get_category_labels();
    foreach ($labels as $slug => $label) {
        if (mb_strtolower($label, 'UTF-8') === $norm) {
            return [$slug, $label];
        }
    }

    $slug = 'cat-' . substr(md5($norm), 0, 8);
    $labels[$slug] = $text;
    save_category_labels($labels);
    return [$slug, $text];
}

/**
 * অ্যাডমিন প্যানেলের সব ড্রপডাউনে ব্যবহারের জন্য — ফিক্সড ট্যাক্সোনমি +
 * সাইট-উইজার্ড/ম্যানুয়ালি তৈরি হওয়া কাস্টম ক্যাটাগরি — সব মিলিয়ে
 * slug => label ম্যাপ রিটার্ন করে।
 */
function get_all_category_options() {
    $fixed = [
        'national' => 'জাতীয়', 'international' => 'আন্তর্জাতিক', 'sports' => 'খেলা',
        'entertainment' => 'বিনোদন', 'technology' => 'প্রযুক্তি', 'business' => 'অর্থনীতি',
        'politics' => 'রাজনীতি', 'opinion' => 'মতামত', 'lifestyle' => 'লাইফস্টাইল',
        'education' => 'শিক্ষা', 'health' => 'স্বাস্থ্য', 'english' => 'English',
    ];
    return array_merge($fixed, get_category_labels());
}

/* =========================================================
 *  SETTINGS (retention days ইত্যাদি)
 * ========================================================= */

function get_settings() {
    $defaults = [
        'retention_days' => DEFAULT_RETENTION_DAYS,
        'fetch_interval_minutes' => 2,
        'scrape_max_per_source' => 8,   // প্রতি সোর্স, প্রতি রানে সর্বোচ্চ কতগুলো নতুন লিংক চেক হবে
        'scrape_delay_ms' => 500,       // দুই আর্টিকেল fetch এর মাঝে বিরতি (ms) — সাইটকে ওভারলোড না করার জন্য
        'scrape_max_total_per_run' => 60, // পুরো cron রানে সর্বোচ্চ কতগুলো আর্টিকেল fetch হবে (শেয়ার্ড হোস্টিং টাইমআউট থেকে বাঁচতে)
        'max_article_age_days' => 3,    // এর চেয়ে পুরনো (পাবলিশ ডেট অনুযায়ী) খবর কালেক্ট করা হবে না — ০ দিলে কোনো লিমিট নেই
        'auto_pause_after_fails' => 0,  // পরপর এতবার ব্যর্থ হলে সোর্স অটো বন্ধ হয়ে যাবে — ০ মানে অটো-পজ বন্ধ
        // --- 🤖 AI ফিচার (ঐচ্ছিক — API কী না দিলে সম্পূর্ণ বন্ধ থাকে) ---
        'ai_provider' => 'gemini',   // 'gemini' (ফ্রি ট্রায়াল আছে), 'anthropic' অথবা 'openai'
        'ai_api_key' => '',
        'ai_model' => '',               // খালি রাখলে ডিফল্ট মডেল ব্যবহার হবে
        'ai_enable_summary' => false,   // প্রতিটা আর্টিকেলের জন্য ২-৩ লাইনের বাংলা সারাংশ
        'ai_enable_translate' => false, // ইংরেজি/অন্য ভাষার আর্টিকেল অটো-বাংলা অনুবাদ
        // --- 🔔 Telegram এলার্ট (ঐচ্ছিক) ---
        'telegram_bot_token' => '',
        'telegram_chat_id' => '',
        'telegram_alert_source_down' => false, // কোনো সোর্স অটো-বন্ধ হলে সাথে সাথে জানাবে
        'telegram_daily_summary' => false,     // দিনে একবার (সকাল ৮টায়) সামারি রিপোর্ট পাঠাবে
        // --- 🔴 ব্রেকিং নিউজ ডিটেকশন ---
        'breaking_news_keywords' => 'ব্রেকিং,এইমাত্র,জরুরি ভিত্তিতে,ব্রেকিং নিউজ,breaking news,breaking:',
        'telegram_alert_breaking_news' => false, // ব্রেকিং নিউজ ডিটেক্ট হলে সাথে সাথে Telegram এলার্ট
        // --- 📲 অ্যাপ ভার্সন / ইন-অ্যাপ আপডেট চেক ---
        'latest_version_code' => CURRENT_APP_VERSION_CODE,
        'latest_version_name' => CURRENT_APP_VERSION_NAME,
        'update_url' => '',
        'force_update' => false,
        'changelog' => '',
    ];
    if (!file_exists(SETTINGS_JSON)) {
        save_settings($defaults);
        return $defaults;
    }
    $data = json_decode(file_get_contents(SETTINGS_JSON), true);
    return is_array($data) ? array_merge($defaults, $data) : $defaults;
}

function save_settings($settings) {
    return file_put_contents(SETTINGS_JSON, json_encode($settings, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE), LOCK_EX);
}

/* =========================================================
 *  🌦️ আবহাওয়া — Open-Meteo (সম্পূর্ণ ফ্রি, কোনো API কী লাগে না)
 * ========================================================= */

function fetch_json_url($url, $timeout = 15) {
    if (!function_exists('curl_init')) return null;
    $ch = curl_init($url);
    curl_setopt_array($ch, [
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_TIMEOUT => $timeout,
        CURLOPT_SSL_VERIFYPEER => true,
        CURLOPT_HTTPHEADER => ['Accept: application/json'],
    ]);
    $body = curl_exec($ch);
    $err = curl_error($ch);
    $code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
    curl_close($ch);
    if ($err || $code >= 400 || !$body) {
        app_log("JSON FETCH ERROR [$url] code=$code err=$err", 'fetch_errors.log');
        return null;
    }
    $data = json_decode($body, true);
    return is_array($data) ? $data : null;
}

function get_weather_cities() {
    if (!file_exists(WEATHER_CITIES_JSON)) {
        $defaults = [
            ['id' => 'dhaka',      'name' => 'ঢাকা',       'lat' => 23.8103, 'lon' => 90.4125],
            ['id' => 'chattogram', 'name' => 'চট্টগ্রাম',   'lat' => 22.3569, 'lon' => 91.7832],
            ['id' => 'khulna',     'name' => 'খুলনা',      'lat' => 22.8456, 'lon' => 89.5403],
            ['id' => 'rajshahi',   'name' => 'রাজশাহী',    'lat' => 24.3745, 'lon' => 88.6042],
            ['id' => 'sylhet',     'name' => 'সিলেট',      'lat' => 24.8949, 'lon' => 91.8687],
            ['id' => 'barishal',   'name' => 'বরিশাল',     'lat' => 22.7010, 'lon' => 90.3535],
            ['id' => 'rangpur',    'name' => 'রংপুর',      'lat' => 25.7439, 'lon' => 89.2752],
            ['id' => 'mymensingh', 'name' => 'ময়মনসিংহ',  'lat' => 24.7471, 'lon' => 90.4203],
        ];
        save_weather_cities($defaults);
        return $defaults;
    }
    $data = json_decode(file_get_contents(WEATHER_CITIES_JSON), true);
    return is_array($data) ? $data : [];
}

function save_weather_cities($cities) {
    return file_put_contents(WEATHER_CITIES_JSON, json_encode($cities, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE), LOCK_EX);
}

function add_weather_city($id, $name, $lat, $lon) {
    $id = preg_replace('/[^a-z0-9\-]/', '', strtolower(trim($id)));
    $cities = get_weather_cities();
    foreach ($cities as $c) {
        if ($c['id'] === $id) return false;
    }
    $cities[] = ['id' => $id, 'name' => trim($name), 'lat' => (float)$lat, 'lon' => (float)$lon];
    return save_weather_cities($cities);
}

function delete_weather_city($id) {
    $cities = array_values(array_filter(get_weather_cities(), fn($c) => $c['id'] !== $id));
    return save_weather_cities($cities);
}

function weather_code_to_bangla($code) {
    if ($code === 0) return '☀️ ঝকঝকে রোদ';
    if (in_array($code, [1, 2], true)) return '🌤️ কিছুটা মেঘলা';
    if ($code === 3) return '☁️ মেঘলা';
    if (in_array($code, [45, 48], true)) return '🌫️ কুয়াশা';
    if (in_array($code, [51, 53, 55, 56, 57], true)) return '🌦️ হালকা বৃষ্টি';
    if (in_array($code, [61, 63, 65, 66, 67, 80, 81, 82], true)) return '🌧️ বৃষ্টি';
    if (in_array($code, [71, 73, 75, 77, 85, 86], true)) return '🌨️ তুষারপাত';
    if (in_array($code, [95, 96, 99], true)) return '⛈️ বজ্রঝড়';
    return '🌡️ স্বাভাবিক আবহাওয়া';
}

/**
 * সব শহরের বর্তমান আবহাওয়া + ৩ দিনের পূর্বাভাস আনে — ৩০ মিনিট ক্যাশ
 * করা থাকে (Open-Meteo সম্পূর্ণ ফ্রি ও কী ছাড়াই কাজ করে, তবু বারবার
 * কল না করে ক্যাশ ব্যবহার করা ভালো অভ্যাস)। $forceRefresh দিলে ক্যাশ
 * উপেক্ষা করে সরাসরি নতুন ডাটা আনবে (cron প্রি-ওয়ার্মিং এর জন্য)।
 */
function get_weather_data($forceRefresh = false) {
    $cache = file_exists(WEATHER_CACHE_JSON) ? json_decode(file_get_contents(WEATHER_CACHE_JSON), true) : null;
    $cacheAge = is_array($cache) ? (int)($cache['fetched_at'] ?? 0) : 0;
    if (!$forceRefresh && $cache && (time() - $cacheAge) < 1800) {
        return $cache;
    }

    $oldByCity = [];
    if (is_array($cache)) {
        foreach ($cache['cities'] ?? [] as $old) {
            $oldByCity[$old['id']] = $old;
        }
    }

    $cities = get_weather_cities();
    $results = [];
    foreach ($cities as $city) {
        $url = "https://api.open-meteo.com/v1/forecast?latitude={$city['lat']}&longitude={$city['lon']}"
             . "&current_weather=true&daily=temperature_2m_max,temperature_2m_min,precipitation_sum"
             . "&timezone=Asia%2FDhaka&forecast_days=3";
        $data = fetch_json_url($url, 12);

        if (!$data || !isset($data['current_weather'])) {
            if (isset($oldByCity[$city['id']])) $results[] = $oldByCity[$city['id']]; // ব্যর্থ হলে পুরনো ক্যাশড ডাটা রেখে দেওয়া
            continue;
        }

        $cw = $data['current_weather'];
        $daily = $data['daily'] ?? [];
        $forecast = [];
        foreach ($daily['time'] ?? [] as $i => $date) {
            $forecast[] = [
                'date'    => $date,
                'max'     => $daily['temperature_2m_max'][$i] ?? null,
                'min'     => $daily['temperature_2m_min'][$i] ?? null,
                'rain_mm' => $daily['precipitation_sum'][$i] ?? null,
            ];
        }

        $results[] = [
            'id'          => $city['id'],
            'name'        => $city['name'],
            'temp'        => $cw['temperature'] ?? null,
            'wind_kmh'    => $cw['windspeed'] ?? null,
            'condition'   => weather_code_to_bangla((int)($cw['weathercode'] ?? -1)),
            'weather_code'=> (int)($cw['weathercode'] ?? -1),
            'forecast'    => $forecast,
            'updated_at'  => date('Y-m-d H:i:s'),
        ];
    }

    $out = ['fetched_at' => time(), 'fetched_at_human' => date('Y-m-d H:i:s'), 'cities' => $results];
    file_put_contents(WEATHER_CACHE_JSON, json_encode($out, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE), LOCK_EX);
    return $out;
}

/* =========================================================
 *  🚨 জরুরি/দুর্যোগ সতর্কতা — এডমিন প্যানেল থেকে ম্যানুয়ালি পাবলিশ করা হয়
 *  (বন্যা/ঘূর্ণিঝড়/তাপপ্রবাহ/অন্য যেকোনো সরকারি সতর্কতা)। বাংলাদেশ
 *  সরকারের কোনো স্থিতিশীল/নথিভুক্ত পাবলিক API না থাকায় এটা সরাসরি
 *  অটো-ফেচ করা সম্ভব নয় — তাই এডমিন নিজে সরকারি সূত্র (BMD/FFWC/
 *  দুর্যোগ ব্যবস্থাপনা অধিদপ্তর) দেখে এখানে এন্ট্রি করবেন, আর অ্যাপে
 *  সাথে সাথে সবার কাছে পৌঁছে যাবে।
 * ========================================================= */

function get_alerts() {
    if (!file_exists(ALERTS_JSON)) {
        save_alerts([]);
        return [];
    }
    $data = json_decode(file_get_contents(ALERTS_JSON), true);
    return is_array($data) ? $data : [];
}

function save_alerts($alerts) {
    return file_put_contents(ALERTS_JSON, json_encode($alerts, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE), LOCK_EX);
}

function add_alert($title, $message, $severity, $area, $validUntil) {
    $alerts = get_alerts();
    $alerts[] = [
        'id'          => uniqid('alert_'),
        'title'       => trim($title),
        'message'     => trim($message),
        'severity'    => in_array($severity, ['info', 'warning', 'danger'], true) ? $severity : 'info',
        'area'        => trim($area) ?: 'সারাদেশ',
        'valid_until' => trim($validUntil) ?: null, // খালি হলে মেয়াদ নেই, ম্যানুয়ালি বন্ধ না করা পর্যন্ত দেখাবে
        'created_at'  => date('Y-m-d H:i:s'),
        'active'      => true,
    ];
    save_alerts($alerts);

    // জরুরি সতর্কতা তাই Telegram কনফিগার করা থাকলে সবসময় পাঠানো হয় (আলাদা টগলের প্রয়োজন নেই)
    $severityIcon = ['info' => 'ℹ️', 'warning' => '⚠️', 'danger' => '🚨'][$alerts[count($alerts) - 1]['severity']] ?? 'ℹ️';
    send_telegram_alert("{$severityIcon} <b>{$title}</b>\nএলাকা: " . ($area ?: 'সারাদেশ') . "\n{$message}");

    return true;
}

function delete_alert($id) {
    $alerts = array_values(array_filter(get_alerts(), fn($a) => $a['id'] !== $id));
    return save_alerts($alerts);
}

function toggle_alert($id, $active) {
    $alerts = get_alerts();
    foreach ($alerts as &$a) {
        if ($a['id'] === $id) $a['active'] = (bool)$active;
    }
    return save_alerts($alerts);
}

/**
 * অ্যাপে দেখানোর মতো এখনো বৈধ (মেয়াদোত্তীর্ণ নয়, active) এলার্ট রিটার্ন করে,
 * নতুনগুলো আগে (created_at descending)।
 */
function get_active_alerts() {
    $now = time();
    $active = array_values(array_filter(get_alerts(), function ($a) use ($now) {
        if (empty($a['active'])) return false;
        if (!empty($a['valid_until'])) {
            $exp = strtotime($a['valid_until']);
            if ($exp && $exp < $now) return false;
        }
        return true;
    }));
    usort($active, fn($a, $b) => strtotime($b['created_at']) <=> strtotime($a['created_at']));
    return $active;
}

/* =========================================================
 *  🕵️ এলার্ট ওয়াচ সোর্স — সরকারি সতর্কতা পেজ মনিটরিং (API না থাকায়)
 *  BMD/FFWC/DDM-এর কোনো নথিভুক্ত পাবলিক API না থাকায় এবং সাইট
 *  স্ট্রাকচার RSS/লিংক-লিস্টিং ধাঁচের না হওয়ায় সাধারণ স্ক্রেপার
 *  কাজ করে না। তার বদলে এখানে "কন্টেন্ট-চেঞ্জ মনিটরিং" পদ্ধতি —
 *  পেজের মূল টেক্সট নিয়মিত fetch করে আগেরটার সাথে তুলনা করা হয়;
 *  বদলে গেলে (এবং "no post"/খালি না হলে) স্বয়ংক্রিয়ভাবে একটা
 *  এলার্ট তৈরি হয়ে যায় (get_active_alerts()-এ যোগ হয়ে যাবে)।
 * ========================================================= */

function get_alert_watch_sources() {
    if (!file_exists(ALERT_WATCH_SOURCES_JSON)) {
        $defaults = [
            [
                'id' => 'bmd-special-bulletin',
                'name' => 'BMD বিশেষ আবহাওয়া বিজ্ঞপ্তি',
                'url' => 'https://live6.bmd.gov.bd/bmd_web/p/Special-Weather-Bulletin',
                'default_severity' => 'warning',
                'default_area' => 'সারাদেশ',
                'active' => true,
            ],
        ];
        save_alert_watch_sources($defaults);
        return $defaults;
    }
    $data = json_decode(file_get_contents(ALERT_WATCH_SOURCES_JSON), true);
    return is_array($data) ? $data : [];
}

function save_alert_watch_sources($sources) {
    return file_put_contents(ALERT_WATCH_SOURCES_JSON, json_encode($sources, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE), LOCK_EX);
}

function add_alert_watch_source($id, $name, $url, $severity = 'warning', $area = 'সারাদেশ') {
    $id = preg_replace('/[^a-z0-9\-]/', '', strtolower(trim($id)));
    $sources = get_alert_watch_sources();
    foreach ($sources as $s) {
        if ($s['id'] === $id) return false;
    }
    $sources[] = ['id' => $id, 'name' => trim($name), 'url' => trim($url), 'default_severity' => $severity, 'default_area' => $area, 'active' => true];
    return save_alert_watch_sources($sources);
}

function delete_alert_watch_source($id) {
    $sources = array_values(array_filter(get_alert_watch_sources(), fn($s) => $s['id'] !== $id));
    return save_alert_watch_sources($sources);
}

function toggle_alert_watch_source($id, $active) {
    $sources = get_alert_watch_sources();
    foreach ($sources as &$s) {
        if ($s['id'] === $id) $s['active'] = (bool)$active;
    }
    return save_alert_watch_sources($sources);
}

/**
 * পেজের HTML থেকে পরিষ্কার টেক্সট বের করে (ট্যাগ/স্ক্রিপ্ট/স্টাইল বাদ
 * দিয়ে, হোয়াইটস্পেস গুছিয়ে) — কন্টেন্ট-চেঞ্জ তুলনার জন্য ব্যবহার হয়।
 */
function fetch_page_text($url) {
    $res = fetch_url($url, 20, 'html');
    if (!$res || empty($res['body'])) return null;
    $text = preg_replace('#<(script|style)\b[^>]*>.*?</\1>#is', ' ', $res['body']);
    $text = strip_tags($text);
    $text = html_entity_decode($text, ENT_QUOTES | ENT_HTML5, 'UTF-8');
    $text = preg_replace('/\s+/u', ' ', $text);
    return trim($text);
}

/**
 * সব সক্রিয় এলার্ট-ওয়াচ সোর্স চেক করে — কন্টেন্ট আগেরবারের চেয়ে
 * বদলে গেলে (আর "no post"/খালির মতো না হলে) স্বয়ংক্রিয়ভাবে একটা
 * নতুন এলার্ট পাবলিশ করে ও Telegram-এ (কনফিগার থাকলে) জানায়।
 * cron/alert_watch.php থেকে পর্যায়ক্রমে কল হয়।
 */
function check_alert_watch_sources() {
    $ignorePhrases = ['no post', 'কোন তথ্য নেই', 'কোনো তথ্য নেই', 'not available'];
    $sources = get_alert_watch_sources();
    $changed = false;
    $newAlerts = 0;

    foreach ($sources as &$src) {
        if (isset($src['active']) && $src['active'] === false) continue;

        $text = fetch_page_text($src['url']);
        $src['last_checked_at'] = date('Y-m-d H:i:s');
        $changed = true;

        if ($text === null) {
            $src['consecutive_fails'] = (int)($src['consecutive_fails'] ?? 0) + 1;
            continue;
        }
        $src['consecutive_fails'] = 0;

        $normalized = mb_strtolower($text, 'UTF-8');
        $isEmptyState = mb_strlen($text) < 15;
        foreach ($ignorePhrases as $p) {
            if (mb_stripos($normalized, $p, 0, 'UTF-8') !== false) { $isEmptyState = true; break; }
        }

        $hash = md5($normalized);
        $prevHash = $src['last_content_hash'] ?? null;

        if (!$isEmptyState && $hash !== $prevHash) {
            // প্রথমবার দেখলে (prevHash না থাকলে) শুধু বেসলাইন সেট করি, এলার্ট বানাই না —
            // নাহলে প্রথম চেকেই বিদ্যমান পুরনো বুলেটিন এলার্ট হিসেবে চলে আসবে
            if ($prevHash !== null) {
                $severity = $src['default_severity'] ?? 'warning';
                if (mb_stripos($normalized, 'ঘূর্ণিঝড়', 0, 'UTF-8') !== false || mb_stripos($normalized, 'cyclone', 0, 'UTF-8') !== false) {
                    $severity = 'danger';
                }
                add_alert(
                    $src['name'],
                    mb_substr($text, 0, 500),
                    $severity,
                    $src['default_area'] ?? 'সারাদেশ',
                    ''
                );
                $newAlerts++;
            }
            $src['last_content_hash'] = $hash;
            $src['last_updated_at'] = date('Y-m-d H:i:s');
        }
    }
    unset($src);

    if ($changed) save_alert_watch_sources($sources);
    return ['checked' => count($sources), 'new_alerts' => $newAlerts];
}

/* =========================================================
 *  🤖 AI ফিচার — অটো-সামারি ও অটো-অনুবাদ (সম্পূর্ণ ঐচ্ছিক)
 *  সেটিংসে API কী দিলে এবং টগল চালু করলে তবেই কাজ করে; কোনো
 *  কারণে API কল ব্যর্থ হলে নীরবে স্কিপ হয়ে যায় — মূল কালেকশন
 *  কখনো আটকে থাকে না।
 * ========================================================= */

function call_ai_api($prompt, $settings) {
    $apiKey = trim($settings['ai_api_key'] ?? '');
    if (!$apiKey || !function_exists('curl_init')) return null;
    $provider = $settings['ai_provider'] ?? 'gemini';

    if ($provider === 'openai') {
        $model = $settings['ai_model'] ?: 'gpt-4o-mini';
        $url = 'https://api.openai.com/v1/chat/completions';
        $payload = json_encode(['model' => $model, 'max_tokens' => 500, 'messages' => [['role' => 'user', 'content' => $prompt]]]);
        $headers = ['Content-Type: application/json', 'Authorization: Bearer ' . $apiKey];
    } elseif ($provider === 'gemini') {
        $model = $settings['ai_model'] ?: 'gemini-2.5-flash';
        $url = "https://generativelanguage.googleapis.com/v1beta/models/{$model}:generateContent";
        $payload = json_encode(['contents' => [['parts' => [['text' => $prompt]]]]]);
        $headers = ['Content-Type: application/json', 'x-goog-api-key: ' . $apiKey];
    } else {
        $model = $settings['ai_model'] ?: 'claude-3-5-haiku-20241022';
        $url = 'https://api.anthropic.com/v1/messages';
        $payload = json_encode(['model' => $model, 'max_tokens' => 500, 'messages' => [['role' => 'user', 'content' => $prompt]]]);
        $headers = ['Content-Type: application/json', 'x-api-key: ' . $apiKey, 'anthropic-version: 2023-06-01'];
    }

    $ch = curl_init($url);
    curl_setopt_array($ch, [
        CURLOPT_POST => true,
        CURLOPT_POSTFIELDS => $payload,
        CURLOPT_HTTPHEADER => $headers,
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_TIMEOUT => 25,
        CURLOPT_SSL_VERIFYPEER => true,
    ]);
    $response = curl_exec($ch);
    $curlErr = curl_error($ch);
    curl_close($ch);

    if ($curlErr || !$response) {
        app_log("AI API কল ব্যর্থ: $curlErr", 'ai_errors.log');
        return null;
    }
    $data = json_decode($response, true);
    if ($provider === 'openai') {
        return $data['choices'][0]['message']['content'] ?? null;
    }
    if (isset($data['error'])) {
        app_log("AI API error: " . json_encode($data['error'], JSON_UNESCAPED_UNICODE), 'ai_errors.log');
        return null;
    }
    if ($provider === 'gemini') {
        return $data['candidates'][0]['content']['parts'][0]['text'] ?? null;
    }
    return $data['content'][0]['text'] ?? null;
}

/**
 * একটা আর্টিকেলের টাইটেল+বর্ণনা AI দিয়ে প্রসেস করে — সেটিংসে যা যা
 * চালু আছে (সারাংশ/অনুবাদ) সেই অনুযায়ী একটাই API কলে ফলাফল আনে।
 * রিটার্ন: null (বন্ধ থাকলে বা ব্যর্থ হলে) অথবা ['summary','title','description']
 */
function ai_process_article($title, $description, $settings) {
    $wantSummary = !empty($settings['ai_enable_summary']);
    $wantTranslate = !empty($settings['ai_enable_translate']);
    if ((!$wantSummary && !$wantTranslate) || empty($settings['ai_api_key'])) return null;
    if (!$title) return null;

    $tasks = [];
    if ($wantSummary) $tasks[] = '"summary" ফিল্ডে ২-৩ লাইনের সহজ বাংলা সারাংশ লিখুন';
    if ($wantTranslate) $tasks[] = '"title_bn" ও "description_bn" ফিল্ডে — টাইটেল/বর্ণনা ইতিমধ্যে বাংলায় থাকলে হুবহু ফেরত দিন, অন্য ভাষায় থাকলে স্বাভাবিক বাংলায় অনুবাদ করুন';

    $prompt = "আপনি একজন বাংলা নিউজ এডিটর। নিচের আর্টিকেলের জন্য এই কাজগুলো করুন: " . implode('; ', $tasks) . "।\n\n"
        . "টাইটেল: {$title}\nবর্ণনা: {$description}\n\n"
        . 'শুধু নিচের JSON ফরম্যাটে উত্তর দিন, অন্য কোনো টেক্সট লিখবেন না: '
        . '{"summary": "...", "title_bn": "...", "description_bn": "..."} — যেটা দরকার নেই সেটা খালি স্ট্রিং রাখুন।';

    $raw = call_ai_api($prompt, $settings);
    if (!$raw) return null;

    $clean = trim(preg_replace('/```json|```/', '', $raw));
    $parsed = json_decode($clean, true);
    if (!is_array($parsed)) return null;

    return [
        'summary'     => $wantSummary ? (trim($parsed['summary'] ?? '') ?: null) : null,
        'title'       => $wantTranslate ? (trim($parsed['title_bn'] ?? '') ?: null) : null,
        'description' => $wantTranslate ? (trim($parsed['description_bn'] ?? '') ?: null) : null,
    ];
}

/* =========================================================
 *  🔔 Telegram এলার্ট — সোর্স ডাউন এলার্ট ও দৈনিক সামারি (ঐচ্ছিক)
 * ========================================================= */

function send_telegram_alert($message, $settings = null) {
    $settings = $settings ?: get_settings();
    $token = trim($settings['telegram_bot_token'] ?? '');
    $chatId = trim($settings['telegram_chat_id'] ?? '');
    if (!$token || !$chatId || !function_exists('curl_init')) return false;

    $url = "https://api.telegram.org/bot{$token}/sendMessage";
    $ch = curl_init($url);
    curl_setopt_array($ch, [
        CURLOPT_POST => true,
        CURLOPT_POSTFIELDS => http_build_query(['chat_id' => $chatId, 'text' => $message, 'parse_mode' => 'HTML']),
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_TIMEOUT => 15,
    ]);
    $response = curl_exec($ch);
    $err = curl_error($ch);
    curl_close($ch);

    if ($err) {
        app_log("Telegram এলার্ট ব্যর্থ: $err", 'ai_errors.log');
        return false;
    }
    $data = json_decode($response, true);
    return !empty($data['ok']);
}

/**
 * দিনে একবার (সকাল ৮টার cron রানে) Telegram-এ সামারি রিপোর্ট পাঠায় —
 * আজকের মোট নতুন খবর ও কোনো সোর্সে সমস্যা আছে কিনা। একই দিনে
 * একাধিকবার পাঠানো এড়াতে settings-এ শেষ পাঠানোর তারিখ সংরক্ষণ করে।
 */
function maybe_send_daily_summary($settings = null) {
    $settings = $settings ?: get_settings();
    if (empty($settings['telegram_daily_summary'])) return;
    if ((int)date('H') !== 8) return; // শুধু সকাল ৮টার আশেপাশের রানে পাঠাবে
    $today = date('Y-m-d');
    if (($settings['last_daily_summary_date'] ?? '') === $today) return; // আজকেরটা আগেই পাঠানো হয়ে গেছে

    $todayCount = count(load_news_file($today));
    $sources = get_sources();
    $scrapeSources = get_scrape_sources();
    $activeTotal = count(array_filter($sources, fn($s) => !isset($s['active']) || $s['active']))
                 + count(array_filter($scrapeSources, fn($s) => !isset($s['active']) || $s['active']));
    $problemCount = count(array_filter(array_merge($sources, $scrapeSources),
        fn($s) => !empty($s['paused_by_health']) || (int)($s['consecutive_fails'] ?? 0) >= 3));

    $message = "📊 <b>দৈনিক সামারি রিপোর্ট</b>\n"
        . "আজকের নতুন খবর: {$todayCount} টি\n"
        . "সক্রিয় সোর্স: {$activeTotal} টি\n"
        . ($problemCount ? "⚠️ সমস্যাযুক্ত সোর্স: {$problemCount} টি — এডমিন প্যানেলে চেক করুন" : "✅ সব সোর্স ঠিকমতো কাজ করছে");

    if (send_telegram_alert($message, $settings)) {
        $settings['last_daily_summary_date'] = $today;
        save_settings($settings);
    }
}

/* =========================================================
 *  📊 অ্যানালিটিক্স — ড্যাশবোর্ড চার্টের জন্য পরিসংখ্যান
 * ========================================================= */

/**
 * শেষ $days দিনের প্রতিদিনের মোট নতুন খবরের সংখ্যা রিটার্ন করে
 * (ট্রেন্ড লাইন চার্টের জন্য) — [['date'=>'07-20','count'=>34], ...]
 */
function get_daily_news_trend($days = 7) {
    $trend = [];
    for ($i = $days - 1; $i >= 0; $i--) {
        $date = date('Y-m-d', strtotime("-$i days"));
        $items = load_news_file($date);
        $trend[] = ['date' => date('d M', strtotime($date)), 'count' => count($items)];
    }
    return $trend;
}

/**
 * আজকের খবরের ক্যাটাগরি-ভিত্তিক ব্রেকডাউন — পাই/বার চার্টের জন্য
 */
function get_today_category_breakdown() {
    $items = load_news_file(date('Y-m-d'));
    $labels = get_all_category_options();
    $counts = [];
    foreach ($items as $item) {
        $cat = $item['category'] ?? 'national';
        $counts[$cat] = ($counts[$cat] ?? 0) + 1;
    }
    arsort($counts);
    $result = [];
    foreach ($counts as $cat => $count) {
        $result[] = ['label' => $labels[$cat] ?? $cat, 'count' => $count];
    }
    return $result;
}

/**
 * আজকের খবরের সোর্স-ভিত্তিক ব্রেকডাউন (কোন সোর্স থেকে কতটা এলো) — টপ ১০
 */
function get_today_source_breakdown($limit = 10) {
    $items = load_news_file(date('Y-m-d'));
    $counts = [];
    foreach ($items as $item) {
        $name = $item['source_name'] ?? 'Unknown';
        $counts[$name] = ($counts[$name] ?? 0) + 1;
    }
    arsort($counts);
    $counts = array_slice($counts, 0, $limit, true);
    $result = [];
    foreach ($counts as $name => $count) {
        $result[] = ['label' => $name, 'count' => $count];
    }
    return $result;
}

function load_index() {
    if (!file_exists(INDEX_JSON)) return [];
    $data = json_decode(file_get_contents(INDEX_JSON), true);
    return is_array($data) ? $data : [];
}

function save_index($index) {
    // ইনডেক্স খুব বড় হয়ে গেলে (৩০ দিনের বেশি) পুরনো এন্ট্রি ছেঁটে ফেলা হয় cleanup ক্রনে
    file_put_contents(INDEX_JSON, json_encode($index, JSON_UNESCAPED_UNICODE), LOCK_EX);
}

function news_hash($link, $title) {
    return md5(strtolower(trim($link)) . '|' . strtolower(trim($title)));
}

/**
 * পুরনো ব্যাকলগ বাদ দেওয়ার জন্য — settings এ সেট করা
 * max_article_age_days এর চেয়ে পুরনো (পাবলিশ তারিখ অনুযায়ী) খবর
 * বাদ দেওয়া হয়, যাতে শুধু আসলেই "নতুন" খবর কালেক্ট হয়।
 * max_article_age_days = 0 মানে কোনো লিমিট নেই।
 */
function is_article_too_old($pubTs, $settings = null) {
    $settings = $settings ?: get_settings();
    $maxDays = (int)($settings['max_article_age_days'] ?? 0);
    if ($maxDays <= 0) return false;
    $cutoff = strtotime("-{$maxDays} days");
    return ((int)$pubTs) < $cutoff;
}

/**
 * টাইটেল দেখে কোনো আর্টিকেল "ব্রেকিং নিউজ" কিনা বোঝার চেষ্টা করে —
 * সেটিংসে কমা দিয়ে আলাদা করা কীওয়ার্ড লিস্ট (breaking_news_keywords)
 * এর সাথে মিলিয়ে দেখে। খালি রাখলে ব্রেকিং-ডিটেকশন বন্ধ থাকবে।
 */
function is_breaking_news_title($title, $settings = null) {
    $settings = $settings ?: get_settings();
    $keywordsStr = trim($settings['breaking_news_keywords'] ?? '');
    if (!$keywordsStr || !$title) return false;

    foreach (array_filter(array_map('trim', explode(',', $keywordsStr))) as $kw) {
        if ($kw !== '' && mb_stripos($title, $kw, 0, 'UTF-8') !== false) return true;
    }
    return false;
}

/* =========================================================
 *  DAILY NEWS FILE STORAGE
 *  data/news/news_YYYY-MM-DD.json  => array of news items
 * ========================================================= */

function today_file($date = null) {
    $date = $date ?: date('Y-m-d');
    return NEWS_DATA_PATH . '/news_' . $date . '.json';
}

function load_news_file($date) {
    $file = today_file($date);
    if (!file_exists($file)) return [];
    $data = json_decode(file_get_contents($file), true);
    return is_array($data) ? $data : [];
}

function save_news_file($date, $items) {
    $file = today_file($date);
    return file_put_contents($file, json_encode($items, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES), LOCK_EX);
}

/* =========================================================
 *  RSS FETCH + PARSE
 * ========================================================= */

function fetch_url($url, $timeout = 15, $mode = 'rss') {
    $accept = $mode === 'html'
        ? 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8'
        : 'application/rss+xml, application/xml, text/xml';
    $userAgent = $mode === 'html'
        ? 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36'
        : 'Mozilla/5.0 (compatible; BDNewsBot/1.0; +https://example.com/bot)';

    $ch = curl_init($url);
    curl_setopt_array($ch, [
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_FOLLOWLOCATION => true,
        CURLOPT_MAXREDIRS => 5,
        CURLOPT_TIMEOUT => $timeout,
        CURLOPT_SSL_VERIFYPEER => true,
        CURLOPT_ENCODING => '', // gzip/deflate auto handle
        CURLOPT_USERAGENT => $userAgent,
        CURLOPT_HTTPHEADER => ['Accept: ' . $accept, 'Accept-Language: bn,en;q=0.8'],
    ]);
    $body = curl_exec($ch);
    $err = curl_error($ch);
    $code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
    $finalUrl = curl_getinfo($ch, CURLINFO_EFFECTIVE_URL);
    curl_close($ch);
    if ($err || $code >= 400 || !$body) {
        app_log("FETCH ERROR [$url] code=$code err=$err", 'fetch_errors.log');
        return false;
    }
    if ($mode === 'html') {
        return ['body' => $body, 'final_url' => $finalUrl ?: $url];
    }
    return $body; // rss মোডে পুরনো বিহেভিয়ার অপরিবর্তিত রাখা হলো (parse_rss এখনো শুধু string আশা করে)
}

function clean_text($str) {
    $str = strip_tags($str);
    $str = html_entity_decode($str, ENT_QUOTES | ENT_HTML5, 'UTF-8');
    return trim(preg_replace('/\s+/u', ' ', $str));
}

function extract_image_from_item($itemXml, $namespaces) {
    // media:content / media:thumbnail
    if (isset($namespaces['media'])) {
        $media = $itemXml->children($namespaces['media']);
        if (isset($media->content) && (string)$media->content->attributes()->url) {
            return (string)$media->content->attributes()->url;
        }
        if (isset($media->thumbnail) && (string)$media->thumbnail->attributes()->url) {
            return (string)$media->thumbnail->attributes()->url;
        }
    }
    // enclosure
    if (isset($itemXml->enclosure) && (string)$itemXml->enclosure->attributes()->url) {
        $type = (string)$itemXml->enclosure->attributes()->type;
        if (strpos($type, 'image') !== false || $type === '') {
            return (string)$itemXml->enclosure->attributes()->url;
        }
    }
    // fallback: <img> ট্যাগ description এর ভিতরে খোঁজা
    $desc = (string)$itemXml->description;
    if (preg_match('/<img[^>]+src=["\']([^"\']+)["\']/i', $desc, $m)) {
        return $m[1];
    }
    return null;
}

/**
 * একটা RSS ফিড fetch + parse করে নিউজ আইটেমের array রিটার্ন করে
 */
function parse_rss($url, $sourceId, $sourceName, $category) {
    $xmlStr = fetch_url($url);
    if (!$xmlStr) return ['ok' => false, 'items' => []];

    libxml_use_internal_errors(true);
    $xml = simplexml_load_string($xmlStr);
    if (!$xml) {
        app_log("XML PARSE ERROR [$url]", 'fetch_errors.log');
        return ['ok' => false, 'items' => []];
    }

    $namespaces = $xml->getNamespaces(true);
    $items = [];

    // RSS 2.0 => channel->item, Atom => entry
    $nodes = isset($xml->channel->item) ? $xml->channel->item : (isset($xml->entry) ? $xml->entry : []);

    foreach ($nodes as $node) {
        $title = clean_text((string)($node->title ?? ''));
        $link = '';
        if (isset($node->link)) {
            $link = trim((string)$node->link);
            if ($link === '' && isset($node->link->attributes()->href)) {
                $link = (string)$node->link->attributes()->href;
            }
        }
        if (!$title || !$link) continue;

        $description = clean_text((string)($node->description ?? $node->summary ?? ''));
        $pubDateRaw = (string)($node->pubDate ?? $node->published ?? $node->updated ?? '');
        $pubTimestamp = $pubDateRaw ? strtotime($pubDateRaw) : time();
        if (!$pubTimestamp) $pubTimestamp = time();

        $image = extract_image_from_item($node, $namespaces);

        $items[] = [
            'id'          => news_hash($link, $title),
            'source_id'   => $sourceId,
            'source_name' => $sourceName,
            'category'    => $category,
            'title'       => $title,
            'description' => mb_substr($description, 0, 400),
            'image'       => $image,
            'link'        => $link,
            'pub_date'    => date('Y-m-d H:i:s', $pubTimestamp),
            'pub_ts'      => $pubTimestamp,
            'fetched_at'  => date('Y-m-d H:i:s'),
        ];
    }
    return ['ok' => true, 'items' => $items];
}

/* =========================================================
 *  GENERIC ARTICLE SCRAPER
 *  যেকোনো নিউজ ওয়েবসাইটের আর্টিকেল লিংক দিলে সেখান থেকে
 *  ছবি, টাইটেল, শর্ট ডেসক্রিপশন, পাবলিশ ডেট/টাইম, সোর্স নাম
 *  বের করে — RSS ফিড ছাড়াই। বেশিরভাগ নিউজ সাইট Open Graph /
 *  Twitter Card / JSON-LD মেটা ট্যাগ ব্যবহার করে বলে এই লজিক
 *  প্রায় সব সাইটে কাজ করে; ব্যর্থ হলে সাধারণ HTML ফলব্যাক ব্যবহৃত হয়।
 * ========================================================= */

function absolute_url($maybeRelative, $baseUrl) {
    $maybeRelative = trim($maybeRelative);
    if ($maybeRelative === '') return '';
    if (preg_match('#^https?://#i', $maybeRelative)) return $maybeRelative;
    if (strpos($maybeRelative, '//') === 0) {
        $scheme = parse_url($baseUrl, PHP_URL_SCHEME) ?: 'https';
        return $scheme . ':' . $maybeRelative;
    }
    $parts = parse_url($baseUrl);
    if (!$parts) return $maybeRelative;
    $scheme = $parts['scheme'] ?? 'https';
    $host = $parts['host'] ?? '';
    if (strpos($maybeRelative, '/') === 0) {
        return "$scheme://$host" . $maybeRelative;
    }
    $basePath = isset($parts['path']) ? preg_replace('#/[^/]*$#', '/', $parts['path']) : '/';
    return "$scheme://$host" . $basePath . $maybeRelative;
}

function site_name_from_host($host) {
    $host = preg_replace('/^www\./i', '', $host);
    $bareParts = explode('.', $host);
    $label = $bareParts[0] ?? $host;
    return ucwords(str_replace(['-', '_'], ' ', $label));
}

/**
 * একটা আর্টিকেল URL fetch + parse করে মেটাডেটা বের করে।
 * রিটার্ন করে: ['title','description','image','pub_date','pub_ts','source_name','source_id','link'] অথবা false ব্যর্থ হলে।
 */
function scrape_article_meta($url) {
    $url = trim($url);
    if (!$url || !preg_match('#^https?://#i', $url)) {
        return ['error' => 'সঠিক http(s):// লিংক দিন।'];
    }

    $res = fetch_url($url, 20, 'html');
    if (!$res || empty($res['body'])) {
        return ['error' => 'লিংকটি fetch করা যায়নি (সাইট ডাউন অথবা bot ব্লক করছে)।'];
    }

    $html = $res['body'];
    $finalUrl = $res['final_url'];

    // এনকোডিং ঠিক রাখতে meta charset চেক করে UTF-8 এ কনভার্ট
    if (preg_match('/<meta[^>]+charset=["\']?([\w-]+)/i', $html, $cm)) {
        $charset = strtoupper($cm[1]);
        if ($charset !== 'UTF-8' && in_array($charset, mb_list_encodings())) {
            $html = mb_convert_encoding($html, 'UTF-8', $charset);
        }
    }

    libxml_use_internal_errors(true);
    $doc = new DOMDocument();
    $doc->loadHTML('<?xml encoding="utf-8" ?>' . $html);
    libxml_clear_errors();
    $xpath = new DOMXPath($doc);

    $getMeta = function (array $props) use ($xpath) {
        foreach ($props as $p) {
            foreach (['property', 'name', 'itemprop'] as $attr) {
                $nodes = $xpath->query("//meta[translate(@$attr,'ABCDEFGHIJKLMNOPQRSTUVWXYZ','abcdefghijklmnopqrstuvwxyz')='" . strtolower($p) . "']/@content");
                if ($nodes->length > 0) {
                    $val = trim($nodes->item(0)->nodeValue);
                    if ($val !== '') return $val;
                }
            }
        }
        return '';
    };

    // ---- Title ----
    $title = $getMeta(['og:title', 'twitter:title']);
    if (!$title) {
        $tNodes = $xpath->query('//title');
        if ($tNodes->length > 0) $title = trim($tNodes->item(0)->nodeValue);
    }
    if (!$title) {
        $h1 = $xpath->query('//h1');
        if ($h1->length > 0) $title = trim($h1->item(0)->nodeValue);
    }
    $title = clean_text($title);

    // ---- Description ----
    $description = $getMeta(['og:description', 'description', 'twitter:description']);
    if (!$description) {
        $pNodes = $xpath->query('//article//p | //*[contains(@class,"content") or contains(@class,"article")]//p');
        foreach ($pNodes as $p) {
            $t = clean_text($p->nodeValue);
            if (mb_strlen($t) > 40) { $description = $t; break; }
        }
    }
    $description = clean_text($description);

    // ---- Image ----
    $image = $getMeta(['og:image', 'og:image:secure_url', 'twitter:image', 'twitter:image:src']);
    if (!$image) {
        $imgNodes = $xpath->query('//article//img/@src | //img[contains(@class,"featured") or contains(@class,"thumb")]/@src');
        if ($imgNodes->length > 0) $image = trim($imgNodes->item(0)->nodeValue);
    }
    if ($image) $image = absolute_url($image, $finalUrl);

    // ---- Publish date/time ----
    $pubRaw = $getMeta([
        'article:published_time', 'og:published_time', 'datePublished',
        'publish-date', 'publishdate', 'pubdate', 'date', 'sailthru.date',
        'og:updated_time', 'article:modified_time',
    ]);
    if (!$pubRaw) {
        $timeNodes = $xpath->query('//time/@datetime');
        if ($timeNodes->length > 0) $pubRaw = trim($timeNodes->item(0)->nodeValue);
    }
    if (!$pubRaw) {
        // JSON-LD এর ভিতরে datePublished খোঁজা
        $ldNodes = $xpath->query('//script[@type="application/ld+json"]');
        foreach ($ldNodes as $ld) {
            $json = json_decode($ld->nodeValue, true);
            if (is_array($json)) {
                $flat = is_array($json[0] ?? null) ? $json[0] : $json;
                if (!empty($flat['datePublished'])) { $pubRaw = $flat['datePublished']; break; }
            }
        }
    }
    $pubTs = $pubRaw ? strtotime($pubRaw) : false;
    if (!$pubTs) $pubTs = time();

    // ---- Source name ----
    $host = parse_url($finalUrl, PHP_URL_HOST) ?: parse_url($url, PHP_URL_HOST);
    $sourceName = $getMeta(['og:site_name', 'application-name']);
    if (!$sourceName) $sourceName = site_name_from_host($host);
    $sourceId = preg_replace('/[^a-z0-9\-]/', '', strtolower(preg_replace('/^www\./i', '', $host)));

    if (!$title) {
        return ['error' => 'এই লিংক থেকে টাইটেল বের করা যায়নি — সাইটটি হয়তো JS দিয়ে কন্টেন্ট লোড করে, ম্যানুয়ালি টাইটেল/বর্ণনা লিখে সেভ করতে পারেন।'];
    }

    return [
        'title'       => $title,
        'description' => mb_substr($description, 0, 400),
        'image'       => $image ?: null,
        'link'        => $finalUrl,
        'pub_date'    => date('Y-m-d H:i:s', $pubTs),
        'pub_ts'      => $pubTs,
        'source_name' => $sourceName,
        'source_id'   => $sourceId ?: 'manual',
    ];
}

/**
 * স্ক্র্যাপ করা একটা আইটেম (এডমিন কনফার্ম করার পর) news_YYYY-MM-DD.json এ সেভ করে,
 * category admin panel থেকে বেছে দেওয়া হয়। ডুপ্লিকেট হলে false রিটার্ন করে।
 */
function save_scraped_news($data, $category, $sourceOverride = null) {
    $title = clean_text($data['title'] ?? '');
    $link = trim($data['link'] ?? '');
    if (!$title || !$link) return false;

    $hash = news_hash($link, $title);
    $index = load_index();
    if (isset($index[$hash])) {
        return false; // আগে থেকেই আছে
    }

    $description = clean_text($data['description'] ?? '');

    // --- 🤖 AI সামারি/অনুবাদ (ঐচ্ছিক — সেটিংসে চালু থাকলেই কেবল চলে) ---
    $settings = get_settings();
    $aiSummary = null;
    $ai = ai_process_article($title, $description, $settings);
    if ($ai) {
        if (!empty($ai['title'])) $title = clean_text($ai['title']);
        if (!empty($ai['description'])) $description = clean_text($ai['description']);
        $aiSummary = $ai['summary'];
    }

    $pubTs = (int)($data['pub_ts'] ?? time());
    $date = date('Y-m-d', $pubTs);

    $sourceId = $data['source_id'] ?: 'manual';
    $sourceName = $data['source_name'] ?: 'Unknown';
    if (is_array($sourceOverride)) {
        if (!empty($sourceOverride['id'])) $sourceId = $sourceOverride['id'];
        if (!empty($sourceOverride['name'])) $sourceName = $sourceOverride['name'];
    }

    $item = [
        'id'          => $hash,
        'source_id'   => $sourceId,
        'source_name' => $sourceName,
        'category'    => $category ?: 'national',
        'title'       => $title,
        'description' => mb_substr($description, 0, 400),
        'ai_summary'  => $aiSummary,
        'is_breaking' => is_breaking_news_title($title, $settings),
        'image'       => $data['image'] ?? null,
        'link'        => $link,
        'pub_date'    => date('Y-m-d H:i:s', $pubTs),
        'pub_ts'      => $pubTs,
        'fetched_at'  => date('Y-m-d H:i:s'),
        'added_via'   => 'manual_scrape',
    ];

    $items = load_news_file($date);
    $items[] = $item;
    usort($items, fn($a, $b) => $b['pub_ts'] <=> $a['pub_ts']);
    save_news_file($date, $items);

    $index[$hash] = $pubTs;
    save_index($index);

    if ($item['is_breaking'] && !empty($settings['telegram_alert_breaking_news'])) {
        send_telegram_alert("🔴 <b>ব্রেকিং নিউজ ডিটেক্ট হয়েছে</b>\n{$item['title']}\nসোর্স: {$sourceName}\n{$link}", $settings);
    }

    app_log("Scrape saved: {$item['title']} [{$item['link']}]", 'scrape.log');
    return $item;
}

/* =========================================================
 *  ADVANCED: LISTING/HOMEPAGE পেজ থেকে আর্টিকেল লিংক অটো-ডিসকভারি
 *  RSS ফিড নেই এমন সাইটের জন্য — homepage/category URL দিলে
 *  সেই পেজের ভেতরের <a> ট্যাগ স্ক্যান করে "আর্টিকেলের মতো" লিংক
 *  বাছাই করে (নেভিগেশন/ট্যাগ/ক্যাটাগরি লিংক বাদ দিয়ে হিউরিস্টিক
 *  স্কোরিং দিয়ে), নতুন থেকে পুরনো (পেজে যেই ক্রমে আছে) অর্ডারে।
 * ========================================================= */

function is_probably_article_url($path) {
    // নন-আর্টিকেল পেজ (ট্যাগ/ক্যাটাগরি/অথর/সার্চ/লগইন ইত্যাদি) বাদ দেওয়া হলো
    $excludePatterns = '#/(tag|tags|category|categories|topic|topics|author|authors|writer|video|videos|photo|photos|gallery|galleries|live|watch|about|about-us|contact|privacy|privacy-policy|terms|terms-of-service|search|login|signin|signup|register|subscribe|rss|feed|feeds|advertise|advertisement|careers|jobs|cart|checkout|epaper|e-paper|archive|archives|page|tags?|widgets?)(/|$|\?)#i';
    if (preg_match($excludePatterns, $path)) return false;
    if ($path === '' || $path === '/') return false;

    $segments = array_values(array_filter(explode('/', $path)));
    if (count($segments) < 1) return false;

    $lastSeg = end($segments);

    // স্কোরিং সিগন্যাল: URL এ বছর/তারিখ প্যাটার্ন, লম্বা সংখ্যা (আর্টিকেল আইডি), বা লম্বা হাইফেনেটেড স্লাগ
    $hasYear = (bool)preg_match('#/(19|20)\d{2}/#', $path);
    $hasLongId = (bool)preg_match('#-?(\d{4,})(\.\w+)?$#', $lastSeg);
    $hasSlug = (mb_substr_count($lastSeg, '-') >= 3 && mb_strlen($lastSeg) >= 20);
    $hasHtmlExt = (bool)preg_match('#\.(html?|php)$#i', $lastSeg);

    return $hasYear || $hasLongId || $hasSlug || ($hasHtmlExt && count($segments) >= 2);
}

function discover_article_links($listingUrl, $maxLinks = 8) {
    $res = fetch_url($listingUrl, 20, 'html');
    if (!$res || empty($res['body'])) return ['ok' => false, 'links' => []];

    $host = parse_url($res['final_url'] ?: $listingUrl, PHP_URL_HOST);

    libxml_use_internal_errors(true);
    $doc = new DOMDocument();
    $doc->loadHTML('<?xml encoding="utf-8" ?>' . $res['body']);
    libxml_clear_errors();
    $xpath = new DOMXPath($doc);

    $found = [];
    $seen = [];
    foreach ($xpath->query('//a/@href') as $hrefNode) {
        $href = trim($hrefNode->nodeValue);
        if ($href === '' || strpos($href, '#') === 0 || stripos($href, 'javascript:') === 0 || stripos($href, 'mailto:') === 0) continue;

        $abs = absolute_url($href, $res['final_url'] ?: $listingUrl);
        $absHost = parse_url($abs, PHP_URL_HOST);
        if (!$absHost || strcasecmp($absHost, $host) !== 0) continue; // শুধু নিজের ডোমেইনের লিংক (বহিরাগত/বিজ্ঞাপন লিংক বাদ)

        $abs = preg_replace('/[#?].*$/', '', $abs); // ট্র্যাকিং query/#anchor বাদ
        if (isset($seen[$abs])) continue;
        $seen[$abs] = true;

        $path = parse_url($abs, PHP_URL_PATH) ?: '';
        if (!is_probably_article_url($path)) continue;

        $found[] = $abs;
        if (count($found) >= $maxLinks) break;
    }

    return ['ok' => true, 'links' => $found];
}

/* =========================================================
 *  🧙 সাইট অটো-অ্যাড উইজার্ড
 *  শুধু একটা মেইন সাইটের লিংক দিলে:
 *   ১. হোমপেজের <link rel="alternate" type="rss/atom"> থেকে RSS ফিড অটো-ডিটেক্ট
 *   ২. হোমপেজের নেভিগেশন/মেনু থেকে ক্যাটাগরি/সেকশন লিংক অটো-ডিসকভার
 *   ৩. প্রতিটা সেকশনের জন্য কমন প্যাটার্নে (section+"feed/") ফিড আছে
 *      কিনা কুইক-চেক করে থাকে RSS সোর্স, না থাকলে অটো-লিংক-স্ক্রেপ সোর্স
 *   ৪. প্রতিটার ক্যাটাগরি অটো-গেস (resolve_category)
 * এডমিন প্যানেলে প্রিভিউ দেখিয়ে কনফার্ম করার পর সব একসাথে সেভ হয়।
 * ========================================================= */

function discover_rss_feeds_from_html($html, $baseUrl) {
    libxml_use_internal_errors(true);
    $doc = new DOMDocument();
    $doc->loadHTML('<?xml encoding="utf-8" ?>' . $html);
    libxml_clear_errors();
    $xpath = new DOMXPath($doc);

    $feeds = [];
    $seen = [];
    $nodes = $xpath->query('//link[translate(@rel,"ALTERNAE","alternae")="alternate"]');
    foreach ($nodes as $node) {
        $type = strtolower(trim($node->getAttribute('type')));
        if (strpos($type, 'rss') === false && strpos($type, 'atom') === false && strpos($type, 'xml') === false) continue;
        $href = trim($node->getAttribute('href'));
        if ($href === '') continue;
        $abs = absolute_url($href, $baseUrl);
        if (isset($seen[$abs])) continue;
        $seen[$abs] = true;
        $title = trim($node->getAttribute('title')) ?: 'RSS Feed';
        $feeds[] = ['url' => $abs, 'title' => $title];
    }
    return $feeds;
}

function is_probably_utility_link($path) {
    $excludePatterns = '#/(login|signin|signup|register|logout|subscribe|subscription|contact|contact-us|about|about-us|privacy|privacy-policy|terms|terms-of-service|terms-of-use|advertise|advertisement|careers|jobs|cart|checkout|epaper|e-paper|rss|feed|feeds|search|account|profile|settings|help|faq|sitemap)(/|$|\?)#i';
    return (bool)preg_match($excludePatterns, $path);
}

/**
 * হোমপেজের নেভিগেশন/মেনু এলাকা থেকে ক্যাটাগরি/সেকশন লিংক খুঁজে বের করে।
 * রিটার্ন: [['name'=>'খেলা','url'=>'https://.../sports'], ...]
 */
function discover_site_sections($homepageUrl, $maxSections = 14) {
    $res = fetch_url($homepageUrl, 20, 'html');
    if (!$res || empty($res['body'])) return ['sections' => [], 'final_url' => $homepageUrl, 'html' => ''];

    $finalUrl = $res['final_url'] ?: $homepageUrl;
    $host = parse_url($finalUrl, PHP_URL_HOST);
    $html = $res['body'];

    libxml_use_internal_errors(true);
    $doc = new DOMDocument();
    $doc->loadHTML('<?xml encoding="utf-8" ?>' . $html);
    libxml_clear_errors();
    $xpath = new DOMXPath($doc);

    // নেভিগেশন/মেনু জাতীয় এলাকার লিংক (ট্যাগ <nav>, অথবা class/id এ menu/nav/topbar/category শব্দ থাকলে)
    $query = '//nav//a | '
        . '//*[contains(translate(@class,"ABCDEFGHIJKLMNOPQRSTUVWXYZ","abcdefghijklmnopqrstuvwxyz"),"menu") '
        . 'or contains(translate(@class,"ABCDEFGHIJKLMNOPQRSTUVWXYZ","abcdefghijklmnopqrstuvwxyz"),"nav") '
        . 'or contains(translate(@id,"ABCDEFGHIJKLMNOPQRSTUVWXYZ","abcdefghijklmnopqrstuvwxyz"),"menu") '
        . 'or contains(translate(@class,"ABCDEFGHIJKLMNOPQRSTUVWXYZ","abcdefghijklmnopqrstuvwxyz"),"category")]//a';

    $found = [];
    $seenPath = [];
    foreach ($xpath->query($query) as $a) {
        $href = trim($a->getAttribute('href'));
        $text = clean_text($a->textContent);
        if ($href === '' || $text === '' || strpos($href, '#') === 0 || stripos($href, 'javascript:') === 0) continue;
        if (mb_strlen($text) > 30) continue; // মেনু-আইটেম সাধারণত ছোট নাম হয়

        $abs = absolute_url($href, $finalUrl);
        $absHost = parse_url($abs, PHP_URL_HOST);
        if (!$absHost || strcasecmp($absHost, $host) !== 0) continue; // শুধু নিজের ডোমেইন

        $abs = preg_replace('/[#?].*$/', '', rtrim($abs, '/')) . '/';
        $path = parse_url($abs, PHP_URL_PATH) ?: '/';
        if ($path === '/' || is_probably_utility_link($path)) continue;

        $segments = array_values(array_filter(explode('/', $path)));
        if (count($segments) > 2) continue; // শুধু টপ-লেভেল/এক ধাপ গভীর সেকশন

        $key = strtolower($path);
        if (isset($seenPath[$key])) continue;
        $seenPath[$key] = true;

        $found[] = ['name' => $text, 'url' => $abs];
        if (count($found) >= $maxSections) break;
    }

    return ['sections' => $found, 'final_url' => $finalUrl, 'html' => $html];
}

/**
 * একটা সেকশন/ক্যাটাগরি URL এর জন্য কমন ফিড প্যাটার্ন কুইক-চেক করে
 * (feed/, ?feed=rss2, /rss) — পেলে ফিড URL রিটার্ন করে, না পেলে null।
 * শেয়ার্ড হোস্টিং টাইমআউট এড়াতে প্রতি প্যাটার্নে কম টাইমআউট ব্যবহার হয়।
 */
function try_detect_feed_for_url($sectionUrl) {
    $base = rtrim($sectionUrl, '/') . '/';
    $candidates = [$base . 'feed/', $base . 'rss/', $base . 'rss.xml'];

    foreach ($candidates as $candidate) {
        $body = fetch_url($candidate, 6, 'rss');
        if (!$body) continue;
        libxml_use_internal_errors(true);
        $xml = @simplexml_load_string($body);
        libxml_clear_errors();
        if ($xml && (isset($xml->channel->item) || isset($xml->entry))) {
            return $candidate;
        }
    }
    return null;
}

/**
 * মাস্টার ফাংশন: শুধু মেইন সাইটের লিংক দিলে RSS + সেকশন সব ডিসকভার
 * করে একটা প্রিভিউ-উপযোগী রেজাল্ট রিটার্ন করে। কোনো কিছু সেভ করে না —
 * এডমিন প্যানেলে দেখিয়ে কনফার্ম করার পর আলাদাভাবে সেভ হয়।
 */
function run_site_autodiscovery($homepageUrl, $probeSectionFeeds = true, $maxSections = 10) {
    $homepageUrl = trim($homepageUrl);
    if (!preg_match('#^https?://#i', $homepageUrl)) {
        $homepageUrl = 'https://' . $homepageUrl;
    }

    $secResult = discover_site_sections($homepageUrl, $maxSections);
    $sections = $secResult['sections'];
    $finalUrl = $secResult['final_url'];
    $html = $secResult['html'];

    if (!$html) {
        return ['error' => 'সাইটটি fetch করা যায়নি — লিংকটি সঠিক কিনা এবং সাইট এখন চালু আছে কিনা চেক করুন।'];
    }

    $siteHost = parse_url($finalUrl, PHP_URL_HOST) ?: parse_url($homepageUrl, PHP_URL_HOST);
    $siteName = site_name_from_host($siteHost);

    // ১. হোমপেজ-লেভেল RSS অটোডিসকভারি
    $homepageFeeds = discover_rss_feeds_from_html($html, $finalUrl);

    $rssCandidates = [];
    $scrapeCandidates = [];
    $usedFeedUrls = [];

    foreach ($homepageFeeds as $f) {
        if (isset($usedFeedUrls[$f['url']])) continue;
        $usedFeedUrls[$f['url']] = true;
        [$catSlug, $catLabel] = resolve_category($f['title'] !== 'RSS Feed' ? $f['title'] : $siteName);
        $rssCandidates[] = [
            'name'     => $siteName . ($f['title'] && $f['title'] !== 'RSS Feed' ? ' — ' . $f['title'] : ''),
            'url'      => $f['url'],
            'category' => $catSlug,
            'category_label' => $catLabel,
            'id'       => preg_replace('/[^a-z0-9\-]/', '', strtolower($siteHost)) . '-' . substr(md5($f['url']), 0, 6),
        ];
    }

    // ২. প্রতিটা সেকশনের জন্য — ফিড আছে কিনা কুইক-চেক (আছে থাকলে RSS, না থাকলে লিংক-স্ক্রেপ)
    $timeBudget = time() + 40; // এডমিন প্যানেল রিকোয়েস্টে বেশি সময় না লাগানোর জন্য বাজেট
    foreach ($sections as $sec) {
        [$catSlug, $catLabel] = resolve_category($sec['name']);
        $secHost = parse_url($sec['url'], PHP_URL_HOST) ?: $siteHost;
        $secId = preg_replace('/[^a-z0-9\-]/', '', strtolower($secHost)) . '-' . substr(md5($sec['url']), 0, 6);

        $feedUrl = null;
        if ($probeSectionFeeds && time() < $timeBudget) {
            $feedUrl = try_detect_feed_for_url($sec['url']);
        }

        if ($feedUrl && !isset($usedFeedUrls[$feedUrl])) {
            $usedFeedUrls[$feedUrl] = true;
            $rssCandidates[] = [
                'name' => $siteName . ' — ' . $sec['name'],
                'url' => $feedUrl,
                'category' => $catSlug,
                'category_label' => $catLabel,
                'id' => $secId,
            ];
        } else {
            $scrapeCandidates[] = [
                'name' => $siteName . ' — ' . $sec['name'],
                'url' => $sec['url'],
                'category' => $catSlug,
                'category_label' => $catLabel,
                'id' => $secId,
            ];
        }
    }

    // কোনো সেকশনই না পেলে, হোমপেজটাকেই একটা জেনেরিক অটো-স্ক্রেপ সোর্স হিসেবে সাজেস্ট করি
    // (হোমপেজে সাধারণত সব ক্যাটাগরির খবর মিশ্রিত থাকে, তাই "অটো" ক্যাটাগরি —
    //  প্রতিটা আর্টিকেলের টাইটেল দেখে আলাদাভাবে ক্যাটাগরি গেস করা হবে সংগ্রহের সময়)
    if (!$sections && !$rssCandidates) {
        $scrapeCandidates[] = [
            'name' => $siteName . ' — হোমপেজ (মিশ্র)',
            'url' => $finalUrl,
            'category' => 'auto',
            'category_label' => '🤖 অটো (প্রতি আর্টিকেল অনুযায়ী)',
            'id' => preg_replace('/[^a-z0-9\-]/', '', strtolower($siteHost)) . '-home',
        ];
    }

    return [
        'site_name' => $siteName,
        'site_host' => $siteHost,
        'homepage'  => $finalUrl,
        'rss_candidates' => $rssCandidates,
        'scrape_candidates' => $scrapeCandidates,
    ];
}

/**
 * নতুন অটো-স্ক্রেপ সোর্স যোগ করার সময় (ঐচ্ছিক) — যেসব আর্টিকেল লিংক
 * এই মুহূর্তে ওই লিস্টিং পেজে আছে সেগুলো সব "প্রসেসড" হিসেবে মার্ক করে
 * দেয়, কিন্তু স্ক্রেপ/সেভ করে না। এর ফলে cron শুধু এরপর থেকে নতুন
 * যোগ হওয়া আর্টিকেলই কালেক্ট করবে — বিদ্যমান পুরনো ব্যাকলগ একসাথে
 * ঢুকে যাবে না।
 */
function baseline_skip_existing_links($listingUrl, $maxLinks = 40) {
    $discovery = discover_article_links($listingUrl, $maxLinks);
    $linkIndex = load_scraped_link_index();
    $count = 0;
    foreach ($discovery['links'] as $link) {
        $hash = link_hash($link);
        if (!isset($linkIndex[$hash])) {
            $linkIndex[$hash] = time();
            $count++;
        }
    }
    save_scraped_link_index($linkIndex);
    return $count;
}

/**
 * অ্যাডভান্স অটো-কালেকশন: সব active "scrape source" এর লিস্টিং পেজ থেকে
 * নতুন আর্টিকেল লিংক ডিসকভার করে, লিংক-লেভেল dedup চেক করে (আগে
 * প্রসেস করা লিংক আবার fetch করবে না), প্রতিটা নতুন লিংক স্ক্রেপ করে
 * সেভ করে (কনটেন্ট-লেভেল dedup RSS এর মতোই news_hash দিয়ে)।
 * Cron থেকে কল হবে — ঠিক run_news_collection() এর মতোই প্যাটার্ন।
 */
function run_link_scrape_collection() {
    $sources = get_scrape_sources();
    $settings = get_settings();
    $maxTotal = (int)($settings['scrape_max_total_per_run'] ?? 60);
    $delayMs = (int)($settings['scrape_delay_ms'] ?? 500);
    $autoPauseThreshold = (int)($settings['auto_pause_after_fails'] ?? 0);

    $linkIndex = load_scraped_link_index();

    $totalChecked = 0;
    $newCount = 0;
    $failedCount = 0;
    $skippedCount = 0;
    $oldSkipped = 0;
    $healthChanged = false;

    foreach ($sources as &$src) {
        if ($totalChecked >= $maxTotal) break;
        if (isset($src['active']) && $src['active'] === false) continue;

        $maxPerSource = (int)($src['max_per_run'] ?? $settings['scrape_max_per_source'] ?? 8);
        $discovery = discover_article_links($src['url'], $maxPerSource);
        $links = $discovery['links'];

        // --- সোর্স হেলথ ট্র্যাকিং ---
        $src['last_checked_at'] = date('Y-m-d H:i:s');
        if ($discovery['ok']) {
            $src['last_success_at'] = date('Y-m-d H:i:s');
            $src['consecutive_fails'] = 0;
        } else {
            $src['consecutive_fails'] = (int)($src['consecutive_fails'] ?? 0) + 1;
            // --- অটো-পজ: পরপর নির্দিষ্ট সংখ্যক ব্যর্থতার পর সোর্স নিজে থেকে বন্ধ হয়ে যায় ---
            if ($autoPauseThreshold > 0 && $src['consecutive_fails'] >= $autoPauseThreshold) {
                $src['active'] = false;
                $src['paused_by_health'] = true;
                app_log("AUTO-PAUSE: স্ক্রেপ সোর্স '{$src['name']}' পরপর {$src['consecutive_fails']} বার ব্যর্থ হওয়ায় স্বয়ংক্রিয়ভাবে বন্ধ করা হলো।", 'scrape.log');
                if (!empty($settings['telegram_alert_source_down'])) {
                    send_telegram_alert("⚠️ <b>অটো-স্ক্রেপ সোর্স অটো-বন্ধ হয়েছে</b>\n{$src['name']}\nপরপর {$src['consecutive_fails']} বার ব্যর্থ হয়েছে। চেক করে দেখুন।", $settings);
                }
            }
        }
        $healthChanged = true;

        foreach ($links as $link) {
            if ($totalChecked >= $maxTotal) break;

            $lHash = link_hash($link);
            if (isset($linkIndex[$lHash])) {
                $skippedCount++;
                continue; // এই লিংক আগেই চেক করা হয়েছে
            }

            $totalChecked++;
            $data = scrape_article_meta($link);
            $linkIndex[$lHash] = time(); // সফল/ব্যর্থ যাই হোক, দ্বিতীয়বার ঘুরে চেক করবে না

            if (isset($data['error'])) {
                $failedCount++;
                app_log("SCRAPE SKIP [$link] " . $data['error'], 'scrape_errors.log');
            } elseif (is_article_too_old($data['pub_ts'] ?? time(), $settings)) {
                $oldSkipped++; // পুরনো খবর — কালেক্ট করা হলো না, শুধু নতুন সংগ্রহ করাই লক্ষ্য
            } else {
                $category = $src['category'] ?? 'national';
                if ($category === 'auto') {
                    [$category] = resolve_category($data['title']);
                }
                $result = save_scraped_news($data, $category, ['id' => $src['id'], 'name' => $src['name']]);
                if ($result) $newCount++; else $skippedCount++;
            }

            if ($delayMs > 0) usleep($delayMs * 1000); // সাইটকে ওভারলোড না করার জন্য বিরতি
        }
    }
    unset($src);

    save_scraped_link_index($linkIndex);
    if ($healthChanged) save_scrape_sources($sources);

    app_log("Link-scrape run: sources=" . count($sources) . " checked=$totalChecked new=$newCount failed=$failedCount old_skipped=$oldSkipped skipped=$skippedCount", 'scrape.log');

    return ['checked' => $totalChecked, 'new' => $newCount, 'failed' => $failedCount, 'old_skipped' => $oldSkipped, 'skipped' => $skippedCount];
}

/**
 * মূল ফাংশন: সব active সোর্স থেকে ফিড fetch করে, ডুপ্লিকেট বাদ দিয়ে,
 * আজকের news_YYYY-MM-DD.json ফাইলে সেভ করে।
 * Cron থেকে প্রতি ২ মিনিটে কল হবে।
 */
function run_news_collection() {
    $sources = get_sources();
    $index = load_index();
    $settings = get_settings();
    $today = date('Y-m-d');
    $todayItems = load_news_file($today);

    $newCount = 0;
    $totalFetched = 0;
    $oldSkipped = 0;
    $healthChanged = false;
    $autoPauseThreshold = (int)($settings['auto_pause_after_fails'] ?? 0);

    foreach ($sources as &$src) {
        if (isset($src['active']) && $src['active'] === false) continue;

        $res = parse_rss($src['url'], $src['id'], $src['name'], $src['category']);
        $items = $res['items'];
        $totalFetched += count($items);

        // --- সোর্স হেলথ ট্র্যাকিং (ড্যাশবোর্ডে সমস্যাযুক্ত সোর্স দেখানোর জন্য) ---
        $src['last_checked_at'] = date('Y-m-d H:i:s');
        if ($res['ok']) {
            $src['last_success_at'] = date('Y-m-d H:i:s');
            $src['consecutive_fails'] = 0;
        } else {
            $src['consecutive_fails'] = (int)($src['consecutive_fails'] ?? 0) + 1;
            // --- অটো-পজ: পরপর নির্দিষ্ট সংখ্যক ব্যর্থতার পর সোর্স নিজে থেকে বন্ধ হয়ে যায় ---
            if ($autoPauseThreshold > 0 && $src['consecutive_fails'] >= $autoPauseThreshold) {
                $src['active'] = false;
                $src['paused_by_health'] = true;
                app_log("AUTO-PAUSE: RSS source '{$src['name']}' পরপর {$src['consecutive_fails']} বার ব্যর্থ হওয়ায় স্বয়ংক্রিয়ভাবে বন্ধ করা হলো।", 'scrape.log');
                if (!empty($settings['telegram_alert_source_down'])) {
                    send_telegram_alert("⚠️ <b>RSS সোর্স অটো-বন্ধ হয়েছে</b>\n{$src['name']}\nপরপর {$src['consecutive_fails']} বার ফেচ ব্যর্থ হয়েছে। চেক করে দেখুন।", $settings);
                }
            }
        }
        $healthChanged = true;

        foreach ($items as $item) {
            $hash = $item['id'];
            if (isset($index[$hash])) {
                continue; // ডুপ্লিকেট — স্কিপ
            }
            if (is_article_too_old($item['pub_ts'], $settings)) {
                $index[$hash] = $item['pub_ts']; // পুরনো — সেভ না করে শুধু ইনডেক্সে মার্ক করে রাখি, বারবার চেক না করার জন্য
                $oldSkipped++;
                continue;
            }
            if (($item['category'] ?? '') === 'auto') {
                [$autoSlug] = resolve_category($item['title']);
                $item['category'] = $autoSlug;
            }
            // --- 🤖 AI সামারি/অনুবাদ (ঐচ্ছিক) ---
            $ai = ai_process_article($item['title'], $item['description'] ?? '', $settings);
            if ($ai) {
                if (!empty($ai['title'])) $item['title'] = clean_text($ai['title']);
                if (!empty($ai['description'])) $item['description'] = clean_text($ai['description']);
                $item['ai_summary'] = $ai['summary'];
            }
            // --- 🔴 ব্রেকিং নিউজ ডিটেকশন ---
            $item['is_breaking'] = is_breaking_news_title($item['title'], $settings);
            if ($item['is_breaking'] && !empty($settings['telegram_alert_breaking_news'])) {
                send_telegram_alert("🔴 <b>ব্রেকিং নিউজ ডিটেক্ট হয়েছে</b>\n{$item['title']}\nসোর্স: {$item['source_name']}\n{$item['link']}", $settings);
            }
            $index[$hash] = $item['pub_ts'];
            $todayItems[] = $item;
            $newCount++;
        }
    }
    unset($src);

    // নতুন আগে দেখানোর জন্য pub_ts দিয়ে sort (descending)
    usort($todayItems, fn($a, $b) => $b['pub_ts'] <=> $a['pub_ts']);

    save_news_file($today, $todayItems);
    save_index($index);
    if ($healthChanged) save_sources($sources);

    app_log("Collection run: fetched=$totalFetched new=$newCount old_skipped=$oldSkipped total_today=" . count($todayItems));

    return ['fetched' => $totalFetched, 'new' => $newCount, 'old_skipped' => $oldSkipped, 'total_today' => count($todayItems)];
}

/* =========================================================
 *  AUTO DELETE (retention) — cron/cleanup.php থেকে চলবে
 * ========================================================= */

function run_cleanup() {
    $settings = get_settings();
    $days = (int)($settings['retention_days'] ?? DEFAULT_RETENTION_DAYS);
    $cutoff = strtotime("-{$days} days");

    $deletedFiles = 0;
    $files = glob(NEWS_DATA_PATH . '/news_*.json');
    foreach ($files as $file) {
        if (preg_match('/news_(\d{4}-\d{2}-\d{2})\.json$/', $file, $m)) {
            $fileTs = strtotime($m[1]);
            if ($fileTs !== false && $fileTs < $cutoff) {
                @unlink($file);
                $deletedFiles++;
            }
        }
    }

    // ইনডেক্স থেকেও পুরনো হ্যাশ ছেঁটে ফেলা (মেমরি/সাইজ বাঁচাতে, retention+5 দিন পর্যন্ত রাখি সেফটির জন্য)
    $index = load_index();
    $indexCutoff = strtotime('-' . ($days + 5) . ' days');
    $newIndex = [];
    foreach ($index as $hash => $ts) {
        if ($ts >= $indexCutoff) $newIndex[$hash] = $ts;
    }
    save_index($newIndex);

    app_log("Cleanup run: retention_days=$days deleted_files=$deletedFiles index_size=" . count($newIndex));

    return ['deleted_files' => $deletedFiles, 'retention_days' => $days];
}

/* =========================================================
 *  READ NEWS (for API) — সব ফাইল মিলিয়ে, ক্যাটাগরি/পেজিনেশন সহ
 * ========================================================= */

function get_all_news($category = null, $limit = 30, $offset = 0, $sourceId = null, $breakingOnly = false) {
    $files = glob(NEWS_DATA_PATH . '/news_*.json');
    rsort($files); // নতুন তারিখ আগে

    $all = [];
    foreach ($files as $file) {
        $data = json_decode(file_get_contents($file), true);
        if (!is_array($data)) continue;
        $all = array_merge($all, $data);
        if (count($all) > ($offset + $limit) * 3) break; // যথেষ্ট হলে থামি (perf)
    }

    if ($category && $category !== 'all') {
        $all = array_values(array_filter($all, fn($n) => $n['category'] === $category));
    }
    if ($sourceId) {
        $all = array_values(array_filter($all, fn($n) => $n['source_id'] === $sourceId));
    }
    if ($breakingOnly) {
        $all = array_values(array_filter($all, fn($n) => !empty($n['is_breaking'])));
    }

    usort($all, fn($a, $b) => $b['pub_ts'] <=> $a['pub_ts']);

    return array_slice($all, $offset, $limit);
}

/**
 * ড্যাশবোর্ড/অ্যাপের জন্য — শেষ $days দিনের মধ্যে থেকে সাম্প্রতিক
 * ব্রেকিং নিউজ (নতুন আগে) — শুধু is_breaking=true মার্ক করা আইটেম।
 */
function get_recent_breaking_news($limit = 15, $days = 2) {
    $all = [];
    for ($i = 0; $i < $days; $i++) {
        $date = date('Y-m-d', strtotime("-$i days"));
        foreach (load_news_file($date) as $item) {
            if (!empty($item['is_breaking'])) $all[] = $item;
        }
    }
    usort($all, fn($a, $b) => $b['pub_ts'] <=> $a['pub_ts']);
    return array_slice($all, 0, $limit);
}

/**
 * নির্দিষ্ট আইডি (hash) দিয়ে একটা মাত্র আর্টিকেল খুঁজে বের করে —
 * আর্টিকেল ডিটেইল স্ক্রিন/শেয়ার-লিংক রিফ্রেশের জন্য দরকার হয়।
 * নতুন তারিখ থেকে খোঁজা শুরু করে, পাওয়া গেলেই থেমে যায়।
 */
function get_news_by_id($id) {
    $files = glob(NEWS_DATA_PATH . '/news_*.json');
    rsort($files);
    foreach ($files as $file) {
        $data = json_decode(file_get_contents($file), true);
        if (!is_array($data)) continue;
        foreach ($data as $item) {
            if (($item['id'] ?? '') === $id) return $item;
        }
    }
    return null;
}

/* =========================================================
 *  📌 EDITOR'S PICK / PINNED NEWS
 *  data/pinned.json => [{id, pinned_at}, ...] — নতুন পিন আগে
 * ========================================================= */

function get_pinned_list() {
    if (!file_exists(PINNED_JSON)) return [];
    $data = json_decode(file_get_contents(PINNED_JSON), true);
    return is_array($data) ? $data : [];
}

function save_pinned_list($list) {
    return file_put_contents(PINNED_JSON, json_encode($list, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE), LOCK_EX);
}

function is_pinned($newsId) {
    foreach (get_pinned_list() as $p) {
        if ($p['id'] === $newsId) return true;
    }
    return false;
}

function pin_news($newsId) {
    $list = get_pinned_list();
    foreach ($list as $p) {
        if ($p['id'] === $newsId) return true; // আগে থেকেই পিন করা
    }
    array_unshift($list, ['id' => $newsId, 'pinned_at' => date('Y-m-d H:i:s')]);
    $list = array_slice($list, 0, 10); // সর্বোচ্চ ১০টা পিন
    return save_pinned_list($list);
}

function unpin_news($newsId) {
    $list = get_pinned_list();
    $list = array_values(array_filter($list, fn($p) => $p['id'] !== $newsId));
    return save_pinned_list($list);
}

/** পিন করা নিউজগুলোর পূর্ণ ডাটা রিটার্ন করে (হোম ফিডের একদম উপরে দেখানোর জন্য) */
function get_pinned_news_items($category = null) {
    $pinned = get_pinned_list();
    $items = [];
    foreach ($pinned as $p) {
        $item = get_news_by_id($p['id']);
        if ($item) {
            if ($category && $category !== 'all' && $item['category'] !== $category) continue;
            $item['pinned'] = true;
            $items[] = $item;
        }
    }
    return $items;
}

/**
 * টাইটেল/বর্ণনায় কিওয়ার্ড খুঁজে খবর সার্চ করে (কেস-ইনসেনসিটিভ,
 * ইউটিএফ-৮ সেফ) — অ্যাপের সার্চ ফিচারের জন্য। পারফরম্যান্সের জন্য
 * সর্বোচ্চ $maxScanFiles টা দিনের ফাইল স্ক্যান করে।
 */
function search_news($query, $limit = 20, $offset = 0, $maxScanFiles = 45) {
    $query = trim($query);
    if ($query === '') return ['items' => [], 'total' => 0];

    $files = glob(NEWS_DATA_PATH . '/news_*.json');
    rsort($files);
    $files = array_slice($files, 0, $maxScanFiles);

    $matches = [];
    foreach ($files as $file) {
        $data = json_decode(file_get_contents($file), true);
        if (!is_array($data)) continue;
        foreach ($data as $item) {
            $haystack = ($item['title'] ?? '') . ' ' . ($item['description'] ?? '');
            if (mb_stripos($haystack, $query, 0, 'UTF-8') !== false) {
                $matches[] = $item;
            }
        }
    }

    usort($matches, fn($a, $b) => $b['pub_ts'] <=> $a['pub_ts']);
    return ['items' => array_slice($matches, $offset, $limit), 'total' => count($matches)];
}

function count_all_news() {
    $files = glob(NEWS_DATA_PATH . '/news_*.json');
    $count = 0;
    foreach ($files as $file) {
        $data = json_decode(file_get_contents($file), true);
        if (is_array($data)) $count += count($data);
    }
    return $count;
}
