import logging
import gspread
from datetime import datetime
from google.oauth2.service_account import Credentials
from telegram import Update, ReplyKeyboardMarkup, ReplyKeyboardRemove, InlineKeyboardButton, InlineKeyboardMarkup, KeyboardButton, InputMediaPhoto
from telegram.constants import ParseMode
from telegram.helpers import escape_markdown
from telegram.error import TimedOut, BadRequest
from telegram.ext import (
    Application,
    CommandHandler,
    ContextTypes,
    ConversationHandler,
    MessageHandler,
    CallbackQueryHandler,
    filters,
    PicklePersistence,
)
from telegram_bot_calendar import DetailedTelegramCalendar, LSTEP

# --- KONFIGURASI WAJIB ---
TOKEN = "8297652932:AAFOcRCbTHOlCGqVB5fkwbn5vi93pvUbin4" # Ganti dengan TOKEN Anda
NAMA_FILE_JSON = "bot-telegram-final-12fde28b0b6f.json" # Ganti dengan nama file JSON Anda
URL_GOOGLE_SHEET = "https://docs.google.com/spreadsheets/d/1eQDfqKZ63i2wowohqkmClkgX132wSRsJmbYgaIIlLJE/edit?usp=sharing"

# --- NAMA WORKSHEET ---
NAMA_WORKSHEET_DATA = "Data"

# Peta Kategori STO berdasarkan DATEL
STO_MAP = {
    "SBU METRO": ["BBE", "KBL", "KJR", "KLN", "KNN", "KPS", "KRP", "LKI", "MGO", "TNS"],
    "GRESIK": ["BPG", "BWN", "CRM", "DDS", "GSK", "KDE", "POG", "SDY"],
    "LAMONGAN": ["BBA", "BDG", "LMG", "SDD"]
}

# --- OPSI-OPSI ---
STATUS_OPTIONS = ["PLAN", "PROGRESS", "CLOSE"]
ALPRO_OPTIONS = ["TIANG", "ODP", "DISTRIBUSI", "FEEDER", "ODC", "MINI-OLT"]

# --- Pengaturan Logging ---
logging.basicConfig(format="%(asctime)s - %(name)s - %(levelname)s - %(message)s", level=logging.INFO)
logger = logging.getLogger(__name__)

# --- Header ---
def generate_headers():
    header_data_base = ["ID PROJECT POSJAR", "LOKASI/JALAN", "DETAIL PEKERJAAN", "DATEL", "STO", "LOKASI (Teks)", "Koordinat", "Link Google Maps", "TANGGAL SURVEI", "TIME PLAN", "STATUS PEKERJAAN", "ALPRO TERDAMPAK"]
    # --- PERUBAHAN: Dinaikkan menjadi 100 foto ---
    for i in range(1, 101):
        photo_headers = [f"FOTO {i} (File ID)", f"Keterangan FOTO {i}", f"Preview FOTO {i}"]
        header_data_base.extend(photo_headers)
    return header_data_base
HEADER_DATA = generate_headers()


# --- Koneksi ke Google Sheets ---
ws_data = None
try:
    scopes = ["https://www.googleapis.com/auth/spreadsheets", "https://www.googleapis.com/auth/drive"]
    creds = Credentials.from_service_account_file(NAMA_FILE_JSON, scopes=scopes)
    client = gspread.authorize(creds)
    spreadsheet = client.open_by_url(URL_GOOGLE_SHEET)
    ws_data = spreadsheet.worksheet(NAMA_WORKSHEET_DATA)
    # Peringatan: Jika Anda mengubah jumlah foto, kosongkan sheet dan mulai ulang bot
    # agar header baru yang benar bisa dibuat.
    if not ws_data.get_all_values() or ws_data.row_values(1) != HEADER_DATA:
        logger.info("Header tidak cocok atau sheet kosong. Membuat header baru...")
        ws_data.clear()
        ws_data.append_row(HEADER_DATA)
    logger.info("✅ Berhasil terhubung ke Google Sheet.")
except gspread.exceptions.WorksheetNotFound:
    logger.error(f"❌ GAGAL: Worksheet dengan nama '{NAMA_WORKSHEET_DATA}' tidak ditemukan. Harap buat terlebih dahulu.")
    ws_data = None
except Exception as e:
    logger.error(f"❌ GAGAL terhubung ke Google Sheets: {e}")

# --- States ---
(
    CHOOSING_MENU, SELECT_PROJECT_FOR_INPUT, NEW_PROJECT_SELECT_DATEL, NEW_PROJECT_SELECT_STO, NEW_PROJECT_FORM,
    DATA_FORM, AWAITING_DATA_PHOTO,
    SHOW_SELECT_DATEL, SHOW_SELECT_STO, SHOW_SELECTING_PROJECT,
    MANAGE_SELECT_DATEL, MANAGE_SELECT_STO, MANAGE_SELECTING_PROJECT,
    MANAGE_CHOOSING_ACTION, MANAGE_SELECTING_FIELD, MANAGE_AWAITING_VALUE,
    AWAITING_DATA_LOCATION, MANAGE_AWAITING_SELECTION,
    SURVEY_DATE_CALENDAR, TIMELINE_START_CALENDAR, TIMELINE_END_CALENDAR,
    MANAGE_SURVEY_DATE_CALENDAR, MANAGE_TIMELINE_START_CALENDAR, MANAGE_TIMELINE_END_CALENDAR,
    AWAITING_STATUS_PEKERJAAN, AWAITING_ALPRO, MANAGE_SELECT_PHOTO_SLOT
) = range(27)

# --- Keyboards ---
main_menu_keyboard = [["Input Data"], ["Lihat Laporan", "Kelola/Edit Laporan"]]
main_markup = ReplyKeyboardMarkup(main_menu_keyboard, one_time_keyboard=True, resize_keyboard=True)
batal_keyboard = ReplyKeyboardMarkup([["Batal"]], resize_keyboard=True, one_time_keyboard=True)
location_keyboard = ReplyKeyboardMarkup([[KeyboardButton("📍 Kirim Lokasi Saat Ini", request_location=True)], ["Batal"]], resize_keyboard=True, one_time_keyboard=True)
selesai_keyboard = ReplyKeyboardMarkup([["Selesai", "Batal"]], resize_keyboard=True, one_time_keyboard=True)

# --- Fungsi Start & Cancel & Helper ---
async def start(update: Update, context: ContextTypes.DEFAULT_TYPE) -> int:
    context.user_data.clear()
    await update.message.reply_text("Bot Laporan Harian siap digunakan.", reply_markup=main_markup)
    return CHOOSING_MENU

async def cancel(update: Update, context: ContextTypes.DEFAULT_TYPE) -> int:
    context.user_data.clear()
    if update.callback_query:
        try:
            await update.callback_query.answer()
            await update.callback_query.edit_message_text("Aksi dibatalkan.", reply_markup=None)
        except BadRequest as e:
            if "message is not modified" not in str(e):
                logger.warning(f"Error editing message on cancel: {e}")
                await context.bot.send_message(chat_id=update.effective_chat.id, text="Aksi dibatalkan.")

    await update.effective_message.reply_text("Kembali ke menu utama.", reply_markup=main_markup, reply_to_message_id=None)
    return ConversationHandler.END

def _generate_posjar_id(sto: str, worksheet: gspread.Worksheet) -> str:
    try:
        today_str = datetime.now().strftime("%d-%m-%Y")
        header = worksheet.row_values(1)
        try:
            id_posjar_col_index = header.index("ID PROJECT POSJAR") + 1
        except ValueError:
            logger.error("Kolom 'ID PROJECT POSJAR' tidak ditemukan di sheet 'Data'!")
            return f"{sto}-1-{today_str}"
        all_ids = worksheet.col_values(id_posjar_col_index)[1:]
        prefix = f"{sto}-"
        suffix = f"-{today_str}"
        max_num = 0
        for pid in all_ids:
            if pid and pid.startswith(prefix) and pid.endswith(suffix):
                try:
                    num_str = pid.replace(prefix, "").replace(suffix, "")
                    num = int(num_str)
                    if num > max_num:
                        max_num = num
                except (ValueError, IndexError):
                    continue
        new_num = max_num + 1
        return f"{sto}-{new_num}-{today_str}"
    except Exception as e:
        logger.error(f"Error generating POSJAR ID: {e}")
        return f"{sto}-1-{datetime.now().strftime('%d-%m-%Y')}"


# --- ALUR UTAMA INPUT ---
async def start_input(update: Update, context: ContextTypes.DEFAULT_TYPE) -> int:
    context.user_data.clear()
    await update.message.reply_text(
        "Memulai input data baru...\n\n"
        "Silakan buat entri baru atau kembali ke menu utama.",
        reply_markup=InlineKeyboardMarkup([
            [InlineKeyboardButton("🚀 Buat Laporan Baru 🚀", callback_data="input_new")],
            [InlineKeyboardButton("<< Kembali ke Menu Utama", callback_data="input_cancel")]
        ])
    )
    return SELECT_PROJECT_FOR_INPUT

async def select_project_for_input(update: Update, context: ContextTypes.DEFAULT_TYPE) -> int:
    query = update.callback_query
    await query.answer()
    if query.data == 'input_cancel':
        return await cancel(update, context)
    if query.data == 'input_new':
        keyboard = [[InlineKeyboardButton(datel, callback_data=f"newdatel_{datel.replace(' ', '_')}") for datel in STO_MAP.keys()], [InlineKeyboardButton("<< Kembali", callback_data="newdatel_back")]]
        await query.edit_message_text("Membuat Laporan Baru.\n\n➡️ **Silakan pilih DATEL terlebih dahulu:**", reply_markup=InlineKeyboardMarkup(keyboard), parse_mode='Markdown')
        return NEW_PROJECT_SELECT_DATEL

# --- FUNGSI ALUR PROYEK BARU ---
async def back_to_new_project_start(update: Update, context: ContextTypes.DEFAULT_TYPE) -> int:
    query = update.callback_query
    await query.answer()
    await query.edit_message_text("Silakan buat entri baru atau kembali ke menu utama.",
        reply_markup=InlineKeyboardMarkup([
            [InlineKeyboardButton("🚀 Buat Laporan Baru 🚀", callback_data="input_new")],
            [InlineKeyboardButton("<< Kembali ke Menu Utama", callback_data="input_cancel")]
        ]))
    return SELECT_PROJECT_FOR_INPUT

async def new_project_select_datel(update: Update, context: ContextTypes.DEFAULT_TYPE) -> int:
    query = update.callback_query
    await query.answer()
    if 'back' in query.data:
        return await back_to_new_project_start(update, context)
    chosen_datel = query.data.split('_', 1)[1].replace('_', ' ')
    context.user_data.setdefault('report_data', {})['datel'] = chosen_datel
    sto_list = STO_MAP.get(chosen_datel, [])
    if not sto_list:
        await query.edit_message_text(f"Tidak ada data STO untuk DATEL {chosen_datel}. Proses dibatalkan.")
        return await cancel(update, context)
    sto_buttons = [InlineKeyboardButton(sto, callback_data=f"newsto_{sto}") for sto in sto_list]
    keyboard = [sto_buttons[i:i + 4] for i in range(0, len(sto_buttons), 4)]
    keyboard.append([InlineKeyboardButton("<< Kembali", callback_data="newsto_back")])
    await query.edit_message_text(f"✅ DATEL dipilih: **{chosen_datel}**\n\n➡️ Sekarang, **pilih STO:**", reply_markup=InlineKeyboardMarkup(keyboard), parse_mode='Markdown')
    return NEW_PROJECT_SELECT_STO

async def new_project_select_sto(update: Update, context: ContextTypes.DEFAULT_TYPE) -> int:
    query = update.callback_query
    await query.answer()
    if 'back' in query.data:
        keyboard = [[InlineKeyboardButton(datel, callback_data=f"newdatel_{datel.replace(' ', '_')}") for datel in STO_MAP.keys()], [InlineKeyboardButton("<< Kembali", callback_data="newdatel_back")]]
        await query.edit_message_text("Membuat Laporan Baru.\n\n➡️ **Silakan pilih DATEL terlebih dahulu:**", reply_markup=InlineKeyboardMarkup(keyboard), parse_mode='Markdown')
        return NEW_PROJECT_SELECT_DATEL
    chosen_sto = query.data.split('_', 1)[1]
    context.user_data['report_data']['sto'] = chosen_sto
    datel = context.user_data.get('report_data', {}).get('datel')
    generated_id = _generate_posjar_id(chosen_sto, ws_data)
    context.user_data['report_data']['id_project_posjar'] = generated_id
    context.user_data['last_question'] = 'lokasi_jalan'
    await query.edit_message_text(
        f"✅ DATEL: **{datel}**\n✅ STO: **{chosen_sto}**\n\n"
        f"ID Project POSJAR Otomatis:\n`{generated_id}`",
        parse_mode='Markdown',
        reply_markup=None
    )
    await query.message.reply_text("➡️ Selanjutnya, **masukkan LOKASI/JALAN untuk pekerjaan ini:**", reply_markup=batal_keyboard)
    return NEW_PROJECT_FORM

async def new_project_form(update: Update, context: ContextTypes.DEFAULT_TYPE) -> int:
    last_question = context.user_data.get('last_question')
    user_input = update.message.text
    if last_question == 'lokasi_jalan': user_input = user_input.upper()
    context.user_data.setdefault('report_data', {})[last_question] = user_input
    questions = {
        'lokasi_jalan': ('✅ Lokasi/Jalan dicatat. Lanjutkan mengisi laporan...\n\n➡️ **Masukkan DETAIL PEKERJAAN:**', 'detail_pekerjaan')
    }
    if last_question not in questions:
        return await cancel(update, context)
    next_prompt, next_key = questions[last_question]
    context.user_data['last_question'] = next_key
    await update.message.reply_text(next_prompt, parse_mode='Markdown', reply_markup=batal_keyboard)
    if next_key == 'detail_pekerjaan':
        return DATA_FORM
    return NEW_PROJECT_FORM

# --- ALUR FORM DATA ---
async def get_data_form(update: Update, context: ContextTypes.DEFAULT_TYPE) -> int:
    message = update.effective_message
    last_question = context.user_data.get('last_question')
    user_input = message.text
    context.user_data.setdefault('report_data', {})[last_question] = user_input
    questions = {
        'detail_pekerjaan': ('➡️ **Kirim LOKASI Anda:**\nBisa juga ketik manual jika tidak memungkinkan.', 'lokasi'),
    }

    if last_question not in questions:
        return await cancel(update, context)

    next_prompt, next_key = questions[last_question]
    context.user_data['last_question'] = next_key

    if next_key == 'lokasi':
        await message.reply_text(next_prompt, reply_markup=location_keyboard)
        return AWAITING_DATA_LOCATION

    await message.reply_text(next_prompt, parse_mode='Markdown', reply_markup=batal_keyboard)
    return DATA_FORM

async def handle_data_location(update: Update, context: ContextTypes.DEFAULT_TYPE) -> int:
    message = update.effective_message
    if message.location:
        lat, lon = message.location.latitude, message.location.longitude
        context.user_data.setdefault('report_data', {})['lokasi_teks'] = f"Dari Peta"
        context.user_data.setdefault('report_data', {})['koordinat'] = f"{lat}, {lon}"
        context.user_data.setdefault('report_data', {})['gmaps_link'] = f"http://maps.google.com/maps?q={lat},{lon}"
        await message.reply_text("✅ Lokasi dari peta diterima.", reply_markup=ReplyKeyboardRemove())
    else:
        context.user_data.setdefault('report_data', {})['lokasi_teks'] = message.text
        context.user_data.setdefault('report_data', {})['koordinat'] = "-"
        context.user_data.setdefault('report_data', {})['gmaps_link'] = "-"
        await message.reply_text("✅ Lokasi teks diterima.", reply_markup=ReplyKeyboardRemove())
    calendar, step = DetailedTelegramCalendar(min_date=datetime.now().date()).build()
    await message.reply_text("➡️ **Pilih TANGGAL SURVEI:**", reply_markup=calendar)
    return SURVEY_DATE_CALENDAR

async def handle_survey_date(update: Update, context: ContextTypes.DEFAULT_TYPE) -> int:
    query = update.callback_query
    await query.answer()
    result, key, step = DetailedTelegramCalendar().process(query.data)
    if not result and key:
        await query.edit_message_text("➡️ **Pilih TANGGAL SURVEI:**", reply_markup=key)
        return SURVEY_DATE_CALENDAR
    elif result:
        formatted_date = result.strftime("%d-%m-%Y")
        context.user_data.setdefault('report_data', {})['tanggal_survei'] = formatted_date
        await query.edit_message_text(f"✅ Tanggal Survei: {formatted_date}")
        calendar, _ = DetailedTelegramCalendar(min_date=result).build()
        await context.bot.send_message(chat_id=query.message.chat_id, text="➡️ Selanjutnya, pilih **Tanggal Mulai TIME PLAN**:", reply_markup=calendar)
        return TIMELINE_START_CALENDAR

async def handle_timeline_start_date(update: Update, context: ContextTypes.DEFAULT_TYPE) -> int:
    query = update.callback_query
    await query.answer()
    result, key, step = DetailedTelegramCalendar().process(query.data)
    if not result and key:
        await query.edit_message_text("➡️ Pilih **Tanggal Mulai TIME PLAN**:", reply_markup=key)
        return TIMELINE_START_CALENDAR
    elif result:
        context.user_data['timeline_start_date'] = result
        formatted_date = result.strftime("%d %B %Y")
        await query.edit_message_text(f"✅ Tanggal Mulai : {formatted_date}")
        calendar, _ = DetailedTelegramCalendar(min_date=result).build()
        await context.bot.send_message(chat_id=query.message.chat_id, text="➡️ Terakhir, pilih **Tanggal Selesai TIME PLAN**:", reply_markup=calendar)
        return TIMELINE_END_CALENDAR

async def handle_timeline_end_date(update: Update, context: ContextTypes.DEFAULT_TYPE) -> int:
    query = update.callback_query
    await query.answer()
    result, key, step = DetailedTelegramCalendar().process(query.data)
    if not result and key:
        await query.edit_message_text("➡️ Pilih **Tanggal Selesai TIME PLAN**:", reply_markup=key)
        return TIMELINE_END_CALENDAR
    elif result:
        start_date = context.user_data['timeline_start_date']
        end_date = result
        formatted_start = start_date.strftime("%d %B %Y")
        formatted_end = end_date.strftime("%d %B %Y")
        time_plan_string = f"{formatted_start} s/d {formatted_end}"
        context.user_data.setdefault('report_data', {})['time_plan'] = time_plan_string
        await query.edit_message_text(f"✅ Time Plan : {time_plan_string}")

        keyboard = [[InlineKeyboardButton(status, callback_data=f"status_{status}")] for status in STATUS_OPTIONS]
        await context.bot.send_message(
            chat_id=query.message.chat_id,
            text="➡️ **Pilih STATUS PEKERJAAN:**",
            reply_markup=InlineKeyboardMarkup(keyboard)
        )
        return AWAITING_STATUS_PEKERJAAN

async def handle_status_pekerjaan(update: Update, context: ContextTypes.DEFAULT_TYPE) -> int:
    query = update.callback_query
    await query.answer()

    status = query.data.split('_', 1)[1]
    context.user_data.setdefault('report_data', {})['status_pekerjaan'] = status

    await query.edit_message_text(f"✅ Status Pekerjaan diatur ke: **{status}**", parse_mode='Markdown')

    keyboard = [[InlineKeyboardButton(alpro, callback_data=f"alpro_{alpro}")] for alpro in ALPRO_OPTIONS]
    await context.bot.send_message(
        chat_id=query.message.chat_id,
        text="➡️ Selanjutnya, **pilih ALPRO TERDAMPAK:**",
        reply_markup=InlineKeyboardMarkup(keyboard)
    )
    return AWAITING_ALPRO

async def handle_alpro_terdampak(update: Update, context: ContextTypes.DEFAULT_TYPE) -> int:
    """Menyimpan alpro terdampak dan lanjut ke foto."""
    query = update.callback_query
    await query.answer()

    alpro = query.data.split('_', 1)[1]
    context.user_data.setdefault('report_data', {})['alpro'] = alpro

    await query.edit_message_text(f"✅ ALPRO TERDAMPAK diatur ke: **{alpro}**", parse_mode='Markdown')

    context.user_data.setdefault('report_data', {})['photos'] = []
    await context.bot.send_message(
        chat_id=query.message.chat_id,
        text="✅ Data teks lengkap.\n\n➡️ **KIRIM FOTO WAJIB DENGAN KETERANGAN SEPERTI BEFORE/PROGRESS/AFTER jika sudah klik SELESAI**",
        parse_mode='Markdown',
        reply_markup=selesai_keyboard
    )
    return AWAITING_DATA_PHOTO


async def collect_data_photos(update: Update, context: ContextTypes.DEFAULT_TYPE) -> int:
    """Menerima dan memvalidasi foto dengan caption manual."""
    if update.message.text and update.message.text.strip().upper() == 'SELESAI':
        if not context.user_data.get('report_data', {}).get('photos'):
            await update.message.reply_text("Anda harus mengirim minimal satu foto.", reply_markup=selesai_keyboard)
            return AWAITING_DATA_PHOTO
        return await save_data(update, context)

    elif update.message.photo:
        # --- PERUBAHAN: Memeriksa jumlah foto agar tidak lebih dari 100 ---
        if len(context.user_data.get('report_data', {}).get('photos', [])) >= 100:
            await update.message.reply_text("Anda sudah mencapai batas maksimal 100 foto. Tekan 'Selesai' untuk menyimpan.")
            return AWAITING_DATA_PHOTO

        caption = update.message.caption or ""

        rejection_message = (
            "⚠️ **Caption Wajib & Harus Sesuai Format!**\n\n"
            "Harap kirim ulang foto dengan caption yang diawali kata kunci **BEFORE**, **PROGRESS**, atau **AFTER**.\n\n"
            "Contoh:\n`BEFORE Pemasangan tiang baru`"
        )

        if not caption.strip():
            await update.message.reply_text(rejection_message, parse_mode='Markdown')
            return AWAITING_DATA_PHOTO

        first_word = caption.strip().upper().split()[0]
        if first_word not in ["BEFORE", "PROGRESS", "AFTER"]:
            await update.message.reply_text(rejection_message, parse_mode='Markdown')
            return AWAITING_DATA_PHOTO

        photo = update.message.photo[-1]
        file_path = None
        try:
            photo_file = await photo.get_file()
            file_path = photo_file.file_path
        except TimedOut: pass
        except Exception as e: logger.warning(f"Gagal mendapatkan detail file: {e}")

        context.user_data.setdefault('report_data', {}).setdefault('photos', []).append({'id': photo.file_id, 'path': file_path, 'caption': caption})
        await update.message.reply_text(f"✅ Foto ke-{len(context.user_data.get('report_data', {}).get('photos',[]))} diterima.", parse_mode='Markdown')

    return AWAITING_DATA_PHOTO


async def save_data(update: Update, context: ContextTypes.DEFAULT_TYPE) -> int:
    await update.effective_message.reply_text("⏳ Menyimpan data...", reply_markup=ReplyKeyboardRemove())
    form_data = context.user_data.get('report_data', {})
    row_data_map = {
        "ID PROJECT POSJAR": form_data.get('id_project_posjar'),
        "LOKASI/JALAN": form_data.get('lokasi_jalan'),
        "DETAIL PEKERJAAN": form_data.get('detail_pekerjaan'), "DATEL": form_data.get('datel'), "STO": form_data.get('sto'),
        "LOKASI (Teks)": form_data.get('lokasi_teks'), "Koordinat": form_data.get('koordinat'), "Link Google Maps": form_data.get('gmaps_link'),
        "TANGGAL SURVEI": form_data.get('tanggal_survei'), "TIME PLAN": form_data.get('time_plan'),
        "STATUS PEKERJAAN": form_data.get('status_pekerjaan'), "ALPRO TERDAMPAK": form_data.get('alpro')
    }
    photos = form_data.get('photos', [])
    # --- PERUBAHAN: Menyimpan hingga 100 foto ---
    for i, photo_data in enumerate(photos[:100]):
        row_data_map[f"FOTO {i+1} (File ID)"] = "'" + photo_data['id']
        row_data_map[f"Keterangan FOTO {i+1}"] = photo_data.get('caption', '')
        row_data_map[f"Preview FOTO {i+1}"] = f'=IMAGE("{photo_data["path"]}")' if photo_data['path'] else "Gagal membuat preview (timeout)"

    final_row = [row_data_map.get(h, "") for h in HEADER_DATA]
    try:
        ws_data.append_row(final_row, value_input_option='USER_ENTERED')
        await update.effective_message.reply_text(f"✅ Laporan untuk **{form_data.get('lokasi_jalan')}** (ID: {form_data.get('id_project_posjar')}) berhasil disimpan!", parse_mode='Markdown', reply_markup=main_markup)
    except Exception as e:
        logger.error(f"Gagal menyimpan ke Google Sheet: {e}")
        await update.effective_message.reply_text(f"❌ Gagal menyimpan data: {e}", reply_markup=main_markup)
    context.user_data.clear()
    return ConversationHandler.END

# --- ALUR LIHAT LAPORAN ---
async def _send_report_details(chat_id: int, context: ContextTypes.DEFAULT_TYPE, header: list, row: list):
    record = dict(zip(header, row))
    text_message = ""
    
    # Kumpulkan data teks
    for key, value in record.items():
        if value and 'preview' not in key.lower() and 'file id' not in key.lower():
            text_message += f"**{escape_markdown(str(key), version=2)}**: {escape_markdown(str(value), version=2)}\n"
    
    # Kirim pesan teks jika ada isinya
    if text_message:
        await context.bot.send_message(chat_id=chat_id, text=text_message.strip(), parse_mode=ParseMode.MARKDOWN_V2)

    # --- PERUBAHAN: Kumpulkan dan kirim foto dalam media group ---
    media_group = []
    # Loop hingga 100 untuk mencari semua kemungkinan foto
    for i in range(1, 101):
        id_key = f"FOTO {i} (File ID)"
        caption_key = f"Keterangan FOTO {i}"
        
        photo_id = record.get(id_key, '').strip().lstrip("'")
        if photo_id:
            caption = record.get(caption_key, '')
            media_group.append(InputMediaPhoto(media=photo_id, caption=caption))

    if media_group:
        await context.bot.send_message(chat_id=chat_id, text=f"Menampilkan {len(media_group)} foto terlampir:")
        # Kirim foto dalam batch berisi 10 (batas maksimal media group)
        for i in range(0, len(media_group), 10):
            batch = media_group[i:i+10]
            try:
                await context.bot.send_media_group(chat_id=chat_id, media=batch)
            except Exception as e:
                logger.warning(f"Gagal mengirim media group batch: {e}")
                # Fallback: kirim satu per satu jika media group gagal
                for media_item in batch:
                    try:
                        await context.bot.send_photo(chat_id=chat_id, photo=media_item.media, caption=media_item.caption)
                    except Exception as e_single:
                         logger.warning(f"Gagal mengirim foto tunggal dengan ID {media_item.media}: {e_single}")
                         await context.bot.send_message(chat_id=chat_id, text=f"_Gagal memuat foto (ID: {escape_markdown(media_item.media, version=2)})_", parse_mode=ParseMode.MARKDOWN_V2)


async def show_start(update: Update, context: ContextTypes.DEFAULT_TYPE) -> int:
    keyboard = [[InlineKeyboardButton(datel, callback_data=f"showdatel_{datel.replace(' ', '_')}") for datel in STO_MAP.keys()], [InlineKeyboardButton("<< Batal", callback_data="showdatel_cancel")]]
    text = "Lihat Laporan\n\n➡️ **Silakan pilih DATEL:**"
    await update.message.reply_text(text, reply_markup=InlineKeyboardMarkup(keyboard), parse_mode="Markdown")
    return SHOW_SELECT_DATEL

async def show_project_data(update: Update, context: ContextTypes.DEFAULT_TYPE) -> int:
    query = update.callback_query
    await query.answer()
    chosen_lokasi_jalan = query.data.split('_', 1)[1].replace('_', ' ')
    chosen_datel = context.user_data.get('show_filter_datel')
    chosen_sto = context.user_data.get('show_filter_sto')
    await query.edit_message_text(text=f"⏳ Mencari laporan untuk lokasi **{chosen_lokasi_jalan}**...", parse_mode='Markdown')
    chat_id = query.message.chat_id
    try:
        all_data_rows = ws_data.get_all_values()
        header_data, data_rows = all_data_rows[0], all_data_rows[1:]
        lokasi_jalan_idx = header_data.index("LOKASI/JALAN")
        datel_idx = header_data.index("DATEL")
        sto_idx = header_data.index("STO")
        target_data_row = None
        for row in data_rows:
            if len(row) > max(lokasi_jalan_idx, datel_idx, sto_idx):
                if (row[lokasi_jalan_idx] == chosen_lokasi_jalan and row[datel_idx] == chosen_datel and row[sto_idx] == chosen_sto):
                    target_data_row = row
                    break
        if not target_data_row:
            await context.bot.send_message(chat_id=chat_id, text=f"Lokasi/Jalan '{chosen_lokasi_jalan}' tidak ditemukan.")
            return await cancel(update, context)

        await context.bot.send_message(chat_id=chat_id, text=f"--- Laporan untuk: {chosen_lokasi_jalan} ---")
        await _send_report_details(chat_id, context, header_data, target_data_row)

    except Exception as e:
        logger.error(f"Error saat memproses show_project_data: {e}")
        await context.bot.send_message(chat_id=chat_id, text=f"Terjadi kesalahan saat mengambil data: {e}")
    await context.bot.send_message(chat_id=chat_id, text="Pencarian selesai.", reply_markup=main_markup)
    context.user_data.clear()
    return ConversationHandler.END


# --- ALUR KELOLA LAPORAN ---
async def manage_start(update: Update, context: ContextTypes.DEFAULT_TYPE) -> int:
    keyboard = [[InlineKeyboardButton(datel, callback_data=f"managedatel_{datel.replace(' ', '_')}") for datel in STO_MAP.keys()], [InlineKeyboardButton("<< Batal", callback_data="managedatel_cancel")]]
    text = "Kelola/Edit Laporan\n\n➡️ **Silakan pilih DATEL:**"
    await update.message.reply_text(text, reply_markup=InlineKeyboardMarkup(keyboard), parse_mode="Markdown")
    return MANAGE_SELECT_DATEL

async def select_datel_filter(update: Update, context: ContextTypes.DEFAULT_TYPE) -> int:
    query = update.callback_query
    await query.answer()
    prefix = query.data.split('_')[0]
    base_prefix = prefix.replace("datel", "")

    if 'cancel' in query.data:
        return await cancel(update, context)
    if 'back' in query.data:
        if base_prefix == 'show': return await show_start(update, context)
        if base_prefix == 'manage': return await manage_start(update, context)

    chosen_datel = query.data.split('_', 1)[1].replace('_', ' ')
    context.user_data[f'{base_prefix}_filter_datel'] = chosen_datel
    sto_list = STO_MAP.get(chosen_datel, [])
    if not sto_list:
        await query.edit_message_text(f"Tidak ada data STO untuk DATEL {chosen_datel}.")
        return await cancel(update, context)

    sto_prefix = prefix.replace("datel", "sto")
    sto_buttons = [InlineKeyboardButton(sto, callback_data=f"{sto_prefix}_{sto}") for sto in sto_list]
    keyboard = [sto_buttons[i:i + 4] for i in range(0, len(sto_buttons), 4)]
    keyboard.append([InlineKeyboardButton("<< Kembali", callback_data=f"{sto_prefix}_back")])
    await query.edit_message_text(f"✅ DATEL: **{chosen_datel}**\n\n➡️ **Silakan pilih STO:**", reply_markup=InlineKeyboardMarkup(keyboard), parse_mode='Markdown')

    if base_prefix == 'show': return SHOW_SELECT_STO
    if base_prefix == 'manage': return MANAGE_SELECT_STO

async def go_back_to_sto_selection(update: Update, context: ContextTypes.DEFAULT_TYPE) -> int:
    query = update.callback_query
    await query.answer()
    base_prefix = ""
    if 'show_filter_datel' in context.user_data:
        base_prefix = 'show'
    elif 'manage_filter_datel' in context.user_data:
        base_prefix = 'manage'
    else:
        return await cancel(update, context)
    chosen_datel = context.user_data.get(f'{base_prefix}_filter_datel')
    if not chosen_datel:
        return await cancel(update, context)
    sto_list = STO_MAP.get(chosen_datel, [])
    sto_prefix = base_prefix + "sto"
    sto_buttons = [InlineKeyboardButton(sto, callback_data=f"{sto_prefix}_{sto}") for sto in sto_list]
    keyboard = [sto_buttons[i:i + 4] for i in range(0, len(sto_buttons), 4)]
    back_prefix = base_prefix + "datel"
    keyboard.append([InlineKeyboardButton("<< Kembali", callback_data=f"{back_prefix}_back")])
    await query.edit_message_text(
        f"✅ DATEL: **{chosen_datel}**\n\n➡️ **Silakan pilih STO:**",
        reply_markup=InlineKeyboardMarkup(keyboard),
        parse_mode='Markdown'
    )
    if base_prefix == 'show': return SHOW_SELECT_STO
    if base_prefix == 'manage': return MANAGE_SELECT_STO
    return ConversationHandler.END

async def manage_select_sto_filter(update: Update, context: ContextTypes.DEFAULT_TYPE) -> int:
    query = update.callback_query
    await query.answer()
    prefix = query.data.split('_')[0]
    base_prefix = prefix.replace("sto", "")

    if 'back' in query.data:
        datel_prefix = base_prefix + "datel"
        datel_keyboard = [
            [InlineKeyboardButton(datel, callback_data=f"{datel_prefix}_{datel.replace(' ', '_')}") for datel in STO_MAP.keys()],
            [InlineKeyboardButton("<< Kembali", callback_data=f"{datel_prefix}_back")]
        ]
        text = f"➡️ **Silakan pilih DATEL:**"
        await query.edit_message_text(text=text, reply_markup=InlineKeyboardMarkup(datel_keyboard), parse_mode="Markdown")
        if base_prefix == 'show': return SHOW_SELECT_DATEL
        if base_prefix == 'manage': return MANAGE_SELECT_DATEL

    chosen_sto = query.data.split('_', 1)[1]
    context.user_data[f'{base_prefix}_filter_sto'] = chosen_sto
    chosen_datel = context.user_data.get(f'{base_prefix}_filter_datel')
    await query.edit_message_text(f"⏳ Mencari data di DATEL **{chosen_datel}** dan STO **{chosen_sto}**...", parse_mode='Markdown')
    try:
        all_data_rows = ws_data.get_all_values()
        header_data, data_rows = all_data_rows[0], all_data_rows[1:]
        datel_col_idx = header_data.index("DATEL")
        sto_col_idx = header_data.index("STO")
        project_buttons = []
        for i, row in enumerate(data_rows):
            if len(row) > max(datel_col_idx, sto_col_idx) and row[datel_col_idx] == chosen_datel and row[sto_col_idx] == chosen_sto:
                lokasi_jalan_val = row[header_data.index("LOKASI/JALAN")]
                row_num_on_sheet = i + 2
                label = f"{lokasi_jalan_val}"
                if base_prefix == 'show':
                    callback_data = f"showproject_{lokasi_jalan_val.replace(' ', '_')}"
                elif base_prefix == 'manage':
                    callback_data = f"managereport_{row_num_on_sheet}"
                project_buttons.append([InlineKeyboardButton(label, callback_data=callback_data)])

        if not project_buttons:
            await query.edit_message_text(f"Tidak ada data yang ditemukan untuk DATEL **{chosen_datel}** dan STO **{chosen_sto}**.", parse_mode='Markdown')
            return ConversationHandler.END

        project_buttons.append([InlineKeyboardButton("<< Kembali", callback_data=f"{base_prefix}sto_back")])
        await query.edit_message_text(f"✅ DATEL: **{chosen_datel}**\n✅ STO: **{chosen_sto}**\n\nBerikut daftar Lokasi/Jalan yang ditemukan:", reply_markup=InlineKeyboardMarkup(project_buttons), parse_mode='Markdown')

        if base_prefix == 'show': return SHOW_SELECTING_PROJECT
        if base_prefix == 'manage': return MANAGE_SELECTING_PROJECT

    except Exception as e:
        logger.error(f"Gagal memfilter data: {e}")
        await query.edit_message_text("Terjadi kesalahan saat mengambil data.")
        return await cancel(update, context)

async def manage_choose_action(update: Update, context: ContextTypes.DEFAULT_TYPE) -> int:
    query = update.callback_query
    await query.answer()

    row_num = int(query.data.split('_')[1])
    context.user_data['manage_row_num'] = row_num

    keyboard = [
        [InlineKeyboardButton("✏️ Ganti/Timpa Data", callback_data="manageaction_edit")],
        [InlineKeyboardButton("➕ Tambah/Lanjutkan Data", callback_data="manageaction_add")],
        [InlineKeyboardButton("<< Kembali Pilih Lokasi", callback_data="go_back_to_sto_selection")]
    ]
    await query.edit_message_text("Apa yang ingin Anda lakukan dengan laporan ini?", reply_markup=InlineKeyboardMarkup(keyboard))
    return MANAGE_CHOOSING_ACTION

async def manage_select_field(update: Update, context: ContextTypes.DEFAULT_TYPE) -> int:
    query = update.callback_query
    await query.answer()
    action = query.data.split('_')[1]
    context.user_data['manage_action'] = action
    row_num = context.user_data['manage_row_num']
    header_list = HEADER_DATA
    
    keyboard = []
    photo_button_added = False

    for i, col_name in enumerate(header_list):
        if "preview" in col_name.lower():
            continue

        # Logic for "edit" action
        if action == 'edit':
            if "FOTO" in col_name.upper():
                if not photo_button_added:
                    keyboard.append([InlineKeyboardButton("✏️ Ganti/Timpa Foto", callback_data="managephotomenu")])
                    photo_button_added = True
            else:
                keyboard.append([InlineKeyboardButton(col_name, callback_data=f"managefield_{i+1}_{col_name.replace(' ', '_')}")])
        
        # Logic for "add" action
        elif action == 'add':
            if "foto" not in col_name.lower():
                keyboard.append([InlineKeyboardButton(col_name, callback_data=f"managefield_{i+1}_{col_name.replace(' ', '_')}")])

    # For 'add' action, find the next empty photo slot
    if action == 'add':
        try:
            row_values = ws_data.row_values(row_num)
            photo_file_id_cols = [h for h in header_list if 'file id' in h.lower()]
            for col_name in photo_file_id_cols:
                col_idx = header_list.index(col_name)
                if col_idx >= len(row_values) or not row_values[col_idx].strip():
                    slot_name = col_name.replace(" (File ID)", "")
                    button_text = f"➕ Tambah {slot_name}"
                    callback_data = f"managefield_{col_idx + 1}_{col_name.replace(' ', '_')}"
                    keyboard.append([InlineKeyboardButton(button_text, callback_data=callback_data)])
                    break
        except Exception as e:
            logger.error(f"Gagal mencari slot foto kosong: {e}")
    
    if not keyboard:
        await query.edit_message_text("Tidak ada kolom yang bisa dipilih untuk aksi ini.", reply_markup=None)
        return await cancel(update, context)
    
    keyboard.append([InlineKeyboardButton("<< Kembali", callback_data=f"managereport_{row_num}")])
    action_text = "diubah/diganti" if action == 'edit' else "ditambah/dilanjutkan"
    await query.edit_message_text(f"Pilih kolom yang ingin {action_text}:", reply_markup=InlineKeyboardMarkup(keyboard))
    return MANAGE_SELECTING_FIELD

async def manage_photo_menu(update: Update, context: ContextTypes.DEFAULT_TYPE) -> int:
    """Displays buttons for each existing photo that can be edited."""
    query = update.callback_query
    await query.answer()
    
    row_num = context.user_data['manage_row_num']
    header_list = HEADER_DATA
    keyboard = []

    try:
        row_values = ws_data.row_values(row_num)
        photo_file_id_cols = {h: header_list.index(h) for h in header_list if 'file id' in h.lower()}

        for col_name, col_idx in photo_file_id_cols.items():
            if col_idx < len(row_values) and row_values[col_idx].strip():
                slot_name = col_name.replace(" (File ID)", "")
                button_text = f"Ganti {slot_name}"
                callback_data = f"managefield_{col_idx + 1}_{col_name.replace(' ', '_')}"
                keyboard.append([InlineKeyboardButton(button_text, callback_data=callback_data)])
        
        if not keyboard:
            await query.edit_message_text("Tidak ada foto yang bisa diubah dalam laporan ini.", reply_markup=None)
            return await cancel(update, context)

        # Add a back button to return to the field selection menu
        keyboard.append([InlineKeyboardButton("<< Kembali", callback_data=f"manageaction_edit")])
        await query.edit_message_text("Pilih foto mana yang ingin Anda ganti:", reply_markup=InlineKeyboardMarkup(keyboard))
        
        return MANAGE_SELECT_PHOTO_SLOT

    except Exception as e:
        logger.error(f"Gagal membuat menu edit foto: {e}")
        await query.edit_message_text("Terjadi kesalahan saat mengambil data foto.")
        return await cancel(update, context)


async def manage_awaiting_value(update: Update, context: ContextTypes.DEFAULT_TYPE) -> int:
    query = update.callback_query
    await query.answer()
    parts = query.data.split('_')
    col_num, col_name = int(parts[1]), ' '.join(parts[2:]).replace('_', ' ')
    context.user_data['manage_col_num'] = col_num
    context.user_data['manage_col_name'] = col_name
    row_num = context.user_data['manage_row_num']

    if col_name == "STATUS PEKERJAAN":
        keyboard = [[InlineKeyboardButton(status, callback_data=f"updatevalue_{row_num}_{col_num}_{status}")] for status in STATUS_OPTIONS]
        await query.edit_message_text(
            f"Anda akan mengubah **{col_name}**. Silakan pilih status baru:",
            reply_markup=InlineKeyboardMarkup(keyboard),
            parse_mode='Markdown'
        )
        return MANAGE_AWAITING_SELECTION

    if col_name == "ALPRO TERDAMPAK":
        keyboard = [[InlineKeyboardButton(alpro, callback_data=f"updatevalue_{row_num}_{col_num}_{alpro}")] for alpro in ALPRO_OPTIONS]
        await query.edit_message_text(
            f"Anda akan mengubah **{col_name}**. Silakan pilih ALPRO baru:",
            reply_markup=InlineKeyboardMarkup(keyboard),
            parse_mode='Markdown'
        )
        return MANAGE_AWAITING_SELECTION

    if col_name == "TANGGAL SURVEI":
        calendar, _ = DetailedTelegramCalendar().build()
        await query.edit_message_text(f"Anda akan mengubah **{col_name}**. Silakan pilih tanggal baru:", reply_markup=calendar, parse_mode='Markdown')
        return MANAGE_SURVEY_DATE_CALENDAR
    elif col_name == "TIME PLAN":
        calendar, _ = DetailedTelegramCalendar().build()
        await query.edit_message_text(f"Anda akan mengubah **{col_name}**. Silakan pilih **Tanggal Mulai**:", reply_markup=calendar, parse_mode='Markdown')
        return MANAGE_TIMELINE_START_CALENDAR
    elif col_name == "DATEL":
        keyboard = []
        for datel in list(STO_MAP.keys()):
            keyboard.append([InlineKeyboardButton(datel, callback_data=f"updatevalue_{row_num}_{col_num}_{datel.replace(' ', '_')}")])
        await query.edit_message_text(f"Anda akan mengubah data untuk **{col_name}**. Silakan pilih DATEL baru:", reply_markup=InlineKeyboardMarkup(keyboard), parse_mode='Markdown')
        return MANAGE_AWAITING_SELECTION
    elif col_name == "STO":
        try:
            datel_col_index = HEADER_DATA.index("DATEL") + 1
            current_datel = ws_data.cell(row_num, datel_col_index).value
            if current_datel and current_datel in STO_MAP:
                keyboard = []
                for sto in STO_MAP[current_datel]:
                    keyboard.append([InlineKeyboardButton(sto, callback_data=f"updatevalue_{row_num}_{col_num}_{sto}")])
                await query.edit_message_text(f"Anda akan mengubah data untuk **{col_name}**.\nDATEL saat ini adalah **{current_datel}**.\n\nSilakan pilih STO baru:", reply_markup=InlineKeyboardMarkup(keyboard), parse_mode='Markdown')
                return MANAGE_AWAITING_SELECTION
            else:
                await query.edit_message_text(f"Tidak dapat menemukan STO karena data DATEL ('{current_datel}') tidak valid. Harap perbaiki DATEL terlebih dahulu.", reply_markup=None)
                return await cancel(update, context)
        except Exception as e:
            logger.error(f"Error getting STO options: {e}")
            await query.edit_message_text("Terjadi kesalahan saat mengambil daftar STO.", reply_markup=None)
            return await cancel(update, context)
    elif "foto" in col_name.lower() and "file id" in col_name.lower():
        await query.edit_message_text(f"Anda akan mengganti atau menambah **{col_name}**. Silakan kirim foto baru (wajib dengan caption):", parse_mode='Markdown', reply_markup=None)
        await query.message.reply_text("Kirim foto di sini:", reply_markup=batal_keyboard)
        return MANAGE_AWAITING_VALUE
    else:
        action_text = "mengganti" if context.user_data.get('manage_action') == 'edit' else "menambahkan"
        await query.edit_message_text(f"Anda akan **{action_text}** data untuk **{col_name}**.", parse_mode='Markdown', reply_markup=None)
        await query.message.reply_text("Silakan kirim teks/data barunya:", reply_markup=batal_keyboard)
        return MANAGE_AWAITING_VALUE

async def update_cell_value(update: Update, context: ContextTypes.DEFAULT_TYPE) -> int:
    row, col = context.user_data['manage_row_num'], context.user_data['manage_col_num']
    col_name = context.user_data['manage_col_name']
    worksheet = ws_data
    try:
        await update.message.reply_text("⏳ Memperbarui data...", reply_markup=ReplyKeyboardRemove())
        if update.message.photo and "foto" in col_name.lower():
            caption = update.message.caption or ""

            rejection_message = (
                "⚠️ **Caption Wajib & Harus Sesuai Format!**\n\n"
                "Harap kirim ulang foto dengan caption yang diawali kata kunci **BEFORE**, **PROGRESS**, atau **AFTER**.\n\n"
                "Contoh:\n`BEFORE Pemasangan tiang baru`"
            )

            if not caption.strip():
                await update.message.reply_text(rejection_message, parse_mode='Markdown', reply_markup=batal_keyboard)
                return MANAGE_AWAITING_VALUE

            first_word = caption.strip().upper().split()[0]
            if first_word not in ["BEFORE", "PROGRESS", "AFTER"]:
                await update.message.reply_text(rejection_message, parse_mode='Markdown', reply_markup=batal_keyboard)
                return MANAGE_AWAITING_VALUE

            photo = update.message.photo[-1]
            file_path = None
            try:
                photo_file = await photo.get_file()
                file_path = photo_file.file_path
            except TimedOut: pass
            except Exception as e: logger.warning(f"Gagal mendapatkan detail file: {e}")
            worksheet.update_cell(row, col, "'" + photo.file_id)
            worksheet.update_cell(row, col + 1, caption)
            if file_path:
                try:
                    worksheet.update_cell(row, col + 2, f'=IMAGE("{file_path}")')
                except Exception as e:
                    logger.warning(f"Tidak dapat update preview foto: {e}")
        else:
            new_value = update.message.text
            action = context.user_data['manage_action']
            if action == 'add':
                old_value = worksheet.cell(row, col).value or ""
                worksheet.update_cell(row, col, f"{old_value}\n{new_value}".strip())
            else:
                worksheet.update_cell(row, col, new_value)
        await update.message.reply_text("✅ Data berhasil diperbarui!", reply_markup=main_markup)
    except Exception as e:
        logger.error(f"Gagal update cell: {e}")
        await update.message.reply_text(f"❌ Gagal memperbarui data: {e}", reply_markup=main_markup)
    context.user_data.clear()
    return ConversationHandler.END


async def process_value_update_callback(update: Update, context: ContextTypes.DEFAULT_TYPE) -> int:
    query = update.callback_query
    await query.answer()
    parts = query.data.split('_')
    row, col, new_value = int(parts[1]), int(parts[2]), ' '.join(parts[3:]).replace('_', ' ')
    worksheet = ws_data
    try:
        await query.edit_message_text(f"⏳ Memperbarui data menjadi **{new_value}**...", parse_mode='Markdown')
        worksheet.update_cell(row, col, new_value)
        await query.edit_message_text("✅ Data berhasil diperbarui!")
        await context.bot.send_message(chat_id=query.message.chat_id, text="Kembali ke menu utama.", reply_markup=main_markup)
    except Exception as e:
        logger.error(f"Gagal update cell via callback: {e}")
        await query.edit_message_text(f"❌ Gagal memperbarui data: {e}")
        await context.bot.send_message(chat_id=query.message.chat_id, text="Silakan coba lagi dari menu utama.", reply_markup=main_markup)
    context.user_data.clear()
    return ConversationHandler.END

async def manage_handle_survey_date(update: Update, context: ContextTypes.DEFAULT_TYPE) -> int:
    query = update.callback_query
    result, key, step = DetailedTelegramCalendar().process(query.data)
    if not result and key:
        await query.edit_message_text("Silakan pilih tanggal baru:", reply_markup=key)
        return MANAGE_SURVEY_DATE_CALENDAR
    elif result:
        await query.answer()
        formatted_date = result.strftime("%d-%m-%Y")
        row, col = context.user_data['manage_row_num'], context.user_data['manage_col_num']
        worksheet = ws_data
        try:
            worksheet.update_cell(row, col, formatted_date)
            await query.edit_message_text(f"✅ Tanggal berhasil diubah menjadi {formatted_date}.")
            await context.bot.send_message(chat_id=query.message.chat_id, text="Kembali ke menu utama.", reply_markup=main_markup)
        except Exception as e:
            await query.edit_message_text(f"❌ Gagal memperbarui data: {e}")
            await context.bot.send_message(chat_id=query.message.chat_id, text="Silakan coba lagi dari menu utama.", reply_markup=main_markup)
        context.user_data.clear()
        return ConversationHandler.END

async def manage_handle_timeline_start_date(update: Update, context: ContextTypes.DEFAULT_TYPE) -> int:
    query = update.callback_query
    result, key, step = DetailedTelegramCalendar().process(query.data)
    if not result and key:
        await query.edit_message_text("Silakan pilih Tanggal Mulai:", reply_markup=key)
        return MANAGE_TIMELINE_START_CALENDAR
    elif result:
        await query.answer()
        context.user_data['manage_timeline_start'] = result
        formatted_date = result.strftime("%d %B %Y")
        await query.edit_message_text(f"✅ Tanggal Mulai : {formatted_date}")
        calendar, _ = DetailedTelegramCalendar(min_date=result).build()
        await context.bot.send_message(chat_id=query.message.chat_id, text="➡️ Sekarang pilih Tanggal Selesai:", reply_markup=calendar)
        return MANAGE_TIMELINE_END_CALENDAR

async def manage_handle_timeline_end_date(update: Update, context: ContextTypes.DEFAULT_TYPE) -> int:
    query = update.callback_query
    result, key, step = DetailedTelegramCalendar().process(query.data)
    if not result and key:
        await query.edit_message_text("Silakan pilih Tanggal Selesai:", reply_markup=key)
        return MANAGE_TIMELINE_END_CALENDAR
    elif result:
        await query.answer()
        start_date = context.user_data['manage_timeline_start']
        end_date = result
        time_plan_string = f"{start_date.strftime('%d %B %Y')} s/d {end_date.strftime('%d %B %Y')}"
        row, col = context.user_data['manage_row_num'], context.user_data['manage_col_num']
        worksheet = ws_data
        try:
            worksheet.update_cell(row, col, time_plan_string)
            await query.edit_message_text(f"✅ Time Plan berhasil diubah menjadi:\n{time_plan_string}")
            await context.bot.send_message(chat_id=query.message.chat_id, text="Kembali ke menu utama.", reply_markup=main_markup)
        except Exception as e:
            await query.edit_message_text(f"❌ Gagal memperbarui data: {e}")
            await context.bot.send_message(chat_id=query.message.chat_id, text="Silakan coba lagi dari menu utama.", reply_markup=main_markup)
        context.user_data.clear()
        return ConversationHandler.END

# --- MAIN ---
def main() -> None:
    my_persistence = PicklePersistence(filepath="bot_persistence_harian")
    application = Application.builder().token(TOKEN).persistence(my_persistence).build()

    cancel_filter = filters.Regex(r'^(?i)Batal$')
    selesai_filter = filters.Regex(r'^(?i)Selesai$')

    conv_handler = ConversationHandler(
        entry_points=[
            CommandHandler("start", start),
            MessageHandler(filters.Regex("^Input Data$"), start_input),
            MessageHandler(filters.Regex("^Lihat Laporan$"), show_start),
            MessageHandler(filters.Regex("^Kelola/Edit Laporan$"), manage_start),
        ],
        states={
            CHOOSING_MENU: [
                MessageHandler(filters.Regex("^Input Data$"), start_input),
                MessageHandler(filters.Regex("^Lihat Laporan$"), show_start),
                MessageHandler(filters.Regex("^Kelola/Edit Laporan$"), manage_start),
            ],
            SELECT_PROJECT_FOR_INPUT: [
                CallbackQueryHandler(select_project_for_input, pattern="^input_")
            ],
            NEW_PROJECT_SELECT_DATEL: [
                CallbackQueryHandler(back_to_new_project_start, pattern="^newdatel_back$"),
                CallbackQueryHandler(new_project_select_datel, pattern="^newdatel_")
            ],
            NEW_PROJECT_SELECT_STO: [CallbackQueryHandler(new_project_select_sto, pattern="^newsto_")],
            NEW_PROJECT_FORM: [MessageHandler(cancel_filter, cancel), MessageHandler(filters.TEXT & ~filters.COMMAND, new_project_form)],
            DATA_FORM: [MessageHandler(cancel_filter, cancel), MessageHandler(filters.TEXT & ~filters.COMMAND, get_data_form)],
            AWAITING_DATA_LOCATION: [MessageHandler(cancel_filter, cancel), MessageHandler(filters.LOCATION | (filters.TEXT & ~filters.COMMAND), handle_data_location)],
            SURVEY_DATE_CALENDAR: [CallbackQueryHandler(handle_survey_date)],
            TIMELINE_START_CALENDAR: [CallbackQueryHandler(handle_timeline_start_date)],
            TIMELINE_END_CALENDAR: [CallbackQueryHandler(handle_timeline_end_date)],
            AWAITING_STATUS_PEKERJAAN: [CallbackQueryHandler(handle_status_pekerjaan, pattern="^status_")],
            AWAITING_ALPRO: [CallbackQueryHandler(handle_alpro_terdampak, pattern="^alpro_")],
            AWAITING_DATA_PHOTO: [MessageHandler(filters.PHOTO | selesai_filter, collect_data_photos)],

            # Alur Lihat Laporan
            SHOW_SELECT_DATEL: [CallbackQueryHandler(select_datel_filter, pattern="^showdatel_")],
            SHOW_SELECT_STO: [CallbackQueryHandler(manage_select_sto_filter, pattern="^showsto_")],
            SHOW_SELECTING_PROJECT: [
                CallbackQueryHandler(go_back_to_sto_selection, pattern="^go_back_to_sto_selection$"),
                CallbackQueryHandler(show_project_data, pattern="^showproject_")
            ],

            # Alur Kelola Laporan
            MANAGE_SELECT_DATEL: [CallbackQueryHandler(select_datel_filter, pattern="^managedatel_")],
            MANAGE_SELECT_STO: [CallbackQueryHandler(manage_select_sto_filter, pattern="^managesto_")],
            MANAGE_SELECTING_PROJECT: [
                CallbackQueryHandler(go_back_to_sto_selection, pattern="^go_back_to_sto_selection$"),
                CallbackQueryHandler(manage_choose_action, pattern="^managereport_")
            ],
            MANAGE_CHOOSING_ACTION: [
                CallbackQueryHandler(manage_select_field, pattern="^manageaction_"),
                CallbackQueryHandler(go_back_to_sto_selection, pattern="^go_back_to_sto_selection$")
            ],
            MANAGE_SELECTING_FIELD: [
                CallbackQueryHandler(manage_choose_action, pattern="^managereport_"),
                CallbackQueryHandler(manage_photo_menu, pattern="^managephotomenu$"), # Handler for the new photo menu
                CallbackQueryHandler(manage_awaiting_value, pattern="^managefield_")
            ],
            MANAGE_SELECT_PHOTO_SLOT: [ # New state to handle photo slot selection
                CallbackQueryHandler(manage_select_field, pattern="^manageaction_edit"), # Back button
                CallbackQueryHandler(manage_awaiting_value, pattern="^managefield_")
            ],
            MANAGE_AWAITING_VALUE: [MessageHandler(cancel_filter, cancel), MessageHandler((filters.TEXT | filters.PHOTO) & ~filters.COMMAND, update_cell_value)],
            MANAGE_AWAITING_SELECTION: [CallbackQueryHandler(process_value_update_callback, pattern="^updatevalue_")],
            MANAGE_SURVEY_DATE_CALENDAR: [CallbackQueryHandler(manage_handle_survey_date)],
            MANAGE_TIMELINE_START_CALENDAR: [CallbackQueryHandler(manage_handle_timeline_start_date)],
            MANAGE_TIMELINE_END_CALENDAR: [CallbackQueryHandler(manage_handle_timeline_end_date)],
        },
        fallbacks=[CommandHandler("batal", cancel), MessageHandler(cancel_filter, cancel), CommandHandler("start", start)],
        name="daily_reporting_conversation",
        persistent=True,
    )
    application.add_handler(conv_handler)
    logger.info("Bot berjalan dengan model pelaporan harian...")
    application.run_polling()

if __name__ == "__main__":
    if ws_data is not None:
        main()
    else:
        logger.error("Tidak dapat menjalankan bot karena koneksi ke Google Sheet gagal atau worksheet tidak ditemukan.")