import React, { useCallback, useMemo, useState } from 'react';
import Box from '@mui/material/Box';
import Typography from '@mui/material/Typography';
import InputBase from '@mui/material/InputBase';
import IconButton from '@mui/material/IconButton';
import ArrowUpwardRoundedIcon from '@mui/icons-material/ArrowUpwardRounded';
import ToolCallBubble from '../tool-bubbles/ToolCallBubble';
import type { ToolPair } from '../tool-bubbles/ToolCallBubble';
import { parseShowUiPayload } from './showUiPayload';
import VendoredToolUi from '@toolui/VendoredToolUi';
import { API_BASE, getAuthToken } from '@/shared/config';

// The choice components that replaced AskUserQuestion, which always had an "Other" escape hatch.
const FREE_TEXT_COMPONENTS = new Set(['option-list', 'question-flow']);

interface AskUiBubbleProps {
  pair: ToolPair;
  sessionId: string;
  isPending: boolean;
  suppressReveal: boolean;
}

function parseResultResponse(pair: ToolPair): Record<string, unknown> | null {
  const rc = pair.result?.content;
  const text = typeof rc === 'string' ? rc : typeof rc === 'object' && rc?.text ? String(rc.text) : '';
  if (!text.startsWith('{')) return null;
  try {
    const parsed = JSON.parse(text);
    return parsed && typeof parsed === 'object' ? parsed : null;
  } catch {
    return null;
  }
}

/** An AskUI call: the live interactive component while the agent waits; its answered state after. */
function AskUiBubble({ pair, sessionId, isPending, suppressReveal }: AskUiBubbleProps): React.ReactElement {
  const payload = parseShowUiPayload(pair);
  const [submitted, setSubmitted] = useState(false);
  const [orphaned, setOrphaned] = useState(false);
  const [freeText, setFreeText] = useState('');
  // The choice captured at click time, so the component flips to its receipt the INSTANT the user
  // answers instead of staying clickable until the agent's tool result lands seconds later.
  const [localChoice, setLocalChoice] = useState<unknown>(undefined);
  const answered = parseResultResponse(pair);
  const freeTextAnswer =
    answered?.action === 'free_text' && answered.value && typeof answered.value === 'object'
      ? String((answered.value as Record<string, unknown>).text ?? '')
      : null;

  const componentId = payload && payload.component === 'vendored' ? String(payload.props.id || '') : '';

  const respond = useCallback(
    (response: Record<string, unknown>) => {
      if (submitted) return;
      setSubmitted(true);
      if (response.action !== 'free_text') {
        setLocalChoice(response.choice ?? response.value ?? undefined);
      }
      void fetch(`${API_BASE}/ui-requests/respond`, {
        method: 'POST',
        headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${getAuthToken()}` },
        body: JSON.stringify({ session_id: sessionId, component_id: componentId, response }),
      })
        .then(async (r) => {
          const body = r.ok ? await r.json().catch(() => null) : null;
          if (!r.ok || (body && body.gone)) {
            // Nothing parked server-side (agent gone or this is a replayed transcript): say so instead of silently swallowing the click.
            setSubmitted(false);
            setLocalChoice(undefined);
            setOrphaned(true);
          }
        })
        .catch(() => { setSubmitted(false); setLocalChoice(undefined); });
    },
    [submitted, sessionId, componentId],
  );

  const waiting = pair.result === null && !submitted;

  // Their embedded-actions contract: onAction(actionId, state) delivers the component's full state,
  // and the components ship their own footer actions (Clear/Confirm), so we only wire the callback.
  // 'cancel' is a local clear, never an answer; approval-card uses onConfirm/onCancel instead.
  const extraProps = useMemo(() => {
    if (!payload || payload.component !== 'vendored') return {};
    if (payload.name === 'approval-card') {
      return waiting
        ? {
            onConfirm: () => respond({ action: 'confirm', choice: 'approved' }),
            onCancel: () => respond({ action: 'cancel', choice: 'denied' }),
          }
        : { choice: (answered?.choice as string) ?? (localChoice as string | undefined) };
    }
    if (waiting) {
      return {
        onAction: (actionId: string, state: unknown) => {
          if (actionId === 'cancel') return;
          respond({ action: actionId, value: state ?? null });
        },
      };
    }
    // A free-text answer isn't an option id; passing it as `choice` would fail their contract.
    if (freeTextAnswer !== null) return {};
    if (answered && 'value' in answered) return { choice: answered.value };
    // Result not landed yet but the user already clicked: the captured choice renders the receipt now.
    return localChoice !== undefined ? { choice: localChoice } : {};
  }, [payload, waiting, respond, answered, freeTextAnswer, localChoice]);

  const submitFreeText = useCallback(() => {
    const text = freeText.trim();
    if (!text) return;
    respond({ action: 'free_text', value: { text } });
  }, [freeText, respond]);

  if (!payload || payload.component !== 'vendored' || !componentId) {
    return (
      <ToolCallBubble call={pair.call} result={pair.result} isPending={isPending} sessionId={sessionId} suppressReveal={suppressReveal} />
    );
  }

  // The vendored choice contracts carry no question field, so agents' title/description props silently vanish and the user sees options with no question; render them as a host header instead (ENG-227).
  const rawProps = (payload.props ?? {}) as Record<string, unknown>;
  const questionTitle: string = [rawProps.title, rawProps.question, rawProps.prompt, rawProps.heading]
    .find((v): v is string => typeof v === 'string' && v.trim().length > 0) ?? '';
  const questionDesc: string = typeof rawProps.description === 'string' && rawProps.description !== questionTitle
    ? rawProps.description : '';
  return (
    <Box sx={{ my: 1, contain: 'layout style' }} data-select-type="tool-ui-ask" data-select-id={pair.id} data-select-meta={JSON.stringify({ component: payload.name })}>
      {(questionTitle || questionDesc) && (
        <Box sx={{ mb: 0.75, px: 0.5 }}>
          {questionTitle && (
            <Typography sx={{ fontSize: '0.9375rem', fontWeight: 600, color: 'text.primary', lineHeight: 1.35 }}>
              {questionTitle}
            </Typography>
          )}
          {questionDesc && (
            <Typography sx={{ fontSize: '0.8125rem', color: 'text.secondary', mt: 0.25, lineHeight: 1.4 }}>
              {questionDesc}
            </Typography>
          )}
        </Box>
      )}
      <VendoredToolUi name={payload.name} props={payload.props} extraProps={extraProps} />
      {waiting && FREE_TEXT_COMPONENTS.has(payload.name) && (
        <Box
          component="form"
          onSubmit={(e: React.FormEvent) => { e.preventDefault(); submitFreeText(); }}
          sx={{
            display: 'flex',
            alignItems: 'center',
            gap: 0.5,
            mt: 0.75,
            px: 1.25,
            py: 0.25,
            borderRadius: 999,
            background: 'rgba(127,127,127,0.08)',
            border: '1px solid rgba(127,127,127,0.14)',
            maxWidth: 420,
          }}
        >
          <InputBase
            value={freeText}
            onChange={(e) => setFreeText(e.target.value)}
            placeholder="Or type your own answer..."
            inputProps={{ 'aria-label': 'Type your own answer' }}
            sx={{ flex: 1, fontSize: '0.8125rem' }}
          />
          <IconButton type="submit" size="small" disabled={!freeText.trim()} aria-label="Send answer">
            <ArrowUpwardRoundedIcon sx={{ fontSize: 16 }} />
          </IconButton>
        </Box>
      )}
      {freeTextAnswer !== null && (
        <Box sx={{ fontSize: '0.75rem', opacity: 0.75, pt: 0.75 }}>
          &#10003; Answered: {freeTextAnswer}
        </Box>
      )}
      {submitted && pair.result === null && (
        <Box sx={{ fontSize: '0.75rem', opacity: 0.55, pt: 0.5 }}>Sent to the agent...</Box>
      )}
      {orphaned && (
        <Box sx={{ fontSize: '0.75rem', opacity: 0.55, pt: 0.5 }}>
          No agent is waiting for this answer (the request expired or this is an old transcript).
        </Box>
      )}
    </Box>
  );
}

export default AskUiBubble;
