"use client";

import {
  useCallback,
  useEffect,
  useMemo,
  useRef,
  useState,
  type DragEvent as ReactDragEvent,
  type KeyboardEvent as ReactKeyboardEvent,
  type ReactNode,
  type RefObject,
} from "react";
import { CloseOutlined, FileTextOutlined, RightOutlined } from "@ant-design/icons";
import { Dropdown, Popover, Tooltip } from "antd";
import { useI18n } from "@/i18n";
import {
  getReferenceMaterialKey,
  type ComposerReferenceMaterial,
} from "@/utils/composerReferenceSubjects";
import { useWorkflowStore } from "@/store/workflowStore";
import type { GenerationNodeConfig } from "@/utils/generationConfig";
import { ComposerMentionInput, type ComposerMentionReferenceItem } from "@/components/composer/ComposerMentionInput";
import { ComposerReferenceMaterialCard } from "@/components/composer/ComposerReferenceMaterialCard";
import type { ComposerCapability } from "@/utils/composerNodeDescriptor";
import type { SelectedModel } from "@/types";
import {
  POPI_VIDEO_SUBTYPE_FIRST_LAST_FRAME,
  POPI_VIDEO_SUBTYPE_IMAGE_TO_VIDEO,
  POPI_VIDEO_SUBTYPE_OMNI_REFERENCE,
} from "@/constants/generationTask";

export interface ComposerConnectedTextCard {
  text: string;
  edgeId?: string;
}

interface ComposerVideoSubTypeOption {
  value: number;
  label: string;
  disabledReason?: string;
}

const EMPTY_PARAMETERS: Record<string, unknown> = {};

function selectedModelSubTypes(model: SelectedModel): number[] {
  return Array.isArray(model.subTypes)
    ? model.subTypes.filter((subType) => Number.isFinite(subType))
    : [];
}

function isVideoSubTypeAllowedForMaterials(
  subType: number,
  referenceImageCount: number,
  hasVideoOrAudio: boolean
): boolean {
  if (subType === POPI_VIDEO_SUBTYPE_IMAGE_TO_VIDEO) return referenceImageCount === 1 && !hasVideoOrAudio;
  if (subType === POPI_VIDEO_SUBTYPE_FIRST_LAST_FRAME) return referenceImageCount === 2 && !hasVideoOrAudio;
  return true;
}

type AssistMenuType = "command";

interface AssistMenuItem {
  key: string;
  label: string;
  value: string;
  image?: string;
  sourceNodeId?: string;
  referenceType?: ComposerReferenceMaterial["type"];
}

interface ComposerPromptMaterialsProps {
  hasMediaHeader: boolean;
  headerStatusContent?: ReactNode;
  activeCapability: ComposerCapability;
  nodeId?: string;
  selectedModel: SelectedModel;
  connectedTextCards: ComposerConnectedTextCard[];
  referenceMaterials: ComposerReferenceMaterial[];
  playingReferenceAudioKey: string | null;
  onRemoveConnectedEdge: (edgeId: string) => void;
  onReferenceMaterialDragStart: (
    event: ReactDragEvent<HTMLDivElement>,
    material: ComposerReferenceMaterial
  ) => void;
  onReferenceMaterialDrop: (
    event: ReactDragEvent<HTMLDivElement>,
    material: ComposerReferenceMaterial
  ) => void;
  onRemoveReferenceMaterial: (material: ComposerReferenceMaterial) => void;
  onToggleReferenceAudio: (material: ComposerReferenceMaterial) => void;
  onStopReferenceAudio: () => void;
  onVideoSubTypeChange?: (subType: number) => void;
  bodyContent?: ReactNode;
  promptInputRef: RefObject<HTMLDivElement | null>;
  promptValue: string;
  promptReadOnly: boolean;
  promptMinHeightPx: number;
  promptMaxHeightPx: number;
  referenceItems: ComposerMentionReferenceItem[];
  onDraftPatch: (patch: Partial<GenerationNodeConfig>, fields: Array<keyof GenerationNodeConfig>) => void;
  onPromptFocus?: () => void;
  onSubmitShortcut: () => void;
}

function getContentEditableCaretOffset(element: HTMLDivElement): number {
  const selection = window.getSelection();
  if (!selection || selection.rangeCount === 0) return element.textContent?.length ?? 0;
  const range = selection.getRangeAt(0);
  if (!element.contains(range.endContainer)) return element.textContent?.length ?? 0;

  const beforeRange = range.cloneRange();
  beforeRange.selectNodeContents(element);
  beforeRange.setEnd(range.endContainer, range.endOffset);
  return beforeRange.toString().length;
}

function getTextNodeAtOffset(root: Node, offset: number): { node: Text; offset: number } | null {
  const walker = document.createTreeWalker(root, NodeFilter.SHOW_TEXT);
  let remaining = offset;
  let current = walker.nextNode();

  while (current) {
    const text = current.textContent ?? "";
    if (remaining <= text.length) {
      return { node: current as Text, offset: remaining };
    }
    remaining -= text.length;
    current = walker.nextNode();
  }

  return null;
}

function setContentEditableCaretOffset(element: HTMLDivElement, offset: number): void {
  const selection = window.getSelection();
  if (!selection) return;
  const textLength = element.textContent?.length ?? 0;
  const safeOffset = Math.max(0, Math.min(offset, textLength));
  const textPosition = getTextNodeAtOffset(element, safeOffset);
  const range = document.createRange();

  if (textPosition) {
    range.setStart(textPosition.node, textPosition.offset);
  } else {
    range.setStart(element, element.childNodes.length);
  }
  range.collapse(true);
  selection.removeAllRanges();
  selection.addRange(range);
}

export function ComposerPromptMaterials({
  hasMediaHeader,
  headerStatusContent,
  activeCapability,
  nodeId,
  selectedModel,
  connectedTextCards,
  referenceMaterials,
  playingReferenceAudioKey,
  onRemoveConnectedEdge,
  onReferenceMaterialDragStart,
  onReferenceMaterialDrop,
  onRemoveReferenceMaterial,
  onToggleReferenceAudio,
  onStopReferenceAudio,
  onVideoSubTypeChange,
  bodyContent,
  promptInputRef,
  promptValue,
  promptReadOnly,
  promptMinHeightPx,
  promptMaxHeightPx,
  referenceItems,
  onDraftPatch,
  onPromptFocus,
  onSubmitShortcut,
}: ComposerPromptMaterialsProps) {
  const { t } = useI18n();
  const storeSubType = useWorkflowStore((state) => {
    if (!nodeId) return undefined;
    const node = state.nodes.find((candidate) => candidate.id === nodeId);
    const subType = (node?.data as { subType?: unknown } | undefined)?.subType;
    return typeof subType === "number" && Number.isFinite(subType) ? subType : undefined;
  });
  const assistTriggerIndexRef = useRef<number | null>(null);
  const [assistMenu, setAssistMenu] = useState<AssistMenuType | null>(null);
  const [assistMenuActiveIndex, setAssistMenuActiveIndex] = useState(0);
  const [assistMenuPosition, setAssistMenuPosition] = useState<{ left: number; top: number } | null>(null);
  const videoSubTypeOptions = useMemo<ComposerVideoSubTypeOption[]>(() => {
    if (activeCapability !== "video") return [];
    const subTypes = selectedModelSubTypes(selectedModel);
    const shouldDisableInvalidOptions = subTypes.length > 1;
    const referenceImageCount = referenceMaterials.filter((material) => material.type === "image").length;
    const hasVideoOrAudio = referenceMaterials.some((material) => material.type === "video" || material.type === "audio");
    return subTypes.flatMap((subType): ComposerVideoSubTypeOption[] => {
      const disabledReason = shouldDisableInvalidOptions && !isVideoSubTypeAllowedForMaterials(subType, referenceImageCount, hasVideoOrAudio)
        ? subType === POPI_VIDEO_SUBTYPE_IMAGE_TO_VIDEO
          ? t("composer.videoSubTypeImageToVideoImageCountRequired")
          : t("composer.videoSubTypeFirstLastFrameImageCountRequired")
        : undefined;
      let label: string | null = null;
      if (subType === POPI_VIDEO_SUBTYPE_IMAGE_TO_VIDEO) {
        label = t("composer.videoSubTypeMultiReference");
      }
      if (subType === POPI_VIDEO_SUBTYPE_OMNI_REFERENCE) {
        label = t("composer.videoSubTypeTextToVideo");
      }
      if (subType === POPI_VIDEO_SUBTYPE_FIRST_LAST_FRAME) {
        label = t("composer.videoSubTypeFirstLastFrame");
      }
      return label ? [{ value: subType, label, disabledReason }] : [];
    });
  }, [activeCapability, referenceMaterials, selectedModel, t]);
  const selectedVideoSubType = typeof storeSubType === "number" && Number.isFinite(storeSubType)
    ? storeSubType
    : typeof selectedModel.subType === "number" && Number.isFinite(selectedModel.subType)
      ? selectedModel.subType
      : videoSubTypeOptions[0]?.value;
  const hasVideoSubTypeHeader = videoSubTypeOptions.length > 0;
  const hasReferenceHeader = connectedTextCards.length > 0 || referenceMaterials.length > 0;
  const commandAssistItems = useMemo<AssistMenuItem[]>(() => [
    { key: "character", label: t("composer.commandCharacter"), value: t("composer.commandCharacterValue") },
    { key: "turnaround", label: t("composer.commandTurnaround"), value: t("composer.commandTurnaroundValue") },
    { key: "video-shot", label: t("composer.commandVideoShot"), value: t("composer.commandVideoShotValue") },
  ], [t]);
  const activeAssistItems = assistMenu === "command" ? commandAssistItems : [];

  const closeAssistMenu = useCallback(() => {
    assistTriggerIndexRef.current = null;
    setAssistMenu(null);
    setAssistMenuPosition(null);
  }, []);

  const syncAssistMenuFromPromptCursor = useCallback((input: HTMLDivElement | null) => {
    if (promptReadOnly || !input) {
      closeAssistMenu();
      return;
    }

    const cursor = getContentEditableCaretOffset(input);
    const inputValue = input.textContent ?? "";
    const previousChar = cursor > 0 ? inputValue[cursor - 1] : "";
    const selection = window.getSelection();
    const selectionRect = selection?.rangeCount ? selection.getRangeAt(0).getBoundingClientRect() : null;
    const inputRect = input.getBoundingClientRect();
    const nextMenuPosition = {
      left: Math.max(8, Math.min((selectionRect?.left || inputRect.left) + 16, window.innerWidth - 296)),
      top: Math.max(8, (selectionRect?.top || inputRect.top) - 8),
    };
    if (previousChar === "/") {
      assistTriggerIndexRef.current = cursor - 1;
      setAssistMenuPosition(nextMenuPosition);
      setAssistMenu("command");
      setAssistMenuActiveIndex(0);
      return;
    }
    closeAssistMenu();
  }, [closeAssistMenu, promptReadOnly]);

  useEffect(() => {
    if (promptReadOnly) closeAssistMenu();
  }, [closeAssistMenu, promptReadOnly]);

  useEffect(() => {
    if (!assistMenu) return;
    setAssistMenuActiveIndex((current) =>
      activeAssistItems.length === 0 ? 0 : Math.min(current, activeAssistItems.length - 1)
    );
  }, [activeAssistItems.length, assistMenu]);

  const insertAssistValue = useCallback((value: string, trigger: "/" | "@") => {
    const input = promptInputRef.current;
    const fallbackIndex = promptValue.endsWith(trigger) ? promptValue.length - 1 : promptValue.length;
    const triggerIndex = assistTriggerIndexRef.current ?? fallbackIndex;
    const shouldReplaceTrigger = promptValue[triggerIndex] === trigger;
    const before = shouldReplaceTrigger ? promptValue.slice(0, triggerIndex) : promptValue.slice(0, triggerIndex);
    const after = shouldReplaceTrigger ? promptValue.slice(triggerIndex + 1) : promptValue.slice(triggerIndex);
    const nextPrompt = `${before}${value}${after}`;

    onDraftPatch({ prompt: nextPrompt }, ["prompt"]);
    closeAssistMenu();

    window.setTimeout(() => {
      input?.focus();
      const nextCursor = before.length + value.length;
      if (input) setContentEditableCaretOffset(input, nextCursor);
    }, 0);
  }, [closeAssistMenu, onDraftPatch, promptInputRef, promptValue]);

  const insertCommand = useCallback((value: string) => {
    insertAssistValue(value, "/");
  }, [insertAssistValue]);

  const selectAssistMenuItem = useCallback((item: AssistMenuItem) => {
    if (assistMenu === "command") {
      insertCommand(item.value);
    }
  }, [assistMenu, insertCommand]);

  const handlePromptKeyDown = useCallback((event: ReactKeyboardEvent<HTMLDivElement>) => {
    if (assistMenu && activeAssistItems.length > 0) {
      if (event.key === "ArrowDown") {
        event.preventDefault();
        setAssistMenuActiveIndex((current) => (current + 1) % activeAssistItems.length);
        return;
      }
      if (event.key === "ArrowUp") {
        event.preventDefault();
        setAssistMenuActiveIndex((current) => (current - 1 + activeAssistItems.length) % activeAssistItems.length);
        return;
      }
      if (event.key === "Enter") {
        event.preventDefault();
        selectAssistMenuItem(activeAssistItems[assistMenuActiveIndex] ?? activeAssistItems[0]);
        return;
      }
    }

    if ((event.metaKey || event.ctrlKey) && event.key === "Enter") {
      event.preventDefault();
      onSubmitShortcut();
    }
  }, [activeAssistItems, assistMenu, assistMenuActiveIndex, onSubmitShortcut, selectAssistMenuItem]);

  const assistMenuOverlay = assistMenu && assistMenuPosition
    ? (
      <Dropdown
        open
        onOpenChange={(nextOpen) => {
          if (nextOpen) return;
          closeAssistMenu();
        }}
        trigger={["click"]}
        popupRender={() => (
          <div className="nodrag nopan nowheel pointer-events-auto w-72 overflow-hidden rounded-xl border border-[var(--border-subtle)] bg-[var(--surface-2)] shadow-[var(--shadow-panel)]">
            <div className="border-b border-[var(--border-subtle)] px-3 py-2 text-[11px] font-medium text-[var(--text-muted)]">
              {t("composer.commands")}
            </div>
            <div className="py-1">
              {activeAssistItems.map((item, itemIndex) => {
                const isActive = itemIndex === assistMenuActiveIndex;
                return (
                  <button
                    key={item.key}
                    type="button"
                    onMouseEnter={() => setAssistMenuActiveIndex(itemIndex)}
                    onClick={() => selectAssistMenuItem(item)}
                    className={`flex w-full items-center justify-between gap-2 px-3 py-2 text-left text-xs transition-colors ${isActive
                        ? "bg-[var(--surface-active)] text-[var(--text-primary)]"
                        : "text-[var(--text-secondary)] hover:bg-[var(--surface-hover)] hover:text-[var(--text-primary)]"
                      }`}
                  >
                    <span className="flex min-w-0 items-center gap-2">
                      {item.image && <img src={item.image} alt="" className="h-8 w-8 rounded object-cover" />}
                      <span className="truncate">{item.label}</span>
                    </span>
                    <RightOutlined className="text-[var(--text-muted)]" />
                  </button>
                );
              })}
            </div>
          </div>
        )}
      >
        <div
          aria-hidden="true"
          className="pointer-events-none fixed z-[10080] h-px w-72"
          style={{
            left: assistMenuPosition.left,
            top: assistMenuPosition.top,
          }}
        />
      </Dropdown>
    )
    : null;

  return (
    <>
      {assistMenuOverlay}
      {hasMediaHeader && (
        <div className="space-y-2 px-2 pt-2 pr-16">
          {hasVideoSubTypeHeader && (
            <div className="flex max-w-[24rem] items-center gap-1 overflow-x-auto">
              {videoSubTypeOptions.map((option) => {
                const selected = option.value === selectedVideoSubType;
                const disabled = Boolean(option.disabledReason);
                return (
                  <button
                    key={option.value}
                    type="button"
                    disabled={disabled}
                    onClick={() => onVideoSubTypeChange?.(option.value)}
                    title={option.disabledReason ?? option.label}
                    className={`h-8 shrink-0 rounded-lg border px-3 text-xs transition-colors ${
                      selected
                        ? "border-[var(--text-primary)] bg-[var(--surface-hover)] text-[var(--text-primary)]"
                        : disabled
                          ? "cursor-not-allowed border-[var(--border-subtle)] text-[var(--text-disabled)] opacity-60"
                          : "border-[var(--border-subtle)] text-[var(--text-muted)] hover:border-[var(--border-strong)] hover:bg-[var(--surface-hover)] hover:text-[var(--text-primary)]"
                    }`}
                  >
                    {option.label}
                  </button>
                );
              })}
            </div>
          )}
          {headerStatusContent ?? (hasReferenceHeader ? (
            <div className="flex max-w-[24rem] items-center gap-1.5 overflow-x-auto rounded-lg pr-1">
              {connectedTextCards.map((item, index) => {
                const edgeId = item.edgeId;

                return (
                  <Tooltip
                    key={`text-${index}-${item.text.slice(0, 24)}`}
                    placement="top"
                    color="#000000"
                    title={(
                      <div className="max-h-[160px] max-w-[400px] overflow-hidden whitespace-pre-wrap break-words text-xs">
                        {item.text}
                      </div>
                    )}
                  >
                    <div
                      aria-label={`${t("node.text")}${index + 1}`}
                      title={item.text}
                      className="group relative flex h-12 w-12 shrink-0 items-center justify-center rounded-lg border border-[var(--border-subtle)] bg-[var(--surface-1)] text-[var(--text-secondary)]"
                    >
                      <FileTextOutlined className="text-lg" />
                      <span className="pointer-events-none absolute right-0.5 top-0.5 z-10 flex h-4 min-w-4 items-center justify-center rounded-full bg-[var(--surface-1)] px-1 text-[10px] text-[var(--text-secondary)] transition-opacity group-hover:opacity-0">
                        {index + 1}
                      </span>
                      {edgeId ? (
                        <button
                          type="button"
                          aria-label={t("composer.removeConnection")}
                          title={t("composer.removeConnection")}
                          onPointerDown={(event) => event.stopPropagation()}
                          onMouseDown={(event) => event.stopPropagation()}
                          onClick={(event) => {
                            event.preventDefault();
                            event.stopPropagation();
                            onRemoveConnectedEdge(edgeId);
                          }}
                          className="nodrag nopan absolute right-0.5 top-0.5 z-20 flex h-4 w-4 items-center justify-center rounded-full bg-[var(--surface-overlay)] text-[var(--text-on-overlay)] opacity-0 transition hover:bg-[var(--danger)] group-hover:opacity-100 focus:opacity-100 focus:outline-none focus-visible:ring-2 focus-visible:ring-cyan-400/70"
                        >
                          <CloseOutlined className="text-[9px]" />
                        </button>
                      ) : null}
                    </div>
                  </Tooltip>
                );
              })}
              {referenceMaterials.map((material, materialIndex) => {
                const materialKey = getReferenceMaterialKey(material);
                const referenceLabel = material.type === "image"
                  ? t("composer.referenceImage", { index: material.id })
                  : material.name;
                const badgeLabel =
                  selectedVideoSubType === POPI_VIDEO_SUBTYPE_FIRST_LAST_FRAME && material.type === "image"
                    ? materialIndex === 0
                      ? t("composer.firstFrame")
                      : materialIndex === 1
                        ? t("composer.lastFrame")
                        : material.id
                    : material.id;
                const referenceCard = (
                  <ComposerReferenceMaterialCard
                    material={material}
                    label={referenceLabel}
                    badgeLabel={badgeLabel}
                    isAudioPlaying={playingReferenceAudioKey === materialKey}
                    onDragStart={onReferenceMaterialDragStart}
                    onDrop={onReferenceMaterialDrop}
                    onRemoveMaterial={onRemoveReferenceMaterial}
                    onToggleAudio={onToggleReferenceAudio}
                    onStopAudio={onStopReferenceAudio}
                    playLabel={t("common.play")}
                    pauseLabel={t("common.pause")}
                    removeLabel={t("composer.removeConnection")}
                  />
                );

                if (material.type === "audio") {
                  return <div key={materialKey}>{referenceCard}</div>;
                }

                return (
                  <Popover
                    key={materialKey}
                    placement="top"
                    trigger="hover"
                    arrow={false}
                    content={(
                      <div className="overflow-hidden rounded-lg bg-[var(--media-preview-bg)]">
                        {material.type === "image" ? (
                          <img
                            src={material.url}
                            alt=""
                            className="max-h-[320px] max-w-[320px] object-contain"
                            style={{ minWidth: 120, minHeight: 120 }}
                          />
                        ) : (
                          <video
                            src={material.url}
                            muted
                            autoPlay
                            loop
                            playsInline
                            preload="metadata"
                            className="max-h-[320px] max-w-[320px] bg-[var(--media-preview-bg)] object-contain"
                            style={{ minWidth: 120, minHeight: 120 }}
                          />
                        )}
                      </div>
                    )}
                    styles={{
                      container: { padding: 0 },
                      content: {
                        padding: 0,
                        background: "transparent",
                        boxShadow: "none",
                      },
                    }}
                  >
                    <div className="inline-flex">
                      {referenceCard}
                    </div>
                  </Popover>
                );
              })}
            </div>
          ) : null)}
        </div>
      )}

      <div className="relative shrink-0 px-2 pt-2">
        {bodyContent ?? (
          <>
            <ComposerMentionInput
              ref={promptInputRef}
              value={promptValue}
              readOnly={promptReadOnly}
              placeholder={t("composer.mainPlaceholder")}
              minHeightPx={promptMinHeightPx}
              maxHeightPx={promptMaxHeightPx}
              onChange={(nextValue) => {
                onDraftPatch({ prompt: nextValue }, ["prompt"]);
                syncAssistMenuFromPromptCursor(promptInputRef.current);
              }}
              onFocus={onPromptFocus}
              onClick={(element) => syncAssistMenuFromPromptCursor(element)}
              onKeyUp={(event) => {
                if (assistMenu && ["ArrowDown", "ArrowUp", "Enter"].includes(event.key)) return;
                syncAssistMenuFromPromptCursor(event.currentTarget);
              }}
              onKeyDown={handlePromptKeyDown}
              referenceItems={referenceItems}
              referenceMenuLabel={t("composer.referenceMaterials")}
              className={`nodrag nopan nowheel w-full overflow-y-auto rounded-lg bg-transparent pr-12 text-sm leading-6 outline-none ${promptReadOnly ? "text-[var(--text-secondary)]" : "text-[var(--text-primary)]"}`}
            />
            {promptReadOnly && (
              <div className="pb-2 text-[11px] text-cyan-200/80">
                {t("composer.promptProvidedByUpstream")}
              </div>
            )}
          </>
        )}
      </div>
    </>
  );
}
