export type ComparisonRule =
  | "EQUALS"
  | "NOT_EQUALS"
  | "GREATER_THAN"
  | "LESS_THAN"
  | "CONTAINS"
  | "NOT_CONTAINS"
  | "MATCHES"
  | "STARTS_WITH"
  | "ENDS_WITH";
export type LogicalRule = "AND" | "OR" | "NOT";
export type ExistenceRule = "EXISTS" | "NOT_EXISTS";

export type Rule = ComparisonRule | LogicalRule | ExistenceRule;

export interface BaseCondition {
  rule: Rule;
  path?: string;
}

export interface ComparisonCondition extends BaseCondition {
  rule: ComparisonRule;
  value: any;
}

export interface LogicalCondition extends BaseCondition {
  rule: LogicalRule;
  conditions: Condition[];
}

export interface ExistenceCondition extends BaseCondition {
  rule: ExistenceRule;
}

export type Condition =
  | ComparisonCondition
  | LogicalCondition
  | ExistenceCondition;

export function executeConditions({
  conditions,
  value,
}: {
  conditions?: Condition[];
  value: any;
}): boolean {
  // If no conditions, consider it a pass
  if (!conditions?.length) return true;

  // Run all conditions (implicit AND)
  return conditions.every((condition) => executeCondition(condition, value));
}

function executeCondition(condition: Condition, value: any): boolean {
  const targetValue = condition.path
    ? getValueFromPath(value, condition.path)
    : value;

  switch (condition.rule) {
    // Logical
    case "AND":
      return (condition as LogicalCondition).conditions.every((c) =>
        executeCondition(c, value),
      );
    case "OR":
      return (condition as LogicalCondition).conditions.some((c) =>
        executeCondition(c, value),
      );
    case "NOT":
      return !(condition as LogicalCondition).conditions.every((c) =>
        executeCondition(c, value),
      );

    // Comparison
    case "EQUALS":
      return targetValue === (condition as ComparisonCondition).value;
    case "NOT_EQUALS":
      return targetValue !== (condition as ComparisonCondition).value;
    case "GREATER_THAN":
      return targetValue > (condition as ComparisonCondition).value;
    case "LESS_THAN":
      return targetValue < (condition as ComparisonCondition).value;
    case "CONTAINS":
      return (
        Array.isArray(targetValue) &&
        targetValue.includes((condition as ComparisonCondition).value)
      );
    case "NOT_CONTAINS":
      return (
        Array.isArray(targetValue) &&
        !targetValue.includes((condition as ComparisonCondition).value)
      );
    case "MATCHES":
      return new RegExp((condition as ComparisonCondition).value).test(
        String(targetValue),
      );
    case "STARTS_WITH":
      return String(targetValue).startsWith(
        (condition as ComparisonCondition).value,
      );
    case "ENDS_WITH":
      return String(targetValue).endsWith(
        (condition as ComparisonCondition).value,
      );

    // Existence
    case "EXISTS":
      return targetValue !== undefined && targetValue !== null;
    case "NOT_EXISTS":
      return targetValue === undefined || targetValue === null;
  }
}

function getValueFromPath(obj: any, path: string): any {
  return path.split(".").reduce((acc, part) => acc?.[part], obj);
}
