import { z } from "zod"

export const GOAL_FREQUENCIES_ZOD = [
  z.literal("daily"),
  z.literal("weekly"),
  z.literal("monthly"),
] as const
export const goalFrequencyZod = z.union(GOAL_FREQUENCIES_ZOD)
export const GOAL_FREQUENCIES = GOAL_FREQUENCIES_ZOD.map((frequency) => {
  return frequency.value
})
export type GoalFrequency = (typeof GOAL_FREQUENCIES)[number]

export const GOAL_FREQUENCIES_TYPES = {
  daily: "day",
  weekly: "week",
  monthly: "month",
} as const

export const GOAL_TYPES_ZOD = [
  z.literal("boolean"),
  z.literal("numeric"),
] as const
export const goalTypeZod = z.union(GOAL_TYPES_ZOD)
export const GOAL_TYPES = GOAL_TYPES_ZOD.map((type) => {
  return type.value
})
export type GoalType = (typeof GOAL_TYPES)[number]

export const GoalCreateSchema = z.object({
  frequency: goalFrequencyZod,
  target: z.discriminatedUnion("type", [
    z.object({ type: z.literal("boolean") }),
    z.object({
      type: z.literal("numeric"),
      value: z.number().int().min(1),
      unit: z.string().min(1),
    }),
  ]),
})

export type GoalCreateData = z.infer<typeof GoalCreateSchema>

interface GoalBase {
  frequency: GoalFrequency
}

export interface GoalBaseJSON extends GoalBase {
  type: GoalType
}
export abstract class Goal implements GoalBase {
  public frequency: GoalBase["frequency"]
  public abstract readonly type: GoalType

  public constructor(options: GoalBase) {
    const { frequency } = options
    this.frequency = frequency
  }

  public static create(options: GoalCreateData): Goal {
    if (options.target.type === "boolean") {
      return new GoalBoolean(options)
    }
    return new GoalNumeric({
      frequency: options.frequency,
      target: options.target,
    })
  }

  public static isNumeric(goal: Goal): goal is GoalNumeric {
    return goal.type === "numeric"
  }
  public isNumeric(): this is GoalNumeric {
    return Goal.isNumeric(this)
  }

  public static isBoolean(goal: Goal): goal is GoalBoolean {
    return goal.type === "boolean"
  }
  public isBoolean(): this is GoalBoolean {
    return Goal.isBoolean(this)
  }

  public abstract toJSON(): GoalBaseJSON
}

export interface GoalProgressBase {
  goal: Goal
}
export abstract class GoalProgress implements GoalProgressBase {
  public abstract readonly goal: Goal
  public abstract isCompleted(): boolean

  public abstract toJSON(): GoalProgressBase

  public static isNumeric(
    goalProgress: GoalProgress,
  ): goalProgress is GoalNumericProgress {
    return goalProgress.goal.isNumeric()
  }
  public isNumeric(): this is GoalNumericProgress {
    return GoalProgress.isNumeric(this)
  }

  public static isBoolean(
    goalProgress: GoalProgress,
  ): goalProgress is GoalBooleanProgress {
    return goalProgress.goal.isBoolean()
  }
  public isBoolean(): this is GoalBooleanProgress {
    return GoalProgress.isBoolean(this)
  }
}

interface GoalNumericOptions extends GoalBase {
  target: {
    value: number
    unit: string
  }
}
export class GoalNumeric extends Goal {
  public readonly type = "numeric"
  public target: {
    value: number
    unit: string
  }

  public constructor(options: GoalNumericOptions) {
    super(options)
    const { target } = options
    this.target = target
  }

  public override toJSON(): GoalNumericOptions & GoalBaseJSON {
    return {
      frequency: this.frequency,
      target: this.target,
      type: this.type,
    }
  }
}
interface GoalNumericProgressOptions extends GoalProgressBase {
  goal: GoalNumeric
  progress: number
}
export class GoalNumericProgress extends GoalProgress {
  public readonly goal: GoalNumeric
  public readonly progress: number

  public constructor(options: GoalNumericProgressOptions) {
    const { goal, progress } = options
    super()
    this.goal = goal
    this.progress = progress
  }

  public override isCompleted(): boolean {
    return this.progress >= this.goal.target.value
  }

  public progressRatio(): number {
    return this.goal.target.value <= 0
      ? 0
      : this.progress / this.goal.target.value
  }

  public override toJSON(): GoalNumericProgressOptions {
    return {
      goal: this.goal,
      progress: this.progress,
    }
  }
}

export class GoalBoolean extends Goal {
  public readonly type = "boolean"

  public override toJSON(): GoalBaseJSON {
    return {
      frequency: this.frequency,
      type: this.type,
    }
  }
}
interface GoalBooleanProgressOptions extends GoalProgressBase {
  goal: GoalBoolean
  progress: boolean
}
export class GoalBooleanProgress extends GoalProgress {
  public readonly goal: GoalBoolean
  public progress: boolean

  public constructor(options: GoalBooleanProgressOptions) {
    const { goal, progress } = options
    super()
    this.goal = goal
    this.progress = progress
  }

  public override isCompleted(): boolean {
    return this.progress
  }

  public override toJSON(): GoalBooleanProgressOptions {
    return {
      goal: this.goal,
      progress: this.progress,
    }
  }
}
