import {
  CodeOutlined,
  CheckCircleOutlined,
  DownOutlined,
  EyeOutlined,
  ExpandOutlined,
  LeftOutlined,
  LinkOutlined,
  LoadingOutlined,
  MessageOutlined,
  RightOutlined,
  UpOutlined,
} from '@ant-design/icons';
import { App, Button, Input, Spin, Tabs, Typography } from 'antd';
import { useEffect, useMemo, useState, type KeyboardEvent as ReactKeyboardEvent } from 'react';
import { StepperUI } from '../../../components/stepper';
import { InlineImage } from '../../../components/common/InlineImage';
import { MarkdownPreviewContent } from '../../../components/markdownPreview/MarkdownPreviewContent';
import { FullscreenPreviewModal, ZoomablePreviewSurface } from '../../../components/previewer';
import { renderEditorBlock } from '../../../components/previewer/renderers';
import { ChatPreviewBody } from './ChatPreviewBody';
import { isMarkdownContentType, isMarkdownResourceUrl, normalizeChatResourceUrl } from './chatResourceUrl';
import { openChatExternalLink } from './linkNavigation';
import { classifyPreviewKind } from './previewKind';
import { resolvePromptPreviewOptionValue } from './promptPreviewState';
import type { ChatMessagePart } from './types';

const { Paragraph, Text } = Typography;

type PromptTarget = Extract<ChatMessagePart, { type: 'prompt' }>;
type PromptOption = PromptTarget['options'][number];
type PromptPreview = NonNullable<PromptOption['preview']>;
type PromptStep = {
  key: string;
  title: string;
  description?: string | null;
  submitLabel?: string | null;
  mode?: 'queue' | 'direct' | null;
  multiple?: boolean;
  optional?: boolean;
  responseTemplate?: string | null;
  freeTextLabel?: string | null;
  freeTextPlaceholder?: string | null;
  selectedValues?: string[];
  options: PromptOption[];
};

export type PromptStepDraftSelection = {
  stepKey: string;
  stepTitle: string;
  selectedValues: string[];
  freeText: string;
  skipped?: boolean;
};

export type PromptDraftSelection = {
  selectedValues: string[];
  freeText: string;
  stepSelections?: PromptStepDraftSelection[];
  summaryText?: string | null;
};

function buildOptionSelectionText(options: PromptOption[], selectedValues: string[]) {
  const selectedOptions = options.filter((option) => selectedValues.includes(option.value));

  return selectedOptions
    .map((option) => (option.label === option.value ? option.label : `${option.label} (${option.value})`))
    .join(', ');
}

function buildSelectionText(target: PromptTarget, selectedValues: string[]) {
  return buildOptionSelectionText(target.options, selectedValues);
}

function normalizePromptSteps(target: PromptTarget): PromptStep[] {
  if (Array.isArray(target.steps) && target.steps.length > 0) {
    return target.steps.map((step, index) => ({
      key: step.key || `step-${index + 1}`,
      title: step.title,
      description: step.description ?? null,
      submitLabel: step.submitLabel ?? null,
      mode: step.mode ?? target.mode ?? null,
      multiple: step.multiple === true,
      optional: step.optional === true,
      responseTemplate: step.responseTemplate ?? null,
      freeTextLabel: step.freeTextLabel ?? null,
      freeTextPlaceholder: step.freeTextPlaceholder ?? null,
      selectedValues: step.selectedValues ?? [],
      options: step.options,
    }));
  }

  return [
    {
      key: 'default',
      title: target.title,
      description: target.description ?? null,
      submitLabel: target.submitLabel ?? null,
      mode: target.mode ?? null,
      multiple: target.multiple === true,
      optional: false,
      responseTemplate: target.responseTemplate ?? null,
      freeTextLabel: target.freeTextLabel ?? null,
      freeTextPlaceholder: target.freeTextPlaceholder ?? null,
      selectedValues: target.selectedValues ?? [],
      options: target.options,
    },
  ];
}

function buildStepSelectionText(step: PromptStep, selectedValues: string[]) {
  return buildOptionSelectionText(step.options, selectedValues);
}

function getPreviewablePromptOptions(step: PromptStep | null | undefined) {
  if (!step) {
    return [];
  }

  return step.options.filter((option) => option.preview);
}

function replacePromptTemplate(
  template: string,
  replacements: Record<string, string>,
) {
  return Object.entries(replacements).reduce(
    (resolved, [key, value]) => resolved.replaceAll(`{{${key}}}`, value),
    template,
  );
}

function normalizePromptTemplateKey(value: string) {
  const normalized = value.trim().replace(/[^a-zA-Z0-9]+/g, '_').replace(/^_+|_+$/g, '');
  return normalized || 'step';
}

function buildStepTemplateReplacements(steps: PromptStep[], stepSelections: PromptStepDraftSelection[]) {
  return stepSelections.reduce<Record<string, string>>((replacements, selection, index) => {
    const step = steps.find((candidate) => candidate.key === selection.stepKey) ?? null;
    const stepKey = normalizePromptTemplateKey(selection.stepKey || step?.key || `step_${index + 1}`);
    const stepTitle = selection.stepTitle || step?.title || `단계 ${index + 1}`;
    const selectedOptions = step?.options.filter((option) => selection.selectedValues.includes(option.value)) ?? [];
    const selectedValues = selection.selectedValues.join(', ');
    const selectedLabels = selectedOptions.map((option) => option.label).join(', ');
    const selectionText = step ? buildStepSelectionText(step, selection.selectedValues) : selectedValues;
    const trimmedFreeText = selection.freeText.trim();
    const summaryText = selection.skipped
      ? `${stepTitle}: 건너뜀`
      : `${stepTitle}: ${selectionText || '선택 없음'}${trimmedFreeText ? ` / 추가 요청: ${trimmedFreeText.replace(/\n+/g, ' ')}` : ''}`;

    replacements[`${stepKey}_title`] = stepTitle;
    replacements[`${stepKey}_value`] = selection.selectedValues[0] ?? '';
    replacements[`${stepKey}_values`] = selectedValues;
    replacements[`${stepKey}_label`] = selectedOptions[0]?.label ?? '';
    replacements[`${stepKey}_labels`] = selectedLabels;
    replacements[`${stepKey}_text`] = selectionText;
    replacements[`${stepKey}_summary`] = summaryText;
    replacements[`${stepKey}_free_text`] = trimmedFreeText;
    replacements[`${stepKey}_free_text_block`] = trimmedFreeText ? `추가 요청:\n${trimmedFreeText}` : '';
    return replacements;
  }, {});
}

function normalizePromptDraftSelection(
  target: PromptTarget,
  selectionOrSelectedValues: PromptDraftSelection | string[],
  freeText = '',
) {
  if (Array.isArray(selectionOrSelectedValues)) {
    return {
      selectedValues: selectionOrSelectedValues,
      freeText,
      stepSelections: undefined,
      summaryText: null,
    } satisfies PromptDraftSelection;
  }

  if (selectionOrSelectedValues.stepSelections && selectionOrSelectedValues.stepSelections.length > 0) {
    return selectionOrSelectedValues;
  }

  const steps = normalizePromptSteps(target);

  if (steps.length <= 1) {
    return selectionOrSelectedValues;
  }

  return {
    ...selectionOrSelectedValues,
    stepSelections: [
      {
        stepKey: steps[0]?.key ?? 'default',
        stepTitle: steps[0]?.title ?? target.title,
        selectedValues: selectionOrSelectedValues.selectedValues,
        freeText: selectionOrSelectedValues.freeText,
      },
    ],
  } satisfies PromptDraftSelection;
}

function buildPromptDraftSummaryText(target: PromptTarget, draft: PromptDraftSelection) {
  const steps = normalizePromptSteps(target);

  if (steps.length <= 1) {
    return buildSelectionText(target, draft.selectedValues);
  }

  const stepSelections = draft.stepSelections ?? [];
  const completedCount = stepSelections.filter((selection) => selection.skipped || selection.selectedValues.length > 0).length;
  const totalCount = steps.length;
  const latestSelection = [...stepSelections].reverse().find((selection) => selection.selectedValues.length > 0);
  const latestLabel = latestSelection
    ? buildStepSelectionText(
        steps.find((step) => step.key === latestSelection.stepKey) ?? steps[0],
        latestSelection.selectedValues,
      )
    : '';

  return latestLabel ? `${completedCount}/${totalCount} 단계 · ${latestLabel}` : `${completedCount}/${totalCount} 단계 선택`;
}

export function buildPromptResponseText(
  target: PromptTarget,
  selectionOrSelectedValues: PromptDraftSelection | string[],
  freeText = '',
) {
  const steps = normalizePromptSteps(target);
  const draft = normalizePromptDraftSelection(target, selectionOrSelectedValues, freeText);

  if (steps.length <= 1) {
    const selectedOptions = target.options.filter((option) => draft.selectedValues.includes(option.value));
    const selectedLabels = selectedOptions.map((option) => option.label);
    const selectedValue = draft.selectedValues[0] ?? '';
    const selectedLabel = selectedLabels[0] ?? '';
    const selectionText = buildSelectionText(target, draft.selectedValues);
    const trimmedFreeText = draft.freeText.trim();
    const template =
      target.responseTemplate?.trim() ||
      `프롬프트 "${target.title}"에서 "${selectionText}" 항목을 선택했습니다. 이 선택을 기준으로 다음 단계를 이어서 진행해 주세요.`;

    const resolvedText = replacePromptTemplate(template, {
      prompt_title: target.title,
      selection_value: selectedValue,
      selection_values: draft.selectedValues.join(', '),
      selection_label: selectedLabel,
      selection_labels: selectedLabels.join(', '),
      selection_text: selectionText,
      custom_text: trimmedFreeText,
      custom_text_block: trimmedFreeText ? `추가 요청:\n${trimmedFreeText}` : '',
      step_summaries: selectionText,
      step_count: draft.selectedValues.length > 0 ? '1' : '0',
    });

    if (!trimmedFreeText || resolvedText.includes(trimmedFreeText)) {
      return resolvedText;
    }

    return `${resolvedText}\n\n추가 요청:\n${trimmedFreeText}`;
  }

  const stepSelections = (draft.stepSelections ?? []).filter((selection) => {
    if (selection.skipped) {
      return true;
    }

    return selection.selectedValues.length > 0 || selection.freeText.trim().length > 0;
  });
  const aggregateSelectedValues = stepSelections.flatMap((selection) => selection.selectedValues);
  const aggregateSelectedLabels = stepSelections.flatMap((selection) => {
    const step = steps.find((candidate) => candidate.key === selection.stepKey);
    if (!step) {
      return [];
    }

    return step.options.filter((option) => selection.selectedValues.includes(option.value)).map((option) => option.label);
  });
  const stepSummaryLines = stepSelections.map((selection, index) => {
    const step = steps.find((candidate) => candidate.key === selection.stepKey);
    const stepTitle = selection.stepTitle || step?.title || `단계 ${index + 1}`;

    if (selection.skipped) {
      return `${index + 1}. ${stepTitle}: 건너뜀`;
    }

    const selectionText = step ? buildStepSelectionText(step, selection.selectedValues) : selection.selectedValues.join(', ');
    const freeTextSummary = selection.freeText.trim()
      ? `\n   추가 요청: ${selection.freeText.trim().replace(/\n+/g, ' ')}`
      : '';

    return `${index + 1}. ${stepTitle}: ${selectionText || '선택 없음'}${freeTextSummary}`;
  });
  const trimmedFreeText = draft.freeText.trim();
  const lastMeaningfulSelection = [...stepSelections].reverse().find(
    (selection) => selection.skipped || selection.selectedValues.length > 0 || selection.freeText.trim().length > 0,
  );
  const lastMeaningfulStep = lastMeaningfulSelection
    ? steps.find((candidate) => candidate.key === lastMeaningfulSelection.stepKey) ?? null
    : null;
  const template =
    lastMeaningfulStep?.responseTemplate?.trim() ||
    target.responseTemplate?.trim() ||
    `프롬프트 "${target.title}" 단계 선택을 정리했습니다.\n{{step_summaries}}\n{{custom_text_block}}`;
  const resolvedText = replacePromptTemplate(template, {
    prompt_title: target.title,
    selection_value: aggregateSelectedValues[0] ?? '',
    selection_values: aggregateSelectedValues.join(', '),
    selection_label: aggregateSelectedLabels[0] ?? '',
    selection_labels: aggregateSelectedLabels.join(', '),
    selection_text: stepSummaryLines.join('\n'),
    custom_text: trimmedFreeText,
    custom_text_block: trimmedFreeText ? `최종 요청:\n${trimmedFreeText}` : '',
    step_summaries: stepSummaryLines.join('\n'),
    step_count: String(stepSelections.length),
    ...buildStepTemplateReplacements(steps, stepSelections),
  }).trim();

  if (!trimmedFreeText || resolvedText.includes(trimmedFreeText)) {
    return resolvedText;
  }

  return `${resolvedText}\n\n최종 요청:\n${trimmedFreeText}`;
}

function buildHtmlFrameDocument(html: string, sourceUrl?: string | null) {
  const trimmed = html.trim();

  if (!trimmed) {
    return '<!doctype html><html><body></body></html>';
  }

  const baseHref = (() => {
    try {
      return sourceUrl ? new URL('.', sourceUrl).toString() : window.location.href;
    } catch {
      return typeof window !== 'undefined' ? window.location.href : 'about:blank';
    }
  })();
  const baseTag = `<base href="${baseHref}">`;

  if (/<head(\s|>)/i.test(trimmed)) {
    return trimmed.replace(/<head(\s*[^>]*)>/i, (match) => `${match}${baseTag}`);
  }

  if (/<html(\s|>)/i.test(trimmed)) {
    return trimmed.replace(/<html(\s*[^>]*)>/i, (match) => `${match}<head>${baseTag}</head>`);
  }

  return `<!doctype html><html><head>${baseTag}</head><body>${trimmed}</body></html>`;
}

function isHtmlLikeUrl(url: string) {
  try {
    const parsed = new URL(url, typeof window !== 'undefined' ? window.location.href : 'http://localhost');
    return /\.(html?)$/i.test(parsed.pathname);
  } catch {
    return false;
  }
}

function canShowHtmlPreviewActions(preview: PromptPreview | null | undefined) {
  if (!preview) {
    return false;
  }

  if (preview.type === 'html') {
    return true;
  }

  if (preview.type !== 'resource') {
    return false;
  }

  if (preview.content?.trim()) {
    return /<(?:!doctype\s+html|html|head|body|main|section|article|div)\b/i.test(preview.content);
  }

  return Boolean(preview.url && isHtmlLikeUrl(preview.url));
}

function canRenderFramePreview(url: string) {
  if (typeof window === 'undefined') {
    return false;
  }

  try {
    const parsed = new URL(url, window.location.href);
    return parsed.origin === window.location.origin;
  } catch {
    return false;
  }
}

function shouldRenderAsHtmlDocument(preview: PromptPreview, remoteContentType: string | null, remoteContent: string | null) {
  if (preview.type === 'html') {
    return true;
  }

  const normalizedContentType = remoteContentType?.toLowerCase() ?? '';

  if (normalizedContentType.includes('text/html')) {
    return true;
  }

  if (preview.url && isHtmlLikeUrl(preview.url)) {
    return true;
  }

  return Boolean(remoteContent && /<(?:!doctype\s+html|html|head|body|main|section|article|div)\b/i.test(remoteContent));
}

function isAppRouteUrl(url: string) {
  if (typeof window === 'undefined') {
    return false;
  }

  try {
    const parsed = new URL(url, window.location.href);
    const pathname = parsed.pathname.toLowerCase();
    const hasKnownFileExtension = /\.[a-z0-9]+$/i.test(pathname);
    const isInternalResource =
      pathname.startsWith('/api/chat/resources/') || pathname.startsWith('/public/.codex_chat/') || pathname.startsWith('/.codex_chat/');
    return parsed.origin === window.location.origin && !hasKnownFileExtension && !isInternalResource;
  } catch {
    return false;
  }
}

function isHtmlFallbackPreview(preview: PromptPreview, htmlContent: string) {
  const normalizedPreview = htmlContent.trimStart().toLowerCase();

  if (!normalizedPreview) {
    return false;
  }

  if (preview.url && isAppRouteUrl(preview.url)) {
    return true;
  }

  return (
    (normalizedPreview.startsWith('<!doctype html') ||
      normalizedPreview.startsWith('<html') ||
      normalizedPreview.includes('<head') ||
      normalizedPreview.includes('<body')) &&
    normalizedPreview.includes('<div id="root"')
  );
}

function isTextLikeContentType(contentType: string | null) {
  if (!contentType) {
    return false;
  }

  const normalized = contentType.toLowerCase();
  return (
    normalized.startsWith('text/') ||
    normalized.includes('json') ||
    normalized.includes('xml') ||
    normalized.includes('javascript') ||
    normalized.includes('svg')
  );
}

function resolvePromptPreviewUrl(url?: string | null) {
  const normalized = String(url ?? '').trim();

  if (!normalized) {
    return '';
  }

  return normalizeChatResourceUrl(normalized);
}

function usePromptPreviewContent(preview: PromptPreview | null | undefined) {
  const [remoteContent, setRemoteContent] = useState<string | null>(preview?.content ?? null);
  const [remoteContentType, setRemoteContentType] = useState<string | null>(null);
  const [isLoading, setIsLoading] = useState(false);
  const [loadError, setLoadError] = useState('');
  const normalizedPreviewUrl = resolvePromptPreviewUrl(preview?.url);

  useEffect(() => {
    setRemoteContent(preview?.content ?? null);
    setRemoteContentType(null);
    setLoadError('');

    const shouldFetchTextPreview =
      preview?.type === 'markdown' || preview?.type === 'html';
    const shouldInspectResourcePreview =
      preview?.type === 'resource' && normalizedPreviewUrl && canRenderFramePreview(normalizedPreviewUrl);

    if (
      !preview ||
      preview.content ||
      !normalizedPreviewUrl ||
      (!shouldFetchTextPreview && !shouldInspectResourcePreview)
    ) {
      setIsLoading(false);
      return;
    }

    const controller = new AbortController();
    setIsLoading(true);

    void fetch(normalizedPreviewUrl, {
      credentials: 'include',
      signal: controller.signal,
    })
      .then(async (response) => {
        if (!response.ok) {
          throw new Error(`${response.status} ${response.statusText}`.trim());
        }

        const contentType = response.headers.get('content-type');

        if (!controller.signal.aborted) {
          setRemoteContentType(contentType);
        }

        if (shouldInspectResourcePreview && !isTextLikeContentType(contentType)) {
          return null;
        }

        return response.text();
      })
      .then((text) => {
        if (!controller.signal.aborted) {
          setRemoteContent(text);
        }
      })
      .catch((error: unknown) => {
        if (controller.signal.aborted) {
          return;
        }

        setLoadError(error instanceof Error ? error.message : 'preview를 불러오지 못했습니다.');
      })
      .finally(() => {
        if (!controller.signal.aborted) {
          setIsLoading(false);
        }
      });

    return () => controller.abort();
  }, [normalizedPreviewUrl, preview]);

  return { remoteContent, remoteContentType, isLoading, loadError };
}

function PromptPreviewSurface({
  preview,
  compact = false,
  htmlMode = 'preview',
}: {
  preview: PromptPreview;
  compact?: boolean;
  htmlMode?: 'preview' | 'source';
}) {
  const { remoteContent, remoteContentType, isLoading, loadError } = usePromptPreviewContent(preview);
  const normalizedPreviewUrl = resolvePromptPreviewUrl(preview.url);
  const shouldRenderAsHtml = shouldRenderAsHtmlDocument(preview, remoteContentType, remoteContent);
  const htmlDocument = shouldRenderAsHtml ? buildHtmlFrameDocument(remoteContent || '', normalizedPreviewUrl || preview.url) : null;

  if (preview.type === 'image' && normalizedPreviewUrl) {
    const imageNode = (
      <InlineImage
        src={normalizedPreviewUrl}
        alt={preview.alt?.trim() || preview.title?.trim() || 'prompt preview image'}
        className={`app-chat-prompt-card__preview-image${compact ? ' app-chat-prompt-card__preview-image--compact' : ''}`}
        fallbackText="이미지 preview를 불러오지 못했습니다."
      />
    );

    if (compact) {
      return imageNode;
    }

    return (
      <ZoomablePreviewSurface stageClassName="app-chat-prompt-card__preview-zoom-stage" contentClassName="app-chat-prompt-card__preview-zoom-content">
        {imageNode}
      </ZoomablePreviewSurface>
    );
  }

  if (isLoading) {
    return (
      <div className="app-chat-prompt-card__preview-placeholder app-chat-prompt-card__preview-placeholder--loading">
        <Spin indicator={<LoadingOutlined spin />} size="small" />
      </div>
    );
  }

  if (loadError) {
    return <div className="app-chat-prompt-card__preview-placeholder">{loadError}</div>;
  }

  if (preview.type === 'markdown') {
    return (
      <div className="app-chat-prompt-card__preview-markdown">
        <MarkdownPreviewContent content={remoteContent || '표시할 markdown preview가 없습니다.'} maxBlocks={compact ? 5 : undefined} />
      </div>
    );
  }

  if (preview.type === 'html') {
    const htmlContent = remoteContent || '';

    if (normalizedPreviewUrl && isAppRouteUrl(normalizedPreviewUrl)) {
      return (
        <div className="app-chat-prompt-card__preview-placeholder">
          앱 화면 경로는 prompt iframe으로 열지 않습니다. `/api/chat/resources/...html`, `/api/resource-manager/preview/...html`, `resource/...html` 같은 실제 HTML 리소스 URL을 사용해 주세요.
        </div>
      );
    }

    if (isHtmlFallbackPreview(preview, htmlContent)) {
      return (
        <div className="app-chat-prompt-card__preview-placeholder">
          현재 앱 fallback HTML이 반환되었습니다. `type:"html"`에는 실제 HTML 본문을 넣거나, `type:"resource"`에 `/api/chat/resources/...html`, `/api/resource-manager/preview/...html`, `resource/...html` 같은 정적 HTML 리소스를 연결해 주세요.
        </div>
      );
    }

    if (htmlMode === 'source') {
      return (
        <div className="app-chat-prompt-card__preview-code">
          {renderEditorBlock(htmlContent || '표시할 HTML 코드가 없습니다.', 'html', 'code')}
        </div>
      );
    }

    const frameNode = (
      <iframe
        title={preview.title?.trim() || 'prompt html preview'}
        srcDoc={htmlDocument ?? buildHtmlFrameDocument(htmlContent, normalizedPreviewUrl || preview.url)}
        className="app-chat-prompt-card__preview-frame"
        sandbox="allow-same-origin"
      />
    );

    if (compact) {
      return frameNode;
    }

    return (
      <ZoomablePreviewSurface stageClassName="app-chat-prompt-card__preview-zoom-stage" contentClassName="app-chat-prompt-card__preview-zoom-content">
        {frameNode}
      </ZoomablePreviewSurface>
    );
  }

  if (preview.type === 'resource' && normalizedPreviewUrl) {
    const resourceKind =
      isMarkdownResourceUrl(normalizedPreviewUrl || preview.url) || isMarkdownContentType(remoteContentType)
        ? 'markdown'
        : classifyPreviewKind(normalizedPreviewUrl);

    if (isAppRouteUrl(normalizedPreviewUrl)) {
      return (
        <div className="app-chat-prompt-card__preview-placeholder">
          앱 화면 URL은 resource preview로 열지 않습니다. `/api/chat/resources/...html`, `/api/resource-manager/preview/...html`, `resource/...html` 같은 정적 리소스 경로만 사용해 주세요.
        </div>
      );
    }

    if (remoteContentType?.toLowerCase().includes('text/html') && isHtmlFallbackPreview(preview, remoteContent || '')) {
      return (
        <div className="app-chat-prompt-card__preview-placeholder">
          현재 앱 fallback HTML이 반환되었습니다. `type:"resource"`에는 실제 리소스(`/api/chat/resources/...html`, `/api/resource-manager/preview/...html`, `resource/...html`)만 연결해 주세요.
        </div>
      );
    }

    return (
      <ChatPreviewBody
        target={{
          label: preview.title?.trim() || preview.alt?.trim() || 'prompt resource preview',
          url: normalizedPreviewUrl,
          kind: resourceKind,
        }}
        previewText={remoteContent || ''}
        isPreviewLoading={isLoading}
        previewError={loadError}
        previewContentType={remoteContentType ?? undefined}
        maxMarkdownBlocks={compact ? 5 : undefined}
        renderHtmlAsFrame={htmlMode !== 'source'}
      />
    );
  }

  return <div className="app-chat-prompt-card__preview-placeholder">표시할 preview가 없습니다.</div>;
}

function PromptPreviewCard({
  option,
  onOpenPreview,
}: {
  option: PromptOption;
  onOpenPreview: (option: PromptOption) => void;
}) {
  const preview = option.preview;
  const { message } = App.useApp();
  const normalizedPreviewUrl = resolvePromptPreviewUrl(preview?.url);

  if (!preview) {
    return null;
  }

  return (
    <div className="app-chat-prompt-card__preview-shell">
      <div className="app-chat-prompt-card__preview-toolbar">
        <Text className="app-chat-prompt-card__preview-title" ellipsis={{ tooltip: preview.title?.trim() || option.label }}>
          {preview.title?.trim() || option.label}
        </Text>
        <Button
          type="text"
          size="small"
          className="app-chat-prompt-card__preview-tool"
          icon={<ExpandOutlined />}
          aria-label={`${option.label} preview 확대`}
          onClick={(event) => {
            event.stopPropagation();
            onOpenPreview(option);
          }}
        />
        {preview.url ? (
          <Button
            type="text"
            size="small"
            className="app-chat-prompt-card__preview-tool"
            icon={<LinkOutlined />}
            aria-label={`${option.label} preview 열기`}
            onClick={(event) => {
              event.stopPropagation();
              openChatExternalLink(normalizedPreviewUrl || preview.url || '', event);
            }}
          />
        ) : (
          <Button
            type="text"
            size="small"
            className="app-chat-prompt-card__preview-tool"
            icon={<LinkOutlined />}
            aria-label={`${option.label} preview 링크 없음`}
            onClick={(event) => {
              event.stopPropagation();
              message.info('이 preview는 내부 본문만 있어 별도 링크가 없습니다.');
            }}
          />
        )}
      </div>
      <PromptPreviewSurface preview={preview} compact />
    </div>
  );
}

export function resolvePromptPreviewOption(
  options: PromptOption[],
  activePreviewOptionValue: string | null,
  selectedValues: string[],
) {
  const resolvedValue = resolvePromptPreviewOptionValue(options, activePreviewOptionValue, selectedValues);
  return options.find((option) => option.value === resolvedValue) ?? null;
}

function buildInitialStepSelectionMap(target: PromptTarget, steps: PromptStep[]) {
  return Object.fromEntries(
    steps.map((step) => {
      const selectedValues =
        steps.length === 1 && step.key === 'default'
          ? target.selectedValues ?? step.selectedValues ?? []
          : step.selectedValues ?? [];

      return [
        step.key,
        {
          stepKey: step.key,
          stepTitle: step.title,
          selectedValues,
          freeText: '',
          skipped: false,
        } satisfies PromptStepDraftSelection,
      ];
    }),
  );
}

function resolveCurrentStepIndex(
  steps: PromptStep[],
  stepSelections: Record<string, PromptStepDraftSelection>,
  preferredStepKey?: string | null,
) {
  const preferredIndex = preferredStepKey ? steps.findIndex((step) => step.key === preferredStepKey) : -1;

  if (preferredIndex >= 0) {
    return preferredIndex;
  }

  const firstIncompleteIndex = steps.findIndex((step) => {
    const selection = stepSelections[step.key];
    return !selection || (!selection.skipped && selection.selectedValues.length === 0);
  });

  if (firstIncompleteIndex >= 0) {
    return firstIncompleteIndex;
  }

  return Math.max(steps.length - 1, 0);
}

function canProceedStep(step: PromptStep, selection: PromptStepDraftSelection | undefined) {
  if (!step) {
    return false;
  }

  if (step.optional && selection?.skipped) {
    return true;
  }

  return (selection?.selectedValues.length ?? 0) > 0;
}

function buildPromptSelectionPayload(target: PromptTarget, stepSelections: Record<string, PromptStepDraftSelection>) {
  const steps = normalizePromptSteps(target);
  const draftSelections = steps
    .map((step) => stepSelections[step.key])
    .filter(Boolean) as PromptStepDraftSelection[];
  const meaningfulSelections = draftSelections.filter((selection) => selection.skipped || selection.selectedValues.length > 0 || selection.freeText.trim());

  if (meaningfulSelections.length === 0) {
    return null;
  }

  const selectedValues = meaningfulSelections.flatMap((selection) => selection.selectedValues);
  const aggregatedFreeText = meaningfulSelections
    .map((selection) => selection.freeText.trim())
    .filter(Boolean)
    .join('\n\n');

  return {
    selectedValues,
    freeText: aggregatedFreeText,
    stepSelections: meaningfulSelections,
    summaryText: buildPromptDraftSummaryText(target, {
      selectedValues,
      freeText: aggregatedFreeText,
      stepSelections: meaningfulSelections,
    }),
  } satisfies PromptDraftSelection;
}

export function ChatPromptCard({
  target,
  onSubmit,
  readOnly = false,
  onSelectionChange,
  onSubmitted,
  submittedSelection,
}: {
  target: PromptTarget;
  onSubmit: (payload: { text: string; mode: 'queue' | 'direct' }) => Promise<boolean>;
  readOnly?: boolean;
  onSelectionChange?: (selection: PromptDraftSelection | null) => void;
  onSubmitted?: (selection: PromptDraftSelection) => void;
  submittedSelection?: PromptDraftSelection | null;
}) {
  const steps = useMemo(() => normalizePromptSteps(target), [target]);
  const hasStepper = steps.length > 1;
  const initialStepSelections = useMemo(() => buildInitialStepSelectionMap(target, steps), [steps, target]);
  const [stepSelections, setStepSelections] = useState<Record<string, PromptStepDraftSelection>>(initialStepSelections);
  const [activeStepIndex, setActiveStepIndex] = useState(() => resolveCurrentStepIndex(steps, initialStepSelections, target.currentStepKey));
  const [isSubmitting, setIsSubmitting] = useState(false);
  const [submittedSummary, setSubmittedSummary] = useState('');
  const [submittedFreeText, setSubmittedFreeText] = useState('');
  const [expandedOptionValue, setExpandedOptionValue] = useState<string | null>(null);
  const [expandedHtmlMode, setExpandedHtmlMode] = useState<'preview' | 'source'>('preview');
  const [isCollapsed, setIsCollapsed] = useState(false);
  const [activePreviewOptionValue, setActivePreviewOptionValue] = useState<string | null>(null);
  const resolvedSelectedValues = target.selectedValues ?? [];
  const resolvedSelectionSummary = buildSelectionText(target, resolvedSelectedValues);
  const isResolved = resolvedSelectedValues.length > 0;
  const externallySubmittedSummary =
    submittedSelection?.summaryText ||
    (submittedSelection ? buildPromptDraftSummaryText(target, submittedSelection) : '');
  const submittedFreeTextValue = submittedFreeText || submittedSelection?.freeText.trim() || '';
  const displayedSubmittedSummary = submittedSummary || externallySubmittedSummary;
  const isLocked =
    readOnly || target.readOnly === true || isResolved || Boolean(submittedSummary) || Boolean(externallySubmittedSummary);
  const resolvedStateLabel =
    target.resolvedBy === 'timeout'
      ? '시간 초과 자동선택'
      : target.resolvedBy === 'system'
        ? '임의 선택 결과'
        : isResolved
          ? '선택 완료'
          : null;
  const expandedOption = steps
    .flatMap((step) => step.options)
    .find((option) => option.value === expandedOptionValue) ?? null;

  useEffect(() => {
    setExpandedHtmlMode('preview');
  }, [expandedOptionValue]);
  const activeStep = steps[Math.min(activeStepIndex, Math.max(steps.length - 1, 0))] ?? steps[0];
  const activeSelection = activeStep ? stepSelections[activeStep.key] : undefined;
  const previewableOptions = useMemo(() => getPreviewablePromptOptions(activeStep), [activeStep]);
  const activePreviewOption = useMemo(
    () => resolvePromptPreviewOption(previewableOptions, activePreviewOptionValue, activeSelection?.selectedValues ?? []),
    [activePreviewOptionValue, activeSelection?.selectedValues, previewableOptions],
  );
  const selectionSummary = activeStep && activeSelection ? buildStepSelectionText(activeStep, activeSelection.selectedValues) : '';
  const displayedSelectionSummary = displayedSubmittedSummary || (hasStepper ? buildPromptDraftSummaryText(target, buildPromptSelectionPayload(target, stepSelections) ?? {
    selectedValues: [],
    freeText: '',
  }) : resolvedSelectionSummary);
  const isFinalStep = activeStepIndex >= steps.length - 1;
  const canProceed = activeStep ? canProceedStep(activeStep, activeSelection) : false;
  const canAdvance = !isLocked && !isSubmitting && canProceed;
  const canSubmit = !isLocked && !isSubmitting && isFinalStep && canProceed;
  const submitLabel = activeStep?.submitLabel?.trim() || target.submitLabel?.trim() || '선택 전달';
  const progressPayload = buildPromptSelectionPayload(target, stepSelections);

  const emitSelectionChange = (nextSelections: Record<string, PromptStepDraftSelection>) => {
    if (!onSelectionChange) {
      return;
    }

    if (isLocked) {
      onSelectionChange(null);
      return;
    }

    onSelectionChange(buildPromptSelectionPayload(target, nextSelections));
  };

  useEffect(() => {
    setStepSelections(initialStepSelections);
    setActiveStepIndex(resolveCurrentStepIndex(steps, initialStepSelections, target.currentStepKey));
  }, [initialStepSelections, steps, target.currentStepKey]);

  useEffect(() => {
    if (previewableOptions.length === 0) {
      setActivePreviewOptionValue(null);
      return;
    }

    setActivePreviewOptionValue((current) => {
      if (current && previewableOptions.some((option) => option.value === current)) {
        return current;
      }

      return null;
    });
  }, [previewableOptions]);

  useEffect(() => {
    if (isLocked) {
      emitSelectionChange({});
    }
  }, [isLocked]);

  const updateActiveSelection = (updater: (current: PromptStepDraftSelection) => PromptStepDraftSelection) => {
    if (!activeStep) {
      return;
    }

    setStepSelections((current) => {
      const currentSelection = current[activeStep.key] ?? {
        stepKey: activeStep.key,
        stepTitle: activeStep.title,
        selectedValues: [],
        freeText: '',
        skipped: false,
      };
      const nextSelection = updater(currentSelection);
      const nextSelections = {
        ...current,
        [activeStep.key]: nextSelection,
      };
      emitSelectionChange(nextSelections);
      return nextSelections;
    });
  };

  const handleStepAdvance = async () => {
    if (!activeStep || !activeSelection) {
      return;
    }

    if (!isFinalStep) {
      if (!canAdvance) {
        return;
      }

      setActiveStepIndex((current) => Math.min(current + 1, steps.length - 1));
      return;
    }

    if (!canSubmit) {
      return;
    }

    const payload = buildPromptSelectionPayload(target, stepSelections);

    if (!payload) {
      return;
    }

    setIsSubmitting(true);
    const isSent = await onSubmit({
      text: buildPromptResponseText(target, payload),
      mode: activeStep.mode === 'direct' ? 'direct' : target.mode === 'direct' ? 'direct' : 'queue',
    });
    setIsSubmitting(false);

    if (!isSent) {
      return;
    }

    setSubmittedSummary(payload.summaryText || buildPromptDraftSummaryText(target, payload));
    setSubmittedFreeText(payload.freeText);
    onSubmitted?.(payload);
  };

  return (
    <>
      <section className="app-chat-preview-card app-chat-preview-card--prompt">
        <div className="app-chat-preview-card__header">
          <div className="app-chat-preview-card__meta">
            <span className="app-chat-preview-card__glyph app-chat-preview-card__glyph--prompt" aria-hidden="true">
              <MessageOutlined />
            </span>
            <div className="app-chat-preview-card__titles">
              <span className="app-chat-preview-card__label">{target.title}</span>
              <span className="app-chat-preview-card__kind">{hasStepper ? 'prompt stepper' : 'prompt'}</span>
            </div>
          </div>
          <div className="app-chat-preview-card__actions">
            {submittedSummary ? (
              <span className="app-chat-prompt-card__submitted">
                <CheckCircleOutlined />
                <span>전달됨</span>
              </span>
            ) : externallySubmittedSummary ? (
              <span className="app-chat-prompt-card__submitted">
                <CheckCircleOutlined />
                <span>전달됨</span>
              </span>
            ) : resolvedStateLabel ? (
              <span className="app-chat-prompt-card__submitted">
                <CheckCircleOutlined />
                <span>{resolvedStateLabel}</span>
              </span>
            ) : null}
            {displayedSelectionSummary ? (
              <span className="app-chat-prompt-card__selection-pill" title={displayedSelectionSummary}>
                {displayedSelectionSummary}
              </span>
            ) : null}
            <Button
              type="text"
              size="small"
              className="app-chat-prompt-card__collapse-toggle"
              icon={isCollapsed ? <DownOutlined /> : <UpOutlined />}
              aria-label={isCollapsed ? `${target.title} 펼치기` : `${target.title} 접기`}
              onClick={(event) => {
                event.preventDefault();
                event.stopPropagation();
                setIsCollapsed((current) => !current);
              }}
            />
          </div>
        </div>
        {!isCollapsed ? (
          <div className="app-chat-preview-card__body app-chat-preview-card__body--prompt">
            {target.description ? (
              <Paragraph className="app-chat-prompt-card__description">{target.description}</Paragraph>
            ) : null}
            {target.resultText ? <Paragraph className="app-chat-prompt-card__result">{target.resultText}</Paragraph> : null}
            {hasStepper ? (
              <div className="app-chat-prompt-card__stepper">
                <StepperUI
                  steps={steps.map((step, index) => {
                    const selection = stepSelections[step.key];
                    const isComplete = selection?.skipped || (selection?.selectedValues.length ?? 0) > 0;

                    return {
                      key: step.key,
                      label: step.title,
                      description:
                        selection?.skipped
                          ? '건너뜀'
                          : isComplete
                            ? buildStepSelectionText(step, selection?.selectedValues ?? []) || step.description
                            : step.description,
                      status: index < activeStepIndex ? 'complete' : index === activeStepIndex ? 'current' : 'pending',
                    };
                  })}
                  currentStepKey={activeStep?.key}
                  direction="vertical"
                  compact
                  onStepClick={
                    isLocked
                      ? undefined
                      : (_, index) => {
                          if (index > activeStepIndex + 1) {
                            return;
                          }

                          setActiveStepIndex(index);
                        }
                  }
                />
              </div>
            ) : null}
            {activeStep ? (
              <div className="app-chat-prompt-card__step-panel">
                {hasStepper ? (
                  <div className="app-chat-prompt-card__step-header">
                    <div>
                      <Text className="app-chat-prompt-card__step-kicker">
                        {activeStepIndex + 1}/{steps.length} 단계
                      </Text>
                      <Text className="app-chat-prompt-card__step-title">{activeStep.title}</Text>
                    </div>
                    {activeStep.optional ? <span className="app-chat-prompt-card__optional-pill">선택 단계</span> : null}
                  </div>
                ) : null}
                {hasStepper && activeStep.description ? (
                  <Paragraph className="app-chat-prompt-card__description">{activeStep.description}</Paragraph>
                ) : null}
                {previewableOptions.length > 0 ? (
                  <div className="app-chat-prompt-card__preview-tabs-shell">
                    <Tabs
                      size="small"
                      className="app-chat-prompt-card__preview-tabs"
                      activeKey={activePreviewOption?.value}
                      onChange={(nextValue) => setActivePreviewOptionValue(nextValue)}
                      items={previewableOptions.map((option) => ({
                        key: option.value,
                        label: option.label,
                      }))}
                    />
                    {activePreviewOption?.preview ? (
                      <PromptPreviewCard
                        option={activePreviewOption}
                        onOpenPreview={(nextOption) => setExpandedOptionValue(nextOption.value)}
                      />
                    ) : null}
                  </div>
                ) : null}
                <div className="app-chat-prompt-card__options" role={activeStep.multiple ? 'group' : 'radiogroup'} aria-label={activeStep.title}>
                  {activeStep.options.map((option) => {
                    const isSelected = activeSelection?.selectedValues.includes(option.value) ?? false;
                    const optionInteractionProps = isLocked
                      ? {}
                      : {
                          role: 'button' as const,
                          tabIndex: 0,
                          onClick: () => {
                            updateActiveSelection((current) => {
                              const nextSelectedValues = activeStep.multiple
                                ? current.selectedValues.includes(option.value)
                                  ? current.selectedValues.filter((value) => value !== option.value)
                                  : [...current.selectedValues, option.value]
                                : [option.value];

                              return {
                                ...current,
                                selectedValues: nextSelectedValues,
                                skipped: false,
                              };
                            });
                          },
                          onKeyDown: (event: ReactKeyboardEvent<HTMLDivElement>) => {
                            if (event.key !== 'Enter' && event.key !== ' ') {
                              return;
                            }

                            event.preventDefault();
                            updateActiveSelection((current) => {
                              const nextSelectedValues = activeStep.multiple
                                ? current.selectedValues.includes(option.value)
                                  ? current.selectedValues.filter((value) => value !== option.value)
                                  : [...current.selectedValues, option.value]
                                : [option.value];

                              return {
                                ...current,
                                selectedValues: nextSelectedValues,
                                skipped: false,
                              };
                            });
                          },
                        };

                    return (
                      <div
                        key={`${activeStep.key}-${option.value}`}
                        className={`app-chat-prompt-card__option${isSelected ? ' app-chat-prompt-card__option--selected' : ''}${
                          isLocked ? ' app-chat-prompt-card__option--readonly' : ''
                        }`}
                        {...optionInteractionProps}
                      >
                        <span className="app-chat-prompt-card__option-head">
                          {activeStep.multiple ? (
                            <span className="app-chat-prompt-card__checkbox" aria-hidden="true">
                              <span />
                            </span>
                          ) : (
                            <span className="app-chat-prompt-card__radio" aria-hidden="true">
                              <span />
                            </span>
                          )}
                          <Text className="app-chat-prompt-card__option-label">{option.label}</Text>
                          {isSelected && (isLocked || Boolean(submittedSummary) || isResolved) ? (
                            <span className="app-chat-prompt-card__option-state">선택됨</span>
                          ) : null}
                        </span>
                        {option.description ? (
                          <span className="app-chat-prompt-card__option-description">{option.description}</span>
                        ) : null}
                        {option.preview ? <span className="app-chat-prompt-card__option-preview-hint">상단 미리보기 탭에서 확인</span> : null}
                      </div>
                    );
                  })}
                </div>
                {!isLocked ? (
                  <div className="app-chat-prompt-card__free-text">
                    <Text className="app-chat-prompt-card__free-text-label">
                      {activeStep.freeTextLabel?.trim() || target.freeTextLabel?.trim() || '기타 요청'}
                    </Text>
                    <Input.TextArea
                      value={activeSelection?.freeText ?? ''}
                      onChange={(event) => {
                        const nextFreeText = event.target.value;
                        updateActiveSelection((current) => ({
                          ...current,
                          freeText: nextFreeText,
                        }));
                      }}
                      placeholder={
                        activeStep.freeTextPlaceholder?.trim() ||
                        target.freeTextPlaceholder?.trim() ||
                        '선택과 함께 전달할 추가 요청을 입력하세요.'
                      }
                      autoSize={{ minRows: 2, maxRows: 5 }}
                      maxLength={1000}
                      disabled={isSubmitting}
                    />
                  </div>
                ) : null}
                <div className="app-chat-prompt-card__footer">
                  <div className="app-chat-prompt-card__summary" aria-live="polite">
                    {submittedSummary
                      ? `마지막 전달: ${submittedSummary}`
                      : externallySubmittedSummary
                        ? `마지막 전달: ${externallySubmittedSummary}`
                        : resolvedSelectionSummary
                          ? target.resolvedBy === 'timeout'
                            ? `시간 초과로 자동 선택: ${resolvedSelectionSummary}`
                            : `선택 결과: ${resolvedSelectionSummary}`
                          : readOnly || target.readOnly === true
                            ? '지난 prompt는 읽기 전용입니다.'
                            : hasStepper
                              ? progressPayload?.summaryText || '단계를 순서대로 선택하세요.'
                              : selectionSummary
                                ? `선택: ${selectionSummary}`
                                : '항목을 선택하세요.'}
                    {submittedFreeTextValue ? (
                      <span className="app-chat-prompt-card__summary-detail">추가 요청: {submittedFreeTextValue}</span>
                    ) : null}
                  </div>
                  <div className="app-chat-prompt-card__footer-actions">
                    {hasStepper ? (
                      <Button
                        size="small"
                        icon={<LeftOutlined />}
                        onClick={() => setActiveStepIndex((current) => Math.max(0, current - 1))}
                        disabled={isSubmitting || activeStepIndex === 0}
                      >
                        이전
                      </Button>
                    ) : null}
                    {hasStepper && activeStep?.optional ? (
                      <Button
                        size="small"
                        onClick={() => {
                          updateActiveSelection((current) => ({
                            ...current,
                            selectedValues: [],
                            skipped: true,
                          }));
                          if (!isFinalStep) {
                            setActiveStepIndex((current) => Math.min(current + 1, steps.length - 1));
                          }
                        }}
                        disabled={isLocked || isSubmitting}
                      >
                        건너뛰기
                      </Button>
                    ) : null}
                    <Button
                      type="primary"
                      size="small"
                      icon={hasStepper && !isFinalStep ? <RightOutlined /> : undefined}
                      onClick={() => void handleStepAdvance()}
                      disabled={hasStepper && !isFinalStep ? !canAdvance : !canSubmit}
                      loading={isSubmitting}
                    >
                      {hasStepper && !isFinalStep ? '다음 단계' : submitLabel}
                    </Button>
                  </div>
                </div>
              </div>
            ) : null}
          </div>
        ) : null}
      </section>
      <FullscreenPreviewModal
        open={Boolean(expandedOption?.preview)}
        onClose={() => setExpandedOptionValue(null)}
        title={expandedOption?.preview?.title?.trim() || expandedOption?.label || 'preview'}
        actions={
          canShowHtmlPreviewActions(expandedOption?.preview) ? (
            <>
              <Button
                type="text"
                className="fullscreen-preview-modal__icon-button"
                aria-label="HTML 실행 미리보기"
                icon={<EyeOutlined />}
                onClick={() => setExpandedHtmlMode('preview')}
              />
              <Button
                type="text"
                className="fullscreen-preview-modal__icon-button"
                aria-label="HTML 코드 보기"
                icon={<CodeOutlined />}
                onClick={() => setExpandedHtmlMode('source')}
              />
            </>
          ) : null
        }
        className="app-chat-prompt-card__preview-modal"
      >
        <div className="app-chat-prompt-card__preview-modal-surface">
          {expandedOption?.preview ? <PromptPreviewSurface preview={expandedOption.preview} htmlMode={expandedHtmlMode} /> : null}
        </div>
      </FullscreenPreviewModal>
    </>
  );
}
