"use client";

import React, { useState, useEffect, useCallback, useRef, useMemo } from "react";
import {
  BarChart, Bar, LineChart, Line, AreaChart, Area,
  XAxis, YAxis, CartesianGrid, Tooltip, Legend, ResponsiveContainer,
} from "recharts";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { Badge } from "@/components/ui/badge";
import {
  Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter,
} from "@/components/ui/dialog";
import { ScrollToTop } from "@/components/common/ScrollToTop";
import { cn } from "@/lib/utils";
import { apiClient } from "@/lib/api/client";

// ============================================
// 공통 상수
// ============================================
const COLORS = [
  "#3b82f6", "#10b981", "#f59e0b", "#ef4444", "#8b5cf6",
  "#ec4899", "#06b6d4", "#84cc16", "#f97316", "#14b8a6",
];

function ChartTooltip({ active, payload, label }: any) {
  if (!active || !payload?.length) return null;
  return (
    <div className="rounded-lg border border-border bg-popover px-3 py-2 shadow-lg">
      <p className="mb-1.5 text-xs font-semibold text-popover-foreground">{label}</p>
      {payload.map((entry: any, i: number) => (
        <div key={i} className="flex items-center gap-2 text-xs">
          <span className="h-2.5 w-2.5 shrink-0 rounded-sm" style={{ background: entry.color }} />
          <span className="text-muted-foreground">{entry.name}:</span>
          <span className="font-medium tabular-nums text-popover-foreground">
            {typeof entry.value === "number" ? entry.value.toLocaleString() : entry.value}
          </span>
        </div>
      ))}
    </div>
  );
}

const AGG_METHODS = [
  { id: "sum", name: "합계 (SUM)" },
  { id: "avg", name: "평균 (AVG)" },
  { id: "max", name: "최대 (MAX)" },
  { id: "min", name: "최소 (MIN)" },
  { id: "count", name: "건수 (COUNT)" },
];

const CHART_TYPES = [
  { id: "bar", name: "막대" },
  { id: "line", name: "선" },
  { id: "area", name: "영역" },
];

const DATE_PRESETS = [
  { id: "today", name: "오늘" },
  { id: "week", name: "이번주" },
  { id: "month", name: "이번달" },
  { id: "quarter", name: "이번분기" },
  { id: "year", name: "올해" },
  { id: "prevMonth", name: "전월" },
  { id: "last3m", name: "최근3개월" },
  { id: "last6m", name: "최근6개월" },
];

const FILTER_OPERATORS: Record<string, { id: string; name: string }[]> = {
  select: [
    { id: "eq", name: "=" },
    { id: "neq", name: "≠" },
    { id: "in", name: "포함(IN)" },
  ],
  number: [
    { id: "eq", name: "=" },
    { id: "neq", name: "≠" },
    { id: "gt", name: ">" },
    { id: "gte", name: "≥" },
    { id: "lt", name: "<" },
    { id: "lte", name: "≤" },
  ],
};

// ============================================
// 타입 정의 (외부 export)
// ============================================
export interface ReportMetric {
  id: string;
  name: string;
  unit: string;
  color: string;
  isRate?: boolean;
}

export interface ReportGroupByOption {
  id: string;
  name: string;
  /** 복합 그룹인 경우 파트 이름들 (예: ["거래처", "사이즈"]). 1차 그룹 선택 드롭다운에 사용 */
  parts?: string[];
}

/**
 * 자유 조합 그룹핑에서 사용할 단위 필드
 * 사용자가 이 필드들을 자유롭게 선택/조합해서 그룹 조건을 만들 수 있음
 */
export interface ReportGroupableField {
  id: string; // getGroupKey에 넘겨질 id (예: "customer", "size", "thickness", "monthly")
  name: string; // 화면 표시명 (예: "거래처")
}

export interface ReportThreshold {
  id: string;
  label: string;
  defaultValue: number;
  unit: string;
}

export interface ReportColumnDef {
  id: string;
  name: string;
  align?: "left" | "right";
  format?: "number" | "text" | "badge" | "date";
}

export interface FilterFieldDef {
  id: string;
  name: string;
  type: "select" | "number";
  optionKey?: string;
}

export interface ReportConfig {
  key: string;
  title: string;
  description: string;
  apiEndpoint: string;
  metrics: ReportMetric[];
  groupByOptions: ReportGroupByOption[];
  /** 자유 조합 그룹핑에 쓸 단위 필드 목록 (선택 사항) */
  groupableFields?: ReportGroupableField[];
  defaultGroupBy: string;
  defaultMetrics: string[];
  thresholds: ReportThreshold[];
  filterFieldDefs: FilterFieldDef[];
  drilldownColumns: ReportColumnDef[];
  rawDataColumns: ReportColumnDef[];
  enrichRow?: (row: Record<string, any>) => Record<string, any>;
  emptyMessage: string;
}

interface ConditionFilter {
  id: number;
  logic: "AND" | "OR" | "";
  field: string;
  operator: string;
  value: string;
  values: string[];
}

interface ConditionGroup {
  id: number;
  name: string;
  metrics: string[];
  aggMethod: string;
  chartType: string;
  collapsed: boolean;
  filters: ConditionFilter[];
}

interface FilterField {
  id: string;
  name: string;
  type: "select" | "number";
  options: { value: string; label: string }[];
}

interface Preset {
  id: number;
  name: string;
  description: string | null;
  config: {
    groupBy: string;
    startDate: string;
    endDate: string;
    conditions: ConditionGroup[];
  };
  created_by?: string | null;
  created_at?: string;
  updated_at?: string;
}

// ============================================
// 유틸 함수
// ============================================
function getDatePresetRange(preset: string): { start: string; end: string } {
  const today = new Date();
  let s = new Date(today);
  let e = new Date(today);

  switch (preset) {
    case "today":
      break;
    case "week":
      s.setDate(today.getDate() - today.getDay());
      e.setDate(s.getDate() + 6);
      break;
    case "month":
      s.setDate(1);
      e = new Date(today.getFullYear(), today.getMonth() + 1, 0);
      break;
    case "quarter": {
      const q = Math.floor(today.getMonth() / 3);
      s = new Date(today.getFullYear(), q * 3, 1);
      e = new Date(today.getFullYear(), q * 3 + 3, 0);
      break;
    }
    case "year":
      s = new Date(today.getFullYear(), 0, 1);
      e = new Date(today.getFullYear(), 11, 31);
      break;
    case "prevMonth":
      s = new Date(today.getFullYear(), today.getMonth() - 1, 1);
      e = new Date(today.getFullYear(), today.getMonth(), 0);
      break;
    case "last3m":
      s = new Date(today.getFullYear(), today.getMonth() - 3, today.getDate());
      break;
    case "last6m":
      s = new Date(today.getFullYear(), today.getMonth() - 6, today.getDate());
      break;
  }

  return {
    start: s.toISOString().split("T")[0],
    end: e.toISOString().split("T")[0],
  };
}

function getGroupKey(row: Record<string, any>, groupBy: string): string {
  const dateStr = row.date || "";
  switch (groupBy) {
    case "daily":
      return dateStr.substring(0, 10) || "미지정";
    case "weekly": {
      if (!dateStr) return "미지정";
      const dt = new Date(dateStr);
      const weekNum = Math.ceil(
        ((dt.getTime() - new Date(dt.getFullYear(), 0, 1).getTime()) / 86400000 +
          new Date(dt.getFullYear(), 0, 1).getDay() + 1) / 7
      );
      return `${dt.getFullYear()}-W${String(weekNum).padStart(2, "0")}`;
    }
    case "monthly":
      return dateStr.substring(0, 7) || "미지정";
    case "quarterly": {
      if (!dateStr) return "미지정";
      const m = parseInt(dateStr.substring(5, 7));
      return `${dateStr.substring(0, 4)}-Q${Math.ceil(m / 3)}`;
    }
    case "thickness": {
      const t = row.thickness;
      if (!t || t === "" || t === "0") return "미지정";
      return `${t}T`;
    }
    case "sizeRange": {
      const area = parseFloat(row.areaSingle) || 0;
      if (area <= 0) return "미지정";
      if (area < 1) return "소형 (1㎡ 미만)";
      if (area < 3) return "중형 (1-3㎡)";
      if (area < 6) return "대형 (3-6㎡)";
      return "특대형 (6㎡ 이상)";
    }
    case "size": {
      const w = row.width, h = row.height;
      if (!w || !h) return "미지정";
      return `${w}×${h}`;
    }
    case "itemSize": {
      const item = row.item || "미지정";
      const w = row.width, h = row.height;
      const sizeStr = w && h ? `${w}×${h}` : "미지정";
      return `${item} / ${sizeStr}`;
    }
    case "customerSize": {
      const customer = row.customer || "미지정";
      const w = row.width, h = row.height;
      const sizeStr = w && h ? `${w}×${h}` : "미지정";
      return `${customer} / ${sizeStr}`;
    }
    case "customerItem": {
      const customer = row.customer || "미지정";
      const item = row.item || "미지정";
      return `${customer} / ${item}`;
    }
    case "customerItemSize": {
      const customer = row.customer || "미지정";
      const item = row.item || "미지정";
      const w = row.width, h = row.height;
      const sizeStr = w && h ? `${w}×${h}` : "미지정";
      return `${customer} / ${item} / ${sizeStr}`;
    }
    default:
      return row[groupBy] || "미지정";
  }
}

function aggregateValues(
  rows: Record<string, any>[],
  metricId: string,
  method: string
): number {
  if (!rows.length) return 0;
  const vals = rows.map((r) => Number(r[metricId]) || 0);
  switch (method) {
    case "sum": return vals.reduce((a, b) => a + b, 0);
    case "avg": return Math.round((vals.reduce((a, b) => a + b, 0) / vals.length) * 10) / 10;
    case "max": return Math.max(...vals);
    case "min": return Math.min(...vals);
    case "count": return rows.length;
    default: return vals.reduce((a, b) => a + b, 0);
  }
}

function evalFilter(
  rec: Record<string, any>,
  f: ConditionFilter,
  filterFields: FilterField[]
): boolean {
  const field = filterFields.find((x) => x.id === f.field);
  if (!field) return true;

  const rv = rec[f.field];

  if (field.type === "select") {
    switch (f.operator) {
      case "eq": return !f.value || rv === f.value;
      case "neq": return !f.value || rv !== f.value;
      case "in": return f.values.length === 0 || f.values.includes(rv);
    }
  } else {
    const nv = parseFloat(rv) || 0;
    const cv = parseFloat(f.value) || 0;
    switch (f.operator) {
      case "eq": return !f.value || nv === cv;
      case "neq": return !f.value || nv !== cv;
      case "gt": return !f.value || nv > cv;
      case "gte": return !f.value || nv >= cv;
      case "lt": return !f.value || nv < cv;
      case "lte": return !f.value || nv <= cv;
    }
  }
  return true;
}

function applyConditionFilters(
  data: Record<string, any>[],
  filters: ConditionFilter[],
  filterFields: FilterField[]
): Record<string, any>[] {
  if (!filters.length) return data;
  return data.filter((d) => {
    let res = evalFilter(d, filters[0], filterFields);
    for (let i = 1; i < filters.length; i++) {
      const v = evalFilter(d, filters[i], filterFields);
      res = filters[i].logic === "OR" ? res || v : res && v;
    }
    return res;
  });
}

function formatNumber(n: number): string {
  return n.toLocaleString("ko-KR");
}

function renderCellValue(row: Record<string, any>, col: ReportColumnDef): React.ReactNode {
  const val = row[col.id];
  switch (col.format) {
    case "number":
      return formatNumber(Number(val) || 0);
    case "date":
      return String(val || "").substring(0, 10);
    case "badge":
      return <Badge variant="outline">{val || "-"}</Badge>;
    default:
      return String(val ?? "");
  }
}

// ============================================
// ReportEngine 컴포넌트
// ============================================
interface ReportEngineProps {
  config: ReportConfig;
}

export default function ReportEngine({ config }: ReportEngineProps) {
  const [rawData, setRawData] = useState<Record<string, any>[]>([]);
  const [filterOptions, setFilterOptions] = useState<Record<string, { value: string; label: string }[]>>({});
  const [isLoading, setIsLoading] = useState(false);

  const [groupBy, setGroupBy] = useState(config.defaultGroupBy);
  // 자유 조합 그룹핑: 기본 groupBy 뒤에 이어 붙일 추가 그룹 필드들 (순서대로)
  const [extraGroupBys, setExtraGroupBys] = useState<string[]>([]);
  const [startDate, setStartDate] = useState("");
  const [endDate, setEndDate] = useState("");
  const [activePreset, setActivePreset] = useState("last1m");
  const [filterOpen, setFilterOpen] = useState(true);

  const [conditions, setConditions] = useState<ConditionGroup[]>([]);
  const condIdRef = useRef(0);
  const filterIdRef = useRef(0);

  const [viewMode, setViewMode] = useState<"table" | "card">("table");
  const [drilldownLabel, setDrilldownLabel] = useState<string | null>(null);
  const [rawDataOpen, setRawDataOpen] = useState(false);
  // 집계 테이블 검색/정렬
  const [tableSearchQuery, setTableSearchQuery] = useState("");
  const [tableSortColumn, setTableSortColumn] = useState<string | null>(null);
  const [tableSortDirection, setTableSortDirection] = useState<"asc" | "desc">("desc");
  // 집계 테이블 그룹핑 (1차 그룹으로 묶기)
  const [tableGrouped, setTableGrouped] = useState(false);
  const [collapsedGroups, setCollapsedGroups] = useState<Set<string>>(new Set());
  const [primaryGroupIndex, setPrimaryGroupIndex] = useState(0); // 현재 그룹화의 어떤 파트를 1차 기준으로 쓸지

  const [presets, setPresets] = useState<Preset[]>([]);
  const [presetModalOpen, setPresetModalOpen] = useState(false);
  const [presetMode, setPresetMode] = useState<"new" | "update">("new");
  const [presetName, setPresetName] = useState("");
  const [presetDesc, setPresetDesc] = useState("");
  const [selectedPresetId, setSelectedPresetId] = useState<string>("");

  const [thresholdValues, setThresholdValues] = useState<Record<string, number>>(() => {
    const defaults: Record<string, number> = {};
    config.thresholds.forEach((t) => { defaults[t.id] = t.defaultValue; });
    return defaults;
  });

  const [refreshInterval, setRefreshInterval] = useState(0);
  const refreshTimerRef = useRef<ReturnType<typeof setInterval> | null>(null);

  const filterFields: FilterField[] = useMemo(
    () =>
      config.filterFieldDefs.map((def) => ({
        id: def.id,
        name: def.name,
        type: def.type,
        options: def.type === "select" && def.optionKey
          ? filterOptions[def.optionKey] || []
          : [],
      })),
    [config.filterFieldDefs, filterOptions]
  );

  const aggLabel = (method: string) =>
    ({ sum: "합계", avg: "평균", max: "최대", min: "최소", count: "건수" }[method] || "합계");

  // ============================================
  // 초기화
  // ============================================
  useEffect(() => {
    const range = getDatePresetRange("last1m");
    setStartDate(range.start);
    setEndDate(range.end);

    condIdRef.current = 1;
    setConditions([
      {
        id: 1,
        name: "조건 1",
        metrics: [...config.defaultMetrics],
        aggMethod: "sum",
        chartType: "bar",
        collapsed: false,
        filters: [],
      },
    ]);

    loadPresets();
  }, []);

  useEffect(() => {
    if (startDate && endDate) {
      fetchData();
    }
  }, [startDate, endDate]);

  useEffect(() => {
    if (refreshTimerRef.current) clearInterval(refreshTimerRef.current);
    if (refreshInterval > 0) {
      refreshTimerRef.current = setInterval(fetchData, refreshInterval * 1000);
    }
    return () => {
      if (refreshTimerRef.current) clearInterval(refreshTimerRef.current);
    };
  }, [refreshInterval]);

  // ============================================
  // API 호출
  // ============================================
  const fetchData = useCallback(async () => {
    setIsLoading(true);
    try {
      const params = new URLSearchParams();
      if (startDate) params.set("startDate", startDate);
      if (endDate) params.set("endDate", endDate);

      const response = await apiClient.get(
        `${config.apiEndpoint}?${params.toString()}`
      );

      if (response.data?.success) {
        const { rows, filterOptions: opts } = response.data.data;
        let processedRows = rows.map((r: any) => {
          const numericRow: Record<string, any> = { ...r };
          config.metrics.forEach((m) => {
            if (numericRow[m.id] !== undefined) {
              numericRow[m.id] = Number(numericRow[m.id]) || 0;
            }
          });
          return numericRow;
        });

        if (config.enrichRow) {
          processedRows = processedRows.map(config.enrichRow);
        }

        setRawData(processedRows);
        setFilterOptions(opts || {});
      }
    } catch (err) {
      console.error("데이터 조회 실패:", err);
    } finally {
      setIsLoading(false);
    }
  }, [startDate, endDate, config.apiEndpoint, config.enrichRow, config.metrics]);

  // ============================================
  // 조건 관리
  // ============================================
  const addCondition = () => {
    condIdRef.current++;
    setConditions((prev) => [
      ...prev,
      {
        id: condIdRef.current,
        name: `조건 ${condIdRef.current}`,
        metrics: [...config.defaultMetrics],
        aggMethod: "sum",
        chartType: "bar",
        collapsed: false,
        filters: [],
      },
    ]);
  };

  const removeCondition = (id: number) => {
    if (conditions.length <= 1) return;
    setConditions((prev) => prev.filter((c) => c.id !== id));
  };

  const duplicateCondition = (id: number) => {
    const src = conditions.find((c) => c.id === id);
    if (!src) return;
    condIdRef.current++;
    setConditions((prev) => [
      ...prev,
      { ...JSON.parse(JSON.stringify(src)), id: condIdRef.current, name: src.name + " (복사)" },
    ]);
  };

  const updateCondition = (id: number, updates: Partial<ConditionGroup>) => {
    setConditions((prev) =>
      prev.map((c) => (c.id === id ? { ...c, ...updates } : c))
    );
  };

  const toggleMetric = (condId: number, metricId: string) => {
    setConditions((prev) =>
      prev.map((c) => {
        if (c.id !== condId) return c;
        const idx = c.metrics.indexOf(metricId);
        if (idx > -1) {
          if (c.metrics.length <= 1) return c;
          return { ...c, metrics: c.metrics.filter((m) => m !== metricId) };
        }
        return { ...c, metrics: [...c.metrics, metricId] };
      })
    );
  };

  // 메트릭 순서 변경 (왼쪽/오른쪽)
  const moveMetric = (condId: number, metricId: string, dir: -1 | 1) => {
    setConditions((prev) =>
      prev.map((c) => {
        if (c.id !== condId) return c;
        const idx = c.metrics.indexOf(metricId);
        if (idx === -1) return c;
        const newIdx = idx + dir;
        if (newIdx < 0 || newIdx >= c.metrics.length) return c;
        const next = [...c.metrics];
        [next[idx], next[newIdx]] = [next[newIdx], next[idx]];
        return { ...c, metrics: next };
      })
    );
  };

  const addFilter = (condId: number) => {
    filterIdRef.current++;
    const firstField = config.filterFieldDefs[0]?.id || "";
    setConditions((prev) =>
      prev.map((c) => {
        if (c.id !== condId) return c;
        return {
          ...c,
          filters: [
            ...c.filters,
            {
              id: filterIdRef.current,
              logic: c.filters.length === 0 ? "" : "AND",
              field: firstField,
              operator: "eq",
              value: "",
              values: [],
            },
          ],
        };
      })
    );
  };

  const removeFilter = (condId: number, filterId: number) => {
    setConditions((prev) =>
      prev.map((c) => {
        if (c.id !== condId) return c;
        const newFilters = c.filters.filter((f) => f.id !== filterId);
        if (newFilters.length > 0) newFilters[0].logic = "";
        return { ...c, filters: newFilters };
      })
    );
  };

  const updateFilter = (
    condId: number,
    filterId: number,
    updates: Partial<ConditionFilter>
  ) => {
    setConditions((prev) =>
      prev.map((c) => {
        if (c.id !== condId) return c;
        return {
          ...c,
          filters: c.filters.map((f) =>
            f.id === filterId ? { ...f, ...updates } : f
          ),
        };
      })
    );
  };

  // ============================================
  // 날짜 프리셋
  // ============================================
  const handleDatePreset = (preset: string) => {
    setActivePreset(preset);
    const range = getDatePresetRange(preset);
    setStartDate(range.start);
    setEndDate(range.end);
  };

  // ============================================
  // 프리셋 저장/불러오기 (DB 기반 - company_code + report_key)
  // ============================================
  const loadPresets = async () => {
    try {
      const res = await apiClient.get(
        `/report-presets?reportKey=${encodeURIComponent(config.key)}`
      );
      if (res.data?.success) {
        setPresets(res.data.data || []);
      }
    } catch (err) {
      console.error("프리셋 목록 조회 실패", err);
    }
  };

  // 저장 모달 열기 - 신규
  const openNewPresetModal = () => {
    setPresetMode("new");
    setPresetName("");
    setPresetDesc("");
    setPresetModalOpen(true);
  };

  // 저장 모달 열기 - 선택된 프리셋 수정
  const openUpdatePresetModal = () => {
    if (!selectedPresetId) return;
    const p = presets.find((x) => String(x.id) === selectedPresetId);
    if (!p) return;
    setPresetMode("update");
    setPresetName(p.name);
    setPresetDesc(p.description || "");
    setPresetModalOpen(true);
  };

  // 모달 저장 - mode 에 따라 POST(신규) / PUT(수정) 분기
  const savePreset = async () => {
    if (!presetName.trim()) return;
    const body = {
      name: presetName.trim(),
      description: presetDesc.trim() || null,
      config: { groupBy, startDate, endDate, conditions },
    };
    try {
      if (presetMode === "update" && selectedPresetId) {
        const res = await apiClient.put(`/report-presets/${selectedPresetId}`, body);
        if (res.data?.success) {
          setPresets((prev) =>
            prev.map((x) => (String(x.id) === selectedPresetId ? res.data.data : x))
          );
          setPresetModalOpen(false);
          setPresetName("");
          setPresetDesc("");
        }
      } else {
        const res = await apiClient.post(`/report-presets`, {
          reportKey: config.key,
          ...body,
        });
        if (res.data?.success) {
          setPresets((prev) => [res.data.data, ...prev]);
          setSelectedPresetId(String(res.data.data.id));
          setPresetModalOpen(false);
          setPresetName("");
          setPresetDesc("");
        }
      }
    } catch (err) {
      console.error("프리셋 저장 실패", err);
    }
  };

  const loadSelectedPreset = (id: string) => {
    setSelectedPresetId(id);
    if (id === "") return;
    const p = presets.find((x) => String(x.id) === id);
    if (!p?.config) return;
    setGroupBy(p.config.groupBy);
    setStartDate(p.config.startDate);
    setEndDate(p.config.endDate);
    setConditions(p.config.conditions);
  };

  const deletePreset = async () => {
    if (selectedPresetId === "") return;
    try {
      const res = await apiClient.delete(`/report-presets/${selectedPresetId}`);
      if (res.data?.success) {
        setPresets((prev) => prev.filter((p) => String(p.id) !== selectedPresetId));
        setSelectedPresetId("");
      }
    } catch (err) {
      console.error("프리셋 삭제 실패", err);
    }
  };

  // ============================================
  // 데이터 분석 (클라이언트 사이드)
  // ============================================
  const analysisResult = useMemo(() => {
    if (!rawData.length) return { series: [], labels: [], chartData: [] };

    const seriesList: {
      condId: number;
      condName: string;
      condIdx: number;
      metricId: string;
      metricName: string;
      metricUnit: string;
      aggMethod: string;
      chartType: string;
      groups: Record<string, Record<string, any>[]>;
      values: Record<string, number>; // 미리 계산된 집계값 (O(1) 조회)
      totalValue: number; // 전체 합계(또는 선택된 집계)
    }[] = [];

    const allLabelsSet = new Set<string>();

    // 각 condition의 filter는 cache (같은 filter면 재사용)
    const condFilterCache = new Map<string, any[]>();

    conditions.forEach((cond, ci) => {
      // filter 결과 캐싱 (같은 필터면 재사용)
      const filterKey = JSON.stringify(cond.filters);
      let condData = condFilterCache.get(filterKey);
      if (!condData) {
        condData = applyConditionFilters(rawData, cond.filters, filterFields);
        condFilterCache.set(filterKey, condData);
      }

      // 그룹핑 (1회) — 기본 groupBy + extraGroupBys 조합
      const allGroupBys = [groupBy, ...extraGroupBys];
      const groups: Record<string, Record<string, any>[]> = {};
      for (let i = 0; i < condData.length; i++) {
        const d = condData[i];
        const keyParts = allGroupBys.map((g) => getGroupKey(d, g));
        const key = keyParts.join(" / ");
        if (!groups[key]) groups[key] = [];
        groups[key].push(d);
      }
      // Set 에 라벨 추가
      for (const k in groups) allLabelsSet.add(k);

      cond.metrics.forEach((metricId) => {
        const m = config.metrics.find((x) => x.id === metricId);
        if (!m) return;
        // 각 라벨별 집계값을 한 번에 계산해서 저장 (렌더링 시 lookup 만)
        const values: Record<string, number> = {};
        for (const lb in groups) {
          values[lb] = aggregateValues(groups[lb], metricId, cond.aggMethod);
        }
        // 전체 합계
        const totalValue = aggregateValues(condData, metricId, cond.aggMethod);
        seriesList.push({
          condId: cond.id,
          condName: cond.name,
          condIdx: ci,
          metricId,
          metricName: m.name,
          metricUnit: m.unit,
          aggMethod: cond.aggMethod,
          chartType: cond.chartType,
          groups,
          values,
          totalValue,
        });
      });
    });

    const isTimeBased = ["monthly", "quarterly", "weekly", "daily"].includes(groupBy);
    let labels = [...allLabelsSet];

    if (isTimeBased) {
      labels.sort((a, b) => a.localeCompare(b));
    } else if (seriesList.length > 0) {
      // 다단계 정렬: 미리 계산된 values 사용 (O(1) 조회)
      labels.sort((a, b) => {
        for (const s of seriesList) {
          const va = s.values[a] || 0;
          const vb = s.values[b] || 0;
          // 0은 하단으로: 한쪽만 0이면 0 아닌 쪽 우선
          if (va === 0 && vb !== 0) return 1;
          if (va !== 0 && vb === 0) return -1;
          if (vb !== va) return vb - va; // 큰 값 먼저
        }
        // 모든 메트릭 동일 시 라벨 자연 정렬
        return a.localeCompare(b, undefined, { numeric: true, sensitivity: "base" });
      });
    }

    // 차트 렌더링 성능 보호: 그룹이 너무 많으면 상위 N개만 차트에 표시
    // (테이블/드릴다운은 전체 labels 유지 — 집계 계산은 그대로)
    const CHART_MAX_LABELS = 30;
    const chartLabels = !isTimeBased && labels.length > CHART_MAX_LABELS
      ? labels.slice(0, CHART_MAX_LABELS)
      : labels;

    const chartData = chartLabels.map((label) => {
      const point: Record<string, any> = { name: label };
      seriesList.forEach((s) => {
        const key = `${s.condName}_${s.metricName}`;
        point[key] = s.values[label] || 0;
      });
      return point;
    });

    return { series: seriesList, labels, chartData };
  }, [rawData, conditions, groupBy, extraGroupBys, filterFields, config.metrics]);

  // 집계 테이블 표시용 라벨 (검색 + 헤더 정렬 적용) — 미리 계산된 values 사용
  const displayLabels = useMemo(() => {
    let list = analysisResult.labels;
    if (tableSearchQuery) {
      const q = tableSearchQuery.toLowerCase();
      list = list.filter((l) => l.toLowerCase().includes(q));
    }
    if (tableSortColumn !== null) {
      list = [...list].sort((a, b) => {
        if (tableSortColumn === "__label__") {
          const cmp = a.localeCompare(b, undefined, { numeric: true, sensitivity: "base" });
          return tableSortDirection === "asc" ? cmp : -cmp;
        }
        const si = Number(tableSortColumn);
        const s = analysisResult.series[si];
        if (!s) return 0;
        const va = s.values[a] || 0;
        const vb = s.values[b] || 0;
        return tableSortDirection === "asc" ? va - vb : vb - va;
      });
    }
    return list;
  }, [analysisResult, tableSearchQuery, tableSortColumn, tableSortDirection]);

  const toggleTableSort = (col: string) => {
    if (tableSortColumn === col) {
      setTableSortDirection((d) => (d === "asc" ? "desc" : "asc"));
    } else {
      setTableSortColumn(col);
      setTableSortDirection("desc");
    }
  };

  // 현재 그룹화 옵션의 parts 메타 (기본 groupBy + extraGroupBys 조합)
  const currentGroupParts = useMemo(() => {
    const getPartName = (id: string): string => {
      const gf = config.groupableFields?.find((f) => f.id === id);
      if (gf) return gf.name;
      const go = config.groupByOptions.find((o) => o.id === id);
      if (go) return go.name.replace(/별$/, "");
      return id;
    };
    if (extraGroupBys.length === 0) {
      // 기본 그룹의 parts 메타 (예전 복합 옵션) 호환
      const opt = config.groupByOptions.find((o) => o.id === groupBy);
      if (opt?.parts) return opt.parts;
      return [];
    }
    return [groupBy, ...extraGroupBys].map(getPartName);
  }, [config.groupByOptions, config.groupableFields, groupBy, extraGroupBys]);

  // 그룹핑 가능 여부 (복합 그룹일 때만): parts가 2개 이상
  const canGroupTable = currentGroupParts.length >= 2;

  // groupBy 변경 시 primaryGroupIndex 초기화
  useEffect(() => {
    if (primaryGroupIndex >= currentGroupParts.length) {
      setPrimaryGroupIndex(0);
    }
  }, [currentGroupParts.length, primaryGroupIndex]);

  // 1차 그룹으로 묶은 결과 (tableGrouped가 true이고 canGroupTable일 때만)
  const groupedLabels = useMemo(() => {
    if (!tableGrouped || !canGroupTable) return null;
    const groups: Record<string, string[]> = {};
    const order: string[] = [];
    for (const label of displayLabels) {
      const parts = label.split(" / ");
      const primary = parts[primaryGroupIndex] ?? parts[0] ?? label;
      if (!groups[primary]) {
        groups[primary] = [];
        order.push(primary);
      }
      groups[primary].push(label);
    }
    return { groups, order };
  }, [displayLabels, tableGrouped, canGroupTable, primaryGroupIndex]);

  const toggleGroupCollapse = (primary: string) => {
    setCollapsedGroups((prev) => {
      const next = new Set(prev);
      if (next.has(primary)) next.delete(primary);
      else next.add(primary);
      return next;
    });
  };

  // ============================================
  // 렌더링
  // ============================================
  return (
    <div className="flex h-full flex-col overflow-auto bg-background">
      <div className="space-y-4 p-4 sm:p-6">
        {/* 헤더 */}
        <div className="flex flex-col gap-3 border-b pb-4 sm:flex-row sm:items-center sm:justify-between">
          <div>
            <h1 className="text-2xl font-bold tracking-tight sm:text-3xl">
              {config.title}
            </h1>
            <p className="text-sm text-muted-foreground">
              {config.description} | 원본 {rawData.length}건
            </p>
          </div>
          <div className="flex flex-wrap items-center gap-2">
            <select
              value={refreshInterval}
              onChange={(e) => setRefreshInterval(Number(e.target.value))}
              className={cn(
                "h-8 rounded-md border px-2 text-xs",
                refreshInterval > 0 && "border-primary bg-primary/5"
              )}
            >
              <option value={0}>자동갱신 OFF</option>
              <option value={30}>30초</option>
              <option value={60}>1분</option>
              <option value={300}>5분</option>
            </select>
            <Button variant="outline" size="sm" onClick={() => window.print()}>
              인쇄
            </Button>
          </div>
        </div>

        {/* 프리셋 바 */}
        <div className="flex flex-wrap items-center gap-2 rounded-md border bg-muted/30 p-3">
          <span className="text-xs font-medium text-muted-foreground">저장된 조건</span>
          <select
            value={selectedPresetId}
            onChange={(e) => loadSelectedPreset(e.target.value)}
            className="h-8 min-w-[180px] rounded-md border px-2 text-xs"
          >
            <option value="">조건을 선택하세요</option>
            {presets.map((p) => (
              <option key={p.id} value={String(p.id)}>{p.name}</option>
            ))}
          </select>
          <Button size="sm" variant="default" className="h-8 text-xs" onClick={openNewPresetModal}>
            신규 저장
          </Button>
          <Button
            size="sm"
            variant="secondary"
            className="h-8 text-xs"
            onClick={openUpdatePresetModal}
            disabled={!selectedPresetId}
            title={selectedPresetId ? "현재 조건을 선택된 프리셋에 덮어쓰기" : "먼저 프리셋을 선택하세요"}
          >
            현재 조건 수정
          </Button>
          <Button
            size="sm"
            variant="outline"
            className="h-8 text-xs"
            onClick={deletePreset}
            disabled={!selectedPresetId}
          >
            삭제
          </Button>
        </div>

        {/* 분석 조건 설정 */}
        <div className="rounded-md border">
          <button
            className="flex w-full items-center justify-between p-3 text-left hover:bg-muted/50"
            onClick={() => setFilterOpen(!filterOpen)}
          >
            <span className="text-sm font-semibold">분석 조건 설정</span>
            <span className={cn("text-xs transition-transform", !filterOpen && "-rotate-90")}>
              ▼
            </span>
          </button>

          {filterOpen && (
            <div className="space-y-4 border-t p-4">
              {/* 기준축 + 기간 */}
              <div className="flex flex-wrap gap-4">
                <div className="space-y-1">
                  <Label className="text-xs">분석 기준 (X축)</Label>
                  <div className="flex flex-wrap items-center gap-1">
                    <select
                      value={groupBy}
                      onChange={(e) => setGroupBy(e.target.value)}
                      className="h-9 w-[160px] rounded-md border px-2 text-sm"
                    >
                      {config.groupByOptions.map((o) => (
                        <option key={o.id} value={o.id}>{o.name}</option>
                      ))}
                    </select>
                    {/* 자유 조합 그룹핑: 추가 그룹 필드 칩 */}
                    {config.groupableFields && config.groupableFields.length > 0 && (
                      <>
                        {extraGroupBys.map((id, idx) => {
                          const f = config.groupableFields!.find((gf) => gf.id === id);
                          return (
                            <span
                              key={`${id}-${idx}`}
                              className="inline-flex items-center gap-1 rounded-md border border-primary/30 bg-primary/10 px-2 py-1 text-xs text-primary"
                            >
                              <span className="text-muted-foreground">+</span>
                              {f?.name || id}
                              <button
                                onClick={() =>
                                  setExtraGroupBys((prev) => prev.filter((_, i) => i !== idx))
                                }
                                className="ml-0.5 text-primary/60 hover:text-destructive"
                                title="제거"
                              >
                                ×
                              </button>
                            </span>
                          );
                        })}
                        <select
                          value=""
                          onChange={(e) => {
                            if (e.target.value) {
                              setExtraGroupBys((prev) => [...prev, e.target.value]);
                            }
                          }}
                          className="h-9 rounded-md border px-2 text-xs text-muted-foreground"
                          title="그룹 조합 추가"
                        >
                          <option value="">+ 그룹 추가</option>
                          {config.groupableFields
                            .filter((f) => f.id !== groupBy && !extraGroupBys.includes(f.id))
                            .map((f) => (
                              <option key={f.id} value={f.id}>
                                {f.name}
                              </option>
                            ))}
                        </select>
                        {extraGroupBys.length > 0 && (
                          <button
                            onClick={() => setExtraGroupBys([])}
                            className="h-9 px-2 rounded-md border text-xs text-muted-foreground hover:text-destructive"
                            title="조합 모두 제거"
                          >
                            초기화
                          </button>
                        )}
                      </>
                    )}
                  </div>
                </div>
                <div className="space-y-1">
                  <Label className="text-xs">기간</Label>
                  <div className="flex items-center gap-2">
                    <Input
                      type="date"
                      value={startDate}
                      onChange={(e) => setStartDate(e.target.value)}
                      className="h-9 w-[150px] text-sm"
                    />
                    <span className="text-muted-foreground">~</span>
                    <Input
                      type="date"
                      value={endDate}
                      onChange={(e) => setEndDate(e.target.value)}
                      className="h-9 w-[150px] text-sm"
                    />
                  </div>
                </div>
              </div>

              {/* 날짜 프리셋 */}
              <div className="flex flex-wrap gap-1.5">
                {DATE_PRESETS.map((p) => (
                  <button
                    key={p.id}
                    onClick={() => handleDatePreset(p.id)}
                    className={cn(
                      "rounded-full border px-3 py-1 text-xs transition-colors",
                      activePreset === p.id
                        ? "border-primary bg-primary text-primary-foreground"
                        : "hover:bg-muted"
                    )}
                  >
                    {p.name}
                  </button>
                ))}
              </div>

              {/* 다중 분석 조건 */}
              <div className="space-y-3">
                <div className="flex items-center justify-between">
                  <span className="text-sm font-medium">분석 조건</span>
                  <Button size="sm" variant="outline" className="h-7 text-xs" onClick={addCondition}>
                    + 조건 추가
                  </Button>
                </div>

                {conditions.map((cond, ci) => {
                  const color = COLORS[ci % COLORS.length];
                  return (
                    <div
                      key={cond.id}
                      className="rounded-md border"
                      style={{ borderLeftColor: color, borderLeftWidth: 3 }}
                    >
                      <div
                        className="flex cursor-pointer items-center justify-between p-3 hover:bg-muted/30"
                        onClick={() => updateCondition(cond.id, { collapsed: !cond.collapsed })}
                      >
                        <div className="flex items-center gap-2">
                          <div className="h-3 w-3 rounded" style={{ background: color }} />
                          <input
                            value={cond.name}
                            onClick={(e) => e.stopPropagation()}
                            onChange={(e) => updateCondition(cond.id, { name: e.target.value })}
                            className="w-[100px] border-b border-transparent bg-transparent text-sm font-medium focus:border-primary focus:outline-none"
                          />
                          <span className="text-xs text-muted-foreground">
                            {cond.metrics.map((id) => config.metrics.find((m) => m.id === id)?.name).join("+")}
                            {" "}{aggLabel(cond.aggMethod)}
                            {" "}{CHART_TYPES.find((t) => t.id === cond.chartType)?.name}
                            {cond.filters.length > 0 && ` | 필터 ${cond.filters.length}개`}
                          </span>
                        </div>
                        <div className="flex items-center gap-1">
                          <button
                            className="rounded p-1 text-xs hover:bg-muted"
                            onClick={(e) => { e.stopPropagation(); duplicateCondition(cond.id); }}
                          >
                            복제
                          </button>
                          <button
                            className="rounded p-1 text-xs text-destructive hover:bg-destructive/10"
                            onClick={(e) => { e.stopPropagation(); removeCondition(cond.id); }}
                          >
                            X
                          </button>
                          <span className={cn("ml-1 text-xs transition-transform", cond.collapsed && "-rotate-90")}>
                            ▼
                          </span>
                        </div>
                      </div>

                      {!cond.collapsed && (
                        <div className="space-y-3 border-t p-3">
                          <div className="space-y-2">
                            <span className="text-xs text-muted-foreground">데이터 (선택 순서 = 표시/정렬 기준)</span>
                            {/* 선택된 메트릭: 순서대로 + 이동 버튼 */}
                            {cond.metrics.length > 0 && (
                              <div className="flex flex-wrap gap-1.5 rounded-md border border-primary/20 bg-primary/5 p-2">
                                {cond.metrics.map((mid, idx) => {
                                  const m = config.metrics.find((x) => x.id === mid);
                                  if (!m) return null;
                                  const isFirst = idx === 0;
                                  const isLast = idx === cond.metrics.length - 1;
                                  return (
                                    <div
                                      key={mid}
                                      className="inline-flex items-center gap-1 rounded-full border border-primary bg-background px-2 py-1 text-xs font-medium"
                                    >
                                      <span className="inline-flex h-4 min-w-4 items-center justify-center rounded-full bg-primary px-1 text-[10px] text-primary-foreground">
                                        {idx + 1}
                                      </span>
                                      <span
                                        className="inline-block h-2 w-2 rounded-full"
                                        style={{ background: m.color }}
                                      />
                                      <span>{m.name}</span>
                                      <button
                                        type="button"
                                        onClick={() => moveMetric(cond.id, mid, -1)}
                                        disabled={isFirst}
                                        className={cn(
                                          "text-muted-foreground hover:text-primary ml-0.5",
                                          isFirst && "opacity-30 cursor-not-allowed"
                                        )}
                                        title="왼쪽으로"
                                      >
                                        ◀
                                      </button>
                                      <button
                                        type="button"
                                        onClick={() => moveMetric(cond.id, mid, 1)}
                                        disabled={isLast}
                                        className={cn(
                                          "text-muted-foreground hover:text-primary",
                                          isLast && "opacity-30 cursor-not-allowed"
                                        )}
                                        title="오른쪽으로"
                                      >
                                        ▶
                                      </button>
                                      <button
                                        type="button"
                                        onClick={() => toggleMetric(cond.id, mid)}
                                        disabled={cond.metrics.length <= 1}
                                        className={cn(
                                          "text-muted-foreground hover:text-destructive ml-0.5",
                                          cond.metrics.length <= 1 && "opacity-30 cursor-not-allowed"
                                        )}
                                        title="제거"
                                      >
                                        ×
                                      </button>
                                    </div>
                                  );
                                })}
                              </div>
                            )}
                            {/* 전체 메트릭 목록 */}
                            <div className="flex flex-wrap gap-1.5">
                              {config.metrics.map((m) => {
                                const active = cond.metrics.includes(m.id);
                                return (
                                  <button
                                    key={m.id}
                                    onClick={() => toggleMetric(cond.id, m.id)}
                                    className={cn(
                                      "flex items-center gap-1.5 rounded-full border px-3 py-1 text-xs transition-colors",
                                      active
                                        ? "border-primary bg-primary/10 font-medium"
                                        : "hover:bg-muted"
                                    )}
                                  >
                                    {active && <span className="text-primary">✓</span>}
                                    <span
                                      className="inline-block h-2 w-2 rounded-full"
                                      style={{ background: m.color }}
                                    />
                                    {m.name}
                                  </button>
                                );
                              })}
                            </div>
                          </div>

                          <div className="flex flex-wrap items-center gap-3">
                            <div className="space-y-1">
                              <span className="text-xs text-muted-foreground">집계</span>
                              <select
                                value={cond.aggMethod}
                                onChange={(e) =>
                                  updateCondition(cond.id, { aggMethod: e.target.value })
                                }
                                className="h-8 rounded-md border px-2 text-xs"
                              >
                                {AGG_METHODS.map((a) => (
                                  <option key={a.id} value={a.id}>{a.name}</option>
                                ))}
                              </select>
                            </div>
                            <div className="space-y-1">
                              <span className="text-xs text-muted-foreground">차트</span>
                              <div className="flex gap-1">
                                {CHART_TYPES.map((ct) => (
                                  <button
                                    key={ct.id}
                                    onClick={() => updateCondition(cond.id, { chartType: ct.id })}
                                    className={cn(
                                      "rounded border px-2.5 py-1 text-xs",
                                      cond.chartType === ct.id
                                        ? "border-primary bg-primary text-primary-foreground"
                                        : "hover:bg-muted"
                                    )}
                                  >
                                    {ct.name}
                                  </button>
                                ))}
                              </div>
                            </div>
                          </div>

                          {/* 필터 */}
                          <div className="space-y-2">
                            <span className="text-xs text-muted-foreground">필터</span>
                            {cond.filters.length === 0 && (
                              <p className="text-xs text-muted-foreground/70">전체 데이터 (필터 없음)</p>
                            )}
                            {cond.filters.map((f, fi) => {
                              const field = filterFields.find((x) => x.id === f.field);
                              const ops = FILTER_OPERATORS[field?.type || "select"];
                              return (
                                <div key={f.id} className="flex flex-wrap items-center gap-1.5">
                                  {fi === 0 ? (
                                    <span className="w-[50px] text-center text-[11px] text-muted-foreground">WHERE</span>
                                  ) : (
                                    <select
                                      value={f.logic}
                                      onChange={(e) =>
                                        updateFilter(cond.id, f.id, {
                                          logic: e.target.value as "AND" | "OR",
                                        })
                                      }
                                      className="h-7 w-[50px] rounded border text-[11px]"
                                    >
                                      <option value="AND">AND</option>
                                      <option value="OR">OR</option>
                                    </select>
                                  )}
                                  <select
                                    value={f.field}
                                    onChange={(e) =>
                                      updateFilter(cond.id, f.id, {
                                        field: e.target.value,
                                        operator: "eq",
                                        value: "",
                                        values: [],
                                      })
                                    }
                                    className="h-7 rounded border px-1.5 text-[11px]"
                                  >
                                    {filterFields.map((ff) => (
                                      <option key={ff.id} value={ff.id}>{ff.name}</option>
                                    ))}
                                  </select>
                                  <select
                                    value={f.operator}
                                    onChange={(e) =>
                                      updateFilter(cond.id, f.id, { operator: e.target.value })
                                    }
                                    className="h-7 rounded border px-1.5 text-[11px]"
                                  >
                                    {ops.map((o) => (
                                      <option key={o.id} value={o.id}>{o.name}</option>
                                    ))}
                                  </select>
                                  {field?.type === "select" ? (
                                    <select
                                      value={f.value}
                                      onChange={(e) =>
                                        updateFilter(cond.id, f.id, { value: e.target.value })
                                      }
                                      className="h-7 min-w-[120px] rounded border px-1.5 text-[11px]"
                                    >
                                      <option value="">선택...</option>
                                      {field.options.map((o) => (
                                        <option key={o.value} value={o.label}>{o.label}</option>
                                      ))}
                                    </select>
                                  ) : (
                                    <input
                                      type="number"
                                      value={f.value}
                                      onChange={(e) =>
                                        updateFilter(cond.id, f.id, { value: e.target.value })
                                      }
                                      className="h-7 w-[100px] rounded border px-2 text-[11px]"
                                    />
                                  )}
                                  <button
                                    onClick={() => removeFilter(cond.id, f.id)}
                                    className="rounded p-1 text-xs text-destructive hover:bg-destructive/10"
                                  >
                                    X
                                  </button>
                                </div>
                              );
                            })}
                            <button
                              onClick={() => addFilter(cond.id)}
                              className="text-xs text-primary hover:underline"
                            >
                              + 필터 추가
                            </button>
                          </div>
                        </div>
                      )}
                    </div>
                  );
                })}
              </div>

              {/* 임계값 */}
              {config.thresholds.length > 0 && (
                <div className="flex flex-wrap items-center gap-4 rounded-md border bg-muted/20 p-3">
                  <span className="text-xs font-medium">임계값 설정</span>
                  {config.thresholds.map((t, ti) => (
                    <div key={t.id} className="flex items-center gap-2">
                      <span
                        className="inline-block h-2.5 w-2.5 rounded-full"
                        style={{ background: ti === 0 ? "#ef4444" : "#10b981" }}
                      />
                      <span className="text-xs">{t.label}</span>
                      <Input
                        type="number"
                        value={thresholdValues[t.id] ?? t.defaultValue}
                        onChange={(e) =>
                          setThresholdValues((prev) => ({
                            ...prev,
                            [t.id]: Number(e.target.value),
                          }))
                        }
                        className="h-7 w-[60px] text-xs"
                      />
                      <span className="text-xs">{t.unit}</span>
                    </div>
                  ))}
                </div>
              )}

              {/* 액션 */}
              <div className="flex justify-end gap-2">
                <Button
                  variant="outline"
                  size="sm"
                  onClick={() => {
                    condIdRef.current = 1;
                    setConditions([
                      {
                        id: 1,
                        name: "조건 1",
                        metrics: [...config.defaultMetrics],
                        aggMethod: "sum",
                        chartType: "bar",
                        collapsed: false,
                        filters: [],
                      },
                    ]);
                    setGroupBy(config.defaultGroupBy);
                    handleDatePreset("last6m");
                  }}
                >
                  전체 초기화
                </Button>
                <Button size="sm" onClick={fetchData} disabled={isLoading}>
                  {isLoading ? "분석 중..." : "분석 실행"}
                </Button>
              </div>
            </div>
          )}
        </div>

        {/* 적용된 조건 태그 */}
        {analysisResult.series.length > 0 && (
          <div className="flex flex-wrap gap-1.5">
            <Badge variant="secondary">
              {extraGroupBys.length > 0 && currentGroupParts.length > 0
                ? currentGroupParts.map((p) => `${p}별`).join(" × ")
                : config.groupByOptions.find((o) => o.id === groupBy)?.name}
            </Badge>
            {(startDate || endDate) && (
              <Badge variant="secondary">
                {startDate || "~"} ~ {endDate || "~"}
              </Badge>
            )}
            {conditions.map((cond, ci) => (
              <Badge
                key={cond.id}
                variant="outline"
                style={{ borderColor: COLORS[ci % COLORS.length] + "80" }}
              >
                <span
                  className="mr-1 inline-block h-2 w-2 rounded"
                  style={{ background: COLORS[ci % COLORS.length] }}
                />
                {cond.name}: {cond.metrics.map((id) => config.metrics.find((m) => m.id === id)?.name).join("+")}
                {" "}({aggLabel(cond.aggMethod)})
              </Badge>
            ))}
          </div>
        )}

        {/* KPI 카드 */}
        {analysisResult.series.length > 0 && (
          <div className="grid grid-cols-2 gap-3 sm:grid-cols-3 lg:grid-cols-5">
            {conditions.flatMap((cond, ci) =>
              cond.metrics.map((metricId) => {
                const m = config.metrics.find((x) => x.id === metricId);
                if (!m) return null;
                const condData = applyConditionFilters(rawData, cond.filters, filterFields);
                const val = aggregateValues(condData, metricId, cond.aggMethod);
                const color = COLORS[ci % COLORS.length];
                return (
                  <div
                    key={`${cond.id}-${metricId}`}
                    className="rounded-lg border p-4"
                    style={{ borderTopColor: color, borderTopWidth: 3 }}
                  >
                    <p className="truncate text-xs text-muted-foreground">
                      {cond.name} · {m.name} ({aggLabel(cond.aggMethod)})
                    </p>
                    <p className="mt-1 text-xl font-bold tabular-nums">
                      {formatNumber(val)}
                      <span className="ml-1 text-xs font-normal text-muted-foreground">
                        {cond.aggMethod === "count" ? "건" : m.unit}
                      </span>
                    </p>
                  </div>
                );
              })
            )}
          </div>
        )}

        {/* 차트 */}
        {analysisResult.chartData.length > 0 && (
          <div className="rounded-md border p-4">
            <h3 className="mb-4 text-sm font-semibold">분석 차트</h3>
            <div style={{ width: "100%", height: 400 }}>
              <ResponsiveContainer>
                {(() => {
                  const firstChartType = conditions[0]?.chartType || "bar";
                  const dataKeys = analysisResult.series.map(
                    (s) => `${s.condName}_${s.metricName}`
                  );

                  if (firstChartType === "line") {
                    return (
                      <LineChart data={analysisResult.chartData}>
                        <CartesianGrid strokeDasharray="3 3" className="stroke-border" />
                        <XAxis dataKey="name" tick={{ fontSize: 12 }} className="fill-muted-foreground" />
                        <YAxis tick={{ fontSize: 12 }} className="fill-muted-foreground" tickFormatter={(v) => v.toLocaleString()} />
                        <Tooltip content={<ChartTooltip />} />
                        <Legend wrapperStyle={{ fontSize: 12 }} />
                        {dataKeys.map((key, i) => (
                          <Line
                            key={key}
                            type="monotone"
                            dataKey={key}
                            stroke={COLORS[i % COLORS.length]}
                            strokeWidth={2}
                            dot={{ r: 4 }}
                          />
                        ))}
                      </LineChart>
                    );
                  }

                  if (firstChartType === "area") {
                    return (
                      <AreaChart data={analysisResult.chartData}>
                        <CartesianGrid strokeDasharray="3 3" className="stroke-border" />
                        <XAxis dataKey="name" tick={{ fontSize: 12 }} className="fill-muted-foreground" />
                        <YAxis tick={{ fontSize: 12 }} className="fill-muted-foreground" tickFormatter={(v) => v.toLocaleString()} />
                        <Tooltip content={<ChartTooltip />} />
                        <Legend wrapperStyle={{ fontSize: 12 }} />
                        {dataKeys.map((key, i) => (
                          <Area
                            key={key}
                            type="monotone"
                            dataKey={key}
                            stroke={COLORS[i % COLORS.length]}
                            fill={COLORS[i % COLORS.length]}
                            fillOpacity={0.3}
                          />
                        ))}
                      </AreaChart>
                    );
                  }

                  return (
                    <BarChart data={analysisResult.chartData}>
                      <CartesianGrid strokeDasharray="3 3" className="stroke-border" />
                      <XAxis dataKey="name" tick={{ fontSize: 12 }} className="fill-muted-foreground" />
                      <YAxis tick={{ fontSize: 12 }} className="fill-muted-foreground" tickFormatter={(v) => v.toLocaleString()} />
                      <Tooltip content={<ChartTooltip />} cursor={{ fill: "hsl(var(--muted))", opacity: 0.3 }} />
                      <Legend wrapperStyle={{ fontSize: 12 }} />
                      {dataKeys.map((key, i) => (
                        <Bar
                          key={key}
                          dataKey={key}
                          fill={COLORS[i % COLORS.length]}
                          radius={[4, 4, 0, 0]}
                        />
                      ))}
                    </BarChart>
                  );
                })()}
              </ResponsiveContainer>
            </div>
          </div>
        )}

        {/* 집계 데이터 */}
        {analysisResult.series.length > 0 && (
          <div className="rounded-md border">
            <div className="flex items-center justify-between gap-2 border-b p-3">
              <h3 className="text-sm font-semibold shrink-0">집계 데이터</h3>
              <div className="flex items-center gap-2 flex-1 justify-end">
                <input
                  type="text"
                  value={tableSearchQuery}
                  onChange={(e) => setTableSearchQuery(e.target.value)}
                  placeholder="검색 (거래처 / 품목 / 사이즈 등)"
                  className="h-8 w-full max-w-[280px] rounded-md border px-2 text-xs"
                />
                {tableSearchQuery && (
                  <button
                    onClick={() => setTableSearchQuery("")}
                    className="text-muted-foreground hover:text-foreground text-xs"
                  >
                    초기화
                  </button>
                )}
                {canGroupTable && (
                  <button
                    onClick={() => setTableGrouped((v) => !v)}
                    className={cn(
                      "h-8 px-3 rounded-md border text-xs shrink-0",
                      tableGrouped && "border-primary bg-primary/10 text-primary"
                    )}
                    title="1차 그룹 기준으로 묶어서 접기/펼치기"
                  >
                    그룹핑 {tableGrouped ? "ON" : "OFF"}
                  </button>
                )}
                {tableGrouped && canGroupTable && (
                  <>
                    <select
                      value={primaryGroupIndex}
                      onChange={(e) => {
                        setPrimaryGroupIndex(Number(e.target.value));
                        setCollapsedGroups(new Set());
                      }}
                      className="h-8 rounded-md border px-2 text-xs shrink-0 bg-background"
                      title="1차 그룹 기준"
                    >
                      {currentGroupParts.map((part, idx) => (
                        <option key={idx} value={idx}>{part}별</option>
                      ))}
                    </select>
                    <button
                      onClick={() => setCollapsedGroups(new Set(groupedLabels?.order || []))}
                      className="h-8 px-2 rounded-md border text-xs shrink-0"
                      title="모두 접기"
                    >
                      모두 접기
                    </button>
                    <button
                      onClick={() => setCollapsedGroups(new Set())}
                      className="h-8 px-2 rounded-md border text-xs shrink-0"
                      title="모두 펼치기"
                    >
                      모두 펼치기
                    </button>
                  </>
                )}
                <div className="flex rounded-md border shrink-0">
                  <button
                    onClick={() => setViewMode("table")}
                    className={cn(
                      "px-3 py-1 text-xs",
                      viewMode === "table" && "bg-primary text-primary-foreground"
                    )}
                  >
                    테이블
                  </button>
                  <button
                    onClick={() => setViewMode("card")}
                    className={cn(
                      "px-3 py-1 text-xs",
                      viewMode === "card" && "bg-primary text-primary-foreground"
                    )}
                  >
                    카드
                  </button>
                </div>
              </div>
            </div>

            <div className="p-3">
              {viewMode === "table" ? (
                <div className="overflow-x-auto">
                  <table className="w-full text-sm">
                    <thead>
                      <tr className="border-b">
                        <th
                          className="p-2 text-left text-xs font-medium text-muted-foreground cursor-pointer hover:bg-muted/50 select-none"
                          onClick={() => toggleTableSort("__label__")}
                        >
                          <span className="inline-flex items-center gap-1">
                            {tableGrouped && canGroupTable && currentGroupParts[primaryGroupIndex]
                              ? `${currentGroupParts[primaryGroupIndex]}별`
                              : (currentGroupParts.length > 0
                                  ? currentGroupParts.map((p) => `${p}별`).join(" × ")
                                  : config.groupByOptions.find((o) => o.id === groupBy)?.name)}
                            {tableSortColumn === "__label__" && (
                              <span className="text-primary">{tableSortDirection === "asc" ? "▲" : "▼"}</span>
                            )}
                          </span>
                        </th>
                        {analysisResult.series.map((s, si) => {
                          const colKey = String(si);
                          return (
                            <th
                              key={si}
                              className="p-2 text-right text-xs font-medium cursor-pointer hover:bg-muted/50 select-none"
                              style={{ borderBottomColor: COLORS[si % COLORS.length], borderBottomWidth: 2 }}
                              onClick={() => toggleTableSort(colKey)}
                            >
                              <span className="inline-flex items-center gap-1 justify-end w-full">
                                <span>
                                  {s.condName}
                                  <br />
                                  <span className="font-normal text-muted-foreground">
                                    {s.metricName}({aggLabel(s.aggMethod)})
                                  </span>
                                </span>
                                {tableSortColumn === colKey && (
                                  <span className="text-primary">{tableSortDirection === "asc" ? "▲" : "▼"}</span>
                                )}
                              </span>
                            </th>
                          );
                        })}
                      </tr>
                    </thead>
                    <tbody>
                      {displayLabels.length === 0 ? (
                        <tr>
                          <td colSpan={analysisResult.series.length + 1} className="p-6 text-center text-xs text-muted-foreground">
                            {tableSearchQuery ? "검색 결과가 없습니다" : "데이터가 없습니다"}
                          </td>
                        </tr>
                      ) : tableGrouped && groupedLabels ? (
                        groupedLabels.order.map((primary) => {
                          const subLabels = groupedLabels.groups[primary];
                          const isCollapsed = collapsedGroups.has(primary);
                          return (
                            <React.Fragment key={`grp-${primary}`}>
                              <tr
                                className="bg-muted/40 border-b border-primary/20 cursor-pointer font-semibold hover:bg-muted"
                                onClick={() => toggleGroupCollapse(primary)}
                              >
                                <td className="p-2">
                                  <span className="inline-flex items-center gap-1">
                                    <span className="text-primary">{isCollapsed ? "▶" : "▼"}</span>
                                    {primary}
                                    <span className="text-muted-foreground text-xs font-normal">({subLabels.length}건)</span>
                                  </span>
                                </td>
                                {analysisResult.series.map((s, si) => {
                                  const allRows = subLabels.flatMap((lb) => s.groups[lb] || []);
                                  return (
                                    <td key={si} className="p-2 text-right tabular-nums">
                                      {formatNumber(aggregateValues(allRows, s.metricId, s.aggMethod))}
                                    </td>
                                  );
                                })}
                              </tr>
                              {!isCollapsed && subLabels.map((label) => {
                                const parts = label.split(" / ");
                                const subPart = parts.filter((_, i) => i !== primaryGroupIndex).join(" / ") || label;
                                return (
                                  <tr
                                    key={label}
                                    className="cursor-pointer border-b transition-colors hover:bg-muted/50"
                                    onClick={() => setDrilldownLabel(label)}
                                  >
                                    <td className="p-2 pl-6 text-muted-foreground">{subPart}</td>
                                    {analysisResult.series.map((s, si) => (
                                      <td key={si} className="p-2 text-right tabular-nums">
                                        {formatNumber(s.values[label] || 0)}
                                      </td>
                                    ))}
                                  </tr>
                                );
                              })}
                            </React.Fragment>
                          );
                        })
                      ) : displayLabels.map((label) => (
                        <tr
                          key={label}
                          className="cursor-pointer border-b transition-colors hover:bg-muted/50"
                          onClick={() => setDrilldownLabel(label)}
                        >
                          <td className="p-2 font-medium">{label}</td>
                          {analysisResult.series.map((s, si) => (
                            <td key={si} className="p-2 text-right tabular-nums">
                              {formatNumber(s.values[label] || 0)}
                            </td>
                          ))}
                        </tr>
                      ))}
                      <tr className="border-t-2 bg-muted/30 font-bold">
                        <td className="p-2">
                          {tableSearchQuery ? `필터 (${displayLabels.length}건)` : "전체"}
                        </td>
                        {analysisResult.series.map((s, si) => {
                          // 검색 시에만 동적 계산, 아니면 미리 계산된 totalValue 사용
                          let total: number;
                          if (tableSearchQuery) {
                            const allRows = displayLabels.flatMap((lb) => s.groups[lb] || []);
                            total = aggregateValues(allRows, s.metricId, s.aggMethod);
                          } else {
                            total = s.totalValue;
                          }
                          return (
                            <td key={si} className="p-2 text-right tabular-nums">
                              {formatNumber(total)}
                            </td>
                          );
                        })}
                      </tr>
                    </tbody>
                  </table>
                </div>
              ) : (
                <div className="grid grid-cols-1 gap-3 sm:grid-cols-2 lg:grid-cols-3">
                  {displayLabels.length === 0 && (
                    <div className="col-span-full p-6 text-center text-xs text-muted-foreground">
                      {tableSearchQuery ? "검색 결과가 없습니다" : "데이터가 없습니다"}
                    </div>
                  )}
                  {displayLabels.map((label) => {
                    const firstS = analysisResult.series[0];
                    const val = firstS ? (firstS.values[label] || 0) : 0;
                    return (
                      <div
                        key={label}
                        className="cursor-pointer rounded-lg border p-4 transition-shadow hover:shadow-md"
                        onClick={() => setDrilldownLabel(label)}
                      >
                        <p className="text-sm font-medium">{label}</p>
                        <p className="mt-1 text-xl font-bold tabular-nums">
                          {formatNumber(val)}
                          <span className="ml-1 text-xs font-normal text-muted-foreground">
                            {firstS?.metricUnit}
                          </span>
                        </p>
                        {analysisResult.series.length > 1 && (
                          <p className="mt-1 truncate text-xs text-muted-foreground">
                            {analysisResult.series.slice(1).map((s) => {
                              const v = s.values[label] || 0;
                              return `${s.condName}-${s.metricName}: ${formatNumber(v)}`;
                            }).join(" | ")}
                          </p>
                        )}
                      </div>
                    );
                  })}
                </div>
              )}
            </div>
          </div>
        )}

        {/* 드릴다운 */}
        {drilldownLabel && (
          <div className="rounded-md border border-primary/30 bg-primary/5">
            <div className="flex items-center justify-between border-b p-3">
              <h3 className="text-sm font-semibold">{drilldownLabel} 상세</h3>
              <button
                onClick={() => setDrilldownLabel(null)}
                className="rounded p-1 text-sm hover:bg-muted"
              >
                X
              </button>
            </div>
            <div className="overflow-x-auto p-3">
              <table className="w-full text-sm">
                <thead>
                  <tr className="border-b">
                    {config.drilldownColumns.map((col) => (
                      <th
                        key={col.id}
                        className={cn("p-2 text-xs", col.align === "right" ? "text-right" : "text-left")}
                      >
                        {col.name}
                      </th>
                    ))}
                  </tr>
                </thead>
                <tbody>
                  {rawData
                    .filter((d) => getGroupKey(d, groupBy) === drilldownLabel)
                    .map((r, i) => (
                      <tr key={i} className="border-b">
                        {config.drilldownColumns.map((col) => (
                          <td
                            key={col.id}
                            className={cn(
                              "p-2 text-xs",
                              col.align === "right" ? "text-right tabular-nums" : ""
                            )}
                          >
                            {renderCellValue(r, col)}
                          </td>
                        ))}
                      </tr>
                    ))}
                </tbody>
              </table>
            </div>
          </div>
        )}

        {/* 원본 데이터 */}
        <div className="rounded-md border">
          <button
            className="flex w-full items-center justify-between p-3 text-left hover:bg-muted/50"
            onClick={() => setRawDataOpen(!rawDataOpen)}
          >
            <span className="text-sm font-semibold">백 데이터 (원본)</span>
            <span className={cn("text-xs transition-transform", !rawDataOpen && "-rotate-90")}>
              ▼
            </span>
          </button>
          {rawDataOpen && (
            <div className="overflow-x-auto border-t p-3">
              <table className="w-full text-sm">
                <thead>
                  <tr className="border-b">
                    {config.rawDataColumns.map((col) => (
                      <th
                        key={col.id}
                        className={cn("p-2 text-xs", col.align === "right" ? "text-right" : "text-left")}
                      >
                        {col.name}
                      </th>
                    ))}
                  </tr>
                </thead>
                <tbody>
                  {rawData.slice(0, 100).map((r, i) => (
                    <tr key={i} className="border-b">
                      {config.rawDataColumns.map((col) => (
                        <td
                          key={col.id}
                          className={cn(
                            "p-2 text-xs",
                            col.align === "right" ? "text-right tabular-nums" : ""
                          )}
                        >
                          {renderCellValue(r, col)}
                        </td>
                      ))}
                    </tr>
                  ))}
                </tbody>
              </table>
              {rawData.length > 100 && (
                <p className="mt-2 text-center text-xs text-muted-foreground">
                  상위 100건만 표시 (전체 {rawData.length}건)
                </p>
              )}
            </div>
          )}
        </div>

        {/* 데이터 없음 */}
        {!isLoading && rawData.length === 0 && (
          <div className="flex flex-col items-center justify-center py-16 text-center">
            <div className="mb-4 flex h-16 w-16 items-center justify-center rounded-full bg-muted">
              <svg className="h-8 w-8 text-muted-foreground" fill="none" viewBox="0 0 24 24" stroke="currentColor">
                <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={1.5}
                  d="M9 17v-2m3 2v-4m3 4v-6m2 10H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"
                />
              </svg>
            </div>
            <h3 className="text-lg font-semibold">{config.emptyMessage}</h3>
            <p className="mt-1 text-sm text-muted-foreground">
              기간을 변경하거나 데이터를 확인해주세요
            </p>
          </div>
        )}
      </div>

      {/* 프리셋 저장 모달 */}
      <Dialog open={presetModalOpen} onOpenChange={setPresetModalOpen}>
        <DialogContent className="max-w-[95vw] sm:max-w-[500px]">
          <DialogHeader>
            <DialogTitle className="text-base sm:text-lg">
              {presetMode === "update" ? "조건 수정" : "신규 조건 저장"}
            </DialogTitle>
          </DialogHeader>
          <div className="space-y-3">
            <div>
              <Label className="text-xs sm:text-sm">
                조건명 <span className="text-destructive">*</span>
              </Label>
              <Input
                value={presetName}
                onChange={(e) => setPresetName(e.target.value)}
                placeholder="예: 월별 추이 분석"
                className="h-8 text-xs sm:h-10 sm:text-sm"
              />
            </div>
            <div>
              <Label className="text-xs sm:text-sm">설명</Label>
              <Input
                value={presetDesc}
                onChange={(e) => setPresetDesc(e.target.value)}
                placeholder="조건 설명 (선택사항)"
                className="h-8 text-xs sm:h-10 sm:text-sm"
              />
            </div>
          </div>
          <DialogFooter className="gap-2 sm:gap-0">
            <Button
              variant="outline"
              onClick={() => setPresetModalOpen(false)}
              className="h-8 flex-1 text-xs sm:h-10 sm:flex-none sm:text-sm"
            >
              취소
            </Button>
            <Button
              onClick={savePreset}
              className="h-8 flex-1 text-xs sm:h-10 sm:flex-none sm:text-sm"
            >
              {presetMode === "update" ? "수정 저장" : "저장"}
            </Button>
          </DialogFooter>
        </DialogContent>
      </Dialog>

      <ScrollToTop />
    </div>
  );
}
