"use client";
import useSWR from "swr";
import { useMemo, useState, useEffect, useRef } from "react";

const fetcher = (url: string) => fetch(url).then((r) => r.json());

export default function AdminBoardsPage() {
  const { data: boardsResp, mutate: mutateBoards } = useSWR<{ boards: any[] }>("/api/admin/boards", fetcher);
  const { data: catsResp, mutate: mutateCats } = useSWR<{ categories: any[] }>("/api/admin/categories", fetcher);
  const rawBoards = boardsResp?.boards ?? [];
  const rawCategories = catsResp?.categories ?? [];

  const [savingId, setSavingId] = useState<string | null>(null);
  const [dirtyBoards, setDirtyBoards] = useState<Record<string, any>>({});
  const [dirtyCats, setDirtyCats] = useState<Record<string, any>>({});
  const [savingAll, setSavingAll] = useState(false);
  const [expanded, setExpanded] = useState<Record<string, boolean>>({});
  const dirtyCount = Object.keys(dirtyBoards).length + Object.keys(dirtyCats).length;
  const [catOrder, setCatOrder] = useState<string[]>([]);
  const [draggingCatIndex, setDraggingCatIndex] = useState<number | null>(null);
  const catRefs = useRef<Record<string, HTMLLIElement | null>>({});
  const boards = useMemo(() => {
    return rawBoards.map((b: any) => ({ ...b, ...(dirtyBoards[b.id] ?? {}) }));
  }, [rawBoards, dirtyBoards]);
  const categories = useMemo(() => {
    const merged = rawCategories.map((c: any) => ({ ...c, ...(dirtyCats[c.id] ?? {}) }));
    return merged.sort((a: any, b: any) => (a.sortOrder ?? 0) - (b.sortOrder ?? 0));
  }, [rawCategories, dirtyCats]);
  const orderedCats = useMemo(() => {
    if (!catOrder.length) return categories;
    const map = new Map(categories.map((c: any) => [c.id, c]));
    const ordered = catOrder.map((id) => map.get(id)).filter(Boolean) as any[];
    // 새로 생긴 카테고리(id 미포함)는 뒤에 추가
    const missing = categories.filter((c: any) => !catOrder.includes(c.id));
    return [...ordered, ...missing];
  }, [categories, catOrder]);
  const groups = useMemo(() => {
    const map: Record<string, any[]> = {};
    for (const b of boards) {
      const cid = b.categoryId ?? "uncat";
      if (!map[cid]) map[cid] = [];
      map[cid].push(b);
    }
    return orderedCats.map((c: any) => ({ ...c, items: (map[c.id] ?? []).sort((a, b) => (a.sortOrder ?? 0) - (b.sortOrder ?? 0)) }));
  }, [boards, orderedCats]);

  // 최초/데이터 변경 시 표시용 카테고리 순서를 초기화
  // 서버 sortOrder에 맞춰 초기 catOrder 설정
  // categories가 바뀔 때만 동기화
  // 사용자가 드래그로 순서를 바꾸면 catOrder가 우선됨
  useEffect(() => {
    if (draggingCatIndex !== null) return; // 드래그 중에는 catOrder를 리셋하지 않음
    const next = categories.map((c: any) => c.id);
    if (next.length && (next.length !== catOrder.length || next.some((id, i) => id !== catOrder[i]))) {
      setCatOrder(next);
    }
  }, [categories, draggingCatIndex]);
  async function save(b: any) {
    setSavingId(b.id);
    await fetch(`/api/admin/boards/${b.id}`, { method: "PATCH", headers: { "content-type": "application/json" }, body: JSON.stringify(b) });
    setSavingId(null);
    mutateBoards();
  }

  // DnD: 카테고리 순서 변경 (저장 시 반영)
  function reorderCategories(next: any[]) {
    setDirtyCats((prev) => {
      // 서버 기준(또는 이전 dirty 오버라이드) sortOrder 맵 구성
      const baseSort = new Map<string, number>();
      // rawCategories에는 서버 값이 들어있음
      // prev에 sortOrder가 있으면 우선 적용
      for (const c of rawCategories) {
        const prevOverride = prev[c.id]?.sortOrder;
        baseSort.set(c.id, prevOverride ?? (c.sortOrder ?? 0));
      }
      const updated: Record<string, any> = { ...prev };
      next.forEach((c, idx) => {
        const target = idx + 1;
        const current = baseSort.get(c.id) ?? 0;
        if (target !== current) {
          updated[c.id] = { ...(updated[c.id] ?? {}), sortOrder: target };
        } else if (updated[c.id]?.sortOrder !== undefined) {
          // 정렬값이 동일해졌다면 해당 키만 제거 (다른 수정값은 유지)
          const { sortOrder, ...rest } = updated[c.id];
          if (Object.keys(rest).length === 0) delete updated[c.id]; else updated[c.id] = rest;
        }
      });
      return updated;
    });
  }

  // DnD: 보드 순서 변경 (저장 시 반영)
  function reorderBoards(categoryId: string, nextItems: any[]) {
    setDirtyBoards((prev) => {
      // 서버 기준(또는 이전 dirty) 정렬/카테고리 맵 구성
      const base = new Map<string, { sortOrder: number; categoryId: string | null | undefined }>();
      for (const b of rawBoards) {
        const prevB = prev[b.id] ?? {};
        base.set(b.id, {
          sortOrder: prevB.sortOrder ?? (b.sortOrder ?? 0),
          categoryId: prevB.categoryId ?? b.categoryId,
        });
      }
      const updated: Record<string, any> = { ...prev };
      nextItems.forEach((b, idx) => {
        const targetSort = idx + 1;
        const targetCat = categoryId;
        const baseVal = base.get(b.id) ?? { sortOrder: b.sortOrder ?? 0, categoryId: b.categoryId };
        if (baseVal.sortOrder !== targetSort || baseVal.categoryId !== targetCat) {
          updated[b.id] = { ...(updated[b.id] ?? {}), sortOrder: targetSort, categoryId: targetCat };
        } else if (updated[b.id]) {
          const { sortOrder, categoryId: catId, ...rest } = updated[b.id];
          if (Object.keys(rest).length === 0) delete updated[b.id]; else updated[b.id] = rest;
        }
      });
      return updated;
    });
  }

  function markBoardDirty(id: string, draft: any) {
    setDirtyBoards((prev) => ({ ...prev, [id]: draft }));
  }
  function markCatDirty(id: string, draft: any) {
    setDirtyCats((prev) => ({ ...prev, [id]: draft }));
  }
  function toggleCat(id: string) {
    setExpanded((prev) => ({ ...prev, [id]: !prev[id] }));
  }
  async function saveAll() {
    const prevDirtyBoards = dirtyBoards;
    const prevDirtyCats = dirtyCats;
    try {
      setSavingAll(true);
      const boardEntries = Object.entries(dirtyBoards);
      const catEntries = Object.entries(dirtyCats);
      // 1) 서버 저장 (병렬) - 실패 시 아래 catch로 이동
      const resps = await Promise.all([
        ...boardEntries.map(([id, payload]) => fetch(`/api/admin/boards/${id}`, { method: "PATCH", headers: { "content-type": "application/json" }, body: JSON.stringify(payload) })),
        ...catEntries.map(([id, payload]) => fetch(`/api/admin/categories/${id}`, { method: "PATCH", headers: { "content-type": "application/json" }, body: JSON.stringify(payload) })),
      ]);
      const anyFail = resps.some((r) => !r.ok);
      if (anyFail) throw new Error("save_failed");
      // 2) 성공 시: 먼저 서버 데이터로 최신화 → 그 다음 dirty 초기화
      await Promise.all([
        mutateBoards(undefined, { revalidate: true }),
        mutateCats(undefined, { revalidate: true }),
      ]);
      if (typeof window !== "undefined") {
        window.dispatchEvent(new Event("categories:reload"));
      }
      setDirtyBoards({});
      setDirtyCats({});
    } catch (e) {
      // 실패 시: 변경 사항 취소하고 서버 상태로 되돌림
      setDirtyBoards({});
      setDirtyCats({});
      await Promise.all([
        mutateBoards(undefined, { revalidate: true }),
        mutateCats(undefined, { revalidate: true }),
      ]);
    } finally {
      setSavingAll(false);
    }
  }

  return (
    <div className="space-y-6">
      <h1 className="text-xl md:text-2xl font-bold text-neutral-900">게시판 관리</h1>
      <div className="sticky top-2 z-10 flex justify-end">
        <button
          onClick={saveAll}
          disabled={savingAll || dirtyCount === 0}
          className="h-9 px-4 rounded-md bg-neutral-900 text-white text-sm shadow disabled:opacity-60"
        >{savingAll ? "저장 중..." : `변경사항 저장 (${dirtyCount})`}</button>
      </div>

      {/* 대분류(헤더)와 소분류(보드 테이블)를 하나의 리스트로 통합, 대분류별 토글 */}
      <div className="rounded-xl border border-neutral-200 overflow-hidden bg-white">
        <div className="px-4 py-2 border-b border-neutral-200 text-sm font-semibold">대분류 및 소분류</div>
        <ul
          className="divide-y-2 divide-neutral-100"
          onDragOver={(e) => {
            e.preventDefault();
            e.dataTransfer.dropEffect = 'move';
            if (draggingCatIndex === null) return;
            const ids = (catOrder.length ? catOrder : categories.map((c: any) => c.id));
            // 현재 마우스 Y에 해당하는 행 인덱스 계산
            let overIdx = -1;
            for (let i = 0; i < ids.length; i++) {
              const el = catRefs.current[ids[i]];
              if (!el) continue;
              const rect = el.getBoundingClientRect();
              const mid = rect.top + rect.height / 2;
              if (e.clientY < mid) { overIdx = i; break; }
            }
            if (overIdx === -1) overIdx = ids.length - 1;
            if (overIdx === draggingCatIndex) return;
            setCatOrder((order) => {
              const base = order.length ? order : categories.map((c: any) => c.id);
              const next = [...base];
              const [moved] = next.splice(draggingCatIndex, 1);
              next.splice(overIdx, 0, moved);
              setDraggingCatIndex(overIdx);
              const nextCats = next.map((id) => categories.find((c: any) => c.id === id)).filter(Boolean) as any[];
              reorderCategories(nextCats);
              return next;
            });
          }}
          onDragEnter={(e) => { e.preventDefault(); e.dataTransfer.dropEffect = 'move'; }}
          onDrop={(e) => {
            e.preventDefault();
            const ids = (catOrder.length ? catOrder : categories.map((c: any) => c.id));
            const nextCats = ids.map((id) => categories.find((c: any) => c.id === id)).filter(Boolean) as any[];
            reorderCategories(nextCats);
            setDraggingCatIndex(null);
          }}
        >
          {groups.map((g, idx) => (
            <li
              key={g.id}
              className="select-none"
              ref={(el) => { catRefs.current[g.id] = el; }}
              
              onDrop={(e) => {
                e.preventDefault();
                setDraggingCatIndex(null);
              }}
              onDragEnd={() => { setDraggingCatIndex(null); }}
            >
              <div className="px-4 py-3 flex items-center gap-3">
                <div className="w-8 text-sm text-neutral-500 select-none">{idx + 1}</div>
                <CategoryHeaderContent g={g} onDirty={(payload) => markCatDirty(g.id, { ...payload })} onDragStart={() => {
                  setDraggingCatIndex(idx);
                }} />
                <button
                  type="button"
                  aria-label="toggle"
                  className="w-8 text-2xl leading-none text-neutral-700"
                  onClick={() => toggleCat(g.id)}
                >{expanded[g.id] ? '▾' : '▸'}</button>
              </div>

              {expanded[g.id] && (
                <div className="overflow-x-auto border-t border-neutral-100 ml-8">
                  <table className="min-w-full text-sm">
                    <thead className="text-xs text-neutral-500 border-b border-neutral-200">
                      <tr>
                        <th className="px-2 py-2 w-8"></th>
                        <th className="px-2 py-2 w-8 text-center">#</th>
                        <th className="px-3 py-2 text-left">이름</th>
                        <th className="px-3 py-2 text-left">slug</th>
                        <th className="px-3 py-2">읽기</th>
                        <th className="px-3 py-2">쓰기</th>
                        <th className="px-3 py-2">익명</th>
                        <th className="px-3 py-2">비밀댓</th>
                        <th className="px-3 py-2">승인</th>
                        <th className="px-3 py-2">유형</th>
                        <th className="px-3 py-2">성인</th>
                        <th className="px-3 py-2"></th>
                      </tr>
                    </thead>
                    <tbody className="divide-y divide-neutral-100">
                      {g.items.map((b, i) => (
                        <DraggableRow
                          key={b.id}
                          index={i}
                          onMove={(from, to) => {
                            const list = [...g.items];
                            const [mv] = list.splice(from, 1);
                            list.splice(to, 0, mv);
                            reorderBoards(g.id, list);
                          }}
                        >
                          <BoardRowCells b={b} onDirty={(id, draft) => markBoardDirty(id, draft)} />
                        </DraggableRow>
                      ))}
                    </tbody>
                  </table>
                </div>
              )}
            </li>
          ))}
        </ul>
      </div>
    </div>
  );
}

function BoardRowCells({ b, onDirty }: { b: any; onDirty: (id: string, draft: any) => void }) {
  const [edit, setEdit] = useState(b);
  return (
    <>
      <td className="px-3 py-2"><input className="h-9 w-full rounded-md border border-neutral-300 px-2 text-sm" value={edit.name} onChange={(e) => { const v = { ...edit, name: e.target.value }; setEdit(v); onDirty(b.id, v); }} /></td>
      <td className="px-3 py-2"><input className="h-9 w-full rounded-md border border-neutral-300 px-2 text-sm" value={edit.slug} onChange={(e) => { const v = { ...edit, slug: e.target.value }; setEdit(v); onDirty(b.id, v); }} /></td>
      <td className="px-3 py-2 text-center">
        <select className="h-9 rounded-md border border-neutral-300 px-2 text-sm" value={edit.readLevel} onChange={(e) => { const v = { ...edit, readLevel: e.target.value }; setEdit(v); onDirty(b.id, v); }}>
          <option value="public">public</option>
          <option value="member">member</option>
          <option value="moderator">moderator</option>
          <option value="admin">admin</option>
        </select>
      </td>
      <td className="px-3 py-2 text-center">
        <select className="h-9 rounded-md border border-neutral-300 px-2 text-sm" value={edit.writeLevel} onChange={(e) => { const v = { ...edit, writeLevel: e.target.value }; setEdit(v); onDirty(b.id, v); }}>
          <option value="public">public</option>
          <option value="member">member</option>
          <option value="moderator">moderator</option>
          <option value="admin">admin</option>
        </select>
      </td>
      <td className="px-3 py-2 text-center"><input type="checkbox" checked={edit.allowAnonymousPost} onChange={(e) => { const v = { ...edit, allowAnonymousPost: e.target.checked }; setEdit(v); onDirty(b.id, v); }} /></td>
      <td className="px-3 py-2 text-center"><input type="checkbox" checked={edit.allowSecretComment} onChange={(e) => { const v = { ...edit, allowSecretComment: e.target.checked }; setEdit(v); onDirty(b.id, v); }} /></td>
      <td className="px-3 py-2 text-center"><input type="checkbox" checked={edit.requiresApproval} onChange={(e) => { const v = { ...edit, requiresApproval: e.target.checked }; setEdit(v); onDirty(b.id, v); }} /></td>
      <td className="px-3 py-2 text-center">
        <select className="h-9 rounded-md border border-neutral-300 px-2 text-sm" value={edit.type} onChange={(e) => { const v = { ...edit, type: e.target.value }; setEdit(v); onDirty(b.id, v); }}>
          <option value="general">general</option>
          <option value="special">special</option>
        </select>
      </td>
      <td className="px-3 py-2 text-center"><input type="checkbox" checked={!!edit.isAdultOnly} onChange={(e) => { const v = { ...edit, isAdultOnly: e.target.checked }; setEdit(v); onDirty(b.id, v); }} /></td>
      
    </>
  );
}

function DraggableRow({ index, onMove, children }: { index: number; onMove: (from: number, to: number) => void; children: React.ReactNode }) {
  return (
    <tr
      onDragOver={(e) => {
        // 수직만 허용: 기본 동작만 유지하고 수평 제스처는 무시
        e.preventDefault();
        e.dataTransfer.dropEffect = 'move';
        const current = e.currentTarget as HTMLTableRowElement;
        const state = (window as any).__adminDnd || {};
        const ph: HTMLElement | undefined = state.placeholder;
        if (!ph || !current.parentElement) return;
        const r = current.getBoundingClientRect();
        const before = e.clientY < r.top + r.height / 2;
        // 목표만 저장하고 DOM 조작은 프레임당 1회 수행
        state.target = current;
        state.before = before;
        if (!state.rAF) {
          state.rAF = requestAnimationFrame(() => {
            const st = (window as any).__adminDnd || {};
            if (!st.placeholder || !st.target || !st.target.parentElement) { st.rAF = 0; return; }
            const parent = st.target.parentElement as HTMLElement;
            const desiredNode = st.before ? st.target : (st.target.nextSibling as any);
            if (desiredNode !== st.placeholder) {
              parent.insertBefore(st.placeholder, desiredNode || null);
              if (st.dragging) {
                const pr = st.placeholder.getBoundingClientRect();
                (st.dragging as HTMLElement).style.top = `${pr.top}px`;
              }
            }
            st.rAF = 0;
          });
        }
      }}
      onDrop={(e) => {
        e.preventDefault();
        const from = Number(e.dataTransfer.getData("text/plain"));
        const state = (window as any).__adminDnd || {};
        const ph: HTMLElement | undefined = state.placeholder;
        let to = index;
        if (ph && ph.parentElement) {
          to = Array.from(ph.parentElement.children).indexOf(ph);
          ph.remove();
        }
        if (!Number.isNaN(from) && from !== to) onMove(from, to);
      }}
      className="align-middle select-none"
    >
      <td
        className="px-2 py-2 w-8 text-xl text-neutral-500 cursor-grab"
        title="드래그하여 순서 변경"
        draggable
        onDragStart={(e) => {
          e.dataTransfer.setData("text/plain", String(index));
          e.dataTransfer.effectAllowed = "move";
          const row = (e.currentTarget as HTMLElement).closest('tr') as HTMLTableRowElement;
          const rect = row.getBoundingClientRect();
          const table = row.closest('table') as HTMLElement | null;
          const tableRect = table?.getBoundingClientRect();
          const placeholder = document.createElement('tr');
          placeholder.style.height = `${rect.height}px`;
          (row.parentNode as HTMLElement).insertBefore(placeholder, row);
          (window as any).__adminDnd = { placeholder, dragging: row, target: null, before: false, rAF: 0 };
          const clamp = (v: number, min: number, max: number) => Math.max(min, Math.min(max, v));
          const offsetY = e.clientY - rect.top;
          row.style.position = 'fixed';
          row.style.left = `${tableRect ? tableRect.left : rect.left}px`;
          row.style.width = `${tableRect ? tableRect.width : rect.width}px`;
          row.style.zIndex = '9999';
          row.classList.add('bg-white');
          const updatePos = (clientY: number) => {
            const top = clamp(clientY - offsetY, (tableRect?.top ?? 0), (tableRect?.bottom ?? (rect.top + rect.height)) - rect.height);
            row.style.top = `${top}px`;
          };
          updatePos(e.clientY);
          const img = document.createElement('canvas');
          img.width = 1; img.height = 1; const ctx = img.getContext('2d'); ctx?.clearRect(0,0,1,1);
          e.dataTransfer.setDragImage(img, 0, 0);
          const onDragOver = (ev: DragEvent) => { if (typeof ev.clientY === 'number') updatePos(ev.clientY); };
          const cleanup = () => {
            row.style.position = '';
            row.style.left = '';
            row.style.top = '';
            row.style.width = '';
            row.style.zIndex = '';
            row.classList.remove('bg-white');
            placeholder.remove();
            window.removeEventListener('dragover', onDragOver, true);
            window.removeEventListener('dragend', cleanup, true);
            const st = (window as any).__adminDnd; if (st?.rAF) cancelAnimationFrame(st.rAF);
            (window as any).__adminDnd = undefined;
          };
          window.addEventListener('dragover', onDragOver, true);
          window.addEventListener('dragend', cleanup, true);
        }}
      >≡</td>
      <td className="px-2 py-2 w-8 text-center text-neutral-500">{index + 1}</td>
      {children}
    </tr>
  );
}

function CategoryHeaderContent({ g, onDirty, onDragStart }: { g: any; onDirty: (payload: any) => void; onDragStart?: () => void }) {
  const [edit, setEdit] = useState({ name: g.name, slug: g.slug });
  return (
    <>
      <div
        className="w-10 text-xl text-neutral-500 cursor-grab select-none"
        draggable
        onDragStart={(e) => {
          e.dataTransfer.setData("text/plain", "category-drag");
          e.dataTransfer.effectAllowed = 'move';
          const img = document.createElement('canvas'); img.width = 1; img.height = 1; e.dataTransfer.setDragImage(img, 0, 0);
          onDragStart && onDragStart();
        }}
        title="드래그하여 순서 변경"
      >≡</div>
      <input className="h-8 rounded-md border border-neutral-300 px-2 text-sm w-48" value={edit.name} onChange={(e) => { const v = { ...edit, name: e.target.value }; setEdit(v); onDirty(v); }} />
      <input className="h-8 rounded-md border border-neutral-300 px-2 text-sm w-48" value={edit.slug} onChange={(e) => { const v = { ...edit, slug: e.target.value }; setEdit(v); onDirty(v); }} />
      <div className="flex-1" />
    </>
  );
}


