import type { FilterInput, ReportGroupSummary } from "./report-query-config.js";
import type { ColumnDef } from "./report-columns.js";

function parseFilterValue(def: ColumnDef | undefined, value: string): unknown {
  if (!def) {
    return value;
  }
  if (def.dataType === "number") {
    const parsed = Number(value);
    return Number.isNaN(parsed) ? null : parsed;
  }
  if (def.dataType === "boolean") {
    return value === "true";
  }
  if (def.dataType === "date") {
    const parsed = new Date(value);
    return Number.isNaN(parsed.getTime()) ? null : parsed.getTime();
  }
  return value;
}

export function matchesInMemoryFilter(
  row: Record<string, unknown>,
  filter: FilterInput,
  columns: ColumnDef[],
): boolean {
  const def = columns.find((column) => column.key === filter.field);
  if (!def) {
    return true;
  }

  const rowValueRaw = row[filter.field];
  const rowValue = def.dataType === "date" && typeof rowValueRaw === "string"
    ? new Date(rowValueRaw).getTime()
    : rowValueRaw;
  const parsedFilterValue = parseFilterValue(def, filter.value);

  if (parsedFilterValue === null) {
    return false;
  }

  switch (filter.op) {
    case "eq":
      return rowValue === parsedFilterValue;
    case "neq":
      return rowValue !== parsedFilterValue;
    case "gt":
      return typeof rowValue === "number" && typeof parsedFilterValue === "number" && rowValue > parsedFilterValue;
    case "lt":
      return typeof rowValue === "number" && typeof parsedFilterValue === "number" && rowValue < parsedFilterValue;
    case "gte":
      return typeof rowValue === "number" && typeof parsedFilterValue === "number" && rowValue >= parsedFilterValue;
    case "lte":
      return typeof rowValue === "number" && typeof parsedFilterValue === "number" && rowValue <= parsedFilterValue;
    case "contains":
      return typeof rowValue === "string" && rowValue.toLowerCase().includes(filter.value.toLowerCase());
    case "in":
      return filter.value.split(",").map((value) => value.trim()).includes(String(rowValue ?? ""));
    default:
      return true;
  }
}

export function sortInMemoryRows(
  rows: Record<string, unknown>[],
  groupBy: string | undefined,
  sortBy: string | undefined,
  sortDir: "asc" | "desc",
  columns: ColumnDef[],
): Record<string, unknown>[] {
  if (!groupBy && !sortBy) {
    return rows;
  }
  return [...rows].sort((left, right) => {
    if (groupBy) {
      const groupDef = columns.find((column) => column.key === groupBy);
      const groupComparison = compareRowValues(left[groupBy], right[groupBy], groupDef, "asc");
      if (groupComparison !== 0) {
        return groupComparison;
      }
    }

    if (!sortBy) {
      return 0;
    }

    const sortDef = columns.find((column) => column.key === sortBy);
    return compareRowValues(left[sortBy], right[sortBy], sortDef, sortDir);
  });
}

function compareRowValues(
  leftValue: unknown,
  rightValue: unknown,
  def: ColumnDef | undefined,
  sortDir: "asc" | "desc",
): number {
  if (leftValue == null && rightValue == null) {
    return 0;
  }
  if (leftValue == null) {
    return 1;
  }
  if (rightValue == null) {
    return -1;
  }

  const direction = sortDir === "asc" ? 1 : -1;
  if (def?.dataType === "number") {
    return direction * (Number(leftValue) - Number(rightValue));
  }
  if (def?.dataType === "boolean") {
    return direction * (Number(Boolean(leftValue)) - Number(Boolean(rightValue)));
  }
  if (def?.dataType === "date") {
    return direction * (new Date(String(leftValue)).getTime() - new Date(String(rightValue)).getTime());
  }
  return direction * String(leftValue).localeCompare(String(rightValue), "de");
}

export function pickColumns(row: Record<string, unknown>, columns: string[]): Record<string, unknown> {
  return Object.fromEntries(columns.map((column) => [column, row[column]]));
}

export function buildReportGroups(
  rows: Record<string, unknown>[],
  groupBy: string | undefined,
): ReportGroupSummary[] {
  if (!groupBy) {
    return [];
  }

  const groups: ReportGroupSummary[] = [];
  let currentKey: string | null = null;

  rows.forEach((row, index) => {
    const rawValue = row[groupBy];
    const label = formatGroupValue(rawValue);
    const key = `${groupBy}:${label}`;

    if (key !== currentKey) {
      groups.push({
        key,
        label,
        rowCount: 1,
        startIndex: index,
      });
      currentKey = key;
      return;
    }

    groups[groups.length - 1]!.rowCount += 1;
  });

  return groups;
}

function formatGroupValue(value: unknown): string {
  if (value === null || value === undefined || value === "") {
    return "No value";
  }
  if (value instanceof Date) {
    return value.toISOString();
  }
  return String(value);
}
