"use client";

import { useEffect, useMemo, useRef, useState, type KeyboardEvent, type ReactNode } from "react";
import { Film, ImagePlus, ImageUp, Loader2, Music, Paintbrush, Send, Upload, X } from "lucide-react";
import clsx from "clsx";
import { ImageEditor, type ImageEditMode } from "@/components/image-editor";
import { clampPage, pageItems, Pagination } from "@/components/pagination";
import { crossfadeIn, pulseFeedback, revealChildren, runScopedMotion } from "@/lib/ui/motion";
import { VIDEO_DURATION_DEFAULT, VIDEO_DURATION_OPTIONS, VIDEO_RATIOS, VIDEO_RESOLUTIONS, clampVideoDuration } from "@/lib/video-settings";
import type { Asset } from "@/lib/types";
import type { PromptMaterial } from "@/lib/prompt/assembler";

type GenerateMode = "image" | "video";
type StudioMode = GenerateMode | ImageEditMode;
type MaterialKind = PromptMaterial["type"];

type MentionState = {
  start: number;
  query: string;
};

const imageSizePresets = [
  { label: "1:1", width: 2048, height: 2048 },
  { label: "4:3", width: 2304, height: 1728 },
  { label: "16:9", width: 2560, height: 1440 },
  { label: "9:16", width: 1440, height: 2560 }
];

const materialUploadAccept = "image/*,video/*,audio/*";
const MATERIAL_PAGE_SIZE = 6;

export function CreateStudio({ initialMode = "image" }: { initialMode?: StudioMode }) {
  const [mode, setMode] = useState<StudioMode>(initialMode);
  const [promptByMode, setPromptByMode] = useState<Record<GenerateMode, string>>({
    image: "",
    video: ""
  });
  const [materials, setMaterials] = useState<PromptMaterial[]>([]);
  const [busy, setBusy] = useState(false);
  const [uploading, setUploading] = useState(false);
  const [error, setError] = useState<string | null>(null);
  const [notice, setNotice] = useState<string | null>(null);
  const [imageSize, setImageSize] = useState(imageSizePresets[0]);
  const [imageScale, setImageScale] = useState(50);
  const [forceSingle, setForceSingle] = useState(true);
  const [videoRatio, setVideoRatio] = useState("9:16");
  const [videoDuration, setVideoDuration] = useState(VIDEO_DURATION_DEFAULT);
  const [videoResolution, setVideoResolution] = useState("720p");
  const [mentionState, setMentionState] = useState<MentionState | null>(null);
  const [activeMentionIndex, setActiveMentionIndex] = useState(0);
  const [promptScrollTop, setPromptScrollTop] = useState(0);
  const [materialPage, setMaterialPage] = useState(1);
  const studioRef = useRef<HTMLDivElement | null>(null);
  const modePanelRef = useRef<HTMLDivElement | HTMLElement | null>(null);
  const feedbackRef = useRef<HTMLDivElement | null>(null);
  const materialBoardRef = useRef<HTMLDivElement | null>(null);
  const promptRef = useRef<HTMLTextAreaElement | null>(null);

  const isImageEditMode = mode === "inpaint" || mode === "upscale";
  const generateMode: GenerateMode = mode === "video" ? "video" : "image";
  const prompt = promptByMode[generateMode];
  const visibleMaterials = pageItems(materials, materialPage, MATERIAL_PAGE_SIZE);
  const materialPageOffset = (clampPage(materialPage, materials.length, MATERIAL_PAGE_SIZE) - 1) * MATERIAL_PAGE_SIZE;
  const mentionSuggestions = useMemo(() => {
    if (!mentionState) return [];
    return materials.filter((material) => materialMatchesMention(material, mentionState.query)).slice(0, 8);
  }, [materials, mentionState]);

  useEffect(() => {
    setActiveMentionIndex(0);
  }, [mentionState?.query, mentionSuggestions.length]);

  useEffect(() => {
    setMaterialPage((page) => clampPage(page, materials.length, MATERIAL_PAGE_SIZE));
  }, [materials.length]);

  useEffect(() => {
    return runScopedMotion(studioRef, (scope) => revealChildren(scope));
  }, []);

  useEffect(() => {
    crossfadeIn(modePanelRef.current);
  }, [mode]);

  useEffect(() => {
    pulseFeedback(feedbackRef.current);
  }, [error, notice]);

  useEffect(() => {
    if (materialBoardRef.current) revealChildren(materialBoardRef.current, ".material-card");
  }, [materials.length, materialPage]);

  function setPrompt(value: string) {
    setPromptByMode((items) => ({ ...items, [generateMode]: value }));
  }

  function handlePromptInput(value: string, cursor: number) {
    setPrompt(value);
    updateMention(value, cursor);
  }

  function updateMention(value: string, cursor: number) {
    setMentionState(findMentionAtCursor(value, cursor));
  }

  async function uploadFiles(files: FileList | null) {
    if (!files?.length) return;
    setUploading(true);
    setError(null);
    setNotice(null);
    try {
      const formData = new FormData();
      for (const file of Array.from(files)) formData.append("files", file);
      const response = await fetch("/api/assets/upload", {
        method: "POST",
        body: formData
      });
      const payload = await response.json();
      if (!response.ok) throw new Error(payload.error || "上传失败");
      const uploaded = payload.assets as Asset[];
      setMaterials((items) => {
        const next = [...items];
        for (const asset of uploaded) next.push(materialFromAsset(asset, next));
        return next;
      });
    } catch (err) {
      setError(err instanceof Error ? err.message : String(err));
    } finally {
      setUploading(false);
    }
  }

  function materialFromAsset(asset: Asset, existing: PromptMaterial[]): PromptMaterial {
    const type = materialTypeForAsset(asset);
    const currentCount = existing.filter((material) => material.type === type).length;
    return {
      id: asset.id,
      url: asset.url,
      type,
      role: "upload",
      label: labelFor(type, currentCount + 1),
      name: asset.name
    };
  }

  function removeMaterial(index: number) {
    setMaterials((items) => relabel(items.filter((_, itemIndex) => itemIndex !== index)));
  }

  function insertToken(token?: string) {
    if (!token) return;
    insertAtCursor(token);
  }

  function insertAtCursor(text: string) {
    const textarea = promptRef.current;
    if (!textarea) {
      const joiner = prompt.endsWith("\n") || !prompt.trim() ? "" : "\n";
      setPrompt(`${prompt}${joiner}${text}`);
      return;
    }
    const start = textarea.selectionStart ?? prompt.length;
    const end = textarea.selectionEnd ?? prompt.length;
    const before = prompt.slice(0, start);
    const after = prompt.slice(end);
    const needsLeadingSpace = before.length > 0 && !/[\s，。；：、（(【\[]$/.test(before);
    const needsTrailingSpace = after.length > 0 && !/^[\s，。；：、）)】\]]/.test(after);
    const insertion = `${needsLeadingSpace ? " " : ""}${text}${needsTrailingSpace ? " " : ""}`;
    const nextPrompt = `${before}${insertion}${after}`;
    const nextCursor = before.length + insertion.length;
    setPrompt(nextPrompt);
    window.requestAnimationFrame(() => {
      textarea.focus();
      textarea.setSelectionRange(nextCursor, nextCursor);
    });
  }

  function selectMention(material: PromptMaterial) {
    if (!material.label) return;
    const textarea = promptRef.current;
    const end = textarea?.selectionStart ?? prompt.length;
    const start = mentionState?.start ?? end;
    const before = prompt.slice(0, start);
    const after = prompt.slice(end);
    const needsTrailingSpace = after.length > 0 && !/^[\s，。；：、）)】\]]/.test(after);
    const insertion = `${material.label}${needsTrailingSpace ? " " : ""}`;
    const nextPrompt = `${before}${insertion}${after}`;
    const nextCursor = before.length + insertion.length;
    setPrompt(nextPrompt);
    setMentionState(null);
    window.requestAnimationFrame(() => {
      textarea?.focus();
      textarea?.setSelectionRange(nextCursor, nextCursor);
    });
  }

  function handlePromptKeyDown(event: KeyboardEvent<HTMLTextAreaElement>) {
    if (!mentionState) return;
    if (event.key === "Escape") {
      setMentionState(null);
      return;
    }
    if (!mentionSuggestions.length) return;
    if (event.key === "ArrowDown") {
      event.preventDefault();
      setActiveMentionIndex((index) => (index + 1) % mentionSuggestions.length);
    }
    if (event.key === "ArrowUp") {
      event.preventDefault();
      setActiveMentionIndex((index) => (index - 1 + mentionSuggestions.length) % mentionSuggestions.length);
    }
    if (event.key === "Enter" || event.key === "Tab") {
      event.preventDefault();
      selectMention(mentionSuggestions[activeMentionIndex] || mentionSuggestions[0]);
    }
  }

  async function submit() {
    if (isImageEditMode) return;
    setBusy(true);
    setError(null);
    setNotice(null);
    try {
      const endpoint = generateMode === "image" ? "/api/generations/image" : "/api/generations/video";
      const body = generateMode === "image"
        ? {
            capability: "image.generate",
            prompt,
            materials: materials.filter((material) => material.type === "image"),
            width: imageSize.width,
            height: imageSize.height,
            scale: imageScale,
            force_single: forceSingle
          }
        : {
            prompt,
            materials,
            settings: {
              ratio: videoRatio,
              duration: videoDuration,
              resolution: videoResolution
            }
          };
      const response = await fetch(endpoint, {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify(body)
      });
      const payload = await response.json();
      if (!response.ok) throw new Error(payload.error || "提交生成失败");
      setNotice(`${generateMode === "image" ? "图片" : "视频"}生成已提交。任务已进入「结果」，生成完成后结果资产会自动保留。`);
      setPrompt("");
      setMaterials([]);
      setMaterialPage(1);
      setMentionState(null);
      setPromptScrollTop(0);
    } catch (err) {
      setError(err instanceof Error ? err.message : String(err));
    } finally {
      setBusy(false);
    }
  }

  return (
    <div className={clsx("create-studio", isImageEditMode && "enhance-studio")} ref={studioRef}>
      <div className="workspace-head" data-animate>
        <div>
          <h1 className="workspace-title">创作生成台</h1>
        </div>
      </div>

      <div className="create-mode-bar" data-animate>
        <div className="segmented mode-switch" aria-label="创作类型">
          <button type="button" className={clsx(mode === "image" && "active")} aria-pressed={mode === "image"} onClick={() => setMode("image")}>
            <ImagePlus size={17} />
            图片
          </button>
          <button type="button" className={clsx(mode === "video" && "active")} aria-pressed={mode === "video"} onClick={() => setMode("video")}>
            <Film size={17} />
            视频
          </button>
          <button type="button" className={clsx(mode === "inpaint" && "active")} aria-pressed={mode === "inpaint"} onClick={() => setMode("inpaint")}>
            <Paintbrush size={17} />
            局部重绘
          </button>
          <button type="button" className={clsx(mode === "upscale" && "active")} aria-pressed={mode === "upscale"} onClick={() => setMode("upscale")}>
            <ImageUp size={17} />
            智能超清
          </button>
        </div>
        {!isImageEditMode ? (
          <div className="toolbar create-actions">
            <button
              className="button primary create-submit-button"
              type="button"
              disabled={busy || !prompt.trim()}
              onClick={submit}
              aria-label={generateMode === "image" ? "生成图片" : "生成视频"}
              title={generateMode === "image" ? "生成图片" : "生成视频"}
            >
              {busy ? <Loader2 className="spin" size={18} /> : <Send size={18} />}
              <span className="create-submit-label">{generateMode === "image" ? "生成图片" : "生成视频"}</span>
            </button>
          </div>
        ) : null}
      </div>

      {isImageEditMode ? (
        <div ref={(node) => { modePanelRef.current = node; }} data-animate>
          <ImageEditor initialMode={mode} hideModeSwitch />
        </div>
      ) : (
        <section className="panel" ref={(node) => { modePanelRef.current = node; }} data-animate>

        {error || notice ? (
          <div className="studio-messages top-studio-messages" ref={feedbackRef}>
            {error ? <div className="callout" role="alert">{error}</div> : null}
            {notice ? <div className="callout success-callout" role="status" aria-live="polite">{notice}</div> : null}
          </div>
        ) : null}

        <div className="field prompt-field">
          <div className="prompt-label-row">
            <label htmlFor="createPrompt">{generateMode === "image" ? "图片提示词" : "视频提示词"}</label>
            <label className="icon-button prompt-upload-button" title="上传素材" aria-label="上传素材">
              {uploading ? <Loader2 className="spin" size={18} /> : <Upload size={18} />}
              <input
                type="file"
                multiple
                accept={materialUploadAccept}
                onChange={(event) => {
                  void uploadFiles(event.target.files);
                  event.currentTarget.value = "";
                }}
              />
            </label>
          </div>
          <div className="prompt-editor-wrap">
            <div className="prompt-token-layer" aria-hidden="true">
              <div style={{ transform: `translateY(-${promptScrollTop}px)` }}>
                {renderPromptTokenLayer(prompt, materials)}
              </div>
            </div>
            <textarea
              id="createPrompt"
              ref={promptRef}
              value={prompt}
              onChange={(event) => {
                handlePromptInput(event.target.value, event.target.selectionStart);
              }}
              onInput={(event) => handlePromptInput(event.currentTarget.value, event.currentTarget.selectionStart)}
              onClick={(event) => updateMention(event.currentTarget.value, event.currentTarget.selectionStart)}
              onKeyDown={handlePromptKeyDown}
              onKeyUp={(event) => updateMention(event.currentTarget.value, event.currentTarget.selectionStart)}
              onScroll={(event) => setPromptScrollTop(event.currentTarget.scrollTop)}
              placeholder="输入营销内容提示词，键入 @ 选择已上传素材"
            />
            {mentionState && mentionSuggestions.length ? (
              <div className="mention-popover">
                {mentionSuggestions.map((material, index) => (
                  <button
                    className={clsx("mention-option", index === activeMentionIndex && "active")}
                    type="button"
                    key={`${material.label}-${material.url}`}
                    onMouseDown={(event) => {
                      event.preventDefault();
                      selectMention(material);
                    }}
                  >
                    {renderMaterialPreview(material, "tiny")}
                    <span className="chip-token">{material.label}</span>
                    <span className="chip-name">{material.name || material.url}</span>
                    <span className="mention-kind">{shortTypeName(material.type)}</span>
                  </button>
                ))}
              </div>
            ) : null}
          </div>

          {materials.length ? (
            <>
              <div className="material-board prompt-material-board" aria-label="已上传素材" ref={materialBoardRef}>
                {visibleMaterials.map((material, index) => (
                  <article
                    className={clsx("material-card", material.type, material.label && prompt.includes(material.label) ? "referenced" : "missing")}
                    key={`${material.url}-${materialPageOffset + index}`}
                  >
                    <button
                      className="material-card-main"
                      type="button"
                      title={`${material.label || ""} ${material.name || material.url}`}
                      onClick={() => insertToken(material.label)}
                    >
                      {renderMaterialPreview(material, "large")}
                      <span className="material-card-copy">
                        <span className="material-card-head">
                          <span className="chip-token">{material.label}</span>
                          <span className="material-state">{material.label && prompt.includes(material.label) ? "已引用" : "未引用"}</span>
                        </span>
                        <span className="chip-name">{material.name || material.url}</span>
                      </span>
                    </button>
                    <button
                      className="material-remove"
                      type="button"
                      title="移除素材"
                      onClick={(event) => {
                        event.stopPropagation();
                        removeMaterial(materialPageOffset + index);
                      }}
                    >
                      <X size={14} aria-hidden="true" />
                    </button>
                  </article>
                ))}
              </div>
              <Pagination page={materialPage} pageSize={MATERIAL_PAGE_SIZE} total={materials.length} label="素材分页" onPageChange={setMaterialPage} />
            </>
          ) : null}
        </div>

        <div className="inline-settings">
          {generateMode === "image" ? (
            <>
              <div className="field inline-field">
                <label htmlFor="imageSize">画幅</label>
                <select
                  id="imageSize"
                  value={imageSize.label}
                  onChange={(event) => setImageSize(imageSizePresets.find((item) => item.label === event.target.value) || imageSizePresets[0])}
                >
                  {imageSizePresets.map((preset) => <option key={preset.label}>{preset.label}</option>)}
                </select>
              </div>
              <div className="field inline-field range-field">
                <label htmlFor="imageScale">文本影响 {imageScale}</label>
                <input id="imageScale" type="range" min="1" max="100" value={imageScale} onChange={(event) => setImageScale(Number(event.target.value))} />
              </div>
              <label className="toggle-line">
                <input type="checkbox" checked={forceSingle} onChange={(event) => setForceSingle(event.target.checked)} />
                <span>单图</span>
              </label>
            </>
          ) : (
            <>
              <div className="field inline-field">
                <label htmlFor="videoRatio">比例</label>
                <select id="videoRatio" value={videoRatio} onChange={(event) => setVideoRatio(event.target.value)}>
                  {VIDEO_RATIOS.map((ratio) => (
                    <option key={ratio} value={ratio}>{ratio === "adaptive" ? "自适应" : ratio}</option>
                  ))}
                </select>
              </div>
              <div className="field inline-field">
                <label htmlFor="videoDuration">时长</label>
                <select
                  id="videoDuration"
                  value={videoDuration}
                  onChange={(event) => setVideoDuration(clampVideoDuration(event.target.value, videoDuration, { allowAuto: false }))}
                >
                  {VIDEO_DURATION_OPTIONS.map((seconds) => <option key={seconds} value={seconds}>{seconds} 秒</option>)}
                </select>
              </div>
              <div className="field inline-field">
                <label htmlFor="videoResolution">清晰度</label>
                <select id="videoResolution" value={videoResolution} onChange={(event) => setVideoResolution(event.target.value)}>
                  {VIDEO_RESOLUTIONS.map((resolution) => <option key={resolution} value={resolution}>{resolution}</option>)}
                </select>
              </div>
            </>
          )}
        </div>
      </section>
      )}
    </div>
  );
}

function renderMaterialPreview(material: PromptMaterial, size: "normal" | "tiny" | "large" = "normal") {
  return (
    <span className={clsx("material-preview", size, material.type)} aria-hidden="true">
      {material.type === "image" ? <img src={material.url} alt="" /> : null}
      {material.type === "video" ? <video src={material.url} muted playsInline preload="metadata" /> : null}
      {material.type === "audio" ? <Music size={size === "tiny" ? 13 : size === "large" ? 20 : 15} /> : null}
    </span>
  );
}

function materialTypeForAsset(asset: Asset): MaterialKind {
  const contentType = typeof asset.metadata.contentType === "string" ? asset.metadata.contentType.toLowerCase() : "";
  const url = asset.url.toLowerCase();
  if (asset.kind === "video" || contentType.startsWith("video/") || /\.(mp4|mov|webm)(\?|$)/.test(url)) return "video";
  if (contentType.startsWith("audio/") || /\.(mp3|wav|m4a|aac|flac)(\?|$)/.test(url)) return "audio";
  return "image";
}

function labelFor(type: MaterialKind, index: number): string {
  if (type === "video") return `@视频${index}`;
  if (type === "audio") return `@音频${index}`;
  return `@图片${index}`;
}

function relabel(items: PromptMaterial[]): PromptMaterial[] {
  const counts: Record<MaterialKind, number> = { image: 0, video: 0, audio: 0 };
  return items.map((item) => {
    counts[item.type] += 1;
    return { ...item, label: labelFor(item.type, counts[item.type]) };
  });
}

function findMentionAtCursor(value: string, cursor: number): MentionState | null {
  const beforeCursor = value.slice(0, cursor);
  const start = beforeCursor.lastIndexOf("@");
  if (start === -1) return null;
  const query = beforeCursor.slice(start + 1);
  if (query.length > 16 || /[\s\n\r，。；：、,.!?！？()[\]{}<>《》"'“”]/.test(query)) return null;
  if (/^(图片|图|视频|音频)\d+$/.test(query)) return null;
  return { start, query };
}

function materialMatchesMention(material: PromptMaterial, query: string): boolean {
  const normalizedQuery = normalizeMentionText(query);
  if (!normalizedQuery) return true;
  return [
    material.label,
    material.label?.replace(/^@/, ""),
    material.name,
    shortTypeName(material.type),
    material.type
  ].filter(Boolean).some((value) => normalizeMentionText(String(value)).includes(normalizedQuery));
}

function normalizeMentionText(value: string): string {
  return value.replace(/^@/, "").trim().toLowerCase();
}

function renderPromptTokenLayer(prompt: string, materials: PromptMaterial[]) {
  const materialByLabel = new Map(materials.map((material) => [material.label, material]));
  const nodes: ReactNode[] = [];
  let cursor = 0;
  for (const match of prompt.matchAll(/@(图片|图|视频|音频)(\d+)/g)) {
    const index = match.index ?? 0;
    if (index > cursor) nodes.push(prompt.slice(cursor, index));
    const token = match[1] === "图" ? `@图片${match[2]}` : match[0];
    const material = materialByLabel.get(token);
    nodes.push(
      <span className={clsx("prompt-token-card", material ? "linked" : "unbound")} key={`${token}-${index}`}>
        {token}
      </span>
    );
    cursor = index + match[0].length;
  }
  if (cursor < prompt.length) nodes.push(prompt.slice(cursor));
  return nodes.length ? nodes : prompt;
}

function shortTypeName(type: MaterialKind) {
  if (type === "video") return "视频";
  if (type === "audio") return "音频";
  return "图片";
}
