import { z } from 'zod/v4';
import { PlexSearchSchema } from './plexSearch.js';

const SlotProgrammingOrderSchema = z.enum([
  'next',
  'shuffle',
  'ordered_shuffle',
  'alphanumeric',
  'chronological',
]);

//
// Base slots
//
const MovieProgrammingSlotSchema = z.object({
  type: z.literal('movie'),
});

const ShowProgrammingSlotSchema = z.object({
  type: z.literal('show'),
  showId: z.string(),
});

const FlexProgrammingSlotSchema = z.object({
  type: z.literal('flex'),
});

const RedirectProgrammingSlotSchema = z.object({
  type: z.literal('redirect'),
  channelId: z.string(),
  channelName: z.string().optional(),
});

const CustomShowProgrammingSlotSchema = z.object({
  type: z.literal('custom-show'),
  customShowId: z.string().uuid(),
});

export const BaseSlotSchema = z.object({
  order: SlotProgrammingOrderSchema,
  direction: z.enum(['asc', 'desc']).default('asc'),
  programming: z.discriminatedUnion('type', [
    MovieProgrammingSlotSchema,
    ShowProgrammingSlotSchema,
    FlexProgrammingSlotSchema,
    RedirectProgrammingSlotSchema,
    CustomShowProgrammingSlotSchema,
  ]),
});

export type BaseSlot = z.infer<typeof BaseSlotSchema>;

//
// Time slots
//

export const MovieProgrammingTimeSlotSchema = MovieProgrammingSlotSchema;
export const ShowProgrammingTimeSlotSchema = ShowProgrammingSlotSchema;
export const FlexProgrammingTimeSlotSchema = FlexProgrammingSlotSchema;
export const RedirectProgrammingTimeSlotSchema = RedirectProgrammingSlotSchema;
export const CustomShowProgrammingTimeSlotSchema =
  CustomShowProgrammingSlotSchema;

export type MovieProgrammingTimeSlot = z.infer<
  typeof MovieProgrammingTimeSlotSchema
>;

export type ShowProgrammingTimeSlot = z.infer<
  typeof ShowProgrammingTimeSlotSchema
>;

export type FlexProgrammingTimeSlot = z.infer<
  typeof FlexProgrammingTimeSlotSchema
>;

export type CustomShowProgrammingTimeSlot = z.infer<
  typeof CustomShowProgrammingRandomSchema
>;

export type RedirectProgrammingTimeSlot = z.infer<
  typeof RedirectProgrammingRandomSlotSchema
>;

export const TimeSlotProgrammingSchema = z.discriminatedUnion('type', [
  MovieProgrammingTimeSlotSchema,
  ShowProgrammingTimeSlotSchema,
  FlexProgrammingTimeSlotSchema,
  RedirectProgrammingTimeSlotSchema,
  CustomShowProgrammingTimeSlotSchema,
]);

export type TimeSlotProgramming = z.infer<typeof TimeSlotProgrammingSchema>;

export const TimeSlotSchema = z.object({
  order: SlotProgrammingOrderSchema,
  direction: z.enum(['asc', 'desc']).default('asc'),
  programming: TimeSlotProgrammingSchema,
  startTime: z.number(), // Offset from midnight in millis
});

export type TimeSlot = z.infer<typeof TimeSlotSchema>;

export const TimeSlotScheduleSchema = z.object({
  type: z.literal('time'),
  flexPreference: z.enum(['distribute', 'end']),
  latenessMs: z.number(), // max lateness in millis
  maxDays: z.number(), // days to pregenerate schedule for
  padMs: z.number(), // Pad time in millis
  period: z.enum(['day', 'week', 'month']),
  slots: z.array(TimeSlotSchema),
  timeZoneOffset: z.number(), // tz offset in...minutes, i think?
  startTomorrow: z.boolean().optional(),
});

export type TimeSlotSchedule = z.infer<typeof TimeSlotScheduleSchema>;

//
// Random slots
//

export const MovieProgrammingRandomSlotSchema = MovieProgrammingSlotSchema;
export const ShowProgrammingRandomSlotSchema = ShowProgrammingSlotSchema;
export const FlexProgrammingRandomSlotSchema = FlexProgrammingSlotSchema;
export const RedirectProgrammingRandomSlotSchema =
  RedirectProgrammingSlotSchema;
export const CustomShowProgrammingRandomSchema =
  CustomShowProgrammingSlotSchema;

export type MovieProgrammingRandomSlot = z.infer<
  typeof MovieProgrammingRandomSlotSchema
>;

export type ShowProgrammingRandomSlot = z.infer<
  typeof ShowProgrammingRandomSlotSchema
>;

export type FlexProgrammingRandomSlot = z.infer<
  typeof FlexProgrammingRandomSlotSchema
>;

export type RedirectProgrammingRandomSlot = z.infer<
  typeof RedirectProgrammingRandomSlotSchema
>;

export type CustomShowProgrammingRandom = z.infer<
  typeof CustomShowProgrammingRandomSchema
>;

export const RandomSlotProgrammingSchema = z.discriminatedUnion('type', [
  MovieProgrammingRandomSlotSchema,
  ShowProgrammingRandomSlotSchema,
  FlexProgrammingRandomSlotSchema,
  RedirectProgrammingRandomSlotSchema,
  CustomShowProgrammingRandomSchema,
]);

export type RandomSlotProgramming = z.infer<typeof RandomSlotProgrammingSchema>;

export const RandomSlotFixedDurationSpecSchema = z.object({
  durationMs: z.number(),
  type: z.literal('fixed'),
});

export const RandomSlotDynamicDurationspecSchema = z.object({
  type: z.literal('dynamic'),
  programCount: z.number().min(1),
});

export const RandomSlotDurationSpec = z.discriminatedUnion('type', [
  RandomSlotFixedDurationSpecSchema,
  RandomSlotDynamicDurationspecSchema,
]);

export const RandomSlotSchema = z.object({
  order: SlotProgrammingOrderSchema,
  direction: z.enum(['asc', 'desc']).default('asc'),
  startTime: z.number().optional(), // Offset from midnight millis
  cooldownMs: z.number(),
  periodMs: z.number().optional(),
  // Deprecated -  use durationSpec
  durationMs: z.number().optional(),
  durationSpec: RandomSlotDurationSpec.default({
    type: 'dynamic',
    programCount: 1,
  }),
  weight: z.number(),
  programming: RandomSlotProgrammingSchema,
  index: z.number().optional(),
});

export type RandomSlot = z.infer<typeof RandomSlotSchema>;

export const RandomSlotDistributionTypeSchema = z.enum([
  'uniform',
  'weighted',
  'none',
]);

export type RandomSlotDistributionType = z.infer<
  typeof RandomSlotDistributionTypeSchema
>;

export const RandomSlotScheduleSchema = z.object({
  type: z.literal('random'),
  flexPreference: z.enum(['distribute', 'end']),
  maxDays: z.number(),
  padMs: z.number(),
  padStyle: z.enum(['slot', 'episode']),
  slots: z.array(RandomSlotSchema),
  timeZoneOffset: z.number().optional(), // Timezone offset in minutes
  randomDistribution: RandomSlotDistributionTypeSchema,
  periodMs: z.number().optional(),
  // Purely for UI purposes. Adjusting weight of one program affects the
  // weights of all others if lock weights === true.
  lockWeights: z.boolean().default(false),
});

export type RandomSlotSchedule = z.infer<typeof RandomSlotScheduleSchema>;

//
// Dynamic content
//
export const DynamicContentCronUpdaterConfigSchema = z.object({
  // Unique ID to track scheduling. Not for use outside of bookkeeping
  _id: z.string(),
  type: z.literal('cron'),
  // Cron schedule string compatible with node-schedule
  schedule: z.string(),
});

export type DynamicContentCronUpdaterConfig = z.infer<
  typeof DynamicContentCronUpdaterConfigSchema
>;

export const DynamicContentUpdaterConfigSchema = z.discriminatedUnion('type', [
  DynamicContentCronUpdaterConfigSchema,
]);

export type DynamicContentUpdaterConfig = z.infer<
  typeof DynamicContentUpdaterConfigSchema
>;

const WithEnabledSchema = z.object({
  enabled: z.boolean().default(true).catch(true),
});

export const DynamicContentConfigPlexSourceSchema = z
  .object({
    type: z.literal('plex'),
    plexServerId: z.string().min(1), // server name or unique ID
    plexLibraryKey: z.string().min(1),
    search: PlexSearchSchema.optional(),
    updater: DynamicContentUpdaterConfigSchema,
  })
  .merge(WithEnabledSchema);

export type DynamicContentConfigPlexSource = z.infer<
  typeof DynamicContentConfigPlexSourceSchema
>;

export const DynamicContentConfigSourceSchema = z.discriminatedUnion('type', [
  DynamicContentConfigPlexSourceSchema,
]);

export type DynamicContentConfigSource = z.infer<
  typeof DynamicContentConfigSourceSchema
>;

export const DynamicContentConfigSchema = z
  .object({
    contentSources: z.array(DynamicContentConfigSourceSchema).nonempty(),
  })
  .merge(WithEnabledSchema);

export type DynamicContentConfig = z.infer<typeof DynamicContentConfigSchema>;

//
// Lineups
//
export const LineupScheduleSchema = z.discriminatedUnion('type', [
  TimeSlotScheduleSchema,
  RandomSlotScheduleSchema,
]);

export type LineupSchedule = z.infer<typeof LineupScheduleSchema>;

//
// Tools
//

const BaseSchedulingOpertionSchema = z.object({
  allowMultiple: z.boolean().default(true).optional(),
});

const BaseSortOperationSchema = (defaultAsc: boolean = true) =>
  z.object({
    type: z.literal('ordering'),
    ascending: z.boolean().optional().default(defaultAsc).catch(defaultAsc),
  });

const RandomSortOrderOperationSchema = BaseSchedulingOpertionSchema.extend({
  type: z.literal('ordering'),
  id: z.literal('random_sort'),
});

export type RandomSortOrderOperation = z.infer<
  typeof RandomSortOrderOperationSchema
>;

const ReleaseDateSortOrderOperationSchema = BaseSchedulingOpertionSchema.merge(
  BaseSortOperationSchema(),
).extend({
  id: z.literal('release_date_sort'),
});

export type ReleaseDateSortOrderOperation = z.infer<
  typeof ReleaseDateSortOrderOperationSchema
>;

const ScheduledRedirectOperationSchema = BaseSchedulingOpertionSchema.extend({
  type: z.literal('modifier'),
  id: z.literal('scheduled_redirect'),
  channelId: z.string().uuid(),
  startHour: z.number().min(0).max(23),
  // Anything less than 30 mins doesn't really make sense?
  // And can't schedule more than 24 hours either...
  duration: z
    .number()
    .min(15 * 60 * 1000)
    .max(24 * 60 * 60 * 1000),
});

export type ScheduledRedirectOperation = z.infer<
  typeof ScheduledRedirectOperationSchema
>;

const AddPaddingOperationSchema = BaseSchedulingOpertionSchema.extend({
  type: z.literal('modifier'), // not sure I like this name yet
  id: z.literal('add_padding'), // every operation needs a unique ID
  mod: z.number(),
  allowedOffsets: z.array(z.number()).optional(),
  alignChannelStartTime: z.boolean().default(false),
  allowMultiple: z.literal(false).default(false).optional(),
});

export type AddPaddingOperation = z.infer<typeof AddPaddingOperationSchema>;

export const SchedulingOperationSchema = z.union([
  AddPaddingOperationSchema,
  ScheduledRedirectOperationSchema,
  RandomSortOrderOperationSchema,
  ReleaseDateSortOrderOperationSchema,
]);

export type SchedulingOperation = z.infer<typeof SchedulingOperationSchema>;
