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 {
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 { TeamArtifactIcon, TeamSearch } from "@/components/public/team-search";
import { GuideImageExport } from "@/components/public/guide-image-export";
import { ExportText } from "@/components/public/export-language";
import { WeaponImageExport } from "@/components/public/weapon-image-export";
import { WeaponChart } from "@/components/public/weapon-chart";
import { WeaponDisplayMode, WeaponDisplayModeProvider } from "@/components/public/weapon-display-mode";
import { ArtifactImageExport } from "@/components/public/artifact-image-export";
import { GuideExportBranding } from "@/components/public/guide-export-branding";
import { ConstellationImage } from "@/components/public/constellation-image";
import { ConditionSwitcher } from "@/components/public/condition-switcher";
import { MaterialPoster } from "@/components/public/material-poster";
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,
} 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((condition) => condition.tiers.some((group) => group.rows.length > 0)) ||
Boolean(
guide.artifactProfile &&
(guide.artifactProfile.recommendedConditions.some((condition) => condition.sands.length || condition.goblet.length || condition.circlet.length || condition.substats.length) ||
guide.artifactProfile.sands.length ||
guide.artifactProfile.goblet.length ||
guide.artifactProfile.circlet.length ||
guide.artifactProfile.substats.length ||
guide.artifactProfile.targetConditions.some((group) => group.rows.length) ||
guide.artifactProfile.talents.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 youtubeVideos = guide.youtubeUrls.flatMap((url) => {
const video = parseYouTubeUrl(url);
return video ? [video] : [];
});
return (
ภาพรวมแนวทาง
{guide.name}
{guide.overview}
{guide.posterArtUrl || youtubeVideos.length ? (
<>
{guide.posterArtUrl ? (
) : null}
{youtubeVideos.length ? (
{youtubeVideos.map((youtubeVideo, index) => (
))}
) : null}
>
) : null}
);
}
function GuideNavigation({
guide,
sections,
}: {
guide: PublicGuide;
sections: ReturnType;
}) {
return sections.length ? (
) : (
กำลังจัดทำเนื้อหา
กลับมาตรวจสอบอีกครั้งหลังผู้ดูแลเพิ่มข้อมูล
);
}
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, locale?: string) {
return value === null
? null
: `${Number(value).toLocaleString(locale, { maximumFractionDigits: 2 })}%`;
}
function exportLabel(exporting: boolean, th: string, en: string): ReactNode {
return exporting ? : th;
}
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}
อาวุธที่แนะนำ
Weapons
{note ? (
{note}
) : null}
group.name || (exporting ? `Condition ${index + 1}` : `เงื่อนไข ${index + 1}`))}
>
{weaponGroups.map((group, groupIndex, groups) => (
{groups.length > 1 || conditionNumber ? (
{group.name || (exporting ? `Condition ${conditionNumber ?? groupIndex + 1}` : `เงื่อนไข ${conditionNumber ?? groupIndex + 1}`)}
) : }
{showExportActions ? (
) : null}
{group.note ?
{group.note}
: null}
: null}>
))}
);
}
function ArtifactSection({ guide }: { guide: PublicGuide }) {
const targetConditions = guide.artifactProfile?.targetConditions.filter(
(group) => group.rows.length,
) ?? [];
return (
group.name || `เงื่อนไข ${index + 1}`)}
targetConditionLabels={targetConditions.map((group, index) => group.name || `เงื่อนไข ${index + 1}`)}
tierConditionLabels={guide.artifactGroups.map((group, index) => group.name || `เงื่อนไข ${index + 1}`)}
>
}
/>
);
}
function ArtifactSectionContent({
guide,
action,
exporting = false,
}: {
guide: PublicGuide;
action?: ReactNode;
exporting?: boolean;
}) {
const profile = guide.artifactProfile;
const recommendedConditions = profile?.recommendedConditions ?? [];
const recommendedCards = recommendedConditions.map((condition, conditionIndex) => (
{exporting && recommendedConditions.length > 1 ?
{condition.name || }
: null}
{condition.note ?
{condition.note}
: null}
{profile ? (
{exportLabel(exporting, "ออฟหลักที่ดีที่สุด", "Best Main Stats")}
) : null}
{condition.substats.length ? (
{exportLabel(exporting, "ออฟรองที่ดีที่สุด", "Best Substats")}
{condition.substats.map((stat, index) => (
))}
) : null}
));
return (
{exporting ? (
) : null}
{" "}
{exporting ? recommendedCards : (
condition.name || `เงื่อนไข ${index + 1}`)}>
{recommendedCards}
)}
{(() => {
const tierCards = guide.artifactGroups.map((condition, conditionIndex) => (
{exporting && guide.artifactGroups.length > 1 ?
{condition.name || }
: null}
{condition.note ?
{condition.note}
: null}
{condition.tiers.map((group) =>
{group.name === "Late Game"
? exportLabel(exporting, "เลทเกม", "Late Game")
: group.name === "Early Game"
? exportLabel(exporting, "ต้นเกม", "Early Game")
: group.name}
{group.rows.map((row, index) => (
{artifactTierCount(row.second?.name ?? null)}{" "}
{row.first.name}
{row.second ? ` + 2 ${row.second.name}` : ""}
{row.note ? (
{row.note}
) : null}
{Number(row.percent) === 0 ? "-%" : metric(row.percent, exporting ? "en-US" : undefined)}
))}
)}
));
return exporting ? tierCards :
condition.name || `เงื่อนไข ${index + 1}`)}>{tierCards};
})()}
{profile ? (
{profile.targetConditions.some((group) => group.rows.length) ? (
exporting ? (
profile.targetConditions.filter((group) => group.rows.length).map((group, groupIndex) => (
{exportLabel(exporting, "ค่าสถานะต่ำๆที่ควรจะปั้นไว้", "Recommended Minimum Stats")}
{group.note ?
{group.note}
: null}
{group.rows.map((target, index) => (
{targetStatIconName(target.stat) ? (
) : null}
{statLabel(
normalizeTargetStat(target.stat) ?? target.stat, exporting,
)}
{target.value}
))}
))
) : (
group.rows.length).map((group, index) => group.name || `เงื่อนไข ${index + 1}`)}>
{profile.targetConditions.filter((group) => group.rows.length).map((group, groupIndex) => (
{exportLabel(exporting, "ค่าสถานะต่ำๆที่ควรจะปั้นไว้", "Recommended Minimum Stats")}
{group.note ?
{group.note}
: null}
{group.rows.map((target, index) => (
{targetStatIconName(target.stat) ? : null}
{statLabel(normalizeTargetStat(target.stat) ?? target.stat)}
{target.value}
))}
))}
)
) : null}
{profile.talents.length ? (
{exportLabel(exporting, "ลำดับการอัพพรสวรรค์", "Talent Priority")}
{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
? exportLabel(exporting, `เลเวล ${talent.level}+`, `Level ${talent.level}+`)
: "-"}
{talentName}
{talent.type === "E"
? exportLabel(exporting, "สกิลธาตุ", "Elemental Skill")
: talent.type === "Q"
? exportLabel(exporting, "ท่าไม้ตาย", "Elemental Burst")
: exportLabel(exporting, "ตีปกติ", "Normal Attack")}
{index < talents.length - 1 ? (
{talentRelationSymbol(talent.relationToNext)}
) : null}
);
},
)}
) : null}
{profile.note ? (
{exportLabel(exporting, "โน็ต", "Notes")}
{profile.note}
) : null}
) : null}
);
}
const thaiStats: 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%": "โบนัสการรักษา",
};
const englishStats: Record = {
HP: "HP",
ATK: "ATK",
DEF: "DEF",
EM: "Elemental Mastery",
ER: "Energy Recharge",
"CRIT RATE": "CRIT Rate",
"CRIT DAMAGE": "CRIT DMG",
"CRIT Rate/CRIT DMG": "CRIT Rate / CRIT DMG",
};
function statLabel(value: string, exporting = false): ReactNode {
if (!exporting) return thaiStats[value] ?? value;
return (
);
}
function ArtifactStat({
iconSrc,
label,
values,
exporting = false,
elementIconUrls,
}: {
iconSrc: string;
label: ReactNode;
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, exporting)}
);
}
function ConstellationSection({ guide }: { guide: PublicGuide }) {
const note = guide.sections.find(
(section) => section.slug === "constellations",
)?.note;
return (
กลุ่มดาวที่แนะนำ
Constellations
{note ? (
{note}
) : null}
group.rows.length > 0).map((group, index) => group.name || `เงื่อนไข ${index + 1}`)}>
{guide.constellationGroups.filter((group) => group.rows.length > 0).map((group, groupIndex, groups) => (
1 ? `-${group.name || `Condition-${groupIndex + 1}`}` : ""}.png`}
width={1500}
height={1500}
captureClassName="p-0"
showLanguageSelector={false}
promptForLanguage
>
{groups.length > 1 ?
{group.name || `เงื่อนไข ${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 (
คำนวณความเสียหาย
Team Calculation
);
}
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}
);
}