import React, { useState, useCallback, useEffect, useRef } from 'react';
import { Box, Text, useInput, useApp } from 'ink';
import TextInput from 'ink-text-input';
import Spinner from 'ink-spinner';
import { io, type Socket } from 'socket.io-client';
import { fetchAvailableModels, type ModelInfo } from './gateway-api.js';

interface Message {
  role: 'user' | 'assistant' | 'system';
  content: string;
}

interface TuiAppProps {
  gatewayUrl: string;
  conversationId?: string;
  sessionCookie?: string;
  initialModel?: string;
  initialProvider?: string;
}

/**
 * Parse a slash command from user input.
 * Returns null if the input is not a slash command.
 */
function parseSlashCommand(value: string): { command: string; args: string[] } | null {
  const trimmed = value.trim();
  if (!trimmed.startsWith('/')) return null;
  const parts = trimmed.slice(1).split(/\s+/);
  const command = parts[0]?.toLowerCase() ?? '';
  const args = parts.slice(1);
  return { command, args };
}

export function TuiApp({
  gatewayUrl,
  conversationId: initialConversationId,
  sessionCookie,
  initialModel,
  initialProvider,
}: TuiAppProps) {
  const { exit } = useApp();
  const [messages, setMessages] = useState<Message[]>([]);
  const [input, setInput] = useState('');
  const [isStreaming, setIsStreaming] = useState(false);
  const [connected, setConnected] = useState(false);
  const [conversationId, setConversationId] = useState(initialConversationId);
  const [currentStreamText, setCurrentStreamText] = useState('');

  // Model/provider state
  const [currentModel, setCurrentModel] = useState<string | undefined>(initialModel);
  const [currentProvider, setCurrentProvider] = useState<string | undefined>(initialProvider);
  const [availableModels, setAvailableModels] = useState<ModelInfo[]>([]);

  const socketRef = useRef<Socket | null>(null);
  const currentStreamTextRef = useRef('');

  // Fetch available models on mount
  useEffect(() => {
    fetchAvailableModels(gatewayUrl, sessionCookie)
      .then((models) => {
        setAvailableModels(models);
        // If no model/provider specified and models are available, show the default
        if (!initialModel && !initialProvider && models.length > 0) {
          const first = models[0];
          if (first) {
            setCurrentModel(first.id);
            setCurrentProvider(first.provider);
          }
        }
      })
      .catch(() => {
        // Non-fatal: TUI works without model list
      });
  }, [gatewayUrl, sessionCookie, initialModel, initialProvider]);

  useEffect(() => {
    const socket = io(`${gatewayUrl}/chat`, {
      transports: ['websocket'],
      extraHeaders: sessionCookie ? { Cookie: sessionCookie } : undefined,
    });

    socketRef.current = socket;

    socket.on('connect', () => setConnected(true));
    socket.on('disconnect', () => {
      setConnected(false);
      setIsStreaming(false);
      setCurrentStreamText('');
    });
    socket.on('connect_error', (err: Error) => {
      setMessages((msgs) => [
        ...msgs,
        {
          role: 'assistant',
          content: `Connection failed: ${err.message}. Check that the gateway is running at ${gatewayUrl}.`,
        },
      ]);
    });

    socket.on('message:ack', (data: { conversationId: string }) => {
      setConversationId(data.conversationId);
    });

    socket.on('agent:start', () => {
      setIsStreaming(true);
      currentStreamTextRef.current = '';
      setCurrentStreamText('');
    });

    socket.on('agent:text', (data: { text: string }) => {
      currentStreamTextRef.current += data.text;
      setCurrentStreamText(currentStreamTextRef.current);
    });

    socket.on('agent:end', () => {
      const finalText = currentStreamTextRef.current;
      currentStreamTextRef.current = '';
      setCurrentStreamText('');
      if (finalText) {
        setMessages((msgs) => [...msgs, { role: 'assistant', content: finalText }]);
      }
      setIsStreaming(false);
    });

    socket.on('error', (data: { error: string }) => {
      setMessages((msgs) => [...msgs, { role: 'assistant', content: `Error: ${data.error}` }]);
      setIsStreaming(false);
    });

    return () => {
      socket.disconnect();
    };
  }, [gatewayUrl]);

  /**
   * Handle /model and /provider slash commands.
   * Returns true if the input was a handled slash command (should not be sent to gateway).
   */
  const handleSlashCommand = useCallback(
    (value: string): boolean => {
      const parsed = parseSlashCommand(value);
      if (!parsed) return false;

      const { command, args } = parsed;

      if (command === 'model') {
        if (args.length === 0) {
          // List available models
          if (availableModels.length === 0) {
            setMessages((msgs) => [
              ...msgs,
              {
                role: 'system',
                content:
                  'No models available (could not reach gateway). Use /model <modelId> to set one manually.',
              },
            ]);
          } else {
            const lines = availableModels.map(
              (m) =>
                `  ${m.provider}/${m.id}${m.id === currentModel && m.provider === currentProvider ? ' (active)' : ''}`,
            );
            setMessages((msgs) => [
              ...msgs,
              {
                role: 'system',
                content: `Available models:\n${lines.join('\n')}`,
              },
            ]);
          }
        } else {
          // Switch model: /model <modelId> or /model <provider>/<modelId>
          const arg = args[0]!;
          const slashIdx = arg.indexOf('/');
          let newProvider: string | undefined;
          let newModelId: string;

          if (slashIdx !== -1) {
            newProvider = arg.slice(0, slashIdx);
            newModelId = arg.slice(slashIdx + 1);
          } else {
            newModelId = arg;
            // Try to find provider from available models list
            const match = availableModels.find((m) => m.id === newModelId);
            newProvider = match?.provider ?? currentProvider;
          }

          setCurrentModel(newModelId);
          if (newProvider) setCurrentProvider(newProvider);

          setMessages((msgs) => [
            ...msgs,
            {
              role: 'system',
              content: `Switched to model: ${newProvider ? `${newProvider}/` : ''}${newModelId}. Takes effect on next message.`,
            },
          ]);
        }
        return true;
      }

      if (command === 'provider') {
        if (args.length === 0) {
          // List providers from available models
          const providers = [...new Set(availableModels.map((m) => m.provider))];
          if (providers.length === 0) {
            setMessages((msgs) => [
              ...msgs,
              {
                role: 'system',
                content:
                  'No providers available (could not reach gateway). Use /provider <name> to set one manually.',
              },
            ]);
          } else {
            const lines = providers.map((p) => `  ${p}${p === currentProvider ? ' (active)' : ''}`);
            setMessages((msgs) => [
              ...msgs,
              {
                role: 'system',
                content: `Available providers:\n${lines.join('\n')}`,
              },
            ]);
          }
        } else {
          const newProvider = args[0]!;
          setCurrentProvider(newProvider);
          // If switching provider, auto-select first model for that provider
          const providerModels = availableModels.filter((m) => m.provider === newProvider);
          if (providerModels.length > 0 && providerModels[0]) {
            setCurrentModel(providerModels[0].id);
            setMessages((msgs) => [
              ...msgs,
              {
                role: 'system',
                content: `Switched to provider: ${newProvider} (model: ${providerModels[0]!.id}). Takes effect on next message.`,
              },
            ]);
          } else {
            setMessages((msgs) => [
              ...msgs,
              {
                role: 'system',
                content: `Switched to provider: ${newProvider}. Takes effect on next message.`,
              },
            ]);
          }
        }
        return true;
      }

      if (command === 'help') {
        setMessages((msgs) => [
          ...msgs,
          {
            role: 'system',
            content: [
              'Available commands:',
              '  /model              — list available models',
              '  /model <id>         — switch model (e.g. /model gpt-4o)',
              '  /model <p>/<id>     — switch model with provider (e.g. /model ollama/llama3.2)',
              '  /provider           — list available providers',
              '  /provider <name>    — switch provider (e.g. /provider ollama)',
              '  /help               — show this help',
            ].join('\n'),
          },
        ]);
        return true;
      }

      // Unknown slash command — let the user know
      setMessages((msgs) => [
        ...msgs,
        {
          role: 'system',
          content: `Unknown command: /${command}. Type /help for available commands.`,
        },
      ]);
      return true;
    },
    [availableModels, currentModel, currentProvider],
  );

  const handleSubmit = useCallback(
    (value: string) => {
      if (!value.trim() || isStreaming) return;

      setInput('');

      // Handle slash commands first
      if (handleSlashCommand(value)) return;

      if (!socketRef.current?.connected) {
        setMessages((msgs) => [
          ...msgs,
          { role: 'assistant', content: 'Not connected to gateway. Message not sent.' },
        ]);
        return;
      }

      setMessages((msgs) => [...msgs, { role: 'user', content: value }]);

      socketRef.current.emit('message', {
        conversationId,
        content: value,
        provider: currentProvider,
        modelId: currentModel,
      });
    },
    [conversationId, isStreaming, currentModel, currentProvider, handleSlashCommand],
  );

  useInput((ch, key) => {
    if (key.ctrl && ch === 'c') {
      exit();
    }
  });

  const modelLabel = currentModel
    ? currentProvider
      ? `${currentProvider}/${currentModel}`
      : currentModel
    : null;

  return (
    <Box flexDirection="column" padding={1}>
      <Box marginBottom={1}>
        <Text bold color="blue">
          Mosaic
        </Text>
        <Text> </Text>
        <Text dimColor>{connected ? `connected` : 'connecting...'}</Text>
        {conversationId && <Text dimColor> | {conversationId.slice(0, 8)}</Text>}
        {modelLabel && (
          <>
            <Text dimColor> | </Text>
            <Text color="yellow">{modelLabel}</Text>
          </>
        )}
      </Box>

      <Box flexDirection="column" marginBottom={1}>
        {messages.map((msg, i) => (
          <Box key={i} marginBottom={1}>
            {msg.role === 'system' ? (
              <Text dimColor italic>
                {msg.content}
              </Text>
            ) : (
              <>
                <Text bold color={msg.role === 'user' ? 'green' : 'cyan'}>
                  {msg.role === 'user' ? '> ' : '  '}
                </Text>
                <Text wrap="wrap">{msg.content}</Text>
              </>
            )}
          </Box>
        ))}

        {isStreaming && currentStreamText && (
          <Box marginBottom={1}>
            <Text bold color="cyan">
              {'  '}
            </Text>
            <Text wrap="wrap">{currentStreamText}</Text>
          </Box>
        )}

        {isStreaming && !currentStreamText && (
          <Box>
            <Text color="cyan">
              <Spinner type="dots" />
            </Text>
            <Text dimColor> thinking...</Text>
          </Box>
        )}
      </Box>

      <Box>
        <Text bold color="green">
          {'> '}
        </Text>
        <TextInput
          value={input}
          onChange={setInput}
          onSubmit={handleSubmit}
          placeholder={isStreaming ? 'waiting...' : 'type a message or /help'}
        />
      </Box>
    </Box>
  );
}
