import type { ParsedArtifactCandidate } from "./artifactOcrParser.js"; import type { StoredArtifactRecord } from "../types/storage.js"; /** * Signature used inside one scan session to detect that the detail panel * actually changed after a click. Includes the equipped character so two * otherwise identical pieces on different characters still count as new. */ export function sessionSignature(parsed: ParsedArtifactCandidate) { return [ parsed.name, parsed.slot, parsed.level, parsed.mainStat, parsed.mainValue, parsed.setName, parsed.equipped, parsed.substats.join("|"), ].join("::"); } /** * Signature used for the persistent store. Excludes the equipped character, * so re-equipping an artifact updates the existing record instead of * duplicating it. Level is part of the identity because the local DB now * persists partially leveled pieces as distinct scan states. */ export function storeSignature(parsed: ParsedArtifactCandidate) { return [ parsed.name, parsed.slot, parsed.level, parsed.mainStat, parsed.mainValue, parsed.setName, parsed.substats.join("|"), ].join("::"); } export function hashId(value: string) { let hash = 5381; for (let index = 0; index < value.length; index++) { hash = ((hash << 5) + hash + value.charCodeAt(index)) >>> 0; } return `${hash.toString(16).padStart(8, "0")}-${value.length.toString(16)}`; } export function toStoredArtifact(parsed: ParsedArtifactCandidate, source: string, needsReview: boolean, locked?: boolean): StoredArtifactRecord { return { id: hashId(storeSignature(parsed)), name: parsed.name, slot: parsed.slot, level: parsed.level, setName: parsed.setName, mainStat: parsed.mainStat, mainValue: parsed.mainValue, substats: [...parsed.substats], equipped: parsed.equipped, confidence: parsed.confidence, needsReview, locked, source, }; } export function isReviewOnlyArtifactSource(source: string | null | undefined) { return /^review-/i.test(source ?? ""); } export function storedArtifactStrength(record: StoredArtifactRecord | null | undefined) { if (!record) return 0; return ( (record.confidence ?? 0) + Math.min(16, (record.substats?.length ?? 0) * 4) + (record.needsReview ? -10 : 8) + (record.equipped && !/not detected/i.test(record.equipped) ? 3 : 0) ); } export function resolveStoredArtifactSource(existingSource: string | null | undefined, incomingSource: string | null | undefined) { const existing = existingSource ?? ""; const incoming = incomingSource ?? ""; if (!incoming) return existing; if (!existing) return incoming; if (isReviewOnlyArtifactSource(incoming) && !isReviewOnlyArtifactSource(existing)) return existing; return incoming; }