// src/pages/MyTicket.jsx (FINAL - client-side pagination like AdminTickets)
// ✅ Messages: نمایش attachment داخل چت (audio player / image preview / download card)
// ✅ Reply: ارسال attachment + ضبط صدا مثل NewTicket
// ✅ محدودیت‌ها: ضبط 5 دقیقه + حجم 5MB برای upload/record
// ✅ JSON اگر فایل نبود / multipart اگر فایل بود
// ✅ Toast + RefreshGuard حفظ شد
// ✅ Pagination (frontend): page/pageSize + prev/next + reset on search

import React, { useEffect, useMemo, useRef, useState } from "react";
import { Axiosinstance } from "../services/auth/Axiosinstance";
import { TbPhone, TbPlus, TbPaperclip, TbX, TbMicrophone, TbPlayerStop, TbTrash } from "react-icons/tb";
import { LuChevronLeft, LuSend, LuRefreshCcw, LuSearch, LuMessageCircle, LuExternalLink } from "react-icons/lu";
import { useNavigate } from "react-router";
import { useToast } from "../components/ToastProvider";

/* ---------------- constants ---------------- */
const MAX_REPLY_CHARS = 10000;
const MAX_UPLOAD_BYTES = 5 * 1024 * 1024; // 5MB
const MAX_RECORD_SECONDS = 5 * 60; // 5 دقیقه
const MIN_AUDIO_BYTES = 1200;

// ✅ pagination like admin
const DEFAULT_PAGE_SIZE = 6;

function cn(...arr) {
  return arr.filter(Boolean).join(" ");
}

function formatDate(iso) {
  if (!iso) return "-";
  try {
    return new Date(iso).toLocaleString("fa-IR");
  } catch {
    return iso;
  }
}

function bytesToHuman(bytes) {
  if (!Number.isFinite(bytes)) return "-";
  const units = ["B", "KB", "MB", "GB", "TB"];
  let b = bytes;
  let i = 0;
  while (b >= 1024 && i < units.length - 1) {
    b /= 1024;
    i++;
  }
  return `${b.toFixed(i === 0 ? 0 : 2)} ${units[i]}`;
}

function formatTime(sec) {
  const s = Math.max(0, Math.floor(sec));
  const mm = String(Math.floor(s / 60)).padStart(2, "0");
  const ss = String(s % 60).padStart(2, "0");
  return `${mm}:${ss}`;
}

const getErrMsg = (e, fallback = "خطا") => {
  const d = e?.response?.data;
  if (!d) return e?.message || fallback;
  if (typeof d === "string") return d;
  if (d.detail) return d.detail;
  if (Array.isArray(d.body) && d.body[0]) return d.body[0];
  if (Array.isArray(d.attachment) && d.attachment[0]) return d.attachment[0];
  try {
    return JSON.stringify(d);
  } catch {
    return fallback;
  }
};

/* ---------------- meta helpers ---------------- */
function statusMeta(status) {
  const s = (status || "").toLowerCase();
  if (s === "open")
    return { label: "باز", cls: "border-yellow-500/30 bg-yellow-500/10 text-yellow-700 dark:text-yellow-200" };
  if (s === "pending")
    return { label: "در انتظار", cls: "border-yellow-500/30 bg-yellow-500/10 text-yellow-700 dark:text-yellow-200" };
  if (s === "answered")
    return { label: "پاسخ داده شده", cls: "border-emerald-500/30 bg-emerald-500/10 text-emerald-700 dark:text-emerald-200" };
  if (s === "closed")
    return { label: "بسته شده", cls: "border-black/10 bg-black/5 text-black/60 dark:border-white/10 dark:bg-white/10 dark:text-white/70" };
  return { label: status || "-", cls: "border-black/10 bg-black/5 text-black/60 dark:border-white/10 dark:bg-white/10 dark:text-white/70" };
}

function priorityMeta(priority) {
  const p = (priority || "").toLowerCase();
  if (p === "urgent")
    return { label: "فوری", cls: "border-red-500/30 bg-red-500/10 text-red-700 dark:text-red-200" };
  if (p === "high")
    return { label: "زیاد", cls: "border-orange-500/30 bg-orange-500/10 text-orange-700 dark:text-orange-200" };
  if (p === "medium" || p === "normal")
    return { label: p === "medium" ? "متوسط" : "عادی", cls: "border-yellow-500/30 bg-yellow-500/10 text-yellow-700 dark:text-yellow-200" };
  return { label: "کم", cls: "border-emerald-500/30 bg-emerald-500/10 text-emerald-700 dark:text-emerald-200" };
}

/* ---------------- attachment detection ---------------- */
function getFileNameFromUrl(url = "") {
  try {
    const u = new URL(url);
    const last = u.pathname.split("/").filter(Boolean).pop();
    return decodeURIComponent(last || "attachment");
  } catch {
    const parts = String(url).split("/").filter(Boolean);
    return decodeURIComponent(parts[parts.length - 1] || "attachment");
  }
}

function getExt(name = "") {
  const n = String(name).toLowerCase();
  const i = n.lastIndexOf(".");
  return i >= 0 ? n.slice(i + 1) : "";
}

function guessKindFromUrl(url = "") {
  const name = getFileNameFromUrl(url);
  const ext = getExt(name);

  const audioExt = ["webm", "ogg", "mp3", "wav", "m4a", "aac", "flac", "opus"];
  const imgExt = ["png", "jpg", "jpeg", "webp", "gif", "bmp", "svg"];
  const pdfExt = ["pdf"];

  if (audioExt.includes(ext)) return "audio";
  if (imgExt.includes(ext)) return "image";
  if (pdfExt.includes(ext)) return "pdf";
  return "file";
}

/* ---------------- Audio Recorder helpers ---------------- */
function pickBestRecorderMime() {
  const MR = window.MediaRecorder;
  if (!MR?.isTypeSupported) return { mimeType: "", ext: "webm" };

  const list = [
    { mt: "audio/webm;codecs=opus", ext: "webm" },
    { mt: "audio/webm", ext: "webm" },
    { mt: "audio/ogg;codecs=opus", ext: "ogg" },
    { mt: "audio/ogg", ext: "ogg" },
  ];
  for (const it of list) {
    try {
      if (MR.isTypeSupported(it.mt)) return { mimeType: it.mt, ext: it.ext };
    } catch {}
  }
  return { mimeType: "", ext: "webm" };
}

/* ---------------- Reply Attachment UI (Upload + Record) ---------------- */
function ReplyAttachmentBar({
  disabled,
  file,
  onPickFile,
  onClearFile,

  canRecord,
  isRecording,
  recordSec,
  onStartRecording,
  onStopRecording,
  onDiscardRecording,
}) {
  const inputRef = useRef(null);

  const openPicker = () => {
    if (disabled) return;
    inputRef.current?.click();
  };

  return (
    <div className="grid gap-3">
      <div className="flex items-center justify-between flex-wrap gap-2">
        <div className="text-xs text-black/60 dark:text-white/70">
          پیوست اختیاری • محدودیت حجم: <b className="text-black dark:text-white">۵MB</b> • ضبط: <b className="text-black dark:text-white">۵ دقیقه</b>
        </div>

        <div className="flex items-center gap-2 flex-wrap">
          <button
            type="button"
            onClick={openPicker}
            disabled={disabled || isRecording}
            className={cn(
              "px-3 py-2 rounded-xl text-xs font-bold border border-black/10 dark:border-white/10",
              "bg-white/70 dark:bg-white/10 hover:bg-black/5 dark:hover:bg-white/10 transition flex items-center gap-2",
              (disabled || isRecording) ? "opacity-60 cursor-not-allowed" : ""
            )}
            title={isRecording ? "اول ضبط را متوقف کن" : "انتخاب فایل"}
          >
            <TbPaperclip className="w-4 h-4" />
            انتخاب فایل
          </button>

          <button
            type="button"
            disabled={disabled || !canRecord || isRecording}
            onClick={onStartRecording}
            className={cn(
              "px-3 py-2 rounded-xl text-xs font-bold border border-black/10 dark:border-white/10",
              "bg-white/70 dark:bg-white/10 hover:bg-black/5 dark:hover:bg-white/10 transition flex items-center gap-2",
              (disabled || !canRecord || isRecording) ? "opacity-60 cursor-not-allowed" : ""
            )}
            title={!canRecord ? "مرورگر شما ضبط صدا را پشتیبانی نمی‌کند" : "شروع ضبط صدا"}
          >
            <TbMicrophone className="w-4 h-4" />
            ضبط صدا
          </button>

          <button
            type="button"
            disabled={disabled || !isRecording}
            onClick={onStopRecording}
            className={cn(
              "px-3 py-2 rounded-xl text-xs font-bold border border-black/10 dark:border-white/10",
              "bg-white/70 dark:bg-white/10 hover:bg-red-500/10 transition flex items-center gap-2",
              (disabled || !isRecording) ? "opacity-60 cursor-not-allowed" : ""
            )}
            title="توقف ضبط"
          >
            <TbPlayerStop className="w-4 h-4 text-red-600 dark:text-red-300" />
            توقف
          </button>

          <button
            type="button"
            disabled={disabled || (!file && !isRecording)}
            onClick={() => {
              if (isRecording) onDiscardRecording?.();
              else onClearFile?.();
            }}
            className={cn(
              "px-3 py-2 rounded-xl text-xs font-bold border border-black/10 dark:border-white/10",
              "bg-white/70 dark:bg-white/10 hover:bg-red-500/10 transition flex items-center gap-2",
              (disabled || (!file && !isRecording)) ? "opacity-60 cursor-not-allowed" : ""
            )}
            title="حذف"
          >
            <TbTrash className="w-4 h-4 text-red-600 dark:text-red-300" />
            حذف
          </button>

          {isRecording && (
            <span className="text-xs font-bold text-red-600 dark:text-red-300 inline-flex items-center gap-2">
              <span className="w-2 h-2 rounded-full bg-red-500 animate-pulse" />
              در حال ضبط: {formatTime(recordSec)} / {formatTime(MAX_RECORD_SECONDS)}
            </span>
          )}
        </div>
      </div>

      <input
        ref={inputRef}
        type="file"
        className="hidden"
        accept="image/*,.png,.jpg,.jpeg,.webp,.pdf,audio/*,.mp3,.wav,.m4a,.webm,.ogg"
        onChange={(e) => onPickFile?.(e.target.files?.[0] || null)}
      />

      {!!file && (
        <div className="rounded-2xl border border-black/10 dark:border-white/10 bg-white/60 dark:bg-white/5 p-3 flex items-center justify-between gap-3">
          <div className="min-w-0">
            <div className="text-sm font-extrabold text-black dark:text-white truncate">{file.name}</div>
            <div className="mt-1 text-xs text-black/60 dark:text-white/70">
              حجم: <b className="text-black dark:text-white">{bytesToHuman(file.size)}</b> • type:{" "}
              <b className="text-black dark:text-white">{file.type || "-"}</b>
            </div>
          </div>

          <button
            type="button"
            onClick={onClearFile}
            className="px-3 py-2 rounded-xl text-xs font-bold border border-black/10 dark:border-white/10 bg-white/70 dark:bg-white/10 hover:bg-red-500/10 transition flex items-center gap-2"
            title="حذف فایل"
          >
            <TbX className="w-4 h-4 text-red-600 dark:text-red-300" />
            حذف
          </button>
        </div>
      )}
    </div>
  );
}

/* ---------------- message attachment renderer ---------------- */
function AttachmentBubble({ url }) {
  if (!url) return null;

  const kind = guessKindFromUrl(url);
  const name = getFileNameFromUrl(url);

  if (kind === "audio") {
    return (
      <div className="mt-2 rounded-xl border border-black/10 dark:border-white/10 bg-black/5 dark:bg-white/5 p-2">
        <div className="text-[11px] text-black/60 dark:text-white/60 mb-2 truncate flex items-center justify-between gap-2">
          <span className="truncate">{name}</span>
          <a
            href={url}
            target="_blank"
            rel="noreferrer"
            className="inline-flex items-center gap-1 text-[11px] font-bold text-[#0b7bd6] dark:text-[#43BEFF]"
            title="باز کردن/دانلود"
          >
            <LuExternalLink className="w-3.5 h-3.5" />
            دانلود
          </a>
        </div>
        <audio controls preload="metadata" src={url} className="w-full" />
      </div>
    );
  }

  if (kind === "image") {
    return (
      <div className="mt-2 rounded-xl border border-black/10 dark:border-white/10 bg-black/5 dark:bg-white/5 p-2">
        <a href={url} target="_blank" rel="noreferrer" className="block">
          <img
            src={url}
            alt={name}
            className="w-full max-h-[260px] object-contain rounded-lg border border-black/10 dark:border-white/10 bg-white/50 dark:bg-white/5"
            loading="lazy"
          />
        </a>
        <div className="mt-2 flex items-center justify-between gap-2">
          <div className="text-[11px] text-black/60 dark:text-white/60 truncate">{name}</div>
          <a
            href={url}
            target="_blank"
            rel="noreferrer"
            className="inline-flex items-center gap-1 text-[11px] font-bold text-[#0b7bd6] dark:text-[#43BEFF]"
          >
            <LuExternalLink className="w-3.5 h-3.5" />
            باز کردن
          </a>
        </div>
      </div>
    );
  }

  return (
    <div className="mt-2 rounded-xl border border-black/10 dark:border-white/10 bg-black/5 dark:bg-white/5 p-3 flex items-center justify-between gap-3">
      <div className="min-w-0">
        <div className="text-xs font-bold text-black dark:text-white truncate">{name}</div>
        <div className="text-[11px] text-black/60 dark:text-white/60">{kind === "pdf" ? "PDF" : "فایل"}</div>
      </div>
      <a
        href={url}
        target="_blank"
        rel="noreferrer"
        className="px-3 py-2 rounded-xl text-xs font-bold bg-white/70 dark:bg-white/10 border border-black/10 dark:border-white/10 hover:bg-black/5 dark:hover:bg-white/15 transition inline-flex items-center gap-2"
      >
        <LuExternalLink className="w-4 h-4" />
        دانلود
      </a>
    </div>
  );
}

/* ---------------- main component ---------------- */
function MyTicket() {
  const navigate = useNavigate();
  const { success, error: toastError, info, warning } = useToast();

  const [tickets, setTickets] = useState([]);
  const [selectedId, setSelectedId] = useState(null);
  const [selected, setSelected] = useState(null);

  const [loadingList, setLoadingList] = useState(true);
  const [loadingDetail, setLoadingDetail] = useState(false);
  const [sending, setSending] = useState(false);

  const [query, setQuery] = useState("");
  const [replyBody, setReplyBody] = useState("");

  // ✅ pagination state
  const [page, setPage] = useState(1);
  const pageSize = DEFAULT_PAGE_SIZE;

  // ✅ reply attachment state
  const [replyFile, setReplyFile] = useState(null);

  // --- recorder for reply ---
  const canRecord = typeof window !== "undefined" && !!navigator.mediaDevices?.getUserMedia && !!window.MediaRecorder;
  const mediaStreamRef = useRef(null);
  const mediaRecorderRef = useRef(null);
  const chunksRef = useRef([]);
  const recordTickRef = useRef(null);
  const flushRef = useRef(null);
  const stopRequestedRef = useRef(false);

  const [isRecording, setIsRecording] = useState(false);
  const [recordSec, setRecordSec] = useState(0);

  /* ==================== Refresh Guards (anti-spam) ==================== */
  const REFRESH_COOLDOWN_MS = 3000;

  const listInFlightRef = useRef(false);
  const listLastAtRef = useRef(0);
  const listToastLockRef = useRef(0);
  const [listCooldownLeftMs, setListCooldownLeftMs] = useState(0);

  const detailInFlightRef = useRef(false);
  const detailLastAtRef = useRef(0);
  const detailToastLockRef = useRef(0);
  const [detailCooldownLeftMs, setDetailCooldownLeftMs] = useState(0);

  useEffect(() => {
    if (listCooldownLeftMs <= 0) return;
    const t = setInterval(() => {
      const left = Math.max(0, REFRESH_COOLDOWN_MS - (Date.now() - listLastAtRef.current));
      setListCooldownLeftMs(left);
      if (left <= 0) clearInterval(t);
    }, 120);
    return () => clearInterval(t);
  }, [listCooldownLeftMs]);

  useEffect(() => {
    if (detailCooldownLeftMs <= 0) return;
    const t = setInterval(() => {
      const left = Math.max(0, REFRESH_COOLDOWN_MS - (Date.now() - detailLastAtRef.current));
      setDetailCooldownLeftMs(left);
      if (left <= 0) clearInterval(t);
    }, 120);
    return () => clearInterval(t);
  }, [detailCooldownLeftMs]);
  /* ====================================================================== */

  const fetchList = async ({ silent = false } = {}) => {
    setLoadingList(true);
    try {
      const res = await Axiosinstance.get("tickets/");
      setTickets(res.data || []);
      if (!silent) info("لیست تیکت‌ها بروزرسانی شد.", { title: "تیکت‌ها" });
    } catch (e) {
      toastError(getErrMsg(e, "خطا در دریافت تیکت‌ها"), { title: "تیکت‌ها" });
    } finally {
      setLoadingList(false);
    }
  };

  const fetchDetail = async (id, { silent = false } = {}) => {
    if (!id) return;
    setLoadingDetail(true);
    try {
      const res = await Axiosinstance.get(`tickets/${id}/`);
      setSelected(res.data || null);
      if (!silent) info("جزئیات تیکت بروزرسانی شد.", { title: `تیکت #${id}` });
    } catch (e) {
      toastError(getErrMsg(e, "خطا در دریافت جزئیات تیکت"), { title: `تیکت #${id}` });
    } finally {
      setLoadingDetail(false);
    }
  };

  const guardedFetchList = async () => {
    const now = Date.now();

    if (listInFlightRef.current || loadingList) {
      if (now - listToastLockRef.current > 900) {
        listToastLockRef.current = now;
        info("در حال بروزرسانی لیست...", { title: "تیکت‌ها" });
      }
      return;
    }

    const diff = now - listLastAtRef.current;
    if (diff < REFRESH_COOLDOWN_MS) {
      const left = REFRESH_COOLDOWN_MS - diff;
      setListCooldownLeftMs(left);
      if (now - listToastLockRef.current > 900) {
        listToastLockRef.current = now;
        info(`لطفاً ${Math.ceil(left / 1000)} ثانیه صبر کن.`, { title: "بروزرسانی" });
      }
      return;
    }

    listInFlightRef.current = true;
    listLastAtRef.current = now;
    setListCooldownLeftMs(REFRESH_COOLDOWN_MS);

    try {
      await fetchList({ silent: false });
    } finally {
      listInFlightRef.current = false;
    }
  };

  const guardedFetchDetail = async (id) => {
    if (!id) return;
    const now = Date.now();

    if (detailInFlightRef.current || loadingDetail) {
      if (now - detailToastLockRef.current > 900) {
        detailToastLockRef.current = now;
        info("در حال بروزرسانی جزئیات...", { title: `تیکت #${id}` });
      }
      return;
    }

    const diff = now - detailLastAtRef.current;
    if (diff < REFRESH_COOLDOWN_MS) {
      const left = REFRESH_COOLDOWN_MS - diff;
      setDetailCooldownLeftMs(left);
      if (now - detailToastLockRef.current > 900) {
        detailToastLockRef.current = now;
        info(`لطفاً ${Math.ceil(left / 1000)} ثانیه صبر کن.`, { title: "رفرش" });
      }
      return;
    }

    detailInFlightRef.current = true;
    detailLastAtRef.current = now;
    setDetailCooldownLeftMs(REFRESH_COOLDOWN_MS);

    try {
      await fetchDetail(id, { silent: false });
    } finally {
      detailInFlightRef.current = false;
    }
  };

  useEffect(() => {
    fetchList({ silent: true });
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, []);

  // ✅ reset page when search changes
  useEffect(() => {
    setPage(1);
  }, [query]);

  const filtered = useMemo(() => {
    const q = query.trim().toLowerCase();
    return (tickets || [])
      .filter((t) => {
        if (!q) return true;
        const hay = [t?.id, t?.subject, t?.status, t?.priority, t?.owner_email].filter(Boolean).join(" ").toLowerCase();
        return hay.includes(q);
      })
      .sort((a, b) => {
        const da = new Date(a?.last_message_at || a?.updated_at || 0).getTime();
        const db = new Date(b?.last_message_at || b?.updated_at || 0).getTime();
        return db - da;
      });
  }, [tickets, query]);

  // ✅ pagination derived
  const totalCount = filtered.length;
  const totalPages = useMemo(() => Math.max(1, Math.ceil(totalCount / pageSize)), [totalCount, pageSize]);

  useEffect(() => {
    if (page > totalPages) setPage(1);
  }, [page, totalPages]);

  const pagedTickets = useMemo(() => {
    const start = (page - 1) * pageSize;
    return filtered.slice(start, start + pageSize);
  }, [filtered, page, pageSize]);

  const hasPrevPage = page > 1;
  const hasNextPage = page < totalPages;

  const openTicket = async (id) => {
    setSelectedId(id);
    setSelected(null);
    setReplyBody("");
    setReplyFile(null);
    info("در حال باز کردن تیکت...", { title: `تیکت #${id}` });
    await fetchDetail(id, { silent: true });
  };

  const backToList = () => {
    setSelectedId(null);
    setSelected(null);
    setReplyBody("");
    setReplyFile(null);
    fetchList({ silent: true });
    info("بازگشت به لیست تیکت‌ها", { title: "تیکت‌ها" });
  };

  /* ---------------- recorder utils ---------------- */
  const stopTick = () => {
    if (recordTickRef.current) {
      clearInterval(recordTickRef.current);
      recordTickRef.current = null;
    }
  };
  const stopFlush = () => {
    if (flushRef.current) {
      clearInterval(flushRef.current);
      flushRef.current = null;
    }
  };
  const cleanupStreamOnly = () => {
    if (mediaStreamRef.current) {
      try {
        mediaStreamRef.current.getTracks?.().forEach((t) => t.stop());
      } catch {}
    }
    mediaStreamRef.current = null;
  };

  const discardRecording = () => {
    stopTick();
    stopFlush();
    stopRequestedRef.current = false;

    try {
      const mr = mediaRecorderRef.current;
      if (mr && mr.state !== "inactive") {
        try { mr.stop(); } catch {}
      }
    } catch {}

    mediaRecorderRef.current = null;
    chunksRef.current = [];

    cleanupStreamOnly();

    setIsRecording(false);
    setRecordSec(0);
  };

  useEffect(() => {
    return () => discardRecording();
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, []);

  const setReplyFileWithLimit = (f) => {
    if (!f) {
      setReplyFile(null);
      return true;
    }
    if (f.size > MAX_UPLOAD_BYTES) {
      warning(`حجم فایل بیشتر از ۵ مگابایت است (${bytesToHuman(f.size)}).`, { title: "محدودیت حجم" });
      return false;
    }
    setReplyFile(f);
    return true;
  };

  const startRecording = async () => {
    if (!canRecord) {
      warning("مرورگر شما امکان ضبط صدا را پشتیبانی نمی‌کند.", { title: "ضبط صدا" });
      return;
    }
    if (sending || isRecording) return;

    if (replyFile) setReplyFile(null);

    stopRequestedRef.current = false;
    chunksRef.current = [];

    try {
      const stream = await navigator.mediaDevices.getUserMedia({
        audio: { echoCancellation: true, noiseSuppression: true, autoGainControl: true },
      });
      mediaStreamRef.current = stream;

      const { mimeType, ext } = pickBestRecorderMime();

      const mr = new MediaRecorder(
        stream,
        mimeType ? { mimeType, audioBitsPerSecond: 128000 } : { audioBitsPerSecond: 128000 }
      );

      mediaRecorderRef.current = mr;
      setRecordSec(0);
      setIsRecording(true);

      mr.ondataavailable = (ev) => {
        if (ev.data && ev.data.size > 0) chunksRef.current.push(ev.data);
      };

      mr.onerror = (ev) => {
        toastError("خطا در ضبط صدا", { title: "ضبط صدا" });
        console.log("MediaRecorder error:", ev);
      };

      mr.onstop = async () => {
        stopTick();
        stopFlush();
        setIsRecording(false);

        await new Promise((r) => setTimeout(r, 120));

        const chunks = chunksRef.current || [];
        chunksRef.current = [];

        const typeGuess =
          mr.mimeType ||
          chunks.find((c) => c?.type)?.type ||
          (ext === "ogg" ? "audio/ogg" : "audio/webm");

        const blob = new Blob(chunks, { type: typeGuess });

        if (!blob || blob.size < MIN_AUDIO_BYTES) {
          cleanupStreamOnly();
          warning("فایل ضبط‌شده خالی بود یا صدا ضبط نشد. دوباره ضبط کن.", { title: "ضبط صدا" });
          return;
        }

        if (blob.size > MAX_UPLOAD_BYTES) {
          cleanupStreamOnly();
          warning(`حجم صدای ضبط‌شده بیشتر از ۵ مگابایت شد (${bytesToHuman(blob.size)}).`, { title: "محدودیت حجم" });
          return;
        }

        const safeExt = ext || (typeGuess.includes("ogg") ? "ogg" : "webm");
        const fileName = `voice-${new Date().toISOString().replace(/[:.]/g, "-")}.${safeExt}`;
        const f = new File([blob], fileName, { type: blob.type || typeGuess });

        if (setReplyFileWithLimit(f)) {
          success("صدای ضبط‌شده به پیوست Reply اضافه شد ✅", { title: "ضبط صدا" });
        }
        cleanupStreamOnly();
      };

      mr.start();

      flushRef.current = setInterval(() => {
        try {
          const rec = mediaRecorderRef.current;
          if (!rec || rec.state !== "recording") return;
          rec.requestData?.();
        } catch {}
      }, 800);

      recordTickRef.current = setInterval(() => {
        setRecordSec((s) => {
          const next = s + 1;
          if (next >= MAX_RECORD_SECONDS) {
            try {
              info("حداکثر زمان ضبط (۵ دقیقه) رسید. ضبط متوقف شد.", { title: "ضبط صدا" });
              stopRecording();
            } catch {}
          }
          return next;
        });
      }, 1000);

      info("ضبط شروع شد. حداکثر ۵ دقیقه.", { title: "ضبط صدا" });
    } catch (e) {
      discardRecording();
      toastError(getErrMsg(e, "اجازه میکروفون داده نشد یا خطا رخ داد"), { title: "ضبط صدا" });
    }
  };

  const stopRecording = () => {
    if (!isRecording) return;
    const mr = mediaRecorderRef.current;
    if (!mr) return;

    if (stopRequestedRef.current) return;
    stopRequestedRef.current = true;

    try {
      try { mr.requestData?.(); } catch {}
      setTimeout(() => {
        try {
          if (mr.state !== "inactive") mr.stop();
        } catch {
          discardRecording();
        }
      }, 150);
    } catch {
      discardRecording();
    }
  };

  /* ---------------- send reply (JSON or multipart) ---------------- */
  const sendReply = async () => {
    if (!selectedId) return;

    const body = replyBody.trim();
    const hasFile = !!replyFile;

    if (!body) {
      warning("متن پیام را وارد کنید.", { title: "ارسال پیام" });
      return;
    }

    if (isRecording) {
      warning("اول ضبط صدا را متوقف کن، بعد ارسال بزن.", { title: "ضبط صدا" });
      return;
    }

    if (hasFile && replyFile.size > MAX_UPLOAD_BYTES) {
      warning("حجم پیوست بیشتر از ۵ مگابایت است. ارسال انجام نشد.", { title: "محدودیت حجم" });
      return;
    }

    setSending(true);
    try {
      if (hasFile) {
        const fd = new FormData();
        fd.append("body", body);
        fd.append("new_status", "open");
        fd.append("attachment", replyFile);
        await Axiosinstance.post(`tickets/${selectedId}/reply/`, fd);
      } else {
        await Axiosinstance.post(`tickets/${selectedId}/reply/`, { body, new_status: "open" });
      }

      setReplyBody("");
      setReplyFile(null);
      success("پیام ارسال شد ✅", { title: `تیکت #${selectedId}` });

      await fetchDetail(selectedId, { silent: true });
      fetchList({ silent: true });
    } catch (e) {
      toastError(getErrMsg(e, "خطا در ارسال پیام"), { title: `تیکت #${selectedId}` });
    } finally {
      setSending(false);
    }
  };

  const headerCard = "rounded-2xl shadow border border-black/10 dark:border-white/10 bg-white dark:bg-[#16325b]";
  const softCard = "rounded-2xl shadow border border-black/10 dark:border-white/10 bg-[#f7fbff] dark:bg-[#001F3F]";

  const listRefreshDisabled = loadingList || listCooldownLeftMs > 0;
  const listRefreshLabel = loadingList ? "درحال بروزرسانی..." : "بروزرسانی";

  const detailRefreshDisabled = loadingDetail || detailCooldownLeftMs > 0;
  const detailRefreshLabel = loadingDetail ? "درحال رفرش..." : "رفرش";

  return (
    <div className="max-w-7xl w-full dark:text-white">
      <div className="mb-5 rounded-2xl shadow border border-black/10 dark:border-white/10 bg-[#A1E3F910] dark:bg-[#A1E3F910] p-3 sm:p-4">
        <div className="flex flex-col lg:flex-row gap-3 lg:items-center lg:justify-between min-w-0">
          <div className="min-w-0">
            <h1 className="text-lg sm:text-xl font-extrabold">تیکت‌ها</h1>
            <p className="text-xs sm:text-sm text-black/60 dark:text-white/70 truncate">
              لیست درخواست‌های پشتیبانی و گفت‌وگو با تیم
            </p>
          </div>

          <div className="flex items-center gap-2 w-full lg:w-auto">

            <button
              type="button"
              onClick={() => navigate("/newticket")}
              className="px-4 py-2 flex gap-2 items-center justify-center rounded-xl shadow dark:bg-[#001F3F] bg-[#f2f2f2] hover:bg-[#e2e2e2] dark:hover:bg-[#A1E3F919] text-sm transition"
            >
              <TbPlus className="h-5 w-5" /> ایجاد تیکت
            </button>
            <button
              type="button"
              onClick={guardedFetchList}
              className="lg:w-auto w-full px-4 py-2 rounded-xl shadow bg-white dark:bg-[#16325b] border border-black/10 dark:border-white/10 hover:bg-black/5 dark:hover:bg-white/10 text-sm flex items-center justify-center gap-2 transition disabled:opacity-60"
              disabled={listRefreshDisabled}
              title={listCooldownLeftMs > 0 ? `تا ${Math.ceil(listCooldownLeftMs / 1000)} ثانیه دیگر` : "بروزرسانی لیست"}
            >
              <LuRefreshCcw className={cn("w-4 h-4 text-[#43BEFF] dark:text-white", loadingList ? "animate-spin" : "")} />
              {listRefreshLabel}
            </button>
          </div>
        </div>
      </div>

      {/* LIST VIEW */}
      {!selectedId && (
        <div className="mt-5">
          <div className="flex flex-col lg:flex-row gap-3 lg:items-center">
            <div className="flex items-center gap-2 w-full lg:flex-1 rounded-xl shadow bg-[#A1E3F980] dark:bg-[#A1E3F910] px-3 py-3 border border-black/10 dark:border-white/10">
              <LuSearch className="w-6 h-6 opacity-70" />
              <input
                value={query}
                onChange={(e) => setQuery(e.target.value)}
                placeholder="جستجو: موضوع، وضعیت، اولویت..."
                className="bg-transparent outline-none w-full text-black dark:text-white placeholder:text-black/40 dark:placeholder:text-white/40"
              />
            </div>

            {/* ✅ pagination bar like admin */}
            <div className={cn("rounded-xl shadow border border-black/10 dark:border-white/10 bg-white/70 dark:bg-white/5 px-3 py-2 flex items-center justify-between gap-2 w-full lg:w-[280px]")}>
              <span className="text-xs text-black/55 dark:text-white/60">
                صفحه: <b>{page}</b> / <b>{totalPages}</b> • تعداد: <b>{totalCount}</b>
              </span>
              <div className="flex items-center gap-2">
                <button
                  type="button"
                  disabled={!hasPrevPage}
                  onClick={() => setPage((p) => Math.max(1, p - 1))}
                  className="px-3 py-2 rounded-xl border border-black/10 dark:border-white/10 hover:bg-black/5 dark:hover:bg-white/10 transition text-xs disabled:opacity-50"
                >
                  قبلی
                </button>
                <button
                  type="button"
                  disabled={!hasNextPage}
                  onClick={() => setPage((p) => Math.min(totalPages, p + 1))}
                  className="px-3 py-2 rounded-xl border border-black/10 dark:border-white/10 hover:bg-black/5 dark:hover:bg-white/10 transition text-xs disabled:opacity-50"
                >
                  بعدی
                </button>
              </div>
            </div>
          </div>

          <div className={cn(softCard, "mt-4 overflow-hidden")}>
            <div className="hidden md:block px-4 py-3 border-b border-black/10 dark:border-white/10 bg-white/70 dark:bg-white/5">
              <div className="grid grid-cols-12 text-xs font-semibold text-black/60 dark:text-white/70">
                <div className="col-span-1">کد</div>
                <div className="col-span-5">موضوع</div>
                <div className="col-span-2 text-center">وضعیت</div>
                <div className="col-span-1 text-center">اولویت</div>
                <div className="col-span-2 text-center">ایجاد</div>
                <div className="col-span-1 text-center">آخرین</div>
              </div>
            </div>

            <div className="max-h-[520px] overflow-y-auto">
              {loadingList && <div className="p-6 text-sm text-black/60 dark:text-white/70">در حال دریافت...</div>}

              {!loadingList && filtered.length === 0 && (
                <div className="p-10 text-center">
                  <div className="mx-auto w-12 h-12 rounded-2xl shadow bg-[#43BEFF20] dark:bg-white/10 flex items-center justify-center">
                    <LuMessageCircle className="w-6 h-6 text-[#43BEFF] dark:text-white" />
                  </div>
                  <div className="mt-3 font-semibold text-black dark:text-white">تیکتی یافت نشد</div>
                  <div className="mt-1 text-sm text-black/60 dark:text-white/70">
                    می‌تونی یک تیکت جدید بسازی یا عبارت جستجو رو تغییر بدی.
                  </div>
                </div>
              )}

              {!loadingList && filtered.length > 0 && pagedTickets.length === 0 && (
                <div className="p-10 text-center text-sm text-black/60 dark:text-white/70">
                  این صفحه آیتمی ندارد (احتمالاً صفحه بزرگ‌تر از تعداد صفحات است). <button className="underline" onClick={() => setPage(1)}>رفتن به صفحه ۱</button>
                </div>
              )}

              {/* Desktop rows (paged) */}
              {!loadingList &&
                pagedTickets.length > 0 &&
                pagedTickets.map((t) => {
                  const sm = statusMeta(t.status);
                  const pm = priorityMeta(t.priority);

                  return (
                    <button
                      key={t.id}
                      type="button"
                      onClick={() => openTicket(t.id)}
                      className={cn(
                        "hidden md:block w-full bg-[#A1E3F919] text-right px-4 py-4 border-b last:border-b-0",
                        "border-black/10 dark:border-white/10",
                        "hover:bg-white/60 dark:hover:bg-white/5 transition"
                      )}
                    >
                      <div className="grid grid-cols-12 items-center gap-2">
                        <div className="col-span-1 font-semibold text-black dark:text-white">#{t.id}</div>

                        <div className="col-span-5 min-w-0">
                          <div className="font-semibold text-sm truncate text-black dark:text-white">{t.subject || "-"}</div>
                          <div className="text-xs truncate text-black/50 dark:text-white/60">{t.owner_email || ""}</div>
                        </div>

                        <div className="col-span-2 text-center">
                          <span className={cn("inline-flex px-3 py-1 rounded-full border text-xs", sm.cls)}>{sm.label}</span>
                        </div>

                        <div className="col-span-1 text-center">
                          <span className={cn("inline-flex px-3 py-1 rounded-full border text-xs", pm.cls)}>{pm.label}</span>
                        </div>

                        <div className="col-span-2 text-center text-xs text-black/60 dark:text-white/70">{formatDate(t.created_at)}</div>

                        <div className="col-span-1 text-center text-xs text-black/60 dark:text-white/70">{formatDate(t.last_message_at || t.updated_at)}</div>
                      </div>
                    </button>
                  );
                })}

              {/* Mobile cards (paged) */}
              {!loadingList &&
                pagedTickets.length > 0 &&
                pagedTickets.map((t) => {
                  const sm = statusMeta(t.status);
                  const pm = priorityMeta(t.priority);

                  return (
                    <button
                      key={`m-${t.id}`}
                      type="button"
                      onClick={() => openTicket(t.id)}
                      className={cn(
                        "md:hidden w-full text-right p-4 border-b last:border-b-0",
                        "border-black/10 dark:border-white/10",
                        "hover:bg-white/60 dark:hover:bg-white/5 transition"
                      )}
                    >
                      <div className="flex items-start justify-between gap-3">
                        <div className="min-w-0">
                          <div className="font-semibold text-black dark:text-white">
                            #{t.id} — {t.subject || "-"}
                          </div>
                          <div className="text-xs mt-1 text-black/50 dark:text-white/60 truncate">{t.owner_email || ""}</div>
                        </div>

                        <div className="flex flex-col items-end gap-2">
                          <span className={cn("inline-flex px-3 py-1 rounded-full border text-xs", pm.cls)}>{pm.label}</span>
                          <span className={cn("inline-flex px-3 py-1 rounded-full border text-xs", sm.cls)}>{sm.label}</span>
                        </div>
                      </div>

                      <div className="mt-3 grid grid-cols-2 gap-2 text-xs text-black/60 dark:text-white/70">
                        <div>
                          <div className="opacity-70">ایجاد</div>
                          <div>{formatDate(t.created_at)}</div>
                        </div>
                        <div className="text-end">
                          <div className="opacity-70">آخرین پیام</div>
                          <div>{formatDate(t.last_message_at || t.updated_at)}</div>
                        </div>
                      </div>
                    </button>
                  );
                })}
            </div>
          </div>
        </div>
      )}

      {/* DETAIL VIEW */}
      {selectedId && (
        <div className="mt-5">
          <button
            type="button"
            onClick={backToList}
            className="mb-4 px-4 py-2 rounded-xl shadow bg-white dark:bg-[#16325b] border border-black/10 dark:border-white/10 hover:bg-black/5 dark:hover:bg-white/10 text-sm inline-flex items-center gap-2 transition"
          >
            <LuChevronLeft className="w-4 h-4 rotate-180 text-[#43BEFF] dark:text-white" />
            برگشت به لیست
          </button>

          <div className={cn(headerCard, "overflow-hidden")}>
            <div className="p-5 bg-[#A1E3F919] dark:bg-[#A1E3F910] border-b border-black/10 dark:border-white/10">
              <div className="flex flex-col md:flex-row md:items-start md:justify-between gap-4">
                <div className="min-w-0">
                  <div className="text-xs text-black/60 dark:text-white/70">تیکت #{selectedId}</div>
                  <div className="text-lg md:text-xl font-extrabold text-black dark:text-white truncate">{selected?.subject || "…"}</div>
                  <div className="mt-1 text-xs text-black/60 dark:text-white/70">
                    {selected?.owner_email || ""} • ایجاد: {formatDate(selected?.created_at)}
                  </div>
                </div>

                <div className="flex items-center gap-2 flex-wrap">
                  <span className={cn("inline-flex px-3 py-1 rounded-full border text-xs", priorityMeta(selected?.priority).cls)}>
                    {priorityMeta(selected?.priority).label}
                  </span>
                  <span className={cn("inline-flex px-3 py-1 rounded-full border text-xs", statusMeta(selected?.status).cls)}>
                    {statusMeta(selected?.status).label}
                  </span>

                  <button
                    type="button"
                    onClick={() => guardedFetchDetail(selectedId)}
                    className="px-3 py-1.5 rounded-full bg-white/60 dark:bg-white/10 hover:bg-white/80 dark:hover:bg-white/15 border border-black/10 dark:border-white/10 text-xs inline-flex items-center gap-2 transition disabled:opacity-60"
                    disabled={detailRefreshDisabled}
                    title={detailCooldownLeftMs > 0 ? `تا ${Math.ceil(detailCooldownLeftMs / 1000)} ثانیه دیگر` : "رفرش جزئیات"}
                  >
                    <LuRefreshCcw className={cn("w-4 h-4", loadingDetail ? "animate-spin" : "")} />
                    {detailRefreshLabel}
                  </button>
                </div>
              </div>
            </div>

            <div className="p-5 bg-[#f2f2f2] dark:bg-[#001F3F]">
              {loadingDetail && <div className="text-sm text-black/60 dark:text-white/70">در حال دریافت گفتگو...</div>}

              {!loadingDetail && selected?.messages?.length === 0 && (
                <div className="text-sm text-black/60 dark:text-white/70">هنوز پیامی ثبت نشده.</div>
              )}

              {!loadingDetail && (selected?.messages || []).length > 0 && (
                <div className="space-y-3 max-h-[420px] overflow-y-auto pr-1">
                  {selected.messages.map((m) => {
                    const isStaff = !!m.sender_is_staff || !!m.is_from_staff;
                    return (
                      <div key={m.id} className={cn("w-full flex", isStaff ? "justify-end" : "justify-start")}>
                        <div
                          className={cn(
                            "max-w-[92%] md:max-w-[70%] rounded-2xl border p-3",
                            "shadow m-1",
                            isStaff
                              ? "bg-white dark:bg-[#0b2445] border-black/10 dark:border-white/10"
                              : "bg-[#A1E3F980] dark:bg-[#A1E3F919] border-[#43BEFF40] dark:border-white/10"
                          )}
                        >
                          <div className="text-xs opacity-70 mb-1 flex items-center justify-between gap-3">
                            <span className="font-semibold">{isStaff ? "پشتیبانی" : "شما"}</span>
                            <span className="whitespace-nowrap">{formatDate(m.created_at)}</span>
                          </div>

                          <div className="text-sm whitespace-pre-wrap break-words text-black dark:text-white">{m.body}</div>

                          {!!m.attachment && <AttachmentBubble url={m.attachment} />}
                        </div>
                      </div>
                    );
                  })}
                </div>
              )}

              {/* Reply box */}
              <div className="mt-5 pt-5 border-t border-black/10 dark:border-white/10">
                <div className="flex items-center justify-between gap-3 mb-2">
                  <div className="text-sm font-bold text-black dark:text-white">ارسال پیام جدید</div>
                  <div className="text-xs text-black/50 dark:text-white/60">پیوست/ضبط صدا هم می‌تونی بفرستی</div>
                </div>

                <div className="rounded-2xl shadow border border-black/10 dark:border-white/10 bg-white/70 dark:bg-white/5 overflow-hidden">
                  <textarea
                    value={replyBody}
                    onChange={(e) => setReplyBody(e.target.value)}
                    placeholder="پیام خود را بنویسید..."
                    maxLength={MAX_REPLY_CHARS}
                    className="w-full min-h-[120px] bg-transparent outline-none text-sm text-black dark:text-white placeholder:text-black/40 dark:placeholder:text-white/40 p-4"
                    disabled={sending}
                  />

                  <div className="p-3 border-t border-black/10 dark:border-white/10 bg-white/50 dark:bg-white/5 grid gap-3">
                    <ReplyAttachmentBar
                      disabled={sending}
                      file={replyFile}
                      onPickFile={(f) => setReplyFileWithLimit(f)}
                      onClearFile={() => setReplyFile(null)}
                      canRecord={canRecord}
                      isRecording={isRecording}
                      recordSec={recordSec}
                      onStartRecording={startRecording}
                      onStopRecording={stopRecording}
                      onDiscardRecording={() => {
                        discardRecording();
                        setReplyFile(null);
                        info("ضبط حذف شد.", { title: "ضبط صدا" });
                      }}
                    />

                    <div className="flex items-center justify-between gap-3">
                      <div className="text-xs text-black/50 dark:text-white/60">
                        {replyBody.trim().length}/{MAX_REPLY_CHARS}
                        {!!replyFile && (
                          <span className="ml-2">
                            • پیوست: <b className="text-black dark:text-white">{bytesToHuman(replyFile.size)}</b>
                          </span>
                        )}
                      </div>

                      <button
                        type="button"
                        onClick={async () => {
                          if (!replyBody.trim()) {
                            warning("متن پیام را وارد کنید.", { title: "ارسال پیام" });
                            return;
                          }
                          await sendReply();
                        }}
                        disabled={sending || isRecording || !replyBody.trim()}
                        className="px-4 py-2 rounded-xl shadow bg-[#43BEFF] hover:bg-[#34b6ff] text-sm text-white flex items-center gap-2 disabled:opacity-60 transition"
                        title={isRecording ? "اول ضبط را متوقف کن" : "ارسال پیام"}
                      >
                        <LuSend className="w-4 h-4" />
                        {sending ? "در حال ارسال..." : "ارسال پیام"}
                      </button>
                    </div>
                  </div>
                </div>
              </div>

              <div className="mt-3 text-xs text-black/50 dark:text-white/60">
                اگر فایلِ داخل چت پخش نشد، از دکمه «دانلود» کنار آن استفاده کن.
              </div>
            </div>
          </div>
        </div>
      )}
    </div>
  );
}

export default MyTicket;
