import { useEffect, useRef, useState } from "react";

import { navigateTo } from "@/app/browser-location";
import { routePaths } from "@/app/route-paths";
import { getCharacter, updateCharacter } from "@/features/characters/api/character-api";
import type { UpdateCharacterParams } from "@/features/characters/api/character-api";
import { CharacterOptionalFields } from "@/features/characters/components/CharacterOptionalFields";
import { characterOptionalFieldsFromDetail, toUpdateCharacterOptionalRequest } from "@/features/characters/components/character-optional-field-serialization";
import { OriginalWorkSearchField } from "@/features/characters/components/OriginalWorkSearchField";
import type { OriginalWorkSelection } from "@/features/characters/components/OriginalWorkSearchField";
import type { CharacterDetail } from "@/features/characters/model/types";
import { validateCharacterImageFile } from "@/features/characters/validation/character-image-policy";
import { ApiError } from "@/shared/api/api-error";
import type { ApiClient } from "@/shared/api/client";
import type { CropRenderRequest } from "@/shared/lib/crop-image";
import { createImageCropSource } from "@/shared/lib/create-image-crop-source";
import { focusFirstInvalidControl } from "@/shared/lib/focus-first-invalid-control";
import { FileField } from "@/shared/ui/file-field";
import { ImageCropDialog } from "@/shared/ui/image-crop-dialog";
import type { CropSourceImage } from "@/shared/ui/image-crop-dialog";
import { PageState } from "@/shared/ui/page-state";
import { UnsavedChangesGuard } from "@/shared/ui/unsaved-changes-guard";

type FieldErrors = {
  readonly description?: string;
  readonly form?: string;
  readonly image?: string;
  readonly name?: string;
  readonly systemPrompt?: string;
};

type EditState =
  | { readonly requestKey: string; readonly status: "loading" }
  | { readonly character: CharacterDetail; readonly requestKey: string; readonly status: "content" }
  | { readonly message: string; readonly requestKey: string; readonly status: "error" };

const profileCropPolicy = { aspect: 1, maxWidth: 800, noUpscale: true } as const;
const imagePreparationErrorMessage = "이미지 미리보기 준비에 실패했습니다.";

const errorIds = {
  description: "character-edit-description-error",
  name: "character-edit-name-error",
  systemPrompt: "character-edit-system-prompt-error",
};

const mobileEditGuidance = (
  <section className="rounded-lg border border-border bg-card p-4 md:hidden" aria-labelledby="character-edit-mobile-guidance-title">
    <h2 className="text-xl font-semibold" id="character-edit-mobile-guidance-title">데스크톱에서 수정해 주세요.</h2>
    <p className="mt-2 text-sm text-muted-foreground">AI 캐릭터 수정과 파일 업로드 관리는 태블릿 이상 화면에서 이용할 수 있습니다.</p>
  </section>
);

function validateImage(image: File | null): string | undefined {
  if (image === null) {
    return undefined;
  }
  const result = validateCharacterImageFile(image);
  if (result.ok) {
    return undefined;
  }
  if (result.reason === "size") {
    return "프로필 이미지는 10MB 이하만 업로드할 수 있습니다.";
  }

  return "JPEG 또는 PNG 파일만 업로드하세요.";
}

function hasRequestFields(request: UpdateCharacterParams["request"]): boolean {
  return Object.keys(request).length > 0;
}

function CharacterEditForm({ apiClient, character, createCropSource, renderCrop }: { readonly apiClient: ApiClient; readonly character: CharacterDetail; readonly createCropSource: (file: File) => Promise<CropSourceImage>; readonly renderCrop?: (request: CropRenderRequest) => Promise<File> }) {
  const formRef = useRef<HTMLFormElement>(null);
  const [description, setDescription] = useState(character.description);
  const [image, setImage] = useState<File | null>(null);
  const [cropSource, setCropSource] = useState<CropSourceImage | null>(null);
  const [name, setName] = useState(character.name);
  const [optionalFields, setOptionalFields] = useState(() => characterOptionalFieldsFromDetail(character));
  const [originalWork, setOriginalWork] = useState<OriginalWorkSelection | null>(character.originalWork);
  const [systemPrompt, setSystemPrompt] = useState(character.systemPrompt);
  const [errors, setErrors] = useState<FieldErrors>({});
  const [isImagePreparing, setIsImagePreparing] = useState(false);
  const [isSubmitting, setIsSubmitting] = useState(false);
  const imageSelectionId = useRef(0);
  const detailPath = routePaths.aiCharacterDetail(String(character.id));
  const initialOptionalFields = characterOptionalFieldsFromDetail(character);
  const initialDescription = character.description.trim();
  const initialName = character.name.trim();
  const initialSystemPrompt = character.systemPrompt.trim();
  const request: UpdateCharacterParams["request"] = {
    ...toUpdateCharacterOptionalRequest(optionalFields, initialOptionalFields),
    ...(description.trim() === initialDescription ? {} : { description: description.trim() }),
    ...(name.trim() === initialName ? {} : { name: name.trim() }),
    ...(originalWork !== null && originalWork.id !== character.originalWork?.id ? { originalWorkId: originalWork.id } : {}),
    ...(systemPrompt.trim() === initialSystemPrompt ? {} : { systemPrompt: systemPrompt.trim() }),
  };
  const hasChanges = image !== null || hasRequestFields(request);
  const dirty = hasChanges || isImagePreparing || cropSource !== null;
  const imageSubmitBlocked = isImagePreparing || cropSource !== null;

  useEffect(() => () => cropSource?.release?.(), [cropSource]);

  async function selectImage(file: File | null) {
    imageSelectionId.current += 1;
    const currentSelectionId = imageSelectionId.current;
    if (file === null) {
      setImage(null);
      setCropSource(null);
      setIsImagePreparing(false);
      setErrors((current) => ({ ...current, image: undefined }));
      return;
    }

    const imageError = validateImage(file);
    setImage(null);
    if (imageError !== undefined) {
      setCropSource(null);
      setIsImagePreparing(false);
      setErrors((current) => ({ ...current, image: imageError }));
      return;
    }

    setIsImagePreparing(true);
    try {
      const nextCropSource = await createCropSource(file);
      if (currentSelectionId !== imageSelectionId.current) {
        nextCropSource.release?.();
        return;
      }
      setCropSource(nextCropSource);
      setErrors((current) => ({ ...current, image: undefined }));
    } catch (error: unknown) {
      if (currentSelectionId !== imageSelectionId.current) {
        return;
      }
      if (error instanceof Error) {
        setCropSource(null);
        setErrors((current) => ({ ...current, image: imagePreparationErrorMessage }));
        return;
      }
      throw error;
    } finally {
      if (currentSelectionId === imageSelectionId.current) {
        setIsImagePreparing(false);
      }
    }
  }

  function validate(): FieldErrors {
    return {
      description: description.trim().length === 0 ? "설명을 입력하세요." : undefined,
      image: imageSubmitBlocked ? "이미지 처리가 끝난 뒤 저장하세요." : errors.image ?? validateImage(image),
      name: name.trim().length === 0 ? "이름을 입력하세요." : undefined,
      systemPrompt: systemPrompt.trim().length === 0 ? "시스템 프롬프트를 입력하세요." : undefined,
    };
  }

  async function submit(event: { readonly preventDefault: () => void }) {
    event.preventDefault();
    if (isSubmitting) {
      return;
    }
    if (!hasChanges) {
      return;
    }

    const nextErrors = validate();
    setErrors(nextErrors);
    if (nextErrors.description !== undefined || nextErrors.image !== undefined || nextErrors.name !== undefined || nextErrors.systemPrompt !== undefined) {
      queueMicrotask(() => focusFirstInvalidControl(formRef.current));
      return;
    }

    setIsSubmitting(true);
    try {
      await updateCharacter(apiClient, String(character.id), {
        image: image ?? undefined,
        request,
      });
      navigateTo(routePaths.aiCharacterDetail(String(character.id)), { successNotification: "AI 캐릭터를 저장했습니다." });
    } catch (error: unknown) {
      setErrors({ form: error instanceof ApiError ? error.message : "AI 캐릭터를 저장하지 못했습니다." });
    } finally {
      setIsSubmitting(false);
    }
  }

  return (
    <UnsavedChangesGuard dirty={dirty} impactDescription="저장하지 않은 변경사항이 사라집니다." title="수정을 취소하시겠습니까?">
      {(requestRouteLeave) => (
        <section className="flex flex-col gap-4" aria-labelledby="character-edit-title">
          <div className="flex flex-col gap-2">
            <p className="text-xs font-semibold text-info">AI CHARACTER ADMIN</p>
            <h1 className="text-2xl font-bold leading-tight" id="character-edit-title">AI 캐릭터 수정</h1>
            <p className="text-sm text-muted-foreground">프로필 텍스트와 이미지를 수정합니다. 지역은 생성 후 변경할 수 없습니다.</p>
          </div>
          <form className="flex flex-col gap-4 rounded-lg border border-border bg-card p-4" aria-label="AI 캐릭터 수정 form" onSubmit={(event) => void submit(event)} ref={formRef}>
	        <label className="flex flex-col gap-2 text-sm font-semibold">
	          이름
          <input aria-describedby={errors.name === undefined ? undefined : errorIds.name} aria-invalid={errors.name === undefined ? undefined : true} className="rounded-md border border-input bg-card px-3 py-2 text-base font-normal text-foreground" name="name" onChange={(event) => setName(event.currentTarget.value)} value={name} />
	        </label>
	        {errors.name === undefined ? null : <p className="text-sm font-semibold text-destructive" id={errorIds.name} role="alert">{errors.name}</p>}
	        <label className="flex flex-col gap-2 text-sm font-semibold">
	          시스템 프롬프트
          <textarea aria-describedby={errors.systemPrompt === undefined ? undefined : errorIds.systemPrompt} aria-invalid={errors.systemPrompt === undefined ? undefined : true} className="min-h-32 rounded-md border border-input bg-card px-3 py-2 text-base font-normal text-foreground" name="systemPrompt" onChange={(event) => setSystemPrompt(event.currentTarget.value)} value={systemPrompt} />
	        </label>
	        {errors.systemPrompt === undefined ? null : <p className="text-sm font-semibold text-destructive" id={errorIds.systemPrompt} role="alert">{errors.systemPrompt}</p>}
	        <label className="flex flex-col gap-2 text-sm font-semibold">
	          설명
          <textarea aria-describedby={errors.description === undefined ? undefined : errorIds.description} aria-invalid={errors.description === undefined ? undefined : true} className="min-h-24 rounded-md border border-input bg-card px-3 py-2 text-base font-normal text-foreground" name="description" onChange={(event) => setDescription(event.currentTarget.value)} value={description} />
	        </label>
	        {errors.description === undefined ? null : <p className="text-sm font-semibold text-destructive" id={errorIds.description} role="alert">{errors.description}</p>}
	        <CharacterOptionalFields onChange={setOptionalFields} regionEditable={false} value={optionalFields} />
	        <label className="flex flex-col gap-2 text-sm font-semibold">
	          지역
          <input className="rounded-md border border-input bg-muted px-3 py-2 text-base font-normal text-muted-foreground" disabled name="region" value={character.region} />
        </label>
	        <OriginalWorkSearchField apiClient={apiClient} onChange={setOriginalWork} value={originalWork} />
	        <FileField accept="image/jpeg,image/png" acceptDescription="선택하지 않으면 기존 프로필 이미지를 유지합니다. JPEG 또는 PNG, 10MB 이하." error={errors.image} label="프로필 이미지" onChange={(file) => void selectImage(file)} value={image} />
	        {errors.form === undefined ? null : <p className="text-sm font-semibold text-destructive" role="alert">{errors.form}</p>}
            <div className="flex justify-end gap-2">
              <button className="rounded-md border border-input bg-card px-4 py-2 font-semibold hover:bg-accent" onClick={(event) => requestRouteLeave(event.currentTarget, () => navigateTo(detailPath))} type="button">
                상세로 돌아가기
              </button>
              <button className="rounded-md border border-input bg-primary px-4 py-2 font-semibold text-primary-foreground hover:bg-[var(--button-bg-hover)] active:bg-[var(--button-bg-active)] disabled:opacity-60" disabled={isSubmitting || imageSubmitBlocked || !hasChanges} type="submit">
                {isSubmitting ? "저장 중" : "저장"}
              </button>
            </div>
          </form>
          {cropSource === null ? null : (
            <ImageCropDialog
              image={cropSource}
              onApply={(file) => {
                setImage(file);
                setCropSource(null);
                setErrors((current) => ({ ...current, image: undefined }));
              }}
              onCancel={() => {
                setImage(null);
                setCropSource(null);
                setErrors((current) => ({ ...current, image: undefined }));
              }}
              open
              policy={profileCropPolicy}
              renderCrop={renderCrop}
            />
          )}
        </section>
      )}
    </UnsavedChangesGuard>
  );
}

export function CharacterEditPage({ apiClient, characterId, createCropSource = createImageCropSource, renderCrop }: { readonly apiClient: ApiClient; readonly characterId: string; readonly createCropSource?: (file: File) => Promise<CropSourceImage>; readonly renderCrop?: (request: CropRenderRequest) => Promise<File> }) {
  const [state, setState] = useState<EditState>({ requestKey: "", status: "loading" });
  const [retryKey, setRetryKey] = useState(0);
  const requestKey = `${characterId}:${retryKey}`;

  useEffect(() => {
    let isCurrent = true;
    void getCharacter(apiClient, characterId)
      .then((character) => {
        if (isCurrent) {
          setState({ character, requestKey, status: "content" });
        }
      })
      .catch((error: unknown) => {
        if (isCurrent) {
          setState({ message: error instanceof ApiError ? error.message : "AI 캐릭터 상세를 불러오지 못했습니다.", requestKey, status: "error" });
        }
      });

    return () => {
      isCurrent = false;
    };
  }, [apiClient, characterId, requestKey]);

  const visibleState: EditState = state.requestKey === requestKey ? state : { requestKey, status: "loading" };

  if (visibleState.status === "loading") {
    return <>{mobileEditGuidance}<div className="hidden md:block"><PageState state="loading" title="AI 캐릭터 수정 정보를 불러오는 중" /></div></>;
  }
  if (visibleState.status === "error") {
    return <>{mobileEditGuidance}<div className="hidden md:block"><PageState description={visibleState.message} onRetry={() => setRetryKey((key) => key + 1)} state="error" title="AI 캐릭터 수정 정보 조회 실패" /></div></>;
  }
  if (!visibleState.character.isActive) {
    return <>{mobileEditGuidance}<div className="hidden md:block"><PageState description="상세 화면에서 조회만 가능합니다." state="empty" title="비활성화된 AI 캐릭터는 수정할 수 없습니다." /></div></>;
  }

  return <>{mobileEditGuidance}<div className="hidden md:block"><CharacterEditForm apiClient={apiClient} character={visibleState.character} createCropSource={createCropSource} renderCrop={renderCrop} /></div></>;
}
