import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import './App.css';
import { fetchHistory, fetchProviders, fetchSessions, sendQuery } from './api/client';

const STORAGE_KEYS = {
  sessionId: 'backend.sessionId',
  sessionTitle: 'backend.sessionTitle',
};

const DEFAULT_SESSION_TITLE = 'Your virtual receptionist for faculty, research, and events.';

const formatTimestamp = (ts) => {
  if (!ts) {
    return '';
  }
  const date = new Date(ts * 1000);
  return date.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });
};

const friendlyTitleFromDate = (date = new Date()) =>
  `Chat – ${date.toLocaleString([], {
    month: 'short',
    day: 'numeric',
    hour: 'numeric',
    minute: '2-digit',
  })}`;

const toHistoryEntry = (entry, index = 0) => ({
  id: `${entry.session_id || 'session'}-${entry.timestamp || index}`,
  question: entry.question,
  answer: entry.answer,
  timestamp: entry.timestamp,
  blueprint: entry.blueprint,
  facts: entry.facts || [],
  notes: entry.notes || [],
  usage: entry.usage,
  action: entry.action,
  pending: Boolean(entry.pending),
});

const buildUsageMeta = (entry, providerCatalog) => {
  if (entry.pending) {
    return 'Thinking…';
  }
  const usage = entry.usage || {};
  const metaParts = [];
  if (usage.provider) {
    const providerLabel = providerCatalog[usage.provider]?.name || usage.provider;
    metaParts.push(`Provider: ${providerLabel}`);
  }
  if (usage.model) {
    metaParts.push(`Model: ${usage.model}`);
  }
  if (usage.total_tokens != null) {
    metaParts.push(`Tokens: ${usage.total_tokens}`);
  } else if (usage.response_tokens != null) {
    metaParts.push(`Tokens: ${usage.response_tokens}`);
  }
  if (usage.fallback) {
    metaParts.push('Fallback: rule-based');
  }
  if (metaParts.length) {
    return metaParts.join(' • ');
  }
  return formatTimestamp(entry.timestamp);
};

const hasProvenance = (entry) =>
  Boolean(
    entry.blueprint ||
      entry.facts?.length ||
      entry.notes?.length ||
      entry.action ||
      (entry.usage && Object.keys(entry.usage).length),
  );

const formatContent = (value) => {
  if (value == null) {
    return '';
  }
  if (typeof value === 'string') {
    return value;
  }
  if (typeof value === 'number' || typeof value === 'boolean') {
    return String(value);
  }
  try {
    return JSON.stringify(value);
  } catch {
    return String(value);
  }
};

function Message({ role, text, meta, pending }) {
  return (
    <div className={`message ${role}${pending ? ' pending' : ''}`}>
      <div className="content">{text}</div>
      {meta ? <div className="meta">{meta}</div> : null}
    </div>
  );
}

function Provenance({ entry, providerCatalog }) {
  if (!hasProvenance(entry)) {
    return null;
  }

  const facts = entry.facts || [];
  const notes = entry.notes || [];
  const usage = entry.usage || {};
  const action = entry.action || {};
  const providerLabel = usage.provider ? providerCatalog[usage.provider]?.name || usage.provider : null;

  return (
    <details className="provenance">
      <summary>Show provenance</summary>
      <div className="provenance-body">
        {entry.blueprint ? (
          <p>
            <strong>Blueprint:</strong> {entry.blueprint}
          </p>
        ) : null}
        {action.type ? (
          <p>
            <strong>Action:</strong> {action.type}
            {action.arguments ? ` — ${JSON.stringify(action.arguments)}` : ''}
          </p>
        ) : null}
        {providerLabel || usage.model || usage.total_tokens != null || usage.response_tokens != null || usage.fallback ? (
          <p>
            <strong>Usage:</strong>{' '}
            {[
              providerLabel ? `Provider: ${providerLabel}` : null,
              usage.model ? `Model: ${usage.model}` : null,
              usage.total_tokens != null
                ? `Tokens: ${usage.total_tokens}`
                : usage.response_tokens != null
                  ? `Tokens: ${usage.response_tokens}`
                  : null,
              usage.fallback ? 'Fallback: rule-based' : null,
            ]
              .filter(Boolean)
              .join(' • ')}
          </p>
        ) : null}
        {facts.length ? (
          <>
            <h4>Facts</h4>
            <ul>
              {facts.map((fact, index) => (
                <li key={`${fact.subject || fact.predicate || index}-${index}`}>
                  <strong>{formatContent(fact.subject) || 'Subject'}</strong> —{' '}
                  {formatContent(fact.predicate) || 'Detail'}: {formatContent(fact.value) || 'N/A'}
                  {fact.source ? <em> ({formatContent(fact.source)})</em> : null}
                </li>
              ))}
            </ul>
          </>
        ) : null}
        {notes.length ? (
          <>
            <h4>Notes</h4>
            <ul>
              {notes.map((note, index) => (
                <li key={`${index}-${formatContent(note)}`}>{formatContent(note) || 'Note'}</li>
              ))}
            </ul>
          </>
        ) : null}
      </div>
    </details>
  );
}

function HistoryEntry({ entry, providerCatalog }) {
  return (
    <div className="history-entry">
      <Message role="user" text={entry.question} meta={formatTimestamp(entry.timestamp)} />
      <Message
        role="assistant"
        text={entry.answer || (entry.pending ? 'Thinking…' : '')}
        meta={buildUsageMeta(entry, providerCatalog)}
        pending={entry.pending}
      />
      <Provenance entry={entry} providerCatalog={providerCatalog} />
    </div>
  );
}

function App() {
  const storedSessionId = localStorage.getItem(STORAGE_KEYS.sessionId);
  const storedSessionTitle = localStorage.getItem(STORAGE_KEYS.sessionTitle);

  const [sessionId, setSessionId] = useState(storedSessionId || `session-${Date.now()}`);
  const [sessionTitle, setSessionTitle] = useState(storedSessionTitle || DEFAULT_SESSION_TITLE);
  const [historyEntries, setHistoryEntries] = useState([]);
  const [historyLoading, setHistoryLoading] = useState(false);
  const [sessions, setSessions] = useState([]);
  const [providers, setProviders] = useState([]);
  const [selectedProvider, setSelectedProvider] = useState(null);
  const [providerError, setProviderError] = useState('');
  const [sessionsError, setSessionsError] = useState('');
  const [historyError, setHistoryError] = useState('');
  const [composerValue, setComposerValue] = useState('');
  const [isSending, setIsSending] = useState(false);
  const composerRef = useRef(null);

  const providerCatalog = useMemo(() => {
    const catalog = {};
    providers.forEach((provider) => {
      catalog[provider.id] = provider;
    });
    return catalog;
  }, [providers]);

  const providerNote = useMemo(() => {
    if (providerError) {
      return providerError;
    }
    if (!selectedProvider) {
      return 'Select a model to route responses.';
    }
    const provider = providerCatalog[selectedProvider];
    if (!provider) {
      return 'Select a model to route responses.';
    }
    if (!provider.configured) {
      return provider.note || 'Configure this provider before using it.';
    }
    return `Default model: ${provider.default_model || 'default'}`;
  }, [providerCatalog, providerError, selectedProvider]);

  const persistSessionId = useCallback((value) => {
    localStorage.setItem(STORAGE_KEYS.sessionId, value);
    setSessionId(value);
  }, []);

  const persistSessionTitle = useCallback((value) => {
    localStorage.setItem(STORAGE_KEYS.sessionTitle, value);
    setSessionTitle(value);
  }, []);

  const loadProviders = useCallback(async () => {
    try {
      setProviderError('');
      const payload = await fetchProviders();
      const entries = Object.values(payload.providers || {});
      entries.sort((a, b) => {
        if (a.id === 'vanilla') return -1;
        if (b.id === 'vanilla') return 1;
        return a.name.localeCompare(b.name);
      });
      setProviders(entries);
      const available = new Set(entries.map((entry) => entry.id));
      const fallback =
        (payload.default_provider && available.has(payload.default_provider) && payload.default_provider) ||
        entries[0]?.id ||
        null;
      setSelectedProvider((current) => {
        if (current && available.has(current)) {
          return current;
        }
        return fallback;
      });
    } catch (error) {
      setProviderError(error.message || 'Unable to load providers.');
    }
  }, []);

  const loadSessions = useCallback(async () => {
    try {
      setSessionsError('');
      const payload = await fetchSessions();
      setSessions(payload.sessions || []);
    } catch (error) {
      setSessionsError(error.message || 'Unable to load sessions.');
    }
  }, []);

  const loadHistoryData = useCallback(
    async (targetSessionId) => {
      if (!targetSessionId) {
        return;
      }
      try {
        setHistoryError('');
        setHistoryLoading(true);
        const payload = await fetchHistory(targetSessionId);
        const entries = (payload.history || []).map((entry, index) =>
          toHistoryEntry(
            {
              ...entry,
              session_id: targetSessionId,
            },
            index,
          ),
        );
        setHistoryEntries(entries);
        if (payload.title) {
          persistSessionTitle(payload.title);
        }
      } catch (error) {
        setHistoryError(error.message || 'Unable to load history.');
      } finally {
        setHistoryLoading(false);
      }
    },
    [persistSessionTitle],
  );

  useEffect(() => {
    const updateShellHeight = () => {
      const root = document.getElementById('root');
      if (!root) {
        return;
      }
      const topOffset = root.getBoundingClientRect().top;
      const available = window.innerHeight - topOffset;
      const clamped = Math.max(available, 320);
      document.documentElement.style.setProperty('--app-shell-height', `${clamped}px`);
    };
    updateShellHeight();
    window.addEventListener('resize', updateShellHeight);
    window.addEventListener('orientationchange', updateShellHeight);
    return () => {
      window.removeEventListener('resize', updateShellHeight);
      window.removeEventListener('orientationchange', updateShellHeight);
    };
  }, []);

  useEffect(() => {
    loadProviders();
    loadSessions();
  }, [loadProviders, loadSessions]);

  useEffect(() => {
    loadHistoryData(sessionId);
  }, [loadHistoryData, sessionId]);

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

  const handleSelectSession = (session) => {
    if (!session) {
      return;
    }
    persistSessionId(session.session_id);
    persistSessionTitle(session.title || friendlyTitleFromDate(new Date(session.created_at * 1000)));
    loadHistoryData(session.session_id);
  };

  const handleNewChat = () => {
    const newSessionId = `session-${Date.now()}`;
    const title = friendlyTitleFromDate();
    persistSessionId(newSessionId);
    persistSessionTitle(title);
    setHistoryEntries([]);
    setComposerValue('');
  };

  const sendMessage = async () => {
    if (isSending) {
      return;
    }
    const question = composerValue.trim();
    if (!question) {
      return;
    }
    setComposerValue('');
    setIsSending(true);
    setHistoryError('');
    const tempId = `pending-${Date.now()}`;
    const timestamp = Date.now() / 1000;
    setHistoryEntries((prev) => [
      ...prev,
      {
        id: tempId,
        session_id: sessionId,
        question,
        answer: 'Thinking…',
        timestamp,
        pending: true,
        facts: [],
        notes: [],
        usage: null,
        action: null,
      },
    ]);
    try {
      const payload = await sendQuery({
        sessionId,
        question,
        provider: selectedProvider || undefined,
      });
      setHistoryEntries((prev) =>
        prev.map((entry) =>
          entry.id === tempId
            ? {
                ...entry,
                id: `${payload.session_id || sessionId}-${timestamp}`,
                pending: false,
                answer: payload.answer || entry.answer,
                blueprint: payload.blueprint,
                facts: payload.facts || [],
                notes: payload.notes || [],
                usage: payload.usage || {},
                action: payload.action || {},
              }
            : entry,
        ),
      );
      if (payload.session_id && payload.session_id !== sessionId) {
        persistSessionId(payload.session_id);
      }
      if (payload.session_title) {
        persistSessionTitle(payload.session_title);
      }
      loadSessions();
    } catch (error) {
      setHistoryEntries((prev) =>
        prev.map((entry) =>
          entry.id === tempId
            ? {
                ...entry,
                pending: false,
                answer: `Sorry, something went wrong: ${error.message}`,
              }
            : entry,
        ),
      );
    } finally {
      setIsSending(false);
      composerRef.current?.focus();
    }
  };

  const handleSend = (event) => {
    event.preventDefault();
    void sendMessage();
  };

  const handleComposerKeyDown = (event) => {
    if ((event.key === 'Enter' || event.key === 'NumpadEnter') && !event.shiftKey) {
      if (event.nativeEvent.isComposing) {
        return;
      }
      event.preventDefault();
      const form = event.currentTarget.form;
      if (form && typeof form.requestSubmit === 'function') {
        form.requestSubmit();
      } else {
        void sendMessage();
      }
    }
  };

  const providerOptions = providers.map((provider) => {
    const label =
      provider.id === 'vanilla'
        ? provider.name
        : provider.default_model
          ? `${provider.name} (${provider.default_model})`
          : provider.name;
    return (
      <option key={provider.id} value={provider.id}>
        {label}
        {!provider.configured ? ' – configure to enable' : ''}
      </option>
    );
  });

  const hasActiveSession = sessions.some((session) => session.session_id === sessionId);

  const placeholderSession = (
    <button type="button" className="session-item active" key="draft">
      <span className="title">{sessionTitle || friendlyTitleFromDate()}</span>
      <span className="timestamp">Draft</span>
    </button>
  );

  return (
    <div className="app-shell">
      <aside className="sidebar">
        <div className="sidebar-header">
          <div>
            <h2>Conversations</h2>
            <p>Jump back into a previous session or start fresh.</p>
          </div>
          <button type="button" className="new-chat" onClick={handleNewChat}>
            New chat
          </button>
        </div>
        <div className="session-list">
          {sessionsError ? <p className="error">{sessionsError}</p> : null}
          {!sessions.length && placeholderSession}
          {sessions.map((session) => (
            <button
              type="button"
              key={session.session_id}
              className={`session-item${session.session_id === sessionId ? ' active' : ''}`}
              onClick={() => handleSelectSession(session)}
            >
              <span className="title">{session.title || session.session_id}</span>
              <span className="timestamp">
                {session.updated_at ? new Date(session.updated_at * 1000).toLocaleString() : ''}
              </span>
            </button>
          ))}
          {!hasActiveSession && sessions.length ? placeholderSession : null}
        </div>
      </aside>
      <main className="chat-pane">
        <header className="chat-header">
          <div>
            <h1>Northwestern CS Kiosk (Vanilla LM)</h1>
            <p>{sessionTitle || DEFAULT_SESSION_TITLE}</p>
          </div>
          <div className="model-picker">
            <label htmlFor="provider-select">Response model</label>
            <select
              id="provider-select"
              value={selectedProvider || ''}
              onChange={(event) => setSelectedProvider(event.target.value || null)}
            >
              {providerOptions}
            </select>
            <p className="model-note">{providerNote}</p>
          </div>
        </header>
        <section className="chat-log">
          {historyLoading ? <p className="status">Loading conversation…</p> : null}
          {historyError ? <p className="error">{historyError}</p> : null}
          {!historyEntries.length && !historyLoading ? (
            <div className="empty-state">
              <p>Ask about faculty, research, upcoming events, and more.</p>
            </div>
          ) : null}
          {historyEntries.map((entry) => (
            <HistoryEntry key={entry.id} entry={entry} providerCatalog={providerCatalog} />
          ))}
        </section>
        <form className="composer" onSubmit={handleSend}>
          <textarea
            ref={composerRef}
            placeholder="Ask anything about the CS department…"
            value={composerValue}
            onChange={(event) => setComposerValue(event.target.value)}
            onKeyDown={handleComposerKeyDown}
            disabled={isSending}
            rows={3}
          />
          <button type="submit" disabled={isSending || !composerValue.trim()}>
            {isSending ? 'Sending…' : 'Send'}
          </button>
        </form>
      </main>
    </div>
  );
}

export default App;
