/**
 * 탭별 상태를 sessionStorage에 캐싱/복원하는 엔진.
 * F5 새로고침 시 비활성 탭의 데이터를 보존한다.
 *
 * 캐싱 키 구조: `tab-cache-{tabId}`
 * 값: JSON 직렬화된 TabCacheData
 */

const CACHE_PREFIX = "tab-cache-";

// --- 캐싱할 상태 구조 ---

export interface FormFieldSnapshot {
  idx: number;
  tag: string;
  type: string;
  name: string;
  id: string;
  value?: string;
  checked?: boolean;
}

/** 개별 스크롤 요소의 위치 스냅샷 (DOM 경로 기반) */
export interface ScrollSnapshot {
  /** 탭 컨테이너 기준 자식 인덱스 경로 (예: "0/2/1/3") */
  path: string;
  top: number;
  left: number;
}

export interface TabCacheData {
  /** DOM 폼 필드 스냅샷 (F5 복원용) */
  domFormFields?: FormFieldSnapshot[];

  /** 다중 스크롤 위치 (split panel 등 여러 스크롤 영역 지원) */
  scrollPositions?: ScrollSnapshot[];

  /** 캐싱 시각 */
  cachedAt: number;
}

// --- 공개 API ---

/**
 * 탭 상태를 sessionStorage에 즉시 저장
 */
export function saveTabCacheImmediate(tabId: string, data: Partial<Omit<TabCacheData, "cachedAt">>): void {
  if (typeof window === "undefined") return;

  try {
    const key = CACHE_PREFIX + tabId;
    const current = loadTabCache(tabId);
    const merged: TabCacheData = {
      ...current,
      ...data,
      cachedAt: Date.now(),
    };
    sessionStorage.setItem(key, JSON.stringify(merged));
  } catch (e) {
    console.warn("[TabCache] 저장 실패:", tabId, e);
  }
}

/**
 * 탭 상태를 sessionStorage에서 로드
 */
export function loadTabCache(tabId: string): TabCacheData | null {
  if (typeof window === "undefined") return null;

  try {
    const key = CACHE_PREFIX + tabId;
    const raw = sessionStorage.getItem(key);
    if (!raw) return null;
    return JSON.parse(raw) as TabCacheData;
  } catch {
    return null;
  }
}

/**
 * 특정 탭의 캐시 삭제.
 * tab-cache-{tabId} 외에도 테이블 관련 키(tableState_, pageSize_, filterSettings_, groupSettings_)를 일괄 제거한다.
 */
export function clearTabCache(tabId: string): void {
  if (typeof window === "undefined") return;

  try {
    sessionStorage.removeItem(CACHE_PREFIX + tabId);

    const suffix = `_${tabId}_`;
    const keysToRemove: string[] = [];
    for (let i = 0; i < sessionStorage.length; i++) {
      const key = sessionStorage.key(i);
      if (
        key &&
        (key.startsWith("tableState" + suffix) ||
          key.startsWith("pageSize" + suffix) ||
          key.startsWith("filterSettings" + suffix) ||
          key.startsWith("groupSettings" + suffix))
      ) {
        keysToRemove.push(key);
      }
    }
    keysToRemove.forEach((k) => sessionStorage.removeItem(k));
  } catch {
    // ignore
  }
}

/**
 * 모든 탭 캐시 삭제
 */
export function clearAllTabCaches(): void {
  if (typeof window === "undefined") return;

  try {
    const keysToRemove: string[] = [];
    for (let i = 0; i < sessionStorage.length; i++) {
      const key = sessionStorage.key(i);
      if (key?.startsWith(CACHE_PREFIX)) {
        keysToRemove.push(key);
      }
    }
    keysToRemove.forEach((key) => sessionStorage.removeItem(key));
  } catch {
    // ignore
  }
}

// ============================================================
// DOM 폼 상태 캡처/복원
// ============================================================

/**
 * 컨테이너 내의 모든 폼 요소 상태를 스냅샷으로 캡처
 */
export function captureFormState(container: HTMLElement | null): FormFieldSnapshot[] | null {
  if (!container) return null;

  const fields: FormFieldSnapshot[] = [];
  const elements = container.querySelectorAll<HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement>(
    "input, textarea, select",
  );

  elements.forEach((el, idx) => {
    const field: FormFieldSnapshot = {
      idx,
      tag: el.tagName.toLowerCase(),
      type: (el as HTMLInputElement).type || "",
      name: el.name || "",
      id: el.id || "",
    };

    if (el instanceof HTMLInputElement) {
      if (el.type === "checkbox" || el.type === "radio") {
        field.checked = el.checked;
      } else if (el.type !== "file" && el.type !== "password") {
        field.value = el.value;
      }
    } else if (el instanceof HTMLTextAreaElement) {
      field.value = el.value;
    } else if (el instanceof HTMLSelectElement) {
      field.value = el.value;
    }

    fields.push(field);
  });

  return fields.length > 0 ? fields : null;
}

/**
 * 단일 폼 필드에 값을 복원하고 React onChange를 트리거
 */
function applyFieldValue(el: Element, field: FormFieldSnapshot): void {
  if (el instanceof HTMLInputElement) {
    if (field.type === "checkbox" || field.type === "radio") {
      if (el.checked !== field.checked) {
        const setter = Object.getOwnPropertyDescriptor(HTMLInputElement.prototype, "checked")?.set;
        setter?.call(el, field.checked);
        el.dispatchEvent(new Event("input", { bubbles: true }));
        el.dispatchEvent(new Event("change", { bubbles: true }));
      }
    } else if (field.value !== undefined && el.value !== field.value) {
      const setter = Object.getOwnPropertyDescriptor(HTMLInputElement.prototype, "value")?.set;
      setter?.call(el, field.value);
      el.dispatchEvent(new Event("input", { bubbles: true }));
      el.dispatchEvent(new Event("change", { bubbles: true }));
    }
  } else if (el instanceof HTMLTextAreaElement) {
    if (field.value !== undefined && el.value !== field.value) {
      const setter = Object.getOwnPropertyDescriptor(HTMLTextAreaElement.prototype, "value")?.set;
      setter?.call(el, field.value);
      el.dispatchEvent(new Event("input", { bubbles: true }));
      el.dispatchEvent(new Event("change", { bubbles: true }));
    }
  } else if (el instanceof HTMLSelectElement) {
    if (field.value !== undefined && el.value !== field.value) {
      el.value = field.value;
      el.dispatchEvent(new Event("change", { bubbles: true }));
    }
  }
}

/**
 * 컨테이너에서 스냅샷 필드에 해당하는 DOM 요소를 찾는다
 */
function findFieldElement(
  container: HTMLElement,
  field: FormFieldSnapshot,
  allElements: NodeListOf<HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement>,
): Element | null {
  // 1순위: id로 검색
  if (field.id) {
    try {
      const el = container.querySelector(`#${CSS.escape(field.id)}`);
      if (el) return el;
    } catch {
      /* ignore */
    }
  }
  // 2순위: name으로 검색 (유일한 경우)
  if (field.name) {
    try {
      const candidates = container.querySelectorAll(`[name="${CSS.escape(field.name)}"]`);
      if (candidates.length === 1) return candidates[0];
    } catch {
      /* ignore */
    }
  }
  // 3순위: 인덱스 + tag/type 일치 검증
  if (field.idx < allElements.length) {
    const candidate = allElements[field.idx];
    if (candidate.tagName.toLowerCase() === field.tag && ((candidate as HTMLInputElement).type || "") === field.type) {
      return candidate;
    }
  }
  return null;
}

/**
 * 캡처한 폼 스냅샷을 DOM에 복원하고 React onChange를 트리거.
 * 폼 필드가 아직 DOM에 없으면 폴링으로 대기한다.
 * 반환된 cleanup 함수를 호출하면 대기를 취소할 수 있다.
 */
export function restoreFormState(
  container: HTMLElement | null,
  fields: FormFieldSnapshot[] | null,
): (() => void) | undefined {
  if (!container || !fields || fields.length === 0) return undefined;

  let cleaned = false;

  const cleanup = () => {
    if (cleaned) return;
    cleaned = true;
    clearInterval(pollId);
    clearTimeout(timeoutId);
  };

  const tryRestore = (): boolean => {
    const elements = container.querySelectorAll<HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement>(
      "input, textarea, select",
    );
    if (elements.length === 0) return false;

    let restoredCount = 0;
    for (const field of fields) {
      const el = findFieldElement(container, field, elements);
      if (el) {
        applyFieldValue(el, field);
        restoredCount++;
      }
    }
    return restoredCount > 0;
  };

  // 즉시 시도
  if (tryRestore()) return undefined;

  // 다음 프레임에서 재시도
  requestAnimationFrame(() => {
    if (cleaned) return;
    if (tryRestore()) {
      cleanup();
      return;
    }
  });

  // 폼 필드가 DOM에 나타날 때까지 폴링 (API 데이터 로드 대기)
  const pollId = setInterval(() => {
    if (tryRestore()) cleanup();
  }, 100);

  // 최대 5초 대기 후 포기
  const timeoutId = setTimeout(() => {
    tryRestore();
    cleanup();
  }, 5000);

  return cleanup;
}

// ============================================================
// DOM 경로 기반 스크롤 위치 캡처/복원 (다중 스크롤 영역 지원)
// ============================================================

/**
 * 컨테이너 기준 자식 인덱스 경로를 생성한다.
 * 예: container > div(2번째) > div(1번째) > div(3번째) → "2/1/3"
 */
export function getElementPath(element: HTMLElement, container: HTMLElement): string | null {
  const indices: number[] = [];
  let current: HTMLElement | null = element;

  while (current && current !== container) {
    const parent: HTMLElement | null = current.parentElement;
    if (!parent) return null;

    const children = parent.children;
    let idx = -1;
    for (let i = 0; i < children.length; i++) {
      if (children[i] === current) {
        idx = i;
        break;
      }
    }
    if (idx === -1) return null;

    indices.unshift(idx);
    current = parent;
  }

  if (current !== container) return null;
  return indices.join("/");
}

/**
 * 경로 문자열로 컨테이너 내의 요소를 찾는다.
 */
function findElementByPath(container: HTMLElement, path: string): HTMLElement | null {
  if (!path) return container;

  const indices = path.split("/").map(Number);
  let current: HTMLElement = container;

  for (const idx of indices) {
    if (!current.children || idx >= current.children.length) return null;
    const child = current.children[idx];
    if (!(child instanceof HTMLElement)) return null;
    current = child;
  }

  return current;
}

/**
 * 컨테이너 하위의 모든 스크롤된 요소를 찾아 경로와 함께 캡처한다.
 * F5 직전 (beforeunload)에 호출 - 활성 탭은 display:block이므로 DOM 값이 정확하다.
 */
export function captureAllScrollPositions(container: HTMLElement | null): ScrollSnapshot[] | undefined {
  if (!container) return undefined;

  const snapshots: ScrollSnapshot[] = [];
  const walker = document.createTreeWalker(container, NodeFilter.SHOW_ELEMENT);
  let node: Node | null;

  while ((node = walker.nextNode())) {
    const el = node as HTMLElement;
    if (el.scrollTop > 0 || el.scrollLeft > 0) {
      const path = getElementPath(el, container);
      if (path) {
        snapshots.push({ path, top: el.scrollTop, left: el.scrollLeft });
      }
    }
  }

  return snapshots.length > 0 ? snapshots : undefined;
}

/**
 * 다중 스크롤 위치를 DOM 경로 기반으로 복원한다.
 * 컨텐츠가 아직 로드되지 않았을 수 있으므로 폴링으로 대기한다.
 */
export function restoreAllScrollPositions(
  container: HTMLElement | null,
  positions?: ScrollSnapshot[],
): (() => void) | undefined {
  if (!container || !positions || positions.length === 0) return undefined;

  let cleaned = false;

  const cleanup = () => {
    if (cleaned) return;
    cleaned = true;
    clearInterval(pollId);
    clearTimeout(timeoutId);
  };

  const tryRestore = (): boolean => {
    let restoredCount = 0;

    for (const pos of positions) {
      const el = findElementByPath(container, pos.path);
      if (!el) continue;

      if (el.scrollHeight >= pos.top + el.clientHeight) {
        el.scrollTop = pos.top;
        el.scrollLeft = pos.left;
        restoredCount++;
      }
    }

    return restoredCount === positions.length;
  };

  if (tryRestore()) return undefined;

  requestAnimationFrame(() => {
    if (cleaned) return;
    if (tryRestore()) {
      cleanup();
      return;
    }
  });

  const pollId = setInterval(() => {
    if (tryRestore()) cleanup();
  }, 50);

  // 최대 5초 대기 후 강제 복원
  const timeoutId = setTimeout(() => {
    for (const pos of positions) {
      const el = findElementByPath(container, pos.path);
      if (el) {
        el.scrollTop = pos.top;
        el.scrollLeft = pos.left;
      }
    }
    cleanup();
  }, 5000);

  return cleanup;
}

