import { z } from "zod";

import { datum_schema } from "./datum.svelte.ts";

export const all_expression_types = [
  "Comparison",
  "Identifier",
  "Literal",
  "ToJson",
] as const;
// Type checking to ensure that all valid enum tags are included.
type Assert<_T extends true> = void;
type _ = Assert<PgExpressionAny["t"] extends PgExpressionType ? true : false>;

export const expression_type_schema = z.enum(all_expression_types);

export const all_infix_comparison_operators = [
  "Eq",
  "Neq",
  "Gt",
  "Lt",
  "And",
  "Or",
] as const;

const pg_comparison_operator_schema = z.enum(all_infix_comparison_operators);

const pg_infix_expression_schema = z.object({
  operator: z.union([pg_comparison_operator_schema]),
  get lhs() {
    return pg_expression_any_schema.optional();
  },
  get rhs() {
    return pg_expression_any_schema.optional();
  },
});

const pg_comparison_expression_infix_schema = z.object({
  t: z.literal("Infix"),
  c: pg_infix_expression_schema,
});

const pg_is_null_expression_schema = z.object({
  get lhs() {
    return pg_expression_any_schema.optional();
  },
});

const pg_comparison_expression_is_null_schema = z.object({
  t: z.literal("IsNull"),
  c: pg_is_null_expression_schema,
});

const pg_is_not_null_expression_schema = z.object({
  get lhs() {
    return pg_expression_any_schema.optional();
  },
});

const pg_comparison_expression_is_not_null_schema = z.object({
  t: z.literal("IsNotNull"),
  c: pg_is_not_null_expression_schema,
});

const pg_comparison_expression_schema = z.union([
  pg_comparison_expression_infix_schema,
  pg_comparison_expression_is_null_schema,
  pg_comparison_expression_is_not_null_schema,
]);

const pg_expression_any_comparison_schema = z.object({
  t: z.literal("Comparison"),
  c: pg_comparison_expression_schema,
});

const pg_identifier_expression_schema = z.object({
  parts_raw: z.array(z.string()),
});

const pg_expression_any_identifier_schema = z.object({
  t: z.literal("Identifier"),
  c: pg_identifier_expression_schema,
});

const pg_expression_any_literal_schema = z.object({
  t: z.literal("Literal"),
  c: datum_schema,
});

const pg_to_json_expression_schema = z.object({
  get entries() {
    return z.array(z.tuple([z.string(), pg_expression_any_schema.optional()]));
  },
});

const pg_expression_any_to_json_expression_schema = z.object({
  t: z.literal("ToJson"),
  c: pg_to_json_expression_schema,
});

export const pg_expression_any_schema = z.union([
  pg_expression_any_comparison_schema,
  pg_expression_any_identifier_schema,
  pg_expression_any_literal_schema,
  pg_expression_any_to_json_expression_schema,
]);

export type PgExpressionAny = z.infer<typeof pg_expression_any_schema>;
export type PgExpressionType = z.infer<typeof expression_type_schema>;

export function expression_human_name(expr_type: PgExpressionType): string {
  if (expr_type === "Comparison") {
    return "Condition";
  }
  if (expr_type === "Identifier") {
    return "Identifier";
  }
  if (expr_type === "Literal") {
    return "Literal";
  }
  if (expr_type === "ToJson") {
    return "JSON";
  }
  // Type guard to check for exhaustive matching.
  type _ = Assert<typeof expr_type extends never ? true : false>;
  throw new Error("this should be unreachable");
}

export function expression_icon(expr: PgExpressionAny): {
  html: string;
  label: string;
} {
  if (expr.t === "Comparison") {
    if (expr.c.t === "Infix") {
      const op = expr.c.c.operator;
      if (op === "And") {
        return { html: "&&", label: "And" };
      }
      if (op === "Eq") {
        return { html: "=", label: "Is Equal To" };
      }
      if (op === "Gt") {
        return { html: ">", label: "Is Greater Than" };
      }
      if (op === "Lt") {
        return { html: "<", label: "Is Less Than" };
      }
      if (op === "Or") {
        return { html: "||", label: "Or" };
      }
      if (op === "Neq") {
        return { html: "\u2260", label: "Is Not Equal To" };
      }
      // Type guard to check for exhaustive matching.
      type _ = Assert<typeof op extends never ? true : false>;
      throw new Error("this should be unreachable");
    } else if (expr.c.t === "IsNull") {
      return { html: '<i class="ti ti-cube-3d-sphere-off"></i>', label: "Is Null" };
    } else if (expr.c.t === "IsNotNull") {
      return { html: '<i class="ti ti-cube"></i>', label: "Is Not Null" };
    }
    // Type guard to check for exhaustive matching.
    type _ = Assert<typeof expr.c extends never ? true : false>;
    throw new Error("this should be unreachable");
  } else if (expr.t === "Identifier") {
    return { html: '<i class="ti ti-variable"></i>', label: "Dynamic Value" };
  } else if (expr.t === "Literal") {
    return { html: '<i class="ti ti-hash"></i>', label: "Static Value" };
  } else if (expr.t === "ToJson") {
    return { html: '<i class="ti ti-code"></i>', label: "JSON String" };
  }
  // Type guard to check for exhaustive matching.
  type _ = Assert<typeof expr extends never ? true : false>;
  throw new Error("this should be unreachable");
}
