import * as z from "zod"; export const guideSectionKinds = ["weapon", "artifact", "constellations", "team"] as const; export type GuideSectionKind = (typeof guideSectionKinds)[number]; const percent = z.coerce.number().finite().min(0).max(9999); const catalogKey = z.string().trim().min(1).max(64); export const createGuideSchema = z.strictObject({ characterKey: catalogKey, coverMediaId: z.string().uuid(), overview: z.string().trim().max(500), }); export const updateGuideSchema = z.strictObject({ expectedVersion: z.number().int().positive(), coverMediaId: z.string().uuid(), overview: z.string().trim().min(1).max(500), }); export const weaponSectionSchema = z.strictObject({ expectedVersion: z.number().int().positive(), rows: z.array(z.strictObject({ weaponKey: catalogKey, refinement: z.number().int().min(1).max(5), overallPercent: percent.nullable(), personalPercent: percent.nullable(), }).refine((row) => row.overallPercent !== null || row.personalPercent !== null, { message: "อาวุธต้องมีเปอร์เซ็นต์อย่างน้อยหนึ่งค่า", })).max(100), }); export const mainStatOptions = { sands: ["EM", "ER", "ATK%", "HP%", "DEF%"], goblet: ["EM", "ATK%", "DEF%", "HP%", "Pyro DMG%", "Hydro DMG%", "Cryo DMG%", "Electro DMG%", "Anemo DMG%", "Geo DMG%", "Dendro DMG%", "Physical DMG%"], circlet: ["HP%", "ATK%", "DEF%", "EM", "CRIT Rate%/CRIT DMG%", "Healing Bonus%"], } as const; export const substatOptions = ["CRIT Rate%/CRIT DMG%", "HP%", "ATK%", "DEF%", "EM", "ER"] as const; export const talentLevelOptions = ["1", "2", "3", "4", "5", "6", "7", "8", "9", "10"] as const; const artifactGroupSchema = (name: "Late Game" | "Early Game") => z.strictObject({ name: z.literal(name), rows: z.array(z.strictObject({ firstArtifactKey: catalogKey, secondArtifactKey: catalogKey.nullable(), percent, })).max(50), }); export const artifactSectionSchema = z.strictObject({ expectedVersion: z.number().int().positive(), groups: z.tuple([ artifactGroupSchema("Late Game"), artifactGroupSchema("Early Game"), ]), sands: z.array(z.enum(mainStatOptions.sands)).max(2), goblet: z.array(z.enum(mainStatOptions.goblet)).max(2), circlet: z.array(z.enum(mainStatOptions.circlet)).max(2), substats: z.array(z.enum(substatOptions)).max(substatOptions.length), targets: z.array(z.strictObject({ stat: z.string().trim().min(1).max(80), value: z.string().trim().min(1).max(80), })).max(20), talents: z.array(z.strictObject({ type: z.enum(["NA", "E", "Q"]), priority: z.number().int().min(1).max(3), level: z.enum(talentLevelOptions).optional(), })).max(3), note: z.string().trim().max(2_000).nullable(), }); export const constellationSectionSchema = z.strictObject({ expectedVersion: z.number().int().positive(), rows: z.array(z.strictObject({ level: z.number().int().min(1).max(6), detail: z.string().trim().min(1).max(2_000), cumulativePercent: percent, })).max(6), }); export const teamSectionSchema = z.strictObject({ expectedVersion: z.number().int().positive(), teams: z.array(z.strictObject({ title: z.string().trim().min(1).max(160), rotationSeconds: z.coerce.number().finite().positive().max(9999), combo: z.string().trim().min(1).max(2_000), members: z.array(z.strictObject({ characterKey: catalogKey, weaponKey: catalogKey.nullable(), artifactKey: catalogKey.nullable(), constellation: z.number().int().min(0).max(6), refinement: z.number().int().min(1).max(5), damage: z.coerce.number().finite().nonnegative().max(999_999_999_999), })).length(4).refine((members) => new Set(members.map((member) => member.characterKey)).size === 4, { message: "Team ต้องมีตัวละครไม่ซ้ำกัน 4 ตัว", }).refine((members) => members.some((member) => member.damage > 0), { message: "ความเสียหายรวมต้องมากกว่า 0", }), })).max(30), }); export const extraSectionSchema = z.strictObject({ expectedVersion: z.number().int().positive(), sections: z.array(z.strictObject({ title: z.string().trim().min(1).max(160), rows: z.array(z.strictObject({ mediaId: z.string().uuid().nullable(), heading: z.string().trim().min(1).max(200), body: z.string().trim().max(2_000), percent: percent.nullable(), })).max(50), })).max(20), }); export type WeaponSectionInput = z.output; export type ArtifactSectionInput = z.output; export type ConstellationSectionInput = z.output; export type TeamSectionInput = z.output; export function slugFromCharacterName(name: string, key: string): string { const slug = name.normalize("NFKD").toLowerCase().replace(/[^a-z0-9]+/gu, "-").replace(/^-+|-+$/gu, ""); return slug || `character-${key.toLowerCase()}`; }