import {
  BooleanExpression,
  Expression,
  FloatExpression,
  IntExpression,
  ListExpression,
  NoOpExpression,
  ObjectExpression,
  ObjectValue,
  StringExpression,
  SwitchExpression,
  Value,
} from "../types";
import uniqueId from "./uniqueId";

// When modifying expression constructors, ensure that they properly thread
// through optional fields like isTransient and don't accidentally drop them

export function getAnonymousExpression(
  value: Value,
  isTransient?: boolean
): Expression {
  if (typeof value === "boolean") {
    return getBooleanExpression(value, isTransient);
  }
  if (typeof value === "number") {
    if (Number.isInteger(value)) {
      return getIntExpression(value, isTransient);
    }
    return getFloatExpression(value, isTransient);
  }
  if (typeof value === "string") {
    return getStringExpression(value, isTransient);
  }
  if (Array.isArray(value)) {
    return getAnonymousListExpression(value, isTransient);
  }
  if (typeof value === "object") {
    return getAnonymousObjectExpression(value, isTransient);
  }
  const neverValue: never = value;
  throw new Error(`Unexpected value: ${neverValue}`);
}

export function getNoOpExpression(isTransient?: boolean): NoOpExpression {
  const expression: NoOpExpression = {
    id: uniqueId(),
    isTransient,
    type: "NoOpExpression",
    valueType: { type: "VoidValueType" },
  };
  return expression;
}

export function getBooleanIfExpression(
  value: boolean,
  isTransient?: boolean
): SwitchExpression {
  const expression: SwitchExpression = {
    id: uniqueId(),
    isTransient,
    type: "SwitchExpression",
    valueType: { type: "BooleanValueType" },
    control: getBooleanExpression(true),
    cases: [],
    default: getBooleanExpression(value, isTransient),
  };
  return expression;
}

export function getBooleanExpression(
  value: boolean,
  isTransient?: boolean
): BooleanExpression {
  const expression: BooleanExpression = {
    id: uniqueId(),
    isTransient,
    type: "BooleanExpression",
    valueType: { type: "BooleanValueType" },
    value,
  };
  return expression;
}

export function getIntExpression(
  value: number,
  isTransient?: boolean
): IntExpression {
  const expression: IntExpression = {
    id: uniqueId(),
    isTransient,
    type: "IntExpression",
    valueType: { type: "IntValueType" },
    value: Math.round(value),
  };
  return expression;
}

export function getFloatExpression(
  value: number,
  isTransient?: boolean
): FloatExpression {
  const expression: FloatExpression = {
    id: uniqueId(),
    isTransient,
    type: "FloatExpression",
    valueType: { type: "FloatValueType" },
    value,
  };
  return expression;
}

export function getStringExpression(
  value: string,
  isTransient?: boolean
): StringExpression {
  const expression: StringExpression = {
    id: uniqueId(),
    isTransient,
    type: "StringExpression",
    valueType: { type: "StringValueType" },
    value,
  };
  return expression;
}

export function getAnonymousListExpression(
  value: Value[],
  isTransient?: boolean
): ListExpression {
  const expression: ListExpression = {
    id: uniqueId(),
    isTransient,
    type: "ListExpression",
    valueType: {
      type: "ListValueType",
      itemValueType: { type: "VoidValueType" },
    }, // Dummy
    items: value.map((item) => getAnonymousExpression(item, isTransient)),
  };
  return expression;
}

export function getAnonymousObjectExpression(
  value: ObjectValue,
  isTransient?: boolean
): ObjectExpression {
  const objectTypeName = ""; // Dummy
  const expression: ObjectExpression = {
    id: uniqueId(),
    isTransient,
    type: "ObjectExpression",
    valueType: { type: "ObjectValueType", objectTypeName },
    objectTypeName,
    fields: Object.fromEntries(
      Object.entries(value).map(([fieldName, fieldValue]) => [
        fieldName,
        getAnonymousExpression(fieldValue, isTransient),
      ])
    ),
  };
  return expression;
}
