import { useEffect, useMemo, useRef, useState, type FormEvent, type KeyboardEvent } from 'react';
import type {
  ChatMessage,
  ComposerSubmission,
  ModelLoadProgress,
  ModelId,
  ModelOption,
  ShardRetryNotice,
  StreamState,
  ToolRun,
} from '../../lib/contracts';
import type { CodeTheme } from '../../lib/code-highlighter';
import { TOOL_CATALOG } from '../../lib/agent-tools';
import { segmentsForMessage } from '../../lib/message-segments';
import { ArtifactCard } from './ArtifactCard';
import { ComposerActivity } from './ComposerActivity';
import { MarkdownContent } from './MarkdownContent';

interface ChatSurfaceProps {
  activeModel: ModelOption;
  loadedModelId: ModelId | null;
  messages: ChatMessage[];
  streamState: StreamState;
  modelLoadProgress: ModelLoadProgress;
  toolsEnabled: boolean;
  runtimeStatus: string;
  error: string | null;
  shardRetry: ShardRetryNotice | null;
  theme: CodeTheme;
  onLoadModel: (modelId: ModelId) => void;
  onDismissError: () => void;
  onRetryShard: (modelId: ModelId) => void;
  onSend: (submission: ComposerSubmission) => void;
  onRegenerate: (messageId: string) => void;
  onEditUserMessage: (messageId: string, content: string) => void;
  onCancel: () => void;
  onToolsEnabledChange: (enabled: boolean) => void;
  onOpenConversationList: () => void;
  onOpenSettings: () => void;
}

export function ChatSurface({
  activeModel,
  loadedModelId,
  messages,
  streamState,
  modelLoadProgress,
  toolsEnabled,
  runtimeStatus,
  error,
  shardRetry,
  theme,
  onLoadModel,
  onDismissError,
  onRetryShard,
  onSend,
  onRegenerate,
  onEditUserMessage,
  onCancel,
  onToolsEnabledChange,
  onOpenConversationList,
  onOpenSettings,
}: ChatSurfaceProps) {
  const [prompt, setPrompt] = useState('');
  const isLoading = streamState === 'loading';
  const isStreaming = streamState === 'streaming';
  const isBusy = isStreaming || isLoading;
  const isLoaded = loadedModelId === activeModel.id;
  const runtimeContextLimit = activeModel.id === 'bonsai-27b'
    ? activeModel.defaultContext
    : activeModel.contextLimit;
  const logDate = useMemo(() => new Intl.DateTimeFormat('en-GB', {
    day: '2-digit', month: 'long', year: 'numeric',
  }).format(new Date()), []);

  const submit = () => {
    const trimmed = prompt.trim();
    if (!trimmed || isBusy || !isLoaded) return;
    onSend({ prompt: trimmed, toolsEnabled });
    setPrompt('');
  };

  const handleSubmit = (event: FormEvent<HTMLFormElement>) => {
    event.preventDefault();
    submit();
  };

  const handleKeyDown = (event: KeyboardEvent<HTMLTextAreaElement>) => {
    if (event.key === 'Enter' && !event.shiftKey) {
      event.preventDefault();
      submit();
    }
  };

  return (
    <main className="chat-surface">
      <header className="chat-header">
        <div className="chat-header-main">
          <button className="chat-list-button" type="button" disabled={isBusy} onClick={onOpenConversationList} aria-label="Open chat list">
            <BackIcon /> Chats
          </button>
          <button className="chat-settings-button" type="button" disabled={isBusy} onClick={onOpenSettings}>Chat settings</button>
          <div className="chat-title">
            <h2>{activeModel.label}</h2>
            <span className="architecture-subtitle">{activeModel.architectureLabel}</span>
          </div>
        </div>
        <div className="chat-header-meta">
          <span>{activeModel.footprint} download</span>
          <span>{Math.round(runtimeContextLimit / 1024)}K max context</span>
          <span className={`live-label${isLoaded ? '' : ' is-dormant'}`}><i /> {isLoaded ? 'loaded' : 'not loaded'}</span>
        </div>
      </header>

      {shardRetry && (
        <div className="runtime-error download-retry" role="alert">
          <div>
            <strong>Shard {shardRetry.shardIndex + 1}/{shardRetry.shardCount} failed</strong>
            <span>{shardRetry.shardPath} · {shardRetry.error}</span>
          </div>
          <button type="button" disabled={isBusy} onClick={() => onRetryShard(shardRetry.modelId)}>
            Retry shard from byte zero
          </button>
        </div>
      )}

      {error && !shardRetry && (
        <div className="runtime-error" role="alert">
          <div><strong>Runtime stopped safely</strong><span>{error}</span></div>
          <button type="button" onClick={onDismissError}>Dismiss</button>
        </div>
      )}

      <section className="transcript" aria-label="Conversation transcript" aria-live="polite">
        <div className="transcript-date"><span>{logDate}</span></div>
        {messages.length === 0 && (
          <div className="transcript-empty">
            <h3>Start a chat</h3>
            <p>Choose a model, keep the backend on Auto, and send a prompt. The model is downloaded once and runs inside this browser.</p>
            <button className="system-instructions-button" type="button" onClick={onOpenSettings}>Set system instructions</button>
          </div>
        )}
        {messages.map((message) => (
          <Message
            key={message.id}
            message={message}
            theme={theme}
            regenerateDisabled={isBusy || !isLoaded}
            onRegenerate={onRegenerate}
            onEditUserMessage={onEditUserMessage}
          />
        ))}
      </section>

      <form className="composer" onSubmit={handleSubmit}>
        <ComposerActivity
          modelId={activeModel.id}
          streamState={streamState}
          progress={modelLoadProgress}
        />
        <div className="composer-meta">
          <div className="tool-controls">
            <button
              className={`tool-toggle${toolsEnabled ? ' is-on' : ''}`}
              type="button"
              aria-pressed={toolsEnabled}
              onClick={() => onToolsEnabledChange(!toolsEnabled)}
            >
              <ToolIcon /> Tools {toolsEnabled ? 'on' : 'off'}
            </button>
            <div className="tool-info">
              <button type="button" aria-label="Show available tools" aria-describedby="tool-reference" title="Available tools"><InfoIcon /></button>
              <div className="tool-popover" id="tool-reference" role="tooltip">
                <header>
                  <strong>Available tools</strong>
                  <span>{TOOL_CATALOG.length} capabilities</span>
                </header>
                <ul>
                  {TOOL_CATALOG.map((tool) => (
                    <li key={tool.name}>
                      <div><code>{tool.name}</code><small>{tool.scope}</small></div>
                      <p>{tool.description}</p>
                    </li>
                  ))}
                </ul>
                <footer>Inference and local tools stay in this browser. Network tools ask before sending a query.</footer>
              </div>
            </div>
          </div>
          <span>{runtimeStatus}</span>
        </div>
        <div className="composer-field">
          <label htmlFor="bonsai-prompt">Prompt</label>
          <textarea
            id="bonsai-prompt"
            aria-label="Ask Bonsai"
            value={prompt}
            onChange={(event) => setPrompt(event.target.value)}
            onKeyDown={handleKeyDown}
            placeholder={isLoaded ? 'Ask anything, or request an HTML artifact…' : 'Write while the model loads, then run locally…'}
            rows={2}
            disabled={isStreaming}
          />
          {isBusy ? (
            <button className="send-button stop" type="button" onClick={onCancel}>
              <span aria-hidden="true" /> {isLoading ? 'Stop load' : 'Stop'}
            </button>
          ) : !isLoaded ? (
            <button className="send-button load" type="button" onClick={() => onLoadModel(activeModel.id)}>
              Load model <DownloadIcon />
            </button>
          ) : (
            <button className="send-button" type="submit" disabled={!prompt.trim()}>
              Run <ArrowIcon />
            </button>
          )}
        </div>
      </form>
    </main>
  );
}

interface MessageProps {
  message: ChatMessage;
  theme: CodeTheme;
  regenerateDisabled: boolean;
  onRegenerate: (messageId: string) => void;
  onEditUserMessage: (messageId: string, content: string) => void;
}

function Message({ message, theme, regenerateDisabled, onRegenerate, onEditUserMessage }: MessageProps) {
  const streaming = message.state === 'streaming';
  const [copyState, setCopyState] = useState<'idle' | 'copied' | 'error'>('idle');
  const [editing, setEditing] = useState(false);
  const [editText, setEditText] = useState(message.content);
  const copyResetRef = useRef<number | null>(null);

  useEffect(() => () => {
    if (copyResetRef.current !== null) window.clearTimeout(copyResetRef.current);
  }, []);

  const scheduleCopyReset = () => {
    if (copyResetRef.current !== null) window.clearTimeout(copyResetRef.current);
    copyResetRef.current = window.setTimeout(() => setCopyState('idle'), 1_600);
  };

  const copyMessage = () => {
    if (!message.content || !navigator.clipboard?.writeText) {
      setCopyState('error');
      scheduleCopyReset();
      return;
    }
    void navigator.clipboard.writeText(message.content)
      .then(() => setCopyState('copied'))
      .catch(() => setCopyState('error'))
      .finally(scheduleCopyReset);
  };

  const regenerateLabel = message.role === 'user'
    ? 'Regenerate from this message'
    : 'Regenerate this response';
  const segments = message.role === 'assistant' ? segmentsForMessage(message) : [];
  const tools = new Map((message.tools ?? []).map((tool) => [tool.id, tool]));
  const artifacts = new Map((message.artifacts ?? []).map((artifact) => [artifact.id, artifact]));
  const lastToolForArtifact = new Map<string, number>();
  segments.forEach((segment, index) => {
    if (segment.kind !== 'tool') return;
    const artifactId = tools.get(segment.toolCallId)?.artifactId;
    if (artifactId && artifacts.has(artifactId)) lastToolForArtifact.set(artifactId, index);
  });
  const lastReasoningId = [...segments].reverse().find((segment) => segment.kind === 'reasoning')?.id;
  const saveEdit = () => {
    const normalized = editText.trim();
    if (!normalized || normalized === message.content.trim() || regenerateDisabled) return;
    setEditing(false);
    onEditUserMessage(message.id, normalized);
  };

  return (
    <article className={`message message-${message.role}${streaming ? ' is-streaming' : ''}${message.error ? ' has-error' : ''}${message.artifacts?.length ? ' has-artifacts' : ''}`}>
      <header>
        <strong>{message.role === 'assistant' ? 'Bonsai' : message.role === 'system' ? 'System' : 'You'}</strong>
        {message.editedAt && <span className="message-edited">edited</span>}
        <time>{message.timestamp}</time>
      </header>
      <div className="message-content">
        {message.role === 'user' && editing ? (
          <form className="message-edit-form" onSubmit={(event) => { event.preventDefault(); saveEdit(); }}>
            <label htmlFor={`edit-${message.id}`}>Edit message</label>
            <textarea
              id={`edit-${message.id}`}
              aria-label="Edit message"
              value={editText}
              rows={Math.min(10, Math.max(3, editText.split('\n').length + 1))}
              autoFocus
              onChange={(event) => setEditText(event.target.value)}
              onKeyDown={(event) => {
                if (event.key === 'Escape') {
                  event.preventDefault();
                  setEditText(message.content);
                  setEditing(false);
                }
                if (event.key === 'Enter' && (event.metaKey || event.ctrlKey)) {
                  event.preventDefault();
                  saveEdit();
                }
              }}
            />
            <div>
              <button type="button" onClick={() => { setEditText(message.content); setEditing(false); }}>Cancel</button>
              <button type="submit" disabled={!editText.trim() || editText.trim() === message.content.trim()}>Save &amp; regenerate</button>
            </div>
          </form>
        ) : message.role === 'assistant' ? segments.map((segment, index) => {
          if (segment.kind === 'reasoning') {
            const active = message.isThinking && segment.id === lastReasoningId;
            return (
              <div className="message-segment message-segment-reasoning" key={segment.id}>
                <details className="reasoning-block" open={active}>
                  <summary>{active ? 'Reasoning in progress…' : 'Reasoning trace'}</summary>
                  <MarkdownContent content={segment.content} theme={theme} streaming={streaming && active} />
                </details>
              </div>
            );
          }
          if (segment.kind === 'text') {
            return <div className="message-segment message-segment-text" key={segment.id}><MarkdownContent content={segment.content} theme={theme} streaming={streaming} /></div>;
          }
          if (segment.kind === 'artifact') {
            const artifact = artifacts.get(segment.artifactId);
            return artifact ? <div className="message-segment message-segment-artifact" key={segment.id}><ArtifactCard artifact={artifact} /></div> : null;
          }
          const tool = tools.get(segment.toolCallId);
          if (!tool) return null;
          const artifact = tool.artifactId && lastToolForArtifact.get(tool.artifactId) === index
            ? artifacts.get(tool.artifactId)
            : undefined;
          return (
            <div className="message-segment message-segment-tool" key={segment.id}>
              <ToolCall tool={tool} />
              {artifact && <ArtifactCard artifact={artifact} />}
            </div>
          );
        }) : message.content ? <MarkdownContent content={message.content} theme={theme} streaming={streaming} /> : null}
        {streaming && message.toolActivity && (
          <p className="tool-generation" aria-label={`Generating ${message.toolActivity.name} tool call`}>
            <span>Calling</span>
            <code>{message.toolActivity.name}</code>
            <span>· {message.toolActivity.argumentCharacters.toLocaleString('en-US')} chars</span>
            <span className="stream-cursor" aria-hidden="true" />
          </p>
        )}
        {segments.length === 0 && !message.content && streaming && !message.toolActivity && (
          <p className="generation-placeholder">
            Generating… <span className="stream-cursor" aria-label="Streaming response" />
          </p>
        )}
        {(segments.length > 0 || message.content) && streaming && !message.toolActivity && (
          <span className="stream-cursor" aria-label="Streaming response" />
        )}
        {message.error && <p className="message-error">{message.error}</p>}
      </div>
      {message.metrics && (
        <footer>
          <span>{message.metrics.tokens} tok</span>
          {message.metrics.promptTokensPerSecond !== undefined && (
            <span>Prefill {message.metrics.promptTokensPerSecond.toFixed(1)} tok/s</span>
          )}
          <span>Decode {message.metrics.tokensPerSecond.toFixed(1)} tok/s</span>
          <span>TTFT {message.metrics.timeToFirstTokenMs} ms</span>
        </footer>
      )}
      {!editing && <div className="message-actions" aria-label="Message actions">
        <button
          type="button"
          disabled={!message.content}
          onClick={copyMessage}
          aria-label={copyState === 'copied' ? 'Message copied' : 'Copy message'}
        >
          <CopyIcon />
          {copyState === 'copied' ? 'Copied' : copyState === 'error' ? 'Copy failed' : 'Copy'}
        </button>
        {message.role === 'user' && (
          <button
            type="button"
            disabled={regenerateDisabled}
            onClick={() => { setEditText(message.content); setEditing(true); }}
            aria-label="Edit message and regenerate response"
            title="Edit this message, delete later turns, and generate a new response"
          >
            <EditIcon /> Edit
          </button>
        )}
        {message.role !== 'system' && (
          <button
            type="button"
            disabled={regenerateDisabled}
            onClick={() => onRegenerate(message.id)}
            aria-label={regenerateLabel}
            title={message.role === 'user'
              ? 'Delete later turns and answer this message again'
              : 'Delete this response and later turns, then generate it again'}
          >
            <RegenerateIcon /> Regenerate
          </button>
        )}
      </div>}
    </article>
  );
}

function ToolCall({ tool }: { tool: ToolRun }) {
  const output = parseToolOutput(tool.output);
  return (
    <section className={`tool-call tool-call-${tool.state}`}>
      <header>
        <span className={`tool-state tool-state-${tool.state}`} />
        <code>{tool.name}</code>
        <span>{tool.state}</span>
      </header>
      <ToolResult tool={tool} output={output} />
      <details className="tool-call-raw">
        <summary>Arguments and raw result</summary>
        <div>
          <strong>Arguments</strong>
          <pre>{prettyJson(tool.input)}</pre>
          {tool.output && <><strong>Result</strong><pre>{prettyJson(tool.output)}</pre></>}
        </div>
      </details>
    </section>
  );
}

function ToolResult({ tool, output }: { tool: ToolRun; output: Record<string, unknown> | null }) {
  if (tool.state === 'running' || tool.state === 'queued') {
    return <p className="tool-result-status">Running locally…</p>;
  }
  if (!output) return tool.output ? <p className="tool-result-status">Result returned</p> : null;
  if (output.ok === false) {
    return <p className="tool-result-status is-error">{String(output.error ?? 'Tool failed')}</p>;
  }
  if (tool.name === 'js_eval' && 'value' in output) {
    const logs = Array.isArray(output.logs) ? output.logs.map(String) : [];
    return <div className="tool-value-result"><span>Result</span><code>{String(output.value)}</code>{logs.length > 0 && <small>{logs.join('\n')}</small>}</div>;
  }
  if (tool.name === 'web_search' && Array.isArray(output.results)) {
    return (
      <ol className="tool-search-results">
        {output.results.slice(0, 8).map((item, index) => {
          const result = isRecord(item) ? item : {};
          const url = safeHttpUrl(result.url);
          return (
            <li key={`${String(result.title ?? 'Result')}-${index}`}>
              {url ? <a href={url} target="_blank" rel="noreferrer">{String(result.title ?? url)}</a> : <strong>{String(result.title ?? 'Result')}</strong>}
              {Boolean(result.snippet) && <p>{String(result.snippet)}</p>}
            </li>
          );
        })}
      </ol>
    );
  }
  const evaluation = isRecord(output.evaluation) ? output.evaluation : null;
  if (evaluation) {
    const errors = Array.isArray(evaluation.errors) ? evaluation.errors.length : 0;
    return <p className={`tool-result-status${evaluation.status === 'passed' ? ' is-passed' : ' is-error'}`}>Artifact test {String(evaluation.status ?? 'complete')}{errors > 0 ? ` · ${errors} ${errors === 1 ? 'error' : 'errors'}` : ''}</p>;
  }
  if (typeof output.action === 'string') {
    return <p className="tool-result-status is-passed">Memory {output.action} complete</p>;
  }
  return <p className="tool-result-status is-passed">Result returned</p>;
}

function parseToolOutput(value: string | undefined): Record<string, unknown> | null {
  if (!value) return null;
  try {
    const parsed: unknown = JSON.parse(value);
    return isRecord(parsed) ? parsed : null;
  } catch {
    return null;
  }
}

function prettyJson(value: string): string {
  try { return JSON.stringify(JSON.parse(value), null, 2); } catch { return value; }
}

function isRecord(value: unknown): value is Record<string, unknown> {
  return typeof value === 'object' && value !== null && !Array.isArray(value);
}

function safeHttpUrl(value: unknown): string | undefined {
  if (typeof value !== 'string') return undefined;
  try {
    const url = new URL(value);
    return url.protocol === 'http:' || url.protocol === 'https:' ? url.href : undefined;
  } catch {
    return undefined;
  }
}

function ToolIcon() {
  return (
    <svg viewBox="0 0 20 20" aria-hidden="true">
      <path d="m12.7 3.2-2.4 2.4 4.1 4.1 2.4-2.4a5 5 0 0 1-6.4 6.4l-5 5-3.1-3.1 5-5a5 5 0 0 1 5.4-7.4Z" />
    </svg>
  );
}

function InfoIcon() {
  return (
    <svg viewBox="0 0 20 20" aria-hidden="true">
      <circle cx="10" cy="10" r="7" />
      <path d="M10 8.4v5M10 5.8h.01" />
    </svg>
  );
}

function CopyIcon() {
  return (
    <svg viewBox="0 0 20 20" aria-hidden="true">
      <rect x="6.5" y="6.5" width="9" height="9" rx="1.5" />
      <path d="M13.5 6.5V5A1.5 1.5 0 0 0 12 3.5H5A1.5 1.5 0 0 0 3.5 5v7A1.5 1.5 0 0 0 5 13.5h1.5" />
    </svg>
  );
}

function RegenerateIcon() {
  return (
    <svg viewBox="0 0 20 20" aria-hidden="true">
      <path d="M15.7 7.2A6.3 6.3 0 1 0 16 12M15.7 7.2V3.8m0 3.4h-3.4" />
    </svg>
  );
}

function EditIcon() {
  return (
    <svg viewBox="0 0 20 20" aria-hidden="true">
      <path d="m4 14.8.5-3.4 7.8-7.8a1.5 1.5 0 0 1 2.1 0l2 2a1.5 1.5 0 0 1 0 2.1l-7.8 7.8-3.4.5L4 14.8Z" />
      <path d="m11.3 4.6 4.1 4.1" />
    </svg>
  );
}

function BackIcon() {
  return <svg viewBox="0 0 20 20" aria-hidden="true"><path d="m11.5 5-5 5 5 5M7 10h7" /></svg>;
}

function ArrowIcon() {
  return (
    <svg viewBox="0 0 20 20" aria-hidden="true">
      <path d="M3 10h13M11 5l5 5-5 5" />
    </svg>
  );
}

function DownloadIcon() {
  return (
    <svg viewBox="0 0 20 20" aria-hidden="true">
      <path d="M10 2v11m-4-4 4 4 4-4M3 17h14" />
    </svg>
  );
}
