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

const API = '/api/v1';

function api(path, opts = {}) {
  const token = localStorage.getItem('chat_token');
  const headers = { 'Content-Type': 'application/json', ...(opts.headers || {}) };
  if (token) headers['Authorization'] = `Bearer ${token}`;
  return fetch(API + path, { ...opts, headers }).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' });
}

// Nick color hashing
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(() => {});
    // Check for saved token
    const saved = localStorage.getItem('chat_token');
    if (saved) {
      api('/state').then(u => onLogin(u.nick, saved)).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, res.token);
    } 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 }) {
  return (
    <div class={`message ${msg.system ? 'system' : ''}`}>
      <span class="timestamp">{formatTime(msg.createdAt)}</span>
      <span class="nick" style={{ color: msg.system ? undefined : nickColor(msg.nick) }}>{msg.nick}</span>
      <span class="content">{msg.content}</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: [] }); // keyed by tab name
  const [members, setMembers] = useState({}); // keyed by channel name
  const [input, setInput] = useState('');
  const [joinInput, setJoinInput] = useState('');
  const [lastMsgId, setLastMsgId] = useState(0);
  const messagesEndRef = useRef();
  const inputRef = useRef();
  const pollRef = useRef();

  const addMessage = useCallback((tabName, msg) => {
    setMessages(prev => ({
      ...prev,
      [tabName]: [...(prev[tabName] || []), msg]
    }));
  }, []);

  const addSystemMessage = useCallback((tabName, text) => {
    addMessage(tabName, {
      id: Date.now(),
      nick: '*',
      content: text,
      createdAt: new Date().toISOString(),
      system: true
    });
  }, [addMessage]);

  const onLogin = useCallback((userNick, token) => {
    setNick(userNick);
    setLoggedIn(true);
    addSystemMessage('server', `Connected as ${userNick}`);
    // Fetch server info
    api('/server').then(s => {
      if (s.motd) addSystemMessage('server', `MOTD: ${s.motd}`);
    }).catch(() => {});
  }, [addSystemMessage]);

  // Poll for new messages
  useEffect(() => {
    if (!loggedIn) return;
    let alive = true;
    const poll = async () => {
      try {
        const msgs = await api(`/messages?after=${lastMsgId}`);
        if (!alive) return;
        let maxId = lastMsgId;
        for (const msg of msgs) {
          if (msg.id > maxId) maxId = msg.id;
          if (msg.isDm) {
            const dmTab = msg.nick === nick ? msg.dmTarget : msg.nick;
            // Ensure DM tab exists
            setTabs(prev => {
              if (!prev.find(t => t.type === 'dm' && t.name === dmTab)) {
                return [...prev, { type: 'dm', name: dmTab }];
              }
              return prev;
            });
            addMessage(dmTab, msg);
          } else if (msg.channel) {
            addMessage(msg.channel, msg);
          }
        }
        if (maxId > lastMsgId) setLastMsgId(maxId);
      } catch (err) {
        // silent
      }
    };
    pollRef.current = setInterval(poll, 1500);
    poll();
    return () => { alive = false; clearInterval(pollRef.current); };
  }, [loggedIn, lastMsgId, nick, addMessage]);

  // Fetch members for active channel tab
  useEffect(() => {
    if (!loggedIn) return;
    const tab = tabs[activeTab];
    if (!tab || tab.type !== 'channel') return;
    const chName = tab.name.replace('#', '');
    api(`/channels/${chName}/members`).then(m => {
      setMembers(prev => ({ ...prev, [tab.name]: m }));
    }).catch(() => {});
    const iv = setInterval(() => {
      api(`/channels/${chName}/members`).then(m => {
        setMembers(prev => ({ ...prev, [tab.name]: m }));
      }).catch(() => {});
    }, 5000);
    return () => clearInterval(iv);
  }, [loggedIn, activeTab, tabs]);

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

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

  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); // switch to new tab
      addSystemMessage(name, `Joined ${name}`);
      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 (err) { /* ignore */ }
    setTabs(prev => {
      const next = prev.filter(t => !(t.type === 'channel' && t.name === name));
      return next;
    });
    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 }];
    });
    setActiveTab(tabs.findIndex(t => t.type === 'dm' && t.name === targetNick) || tabs.length);
  };

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

    // Handle /commands
    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 msg = parts.slice(2).join(' ');
        try {
          await api('/messages', { method: 'POST', body: JSON.stringify({ command: 'PRIVMSG', to: target, body: [msg] }) });
          openDM(target);
        } catch (err) {
          addSystemMessage('server', `Failed to send DM: ${err.data?.error || 'error'}`);
        }
        return;
      }
      if (cmd === '/nick' && parts[1]) {
        try {
          await api('/messages', { method: 'POST', body: JSON.stringify({ command: 'NICK', body: [parts[1]] }) });
          setNick(parts[1]);
          addSystemMessage('server', `Nick changed to ${parts[1]}`);
        } catch (err) {
          addSystemMessage('server', `Nick change failed: ${err.data?.error || 'error'}`);
        }
        return;
      }
      addSystemMessage('server', `Unknown command: ${cmd}`);
      return;
    }

    const to = tab.type === 'channel' ? tab.name : tab.name;
    try {
      await api('/messages', { method: 'POST', body: JSON.stringify({ command: 'PRIVMSG', to, 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] || [];

  return (
    <div class="app">
      <div class="tab-bar">
        {tabs.map((tab, i) => (
          <div
            class={`tab ${i === activeTab ? 'active' : ''}`}
            onClick={() => setActiveTab(i)}
          >
            {tab.type === 'dm' ? `→${tab.name}` : tab.name}
            {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>

      <div class="content">
        <div class="messages-pane">
          {currentTab.type === 'server' ? (
            <div class="server-messages">
              {currentMessages.map(m => <Message msg={m} />)}
              <div ref={messagesEndRef} />
            </div>
          ) : (
            <>
              <div class="messages">
                {currentMessages.map(m => <Message msg={m} />)}
                <div ref={messagesEndRef} />
              </div>
              <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'));
