import React, { useState, useCallback, useRef, useEffect } from 'react';
import { Box, Text, useApp, useInput } from 'ink';
import { StatusBar } from './StatusBar.js';
import { MessageList } from './MessageList.js';
import { InputBar } from './InputBar.js';
import { parseCommand, getHelpText, resolveModelAlias, getCommandCompletions } from '../commands.js';
import type { Message, ModelClient, TokenUsage } from '../../../models/types.js';
import type { ModelRouter } from '../../../models/router.js';
import type { ManagedSession } from '../../../session/index.js';
import type { NativeAgent, ToolUseEvent } from '../../../backends/native/agent.js';
import type { HookEngine, HookResult } from '../../../hooks/index.js';
import type { ModelConfig, ModelProvider } from '../../../config/schema.js';
import { MODEL_PROVIDERS } from '../../../config/schema.js';
import { createClientFromConfig } from '../../../daemon/index.js';
import { estimateMessageTokens, getContextWindow } from '../../../context/tokens.js';

/** Format a tool name like "gmail.list" -> "Gmail: List" */
function formatToolName(name: string): string {
  const parts = name.split('.');
  return parts.map((p, i) => {
    const capitalized = p.charAt(0).toUpperCase() + p.slice(1);
    return i === 0 && parts.length > 1 ? capitalized + ':' : capitalized;
  }).join(' ');
}

/** Format tool args as a compact, readable summary. */
function formatToolArgs(args: unknown): string {
  if (!args || typeof args !== 'object') {return '';}
  const entries = Object.entries(args as Record<string, unknown>);
  if (entries.length === 0) {return '';}
  const parts = entries.map(([key, value]) => {
    if (typeof value === 'string') {
      const display = value.length > 50 ? value.slice(0, 47) + '...' : value;
      return `${key}: "${display}"`;
    }
    if (typeof value === 'number' || typeof value === 'boolean') {
      return `${key}: ${value}`;
    }
    return `${key}: ${JSON.stringify(value)}`;
  });
  return parts.join(', ');
}

export interface AppProps {
  session: ManagedSession;
  modelClient: ModelClient;
  modelRouter?: ModelRouter;
  systemPrompt: string;
  model: string;
  agent?: NativeAgent;
  hookEngine?: HookEngine;
  modelProviderConfigs?: Partial<Record<ModelProvider, ModelConfig>>;
  contextThresholdPct?: number;
  onExit?: () => void;
}

export function App({
  session,
  modelClient,
  modelRouter,
  systemPrompt,
  model,
  agent,
  hookEngine,
  modelProviderConfigs,
  contextThresholdPct,
  onExit,
}: AppProps): React.ReactElement {
  const { exit } = useApp();
  const [input, setInput] = useState('');
  const [messages, setMessages] = useState<Message[]>(session.getHistory());
  const [isStreaming, setIsStreaming] = useState(false);
  const [streamingContent, setStreamingContent] = useState('');
  const [scrollOffset, setScrollOffset] = useState(0);
  const [tokenUsage, setTokenUsage] = useState<TokenUsage>({ inputTokens: 0, outputTokens: 0 });
  const [verbose, setVerbose] = useState(false);
  const [currentModel, setCurrentModel] = useState(() => {
    if (!modelRouter) {return model;}
    return modelRouter.getLabel(modelRouter.getTier());
  });

  const abortRef = useRef(false);
  const toolLinesRef = useRef<string[]>([]);

  const confirmResolveRef = useRef<((result: HookResult) => void) | null>(null);
  const [confirmation, setConfirmation] = useState<{ tool: string; args: Record<string, unknown> } | null>(null);

  // Set up an Ink-compatible onToolUse callback for the agent.
  // This replaces process.stdout writes (which corrupt Ink rendering)
  // with one that updates React state to show tool activity.
  useEffect(() => {
    if (!agent) {return;}

    const handleToolEvent = (event: ToolUseEvent) => {
      if (event.type === 'start') {
        const label = formatToolName(event.tool);
        const argsStr = event.args ? ` (${formatToolArgs(event.args)})` : '';
        toolLinesRef.current = [...toolLinesRef.current, `> ${label}${argsStr}`];
        setStreamingContent(toolLinesRef.current.join('\n'));
        return;
      }

      if (event.type === 'end' && event.result) {
        const icon = event.result.success ? 'done' : 'error';
        const detail = event.result.success
          ? `(${event.result.output.split('\n').length} lines)`
          : (event.result.error ?? 'unknown error');
        toolLinesRef.current = [...toolLinesRef.current, `  ${icon} ${detail}`];
        setStreamingContent(toolLinesRef.current.join('\n'));
      }
    };

    agent.setOnToolUse(handleToolEvent);
    return () => {
      agent.setOnToolUse(undefined);
    };
  }, [agent]);

  // Inline confirmations for dangerous tools (e.g. shell.exec) in fullscreen mode.
  useEffect(() => {
    if (!hookEngine) {return;}

    hookEngine.setInteractiveConfirmer(async (pending) => {
      return new Promise<HookResult>((resolve) => {
        confirmResolveRef.current = resolve;
        setConfirmation({ tool: pending.tool, args: pending.args });
      });
    });

    return () => {
      hookEngine.setInteractiveConfirmer(undefined);
      confirmResolveRef.current = null;
      setConfirmation(null);
    };
  }, [hookEngine]);

  useInput((inputChar, key) => {
    // Confirmation prompt mode: capture y/n and ignore everything else.
    if (confirmation && confirmResolveRef.current) {
      const c = inputChar.toLowerCase();
      if (c === 'y') {
        confirmResolveRef.current({ approved: true });
        confirmResolveRef.current = null;
        setConfirmation(null);
        return;
      }
      if (c === 'n') {
        confirmResolveRef.current({ approved: false, reason: 'Denied by user' });
        confirmResolveRef.current = null;
        setConfirmation(null);
        return;
      }
      return;
    }

    if (key.escape) {
      if (isStreaming) {
        abortRef.current = true;
      } else {
        onExit?.();
        exit();
      }
      return;
    }

    // Tab completion for slash commands
    if (key.tab && !isStreaming) {
      const completions = getCommandCompletions(input);
      if (completions.length > 0) {
        setInput(completions[0]);
        return;
      }
    }

    // Only handle scroll keys, ignore regular typing
    if (!key.upArrow && !key.downArrow && !key.pageUp && !key.pageDown && !key.tab) {
      return;
    }

    if (key.upArrow && scrollOffset > 0) {
      setScrollOffset(prev => Math.max(0, prev - 1));
    }
    if (key.downArrow) {
      setScrollOffset(prev => Math.min(messages.length - 1, prev + 1));
    }
    if (key.pageUp) {
      setScrollOffset(prev => Math.max(0, prev - 10));
    }
    if (key.pageDown) {
      setScrollOffset(prev => Math.min(messages.length - 1, prev + 10));
    }
  });

  const handleSubmit = useCallback(async (value: string) => {
    if (confirmation) {
      return;
    }

    const command = parseCommand(value);
    if (!command) {return;}

    setInput('');

    switch (command.type) {
      case 'quit':
        onExit?.();
        exit();
        return;

      case 'reset':
        if (agent) {
          agent.reset();
        } else {
          session.clear();
        }
        setMessages([]);
        setTokenUsage({ inputTokens: 0, outputTokens: 0 });
        setScrollOffset(0);
        return;

      case 'help': {
        const helpMsg: Message = { role: 'assistant', content: getHelpText() };
        setMessages(prev => [...prev, session.addMessage(helpMsg)]);
        return;
      }

      case 'status': {
        const status = `Session: ${session.id}\nMessages: ${messages.length}\nTokens: ${tokenUsage.inputTokens} in / ${tokenUsage.outputTokens} out`;
        const statusMsg: Message = { role: 'assistant', content: status };
        setMessages(prev => [...prev, session.addMessage(statusMsg)]);
        return;
      }

      case 'compact': {
        setMessages(prev => [...prev, session.addMessage({ role: 'assistant', content: 'Compact command is not available in fullscreen TUI mode.' })]);
        return;
      }

      case 'usage': {
        const text = `Token Usage\n\nTotal: ${tokenUsage.inputTokens} in / ${tokenUsage.outputTokens} out`;
        setMessages(prev => [...prev, session.addMessage({ role: 'assistant', content: text })]);
        return;
      }

      case 'context': {
        const history = session.getHistory();
        const estimated = estimateMessageTokens(history);
        const tier = modelRouter?.getTier() ?? 'default';
        const modelName = modelRouter?.getLabel(tier) ?? model;
        const window = getContextWindow(modelName);
        const usagePct = window > 0 ? (estimated / window) * 100 : 0;
        const thresholdPct = contextThresholdPct ?? 80;
        const thresholdTokens = Math.floor((thresholdPct / 100) * window);
        const remaining = Math.max(0, window - estimated);
        const text = [
          'Context Usage (estimated)',
          '',
          `Model: ${modelName}`,
          `Used: ${estimated.toLocaleString()} / ${window.toLocaleString()} tokens (${usagePct.toFixed(1)}%)`,
          `Remaining: ${remaining.toLocaleString()} tokens`,
          `Compaction threshold: ${thresholdPct}% (${thresholdTokens.toLocaleString()} tokens)`,
          `Should compact: ${estimated > thresholdTokens ? 'yes' : 'no'}`,
        ].join('\n');
        setMessages(prev => [...prev, session.addMessage({ role: 'assistant', content: text })]);
        return;
      }

      case 'verbose': {
        const next = !verbose;
        setVerbose(next);
        setMessages(prev => [...prev, session.addMessage({ role: 'assistant', content: `Verbose mode: ${next ? 'on' : 'off'}` })]);
        return;
      }

      case 'model': {
        if (!modelRouter) {
          setMessages(prev => [...prev, session.addMessage({ role: 'assistant', content: 'Model switching not available.' })]);
          return;
        }

        // /model
        if (!command.name) {
          const current = modelRouter.getTier();
          const available = modelRouter.getAvailableTiers();
          const labels = modelRouter.getAllLabels();

          const lines: string[] = [];
          lines.push(`Active tier: ${current}`);
          for (const t of available) {
            const label = labels[t] ?? 'unknown';
            const strict = modelRouter.isTierStrict(t) ? ' (strict)' : '';
            lines.push(`  ${t}: ${label}${strict}${t === current ? ' ←' : ''}`);
          }

          setMessages(prev => [...prev, session.addMessage({ role: 'assistant', content: lines.join('\n') })]);
          return;
        }

        // /model <tier> <provider/model>
        if (command.providerModel) {
          const tier = resolveModelAlias(command.name);
          const providerModel = command.providerModel;

          const slashIdx = providerModel.indexOf('/');
          if (slashIdx === -1) {
            setMessages(prev => [...prev, session.addMessage({
              role: 'assistant',
              content: 'Invalid format. Use provider/model (e.g. anthropic/claude-sonnet-4)',
            })]);
            return;
          }

          const provider = providerModel.slice(0, slashIdx);
          const modelName = providerModel.slice(slashIdx + 1);

          if (!MODEL_PROVIDERS.includes(provider as ModelProvider)) {
            setMessages(prev => [...prev, session.addMessage({
              role: 'assistant',
              content: `Unknown provider "${provider}". Known providers: ${MODEL_PROVIDERS.join(', ')}`,
            })]);
            return;
          }

          try {
            const providerType = provider as ModelProvider;
            const template = modelProviderConfigs?.[providerType];
            const client = createClientFromConfig({
              ...(template ?? {}),
              provider: providerType,
              model: modelName,
            });

            modelRouter.setClient(tier, client, providerModel);
            modelRouter.setTierStrict(tier, true);

            if (agent && tier === modelRouter.getTier()) {
              agent.setModelTier(tier);
              setCurrentModel(modelRouter.getLabel(tier));
            }

            setMessages(prev => [...prev, session.addMessage({
              role: 'assistant',
              content: `Set ${tier} to: ${providerModel}\nFallbacks for ${tier} disabled (strict tier mode).`,
            })]);
          } catch (error) {
            const msg = error instanceof Error ? error.message : String(error);
            setMessages(prev => [...prev, session.addMessage({ role: 'assistant', content: `Failed to create client: ${msg}` })]);
          }

          return;
        }

        // /model <tier>
        const tier = resolveModelAlias(command.name);
        if (modelRouter.setTier(tier)) {
          if (agent) {
            agent.setModelTier(tier);
          }
          setCurrentModel(modelRouter.getLabel(tier));
          setMessages(prev => [...prev, session.addMessage({ role: 'assistant', content: `Switched to model: ${tier}` })]);
        } else {
          setMessages(prev => [...prev, session.addMessage({ role: 'assistant', content: `Model not available: ${command.name}` })]);
        }
        return;
      }

      case 'fullscreen':
        return;

      case 'transfer':
        setMessages(prev => [...prev, session.addMessage({ role: 'assistant', content: 'Transfer not supported in fullscreen mode.' })]);
        return;

      case 'queue': {
        if (!command.action || command.action === 'show') {
          const mode = session.getConfig('queue.mode') ?? 'collect';
          const cap = session.getConfig('queue.cap') ?? '50';
          const overflow = session.getConfig('queue.overflow') ?? 'drop_old';
          const debounceMs = session.getConfig('queue.debounce_ms') ?? '0';
          const summarizeOverflow = session.getConfig('queue.summarize_overflow') ?? 'true';
          const text = [
            'Queue policy:',
            `mode: ${mode}`,
            `cap: ${cap}`,
            `overflow: ${overflow}`,
            `debounce_ms: ${debounceMs}`,
            `summarize_overflow: ${summarizeOverflow}`,
          ].join('\n');
          setMessages(prev => [...prev, session.addMessage({ role: 'assistant', content: text })]);
          return;
        }

        if (command.action === 'reset') {
          session.deleteConfig('queue.mode');
          session.deleteConfig('queue.cap');
          session.deleteConfig('queue.overflow');
          session.deleteConfig('queue.debounce_ms');
          session.deleteConfig('queue.summarize_overflow');
          setMessages(prev => [...prev, session.addMessage({ role: 'assistant', content: 'Reset session queue overrides.' })]);
          return;
        }

        const raw = (command.args ?? '').trim();
        const [rawKey, ...rest] = raw.split(/\s+/);
        const value = rest.join(' ').trim();
        if (!rawKey || !value) {
          setMessages(prev => [...prev, session.addMessage({ role: 'assistant', content: 'Usage: /queue set <mode|cap|overflow|debounce_ms|summarize_overflow> <value>' })]);
          return;
        }

        const key = rawKey.toLowerCase();
        if (key === 'mode') {
          if (!['collect', 'followup', 'steer', 'steer_backlog', 'interrupt'].includes(value)) {
            setMessages(prev => [...prev, session.addMessage({ role: 'assistant', content: 'Invalid mode. Use: collect, followup, steer, steer_backlog, interrupt' })]);
            return;
          }
          session.setConfig('queue.mode', value);
          setMessages(prev => [...prev, session.addMessage({ role: 'assistant', content: `Set queue.mode=${value}` })]);
          return;
        }

        if (key === 'cap') {
          const cap = Number.parseInt(value, 10);
          if (!Number.isFinite(cap) || cap < 1 || cap > 1000) {
            setMessages(prev => [...prev, session.addMessage({ role: 'assistant', content: 'Invalid cap. Use an integer between 1 and 1000.' })]);
            return;
          }
          session.setConfig('queue.cap', String(cap));
          setMessages(prev => [...prev, session.addMessage({ role: 'assistant', content: `Set queue.cap=${cap}` })]);
          return;
        }

        if (key === 'overflow') {
          if (value !== 'drop_old' && value !== 'drop_new') {
            setMessages(prev => [...prev, session.addMessage({ role: 'assistant', content: 'Invalid overflow. Use drop_old or drop_new.' })]);
            return;
          }
          session.setConfig('queue.overflow', value);
          setMessages(prev => [...prev, session.addMessage({ role: 'assistant', content: `Set queue.overflow=${value}` })]);
          return;
        }

        if (key === 'debounce_ms') {
          const debounceMs = Number.parseInt(value, 10);
          if (!Number.isFinite(debounceMs) || debounceMs < 0 || debounceMs > 60_000) {
            setMessages(prev => [...prev, session.addMessage({ role: 'assistant', content: 'Invalid debounce_ms. Use an integer between 0 and 60000.' })]);
            return;
          }
          session.setConfig('queue.debounce_ms', String(debounceMs));
          setMessages(prev => [...prev, session.addMessage({ role: 'assistant', content: `Set queue.debounce_ms=${debounceMs}` })]);
          return;
        }

        if (key === 'summarize_overflow') {
          const normalized = value.toLowerCase();
          if (normalized !== 'true' && normalized !== 'false') {
            setMessages(prev => [...prev, session.addMessage({ role: 'assistant', content: 'Invalid summarize_overflow. Use true or false.' })]);
            return;
          }
          session.setConfig('queue.summarize_overflow', normalized);
          setMessages(prev => [...prev, session.addMessage({ role: 'assistant', content: `Set queue.summarize_overflow=${normalized}` })]);
          return;
        }

        setMessages(prev => [...prev, session.addMessage({ role: 'assistant', content: 'Unknown queue key. Use mode, cap, overflow, debounce_ms, summarize_overflow.' })]);
        return;
      }

      case 'backend':
      case 'login':
      case 'pair':
      case 'elevate':
        setMessages(prev => [...prev, session.addMessage({ role: 'assistant', content: `/${command.type} is not supported in fullscreen mode.` })]);
        return;

      case 'message':
        break;

      default: {
        const exhaustive: never = command;
        throw new Error(`Unhandled command: ${JSON.stringify(exhaustive)}`);
      }
    }

    if (command.type !== 'message' || isStreaming) {
      return;
    }

    // Add user message to UI (and session if no agent — agent adds it internally)
    const userMessage: Message = { role: 'user', content: command.content };
    if (!agent) {
      const messageWithTimestamp = session.addMessage(userMessage);
      setMessages(prev => [...prev, messageWithTimestamp]);
    } else {
      setMessages(prev => [...prev, { ...userMessage, timestamp: Date.now() }]);
    }
    setScrollOffset(0);

    setIsStreaming(true);
    setStreamingContent('');
    toolLinesRef.current = [];
    abortRef.current = false;

    try {
      if (agent) {
        await agent.process(command.content);
        const usage = agent.getUsage();
        setTokenUsage({ inputTokens: usage.inputTokens, outputTokens: usage.outputTokens });
        setMessages(session.getHistory());
      } else if (modelClient.chatStream) {
        let fullContent = '';

        for await (const event of modelClient.chatStream({
          messages: session.getHistory(),
          system: systemPrompt,
        })) {
          if (abortRef.current) {
            fullContent += '\n\n[interrupted]';
            break;
          }

          if (event.type === 'content' && event.content) {
            fullContent += event.content;
            setStreamingContent(fullContent);
          }
          if (event.type === 'done' && event.usage) {
            const usage = event.usage;
            setTokenUsage(prev => ({
              inputTokens: prev.inputTokens + usage.inputTokens,
              outputTokens: prev.outputTokens + usage.outputTokens,
            }));
          }
          if (event.type === 'error') {
            throw event.error ?? new Error('Stream error');
          }
        }

        const assistantMessage: Message = { role: 'assistant', content: fullContent };
        setMessages(prev => [...prev, session.addMessage(assistantMessage)]);
      } else {
        const response = await modelClient.chat({
          messages: session.getHistory(),
          system: systemPrompt,
        });

        setTokenUsage(prev => ({
          inputTokens: prev.inputTokens + response.usage.inputTokens,
          outputTokens: prev.outputTokens + response.usage.outputTokens,
        }));

        const assistantMessage: Message = { role: 'assistant', content: response.content };
        setMessages(prev => [...prev, session.addMessage(assistantMessage)]);
      }
    } catch (error) {
      const msg = error instanceof Error ? error.message : 'Unknown error';
      setMessages(prev => [...prev, session.addMessage({ role: 'assistant', content: `Error: ${msg}` })]);
    } finally {
      setIsStreaming(false);
      setStreamingContent('');
    }
  }, [
    confirmation,
    session,
    agent,
    modelClient,
    modelRouter,
    systemPrompt,
    exit,
    onExit,
    isStreaming,
    messages.length,
    tokenUsage.inputTokens,
    tokenUsage.outputTokens,
    modelProviderConfigs,
  ]);

  return (
    <Box flexDirection="column" height="100%">
      <MessageList
        messages={messages}
        scrollOffset={scrollOffset}
        streamingContent={isStreaming ? streamingContent : undefined}
      />

      {confirmation ? (
        <Box paddingX={1} paddingY={0} borderStyle="round" borderColor="yellow">
          <Text color="yellow">
            Confirmation required: {confirmation.tool}{' '}
            {Object.keys(confirmation.args).length > 0 ? JSON.stringify(confirmation.args) : ''}
          </Text>
          <Text color="yellow">Press y to approve, n to deny.</Text>
        </Box>
      ) : null}

      <InputBar
        value={input}
        onChange={setInput}
        onSubmit={handleSubmit}
        isLoading={isStreaming || !!confirmation}
        placeholder={confirmation
          ? 'Confirmation required (press y/n)'
          : (isStreaming ? 'Flynn is typing... (Esc to cancel)' : 'Type a message... (Esc=exit, /help)')}
      />

      <StatusBar
        sessionId={session.id}
        messageCount={messages.length}
        model={currentModel}
        tokenUsage={{ input: tokenUsage.inputTokens, output: tokenUsage.outputTokens }}
        isStreaming={isStreaming}
      />
    </Box>
  );
}
