import { z } from "zod";
import parseDuration from "parse-duration";

const DurationString = z.string().refine(
  (val) => {
    const ms = parseDuration(val);
    return ms !== null && ms > 0;
  },
  (val) => ({ message: `Invalid or non-positive duration string: "${val}"` })
);

const BracketSchema = z.object({
  max: z.union([z.literal("Infinity"), DurationString]),
  granularity: DurationString,
});

const BracketsSchema = z
  .array(BracketSchema)
  .min(1, "TimeGranularity requires at least one bracket");

export type TimeGranularityBracket = z.input<typeof BracketSchema>;

type ParsedBracket = {
  maxMs: number;
  granularityMs: number;
};

function requireParsedDuration(input: string): number {
  const ms = parseDuration(input);
  if (ms === null || ms <= 0) {
    throw new Error(`Duration must be strictly positive, got "${input}" (${ms}ms)`);
  }
  return ms;
}

export class TimeGranularity {
  private readonly parsed: ParsedBracket[];

  constructor(brackets: TimeGranularityBracket[]) {
    const validated = BracketsSchema.parse(brackets);

    this.parsed = validated.map((b) => ({
      maxMs: b.max === "Infinity" ? Infinity : requireParsedDuration(b.max),
      granularityMs: requireParsedDuration(b.granularity),
    }));
  }

  getTimeGranularityMs(from: Date, to: Date): number {
    if (from.getTime() > to.getTime()) {
      return this.parsed[this.parsed.length - 1].granularityMs;
    }

    const rangeMs = to.getTime() - from.getTime();
    for (const bracket of this.parsed) {
      if (rangeMs <= bracket.maxMs) {
        return bracket.granularityMs;
      }
    }
    return this.parsed[this.parsed.length - 1].granularityMs;
  }
}
