"use client";

import { ReactNode, Fragment, useState, useEffect, useRef, useCallback, Children, isValidElement, cloneElement } from "react";
import { LucideIcon, Trash2, Plus, Loader2, ArrowUpDown, ArrowUp, ArrowDown, Search, XCircle } from "lucide-react";
import { DateRangeSelector } from "@/components/molecules/DateRangeSelector";
import { Card, CardContent } from "@/components/ui/card";
import { Tabs, TabsContent } from "@/components/ui/tabs";
import { TableSkeleton, MobileCardGridSkeleton } from "@/components/ui/skeleton";
import { Table, TableBody, TableCell, TableFooter, TableHead, TableHeader, TableRow } from "@/components/ui/table";
import { Checkbox } from "@/components/ui/checkbox";
import { Button } from "@/components/ui/button";
import {
  Select,
  SelectContent,
  SelectItem,
  SelectTrigger,
  SelectValue,
} from "@/components/ui/select";
import { DeleteConfirmDialog } from '@/components/ui/confirm-dialog';
import { Input } from "@/components/ui/input";
import { PageLayout } from "@/components/organisms/PageLayout";
import { PageHeader } from "@/components/organisms/PageHeader";
import { StatCards } from "@/components/organisms/StatCards";
import { SearchFilter } from "@/components/organisms/SearchFilter";
import { ScreenVersionHistory } from "@/components/organisms/ScreenVersionHistory";
import { TabChip } from "@/components/atoms/TabChip";
import { MultiSelectCombobox } from "@/components/ui/multi-select-combobox";
import { MobileFilter, FilterFieldConfig, FilterValues } from "@/components/molecules/MobileFilter";
import { formatNumber } from '@/lib/utils/amount';
import { CopyableCell } from '@/components/molecules';

// 렌더링된 React 노드에서 텍스트만 추출 (fallback 표시값 복사 대응)
function extractTextFromNode(node: ReactNode): string {
  if (typeof node === 'string') return node;
  if (typeof node === 'number') return String(node);
  if (!node) return '';
  if (Array.isArray(node)) return node.map(extractTextFromNode).join('');
  if (isValidElement(node)) {
    return extractTextFromNode((node.props as { children?: ReactNode }).children);
  }
  return '';
}

/**
 * 기본 통합 목록_버젼2
 *
 * 품목관리 스타일의 완전한 목록 템플릿
 * - PageHeader, StatCards, SearchFilter, ScreenVersionHistory
 * - 탭 기반 필터 (데스크톱: TabsList, 모바일: 커스텀 버튼)
 * - 체크박스 포함 DataTable (Desktop)
 * - 체크박스 포함 모바일 카드 (Mobile)
 * - 페이지네이션
 */

export interface TabOption {
  value: string;
  label: string;
  mobileLabel?: string;
  count: number;
  color?: string; // 모바일 탭 색상
}

export interface TableColumn {
  key: string;
  label: string;
  className?: string;
  hideOnMobile?: boolean;
  hideOnTablet?: boolean;
  /** 정렬 가능 여부 */
  sortable?: boolean;
  /** 셀 hover 시 복사 버튼 표시 (CopyableCell 자동 래핑) */
  copyable?: boolean;
}

export interface PaginationConfig {
  currentPage: number;
  totalPages: number;
  totalItems: number;
  itemsPerPage: number;
  onPageChange: (page: number) => void;
}

export interface StatCard {
  label: string;
  value: string | number;
  icon: LucideIcon;
  iconColor: string;
  onClick?: () => void;
  isActive?: boolean;
}

export interface VersionHistoryItem {
  version: string;
  description: string;
  modifiedBy: string;
  modifiedAt: string;
}

export interface DevMetadata {
  componentName: string;
  pagePath: string;
  description: string;
  apis?: unknown[];
  dataStructures?: unknown[];
  dbSchema?: unknown[];
  businessLogic?: unknown[];
}

export interface IntegratedListTemplateV2Props<T = any> {
  // 페이지 헤더
  title: string | ReactNode;
  description?: string;
  icon?: LucideIcon;
  headerActions?: ReactNode;

  // ===== 공통 헤더 옵션 (달력/등록버튼) =====
  /**
   * 날짜 범위 선택기 (왼쪽 배치)
   * - enabled: 달력 표시 여부
   * - showPresets: 프리셋 버튼 (당해년도, 전전월, 전월, 당월, 어제, 오늘)
   * - startDate/endDate: 외부 상태 연동
   * - onChange: 날짜 변경 콜백
   */
  dateRangeSelector?: {
    enabled: boolean;
    showPresets?: boolean;
    /** 날짜 입력 숨김 (검색창만 표시하고 싶을 때) */
    hideDateInputs?: boolean;
    /** 표시할 프리셋 목록 */
    presets?: import('@/components/molecules/DateRangeSelector').DatePreset[];
    /** 프리셋 레이블 커스텀 오버라이드 */
    presetLabels?: Partial<Record<import('@/components/molecules/DateRangeSelector').DatePreset, string>>;
    /** 프리셋 버튼 위치 */
    presetsPosition?: 'inline' | 'below';
    /** 날짜 입력 변형: 'split' (DatePicker 2개), 'combined' (DateRangePicker 1개) */
    variant?: 'split' | 'combined';
    startDate?: string;
    endDate?: string;
    onStartDateChange?: (date: string) => void;
    onEndDateChange?: (date: string) => void;
    /** 추가 액션 (검색창 등) - 프리셋 버튼 옆에 배치 */
    extraActions?: ReactNode;
  };
  /**
   * 등록 버튼 (오른쪽 끝 배치)
   * - label: 버튼 텍스트 (예: '등록', '공정 등록')
   * - onClick: 클릭 핸들러
   * - icon: 아이콘 (기본: Plus)
   */
  createButton?: {
    label: string;
    onClick: () => void;
    icon?: LucideIcon;
  };

  // 탭 콘텐츠 (헤더 액션 아래, 검색 위에 표시되는 커스텀 탭)
  tabsContent?: ReactNode;

  // 통계 카드
  stats?: StatCard[];

  // 경고 배너 (통계 카드와 검색 영역 사이)
  alertBanner?: ReactNode;

  // 버전 이력
  versionHistory?: VersionHistoryItem[];
  versionHistoryTitle?: string;

  // 검색 및 필터
  searchValue?: string;
  onSearchChange?: (value: string) => void;
  searchPlaceholder?: string;
  extraFilters?: ReactNode; // Select, DatePicker 등 추가 필터
  hideSearch?: boolean; // 검색창 숨김 여부

  // 탭 (품목 유형, 상태 등) - optional
  tabs?: TabOption[];
  activeTab?: string;
  onTabChange?: (value: string) => void;
  /** 탭 렌더링 위치: 'card' (기본, 테이블 카드 내부) | 'above-stats' (통계 카드 위) */
  tabsPosition?: 'card' | 'above-stats';

  // 테이블 헤더 액션 (탭 옆에 표시될 셀렉트박스 등)
  tableHeaderActions?: ReactNode;

  // 모바일/카드 뷰용 필터 슬롯 (xl 미만에서 카드 목록 위에 표시)
  mobileFilterSlot?: ReactNode;

  // ===== 새로운 통합 필터 시스템 (선택적 사용) =====
  // filterConfig를 전달하면 PC는 인라인, 모바일은 바텀시트로 자동 분기
  // 기존 tableHeaderActions, mobileFilterSlot과 함께 사용 가능
  filterConfig?: FilterFieldConfig[];
  filterValues?: FilterValues;
  onFilterChange?: (key: string, value: string | string[]) => void;
  onFilterReset?: () => void;
  filterTitle?: string; // 모바일 필터 바텀시트 제목 (기본: "검색 필터")

  // 테이블 앞에 표시될 컨텐츠 (계정과목명 + 저장 버튼 등)
  beforeTableContent?: ReactNode;

  // 테이블 뒤에 표시될 컨텐츠 (캘린더 등)
  afterTableContent?: ReactNode;

  // 테이블 컬럼
  tableColumns: TableColumn[];
  tableTitle?: string; // "전체 목록 (100개)" 같은 타이틀

  // ===== 정렬 설정 =====
  /** 현재 정렬 컬럼 키 */
  sortBy?: string;
  /** 정렬 방향 */
  sortOrder?: 'asc' | 'desc';
  /** 정렬 변경 핸들러 */
  onSort?: (key: string) => void;

  // 커스텀 테이블 헤더 렌더링 (동적 컬럼용)
  renderCustomTableHeader?: () => ReactNode;

  // 테이블 하단 푸터 (합계 등)
  tableFooter?: ReactNode;

  // 데이터
  data: T[]; // 데스크톱용 페이지네이션된 데이터
  totalCount?: number; // 전체 데이터 개수 (역순 번호 계산용)
  allData?: T[]; // 클라이언트 사이드 필터링용 전체 데이터 (소량 데이터 페이지용)
  mobileDisplayCount?: number; // 클라이언트 사이드 인피니티에서 표시할 개수
  onLoadMore?: () => void; // 더 불러오기 콜백 (레거시)

  // ===== 서버 사이드 모바일 인피니티 스크롤 =====
  // 모바일에서 스크롤/버튼으로 다음 페이지 로드, 데이터 누적 표시
  enableMobileInfinityScroll?: boolean; // 서버 사이드 인피니티 활성화 (기본: true)
  isMobileLoading?: boolean; // 모바일 추가 로딩 중 상태

  // 체크박스 선택
  selectedItems: Set<string>;
  onToggleSelection: (id: string) => void;
  onToggleSelectAll: () => void;
  getItemId: (item: T) => string; // 아이템에서 ID 추출
  onBulkDelete?: () => void; // 일괄 삭제 핸들러
  /** 선택 액션 버튼 (테이블 왼쪽 "전체 N건 / N개 선택됨" 옆에 표시) */
  selectionActions?: ReactNode;

  // 테이블 표시 옵션
  showCheckbox?: boolean; // 체크박스 표시 여부 (기본: true)
  showRowNumber?: boolean; // 번호 컬럼 표시 여부 (기본: true, tableColumns에 번호 포함 시)

  // 렌더링 함수
  renderTableRow: (item: T, index: number, globalIndex: number) => ReactNode;
  renderMobileCard: (item: T, index: number, globalIndex: number, isSelected: boolean, onToggle: () => void) => ReactNode;

  // 페이지네이션
  pagination: PaginationConfig;

  // 개발자 메타데이터
  devMetadata?: DevMetadata;

  // 로딩 상태
  isLoading?: boolean;

  // ===== 컬럼 리사이즈 & 가시성 설정 (opt-in) =====
  columnSettings?: {
    columnWidths: Record<string, number>;
    onColumnResize: (columnKey: string, width: number) => void;
    settingsPopover: ReactNode;
  };
}

export function IntegratedListTemplateV2<T = any>({
  title,
  description,
  icon,
  headerActions,
  dateRangeSelector,
  createButton,
  tabsContent,
  stats,
  alertBanner,
  versionHistory,
  versionHistoryTitle = "수정 이력",
  searchValue,
  onSearchChange,
  searchPlaceholder = "검색...",
  extraFilters,
  hideSearch = true, // 기본값: 타이틀 아래에 검색창 표시 (Card 안 SearchFilter 숨김)
  tabs,
  activeTab,
  onTabChange,
  tabsPosition = 'card',
  tableHeaderActions,
  mobileFilterSlot,
  filterConfig,
  filterValues,
  onFilterChange,
  onFilterReset,
  filterTitle = "검색 필터",
  beforeTableContent,
  afterTableContent,
  tableColumns,
  tableTitle: _tableTitle,
  sortBy,
  sortOrder,
  onSort,
  renderCustomTableHeader,
  tableFooter,
  data,
  totalCount: _totalCount,
  allData,
  mobileDisplayCount,
  onLoadMore,
  enableMobileInfinityScroll = true, // 기본값: 활성화
  isMobileLoading = false,
  selectedItems,
  onToggleSelection,
  onToggleSelectAll,
  getItemId,
  onBulkDelete,
  selectionActions,
  showCheckbox = true, // 기본값 true
  showRowNumber: _showRowNumber = true, // 기본값 true (번호 컬럼은 renderTableRow에서 처리)
  renderTableRow,
  renderMobileCard,
  pagination,
  devMetadata: _devMetadata,
  isLoading,
  columnSettings,
}: IntegratedListTemplateV2Props<T>) {

  const [showDeleteDialog, setShowDeleteDialog] = useState(false);

  // ===== copyable 컬럼 자동 래핑 =====
  // copyable: true인 컬럼의 셀 인덱스 맵 (cell index → column key)
  const copyableMap = useCallback(() => {
    const map = new Map<number, string>();
    const offset = showCheckbox ? 1 : 0;
    tableColumns.forEach((col, i) => {
      if (col.copyable) map.set(i + offset, col.key);
    });
    return map;
  }, [tableColumns, showCheckbox]);

  const applyCopyableCells = useCallback((row: ReactNode, item: T): ReactNode => {
    const map = copyableMap();
    if (map.size === 0 || !isValidElement(row)) return row;

    const rowEl = row as React.ReactElement<{ children?: ReactNode }>;
    const cells = Children.toArray(rowEl.props.children);
    if (cells.length === 0) return row;

    const newCells = cells.map((cell, idx) => {
      const colKey = map.get(idx);
      if (!colKey || !isValidElement(cell)) return cell;

      const rawValue = (item as Record<string, unknown>)[colKey];
      let copyValue = rawValue != null ? String(rawValue) : '';
      // 데이터 키가 비어있으면 렌더링된 셀 텍스트를 복사값으로 사용 (fallback 표시값 대응)
      if (!copyValue) {
        copyValue = extractTextFromNode(cell).trim();
      }
      if (!copyValue || copyValue === '-') return cell;

      const cellEl = cell as React.ReactElement<{ children?: ReactNode }>;
      return cloneElement(cellEl, {},
        <CopyableCell value={copyValue}>{cellEl.props.children}</CopyableCell>
      );
    });

    return cloneElement(rowEl, {}, ...newCells);
  }, [copyableMap]);

  // ===== 서버 사이드 모바일 인피니티 스크롤 =====
  // 모바일에서 누적 데이터를 관리하여 스크롤 시 계속 추가
  const [accumulatedMobileData, setAccumulatedMobileData] = useState<T[]>([]);
  const [lastAccumulatedPage, setLastAccumulatedPage] = useState(0);
  const mobileScrollSentinelRef = useRef<HTMLDivElement>(null);
  const mobileCardAreaRef = useRef<HTMLDivElement>(null);

  // 클라이언트 사이드 인피니티용 (allData가 있는 경우)
  const [clientDisplayCount, setClientDisplayCount] = useState(mobileDisplayCount || 20);

  // 서버 페이지네이션: 데이터 누적 로직
  // - 페이지 1이면 리셋 (필터/탭 변경)
  // - 이전 페이지 + 1이면 누적 (스크롤로 다음 페이지 로드)
  //
  // 서버 사이드 판단: allData가 없거나, pagination.totalItems > allData.length
  // (외부 훅으로 데이터 관리하면서 서버 페이지네이션 사용하는 경우)
  useEffect(() => {
    const isServerSide = !allData || pagination.totalItems > allData.length;

    if (!isServerSide) {
      // 순수 클라이언트 사이드 필터링 모드 - 누적 불필요
      return;
    }

    if (!enableMobileInfinityScroll) {
      // 서버 사이드 인피니티 비활성화 - data만 사용
      setAccumulatedMobileData(data);
      return;
    }

    if (pagination.currentPage === 1) {
      // 페이지 1: 필터/탭 변경으로 리셋
      setAccumulatedMobileData(data);
      setLastAccumulatedPage(1);
    } else if (pagination.currentPage === lastAccumulatedPage + 1) {
      // 다음 페이지: 기존 데이터에 누적 (중복 제거)
      setAccumulatedMobileData(prev => {
        const existingIds = new Set(prev.map(item => getItemId(item)));
        const newItems = data.filter(item => !existingIds.has(getItemId(item)));
        return [...prev, ...newItems];
      });
      setLastAccumulatedPage(pagination.currentPage);
    } else if (pagination.currentPage !== lastAccumulatedPage) {
      // 페이지 점프 (예: PC에서 페이지 변경 후 모바일로): 현재 데이터만 표시
      setAccumulatedMobileData(data);
      setLastAccumulatedPage(pagination.currentPage);
    }
  }, [data, pagination.currentPage, pagination.totalItems, allData, enableMobileInfinityScroll, lastAccumulatedPage, getItemId]);

  // 탭 변경 감지: activeTab 변경 시 누적 데이터 리셋
  // 주의: allData를 dependency에 넣으면 페이지 변경 시마다 리셋됨 (외부 훅 사용 시)
  useEffect(() => {
    if (enableMobileInfinityScroll) {
      setAccumulatedMobileData([]);
      setLastAccumulatedPage(0);
    }
     
  }, [activeTab]); // activeTab만 감지 - 탭 변경 시에만 리셋

  // 클라이언트 사이드: allData가 변경되면 displayCount 리셋
  useEffect(() => {
    if (allData) {
      setClientDisplayCount(mobileDisplayCount || 20);
    }
  }, [allData, mobileDisplayCount]);

  // 서버 사이드: 스크롤/버튼으로 다음 페이지 로드
  const handleLoadMoreMobile = useCallback(() => {
    if (isMobileLoading) return;
    if (pagination.currentPage >= pagination.totalPages) return;

    // 다음 페이지 요청
    pagination.onPageChange(pagination.currentPage + 1);
  }, [isMobileLoading, pagination]);

  // 클라이언트 사이드: 더 보기
  const handleLoadMoreClient = useCallback(() => {
    if (!allData) return;
    setClientDisplayCount(prev => Math.min(prev + 20, allData.length));
    onLoadMore?.();
  }, [allData, onLoadMore]);

  // 서버 사이드 페이지네이션 사용 여부 판단 (useEffect보다 먼저 정의)
  // - allData가 없으면 서버 사이드
  // - allData가 있어도 pagination.totalItems가 더 크면 서버 사이드 (외부 훅으로 데이터 관리하는 경우)
  const isServerSidePagination = !allData || pagination.totalItems > allData.length;

  // Intersection Observer - 서버 사이드 & 클라이언트 사이드 공통
  useEffect(() => {
    if (!mobileScrollSentinelRef.current) return;

    const observer = new IntersectionObserver(
      (entries) => {
        if (!entries[0].isIntersecting) return;

        if (isServerSidePagination) {
          // 서버 사이드 인피니티
          if (enableMobileInfinityScroll && !isMobileLoading && pagination.currentPage < pagination.totalPages) {
            handleLoadMoreMobile();
          }
        } else {
          // 클라이언트 사이드 인피니티
          if (clientDisplayCount < allData!.length) {
            handleLoadMoreClient();
          }
        }
      },
      { threshold: 0.1 }
    );

    observer.observe(mobileScrollSentinelRef.current);
    return () => observer.disconnect();
  }, [isServerSidePagination, allData, clientDisplayCount, enableMobileInfinityScroll, isMobileLoading, pagination.currentPage, pagination.totalPages, handleLoadMoreClient, handleLoadMoreMobile]);

  // ===== 모바일 카드 영역 내부 스크롤 컨테인먼트 =====
  // 카드 영역이 뷰포트 남은 높이만큼만 차지하고, 내부에서만 스크롤되도록 설정
  // → 헤더/검색/탭은 항상 보이고, 카드만 스크롤
  useEffect(() => {
    const el = mobileCardAreaRef.current;
    if (!el) return;

    const applyScrollContainment = () => {
      // xl(1280px) 이상은 데스크톱 → 테이블+페이지네이션 사용, 컨테인먼트 해제
      if (window.innerWidth >= 1280) {
        el.style.maxHeight = '';
        el.style.overflowY = '';
        el.style.overscrollBehavior = '';
        return;
      }

      const rect = el.getBoundingClientRect();
      const available = window.innerHeight - rect.top - 16; // 16px 하단 여유

      if (available > 200) {
        el.style.maxHeight = `${available}px`;
        el.style.overflowY = 'auto';
        el.style.overscrollBehavior = 'contain'; // 스크롤 누수 방지
      }
    };

    // 페이지 스크롤을 최상단으로 리셋 후 정확한 위치 측정
    window.scrollTo(0, 0);
    el.scrollTop = 0;

    const raf = requestAnimationFrame(applyScrollContainment);
    window.addEventListener('resize', applyScrollContainment);

    return () => {
      cancelAnimationFrame(raf);
      window.removeEventListener('resize', applyScrollContainment);
    };
  }, [activeTab, isLoading]);

  const startIndex = (pagination.currentPage - 1) * pagination.itemsPerPage;
  const allSelected = selectedItems.size === data.length && data.length > 0;

  // 모바일용 데이터 결정
  // 1. 서버 사이드: 누적 데이터 또는 현재 페이지 데이터
  // 2. 클라이언트 사이드: allData에서 slice
  // 참고: accumulatedMobileData가 비어있으면 data 또는 allData를 폴백으로 사용
  const mobileData = isServerSidePagination
    ? (enableMobileInfinityScroll
        ? (accumulatedMobileData.length > 0 ? accumulatedMobileData : (allData || data))
        : data)
    : allData!.slice(0, clientDisplayCount);

  // 더 로드 가능 여부
  const hasMoreData = isServerSidePagination
    ? pagination.currentPage < pagination.totalPages
    : clientDisplayCount < allData!.length;

  // 현재 로드된 개수 / 전체 개수
  const loadedCount = isServerSidePagination
    ? (accumulatedMobileData.length > 0 ? accumulatedMobileData.length : mobileData.length)
    : clientDisplayCount;
  const totalDataCount = isServerSidePagination
    ? pagination.totalItems
    : allData!.length;

  // ===== filterConfig 기반 자동 필터 렌더링 =====
  // PC용 인라인 필터 (xl 이상에서 표시)
  const renderAutoFilters = () => {
    if (!filterConfig || !filterValues || !onFilterChange) return null;

    return (
      <div className="flex items-center gap-2 flex-wrap">
        {filterConfig.map((field) => {
          if (field.type === 'single') {
            // 단일선택: Select
            return (
              <Select
                key={field.key}
                value={(filterValues[field.key] as string) || 'all'}
                onValueChange={(value) => onFilterChange(field.key, value)}
              >
                <SelectTrigger className="min-w-[120px] w-auto">
                  <SelectValue placeholder={field.allOptionLabel || field.label} />
                </SelectTrigger>
                <SelectContent>
                  <SelectItem value="all">
                    {field.allOptionLabel || '전체'}
                  </SelectItem>
                  {field.options.filter(opt => opt.value !== '').map((option) => (
                    <SelectItem key={option.value} value={option.value}>
                      {option.label}
                    </SelectItem>
                  ))}
                </SelectContent>
              </Select>
            );
          } else {
            // 다중선택: MultiSelectCombobox
            return (
              <MultiSelectCombobox
                key={field.key}
                options={field.options.map((opt) => ({
                  value: opt.value,
                  label: opt.label,
                }))}
                value={(filterValues[field.key] as string[]) || []}
                onChange={(value) => onFilterChange(field.key, value)}
                placeholder={field.label}
                searchPlaceholder={`${field.label} 검색...`}
                className="w-[140px]"
              />
            );
          }
        })}
      </div>
    );
  };

  // 모바일용 바텀시트 필터 (xl 미만에서 표시)
  const renderAutoMobileFilter = () => {
    if (!filterConfig || !filterValues || !onFilterChange || !onFilterReset) return null;

    return (
      <MobileFilter
        fields={filterConfig}
        values={filterValues}
        onChange={onFilterChange}
        onReset={onFilterReset}
        buttonLabel="필터"
        title={filterTitle}
      />
    );
  };

  // 일괄삭제 확인 핸들러
  const handleBulkDeleteClick = () => {
    setShowDeleteDialog(true);
  };

  // 일괄삭제 실행
  const handleConfirmDelete = () => {
    if (onBulkDelete) {
      onBulkDelete();
    }
    setShowDeleteDialog(false);
  };

  // 헤더 액션 스켈레톤 (달력 + 프리셋 버튼 + 등록 버튼)
  const _renderHeaderActionSkeleton = () => (
    <div className="flex items-center gap-2 flex-wrap w-full">
      {dateRangeSelector?.enabled && (
        <>
          <div className="flex items-center gap-2">
            <div className="h-10 w-[140px] rounded-md border border-gray-200 bg-gray-100 animate-pulse" />
            <div className="h-4 w-4 bg-gray-300 rounded animate-pulse" />
            <div className="h-10 w-[140px] rounded-md border border-gray-200 bg-gray-100 animate-pulse" />
          </div>
          <div className="hidden md:flex items-center gap-1.5">
            <div className="h-8 w-16 rounded-md border border-gray-200 bg-gray-100 animate-pulse" />
            <div className="h-8 w-14 rounded-md border border-gray-200 bg-gray-100 animate-pulse" />
            <div className="h-8 w-12 rounded-md border border-gray-200 bg-gray-100 animate-pulse" />
            <div className="h-8 w-12 rounded-md border border-gray-200 bg-gray-100 animate-pulse" />
            <div className="h-8 w-10 rounded-md border border-gray-200 bg-gray-100 animate-pulse" />
            <div className="h-8 w-10 rounded-md border border-gray-200 bg-gray-100 animate-pulse" />
          </div>
        </>
      )}
      {createButton && (
        <div className="h-10 w-28 rounded-md border border-gray-200 bg-gray-100 animate-pulse ml-auto" />
      )}
    </div>
  );

  return (
    <PageLayout>
      {/* 페이지 헤더 - 항상 표시 */}
      <PageHeader
        title={title}
        description={description}
        icon={icon}
      />

      {/* 헤더 액션 (달력, 버튼 등) - 타이틀 아래 배치 */}
      {/* 레이아웃: [달력] [프리셋버튼] [검색창] -------------- [추가버튼들] [등록버튼] (오른쪽 끝) */}
      {(dateRangeSelector?.enabled || createButton || headerActions || (hideSearch && onSearchChange)) && (
          <div className="flex flex-col xl:flex-row xl:flex-wrap xl:items-center xl:justify-between gap-2 w-full">
            {/* 날짜 범위 선택기 + 검색창 (왼쪽) */}
            {dateRangeSelector?.enabled ? (
              <DateRangeSelector
                startDate={dateRangeSelector.startDate || ''}
                endDate={dateRangeSelector.endDate || ''}
                onStartDateChange={dateRangeSelector.onStartDateChange || (() => {})}
                onEndDateChange={dateRangeSelector.onEndDateChange || (() => {})}
                hidePresets={dateRangeSelector.showPresets === false}
                hideDateInputs={dateRangeSelector.hideDateInputs}
                presets={dateRangeSelector.presets}
                presetLabels={dateRangeSelector.presetLabels}
                presetsPosition={dateRangeSelector.presetsPosition}
                variant={dateRangeSelector.variant}
                extraActions={
                  <>
                    {/* hideSearch=true면 검색창 자동 추가 (extraActions 앞에) */}
                    {hideSearch && onSearchChange && (
                      <div className="relative w-full xl:w-[300px]">
                        <Search className="absolute left-3 top-1/2 transform -translate-y-1/2 h-4 w-4 text-muted-foreground" />
                        <Input
                          type="text"
                          placeholder={searchPlaceholder}
                          value={searchValue || ''}
                          onChange={(e) => onSearchChange(e.target.value)}
                          className="pl-9 pr-8 w-full bg-gray-50 border-gray-200"
                        />
                        {searchValue && (
                          <button
                            type="button"
                            onClick={() => onSearchChange('')}
                            className="absolute right-2.5 top-1/2 -translate-y-1/2 text-muted-foreground/60 hover:text-muted-foreground transition-colors"
                          >
                            <XCircle className="w-4 h-4" />
                          </button>
                        )}
                      </div>
                    )}
                    {/* 기존 extraActions (추가 버튼 등) */}
                    {dateRangeSelector.extraActions}
                  </>
                }
              />
            ) : (
              /* dateRangeSelector 없어도 hideSearch=true면 검색창 표시 */
              hideSearch && onSearchChange && (
                <div className="relative w-full xl:w-[300px]">
                  <Search className="absolute left-3 top-1/2 transform -translate-y-1/2 h-4 w-4 text-muted-foreground" />
                  <Input
                    type="text"
                    placeholder={searchPlaceholder}
                    value={searchValue || ''}
                    onChange={(e) => onSearchChange(e.target.value)}
                    className="pl-9 pr-8 w-full bg-gray-50 border-gray-200"
                  />
                  {searchValue && (
                    <button
                      type="button"
                      onClick={() => onSearchChange('')}
                      className="absolute right-2.5 top-1/2 -translate-y-1/2 text-muted-foreground/60 hover:text-muted-foreground transition-colors"
                    >
                      <XCircle className="w-4 h-4" />
                    </button>
                  )}
                </div>
              )
            )}
            {/* 버튼 영역 (오른쪽 배치, 공간 부족시 자연스럽게 줄바꿈) */}
            {(headerActions || createButton) && (
              <div className="mobile-actions-grid xl:flex xl:items-center xl:gap-2 xl:shrink-0">
                {/* 헤더 액션 (엑셀 다운로드 등 추가 버튼들) */}
                {headerActions}
                {/* 등록 버튼 */}
                {createButton && (
                  <Button onClick={createButton.onClick}>
                    {createButton.icon ? (
                      <createButton.icon className="h-4 w-4 mr-2" />
                    ) : (
                      <Plus className="h-4 w-4 mr-2" />
                    )}
                    {createButton.label}
                  </Button>
                )}
              </div>
            )}
          </div>
      )}

      {/* 커스텀 탭 콘텐츠 (헤더 아래, 검색 위) */}
      {tabsContent && (
        <div className="flex items-center">
          {tabsContent}
        </div>
      )}

      {/* 탭 - 카드 밖 (tabsPosition === 'above-stats') */}
      {tabsPosition === 'above-stats' && tabs && tabs.length > 0 && (
        <div className="xl:overflow-x-auto">
          <div className="mobile-tab-grid flex gap-2 xl:min-w-max">
            {tabs.map((tab) => (
              <TabChip
                key={tab.value}
                label={tab.label}
                count={tab.count}
                active={activeTab === tab.value}
                onClick={() => onTabChange?.(tab.value)}
                color={tab.color as any}
              />
            ))}
          </div>
        </div>
      )}

      {/* 통계 카드 - 태블릿/데스크톱 */}
      {stats && stats.length > 0 ? (
        <div className="hidden md:block">
          <StatCards stats={stats} />
        </div>
      ) : null}

      {/* 경고 배너 (통계 카드와 검색 영역 사이) */}
      {alertBanner}

      {/* 버전 이력 */}
      {versionHistory && versionHistory.length > 0 && (
        <ScreenVersionHistory
          versionHistory={versionHistory as any}
          title={versionHistoryTitle}
        />
      )}

      {/* 검색 및 필터 */}
      {!hideSearch && (
        <Card>
          <CardContent className="p-6">
            <SearchFilter
              searchValue={searchValue || ''}
              onSearchChange={onSearchChange || (() => {})}
              searchPlaceholder={searchPlaceholder}
              filterButton={false}
              extraActions={extraFilters}
            />
          </CardContent>
        </Card>
      )}

      {/* 테이블 앞 컨텐츠 (계정과목명 + 저장 버튼, 달력 등) */}
      {beforeTableContent && (
        <div className="w-full py-2">
          {beforeTableContent}
        </div>
      )}

      {/* 목록 카드 */}
      <Card>
        <CardContent className="pt-6">
          <Tabs value={activeTab || 'default'} onValueChange={onTabChange} className="w-full">
            {/* 데스크톱 (1280px+) - TabChip 탭 */}
            <div className="hidden xl:block mb-4">
              <div className="flex flex-wrap gap-2 justify-between items-center">
                {/* 왼쪽: 전체 건수 + 선택 정보 + 선택삭제 */}
                <div className="flex items-center gap-2 text-sm">
                  <span className="text-muted-foreground">
                    전체 {pagination.totalItems}건
                  </span>
                  {selectedItems.size > 0 && (
                    <>
                      <span className="text-muted-foreground">/</span>
                      <span className="text-primary font-medium">
                        {selectedItems.size}개 항목 선택됨
                      </span>
                    </>
                  )}
                  {selectedItems.size >= 1 && onBulkDelete && (
                    <Button
                      variant="outline"
                      size="sm"
                      onClick={handleBulkDeleteClick}
                      className="flex items-center gap-2 bg-gray-900 text-white hover:bg-gray-800 hover:text-white"
                    >
                      <Trash2 className="h-4 w-4" />
                      선택삭제
                    </Button>
                  )}
                  {/* 선택 액션 버튼 (상신, 승인, 삭제 등) */}
                  {selectedItems.size > 0 && selectionActions}
                </div>
                {/* 오른쪽: 탭 + 헤더 액션 + 필터 */}
                <div className="flex items-center gap-2">
                  {tabsPosition !== 'above-stats' && tabs && tabs.map((tab) => (
                    <TabChip
                      key={tab.value}
                      label={tab.label}
                      count={tab.count}
                      active={activeTab === tab.value}
                      onClick={() => onTabChange?.(tab.value)}
                      color={tab.color as any}
                    />
                  ))}
                  {tableHeaderActions}
                  {renderAutoFilters()}
                  {columnSettings?.settingsPopover}
                </div>
              </div>
            </div>

            {/* 모바일/태블릿 (~1279px) - TabChip 탭 */}
            {tabsPosition !== 'above-stats' && tabs && tabs.length > 0 && (
              <div className="xl:hidden mb-4">
                <div className="mobile-tab-grid flex gap-2">
                  {tabs.map((tab) => (
                    <TabChip
                      key={tab.value}
                      label={tab.mobileLabel || tab.label}
                      count={tab.count}
                      active={activeTab === tab.value}
                      onClick={() => onTabChange?.(tab.value)}
                      color={tab.color as any}
                    />
                  ))}
                </div>
              </div>
            )}

            {/* 탭 컨텐츠 */}
            {(tabs || [{ value: 'default', label: '', count: 0 }]).map((tab) => (
              <TabsContent key={tab.value} value={tab.value} className="mt-0">
                {/* 모바일/태블릿/소형 노트북 (~1279px) - 선택 액션 + 선택 삭제 하단 고정 바 */}
                {selectedItems.size > 0 && (selectionActions || onBulkDelete) && (
                  <div className="xl:hidden fixed bottom-0 left-0 right-0 p-3 bg-white border-t shadow-lg z-50">
                    <div className="flex items-center gap-2 mb-2 text-sm">
                      <span className="text-primary font-medium">
                        {selectedItems.size}개 선택됨
                      </span>
                    </div>
                    <div className="flex gap-2 overflow-x-auto">
                      {selectionActions}
                      {selectedItems.size >= 1 && onBulkDelete && (
                        <Button
                          variant="outline"
                          size="sm"
                          onClick={handleBulkDeleteClick}
                          className="flex items-center gap-2 bg-gray-900 text-white hover:bg-gray-800 hover:text-white shrink-0"
                        >
                          <Trash2 className="h-4 w-4" />
                          선택삭제
                        </Button>
                      )}
                    </div>
                  </div>
                )}

                {/* 모바일/카드 뷰용 필터 - filterConfig 자동 생성 또는 기존 mobileFilterSlot */}
                {(filterConfig || mobileFilterSlot) && (
                  <div className="xl:hidden mb-4">
                    {/* filterConfig가 있으면 자동 생성된 MobileFilter 사용 */}
                    {renderAutoMobileFilter()}
                    {/* 기존 방식: mobileFilterSlot 직접 전달 */}
                    {mobileFilterSlot}
                  </div>
                )}

                {/* 모바일/태블릿/소형 노트북 (~1279px) 카드 뷰 */}
                <div ref={mobileCardAreaRef} className="xl:hidden space-y-4 md:space-y-0 md:grid md:grid-cols-2 md:gap-4 lg:grid-cols-3">
                  {isLoading ? (
                    <div className="col-span-full">
                      <MobileCardGridSkeleton count={6} showCheckbox={showCheckbox} />
                    </div>
                  ) : mobileData.length === 0 ? (
                    <div className="text-center py-6 text-muted-foreground border rounded-lg text-[14px]">
                      검색 결과가 없습니다.
                    </div>
                  ) : (
                    // 인피니티 스크롤: mobileData는 allData?.slice(0, displayCount) || data
                    mobileData.map((item, index) => {
                      const itemId = getItemId(item);
                      const isSelected = selectedItems.has(itemId);
                      // 순차 번호: 1번부터 시작
                      const globalIndex = index + 1;

                      return (
                        <div key={itemId}>
                          {renderMobileCard(
                            item,
                            index,
                            globalIndex,
                            isSelected,
                            () => onToggleSelection(itemId)
                          )}
                        </div>
                      );
                    })
                  )}
                  {/* 모바일 인피니티 스크롤 - 더 보기 버튼 & 로딩 표시 */}
                  {mobileData.length > 0 && (
                    <div className="col-span-full">
                      {hasMoreData ? (
                        <>
                          {/* 스크롤 감지용 Sentinel */}
                          <div
                            ref={mobileScrollSentinelRef}
                            className="h-4 w-full"
                            aria-hidden="true"
                          />
                          {/* 더 보기 버튼 + 진행 상황 */}
                          <div className="flex flex-col items-center gap-2 py-4">
                            {isMobileLoading ? (
                              <div className="flex items-center gap-2 text-sm text-muted-foreground">
                                <Loader2 className="h-4 w-4 animate-spin" />
                                불러오는 중...
                              </div>
                            ) : (
                              <Button
                                variant="outline"
                                size="sm"
                                onClick={isServerSidePagination ? handleLoadMoreMobile : handleLoadMoreClient}
                                className="w-full max-w-xs"
                              >
                                더 보기
                              </Button>
                            )}
                            <span className="text-xs text-muted-foreground">
                              {formatNumber(loadedCount)} / {formatNumber(totalDataCount)}
                            </span>
                          </div>
                        </>
                      ) : (
                        <div className="text-center py-4 text-sm text-muted-foreground">
                          모든 항목을 불러왔습니다 ({formatNumber(totalDataCount)}개)
                        </div>
                      )}
                    </div>
                  )}
                </div>

                {/* 데스크톱 (1280px+) 테이블 뷰 */}
                <div className="hidden xl:block rounded-md border overflow-x-auto [&::-webkit-scrollbar]:h-3 [&::-webkit-scrollbar-track]:bg-gray-100 [&::-webkit-scrollbar-thumb]:bg-gray-300 [&::-webkit-scrollbar-thumb]:rounded-full [&::-webkit-scrollbar-thumb]:hover:bg-gray-400" style={{ scrollbarWidth: 'thin', scrollbarColor: '#d1d5db #f3f4f6' }}>
                  {isLoading ? (
                    <TableSkeleton
                      rows={pagination.itemsPerPage || 10}
                      columns={tableColumns.length}
                      showCheckbox={showCheckbox}
                      showActions={tableColumns.some(col => col.key === 'actions')}
                    />
                  ) : (
                  <Table className={columnSettings && Object.keys(columnSettings.columnWidths).length > 0 ? "table-fixed" : "table-auto [&_td]:whitespace-nowrap [&_th]:whitespace-nowrap"}>
                    {columnSettings && Object.keys(columnSettings.columnWidths).length > 0 && (
                      <colgroup>
                        {showCheckbox && <col style={{ width: 50 }} />}
                        {tableColumns.map((col) => (
                          <col
                            key={col.key}
                            style={columnSettings.columnWidths[col.key] ? { width: columnSettings.columnWidths[col.key] } : undefined}
                          />
                        ))}
                      </colgroup>
                    )}
                    <TableHeader>
                      <TableRow>
                        {renderCustomTableHeader ? (
                          // 커스텀 테이블 헤더 사용 (동적 컬럼용)
                          renderCustomTableHeader()
                        ) : (
                          // 기본 테이블 헤더
                          <>
                            {showCheckbox && (
                              <TableHead className="w-[50px] min-w-[50px] max-w-[50px] text-center">
                                <Checkbox
                                  checked={allSelected}
                                  onCheckedChange={onToggleSelectAll}
                                />
                              </TableHead>
                            )}
                            {tableColumns.map((column) => {
                              // "actions" 컬럼은 항상 렌더링하되, 선택된 항목이 없을 때는 빈 헤더로 표시
                              const isSortable = column.sortable && onSort;
                              const isCurrentSort = sortBy === column.key;

                              return (
                                <TableHead
                                  key={column.key}
                                  className={`${column.className || ''} ${columnSettings ? 'relative' : ''}`}
                                >
                                  {column.key === "actions" ? (
                                    <span>{column.label}</span>
                                  ) : (
                                    <div className={`flex items-center ${(column.className || '').includes('text-right') ? 'justify-end' : (column.className || '').includes('text-center') ? 'justify-center' : ''}`}>
                                    <span
                                      className={`inline-flex items-center gap-1 ${isSortable ? 'group cursor-pointer select-none rounded px-1 hover:bg-muted/50' : ''}`}
                                      onClick={isSortable ? (e) => { e.stopPropagation(); onSort(column.key); } : undefined}
                                    >
                                      <span>{column.label}</span>
                                      {isSortable && (
                                        <span className="text-muted-foreground">
                                          {isCurrentSort ? (
                                            sortOrder === 'asc' ? (
                                              <ArrowUp className="h-4 w-4" />
                                            ) : (
                                              <ArrowDown className="h-4 w-4" />
                                            )
                                          ) : (
                                            <ArrowUpDown className="h-4 w-4 opacity-0 group-hover:opacity-50" />
                                          )}
                                        </span>
                                      )}
                                    </span>
                                    </div>
                                  )}
                                  {columnSettings && (
                                    <div
                                      className="absolute right-0 top-0 bottom-0 w-1 cursor-col-resize hover:bg-blue-400 active:bg-blue-500 z-10"
                                      onMouseDown={(e) => {
                                        e.stopPropagation();
                                        e.preventDefault();
                                        const th = (e.target as HTMLElement).parentElement;
                                        if (!th) return;

                                        // 첫 리사이즈 시 모든 컬럼의 현재 너비를 스냅샷 저장
                                        // → table-auto → table-fixed 전환 시 다른 컬럼 크기 유지
                                        if (Object.keys(columnSettings.columnWidths).length === 0) {
                                          const headerRow = th.parentElement;
                                          if (headerRow) {
                                            const allThs = headerRow.querySelectorAll('th');
                                            const offset = showCheckbox ? 1 : 0;
                                            allThs.forEach((cell, idx) => {
                                              if (idx < offset) return;
                                              const colKey = tableColumns[idx - offset]?.key;
                                              if (colKey) {
                                                columnSettings.onColumnResize(colKey, cell.offsetWidth);
                                              }
                                            });
                                          }
                                        }

                                        const startX = e.clientX;
                                        const startWidth = th.offsetWidth;
                                        const onMouseMove = (ev: MouseEvent) => {
                                          const newWidth = Math.max(40, startWidth + ev.clientX - startX);
                                          columnSettings.onColumnResize(column.key, newWidth);
                                        };
                                        const onMouseUp = () => {
                                          document.removeEventListener('mousemove', onMouseMove);
                                          document.removeEventListener('mouseup', onMouseUp);
                                          document.body.style.cursor = '';
                                          document.body.style.userSelect = '';
                                        };
                                        document.body.style.cursor = 'col-resize';
                                        document.body.style.userSelect = 'none';
                                        document.addEventListener('mousemove', onMouseMove);
                                        document.addEventListener('mouseup', onMouseUp);
                                      }}
                                    />
                                  )}
                                </TableHead>
                              );
                            })}
                          </>
                        )}
                      </TableRow>
                    </TableHeader>
                    <TableBody className="[&_tr]:h-14 [&_tr]:min-h-[56px] [&_tr]:max-h-[56px]">
                      {data.length === 0 ? (
                        <TableRow>
                          <TableCell
                            colSpan={tableColumns.length + (showCheckbox ? 1 : 0)}
                            className="h-24 text-center"
                          >
                            검색 결과가 없습니다.
                          </TableCell>
                        </TableRow>
                      ) : (
                        // 백엔드가 created_at ASC로 정렬해서 보내줌 (오래된 순)
                        data.map((item, index) => {
                          const itemId = getItemId(item);
                          // 순차 번호: startIndex 기준으로 1부터 시작
                          const globalIndex = startIndex + index + 1;
                          return (
                            <Fragment key={itemId}>
                              {applyCopyableCells(renderTableRow(item, index, globalIndex), item)}
                            </Fragment>
                          );
                        })
                      )}
                    </TableBody>
                    {tableFooter && (
                      <TableFooter>
                        {tableFooter}
                      </TableFooter>
                    )}
                  </Table>
                  )}
                </div>
              </TabsContent>
            ))}
          </Tabs>
        </CardContent>
      </Card>

      {/* 페이지네이션 - 데스크톱에서만 표시 (1페이지여도 항상 표시) */}
      <div className="hidden xl:flex items-center justify-between">
        <div className="text-sm text-muted-foreground">
          전체 {pagination.totalItems}개 중 {pagination.totalItems > 0 ? startIndex + 1 : 0}-{Math.min(startIndex + pagination.itemsPerPage, pagination.totalItems)}개 표시
        </div>
        <div className="flex items-center gap-2">
          <Button
            variant="outline"
            size="sm"
            onClick={() => pagination.onPageChange(pagination.currentPage - 1)}
            disabled={pagination.currentPage === 1}
          >
            이전
          </Button>
          <div className="flex items-center gap-1">
            {Array.from({ length: pagination.totalPages }, (_, i) => i + 1).map((page) => {
              // 현재 페이지 근처만 표시
              if (
                page === 1 ||
                page === pagination.totalPages ||
                (page >= pagination.currentPage - 2 && page <= pagination.currentPage + 2)
              ) {
                return (
                  <Button
                    key={page}
                    variant={page === pagination.currentPage ? "default" : "outline"}
                    size="sm"
                    onClick={() => pagination.onPageChange(page)}
                    className="min-w-[36px]"
                  >
                    {page}
                  </Button>
                );
              } else if (
                page === pagination.currentPage - 3 ||
                page === pagination.currentPage + 3
              ) {
                return <span key={page} className="px-2">...</span>;
              }
              return null;
            })}
          </div>
          <Button
            variant="outline"
            size="sm"
            onClick={() => pagination.onPageChange(pagination.currentPage + 1)}
            disabled={pagination.currentPage === pagination.totalPages}
          >
            다음
          </Button>
        </div>
      </div>

      {/* 테이블 뒤 컨텐츠 (캘린더 등) */}
      {afterTableContent && (
        <div className="mt-4">{afterTableContent}</div>
      )}

      {/* 일괄 삭제 확인 다이얼로그 */}
      <DeleteConfirmDialog
        open={showDeleteDialog}
        onOpenChange={setShowDeleteDialog}
        onConfirm={handleConfirmDelete}
        description={
          <>
            선택한 <strong>{selectedItems.size}개</strong>의 항목을 삭제하시겠습니까?
            <br />
            <span className="text-muted-foreground text-sm">
              삭제된 항목은 복구할 수 없습니다. 관련된 데이터도 함께 삭제될 수 있습니다.
            </span>
          </>
        }
      />
    </PageLayout>
  );
}

// 필터 관련 타입 재export (다른 페이지에서 사용 가능)
export type { FilterFieldConfig, FilterValues } from "@/components/molecules/MobileFilter";