// ─────────────────────────────────────────────────────────
// Small helpers (kept on window so other modules can use them)
// ─────────────────────────────────────────────────────────

const STATE_LABEL = {
  disconnected:   'disconnected',
  need_setup:     'needs setup',
  ready:          'ready',
  bridge_offline: 'bridge offline',
};

const fmtClock = (ts) => {
  const d = new Date(ts);
  return d.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit', hour12: false });
};
const fmtClockMs = (ts) => {
  const d = new Date(ts);
  const t = d.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit', second: '2-digit', hour12: false });
  return t + '.' + String(d.getMilliseconds()).padStart(3, '0');
};
const sortChats = (chats) => [...chats].sort((a, b) => {
  const la = a.messages[a.messages.length - 1]?.ts || a.created_at || 0;
  const lb = b.messages[b.messages.length - 1]?.ts || b.created_at || 0;
  return lb - la;
});

window.STATE_LABEL = STATE_LABEL;
window.fmtClock = fmtClock;
window.fmtClockMs = fmtClockMs;
window.sortChats = sortChats;

// ─────────────────────────────────────────────────────────
// StatusPill
// ─────────────────────────────────────────────────────────
function StatusPill({ state }) {
  return (
    <span className={`status-pill ${state}`}>
      <span className="dot" />
      {STATE_LABEL[state] || state}
    </span>
  );
}
window.StatusPill = StatusPill;

// ─────────────────────────────────────────────────────────
// ContactModal — handles both "new chat" and "edit name"
// ─────────────────────────────────────────────────────────
function ContactModal({ mode, initial, onClose, onSave }) {
  // In edit mode, if name == number we treat the existing name as blank.
  const initialName = (initial && initial.name !== initial.number) ? initial.name : '';

  const [number, setNumber] = React.useState(initial?.number || '');
  const [name, setName] = React.useState(initialName);
  const firstField = React.useRef(null);

  React.useEffect(() => { firstField.current?.focus(); }, []);

  const numberEditable = mode !== 'edit';

  const normalise = (s) => {
    const trimmed = s.trim();
    if (!trimmed) return '';
    return trimmed.replace(/[\s-]/g, '');
  };

  const submit = () => {
    const n = normalise(number);
    if (!n) return;
    onSave(n, name.trim() || null);
  };

  const isEdit = mode === 'edit';

  return (
    <div className="modal-bg" onClick={onClose}>
      <div className="modal" onClick={e => e.stopPropagation()}>
        <h3>{isEdit ? 'Edit contact' : 'New chat'}</h3>
        <p>{isEdit
          ? 'Rename this contact, or give a previously unknown number a name.'
          : 'Start a conversation with a phone number. Chats also appear automatically when an SMS or call comes in.'}</p>

        <label>Phone number</label>
        <input
          ref={!isEdit ? firstField : null}
          placeholder="+60149795852"
          value={number}
          onChange={e => setNumber(e.target.value)}
          onKeyDown={e => { if (e.key === 'Enter') submit(); }}
          disabled={!numberEditable}
          style={!numberEditable ? { opacity: 0.7, cursor: 'not-allowed' } : null}
        />

        <label>Name {isEdit ? '' : '(optional)'}</label>
        <input
          ref={isEdit ? firstField : null}
          placeholder="e.g. Mum"
          value={name}
          onChange={e => setName(e.target.value)}
          onKeyDown={e => { if (e.key === 'Enter') submit(); }}
        />

        <div className="modal-actions">
          <button className="btn" onClick={onClose}>cancel</button>
          <button className="btn primary" onClick={submit}
                  disabled={!number.trim() || (isEdit && !name.trim())}>
            {isEdit
              ? <><Icon name="check-pill" size={13} /> save</>
              : <><Icon name="plus" size={13} /> create</>}
          </button>
        </div>
      </div>
    </div>
  );
}
window.ContactModal = ContactModal;

// ─────────────────────────────────────────────────────────
// Sidebar
// ─────────────────────────────────────────────────────────
function Sidebar({ chats, selectedId, onSelect, onDeleteChat, tab, onTab,
                   unreadSettings, bridgeState, onNewChat, dark, onToggleDark,
                   ownNumber }) {
  const [q, setQ] = React.useState('');
  const [ctxMenu, setCtxMenu] = React.useState(null);  // { chat, x, y } | null
  const filtered = React.useMemo(() => {
    if (!q.trim()) return chats;
    const s = q.toLowerCase();
    return chats.filter(c =>
      c.name.toLowerCase().includes(s) ||
      c.number.toLowerCase().includes(s) ||
      c.messages.some(m => (m.body || '').toLowerCase().includes(s))
    );
  }, [q, chats]);

  // Close the right-click / long-press menu on any outside click or touch,
  // scroll, resize, or Esc.
  React.useEffect(() => {
    if (!ctxMenu) return;
    const close = () => setCtxMenu(null);
    const onKey = (e) => { if (e.key === 'Escape') close(); };
    document.addEventListener('mousedown', close);
    document.addEventListener('touchstart', close);
    document.addEventListener('keydown', onKey);
    window.addEventListener('resize', close);
    window.addEventListener('scroll', close, true);
    return () => {
      document.removeEventListener('mousedown', close);
      document.removeEventListener('touchstart', close);
      document.removeEventListener('keydown', onKey);
      window.removeEventListener('resize', close);
      window.removeEventListener('scroll', close, true);
    };
  }, [ctxMenu]);

  const openCtxMenu = (chat, e) => {
    e.preventDefault();
    // Clamp so the menu can't fall off the right edge of the rail.
    const menuW = 180, menuH = 44;
    const x = Math.min(e.clientX, window.innerWidth  - menuW - 8);
    const y = Math.min(e.clientY, window.innerHeight - menuH - 8);
    setCtxMenu({ chat, x, y });
  };

  const confirmDelete = () => {
    if (!ctxMenu) return;
    const chat = ctxMenu.chat;
    setCtxMenu(null);
    const label = chat.name && chat.name !== chat.number
      ? `${chat.name} (${chat.number})` : chat.number;
    if (window.confirm(`Delete ${label}?\n\nAll messages with this contact will be removed.`)) {
      onDeleteChat?.(chat);
    }
  };

  return (
    <aside className="rail">
      <div className="pane-hd">
        <div className="brand">
          <div className="brand-mark">
            <Icon name="logo" size={16} stroke={2} />
          </div>
          <div style={{ minWidth: 0 }}>
            <div className="brand-name">Relay</div>
            <div className="brand-sub">{ownNumber}</div>
          </div>
        </div>
        <StatusPill state={bridgeState} />
      </div>

      <div className="search">
        <div className="search-input">
          <Icon name="search" size={14} />
          <input
            value={q}
            onChange={e => setQ(e.target.value)}
            placeholder="Search messages and numbers"
          />
        </div>
        <button className="icon-btn" title="New chat" onClick={onNewChat}>
          <Icon name="plus" size={16} stroke={2} />
        </button>
      </div>

      <div className="chat-list">
        {filtered.length === 0 && (
          <div style={{ padding: 24, textAlign: 'center', color: 'var(--ink-mute)', fontSize: 12.5 }}>
            {chats.length === 0
              ? <>No chats yet.<br/>They will appear here as messages arrive.</>
              : 'No matches'}
          </div>
        )}
        {filtered.map(c => (
          <ChatListRow
            key={c.number}
            chat={c}
            active={c.number === selectedId}
            onClick={() => onSelect(c.number)}
            onContextMenu={(e) => openCtxMenu(c, e)}
          />
        ))}
      </div>

      {ctxMenu && (
        <div
          className="dropdown"
          role="menu"
          onMouseDown={e => e.stopPropagation()}
          onTouchStart={e => e.stopPropagation()}
          style={{ position: 'fixed', top: ctxMenu.y, left: ctxMenu.x,
                   right: 'auto', minWidth: 200, zIndex: 60 }}>
          <button className="danger" onClick={confirmDelete}
                  style={{ padding: '12px 14px', fontSize: 13.5 }}>
            <Icon name="trash" size={14} />
            Delete chat
          </button>
        </div>
      )}

      <nav className="rail-tabs">
        <button className={`rail-tab ${tab === 'chats' ? 'on' : ''}`} onClick={() => onTab('chats')}>
          <Icon name="chats" size={15} /> Chats
        </button>
        <button className={`rail-tab ${tab === 'settings' ? 'on' : ''}`} onClick={() => onTab('settings')}>
          <Icon name="settings" size={15} /> Bridge
          {unreadSettings ? <span className="badge">{unreadSettings}</span> : null}
        </button>
        <button className="icon-btn" title={dark ? 'Light mode' : 'Dark mode'}
                onClick={onToggleDark} style={{ height: 'auto' }}>
          <Icon name={dark ? 'sun' : 'moon'} size={15} />
        </button>
      </nav>
    </aside>
  );
}
window.Sidebar = Sidebar;

function ChatListRow({ chat, active, onClick, onContextMenu }) {
  const last = chat.messages[chat.messages.length - 1];
  const preview = previewOf(last);

  // Long-press → open the same context menu on touch devices.
  // 500ms hold with <10px drift; the synthetic tap that follows is swallowed.
  const longPressTimer = React.useRef(null);
  const touchStart = React.useRef(null);
  const longPressed = React.useRef(false);

  const cancelLongPress = () => {
    if (longPressTimer.current) {
      clearTimeout(longPressTimer.current);
      longPressTimer.current = null;
    }
  };

  const onTouchStart = (e) => {
    if (e.touches.length !== 1) return;
    const t = e.touches[0];
    touchStart.current = { x: t.clientX, y: t.clientY };
    longPressed.current = false;
    cancelLongPress();
    longPressTimer.current = setTimeout(() => {
      longPressed.current = true;
      if (navigator.vibrate) { try { navigator.vibrate(15); } catch (_) {} }
      onContextMenu?.({
        preventDefault: () => {},
        clientX: t.clientX,
        clientY: t.clientY,
      });
    }, 500);
  };

  const onTouchMove = (e) => {
    if (!touchStart.current || !e.touches.length) return;
    const t = e.touches[0];
    const dx = t.clientX - touchStart.current.x;
    const dy = t.clientY - touchStart.current.y;
    if (Math.hypot(dx, dy) > 10) cancelLongPress();
  };

  const onTouchEnd = () => cancelLongPress();

  const handleClick = (e) => {
    // Swallow the tap that follows a long-press so we don't also open the chat.
    if (longPressed.current) {
      e.preventDefault();
      longPressed.current = false;
      return;
    }
    onClick?.(e);
  };

  return (
    <div className={`chat-row ${active ? 'active' : ''}`}
         onClick={handleClick}
         onContextMenu={onContextMenu}
         onTouchStart={onTouchStart}
         onTouchMove={onTouchMove}
         onTouchEnd={onTouchEnd}
         onTouchCancel={onTouchEnd}>
      <div className={`avatar ${chat.avatar}`}>{chat.initials}</div>
      <div className="chat-meta">
        <div className="chat-name">
          {chat.name}
          {chat.is_short_code ? (
            <span style={{ fontSize: 9.5, fontWeight: 600, letterSpacing: '.06em',
                           color: 'var(--ink-mute)', background: 'var(--panel-2)',
                           padding: '1px 5px', borderRadius: 4 }}>BIZ</span>
          ) : null}
        </div>
        <div className="chat-preview">{preview}</div>
      </div>
      <div className="chat-right">
        <div className="chat-time">{last ? fmtClock(last.ts) : ''}</div>
      </div>
    </div>
  );
}

function previewOf(m) {
  if (!m) return <span style={{ color:'var(--ink-mute)' }}>No messages yet</span>;
  if (m.kind === 'call_in') return <><span className="ico"><Icon name="call-miss" size={11} /></span>Missed call · auto-replied</>;
  if (m.kind === 'sms_out') return <>You: {m.body}</>;
  return m.body;
}

// ─────────────────────────────────────────────────────────
// Conversation
// ─────────────────────────────────────────────────────────
function Conversation({ chat, onSend, bridgeReady, onEditContact }) {
  const [draft, setDraft] = React.useState('');
  const [menuOpen, setMenuOpen] = React.useState(false);
  const bodyRef = React.useRef(null);
  const taRef = React.useRef(null);
  const menuRef = React.useRef(null);

  React.useEffect(() => { setMenuOpen(false); }, [chat?.number]);

  React.useEffect(() => {
    if (!menuOpen) return;
    const onDocClick = (e) => {
      if (menuRef.current && !menuRef.current.contains(e.target)) setMenuOpen(false);
    };
    const onEsc = (e) => { if (e.key === 'Escape') setMenuOpen(false); };
    document.addEventListener('mousedown', onDocClick);
    document.addEventListener('keydown', onEsc);
    return () => {
      document.removeEventListener('mousedown', onDocClick);
      document.removeEventListener('keydown', onEsc);
    };
  }, [menuOpen]);

  React.useEffect(() => {
    if (bodyRef.current) bodyRef.current.scrollTop = bodyRef.current.scrollHeight;
  }, [chat?.number, chat?.messages.length]);

  React.useEffect(() => { setDraft(''); }, [chat?.number]);

  React.useEffect(() => {
    const ta = taRef.current;
    if (!ta) return;
    ta.style.height = 'auto';
    ta.style.height = Math.min(ta.scrollHeight, 120) + 'px';
  }, [draft]);

  if (!chat) {
    return (
      <div className="empty">
        <div className="empty-inner">
          <div className="empty-mark"><Icon name="chats" size={28} stroke={1.5} /></div>
          <h3>Pick a conversation</h3>
          <p>Texts go out through your modem on the other side of the world. Replies and calls land here.</p>
        </div>
      </div>
    );
  }

  const submit = () => {
    const v = draft.trim();
    if (!v) return;
    onSend(chat.number, v);
    setDraft('');
  };
  const handleKey = (e) => {
    if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); submit(); }
  };

  const grouped = groupByDay(chat.messages);

  return (
    <>
      <header className="pane-hd conv-hd">
        <div className={`avatar ${chat.avatar}`} style={{ width: 38, height: 38, fontSize: 13 }}>{chat.initials}</div>
        <div className="who">
          <div className="nm">{chat.name}</div>
          <div className="num">{chat.number}{chat.is_short_code ? ' · short code' : ''}</div>
        </div>
        <span className="status-pill" style={{ marginRight: 4 }}>
          <Icon name="phone-off" size={11} />
          calls auto-reject
        </span>
        <div className="menu-wrap" ref={menuRef}>
          <button className="icon-btn" title="More" onClick={() => setMenuOpen(o => !o)}>
            <Icon name="menu" />
          </button>
          {menuOpen && (
            <div className="dropdown" role="menu">
              <button onClick={() => { setMenuOpen(false); onEditContact?.(chat); }}>
                <Icon name="plus" size={13} />
                {chat.name === chat.number || chat.initials === '?' ? 'Save contact' : 'Edit name'}
              </button>
            </div>
          )}
        </div>
      </header>

      <div className="conv-body" ref={bodyRef}>
        {grouped.length === 0 && (
          <div style={{ textAlign:'center', color:'var(--ink-mute)', fontSize:12.5, padding:'40px 0' }}>
            No messages yet — say hi.
          </div>
        )}
        {grouped.map((grp, gi) => (
          <React.Fragment key={gi}>
            <div className="day-stamp">{grp.label}</div>
            {grp.items.map(m => <Message key={m.id} m={m} contactName={chat.name} />)}
          </React.Fragment>
        ))}
      </div>

      <div className="composer">
        <div className="composer-inner">
          <textarea
            ref={taRef}
            value={draft}
            onChange={e => setDraft(e.target.value)}
            onKeyDown={handleKey}
            placeholder={bridgeReady ? `Message ${chat.name}` : 'Bridge not ready — set it up first'}
            rows={1}
            disabled={!bridgeReady}
          />
          <button className="send" onClick={submit} disabled={!draft.trim() || !bridgeReady} title="Send SMS">
            <Icon name="send" size={14} stroke={2} />
          </button>
        </div>
        <div className="composer-hint">
          <span>SMS via modem/SMS_S · {chat.number}</span>
          <span><kbd>↵</kbd> send · <kbd>⇧↵</kbd> newline</span>
        </div>
      </div>
    </>
  );
}
window.Conversation = Conversation;

function Message({ m, contactName }) {
  if (m.kind === 'call_in') {
    return (
      <>
        <div className="call-card">
          <div className="ci"><Icon name="call-miss" size={15} /></div>
          <div className="ct">
            <b>Missed call from {contactName}</b>
            <span>auto-rejected · {fmtClock(m.ts)}</span>
          </div>
        </div>
        {m.auto && (
          <div className="msg me auto">
            <div className="bubble">{window.AUTO_REPLY_TEXT}</div>
            <div className="msg-foot">
              <span className="msg-tag">auto-reply</span>
              {fmtClock(m.ts)} <span className="msg-tick sent"><Icon name="tick-double" size={12} /></span>
            </div>
          </div>
        )}
      </>
    );
  }

  if (m.kind === 'sms_in') {
    return (
      <div className="msg them">
        <div className="bubble">{m.body}</div>
        <div className="msg-foot">{fmtClock(m.ts)}</div>
      </div>
    );
  }

  // sms_out
  const status = m.status || 'sending';
  return (
    <div className="msg me">
      <div className="bubble">{m.body}</div>
      <div className="msg-foot">
        {fmtClock(m.ts)}
        {status === 'sending' && <span className="msg-tick"><Icon name="clock" size={11} /></span>}
        {status === 'sent'    && <span className="msg-tick sent"><Icon name="tick-double" size={12} /></span>}
        {status === 'failed'  && <span className="msg-tick failed"><Icon name="alert" size={11} /> failed</span>}
      </div>
    </div>
  );
}

function groupByDay(msgs) {
  if (!msgs.length) return [];
  const out = [];
  let cur = null;
  for (const m of msgs) {
    const d = new Date(m.ts);
    const key = d.toDateString();
    if (!cur || cur.key !== key) {
      cur = { key, label: dayLabel(d), items: [] };
      out.push(cur);
    }
    cur.items.push(m);
  }
  return out;
}

function dayLabel(d) {
  const today = new Date();
  const yest = new Date(); yest.setDate(yest.getDate() - 1);
  if (d.toDateString() === today.toDateString()) return 'Today';
  if (d.toDateString() === yest.toDateString()) return 'Yesterday';
  return d.toLocaleDateString([], { weekday: 'long', month: 'short', day: 'numeric' });
}
