import React, { useEffect, useMemo, useState } from 'react';
import { createPortal, flushSync } from 'react-dom';

import { ipcChannels } from '../../shared/ipc/contracts';
import { buildNpcGroupForest } from '../../shared/npcs/npcGroups';
import type { NpcGroupId, ProjectNpc, ProjectNpcGroup } from '../../shared/types';
import editorStyles from '../editor/EditorApp.module.css';
import {
  filterMaterialImagePaths,
  getDroppedFileEntries,
  pickFirstMaterialImagePath,
  useFileDropZone,
} from '../editor/fileDrop';
import { useEditorI18n } from '../editor/i18n/EditorI18nContext';
import matStyles from '../editor/MaterialsModals.module.css';
import { getDndApi } from '../shared/dndApi';
import { Button, Input, Select } from '../shared/ui/controls';
import { useAssetUrl } from '../shared/useAssetImageUrl';

function normalizeName(input: string): string {
  return input.trim().toLowerCase();
}

function flattenGroupOptions(
  nodes: ReturnType<typeof buildNpcGroupForest>['roots'],
  depth = 0,
): { id: NpcGroupId; label: string }[] {
  const out: { id: NpcGroupId; label: string }[] = [];
  for (const node of nodes) {
    const prefix = depth > 0 ? '  '.repeat(depth) : '';
    out.push({ id: node.group.id, label: `${prefix}${node.group.name}` });
    out.push(...flattenGroupOptions(node.children, depth + 1));
  }
  return out;
}

type NpcEditModalProps = {
  open: boolean;
  initial: ProjectNpc | null;
  existingNames: string[];
  npcGroups: ProjectNpcGroup[];
  onClose: () => void;
  onPickImage: () => Promise<{ filePath: string; previewDataUrl: string } | null>;
  onSave: (input: {
    name: string;
    filePath?: string;
    groupId?: NpcGroupId | null;
  }) => Promise<void>;
};

export function NpcEditModal({
  open,
  initial,
  existingNames,
  npcGroups,
  onClose,
  onPickImage,
  onSave,
}: NpcEditModalProps) {
  const { t } = useEditorI18n();
  const api = getDndApi();
  const [name, setName] = useState('');
  const [filePath, setFilePath] = useState<string | null>(null);
  const [localPreviewUrl, setLocalPreviewUrl] = useState<string | null>(null);
  const [groupId, setGroupId] = useState<NpcGroupId | ''>('');
  const [saving, setSaving] = useState(false);
  const [saveProgress, setSaveProgress] = useState<{ percent: number; detail: string } | null>(null);
  const [error, setError] = useState<string | null>(null);
  const existingUrl = useAssetUrl(initial?.avatarAssetId ?? null);

  const groupOptions = useMemo(() => {
    const { roots } = buildNpcGroupForest(npcGroups, []);
    return flattenGroupOptions(roots);
  }, [npcGroups]);

  useEffect(() => {
    if (!open) return;
    setName(initial?.name ?? '');
    setFilePath(null);
    setLocalPreviewUrl(null);
    setGroupId(initial?.groupId ?? '');
    setSaving(false);
    setSaveProgress(null);
    setError(null);
  }, [initial, open]);

  useEffect(() => {
    return () => {
      if (localPreviewUrl?.startsWith('blob:')) URL.revokeObjectURL(localPreviewUrl);
    };
  }, [localPreviewUrl]);

  useEffect(() => {
    if (!open) return;
    return api.on(ipcChannels.project.npcUpsertProgress, (evt) => {
      setSaveProgress({
        percent: Math.max(0, Math.min(100, Math.round(evt.percent))),
        detail: evt.detail?.trim() || t('npcs.savingWait'),
      });
    });
  }, [api, open, t]);

  useEffect(() => {
    if (!open) return;
    const onKey = (e: KeyboardEvent) => {
      if (e.key === 'Escape' && !saving) onClose();
    };
    window.addEventListener('keydown', onKey);
    return () => window.removeEventListener('keydown', onKey);
  }, [onClose, open, saving]);

  const setPreviewFromPathAndUrl = (path: string, previewUrl: string) => {
    setFilePath(path);
    setLocalPreviewUrl((prev) => {
      if (prev?.startsWith('blob:')) URL.revokeObjectURL(prev);
      return previewUrl;
    });
  };

  const drop = useFileDropZone({
    onDropPaths: (paths) => {
      const picked = pickFirstMaterialImagePath(paths);
      if (!picked) return;
      setPreviewFromPathAndUrl(picked, '');
    },
    filterPaths: filterMaterialImagePaths,
  });

  const trimmed = name.trim();
  const nameOk = trimmed.length >= 1;
  const nameDup =
    nameOk &&
    existingNames.some(
      (n) =>
        normalizeName(n) === normalizeName(trimmed) &&
        normalizeName(n) !== normalizeName(initial?.name ?? ''),
    );
  const hasImage = Boolean(filePath) || Boolean(initial?.avatarAssetId);
  const canSave = nameOk && !nameDup && hasImage && !saving;
  const previewSrc = localPreviewUrl ?? existingUrl;
  const progressPercent = saveProgress?.percent ?? (saving ? 0 : 0);
  const progressDetail = saveProgress?.detail ?? t('npcs.savingWait');

  if (!open) return null;

  return createPortal(
    <>
      <button
        type="button"
        aria-label={t('common.close')}
        onClick={() => {
          if (!saving) onClose();
        }}
        className={editorStyles.modalBackdrop}
      />
      <div role="dialog" aria-modal="true" className={editorStyles.modalDialog}>
        <div className={editorStyles.modalHeader}>
          <div className={editorStyles.modalTitle}>{initial ? t('npcs.editTitle') : t('npcs.addTitle')}</div>
          <button
            type="button"
            aria-label={t('common.close')}
            onClick={() => {
              if (!saving) onClose();
            }}
            className={editorStyles.modalClose}
            disabled={saving}
          >
            ×
          </button>
        </div>

        <div className={editorStyles.fieldGrid}>
          <div className={editorStyles.fieldLabel}>{t('npcs.name')}</div>
          <Input value={name} onChange={setName} placeholder={t('npcs.namePlaceholder')} />
          {!nameOk ? <div className={editorStyles.fieldError}>{t('npcs.nameRequired')}</div> : null}
          {nameDup ? <div className={editorStyles.fieldError}>{t('npcs.nameDup')}</div> : null}
        </div>

        {!initial && groupOptions.length > 0 ? (
          <div className={editorStyles.fieldGrid}>
            <div className={editorStyles.fieldLabel}>{t('npcs.group')}</div>
            <Select
              value={groupId}
              ariaLabel={t('npcs.group')}
              onChange={(next) => setGroupId(next as NpcGroupId | '')}
              options={[
                { value: '', label: t('npcs.ungrouped') },
                ...groupOptions.map((g) => ({ value: g.id, label: g.label })),
              ]}
            />
          </div>
        ) : null}

        <div className={editorStyles.fieldGrid}>
          <div className={editorStyles.fieldLabel}>{t('npcs.avatar')}</div>
          <div
            className={[matStyles.imageDrop, drop.dragOver ? matStyles.imageDropOver : ''].join(' ')}
            onDragEnter={drop.onDragEnter}
            onDragLeave={drop.onDragLeave}
            onDragOver={drop.onDragOver}
            onDrop={(e) => {
              drop.onDrop(e);
              const entries = getDroppedFileEntries(e);
              const files = e.dataTransfer.files;
              for (let i = 0; i < entries.length; i += 1) {
                const entry = entries[i];
                if (!entry || !pickFirstMaterialImagePath([entry.path])) continue;
                const file = files[i];
                if (file) {
                  setPreviewFromPathAndUrl(entry.path, URL.createObjectURL(file));
                  return;
                }
                setPreviewFromPathAndUrl(entry.path, '');
                return;
              }
            }}
          >
            {drop.dragOver ? <div className={editorStyles.dropHintOverlay}>{t('npcs.dropHint')}</div> : null}
            {previewSrc ? (
              <img className={matStyles.previewThumb} src={previewSrc} alt="" />
            ) : (
              <div className={matStyles.imageDropEmpty}>
                <div className={editorStyles.muted}>{t('npcs.avatarEmpty')}</div>
                <Button
                  disabled={saving}
                  onClick={() => {
                    void (async () => {
                      const picked = await onPickImage();
                      if (!picked) return;
                      setPreviewFromPathAndUrl(picked.filePath, picked.previewDataUrl);
                    })();
                  }}
                >
                  {t('npcs.chooseAvatar')}
                </Button>
              </div>
            )}
            {previewSrc ? (
              <Button
                disabled={saving}
                onClick={() => {
                  void (async () => {
                    const picked = await onPickImage();
                    if (!picked) return;
                    setPreviewFromPathAndUrl(picked.filePath, picked.previewDataUrl);
                  })();
                }}
              >
                {t('npcs.chooseAvatar')}
              </Button>
            ) : null}
          </div>
          {!hasImage ? <div className={editorStyles.fieldError}>{t('npcs.avatarRequired')}</div> : null}
        </div>

        {error ? <div className={editorStyles.fieldError}>{error}</div> : null}

        <div className={editorStyles.modalFooter}>
          <Button onClick={onClose} disabled={saving}>
            {t('common.cancel')}
          </Button>
          <Button
            variant="primary"
            disabled={!canSave}
            onClick={() => {
              if (!canSave) return;
              void (async () => {
                flushSync(() => {
                  setSaving(true);
                  setSaveProgress({ percent: 0, detail: t('npcs.savingWait') });
                  setError(null);
                });
                try {
                  await onSave({
                    name: trimmed,
                    ...(filePath ? { filePath } : {}),
                    ...(!initial ? { groupId: groupId || null } : {}),
                  });
                  onClose();
                } catch (e) {
                  setError(e instanceof Error ? e.message : String(e));
                } finally {
                  setSaving(false);
                  setSaveProgress(null);
                }
              })();
            }}
          >
            {saving ? t('common.saving') : t('common.save')}
          </Button>
        </div>
      </div>
      {saving ? (
        <div
          className={editorStyles.progressOverlay}
          role="dialog"
          aria-label={t('npcs.savingProgress')}
          aria-busy
        >
          <div className={editorStyles.progressModal}>
            <div className={editorStyles.progressTitle}>{t('npcs.savingTitle')}</div>
            <div className={editorStyles.previewSpinner} aria-hidden />
            <div className={editorStyles.progressBar}>
              <div
                className={editorStyles.progressFill}
                style={{ width: `${String(Math.max(0, Math.min(100, progressPercent)))}%` }}
              />
            </div>
            <div className={editorStyles.progressMeta}>
              <div>{progressDetail}</div>
              <div>{progressPercent}%</div>
            </div>
          </div>
        </div>
      ) : null}
    </>,
    document.body,
  );
}
