import {
  ArrowLeft,
  Bookmark,
  ChevronRight,
  History,
  MessageSquareText,
  Send,
  Settings,
  SlidersHorizontal,
} from 'lucide-react';
import { useEffect, useMemo, useState } from 'react';

import type { ProfileSaveArchiveSummary } from '../../../packages/shared/src/contracts/runtime';
import type {
  VisualNovelCharacterDraft,
  VisualNovelChoiceDraft,
  VisualNovelDialogueStep,
  VisualNovelNarrationStep,
  VisualNovelResultDraft,
  VisualNovelRunSnapshot,
  VisualNovelRuntimeActionRequest,
  VisualNovelRuntimeStep,
  VisualNovelSceneChangeStep,
  VisualNovelTransitionStep,
} from '../../../packages/shared/src/contracts/visualNovel';
import { ResolvedAssetImage } from '../ResolvedAssetImage';
import { useVisualNovelRuntimeController } from './useVisualNovelRuntimeController';
import { mockVisualNovelDraft, mockVisualNovelRun } from './visualNovelMockData';
import {
  VisualNovelRuntimePanel,
  type VisualNovelRuntimePanelKind,
} from './VisualNovelRuntimePanels';

type VisualNovelRuntimeShellProps = {
  draft?: VisualNovelResultDraft | null;
  run?: VisualNovelRunSnapshot | null;
  isBusy?: boolean;
  isSaving?: boolean;
  isLoadingArchives?: boolean;
  resumingWorldKey?: string | null;
  error?: string | null;
  embedded?: boolean;
  streamedSteps?: VisualNovelRuntimeStep[];
  streamingText?: string;
  saveArchives?: ProfileSaveArchiveSummary[];
  onBack: () => void;
  onSubmitAction?: (payload: VisualNovelRuntimeActionRequest) => void;
  onContinue?: () => void;
  onRegenerateHistoryEntry?: (entryId: string) => void;
  onSaveRun?: () => void;
  onResumeSaveArchive?: (worldKey: string) => void;
  onTextModeChange?: (enabled: boolean) => void;
};

type VisualNovelDisplayState = {
  sceneStep: VisualNovelSceneChangeStep | null;
  narrationStep: VisualNovelNarrationStep | null;
  dialogueStep: VisualNovelDialogueStep | null;
  transitionStep: VisualNovelTransitionStep | null;
  choiceStep: VisualNovelRuntimeStep | null;
};

function buildClientEventId(kind: string) {
  return `vn-${kind}-${Date.now()}-${Math.round(Math.random() * 1_000_000)}`;
}

function collectRuntimeSteps(
  run: VisualNovelRunSnapshot,
  streamedSteps: VisualNovelRuntimeStep[],
) {
  return [
    ...run.history.flatMap((entry) => entry.steps),
    ...streamedSteps,
  ];
}

function resolveLatestStep<T extends VisualNovelRuntimeStep['type']>(
  steps: VisualNovelRuntimeStep[],
  type: T,
) {
  return [...steps].reverse().find((step) => step.type === type) as
    | Extract<VisualNovelRuntimeStep, { type: T }>
    | undefined;
}

function resolveDisplayState(
  run: VisualNovelRunSnapshot,
  streamedSteps: VisualNovelRuntimeStep[],
): VisualNovelDisplayState {
  const steps = collectRuntimeSteps(run, streamedSteps);

  return {
    sceneStep: resolveLatestStep(steps, 'scene_change') ?? null,
    narrationStep: resolveLatestStep(steps, 'narration') ?? null,
    dialogueStep: resolveLatestStep(steps, 'dialogue') ?? null,
    transitionStep: resolveLatestStep(steps, 'transition') ?? null,
    choiceStep: resolveLatestStep(steps, 'choice') ?? null,
  };
}

function resolveSceneId(
  run: VisualNovelRunSnapshot,
  sceneStep: VisualNovelSceneChangeStep | null,
) {
  return sceneStep?.sceneId ?? run.currentSceneId;
}

function resolveSceneName(draft: VisualNovelResultDraft, sceneId: string | null) {
  return draft.scenes.find((scene) => scene.sceneId === sceneId)?.name ?? '';
}

function resolveSceneBackground(
  draft: VisualNovelResultDraft,
  sceneId: string | null,
  sceneStep: VisualNovelSceneChangeStep | null,
) {
  return (
    sceneStep?.backgroundImageSrc ??
    draft.scenes.find((scene) => scene.sceneId === sceneId)?.backgroundImageSrc ??
    draft.coverImageSrc
  );
}

function resolveVisibleCharacters(
  draft: VisualNovelResultDraft,
  run: VisualNovelRunSnapshot,
  latestDialogue: VisualNovelDialogueStep | null,
) {
  const visibleIds = new Set(run.visibleCharacterIds);
  if (latestDialogue?.characterId) {
    visibleIds.add(latestDialogue.characterId);
  }

  return Array.from(visibleIds)
    .map((characterId) =>
      draft.characters.find((character) => character.characterId === characterId),
    )
    .filter((character): character is VisualNovelCharacterDraft =>
      Boolean(character),
    )
    .slice(0, 3);
}

function resolveCharacterImage(character: VisualNovelCharacterDraft) {
  if (character.imageAssets.length === 0) {
    return null;
  }

  return (
    character.imageAssets.find(
      (asset) => asset.expression === character.defaultExpression,
    )?.imageSrc ?? character.imageAssets[0]?.imageSrc ?? null
  );
}

function VisualNovelCharacterStandee({
  character,
  index,
  active,
}: {
  character: VisualNovelCharacterDraft;
  index: number;
  active: boolean;
}) {
  const imageSrc = resolveCharacterImage(character);
  const palette =
    index % 2 === 0
      ? 'from-sky-100/90 via-slate-100/78 to-slate-300/72'
      : 'from-rose-100/90 via-zinc-100/78 to-stone-300/72';

  return (
    <div
      className={`flex min-w-0 flex-col items-center transition ${
        active ? 'scale-100 opacity-100' : 'scale-[0.96] opacity-78'
      }`}
    >
      {imageSrc ? (
        <ResolvedAssetImage
          src={imageSrc}
          alt={character.name}
          className="h-[min(48dvh,21rem)] w-[9rem] object-contain drop-shadow-[0_26px_54px_rgba(0,0,0,0.42)] sm:w-[12rem]"
        />
      ) : (
        <div
          className={`h-[min(44dvh,18rem)] w-[7.5rem] rounded-t-full border border-white/18 bg-gradient-to-b ${palette} shadow-[0_26px_58px_rgba(15,23,42,0.32)] sm:w-[10rem]`}
        />
      )}
      <div className="mt-2 max-w-[8rem] truncate rounded-full border border-white/16 bg-black/28 px-3 py-1 text-xs font-black text-white backdrop-blur">
        {character.name}
      </div>
    </div>
  );
}

function buildTextModeLines(
  run: VisualNovelRunSnapshot,
  streamedSteps: VisualNovelRuntimeStep[],
) {
  return [...run.history.flatMap((entry) => entry.steps), ...streamedSteps]
    .filter((step) => step.type === 'narration' || step.type === 'dialogue')
    .map((step) =>
      step.type === 'dialogue'
        ? `${step.characterName}: ${step.text}`
        : step.text,
    )
    .join('\n');
}

function resolveChoices(
  run: VisualNovelRunSnapshot,
  choiceStep: VisualNovelRuntimeStep | null,
) {
  if (choiceStep?.type === 'choice' && choiceStep.choices.length > 0) {
    return choiceStep.choices;
  }

  return run.availableChoices;
}

export function VisualNovelRuntimeShell({
  draft = mockVisualNovelDraft,
  run = mockVisualNovelRun,
  isBusy = false,
  isSaving = false,
  isLoadingArchives = false,
  resumingWorldKey = null,
  error,
  embedded = false,
  streamedSteps = [],
  streamingText = '',
  saveArchives,
  onBack,
  onSubmitAction,
  onContinue,
  onRegenerateHistoryEntry,
  onSaveRun,
  onResumeSaveArchive,
  onTextModeChange,
}: VisualNovelRuntimeShellProps) {
  const [activePanel, setActivePanel] =
    useState<VisualNovelRuntimePanelKind | null>(null);
  const [freeText, setFreeText] = useState('');
  const [localTextModeEnabled, setLocalTextModeEnabled] = useState(
    run?.textModeEnabled ?? draft?.runtimeConfig.defaultTextMode ?? false,
  );
  const displayDraft = draft ?? mockVisualNovelDraft;
  const baseRun = run ?? mockVisualNovelRun;
  const runtimeController = useVisualNovelRuntimeController({
    draft: displayDraft,
    initialRun: baseRun,
    profileId: displayDraft.profileId,
    autoStart: false,
  });
  const displayRun = runtimeController.run ?? baseRun;
  const displayBusy = isBusy || runtimeController.isBusy;
  const displaySaving = isSaving || runtimeController.isSaving;
  const displayLoadingArchives =
    isLoadingArchives || runtimeController.isLoadingArchives;
  const displayResumingWorldKey =
    resumingWorldKey ?? runtimeController.resumingWorldKey;
  const displayError = error ?? runtimeController.error;
  const displaySaveArchives = saveArchives ?? runtimeController.saveArchives;
  const displayStreamedSteps =
    streamedSteps.length > 0 ? streamedSteps : runtimeController.streamedSteps;
  const displayStreamingText =
    streamingText || runtimeController.streamingText;
  const displayState = useMemo(
    () => resolveDisplayState(displayRun, displayStreamedSteps),
    [displayRun, displayStreamedSteps],
  );
  const textModeEnabled = localTextModeEnabled;
  const sceneId = resolveSceneId(displayRun, displayState.sceneStep);
  const sceneName = resolveSceneName(displayDraft, sceneId);
  const backgroundImageSrc = resolveSceneBackground(
    displayDraft,
    sceneId,
    displayState.sceneStep,
  );
  const visibleCharacters = useMemo(
    () =>
      resolveVisibleCharacters(
        displayDraft,
        displayRun,
        displayState.dialogueStep,
      ),
    [displayDraft, displayRun, displayState.dialogueStep],
  );
  const choices = resolveChoices(displayRun, displayState.choiceStep);
  const canSubmitFreeText =
    displayDraft.runtimeConfig.allowFreeTextAction &&
    freeText.trim() &&
    !displayBusy;
  const canShowAttributes =
    displayDraft.runtimeConfig.attributePanelMode !== 'off';
  const primarySpeaker =
    displayState.dialogueStep?.characterName ||
    (displayState.narrationStep ? '旁白' : displayDraft.workTitle);
  const primaryText =
    displayState.dialogueStep?.text ||
    displayState.narrationStep?.text ||
    displayDraft.opening.narration ||
    displayDraft.workDescription;
  const textModeLines = buildTextModeLines(displayRun, displayStreamedSteps);

  const loadRuntimeSaveArchives = runtimeController.loadSaveArchives;

  useEffect(() => {
    if (activePanel === 'save' && !saveArchives) {
      void loadRuntimeSaveArchives();
    }
  }, [activePanel, loadRuntimeSaveArchives, saveArchives]);

  const updateTextMode = (enabled: boolean) => {
    setLocalTextModeEnabled(enabled);
    onTextModeChange?.(enabled);
  };

  const submitChoice = (choice: VisualNovelChoiceDraft) => {
    if (displayBusy) {
      return;
    }
    const payload = {
      actionKind: 'choice',
      choiceId: choice.choiceId,
      clientEventId: buildClientEventId('choice'),
    } satisfies VisualNovelRuntimeActionRequest;
    if (onSubmitAction) {
      onSubmitAction(payload);
      return;
    }
    void runtimeController.submitAction(payload);
  };

  const submitFreeText = () => {
    const text = freeText.trim();
    if (!text || displayBusy) {
      return;
    }
    const payload = {
      actionKind: 'free_text',
      text,
      clientEventId: buildClientEventId('free-text'),
    } satisfies VisualNovelRuntimeActionRequest;
    if (onSubmitAction) {
      onSubmitAction(payload);
    } else {
      void runtimeController.submitAction(payload);
    }
    setFreeText('');
  };

  const continueRuntime = () => {
    if (displayBusy) {
      return;
    }
    if (onContinue) {
      onContinue();
      return;
    }
    void runtimeController.continueRun();
  };

  const regenerateHistoryEntry = (entryId: string) => {
    if (onRegenerateHistoryEntry) {
      onRegenerateHistoryEntry(entryId);
      return;
    }
    void runtimeController.regenerateFromHistory(entryId);
  };

  const saveRuntime = () => {
    if (onSaveRun) {
      onSaveRun();
      return;
    }
    void runtimeController.saveCurrentRun();
  };

  const resumeArchive = (worldKey: string) => {
    if (onResumeSaveArchive) {
      onResumeSaveArchive(worldKey);
      return;
    }
    void runtimeController.resumeSaveArchive(worldKey);
  };

  return (
    <main
      className={`relative flex ${embedded ? 'h-full min-h-0' : 'min-h-dvh'} w-full justify-center overflow-hidden bg-[#111827] text-white`}
    >
      {backgroundImageSrc ? (
        <ResolvedAssetImage
          src={backgroundImageSrc}
          alt=""
          className="absolute inset-0 h-full w-full object-cover"
        />
      ) : (
        <div className="absolute inset-0 bg-[linear-gradient(180deg,rgba(15,23,42,0.10),rgba(15,23,42,0.92)),linear-gradient(135deg,#162235_0%,#334155_46%,#111827_100%)]" />
      )}
      <div className="absolute inset-0 bg-[linear-gradient(180deg,rgba(15,23,42,0.18),rgba(15,23,42,0.88)),linear-gradient(90deg,rgba(0,0,0,0.32),transparent_36%,rgba(0,0,0,0.38))]" />
      <div
        className={`relative flex ${embedded ? 'h-full min-h-0' : 'min-h-dvh'} min-w-0 flex-col overflow-hidden px-3 pb-[calc(env(safe-area-inset-bottom,0px)+0.8rem)] pt-[calc(env(safe-area-inset-top,0px)+0.65rem)]`}
        style={{
          boxSizing: 'border-box',
          maxWidth: '100vw',
          width: 'min(100vw, 64rem)',
        }}
      >
        <header className="flex items-center justify-between gap-2">
          <button
            type="button"
            className="flex h-10 w-10 items-center justify-center rounded-full border border-white/16 bg-black/24 text-white backdrop-blur"
            onClick={onBack}
            aria-label="返回"
            title="返回"
          >
            <ArrowLeft size={20} />
          </button>
          <div className="min-w-0 rounded-full border border-white/16 bg-black/24 px-3 py-2 text-center text-sm font-black backdrop-blur">
            <span className="block max-w-[12rem] truncate sm:max-w-[22rem]">
              {sceneName || displayDraft.workTitle}
            </span>
          </div>
          <button
            type="button"
            className={`flex h-10 w-10 items-center justify-center rounded-full border border-white/16 bg-black/24 text-white backdrop-blur ${
              textModeEnabled ? 'ring-2 ring-white/40' : ''
            }`}
            onClick={() => updateTextMode(!textModeEnabled)}
            aria-label="文本模式"
            title="文本模式"
          >
            <MessageSquareText size={18} />
          </button>
        </header>

        <section className="relative mt-3 flex min-h-0 flex-1 items-end justify-center overflow-hidden rounded-[1.35rem] border border-white/12 bg-black/18 px-3 pb-3 pt-6 shadow-[0_20px_54px_rgba(0,0,0,0.28)] backdrop-blur-sm">
          <div className="absolute inset-0 bg-[linear-gradient(180deg,rgba(255,255,255,0.08),transparent_34%),linear-gradient(90deg,rgba(15,23,42,0.22),transparent_36%,rgba(15,23,42,0.3))]" />
          <div className="relative flex w-full max-w-4xl items-end justify-center gap-3 sm:gap-9">
      {visibleCharacters.length > 0 ? (
              visibleCharacters.map((character, index) => (
                <VisualNovelCharacterStandee
                  key={character.characterId}
                  character={character}
                  index={index}
                  active={
                    !displayState.dialogueStep ||
                    displayState.dialogueStep.characterId ===
                      character.characterId
                  }
                />
              ))
            ) : (
              <div className="h-[min(40dvh,16rem)]" />
            )}
          </div>
        </section>

        <section className="relative mt-3 rounded-[1.25rem] border border-white/16 bg-black/46 p-3 shadow-[0_18px_44px_rgba(0,0,0,0.28)] backdrop-blur">
          <div className="mb-2 flex min-w-0 items-center justify-between gap-3">
            <div className="min-w-0 truncate text-sm font-black text-white">
              {primarySpeaker}
            </div>
            <div className="shrink-0 text-[11px] font-bold text-white/58">
              {displayRun.mode === 'test' ? 'TEST' : 'PLAY'}
            </div>
          </div>
          <p className="m-0 min-h-[4.75rem] break-words text-base leading-7 text-white/92">
            {primaryText}
          </p>
          {displayState.transitionStep?.text ? (
            <div className="mt-3 rounded-[0.9rem] border border-white/12 bg-white/10 px-3 py-2 text-sm font-semibold text-white/76">
              {displayState.transitionStep.text}
            </div>
          ) : null}
          {displayStreamingText ? (
            <div className="mt-3 rounded-[0.9rem] border border-white/12 bg-white/10 px-3 py-2 text-sm leading-6 text-white/72">
              {displayStreamingText}
            </div>
          ) : null}
          {textModeEnabled ? (
            <div className="mt-3 max-h-32 overflow-y-auto rounded-[0.9rem] border border-white/12 bg-white/10 px-3 py-2 whitespace-pre-line text-sm leading-6 text-white/78">
              {textModeLines || primaryText}
            </div>
          ) : null}
        </section>

        {choices.length > 0 ? (
          <section className="mt-3 grid gap-2 sm:grid-cols-2">
            {choices.map((choice) => (
              <button
                key={choice.choiceId}
                type="button"
            disabled={displayBusy}
            onClick={() => submitChoice(choice)}
                className="min-h-12 rounded-[0.95rem] border border-white/16 bg-white/12 px-3 py-2 text-left text-sm font-black leading-5 text-white shadow-[0_10px_24px_rgba(0,0,0,0.18)] backdrop-blur transition hover:bg-white/18 disabled:cursor-not-allowed disabled:opacity-55"
              >
                <span className="block break-words">{choice.text}</span>
              </button>
            ))}
          </section>
        ) : (
          <section className="mt-3">
            <button
              type="button"
              disabled={displayBusy}
              onClick={continueRuntime}
              className="flex min-h-12 w-full items-center justify-center gap-2 rounded-[0.95rem] border border-white/16 bg-white/12 px-3 text-sm font-black text-white backdrop-blur transition hover:bg-white/18 disabled:cursor-not-allowed disabled:opacity-55"
            >
              <span>继续</span>
              <ChevronRight className="h-4 w-4" />
            </button>
          </section>
        )}

        <section className="mt-3 flex gap-2">
          <input
            value={freeText}
            disabled={
              displayBusy || !displayDraft.runtimeConfig.allowFreeTextAction
            }
            onChange={(event) => setFreeText(event.target.value)}
            onKeyDown={(event) => {
              if (event.key === 'Enter') {
                event.preventDefault();
                submitFreeText();
              }
            }}
            className="min-h-11 min-w-0 flex-1 rounded-full border border-white/16 bg-black/26 px-4 text-sm font-semibold text-white outline-none placeholder:text-white/42"
            placeholder="输入行动"
            aria-label="输入行动"
          />
          <button
            type="button"
            disabled={!canSubmitFreeText}
            onClick={submitFreeText}
            className="flex h-11 w-11 shrink-0 items-center justify-center rounded-full border border-white/16 bg-white/14 text-white backdrop-blur transition hover:bg-white/20 disabled:cursor-not-allowed disabled:opacity-55"
            aria-label="发送行动"
            title="发送行动"
          >
            <Send size={18} />
          </button>
        </section>

        {displayError ? (
          <div className="mt-3 rounded-[0.95rem] border border-rose-200/35 bg-rose-400/18 px-3 py-2 text-center text-sm font-black text-rose-50">
            {displayError}
          </div>
        ) : null}

        <footer
          className={`mt-3 grid gap-2 ${canShowAttributes ? 'grid-cols-4' : 'grid-cols-3'}`}
        >
          {[
            { kind: 'history' as const, label: '历史', icon: History },
            { kind: 'save' as const, label: '存档', icon: Bookmark },
            { kind: 'settings' as const, label: '设置', icon: Settings },
            ...(canShowAttributes
              ? [
                  {
                    kind: 'attributes' as const,
                    label: '属性',
                    icon: SlidersHorizontal,
                  },
                ]
              : []),
          ].map((item) => {
            const Icon = item.icon;
            return (
              <button
                key={item.kind}
                type="button"
                onClick={() => setActivePanel(item.kind)}
                className="flex min-h-12 min-w-0 flex-col items-center justify-center gap-1 rounded-[0.95rem] border border-white/14 bg-black/24 px-2 text-xs font-black text-white backdrop-blur transition hover:bg-black/34"
              >
                <Icon className="h-4 w-4" />
                <span className="max-w-full truncate">{item.label}</span>
              </button>
            );
          })}
        </footer>
      </div>

      {activePanel ? (
        <VisualNovelRuntimePanel
          kind={activePanel}
          draft={displayDraft}
          run={displayRun}
          isBusy={displayBusy}
          isSaving={displaySaving}
          isLoadingArchives={displayLoadingArchives}
          resumingWorldKey={displayResumingWorldKey}
          saveArchives={displaySaveArchives}
          allowRegeneration={displayDraft.runtimeConfig.allowHistoryRegeneration}
          onClose={() => setActivePanel(null)}
          onRegenerateHistoryEntry={regenerateHistoryEntry}
          onSaveRun={saveRuntime}
          onResumeSaveArchive={resumeArchive}
          textModeEnabled={textModeEnabled}
          onTextModeChange={updateTextMode}
        />
      ) : null}
    </main>
  );
}

export default VisualNovelRuntimeShell;
