import React, { useCallback, useEffect, useMemo, useState } from 'react';

import { ipcChannels, type SessionState } from '../../shared/ipc/contracts';
import { buildNpcGroupForest, type NpcGroupTreeNode } from '../../shared/npcs/npcGroups';
import type { NpcGroupId, NpcId, ProjectNpc } from '../../shared/types';
import { useEditorI18n } from '../editor/i18n/EditorI18nContext';
import { sanitizeSceneDescriptionHtml } from '../editor/sceneDescriptionHtml';
import { getDndApi } from '../shared/dndApi';
import { useNpcsOverlayState } from '../shared/npcs/useNpcsOverlayState';
import { Button, Input } from '../shared/ui/controls';
import { useAssetUrl } from '../shared/useAssetImageUrl';

import styles from './NpcsApp.module.css';

/** Убрать пустые ветки групп (удобно при поиске). */
function pruneEmptyGroupNodes(nodes: NpcGroupTreeNode[]): NpcGroupTreeNode[] {
  const out: NpcGroupTreeNode[] = [];
  for (const node of nodes) {
    const children = pruneEmptyGroupNodes(node.children);
    if (node.npcs.length === 0 && children.length === 0) continue;
    out.push({ ...node, children });
  }
  return out;
}

function RuntimeNpcTile({
  npc,
  selected,
  accentColor,
  onActivate,
}: {
  npc: ProjectNpc;
  selected: boolean;
  accentColor?: string | null;
  onActivate: () => void;
}) {
  const url = useAssetUrl(npc.avatarAssetId);
  return (
    <button
      type="button"
      className={[styles.tile, selected ? styles.tileSelected : ''].filter(Boolean).join(' ')}
      style={accentColor ? { borderLeftColor: accentColor, borderLeftWidth: 3 } : undefined}
      onClick={onActivate}
    >
      <div className={styles.tileAvatar}>
        {url ? <img className={styles.tileAvatarImg} src={url} alt="" draggable={false} /> : null}
      </div>
      <div className={styles.tileName}>{npc.name}</div>
    </button>
  );
}

function RuntimeGroupSection({
  node,
  depth,
  isExpanded,
  onToggleExpanded,
  selectedIds,
  onActivate,
}: {
  node: NpcGroupTreeNode;
  depth: number;
  isExpanded: (id: NpcGroupId) => boolean;
  onToggleExpanded: (id: NpcGroupId) => void;
  selectedIds: ReadonlySet<NpcId>;
  onActivate: (id: NpcId) => void;
}) {
  const g = node.group;
  const expanded = isExpanded(g.id);

  return (
    <div className={styles.groupBlock} style={{ paddingLeft: depth > 0 ? 12 : 0 }}>
      <div className={styles.groupHeader}>
        <button
          type="button"
          className={styles.groupToggle}
          onClick={() => onToggleExpanded(g.id)}
          aria-expanded={expanded}
        >
          {expanded ? '▾' : '▸'}
        </button>
        <span className={styles.groupColorDot} style={{ background: g.color }} aria-hidden />
        <span className={styles.groupTitle}>{g.name}</span>
      </div>
      {expanded ? (
        <div className={styles.groupBody}>
          {node.npcs.map((n) => (
            <RuntimeNpcTile
              key={n.id}
              npc={n}
              selected={selectedIds.has(n.id)}
              accentColor={g.color}
              onActivate={() => onActivate(n.id)}
            />
          ))}
          {node.children.map((child) => (
            <RuntimeGroupSection
              key={child.group.id}
              node={child}
              depth={depth + 1}
              isExpanded={isExpanded}
              onToggleExpanded={onToggleExpanded}
              selectedIds={selectedIds}
              onActivate={onActivate}
            />
          ))}
        </div>
      ) : null}
    </div>
  );
}

export function NpcsApp() {
  const { t } = useEditorI18n();
  const api = getDndApi();
  const [session, setSession] = useState<SessionState | null>(null);
  const [overlay, overlayApi] = useNpcsOverlayState();
  const [query, setQuery] = useState('');
  const [collapsedGroups, setCollapsedGroups] = useState<Set<NpcGroupId>>(() => new Set());

  useEffect(() => {
    void api.invoke(ipcChannels.project.get, {}).then(({ project }) => {
      setSession({ project, currentSceneId: project?.currentSceneId ?? null });
    });
    return api.on(ipcChannels.session.stateChanged, ({ state }) => {
      setSession(state);
    });
  }, [api]);

  const activeIds = overlay?.activeNpcIds ?? [];
  const hasActive = activeIds.length > 0;

  useEffect(() => {
    const onKey = (e: KeyboardEvent) => {
      if (e.key === 'Escape') {
        if (hasActive) {
          void overlayApi.dispatch({ kind: 'hide' });
          return;
        }
        window.close();
      }
    };
    window.addEventListener('keydown', onKey);
    return () => window.removeEventListener('keydown', onKey);
  }, [hasActive, overlayApi]);

  const npcs = useMemo(() => session?.project?.npcs ?? [], [session?.project?.npcs]);
  const npcGroups = useMemo(() => session?.project?.npcGroups ?? [], [session?.project?.npcGroups]);
  const relations = useMemo(() => session?.project?.npcRelations ?? [], [session?.project?.npcRelations]);
  const selectedIds = useMemo(() => new Set(activeIds), [activeIds]);
  const selectedNpcs = useMemo(
    () => activeIds.map((id) => npcs.find((n) => n.id === id)).filter((n): n is ProjectNpc => Boolean(n)),
    [activeIds, npcs],
  );

  const filteredNpcs = useMemo(() => {
    const q = query.trim().toLowerCase();
    if (!q) return npcs;
    return npcs.filter((n) => n.name.toLowerCase().includes(q));
  }, [npcs, query]);

  const searching = query.trim().length > 0;

  const { roots, ungrouped } = useMemo(() => {
    const forest = buildNpcGroupForest(npcGroups, filteredNpcs);
    if (!searching) return forest;
    return {
      roots: pruneEmptyGroupNodes(forest.roots),
      ungrouped: forest.ungrouped,
    };
  }, [filteredNpcs, npcGroups, searching]);

  const isExpanded = useCallback(
    (id: NpcGroupId) => {
      if (searching) return true;
      return !collapsedGroups.has(id);
    },
    [collapsedGroups, searching],
  );

  const toggleExpanded = useCallback(
    (id: NpcGroupId) => {
      if (searching) return;
      setCollapsedGroups((prev) => {
        const next = new Set(prev);
        if (next.has(id)) next.delete(id);
        else next.add(id);
        return next;
      });
    },
    [searching],
  );

  const relationsByNpcId = useMemo(() => {
    const map = new Map<NpcId, { id: string; text: string }[]>();
    for (const npc of selectedNpcs) {
      const list = relations
        .filter((r) => r.sourceNpcId === npc.id)
        .map((r) => {
          const other = npcs.find((n) => n.id === r.targetNpcId);
          return { id: r.id, text: `${r.label} ${other?.name ?? '—'}` };
        });
      map.set(npc.id, list);
    }
    return map;
  }, [npcs, relations, selectedNpcs]);

  const onSelectTile = useCallback(
    (id: NpcId) => {
      void overlayApi.dispatch({ kind: 'toggle', npcId: id });
    },
    [overlayApi],
  );

  return (
    <div className={styles.page}>
      {hasActive ? (
        <div className={styles.toolbar}>
          <div className={styles.toolbarRow}>
            <Button
              title={t('npcs.closeOverlay')}
              ariaLabel={t('npcs.closeOverlay')}
              tooltipPlacement="bottom"
              onClick={() => {
                void overlayApi.dispatch({ kind: 'hide' });
              }}
            >
              {t('npcs.closeOverlay')}
            </Button>
          </div>
        </div>
      ) : null}

      <div className={styles.body}>
        <div className={styles.detail}>
          {selectedNpcs.length > 0 ? (
            selectedNpcs.map((npc) => {
              const safeHtml = sanitizeSceneDescriptionHtml(npc.description);
              const npcRelations = relationsByNpcId.get(npc.id) ?? [];
              return (
                <div key={npc.id} className={styles.detailCard}>
                  <div className={styles.detailName}>{npc.name}</div>
                  {safeHtml ? (
                    <div>
                      <div className={styles.detailSectionTitle}>{t('npcs.description')}</div>
                      <div className={styles.detailDesc} dangerouslySetInnerHTML={{ __html: safeHtml }} />
                    </div>
                  ) : (
                    <div className={styles.muted}>{t('npcs.descriptionEmpty')}</div>
                  )}
                  {npcRelations.length > 0 ? (
                    <div>
                      <div className={styles.detailSectionTitle}>{t('npcs.relations')}</div>
                      <div className={styles.relationsList}>
                        {npcRelations.map((r) => (
                          <div key={r.id} className={styles.relationItem}>
                            {r.text}
                          </div>
                        ))}
                      </div>
                    </div>
                  ) : null}
                </div>
              );
            })
          ) : (
            <div className={styles.detailEmpty}>
              {npcs.length === 0 ? t('npcs.windowEmpty') : t('npcs.selectToShow')}
            </div>
          )}
        </div>

        <div className={styles.listCol}>
          <Input value={query} onChange={setQuery} placeholder={t('npcs.search')} />
          <div className={styles.list}>
            {npcGroups.length === 0 ? (
              filteredNpcs.map((n) => (
                <RuntimeNpcTile
                  key={n.id}
                  npc={n}
                  selected={selectedIds.has(n.id)}
                  onActivate={() => onSelectTile(n.id)}
                />
              ))
            ) : (
              <>
                {roots.map((node) => (
                  <RuntimeGroupSection
                    key={node.group.id}
                    node={node}
                    depth={0}
                    isExpanded={isExpanded}
                    onToggleExpanded={toggleExpanded}
                    selectedIds={selectedIds}
                    onActivate={onSelectTile}
                  />
                ))}
                {ungrouped.length > 0 || !searching ? (
                  <div className={styles.ungroupedSection}>
                    <div className={styles.ungroupedHeader}>{t('npcs.ungrouped')}</div>
                    <div className={styles.groupBody}>
                      {ungrouped.map((n) => (
                        <RuntimeNpcTile
                          key={n.id}
                          npc={n}
                          selected={selectedIds.has(n.id)}
                          onActivate={() => onSelectTile(n.id)}
                        />
                      ))}
                    </div>
                  </div>
                ) : null}
              </>
            )}

            {npcs.length === 0 ? <div className={styles.muted}>{t('npcs.windowEmpty')}</div> : null}
            {npcs.length > 0 && filteredNpcs.length === 0 ? (
              <div className={styles.muted}>{t('npcs.searchEmpty')}</div>
            ) : null}
          </div>
        </div>
      </div>
    </div>
  );
}
