/**
 * PivotGrid 데이터 처리 엔진
 * 원시 데이터를 피벗 구조로 변환합니다.
 */

import {
  PivotFieldConfig,
  PivotResult,
  PivotHeaderNode,
  PivotFlatRow,
  PivotFlatColumn,
  PivotCellValue,
  DateGroupInterval,
  AggregationType,
  SummaryDisplayMode,
} from "../types";
import { aggregate, formatNumber, formatDate } from "./aggregation";

// ==================== 헬퍼 함수 ====================

/**
 * 필드 값 추출 (날짜 그룹핑 포함)
 */
function getFieldValue(
  row: Record<string, any>,
  field: PivotFieldConfig
): string {
  const rawValue = row[field.field];

  if (rawValue === null || rawValue === undefined) {
    return "(빈 값)";
  }

  // 날짜 그룹핑 처리
  if (field.groupInterval && field.dataType === "date") {
    const date = new Date(rawValue);
    if (isNaN(date.getTime())) return String(rawValue);

    switch (field.groupInterval) {
      case "year":
        return String(date.getFullYear());
      case "quarter":
        return `Q${Math.ceil((date.getMonth() + 1) / 3)}`;
      case "month":
        return `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, "0")}`;
      case "week":
        const weekNum = getWeekNumber(date);
        return `${date.getFullYear()}-W${String(weekNum).padStart(2, "0")}`;
      case "day":
        return formatDate(date);
      default:
        return String(rawValue);
    }
  }

  return String(rawValue);
}

/**
 * 주차 계산
 */
function getWeekNumber(date: Date): number {
  const d = new Date(
    Date.UTC(date.getFullYear(), date.getMonth(), date.getDate())
  );
  const dayNum = d.getUTCDay() || 7;
  d.setUTCDate(d.getUTCDate() + 4 - dayNum);
  const yearStart = new Date(Date.UTC(d.getUTCFullYear(), 0, 1));
  return Math.ceil(((d.getTime() - yearStart.getTime()) / 86400000 + 1) / 7);
}

/**
 * 경로를 키로 변환
 */
export function pathToKey(path: string[]): string {
  return path.join("||");
}

/**
 * 키를 경로로 변환
 */
export function keyToPath(key: string): string[] {
  return key.split("||");
}

// ==================== 헤더 생성 ====================

/**
 * 계층적 헤더 노드 생성
 */
function buildHeaderTree(
  data: Record<string, any>[],
  fields: PivotFieldConfig[],
  expandedPaths: Set<string>
): PivotHeaderNode[] {
  if (fields.length === 0) return [];

  // 첫 번째 필드로 그룹화
  const firstField = fields[0];
  const groups = new Map<string, Record<string, any>[]>();

  data.forEach((row) => {
    const value = getFieldValue(row, firstField);
    if (!groups.has(value)) {
      groups.set(value, []);
    }
    groups.get(value)!.push(row);
  });

  // 정렬
  const sortedKeys = Array.from(groups.keys()).sort((a, b) => {
    if (firstField.sortOrder === "desc") {
      return b.localeCompare(a, "ko");
    }
    return a.localeCompare(b, "ko");
  });

  // 노드 생성
  const nodes: PivotHeaderNode[] = [];
  const remainingFields = fields.slice(1);

  for (const key of sortedKeys) {
    const groupData = groups.get(key)!;
    const path = [key];
    const pathKey = pathToKey(path);

    const node: PivotHeaderNode = {
      value: key,
      caption: key,
      level: 0,
      isExpanded: expandedPaths.has(pathKey),
      path: path,
      span: 1,
    };

    // 자식 노드 생성 (확장된 경우만)
    if (remainingFields.length > 0 && node.isExpanded) {
      node.children = buildChildNodes(
        groupData,
        remainingFields,
        path,
        expandedPaths,
        1
      );
      // span 계산
      node.span = calculateSpan(node.children);
    }

    nodes.push(node);
  }

  return nodes;
}

/**
 * 자식 노드 재귀 생성
 */
function buildChildNodes(
  data: Record<string, any>[],
  fields: PivotFieldConfig[],
  parentPath: string[],
  expandedPaths: Set<string>,
  level: number
): PivotHeaderNode[] {
  if (fields.length === 0) return [];

  const field = fields[0];
  const groups = new Map<string, Record<string, any>[]>();

  data.forEach((row) => {
    const value = getFieldValue(row, field);
    if (!groups.has(value)) {
      groups.set(value, []);
    }
    groups.get(value)!.push(row);
  });

  const sortedKeys = Array.from(groups.keys()).sort((a, b) => {
    if (field.sortOrder === "desc") {
      return b.localeCompare(a, "ko");
    }
    return a.localeCompare(b, "ko");
  });

  const nodes: PivotHeaderNode[] = [];
  const remainingFields = fields.slice(1);

  for (const key of sortedKeys) {
    const groupData = groups.get(key)!;
    const path = [...parentPath, key];
    const pathKey = pathToKey(path);

    const node: PivotHeaderNode = {
      value: key,
      caption: key,
      level: level,
      isExpanded: expandedPaths.has(pathKey),
      path: path,
      span: 1,
    };

    if (remainingFields.length > 0 && node.isExpanded) {
      node.children = buildChildNodes(
        groupData,
        remainingFields,
        path,
        expandedPaths,
        level + 1
      );
      node.span = calculateSpan(node.children);
    }

    nodes.push(node);
  }

  return nodes;
}

/**
 * span 계산 (colspan/rowspan)
 */
function calculateSpan(children?: PivotHeaderNode[]): number {
  if (!children || children.length === 0) return 1;
  return children.reduce((sum, child) => sum + child.span, 0);
}

// ==================== 플랫 구조 변환 ====================

/**
 * 헤더 트리를 플랫 행으로 변환
 */
function flattenRows(nodes: PivotHeaderNode[]): PivotFlatRow[] {
  const result: PivotFlatRow[] = [];

  function traverse(node: PivotHeaderNode) {
    result.push({
      path: node.path,
      level: node.level,
      caption: node.caption,
      isExpanded: node.isExpanded,
      hasChildren: !!(node.children && node.children.length > 0),
    });

    if (node.isExpanded && node.children) {
      for (const child of node.children) {
        traverse(child);
      }
    }
  }

  for (const node of nodes) {
    traverse(node);
  }

  return result;
}

/**
 * 헤더 트리를 플랫 열로 변환 (각 레벨별)
 */
function flattenColumns(
  nodes: PivotHeaderNode[],
  maxLevel: number
): PivotFlatColumn[][] {
  const levels: PivotFlatColumn[][] = Array.from(
    { length: maxLevel + 1 },
    () => []
  );

  function traverse(node: PivotHeaderNode, currentLevel: number) {
    levels[currentLevel].push({
      path: node.path,
      level: currentLevel,
      caption: node.caption,
      span: node.span,
    });

    if (node.children && node.isExpanded) {
      for (const child of node.children) {
        traverse(child, currentLevel + 1);
      }
    } else if (currentLevel < maxLevel) {
      // 확장되지 않은 노드는 다음 레벨들에서 span으로 처리
      for (let i = currentLevel + 1; i <= maxLevel; i++) {
        levels[i].push({
          path: node.path,
          level: i,
          caption: "",
          span: node.span,
        });
      }
    }
  }

  for (const node of nodes) {
    traverse(node, 0);
  }

  return levels;
}

/**
 * 열 헤더의 최대 깊이 계산
 */
function getMaxColumnLevel(
  nodes: PivotHeaderNode[],
  totalFields: number
): number {
  let maxLevel = 0;

  function traverse(node: PivotHeaderNode, level: number) {
    maxLevel = Math.max(maxLevel, level);
    if (node.children && node.isExpanded) {
      for (const child of node.children) {
        traverse(child, level + 1);
      }
    }
  }

  for (const node of nodes) {
    traverse(node, 0);
  }

  return Math.min(maxLevel, totalFields - 1);
}

// ==================== 데이터 매트릭스 생성 ====================

/**
 * 데이터 매트릭스 생성
 */
function buildDataMatrix(
  data: Record<string, any>[],
  rowFields: PivotFieldConfig[],
  columnFields: PivotFieldConfig[],
  dataFields: PivotFieldConfig[],
  flatRows: PivotFlatRow[],
  flatColumnLeaves: string[][]
): Map<string, PivotCellValue[]> {
  const matrix = new Map<string, PivotCellValue[]>();

  // 각 셀에 대해 해당하는 데이터 집계
  for (const row of flatRows) {
    for (const colPath of flatColumnLeaves) {
      const cellKey = `${pathToKey(row.path)}|||${pathToKey(colPath)}`;

      // 해당 행/열 경로에 맞는 데이터 필터링
      const filteredData = data.filter((record) => {
        // 행 조건 확인
        for (let i = 0; i < row.path.length; i++) {
          const field = rowFields[i];
          if (!field) continue;
          const value = getFieldValue(record, field);
          if (value !== row.path[i]) return false;
        }

        // 열 조건 확인
        for (let i = 0; i < colPath.length; i++) {
          const field = columnFields[i];
          if (!field) continue;
          const value = getFieldValue(record, field);
          if (value !== colPath[i]) return false;
        }

        return true;
      });

      // 데이터 필드별 집계
      const cellValues: PivotCellValue[] = dataFields.map((dataField) => {
        const values = filteredData.map((r) => r[dataField.field]);
        const aggregatedValue = aggregate(
          values,
          dataField.summaryType || "sum"
        );
        const formattedValue = formatNumber(
          aggregatedValue,
          dataField.format
        );

        return {
          field: dataField.field,
          value: aggregatedValue,
          formattedValue,
        };
      });

      matrix.set(cellKey, cellValues);
    }
  }

  return matrix;
}

/**
 * 열 leaf 노드 경로 추출
 */
function getColumnLeaves(nodes: PivotHeaderNode[]): string[][] {
  const leaves: string[][] = [];

  function traverse(node: PivotHeaderNode) {
    if (!node.isExpanded || !node.children || node.children.length === 0) {
      leaves.push(node.path);
    } else {
      for (const child of node.children) {
        traverse(child);
      }
    }
  }

  for (const node of nodes) {
    traverse(node);
  }

  // 열 필드가 없을 경우 빈 경로 추가
  if (leaves.length === 0) {
    leaves.push([]);
  }

  return leaves;
}

// ==================== Summary Display Mode 적용 ====================

/**
 * Summary Display Mode에 따른 값 변환
 */
function applyDisplayMode(
  value: number,
  displayMode: SummaryDisplayMode | undefined,
  rowTotal: number,
  columnTotal: number,
  grandTotal: number,
  prevValue: number | null,
  runningTotal: number,
  format?: PivotFieldConfig["format"]
): { value: number; formattedValue: string } {
  if (!displayMode || displayMode === "absoluteValue") {
    return {
      value,
      formattedValue: formatNumber(value, format),
    };
  }

  let resultValue: number;
  let formatOverride: PivotFieldConfig["format"] | undefined;

  switch (displayMode) {
    case "percentOfRowTotal":
      resultValue = rowTotal === 0 ? 0 : (value / rowTotal) * 100;
      formatOverride = { type: "percent", precision: 2, suffix: "%" };
      break;

    case "percentOfColumnTotal":
      resultValue = columnTotal === 0 ? 0 : (value / columnTotal) * 100;
      formatOverride = { type: "percent", precision: 2, suffix: "%" };
      break;

    case "percentOfGrandTotal":
      resultValue = grandTotal === 0 ? 0 : (value / grandTotal) * 100;
      formatOverride = { type: "percent", precision: 2, suffix: "%" };
      break;

    case "percentOfRowGrandTotal":
      resultValue = rowTotal === 0 ? 0 : (value / rowTotal) * 100;
      formatOverride = { type: "percent", precision: 2, suffix: "%" };
      break;

    case "percentOfColumnGrandTotal":
      resultValue = columnTotal === 0 ? 0 : (value / columnTotal) * 100;
      formatOverride = { type: "percent", precision: 2, suffix: "%" };
      break;

    case "runningTotalByRow":
    case "runningTotalByColumn":
      resultValue = runningTotal;
      break;

    case "differenceFromPrevious":
      resultValue = prevValue === null ? 0 : value - prevValue;
      break;

    case "percentDifferenceFromPrevious":
      resultValue = prevValue === null || prevValue === 0 
        ? 0 
        : ((value - prevValue) / Math.abs(prevValue)) * 100;
      formatOverride = { type: "percent", precision: 2, suffix: "%" };
      break;

    default:
      resultValue = value;
  }

  return {
    value: resultValue,
    formattedValue: formatNumber(resultValue, formatOverride || format),
  };
}

/**
 * 데이터 매트릭스에 Summary Display Mode 적용
 */
function applyDisplayModeToMatrix(
  matrix: Map<string, PivotCellValue[]>,
  dataFields: PivotFieldConfig[],
  flatRows: PivotFlatRow[],
  flatColumnLeaves: string[][],
  rowTotals: Map<string, PivotCellValue[]>,
  columnTotals: Map<string, PivotCellValue[]>,
  grandTotals: PivotCellValue[]
): Map<string, PivotCellValue[]> {
  // displayMode가 있는 데이터 필드가 있는지 확인
  const hasDisplayMode = dataFields.some(
    (df) => df.summaryDisplayMode || df.showValuesAs
  );
  if (!hasDisplayMode) return matrix;

  const newMatrix = new Map<string, PivotCellValue[]>();

  // 누계를 위한 추적 (행별, 열별)
  const rowRunningTotals: Map<string, number[]> = new Map(); // fieldIndex -> 누계
  const colRunningTotals: Map<string, Map<number, number>> = new Map(); // colKey -> fieldIndex -> 누계

  // 행 순서대로 처리
  for (const row of flatRows) {
    // 이전 열 값 추적 (차이 계산용)
    const prevColValues: (number | null)[] = dataFields.map(() => null);
    
    for (let colIdx = 0; colIdx < flatColumnLeaves.length; colIdx++) {
      const colPath = flatColumnLeaves[colIdx];
      const cellKey = `${pathToKey(row.path)}|||${pathToKey(colPath)}`;
      const values = matrix.get(cellKey);

      if (!values) {
        newMatrix.set(cellKey, []);
        continue;
      }

      const rowKey = pathToKey(row.path);
      const colKey = pathToKey(colPath);
      
      // 총합 가져오기
      const rowTotal = rowTotals.get(rowKey);
      const colTotal = columnTotals.get(colKey);

      const newValues: PivotCellValue[] = values.map((val, fieldIdx) => {
        const dataField = dataFields[fieldIdx];
        const displayMode = dataField.summaryDisplayMode || dataField.showValuesAs;

        if (!displayMode || displayMode === "absoluteValue") {
          prevColValues[fieldIdx] = val.value;
          return val;
        }

        // 누계 계산
        // 행 방향 누계
        if (!rowRunningTotals.has(rowKey)) {
          rowRunningTotals.set(rowKey, dataFields.map(() => 0));
        }
        const rowRunning = rowRunningTotals.get(rowKey)!;
        rowRunning[fieldIdx] += val.value || 0;

        // 열 방향 누계
        if (!colRunningTotals.has(colKey)) {
          colRunningTotals.set(colKey, new Map());
        }
        const colRunning = colRunningTotals.get(colKey)!;
        if (!colRunning.has(fieldIdx)) {
          colRunning.set(fieldIdx, 0);
        }
        colRunning.set(fieldIdx, (colRunning.get(fieldIdx) || 0) + (val.value || 0));

        const result = applyDisplayMode(
          val.value || 0,
          displayMode,
          rowTotal?.[fieldIdx]?.value || 0,
          colTotal?.[fieldIdx]?.value || 0,
          grandTotals[fieldIdx]?.value || 0,
          prevColValues[fieldIdx],
          displayMode === "runningTotalByRow" 
            ? rowRunning[fieldIdx] 
            : colRunning.get(fieldIdx) || 0,
          dataField.format
        );

        prevColValues[fieldIdx] = val.value;

        return {
          field: val.field,
          value: result.value,
          formattedValue: result.formattedValue,
        };
      });

      newMatrix.set(cellKey, newValues);
    }
  }

  return newMatrix;
}

// ==================== 총합계 계산 ====================

/**
 * 총합계 계산
 */
function calculateGrandTotals(
  data: Record<string, any>[],
  rowFields: PivotFieldConfig[],
  columnFields: PivotFieldConfig[],
  dataFields: PivotFieldConfig[],
  flatRows: PivotFlatRow[],
  flatColumnLeaves: string[][]
): {
  row: Map<string, PivotCellValue[]>;
  column: Map<string, PivotCellValue[]>;
  grand: PivotCellValue[];
} {
  const rowTotals = new Map<string, PivotCellValue[]>();
  const columnTotals = new Map<string, PivotCellValue[]>();

  // 행별 총합 (각 행의 모든 열 합계)
  for (const row of flatRows) {
    const filteredData = data.filter((record) => {
      for (let i = 0; i < row.path.length; i++) {
        const field = rowFields[i];
        if (!field) continue;
        const value = getFieldValue(record, field);
        if (value !== row.path[i]) return false;
      }
      return true;
    });

    const cellValues: PivotCellValue[] = dataFields.map((dataField) => {
      const values = filteredData.map((r) => r[dataField.field]);
      const aggregatedValue = aggregate(values, dataField.summaryType || "sum");
      return {
        field: dataField.field,
        value: aggregatedValue,
        formattedValue: formatNumber(aggregatedValue, dataField.format),
      };
    });

    rowTotals.set(pathToKey(row.path), cellValues);
  }

  // 열별 총합 (각 열의 모든 행 합계)
  for (const colPath of flatColumnLeaves) {
    const filteredData = data.filter((record) => {
      for (let i = 0; i < colPath.length; i++) {
        const field = columnFields[i];
        if (!field) continue;
        const value = getFieldValue(record, field);
        if (value !== colPath[i]) return false;
      }
      return true;
    });

    const cellValues: PivotCellValue[] = dataFields.map((dataField) => {
      const values = filteredData.map((r) => r[dataField.field]);
      const aggregatedValue = aggregate(values, dataField.summaryType || "sum");
      return {
        field: dataField.field,
        value: aggregatedValue,
        formattedValue: formatNumber(aggregatedValue, dataField.format),
      };
    });

    columnTotals.set(pathToKey(colPath), cellValues);
  }

  // 대총합
  const grandValues: PivotCellValue[] = dataFields.map((dataField) => {
    const values = data.map((r) => r[dataField.field]);
    const aggregatedValue = aggregate(values, dataField.summaryType || "sum");
    return {
      field: dataField.field,
      value: aggregatedValue,
      formattedValue: formatNumber(aggregatedValue, dataField.format),
    };
  });

  return {
    row: rowTotals,
    column: columnTotals,
    grand: grandValues,
  };
}

// ==================== 메인 함수 ====================

/**
 * 피벗 데이터 처리
 */
export function processPivotData(
  data: Record<string, any>[],
  fields: PivotFieldConfig[],
  expandedRowPaths: string[][] = [],
  expandedColumnPaths: string[][] = []
): PivotResult {
  // 영역별 필드 분리
  const rowFields = fields
    .filter((f) => f.area === "row" && f.visible !== false)
    .sort((a, b) => (a.areaIndex || 0) - (b.areaIndex || 0));

  const columnFields = fields
    .filter((f) => f.area === "column" && f.visible !== false)
    .sort((a, b) => (a.areaIndex || 0) - (b.areaIndex || 0));

  const dataFields = fields
    .filter((f) => f.area === "data" && f.visible !== false)
    .sort((a, b) => (a.areaIndex || 0) - (b.areaIndex || 0));

  const filterFields = fields.filter(
    (f) => f.area === "filter" && f.visible !== false
  );

  // 필터 적용
  let filteredData = data;
  for (const filterField of filterFields) {
    if (filterField.filterValues && filterField.filterValues.length > 0) {
      filteredData = filteredData.filter((row) => {
        const value = getFieldValue(row, filterField);
        if (filterField.filterType === "exclude") {
          return !filterField.filterValues!.includes(value);
        }
        return filterField.filterValues!.includes(value);
      });
    }
  }

  // 확장 경로 Set 변환
  const expandedRowSet = new Set(expandedRowPaths.map(pathToKey));
  const expandedColSet = new Set(expandedColumnPaths.map(pathToKey));

  // 기본 확장: 첫 번째 레벨 모두 확장
  if (expandedRowPaths.length === 0 && rowFields.length > 0) {
    const firstField = rowFields[0];
    const uniqueValues = new Set(
      filteredData.map((row) => getFieldValue(row, firstField))
    );
    uniqueValues.forEach((val) => expandedRowSet.add(val));
  }

  if (expandedColumnPaths.length === 0 && columnFields.length > 0) {
    const firstField = columnFields[0];
    const uniqueValues = new Set(
      filteredData.map((row) => getFieldValue(row, firstField))
    );
    uniqueValues.forEach((val) => expandedColSet.add(val));
  }

  // 헤더 트리 생성
  const rowHeaders = buildHeaderTree(filteredData, rowFields, expandedRowSet);
  const columnHeaders = buildHeaderTree(
    filteredData,
    columnFields,
    expandedColSet
  );

  // 플랫 구조 변환
  const flatRows = flattenRows(rowHeaders);
  const flatColumnLeaves = getColumnLeaves(columnHeaders);
  const maxColumnLevel = getMaxColumnLevel(columnHeaders, columnFields.length);
  const flatColumns = flattenColumns(columnHeaders, maxColumnLevel);

  // 데이터 매트릭스 생성
  let dataMatrix = buildDataMatrix(
    filteredData,
    rowFields,
    columnFields,
    dataFields,
    flatRows,
    flatColumnLeaves
  );

  // 총합계 계산
  const grandTotals = calculateGrandTotals(
    filteredData,
    rowFields,
    columnFields,
    dataFields,
    flatRows,
    flatColumnLeaves
  );

  // Summary Display Mode 적용
  dataMatrix = applyDisplayModeToMatrix(
    dataMatrix,
    dataFields,
    flatRows,
    flatColumnLeaves,
    grandTotals.row,
    grandTotals.column,
    grandTotals.grand
  );

  return {
    rowHeaders,
    columnHeaders,
    dataMatrix,
    flatRows,
    flatColumns: flatColumnLeaves.map((path, idx) => ({
      path,
      level: path.length - 1,
      caption: path[path.length - 1] || "",
      span: 1,
    })),
    grandTotals,
  };
}


