/* chat.jsx — แชทถามผู้ดูแล (ปุ่ม "แชท" บนแถบบน · แผงเลื่อนออกด้านขวา เปิดค้างไว้ระหว่างเปลี่ยนหน้าได้ จะได้แคปหน้าจอหน้าไหนก็ได้)
 *   1 ฝ่าย 1 ห้อง: ผู้ใช้ฝ่ายเข้าห้องของฝ่ายตัวเองทันที · ผู้ดูแล/ฝ่ายบัญชีเลือกห้องจากรายชื่อฝ่าย
 *   ผู้ดูแลและฝ่ายบัญชีตอบและเริ่มคุยกับฝ่ายไหนก็ได้ (server ตรวจสิทธิ์ทุกครั้ง)
 *   ภาพหน้าจอ: วางด้วย Ctrl+V ลากมาวาง หรือเลือกไฟล์ · ย่อในเบราว์เซอร์ก่อนส่ง · แสดงผ่าน blob URL เพราะ <img> ส่ง token ไม่ได้
 *   ไม่มี websocket: ถามเซิร์ฟเวอร์เป็นระยะ (รายชื่อห้อง 30 วินาที หรือ 15 วินาทีตอนเปิดแผง · ห้องที่เปิดอยู่ 8 วินาที) และหยุดเมื่อแท็บถูกซ่อน
 *   การถามรายชื่อห้องเป็นสัญญาณ "ออนไลน์" ไปด้วย: ผู้ดูแล/ฝ่ายบัญชีเห็นชื่อคนในแต่ละฝ่ายที่เปิดเว็บอยู่ · ผู้ใช้ฝ่ายเห็นแค่ว่าผู้ดูแลหรือฝ่ายบัญชีออนไลน์ไหม */

const CHAT_POLL_ROOMS = 30000;
const CHAT_POLL_THREAD = 8000;
const CHAT_POLL_OPEN = 15000;              // รายชื่อห้อง + สถานะออนไลน์ ตอนเปิดแผงแชทอยู่
const CHAT_IMAGE_BYTES = 1200 * 1024;      // น้อยกว่าเพดานของ server (1,400 KB)
const CHAT_IMAGE_WIDTHS = [1800, 1400, 1000];
const CHAT_TEXT_LIMIT = 2000;
const CHAT_ROLE = { admin: 'ผู้ดูแล', central: 'ฝ่ายบัญชี' };

/** ย่อภาพก่อนส่ง → data URL: PNG ถ้าเล็กพอ (ตัวหนังสือในภาพหน้าจอคมกว่า) ไม่งั้น JPEG ลดคุณภาพแล้วลดขนาดทีละขั้น */
async function chatShrinkImage(file) {
  const bmp = await createImageBitmap(file);
  const bytes = (url) => ((url.length - url.indexOf(',') - 1) * 3) / 4;
  for (const maxW of CHAT_IMAGE_WIDTHS) {
    const scale = Math.min(1, maxW / bmp.width);
    const canvas = document.createElement('canvas');
    canvas.width = Math.max(1, Math.round(bmp.width * scale));
    canvas.height = Math.max(1, Math.round(bmp.height * scale));
    const ctx = canvas.getContext('2d');
    ctx.fillStyle = '#fff';
    ctx.fillRect(0, 0, canvas.width, canvas.height);
    ctx.drawImage(bmp, 0, 0, canvas.width, canvas.height);
    const png = canvas.toDataURL('image/png');
    if (bytes(png) <= CHAT_IMAGE_BYTES) return png;
    for (const q of [0.85, 0.7, 0.55]) {
      const jpg = canvas.toDataURL('image/jpeg', q);
      if (bytes(jpg) <= CHAT_IMAGE_BYTES) return jpg;
    }
  }
  throw new Error('ภาพใหญ่เกินไป');
}

/** ภาพในข้อความ: กดเพื่อดูภาพเต็ม */
function ChatImage({ id, onLoaded }) {
  const [url, setUrl] = useState(null);
  const [failed, setFailed] = useState(false);
  const [zoom, setZoom] = useState(false);
  useEffect(() => {
    let dead = false, made = null;
    API.chatImage(id).then((u) => { made = u; if (dead) URL.revokeObjectURL(u); else setUrl(u); }).catch(() => setFailed(true));
    return () => { dead = true; if (made) URL.revokeObjectURL(made); };
  }, [id]);
  if (failed) return <div className="small mute">โหลดภาพไม่ได้</div>;
  if (!url) return <div className="chatimg-wait small mute">กำลังโหลดภาพ…</div>;
  return (
    <>
      <button type="button" className="chatimg" title="ดูภาพเต็ม" onClick={() => setZoom(true)}><img src={url} alt="ภาพหน้าจอที่แนบ" onLoad={onLoaded} /></button>
      {zoom && ReactDOM.createPortal(
        <div className="chatzoom" role="dialog" aria-label="ภาพเต็ม" onClick={() => setZoom(false)}><img src={url} alt="ภาพหน้าจอที่แนบ" /></div>, document.body,
      )}
    </>
  );
}

/** ห้องแชท 1 ฝ่าย (parent ใส่ key = รหัสฝ่าย เปลี่ยนห้อง = เริ่มใหม่) */
function ChatThread({ me, dept, onSeen }) {
  const [msgs, setMsgs] = useState(null);
  const [err, setErr] = useState('');
  const [text, setText] = useState('');
  const [image, setImage] = useState(null);       // data URL ที่จะส่ง
  const [busy, setBusy] = useState(false);
  const [drag, setDrag] = useState(false);
  const list = useRef(null);
  const file = useRef(null);
  const lastId = useRef(0);
  const alive = useRef(true);
  const toBottom = () => { const el = list.current; if (el) el.scrollTop = el.scrollHeight; };

  const merge = useCallback((rows) => {
    if (!rows.length) return;
    lastId.current = Math.max(lastId.current, ...rows.map((m) => m.id));
    setMsgs((cur) => {
      const seen = new Set((cur || []).map((m) => m.id));
      return [...(cur || []), ...rows.filter((m) => !seen.has(m.id))];
    });
    API.chatRead(dept, lastId.current).then(onSeen).catch(() => {});
  }, [dept, onSeen]);

  useEffect(() => {
    alive.current = true;
    const pull = (first) => API.chatMessages(dept, first ? 0 : lastId.current).then((r) => {
      if (!alive.current) return;
      if (first) setMsgs((cur) => cur || []);
      merge(r.messages);
    });
    pull(true).catch((e) => setErr(e.message));
    const t = setInterval(() => { if (!document.hidden) pull(false).catch(() => {}); }, CHAT_POLL_THREAD);
    return () => { alive.current = false; clearInterval(t); };
  }, [dept, merge]);
  useEffect(toBottom, [msgs && msgs.length]);

  const addImage = async (f) => {
    if (!f || !/^image\//.test(f.type)) { toast('แนบได้เฉพาะไฟล์ภาพ', 'error'); return; }
    try { setImage(await chatShrinkImage(f)); } catch (e) { toast('แนบภาพนี้ไม่ได้: ' + e.message, 'error'); }
  };
  const onPaste = (e) => {
    const it = Array.from(e.clipboardData.items || []).find((x) => x.type.startsWith('image/'));
    if (it) { e.preventDefault(); addImage(it.getAsFile()); }
  };
  const send = async () => {
    const body = text.trim();
    if ((!body && !image) || busy) return;
    setBusy(true);
    try {
      const r = await API.chatSend(dept, body, image);
      setText(''); setImage(null);
      merge([r.message]);
    } catch (e) { toast(e.message, 'error'); } finally { setBusy(false); }
  };

  return (
    <>
      <div className="chatlist" ref={list}>
        {err && <p className="err">{err}</p>}
        {!msgs && !err && <p className="mute small">กำลังโหลด…</p>}
        {msgs && msgs.length === 0 && (
          <div className="chatempty">
            {me.role === 'dept' ? <>สงสัยตรงไหน พิมพ์ถามได้เลย<br /><span className="small mute">แคปหน้าจอด้วย Win + Shift + S แล้วกด Ctrl + V ในช่องพิมพ์เพื่อแนบภาพ</span></>
              : 'ยังไม่มีข้อความ เริ่มคุยกับฝ่ายนี้ได้เลย'}
          </div>
        )}
        {(msgs || []).map((m) => {
          const mine = m.username === me.username;
          return (
            <div key={m.id} className={'chatmsg' + (mine ? ' mine' : '')}>
              <div className="cm-who">
                <b>{mine ? 'คุณ' : m.name}</b>{CHAT_ROLE[m.role] ? <span className="tag">{CHAT_ROLE[m.role]}</span> : null}<span>{fmtDateTime(m.at)}</span>
              </div>
              <div className="cm-bubble">
                {m.hasImage && <ChatImage id={m.id} onLoaded={toBottom} />}
                {m.text && <div className="cm-text">{m.text}</div>}
              </div>
            </div>
          );
        })}
      </div>
      {msgs && (
        <div className={'chatbox' + (drag ? ' on' : '')} onPaste={onPaste}
          onDragOver={(e) => { e.preventDefault(); setDrag(true); }} onDragLeave={() => setDrag(false)}
          onDrop={(e) => { e.preventDefault(); setDrag(false); addImage(e.dataTransfer.files[0]); }}>
          {image && (
            <div className="chatpreview">
              <img src={image} alt="ภาพที่จะส่ง" />
              <button type="button" className="btn ghost icon-only" aria-label="เอาภาพออก" onClick={() => setImage(null)}><Icon name="x" /></button>
            </div>
          )}
          <textarea rows={2} value={text} maxLength={CHAT_TEXT_LIMIT} placeholder="พิมพ์ข้อความ · วางภาพหน้าจอด้วย Ctrl + V" aria-label="ข้อความ"
            onChange={(e) => setText(e.target.value)}
            onKeyDown={(e) => { if (e.key === 'Enter' && !e.shiftKey && !e.nativeEvent.isComposing) { e.preventDefault(); send(); } }} />
          <div className="chatact">
            <input ref={file} type="file" accept="image/*" hidden onChange={(e) => { addImage(e.target.files[0]); e.target.value = ''; }} />
            <button type="button" className="btn sm" onClick={() => file.current.click()}><Icon name="image" />แนบภาพ</button>
            <span className="small mute grow">Enter = ส่ง · Shift + Enter = ขึ้นบรรทัดใหม่</span>
            <button type="button" className="btn primary sm" disabled={busy || (!text.trim() && !image)} onClick={send}><Icon name="send" />ส่ง</button>
          </div>
        </div>
      )}
    </>
  );
}

/** รายชื่อห้องของผู้ดูแล/ฝ่ายบัญชี: ห้องที่มีข้อความล่าสุดอยู่บน · มีทุกฝ่าย จึงเริ่มคุยกับฝ่ายที่ยังไม่มีข้อความได้ */
function ChatRooms({ data, onOpen }) {
  const [q, setQ] = useState('');
  const needle = q.trim().toLowerCase();
  const rooms = data.rooms.filter((r) => !needle || r.dept.toLowerCase().includes(needle) || r.name.toLowerCase().includes(needle));
  return (
    <div className="chatrooms">
      <div className="search"><Icon name="search" /><input type="text" value={q} placeholder="ค้นชื่อฝ่าย" aria-label="ค้นชื่อฝ่าย" onChange={(e) => setQ(e.target.value)} /></div>
      {rooms.length === 0 && <p className="small mute">ไม่พบฝ่าย</p>}
      {rooms.map((r) => (
        <button key={r.dept} type="button" className="chatroom" onClick={() => onOpen(r.dept)}>
          <span className="cr-body">
            <b>{r.online.length > 0 && <i className="chatdot" />}{r.name || r.dept}</b>
            <span className="small mute">{r.last ? r.last.name + ': ' + (r.last.text || 'ส่งภาพ') : 'ยังไม่มีข้อความ'}</span>
            {r.online.length > 0 && <span className="small cr-online">ออนไลน์: {r.online.join(', ')}</span>}
          </span>
          <span className="cr-side">
            {r.last && <span className="small mute">{fmtDateTime(r.last.at)}</span>}
            {r.unread > 0 && <span className="chatbadge">{r.unread}</span>}
          </span>
        </button>
      ))}
    </div>
  );
}

/** ปุ่มบนแถบบน + แผงแชท (วางที่ document.body ไม่รับสีและ stacking ของแถบบน) */
function ChatButton({ me }) {
  const [data, setData] = useState(null);         // GET /chat
  const [open, setOpen] = useState(false);
  const [room, setRoom] = useState(null);         // ห้องที่ผู้ดูแล/ฝ่ายบัญชีเปิดอยู่
  const isDept = me.role === 'dept';
  const load = useCallback(() => { API.chatRooms().then(setData).catch(() => {}); }, []);
  useEffect(() => {
    load();
    const t = setInterval(() => { if (!document.hidden) load(); }, CHAT_POLL_ROOMS);
    return () => clearInterval(t);
  }, [load]);
  useEffect(() => {
    if (!open) return undefined;
    load();                                       // สถานะออนไลน์ต้องสดกว่าตอนปิดแผงอยู่
    const t = setInterval(() => { if (!document.hidden) load(); }, CHAT_POLL_OPEN);
    const onKey = (e) => { if (e.key === 'Escape' && !document.querySelector('.chatzoom')) setOpen(false); };
    document.addEventListener('keydown', onKey);
    return () => { clearInterval(t); document.removeEventListener('keydown', onKey); };
  }, [open, load]);
  const current = isDept ? me.dept : room;
  const roomInfo = data && current ? data.rooms.find((r) => r.dept === current) : null;
  const unread = data ? data.unread : 0;
  // ผู้ใช้ฝ่ายเห็นว่าผู้ดูแลหรือฝ่ายบัญชีออนไลน์ไหม · ผู้ดูแล/ฝ่ายบัญชีเห็นว่าใครในฝ่ายนั้นออนไลน์
  const status = !data ? null : isDept ? { on: data.staffOnline, text: data.staffOnline ? 'ผู้ดูแลหรือฝ่ายบัญชีออนไลน์อยู่' : 'ผู้ดูแลและฝ่ายบัญชียังไม่ออนไลน์ ฝากข้อความไว้ได้' }
    : roomInfo ? { on: roomInfo.online.length > 0, text: roomInfo.online.length ? 'ออนไลน์: ' + roomInfo.online.join(', ') : 'ตอนนี้ไม่มีใครในฝ่ายนี้ออนไลน์' } : null;
  return (
    <>
      <button className="btn ghost sm chatbtn" onClick={() => setOpen(!open)} title={isDept ? 'ถามผู้ดูแล' : 'แชทกับฝ่าย'}>
        <Icon name="chat" />แชท{unread > 0 && <span className="chatbadge" aria-label={'ยังไม่อ่าน ' + unread + ' ข้อความ'}>{unread}</span>}
      </button>
      {open && ReactDOM.createPortal(
        <div className="chatdrawer noprint" role="dialog" aria-label="แชท">
          <div className="chathead">
            {!isDept && current && <button type="button" className="btn ghost icon-only" aria-label="กลับไปรายชื่อฝ่าย" onClick={() => setRoom(null)}><Icon name="back" /></button>}
            <div className="grow">
              <b>{isDept ? 'ถามผู้ดูแล' : current ? (roomInfo ? roomInfo.name || current : current) : 'แชทกับฝ่าย'}</b>
              <div className="small mute">{isDept ? 'ทุกคนในฝ่าย ผู้ดูแล และฝ่ายบัญชีเห็นข้อความในห้องนี้' : current ? 'ผู้ใช้ของฝ่ายนี้ ผู้ดูแล และฝ่ายบัญชีเห็นข้อความในห้องนี้' : 'เลือกฝ่ายเพื่ออ่านหรือเริ่มคุย'}</div>
              {status && <div className={'chatstatus' + (status.on ? ' on' : '')}><i className="chatdot" />{status.text}</div>}
            </div>
            <button type="button" className="btn ghost icon-only" aria-label="ปิดแชท" onClick={() => setOpen(false)}><Icon name="x" /></button>
          </div>
          {current ? <ChatThread key={current} me={me} dept={current} onSeen={load} />
            : data ? <ChatRooms data={data} onOpen={setRoom} /> : <p className="mute small" style={{ padding: 16 }}>กำลังโหลด…</p>}
        </div>, document.body,
      )}
    </>
  );
}
