import {
  FINDING_DELTA,
  FINDING_STATUS,
  type FindingDelta,
  type FindingGroupRow,
  type FindingStatus,
} from "@/types";

type FindingGroupDeltaState = Pick<
  FindingGroupRow,
  | "newCount"
  | "changedCount"
  | "newFailCount"
  | "newFailMutedCount"
  | "newPassCount"
  | "newPassMutedCount"
  | "newManualCount"
  | "newManualMutedCount"
  | "changedFailCount"
  | "changedFailMutedCount"
  | "changedPassCount"
  | "changedPassMutedCount"
  | "changedManualCount"
  | "changedManualMutedCount"
>;

type FindingGroupDelta = Exclude<FindingDelta, null>;

type FindingGroupStatus = FindingStatus;

const FINDING_GROUP_STATUSES = Object.values(FINDING_STATUS);

export function isFindingGroupMuted(
  group: Pick<
    FindingGroupRow,
    "muted" | "mutedCount" | "resourcesFail" | "resourcesTotal"
  >,
): boolean {
  if (typeof group.muted === "boolean") {
    return group.muted;
  }

  const mutedCount = group.mutedCount ?? 0;
  if (mutedCount === 0) {
    return false;
  }

  return (
    mutedCount === group.resourcesFail || mutedCount === group.resourcesTotal
  );
}

export function getFindingGroupImpactedCounts(
  group: Pick<
    FindingGroupRow,
    | "resourcesTotal"
    | "resourcesFail"
    | "passCount"
    | "failCount"
    | "manualCount"
    | "passMutedCount"
    | "failMutedCount"
    | "manualMutedCount"
    | "muted"
    | "mutedCount"
  >,
): { impacted: number; total: number } {
  if (group.resourcesTotal > 0) {
    return {
      impacted: group.resourcesFail,
      total: group.resourcesTotal,
    };
  }

  const total =
    (group.passCount ?? 0) + (group.failCount ?? 0) + (group.manualCount ?? 0);

  if (!isFindingGroupMuted(group)) {
    return {
      impacted: group.failCount ?? 0,
      total,
    };
  }

  return {
    impacted: group.failCount ?? 0,
    total:
      total +
      (group.passMutedCount ?? 0) +
      (group.failMutedCount ?? 0) +
      (group.manualMutedCount ?? 0),
  };
}

export function canDrillDownFindingGroup(
  group: Pick<FindingGroupRow, "resourcesTotal">,
): boolean {
  return group.resourcesTotal > 0;
}

function getNewDeltaTotal(group: FindingGroupDeltaState): number {
  const breakdownTotal =
    (group.newFailCount ?? 0) +
    (group.newFailMutedCount ?? 0) +
    (group.newPassCount ?? 0) +
    (group.newPassMutedCount ?? 0) +
    (group.newManualCount ?? 0) +
    (group.newManualMutedCount ?? 0);

  return breakdownTotal > 0 ? breakdownTotal : group.newCount;
}

function getChangedDeltaTotal(group: FindingGroupDeltaState): number {
  const breakdownTotal =
    (group.changedFailCount ?? 0) +
    (group.changedFailMutedCount ?? 0) +
    (group.changedPassCount ?? 0) +
    (group.changedPassMutedCount ?? 0) +
    (group.changedManualCount ?? 0) +
    (group.changedManualMutedCount ?? 0);

  return breakdownTotal > 0 ? breakdownTotal : group.changedCount;
}

export function getFindingGroupDelta(
  group: FindingGroupDeltaState,
): FindingGroupDelta {
  if (getNewDeltaTotal(group) > 0) {
    return FINDING_DELTA.NEW;
  }

  if (getChangedDeltaTotal(group) > 0) {
    return FINDING_DELTA.CHANGED;
  }

  return FINDING_DELTA.NONE;
}

type FindingGroupFiltersRecord = Record<string, string | string[] | undefined>;

function parseStatusFilterValue(
  rawValue: string | string[] | undefined,
): FindingGroupStatus[] {
  if (!rawValue) {
    return [];
  }

  const joined = Array.isArray(rawValue) ? rawValue.join(",") : rawValue;

  return joined
    .split(",")
    .map((status) => status.trim().toUpperCase())
    .filter((status): status is FindingGroupStatus =>
      (FINDING_GROUP_STATUSES as readonly string[]).includes(status),
    );
}

/**
 * Returns the set of statuses the user has explicitly narrowed the findings
 * view to, or null when no status filter is active (→ all statuses should be
 * considered). Supports both `filter[status]` (single value) and
 * `filter[status__in]` (comma-separated values).
 */
export function getActiveStatusFilter(
  filters: FindingGroupFiltersRecord,
): Set<FindingGroupStatus> | null {
  const direct = parseStatusFilterValue(filters["filter[status]"]);
  if (direct.length > 0) {
    return new Set(direct);
  }

  const multi = parseStatusFilterValue(filters["filter[status__in]"]);
  if (multi.length > 0) {
    return new Set(multi);
  }

  return null;
}

function hasAnyDeltaBreakdown(group: FindingGroupDeltaState): boolean {
  return (
    (group.newFailCount ?? 0) > 0 ||
    (group.newFailMutedCount ?? 0) > 0 ||
    (group.newPassCount ?? 0) > 0 ||
    (group.newPassMutedCount ?? 0) > 0 ||
    (group.newManualCount ?? 0) > 0 ||
    (group.newManualMutedCount ?? 0) > 0 ||
    (group.changedFailCount ?? 0) > 0 ||
    (group.changedFailMutedCount ?? 0) > 0 ||
    (group.changedPassCount ?? 0) > 0 ||
    (group.changedPassMutedCount ?? 0) > 0 ||
    (group.changedManualCount ?? 0) > 0 ||
    (group.changedManualMutedCount ?? 0) > 0
  );
}

function getNewDeltaForStatuses(
  group: FindingGroupDeltaState,
  statuses: Set<FindingGroupStatus>,
): number {
  let total = 0;
  if (statuses.has(FINDING_STATUS.FAIL)) {
    total += (group.newFailCount ?? 0) + (group.newFailMutedCount ?? 0);
  }
  if (statuses.has(FINDING_STATUS.PASS)) {
    total += (group.newPassCount ?? 0) + (group.newPassMutedCount ?? 0);
  }
  if (statuses.has(FINDING_STATUS.MANUAL)) {
    total += (group.newManualCount ?? 0) + (group.newManualMutedCount ?? 0);
  }
  return total;
}

function getChangedDeltaForStatuses(
  group: FindingGroupDeltaState,
  statuses: Set<FindingGroupStatus>,
): number {
  let total = 0;
  if (statuses.has(FINDING_STATUS.FAIL)) {
    total += (group.changedFailCount ?? 0) + (group.changedFailMutedCount ?? 0);
  }
  if (statuses.has(FINDING_STATUS.PASS)) {
    total += (group.changedPassCount ?? 0) + (group.changedPassMutedCount ?? 0);
  }
  if (statuses.has(FINDING_STATUS.MANUAL)) {
    total +=
      (group.changedManualCount ?? 0) + (group.changedManualMutedCount ?? 0);
  }
  return total;
}

/**
 * Filter-aware variant of {@link getFindingGroupDelta}. When a status filter
 * is active, only delta counters belonging to the filtered statuses contribute
 * to the indicator. When no status filter is active, or when the API response
 * lacks breakdown counters (legacy shape), this falls back to the aggregate
 * delta so rows still surface deltas correctly.
 */
export function getFilteredFindingGroupDelta(
  group: FindingGroupDeltaState,
  filters: FindingGroupFiltersRecord,
): FindingGroupDelta {
  const activeStatuses = getActiveStatusFilter(filters);

  if (!activeStatuses || !hasAnyDeltaBreakdown(group)) {
    return getFindingGroupDelta(group);
  }

  if (getNewDeltaForStatuses(group, activeStatuses) > 0) {
    return FINDING_DELTA.NEW;
  }

  if (getChangedDeltaForStatuses(group, activeStatuses) > 0) {
    return FINDING_DELTA.CHANGED;
  }

  return FINDING_DELTA.NONE;
}
