import Image from "next/image";
import Link from "next/link";
import { ArrowUpRightIcon, ChevronLeftIcon } from "lucide-react";
import type { ReactNode } from "react";
import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
import { Kbd } from "@/components/ui/kbd";
import {
Card,
CardAction,
CardContent,
CardDescription,
CardHeader,
CardTitle,
} from "@/components/ui/card";
import { Separator } from "@/components/ui/separator";
import {
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow,
} from "@/components/ui/table";
import { SiteFooter } from "@/components/public/site-footer";
import { SiteHeader } from "@/components/public/site-header";
import { ScrollableNav } from "@/components/public/scrollable-nav";
import { TeamSearch } from "@/components/public/team-search";
import { GuideImageExport } from "@/components/public/guide-image-export";
import { GuideExportBranding } from "@/components/public/guide-export-branding";
import { ConstellationImage } from "@/components/public/constellation-image";
import { RichTextContent } from "@/components/rich-text-content";
import { LiveRefresh } from "@/components/live-refresh";
import { rarityGradientClass, rarityTextClass } from "@/lib/catalog/rarity";
import { elementIconFilter } from "@/lib/catalog/element";
import type { PublicGuide } from "@/lib/guides/queries";
import { artifactTierCount } from "@/lib/guides/artifacts";
import { navigationIconPath } from "@/lib/navigation";
import {
normalizeTargetStat,
recommendedStatElement,
recommendedStatIconName,
statPriorityColorClass,
targetStatIconName,
} from "@/lib/guides/stats";
import {
normalizeTalentPriorities,
talentRelationSymbol,
talentTypeLabel,
} from "@/lib/guides/talents";
import { parseYouTubeUrl } from "@/lib/guides/youtube";
import { normalizeExtraRichText } from "@/lib/rich-text";
import { cn } from "@/lib/utils";
function hasSection(guide: PublicGuide, slug: string): boolean {
if (slug === "weapon") return guide.weapons.length > 0;
if (slug === "artifact")
return (
guide.artifactGroups.some((group) => group.rows.length > 0) ||
Boolean(
guide.artifactProfile &&
(guide.artifactProfile.sands.length ||
guide.artifactProfile.goblet.length ||
guide.artifactProfile.circlet.length),
)
);
if (slug === "constellations") return guide.constellationGroups.some((group) => group.rows.length > 0);
if (slug === "team") return guide.teams.length > 0;
return guide.extras.some(
(extra) => extra.slug === slug && extra.rows.length > 0,
);
}
export function publicSections(guide: PublicGuide) {
return guide.sections.filter((section) => hasSection(guide, section.slug));
}
function publicSectionTitle(slug: string, fallback: string): string {
if (slug === "weapon") return "อาวุธ";
if (slug === "artifact") return "อาร์ติแฟกต์และค่าสถานะ";
if (slug === "constellations") return "กลุ่มดาว";
if (slug === "team") return "คำนวณทีม";
return fallback;
}
export function StructuredGuideShell({
guide,
section,
}: {
guide: PublicGuide;
section?: string;
}) {
const sections = publicSections(guide);
const currentPageTitle = section
? (sections.find((item) => item.slug === section)?.title ?? section)
: "Overview";
return (
{guide.name}
{currentPageTitle}
Overview
{sections.map((item) => (
{navigationIconPath(item.slug) ? (
) : null}
{item.title}
))}
{section ? (
) : (
)}
);
}
function GuideOverview({
guide,
sections,
}: {
guide: PublicGuide;
sections: ReturnType;
}) {
const youtubeVideo = guide.youtubeUrl
? parseYouTubeUrl(guide.youtubeUrl)
: null;
return (
ภาพรวมแนวทาง
{guide.name}
{guide.overview}
{sections.length ? (
) : (
กำลังจัดทำเนื้อหา
กลับมาตรวจสอบอีกครั้งหลังผู้ดูแลเพิ่มข้อมูล
)}
{youtubeVideo ? (
}
nativeButton={false}
>
ดูวิดีโอต้นฉบับบน YouTube
) : null}
);
}
function GuideSection({
guide,
section,
}: {
guide: PublicGuide;
section: string;
}) {
if (section === "weapon") return ;
if (section === "artifact") return ;
if (section === "constellations")
return ;
if (section === "team") return ;
const extra = guide.extras.find((item) => item.slug === section);
return extra ? : null;
}
function metric(value: string | null) {
return value === null
? null
: `${Number(value).toLocaleString(undefined, { maximumFractionDigits: 2 })}%`;
}
function lunarisItemUrl(
kind: "character" | "weapon" | "artifact",
key: string,
): string {
const path = kind === "character" ? "char" : kind;
return `https://lunaris.moe/${path}/${encodeURIComponent(key)}`;
}
function WeaponSection({ guide }: { guide: PublicGuide }) {
return ;
}
function WeaponSectionContent({
guide,
exporting = false,
groups: providedGroups,
conditionNumber,
showExportActions = false,
}: {
guide: PublicGuide;
exporting?: boolean;
groups?: PublicGuide["weaponGroups"];
conditionNumber?: number;
showExportActions?: boolean;
}) {
const note = guide.sections.find(
(section) => section.slug === "weapon",
)?.note;
const weaponGroups = (providedGroups ?? guide.weaponGroups).filter(
(group) => group.rows.length > 0,
);
const useCompactExportGrid =
exporting &&
weaponGroups.reduce((total, group) => total + group.rows.length, 0) > 6;
return (
{exporting ? (
) : null}
{exporting ? (
) : null}
อาวุธที่แนะนำ
Weapons
{note ? (
{note}
) : null}
{weaponGroups.map((group, groupIndex, groups) => (
{groups.length > 1 || conditionNumber ? (
Condition {conditionNumber ?? groupIndex + 1}
) : }
{showExportActions ? (
1 ? groupIndex + 1 : undefined}
/>
) : null}
{group.note ?
{group.note}
: null}
))}
);
}
function ArtifactSection({ guide }: { guide: PublicGuide }) {
return (
}
/>
);
}
function ArtifactSectionContent({
guide,
action,
exporting = false,
}: {
guide: PublicGuide;
action?: ReactNode;
exporting?: boolean;
}) {
const profile = guide.artifactProfile;
return (
{exporting ? (
) : null}
{" "}
{profile ? (
ออฟหลักที่ดีที่สุด
) : null}
{profile?.substats.length ? (
ออฟรองที่ดีที่สุด
{profile.substats.map((stat, index) => (
))}
) : null}
{guide.artifactGroups.map((group) => (
{group.name === "Late Game"
? "เลทเกม"
: group.name === "Early Game"
? "ต้นเกม"
: group.name}
{group.rows.map((row, index) => (
{artifactTierCount(row.second?.name ?? null)}{" "}
{row.first.name}
{row.second ? ` + ${row.second.name}` : ""}
{row.note ? (
{row.note}
) : null}
{Number(row.percent) === 0 ? "-%" : metric(row.percent)}
))}
))}
{profile ? (
{profile.targets.length ? (
ค่าสถานะต่ำๆที่ควรจะปั้นไว้
{profile.targets.map((target, index) => (
{targetStatIconName(target.stat) ? (
) : null}
{statLabel(
normalizeTargetStat(target.stat) ?? target.stat,
)}
{target.value}
))}
) : null}
{profile.talents.length ? (
ลำดับการอัพพรสวรรค์
{normalizeTalentPriorities(profile.talents).map(
(talent, index, talents) => {
const catalogTalent = guide.character.talents.find(
(item) => item.type === talent.type,
);
const talentName = catalogTalent?.name ?? talent.type;
return (
{catalogTalent?.iconUrl ? (
) : (
{talent.type}
)}
{talent.level
? `เลเวล ${talent.level}+`
: "-"}
{talentName}
{talentTypeLabel(talent.type)}
{index < talents.length - 1 ? (
{talentRelationSymbol(talent.relationToNext)}
) : null}
);
},
)}
) : null}
{profile.note ? (
) : null}
) : null}
);
}
function statLabel(value: string): string {
const labels: Record = {
HP: "พลังชีวิต",
ATK: "พลังโจมตี",
DEF: "พลังป้องกัน",
EM: "ความชำนาญธาตุ",
ER: "ฟื้นฟูพลังงาน",
"CRIT RATE": "อัตราคริ",
"CRIT DAMAGE": "ความแรงคริ",
"ATK%": "พลังโจมตี%",
"HP%": "พลังชีวิต%",
"DEF%": "พลังป้องกัน%",
"CRIT Rate%/CRIT DMG%": "อัตราคริ/ความแรงคริ",
"Pyro DMG%": "ความเสียหายธาตุไฟ%",
"Hydro DMG%": "ความเสียหายธาตุน้ำ%",
"Cryo DMG%": "ความเสียหายธาตุน้ำแข็ง%",
"Electro DMG%": "ความเสียหายธาตุไฟฟ้า%",
"Anemo DMG%": "ความเสียหายธาตุลม%",
"Geo DMG%": "ความเสียหายธาตุดิน%",
"Dendro DMG%": "ความเสียหายธาตุไม้%",
"Physical DMG%": "ความเสียหายกายภาพ%",
"Healing Bonus%": "โบนัสการรักษา%",
};
return labels[value] ?? value;
}
function ArtifactStat({
iconSrc,
label,
values,
exporting = false,
elementIconUrls,
}: {
iconSrc: string;
label: string;
values: string[];
exporting?: boolean;
elementIconUrls: Record;
}) {
return (
{label}
{values.length ? (
values.map((value, index) => (
))
) : (
-
)}
);
}
function ArtifactStatValue({
value,
index,
exporting = false,
elementIconUrls,
}: {
value: string;
index: number;
exporting?: boolean;
elementIconUrls: Record;
}) {
const iconName = recommendedStatIconName(value);
const element = recommendedStatElement(value);
const elementIconUrl = element ? elementIconUrls[element] : null;
const hasIcon = Boolean(iconName);
return (
{elementIconUrl ? (
) : hasIcon ? (
) : null}
{statLabel(value)}
);
}
function ConstellationSection({ guide }: { guide: PublicGuide }) {
const note = guide.sections.find(
(section) => section.slug === "constellations",
)?.note;
return (
กลุ่มดาว
กลุ่มดาว
{note ? (
{note}
) : null}
{guide.constellationGroups.filter((group) => group.rows.length > 0).map((group, groupIndex, groups) => (
1 ? `-Condition-${groupIndex + 1}` : ""}.png`}
width={1500}
height={1500}
captureClassName="p-0"
>
{groups.length > 1 ?
Condition {groupIndex + 1}
: null}
{group.note ?
{group.note}
: null}
{group.rows.map((row) => (
{row.iconUrl ? (
) : null}
{metric(row.cumulativePercent)}
C{row.level} · {row.name}
{row.detail}
))}
))}
);
}
function TeamSection({ guide }: { guide: PublicGuide }) {
return (
คำนวณความเสียหาย
คำนวณทีม
);
}
function ExtraSection({
guide,
extra,
}: {
guide: PublicGuide;
extra: PublicGuide["extras"][number];
}) {
return (
}
/>
);
}
function ExtraSectionContent({
guide,
extra,
action,
exporting = false,
}: {
guide: PublicGuide;
extra: PublicGuide["extras"][number];
action?: React.ReactNode;
exporting?: boolean;
}) {
return (
{exporting ? (
) : null}
คู่มือเสริม
{extra.title}
{action}
{extra.rows.map((row) => (
{row.imageUrl ? (
) : null}
{row.catalogItems.length ? (
{row.catalogItems.map((item) => (
{item.imageUrl ? (
) : null}
{item.name}
{!item.available ?
ไม่พบรายการ : null}
))}
) : null}
{row.heading}
{row.percent ? {metric(row.percent)} : null}
{row.sheet ? (
{row.sheet.columns.map((column) => (
{column.label}
))}
{row.sheet.rows.map((sheetRow) => (
{row.sheet!.columns.map((column) => (
))}
))}
) : null}
))}
);
}