import { h, render } from 'preact';
import { useState, useEffect, useRef, useCallback } from 'preact/hooks';

const API = '/api/v1';
const POLL_TIMEOUT = 15;
const RECONNECT_DELAY = 3000;
const MEMBER_REFRESH_INTERVAL = 10000;

function api(path, opts = {}) {
  const token = localStorage.getItem('chat_token');
  const headers = { 'Content-Type': 'application/json', ...(opts.headers || {}) };
  if (token) headers['Authorization'] = `Bearer ${token}`;
  const { signal, ...rest } = opts;
  return fetch(API + path, { ...rest, headers, signal }).then(async r => {
    const data = await r.json().catch(() => null);
    if (!r.ok) throw { status: r.status, data };
    return data;
  });
}

function formatTime(ts) {
  const d = new Date(ts);
  return d.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit', second: '2-digit' });
}

function nickColor(nick) {
  let h = 0;
  for (let i = 0; i < nick.length; i++) h = nick.charCodeAt(i) + ((h << 5) - h);
  const hue = Math.abs(h) % 360;
  return `hsl(${hue}, 70%, 65%)`;
}

function LoginScreen({ onLogin }) {
  const [nick, setNick] = useState('');
  const [error, setError] = useState('');
  const [motd, setMotd] = useState('');
  const [serverName, setServerName] = useState('Chat');
  const inputRef = useRef();

  useEffect(() => {
    api('/server').then(s => {
      if (s.name) setServerName(s.name);
      if (s.motd) setMotd(s.motd);
    }).catch(() => {});
    const saved = localStorage.getItem('chat_token');
    if (saved) {
      api('/state').then(u => onLogin(u.nick)).catch(() => localStorage.removeItem('chat_token'));
    }
    inputRef.current?.focus();
  }, []);

  const submit = async (e) => {
    e.preventDefault();
    setError('');
    try {
      const res = await api('/session', {
        method: 'POST',
        body: JSON.stringify({ nick: nick.trim() })
      });
      localStorage.setItem('chat_token', res.token);
      onLogin(res.nick);
    } catch (err) {
      setError(err.data?.error || 'Connection failed');
    }
  };

  return (
    <div class="login-screen">
      <h1>{serverName}</h1>
      {motd && <div class="motd">{motd}</div>}
      <form onSubmit={submit}>
        <input
          ref={inputRef}
          type="text"
          placeholder="Choose a nickname..."
          value={nick}
          onInput={e => setNick(e.target.value)}
          maxLength={32}
          autoFocus
        />
        <button type="submit">Connect</button>
      </form>
      {error && <div class="error">{error}</div>}
    </div>
  );
}

function Message({ msg }) {
  if (msg.system) {
    return (
      <div class="message system">
        <span class="timestamp">{formatTime(msg.ts)}</span>
        <span class="content">{msg.text}</span>
      </div>
    );
  }
  return (
    <div class="message">
      <span class="timestamp">{formatTime(msg.ts)}</span>
      <span class="nick" style={{ color: nickColor(msg.from) }}>{msg.from}</span>
      <span class="content">{msg.text}</span>
    </div>
  );
}

function App() {
  const [loggedIn, setLoggedIn] = useState(false);
  const [nick, setNick] = useState('');
  const [tabs, setTabs] = useState([{ type: 'server', name: 'Server' }]);
  const [activeTab, setActiveTab] = useState(0);
  const [messages, setMessages] = useState({ Server: [] });
  const [members, setMembers] = useState({});
  const [topics, setTopics] = useState({});
  const [unread, setUnread] = useState({});
  const [input, setInput] = useState('');
  const [joinInput, setJoinInput] = useState('');
  const [connected, setConnected] = useState(true);

  const lastIdRef = useRef(0);
  const seenIdsRef = useRef(new Set());
  const pollAbortRef = useRef(null);
  const tabsRef = useRef(tabs);
  const activeTabRef = useRef(activeTab);
  const nickRef = useRef(nick);
  const messagesEndRef = useRef();
  const inputRef = useRef();

  useEffect(() => { tabsRef.current = tabs; }, [tabs]);
  useEffect(() => { activeTabRef.current = activeTab; }, [activeTab]);
  useEffect(() => { nickRef.current = nick; }, [nick]);

  // Persist joined channels
  useEffect(() => {
    const channels = tabs.filter(t => t.type === 'channel').map(t => t.name);
    localStorage.setItem('chat_channels', JSON.stringify(channels));
  }, [tabs]);

  // Clear unread on tab switch
  useEffect(() => {
    const tab = tabs[activeTab];
    if (tab) setUnread(prev => ({ ...prev, [tab.name]: 0 }));
  }, [activeTab, tabs]);

  const addMessage = useCallback((tabName, msg) => {
    if (msg.id && seenIdsRef.current.has(msg.id)) return;
    if (msg.id) seenIdsRef.current.add(msg.id);
    setMessages(prev => ({
      ...prev,
      [tabName]: [...(prev[tabName] || []), msg]
    }));
    const currentTab = tabsRef.current[activeTabRef.current];
    if (!currentTab || currentTab.name !== tabName) {
      setUnread(prev => ({ ...prev, [tabName]: (prev[tabName] || 0) + 1 }));
    }
  }, []);

  const addSystemMessage = useCallback((tabName, text) => {
    setMessages(prev => ({
      ...prev,
      [tabName]: [...(prev[tabName] || []), {
        id: 'sys-' + Date.now() + '-' + Math.random(),
        ts: new Date().toISOString(),
        text,
        system: true
      }]
    }));
  }, []);

  const refreshMembers = useCallback((channel) => {
    const chName = channel.replace('#', '');
    api(`/channels/${chName}/members`).then(m => {
      setMembers(prev => ({ ...prev, [channel]: m }));
    }).catch(() => {});
  }, []);

  const processMessage = useCallback((msg) => {
    const body = Array.isArray(msg.body) ? msg.body.join('\n') : '';
    const base = { id: msg.id, ts: msg.ts, from: msg.from, to: msg.to, command: msg.command };

    switch (msg.command) {
      case 'PRIVMSG':
      case 'NOTICE': {
        const parsed = { ...base, text: body, system: false };
        const target = msg.to;
        if (target && target.startsWith('#')) {
          addMessage(target, parsed);
        } else {
          const dmPeer = msg.from === nickRef.current ? msg.to : msg.from;
          setTabs(prev => {
            if (!prev.find(t => t.type === 'dm' && t.name === dmPeer)) {
              return [...prev, { type: 'dm', name: dmPeer }];
            }
            return prev;
          });
          addMessage(dmPeer, parsed);
        }
        break;
      }
      case 'JOIN': {
        const text = `${msg.from} has joined ${msg.to}`;
        if (msg.to) addMessage(msg.to, { ...base, text, system: true });
        if (msg.to && msg.to.startsWith('#')) refreshMembers(msg.to);
        break;
      }
      case 'PART': {
        const reason = body ? ': ' + body : '';
        const text = `${msg.from} has left ${msg.to}${reason}`;
        if (msg.to) addMessage(msg.to, { ...base, text, system: true });
        if (msg.to && msg.to.startsWith('#')) refreshMembers(msg.to);
        break;
      }
      case 'QUIT': {
        const reason = body ? ': ' + body : '';
        const text = `${msg.from} has quit${reason}`;
        tabsRef.current.forEach(tab => {
          if (tab.type === 'channel') {
            addMessage(tab.name, { ...base, text, system: true });
          }
        });
        break;
      }
      case 'NICK': {
        const newNick = Array.isArray(msg.body) ? msg.body[0] : body;
        const text = `${msg.from} is now known as ${newNick}`;
        tabsRef.current.forEach(tab => {
          if (tab.type === 'channel') {
            addMessage(tab.name, { ...base, text, system: true });
          }
        });
        if (msg.from === nickRef.current && newNick) setNick(newNick);
        // Refresh members in all channels
        tabsRef.current.forEach(tab => {
          if (tab.type === 'channel') refreshMembers(tab.name);
        });
        break;
      }
      case 'TOPIC': {
        const text = `${msg.from} set the topic: ${body}`;
        if (msg.to) {
          addMessage(msg.to, { ...base, text, system: true });
          setTopics(prev => ({ ...prev, [msg.to]: body }));
        }
        break;
      }
      case '375':
      case '372':
      case '376':
        addMessage('Server', { ...base, text: body, system: true });
        break;
      default:
        addMessage('Server', { ...base, text: body || msg.command, system: true });
    }
  }, [addMessage, refreshMembers]);

  // Long-poll loop
  useEffect(() => {
    if (!loggedIn) return;
    let alive = true;

    const poll = async () => {
      while (alive) {
        try {
          const controller = new AbortController();
          pollAbortRef.current = controller;
          const result = await api(
            `/messages?after=${lastIdRef.current}&timeout=${POLL_TIMEOUT}`,
            { signal: controller.signal }
          );
          if (!alive) break;
          setConnected(true);
          if (result.messages) {
            for (const m of result.messages) processMessage(m);
          }
          if (result.last_id > lastIdRef.current) {
            lastIdRef.current = result.last_id;
          }
        } catch (err) {
          if (!alive) break;
          if (err.name === 'AbortError') continue;
          setConnected(false);
          await new Promise(r => setTimeout(r, RECONNECT_DELAY));
        }
      }
    };

    poll();
    return () => { alive = false; pollAbortRef.current?.abort(); };
  }, [loggedIn, processMessage]);

  // Refresh members for active channel
  useEffect(() => {
    if (!loggedIn) return;
    const tab = tabs[activeTab];
    if (!tab || tab.type !== 'channel') return;
    refreshMembers(tab.name);
    const iv = setInterval(() => refreshMembers(tab.name), MEMBER_REFRESH_INTERVAL);
    return () => clearInterval(iv);
  }, [loggedIn, activeTab, tabs, refreshMembers]);

  // Auto-scroll
  useEffect(() => {
    messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' });
  }, [messages, activeTab]);

  // Focus input on tab change
  useEffect(() => { inputRef.current?.focus(); }, [activeTab]);

  // Fetch topic for active channel
  useEffect(() => {
    if (!loggedIn) return;
    const tab = tabs[activeTab];
    if (!tab || tab.type !== 'channel') return;
    api('/channels').then(channels => {
      const ch = channels.find(c => c.name === tab.name);
      if (ch && ch.topic) setTopics(prev => ({ ...prev, [tab.name]: ch.topic }));
    }).catch(() => {});
  }, [loggedIn, activeTab, tabs]);

  const onLogin = useCallback(async (userNick) => {
    setNick(userNick);
    setLoggedIn(true);
    addSystemMessage('Server', `Connected as ${userNick}`);
    // Auto-rejoin saved channels
    const saved = JSON.parse(localStorage.getItem('chat_channels') || '[]');
    for (const ch of saved) {
      try {
        await api('/messages', { method: 'POST', body: JSON.stringify({ command: 'JOIN', to: ch }) });
        setTabs(prev => {
          if (prev.find(t => t.type === 'channel' && t.name === ch)) return prev;
          return [...prev, { type: 'channel', name: ch }];
        });
      } catch (e) {
        // Channel may not exist anymore
      }
    }
  }, [addSystemMessage]);

  const joinChannel = async (name) => {
    if (!name) return;
    name = name.trim();
    if (!name.startsWith('#')) name = '#' + name;
    try {
      await api('/messages', { method: 'POST', body: JSON.stringify({ command: 'JOIN', to: name }) });
      setTabs(prev => {
        if (prev.find(t => t.type === 'channel' && t.name === name)) return prev;
        return [...prev, { type: 'channel', name }];
      });
      setActiveTab(tabs.length);
      // Load history
      try {
        const hist = await api(`/history?target=${encodeURIComponent(name)}&limit=50`);
        if (Array.isArray(hist)) {
          for (const m of hist) processMessage(m);
        }
      } catch (e) {
        // History may be empty
      }
      setJoinInput('');
    } catch (err) {
      addSystemMessage('Server', `Failed to join ${name}: ${err.data?.error || 'error'}`);
    }
  };

  const partChannel = async (name) => {
    try {
      await api('/messages', { method: 'POST', body: JSON.stringify({ command: 'PART', to: name }) });
    } catch (e) {
      // Ignore
    }
    setTabs(prev => prev.filter(t => !(t.type === 'channel' && t.name === name)));
    setActiveTab(0);
  };

  const closeTab = (idx) => {
    const tab = tabs[idx];
    if (tab.type === 'channel') {
      partChannel(tab.name);
    } else if (tab.type === 'dm') {
      setTabs(prev => prev.filter((_, i) => i !== idx));
      if (activeTab >= idx) setActiveTab(Math.max(0, activeTab - 1));
    }
  };

  const openDM = (targetNick) => {
    setTabs(prev => {
      if (prev.find(t => t.type === 'dm' && t.name === targetNick)) return prev;
      return [...prev, { type: 'dm', name: targetNick }];
    });
    const idx = tabs.findIndex(t => t.type === 'dm' && t.name === targetNick);
    setActiveTab(idx >= 0 ? idx : tabs.length);
  };

  const sendMessage = async () => {
    const text = input.trim();
    if (!text) return;
    setInput('');
    const tab = tabs[activeTab];
    if (!tab || tab.type === 'server') return;

    if (text.startsWith('/')) {
      const parts = text.split(' ');
      const cmd = parts[0].toLowerCase();
      if (cmd === '/join' && parts[1]) { joinChannel(parts[1]); return; }
      if (cmd === '/part') { if (tab.type === 'channel') partChannel(tab.name); return; }
      if (cmd === '/msg' && parts[1] && parts.slice(2).join(' ')) {
        const target = parts[1];
        const body = parts.slice(2).join(' ');
        try {
          await api('/messages', { method: 'POST', body: JSON.stringify({ command: 'PRIVMSG', to: target, body: [body] }) });
          openDM(target);
        } catch (err) {
          addSystemMessage('Server', `DM failed: ${err.data?.error || 'error'}`);
        }
        return;
      }
      if (cmd === '/nick' && parts[1]) {
        try {
          await api('/messages', { method: 'POST', body: JSON.stringify({ command: 'NICK', body: [parts[1]] }) });
        } catch (err) {
          addSystemMessage('Server', `Nick change failed: ${err.data?.error || 'error'}`);
        }
        return;
      }
      if (cmd === '/topic' && tab.type === 'channel') {
        const topicText = parts.slice(1).join(' ');
        try {
          await api('/messages', { method: 'POST', body: JSON.stringify({ command: 'TOPIC', to: tab.name, body: [topicText] }) });
        } catch (err) {
          addSystemMessage('Server', `Topic failed: ${err.data?.error || 'error'}`);
        }
        return;
      }
      addSystemMessage('Server', `Unknown command: ${cmd}`);
      return;
    }

    try {
      await api('/messages', { method: 'POST', body: JSON.stringify({ command: 'PRIVMSG', to: tab.name, body: [text] }) });
    } catch (err) {
      addSystemMessage(tab.name, `Send failed: ${err.data?.error || 'error'}`);
    }
  };

  if (!loggedIn) return <LoginScreen onLogin={onLogin} />;

  const currentTab = tabs[activeTab] || tabs[0];
  const currentMessages = messages[currentTab.name] || [];
  const currentMembers = members[currentTab.name] || [];
  const currentTopic = topics[currentTab.name] || '';

  return (
    <div class="app">
      <div class="tab-bar">
        {!connected && <div class="connection-status">⚠ Reconnecting...</div>}
        {tabs.map((tab, i) => (
          <div
            class={`tab ${i === activeTab ? 'active' : ''}`}
            onClick={() => setActiveTab(i)}
          >
            {tab.type === 'dm' ? `→${tab.name}` : tab.name}
            {unread[tab.name] > 0 && i !== activeTab && (
              <span class="unread-badge">{unread[tab.name]}</span>
            )}
            {tab.type !== 'server' && (
              <span class="close-btn" onClick={(e) => { e.stopPropagation(); closeTab(i); }}>×</span>
            )}
          </div>
        ))}
        <div class="join-dialog">
          <input
            placeholder="#channel"
            value={joinInput}
            onInput={e => setJoinInput(e.target.value)}
            onKeyDown={e => e.key === 'Enter' && joinChannel(joinInput)}
          />
          <button onClick={() => joinChannel(joinInput)}>Join</button>
        </div>
      </div>

      {currentTab.type === 'channel' && currentTopic && (
        <div class="topic-bar" title={currentTopic}>{currentTopic}</div>
      )}

      <div class="content">
        <div class="messages-pane">
          <div class={currentTab.type === 'server' ? 'server-messages' : 'messages'}>
            {currentMessages.map(m => <Message msg={m} />)}
            <div ref={messagesEndRef} />
          </div>
          {currentTab.type !== 'server' && (
            <div class="input-bar">
              <input
                ref={inputRef}
                placeholder={`Message ${currentTab.name}...`}
                value={input}
                onInput={e => setInput(e.target.value)}
                onKeyDown={e => e.key === 'Enter' && sendMessage()}
              />
              <button onClick={sendMessage}>Send</button>
            </div>
          )}
        </div>

        {currentTab.type === 'channel' && (
          <div class="user-list">
            <h3>Users ({currentMembers.length})</h3>
            {currentMembers.map(u => (
              <div class="user" onClick={() => openDM(u.nick)} style={{ color: nickColor(u.nick) }}>
                {u.nick}
              </div>
            ))}
          </div>
        )}
      </div>
    </div>
  );
}

render(<App />, document.getElementById('root'));
