import type { LogicalStep } from "../solver"; import { GUIDED_HINT_STAGES, guidedHintEffectItems, guidedHintFocusSummary, isGuidedHintStageRevealed, logicalTechniqueDescription, logicalTechniqueName, nextGuidedHintStage, type GuidedHintStage, } from "./guidedHint"; const STAGE_LABELS: Record = { focus: "Where to look", technique: "Technique", reasoning: "Reasoning", preview: "Effects preview", }; function revealButtonLabel(stage: GuidedHintStage): string | undefined { const next = nextGuidedHintStage(stage); if (next === "technique") return "Reveal technique"; if (next === "reasoning") return "Reveal reasoning"; if (next === "preview") return "Preview effects"; return undefined; } function stageStatus(stage: GuidedHintStage): string { if (stage === "focus") return "Hint ready: where to look."; if (stage === "technique") return "Technique revealed."; if (stage === "reasoning") return "Reasoning revealed."; return "Effects ready to preview and apply."; } export interface GuidedHintProps { readonly size: number; readonly step?: LogicalStep; readonly stage: GuidedHintStage; readonly busy?: boolean; readonly error?: string; readonly candidateTrackingActive?: boolean; readonly autoMaintainPeerNotes: boolean; readonly onRequestHint: () => void; readonly onRevealNext: () => void; readonly onApply: () => void; readonly onDismiss: () => void; readonly onFillLegalCandidates: () => void; readonly onRemoveInvalidNotes: () => void; readonly onAutoMaintainPeerNotesChange: (enabled: boolean) => void; } /** * A controlled, progressively disclosed hint. The caller owns the hint step * and stage so it can keep the board overlay and undo history in sync. */ export function GuidedHint({ size, step, stage, busy = false, error, candidateTrackingActive = false, autoMaintainPeerNotes, onRequestHint, onRevealNext, onApply, onDismiss, onFillLegalCandidates, onRemoveInvalidNotes, onAutoMaintainPeerNotesChange, }: GuidedHintProps) { const nextLabel = step === undefined ? undefined : revealButtonLabel(stage); const effectItems = step !== undefined && stage === "preview" ? guidedHintEffectItems(step, size) : []; return (

Guided solving

One clue at a time

{step !== undefined && ( )}

Reveal only as much help as you want. Nothing changes until you apply the fully previewed step.

{busy ? "Finding a logical next step locally…" : error !== undefined ? "The hint could not be prepared." : step === undefined ? "No guided hint is open." : stageStatus(stage)}

{error !== undefined && (

{error}

)} {step !== undefined && ( <>
    {GUIDED_HINT_STAGES.map((item) => { const revealed = isGuidedHintStageRevealed(stage, item); return (
  1. {STAGE_LABELS[item]}
  2. ); })}

Where to look

{guidedHintFocusSummary(step, size)}

{isGuidedHintStageRevealed(stage, "technique") && (

Technique

{logicalTechniqueName(step.technique)}

{logicalTechniqueDescription(step.technique)}

)} {isGuidedHintStageRevealed(stage, "reasoning") && (

Why it works

{step.explanation}

)} {stage === "preview" && (

Effects preview

{effectItems.length > 0 ? (
    {effectItems.map((effect, index) => (
  • {effect}
  • ))}
) : (

This deduction does not change the board.

)} {step.eliminations.length > 0 && !candidateTrackingActive && (

Applying this elimination will start a complete legal centre-candidate grid, then remove the previewed candidates.

)}
)}
{nextLabel !== undefined && ( )} {stage === "preview" && ( )}
)} {step === undefined && (
)}
Candidate maintenance

{candidateTrackingActive ? "The guided candidate grid is active." : "Candidate tracking is currently inactive."}

); }