// ===========================================================
// Ben's Amazing Horadric Cube App
// Single-file React app loaded via Babel standalone.
// ===========================================================

const { useState, useEffect, useMemo } = React;

// ===========================================================
// ICONS — inline SVG (lucide-style paths)
// ===========================================================

const Icon = ({ children, size = 16, className = "", style }) => (
  <svg
    xmlns="http://www.w3.org/2000/svg"
    width={size}
    height={size}
    viewBox="0 0 24 24"
    fill="none"
    stroke="currentColor"
    strokeWidth="2"
    strokeLinecap="round"
    strokeLinejoin="round"
    className={className}
    style={style}
  >
    {children}
  </svg>
);
const X = (p) => <Icon {...p}><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></Icon>;
const ChevronRight = (p) => <Icon {...p}><polyline points="9 18 15 12 9 6"/></Icon>;
const BookOpen = (p) => (
  <Icon {...p}>
    <path d="M12 7v14"/>
    <path d="M3 18a1 1 0 0 1-1-1V4a1 1 0 0 1 1-1h5a4 4 0 0 1 4 4 4 4 0 0 1 4-4h5a1 1 0 0 1 1 1v13a1 1 0 0 1-1 1h-6a3 3 0 0 0-3 3 3 3 0 0 0-3-3z"/>
  </Icon>
);
const Sparkles = (p) => (
  <Icon {...p}>
    <path d="M9.937 15.5A2 2 0 0 0 8.5 14.063l-6.135-1.582a.5.5 0 0 1 0-.962L8.5 9.936A2 2 0 0 0 9.937 8.5l1.582-6.135a.5.5 0 0 1 .963 0L14.063 8.5A2 2 0 0 0 15.5 9.937l6.135 1.582a.5.5 0 0 1 0 .962L15.5 14.063a2 2 0 0 0-1.437 1.437l-1.582 6.135a.5.5 0 0 1-.963 0z"/>
    <path d="M20 3v4"/>
    <path d="M22 5h-4"/>
    <path d="M4 17v2"/>
    <path d="M5 18H3"/>
  </Icon>
);
const Trash2 = (p) => (
  <Icon {...p}>
    <path d="M3 6h18"/>
    <path d="M19 6v14c0 1-1 2-2 2H7c-1 0-2-1-2-2V6"/>
    <path d="M8 6V4c0-1 1-2 2-2h4c1 0 2 1 2 2v2"/>
    <line x1="10" x2="10" y1="11" y2="17"/>
    <line x1="14" x2="14" y1="11" y2="17"/>
  </Icon>
);
const AlertTriangle = (p) => (
  <Icon {...p}>
    <path d="m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3"/>
    <path d="M12 9v4"/>
    <path d="M12 17h.01"/>
  </Icon>
);
const Hammer = (p) => (
  <Icon {...p}>
    <path d="m15 12-8.373 8.373a1 1 0 1 1-3-3L12 9"/>
    <path d="m18 15 4-4"/>
    <path d="m21.5 11.5-1.914-1.914A2 2 0 0 1 19 8.172V7l-2.26-2.26a6 6 0 0 0-4.202-1.756L9 2.96l.92.82A6.18 6.18 0 0 1 12 8.4V10l2 2h1.172a2 2 0 0 1 1.414.586L18.5 14.5"/>
  </Icon>
);
const Undo2 = (p) => (
  <Icon {...p}>
    <path d="M9 14 4 9l5-5"/>
    <path d="M4 9h10.5a5.5 5.5 0 0 1 5.5 5.5a5.5 5.5 0 0 1-5.5 5.5H11"/>
  </Icon>
);

// ===========================================================
// DATA — prisms, recipes, affixes
// ===========================================================

const PRISMS = {
  aggressive:  { id: "aggressive",  name: "Aggressive",
    affixes: ["mainstat","weapon_damage","attack_speed","critical_strike_chance","critical_strike_damage","vulnerable_damage","dot_damage","all_damage","elemental_damage","thorns"] },
  pragmatic:   { id: "pragmatic",   name: "Pragmatic",
    affixes: ["barrier_generation","cooldown_reduction","fortify_generation","healing_received","impairment_reduction","life_regeneration","lucky_hit_chance","movement_speed","potion_capacity","thorns","max_evade_charges","attacks_reduce_evade_cooldown","evade_grants_movement_speed"] },
  protectors:  { id: "protectors",  name: "Protector's",
    affixes: ["armor","damage_reduction","dodge_chance","fortify_generation","life_on_hit","life_on_kill","life_regeneration","maximum_life","all_resistance","specific_resistances"] },
  resourceful: { id: "resourceful", name: "Resourceful",
    affixes: ["lucky_hit_restore_resource","maximum_resource","resource_cost_reduction","resource_on_kill","resource_regeneration"] },
  adepts:      { id: "adepts",      name: "Adept's",
    affixes: ["mainstat","skill_ranks"] },
  chromatic:   { id: "chromatic",   name: "Chromatic",
    affixes: ["specific_resistances"] },
};

const AFFIX_NAMES = {
  mainstat: "Mainstat",
  weapon_damage: "Weapon Damage",
  attack_speed: "Attack Speed",
  critical_strike_chance: "Critical Strike Chance",
  critical_strike_damage: "Critical Strike Damage",
  vulnerable_damage: "Vulnerable Damage",
  dot_damage: "DoT Damage",
  all_damage: "All Damage",
  elemental_damage: "Elemental Damage",
  thorns: "Thorns",
  barrier_generation: "Barrier Generation",
  cooldown_reduction: "Cooldown Reduction",
  fortify_generation: "Fortify Generation",
  healing_received: "Healing Received",
  impairment_reduction: "Impairment Reduction",
  life_regeneration: "Life Regeneration",
  lucky_hit_chance: "Lucky Hit Chance",
  movement_speed: "Movement Speed",
  potion_capacity: "Potion Capacity",
  max_evade_charges: "Max Evade Charges",
  attacks_reduce_evade_cooldown: "Attacks Reduce Evade Cooldown",
  evade_grants_movement_speed: "Evade Grants Movement Speed",
  armor: "Armor",
  damage_reduction: "Damage Reduction",
  dodge_chance: "Dodge Chance",
  life_on_hit: "Life on Hit",
  life_on_kill: "Life on Kill",
  maximum_life: "Maximum Life",
  all_resistance: "All Resistance",
  specific_resistances: "Specific Resistances",
  lucky_hit_restore_resource: "Lucky Hit Restore Resource",
  maximum_resource: "Maximum Resource",
  resource_cost_reduction: "Resource Cost Reduction",
  resource_on_kill: "Resource on Kill",
  resource_regeneration: "Resource Regeneration",
  skill_ranks: "Skill Ranks",
};

const ALL_AFFIXES = Object.keys(AFFIX_NAMES);

const RECIPES = {
  add_affix:      { name: "Add Affix",      materials: { coarse_primordial_dust: 1, raw_primordial_dust: 5 } },
  chaotic_reroll: { name: "Chaotic Reroll", materials: { refined_primordial_dust: 1, raw_primordial_dust: 15 } },
  focused_reroll: { name: "Focused Reroll", materials: { refined_primordial_dust: 1, raw_primordial_dust: 15 } },
  remove_affix:   { name: "Remove Affix",   materials: { refined_primordial_dust: 1, raw_primordial_dust: 15 } },
};

const MATERIAL_NAMES = {
  raw_primordial_dust: "Raw Primordial Dust",
  coarse_primordial_dust: "Coarse Primordial Dust",
  refined_primordial_dust: "Refined Primordial Dust",
};

const SLOT_MAX_AFFIXES = 4;
const SLOTS = ["helm","chest","gloves","boots","pants","weapon_1h","weapon_2h","offhand","ring","amulet"];
const RARITIES = ["common","magic","rare","legendary"];
const CLASSES = ["barbarian","druid","necromancer","paladin","rogue","sorcerer","spiritborn","warlock"];

const RARITY_COLORS = {
  common:    { text: "text-slate-100",   border: "border-slate-400",   bg: "bg-slate-400/15",  hex: "#e2e8f0", glow: "rgba(226,232,240,0.35)" },
  magic:     { text: "text-blue-300",    border: "border-blue-500",    bg: "bg-blue-500/15",   hex: "#60a5fa", glow: "rgba(96,165,250,0.45)" },
  rare:      { text: "text-yellow-300",  border: "border-yellow-400",  bg: "bg-yellow-500/15", hex: "#facc15", glow: "rgba(250,204,21,0.45)" },
  legendary: { text: "text-orange-300",  border: "border-orange-500",  bg: "bg-orange-500/15", hex: "#fb923c", glow: "rgba(251,146,60,0.45)" },
};

// ===========================================================
// PLANNER
// ===========================================================

const AFFIX_PRISM_INDEX = (() => {
  const idx = {};
  for (const [prismId, prism] of Object.entries(PRISMS)) {
    for (const affix of prism.affixes) {
      if (!idx[affix]) idx[affix] = [];
      idx[affix].push(prismId);
    }
  }
  return idx;
})();

function getEffectivePool(prismAffixes, blockedAffixes) {
  return prismAffixes.filter((a) => !blockedAffixes.includes(a));
}

function bestPrismForOperation(needed, item, consumedAffix) {
  const blocked = consumedAffix
    ? item.affixes.filter((a) => a !== consumedAffix)
    : item.affixes;
  let best = null;
  for (const [prismId, prism] of Object.entries(PRISMS)) {
    const pool = getEffectivePool(prism.affixes, blocked);
    if (pool.length === 0) continue;
    const hits = needed.filter((a) => pool.includes(a)).length;
    if (hits === 0) continue;
    const hitRate = hits / pool.length;
    if (!best || hitRate > best.hitRate) {
      best = { prism: prismId, hits, poolSize: pool.length, hitRate };
    }
  }
  return best;
}

function bestFocusedReroll(dispensable, needed, item) {
  let best = null;
  for (const disp of dispensable) {
    const dispPrisms = AFFIX_PRISM_INDEX[disp] || [];
    const blocked = item.affixes.filter((a) => a !== disp);
    for (const prismId of dispPrisms) {
      const pool = getEffectivePool(PRISMS[prismId].affixes, blocked);
      if (pool.length === 0) continue;
      const hits = needed.filter((a) => pool.includes(a)).length;
      if (hits === 0) continue;
      const hitRate = hits / pool.length;
      if (!best || hitRate > best.hitRate) {
        best = { prism: prismId, targetAffix: disp, hits, poolSize: pool.length, hitRate };
      }
    }
  }
  return best;
}

function nextAction(item, target) {
  if (!item) return null;
  if (target.required.length === 0 && target.avoid.length === 0) {
    return { action: "no_target", rationale: "Define what you want from this item — required affixes, forbidden affixes, or both.", materials: {} };
  }
  const current = item.affixes;
  const needed = target.required.filter((a) => !current.includes(a));
  const unwanted = current.filter((a) => target.avoid.includes(a));

  if (needed.length === 0 && unwanted.length === 0) {
    return { action: "done", rationale: "Item matches the target spec.", materials: {} };
  }
  if (target.required.length > SLOT_MAX_AFFIXES) {
    return { action: "over_specified", rationale: `Target requires ${target.required.length} affixes; slot supports ${SLOT_MAX_AFFIXES}.`, materials: {} };
  }

  if (unwanted.length > 0) {
    const affix = unwanted[0];
    if (item.rarity === "magic" || item.rarity === "rare") {
      return {
        action: "remove_affix",
        recipe_id: "remove_affix",
        target_affix: affix,
        rationale: `Strip the unwanted affix '${AFFIX_NAMES[affix]}'. Deterministic — no prism needed.`,
        materials: RECIPES.remove_affix.materials,
      };
    } else if (item.rarity === "legendary") {
      const prism = needed.length > 0 ? bestPrismForOperation(needed, item, affix) : null;
      return {
        action: "chaotic_reroll",
        recipe_id: "chaotic_reroll",
        target_affix: affix,
        prism: prism ? prism.prism : undefined,
        rationale: prism
          ? `Legendaries cannot use Remove Affix. Chaotic Reroll '${AFFIX_NAMES[affix]}' with ${PRISMS[prism.prism].name} Prism — ${prism.hits} of ${prism.poolSize} possible outcomes are what you need (~${(prism.hitRate * 100).toFixed(0)}% hit rate).`
          : `Chaotic Reroll '${AFFIX_NAMES[affix]}'. No prism applies — pure random outcome.`,
        materials: RECIPES.chaotic_reroll.materials,
        hit_rate_estimate: prism ? prism.hitRate : undefined,
      };
    } else {
      return {
        action: "no_strategy",
        rationale: `Common items cannot use Remove Affix or Reroll. Upgrade rarity first if you need to strip '${AFFIX_NAMES[affix]}'.`,
        materials: {},
      };
    }
  }

  if (current.length < SLOT_MAX_AFFIXES && needed.length > 0) {
    const prism = bestPrismForOperation(needed, item, null);
    if (prism) {
      return {
        action: "add_affix",
        recipe_id: "add_affix",
        prism: prism.prism,
        rationale: `Slot has room. Add Affix with ${PRISMS[prism.prism].name} Prism — ${prism.hits} of ${prism.poolSize} possible outcomes are what you need (~${(prism.hitRate * 100).toFixed(0)}% hit rate).`,
        materials: RECIPES.add_affix.materials,
        hit_rate_estimate: prism.hitRate,
      };
    } else {
      return {
        action: "no_strategy",
        rationale: `No prism contains any needed affix. Check that your targets are valid for this slot.`,
        materials: {},
      };
    }
  }

  if (needed.length > 0) {
    const dispensable = current.filter((a) => !target.required.includes(a) && !target.avoid.includes(a));
    if (dispensable.length === 0) {
      return {
        action: "over_specified",
        rationale: "All current affixes are required but more required affixes are missing. Trim the required list or mark a current affix as dispensable.",
        materials: {},
      };
    }
    if (item.rarity === "common") {
      return {
        action: "no_strategy",
        rationale: "Common items cannot use Reroll. Upgrade rarity (Add Affix supports common, but rerolling requires Magic or higher).",
        materials: {},
      };
    }
    const focused = bestFocusedReroll(dispensable, needed, item);
    const chaotic = bestPrismForOperation(needed, item, dispensable[0]);
    if (focused && (!chaotic || focused.hitRate > chaotic.hitRate)) {
      return {
        action: "focused_reroll",
        recipe_id: "focused_reroll",
        target_affix: focused.targetAffix,
        prism: focused.prism,
        rationale: `Focused Reroll '${AFFIX_NAMES[focused.targetAffix]}' with ${PRISMS[focused.prism].name} Prism. ${focused.hits} of ${focused.poolSize} possible outcomes are what you need (~${(focused.hitRate * 100).toFixed(0)}% hit rate).`,
        materials: RECIPES.focused_reroll.materials,
        hit_rate_estimate: focused.hitRate,
      };
    }
    if (chaotic) {
      return {
        action: "chaotic_reroll",
        recipe_id: "chaotic_reroll",
        target_affix: dispensable[0],
        prism: chaotic.prism,
        rationale: `Chaotic Reroll '${AFFIX_NAMES[dispensable[0]]}' with ${PRISMS[chaotic.prism].name} Prism — ${chaotic.hits} of ${chaotic.poolSize} possible outcomes are what you need (~${(chaotic.hitRate * 100).toFixed(0)}% hit rate).`,
        materials: RECIPES.chaotic_reroll.materials,
        hit_rate_estimate: chaotic.hitRate,
      };
    }
    return { action: "no_strategy", rationale: "No prism contains any needed affix.", materials: {} };
  }

  return { action: "done", rationale: "No further actions needed.", materials: {} };
}

// ===========================================================
// HELPERS
// ===========================================================

function fmtSlot(s) {
  return s.replace("_", " ").replace(/\b\w/g, (c) => c.toUpperCase());
}
function fmtRarity(r) {
  return r.charAt(0).toUpperCase() + r.slice(1);
}
function newItem() {
  return {
    id: `item_${Date.now()}`,
    slot: "chest",
    rarity: "legendary",
    is_ancestral: true,
    class_context: "barbarian",
    affixes: [],
    history: [],
  };
}

// ===========================================================
// UI COMPONENTS
// ===========================================================

function Ornament() {
  return (
    <div className="flex items-center justify-center gap-3 my-3 text-amber-700/60">
      <span className="h-px flex-1 bg-gradient-to-r from-transparent via-amber-700/40 to-transparent" />
      <span className="text-xs tracking-widest">✦</span>
      <span className="h-px flex-1 bg-gradient-to-r from-transparent via-amber-700/40 to-transparent" />
    </div>
  );
}

function Card({ title, subtitle, children, accent, accentColor }) {
  return (
    <section
      className="relative border border-amber-900/40 bg-stone-900/60 shadow-xl"
      style={accentColor ? { borderTop: `2px solid ${accentColor}`, boxShadow: `0 0 24px -8px ${accentColor}40, 0 10px 25px -10px rgba(0,0,0,0.5)` } : {}}
    >
      <div className="absolute inset-0 pointer-events-none opacity-[0.04] mix-blend-overlay" style={{ backgroundImage: "url(\"data:image/svg+xml,%3Csvg viewBox='0 0 200 200' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='n'%3E%3CfeTurbulence baseFrequency='0.9' numOctaves='3' /%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)' /%3E%3C/svg%3E\")" }} />
      <header className="relative px-5 pt-4 pb-3 border-b border-amber-900/30 flex items-baseline justify-between">
        <h2 className="font-serif tracking-[0.2em] text-amber-200/90 text-sm uppercase" style={{ fontFamily: "'Cinzel', serif" }}>
          {title}
        </h2>
        {subtitle && <span className="text-xs text-amber-200/40 italic">{subtitle}</span>}
        {accent && <span className="absolute top-0 right-0 w-2 h-2 bg-amber-600/60" />}
      </header>
      <div className="relative px-5 py-4">{children}</div>
    </section>
  );
}

function AffixChip({ affix, onRemove, variant = "default" }) {
  const variants = {
    default: "bg-stone-800/80 border-stone-700 text-amber-100/90",
    required: "bg-amber-950/40 border-amber-700/60 text-amber-200",
    avoid: "bg-red-950/50 border-red-800/60 text-red-200",
  };
  return (
    <span className={`inline-flex items-center gap-1.5 px-2.5 py-1 text-xs border ${variants[variant]} font-serif`} style={{ fontFamily: "'EB Garamond', serif" }}>
      {AFFIX_NAMES[affix]}
      {onRemove && (
        <button onClick={onRemove} className="opacity-50 hover:opacity-100 transition">
          <X size={12} />
        </button>
      )}
    </span>
  );
}

function AffixPicker({ excluded, onPick, placeholder = "Add affix..." }) {
  const [query, setQuery] = useState("");
  const [open, setOpen] = useState(false);
  const filtered = ALL_AFFIXES.filter(
    (a) => !excluded.includes(a) && AFFIX_NAMES[a].toLowerCase().includes(query.toLowerCase())
  );
  return (
    <div className="relative">
      <div className="flex items-center gap-2">
        <input
          type="text"
          value={query}
          onChange={(e) => { setQuery(e.target.value); setOpen(true); }}
          onFocus={() => setOpen(true)}
          onBlur={() => setTimeout(() => setOpen(false), 150)}
          placeholder={placeholder}
          className="flex-1 bg-stone-950/60 border border-stone-700 px-3 py-1.5 text-sm text-amber-100/90 placeholder:text-stone-500 focus:border-amber-700/60 focus:outline-none"
          style={{ fontFamily: "'EB Garamond', serif" }}
        />
      </div>
      {open && filtered.length > 0 && (
        <div className="absolute z-50 mt-1 w-full max-h-56 overflow-y-auto bg-stone-950 border border-amber-900/50 shadow-2xl">
          {filtered.map((a) => (
            <button
              key={a}
              onMouseDown={() => { onPick(a); setQuery(""); setOpen(false); }}
              className="w-full text-left px-3 py-1.5 text-sm text-amber-100/80 hover:bg-amber-950/40 hover:text-amber-200 transition"
              style={{ fontFamily: "'EB Garamond', serif" }}
            >
              {AFFIX_NAMES[a]}
            </button>
          ))}
        </div>
      )}
    </div>
  );
}

function LabeledSelect({ label, value, options, onChange, format = (v) => v }) {
  return (
    <label className="block">
      <span className="block text-[10px] uppercase tracking-widest text-amber-200/40 mb-1" style={{ fontFamily: "'Cinzel', serif" }}>
        {label}
      </span>
      <select
        value={value}
        onChange={(e) => onChange(e.target.value)}
        className="w-full bg-stone-950/60 border border-stone-700 px-2.5 py-1.5 text-sm text-amber-100/90 focus:border-amber-700/60 focus:outline-none"
        style={{ fontFamily: "'EB Garamond', serif" }}
      >
        {options.map((o) => (
          <option key={o} value={o}>{format(o)}</option>
        ))}
      </select>
    </label>
  );
}

function RaritySelector({ value, onChange }) {
  return (
    <div>
      <span className="block text-[10px] uppercase tracking-widest text-amber-200/40 mb-1" style={{ fontFamily: "'Cinzel', serif" }}>
        Rarity
      </span>
      <div className="grid grid-cols-4 gap-1">
        {RARITIES.map((r) => {
          const c = RARITY_COLORS[r];
          const selected = value === r;
          return (
            <button
              key={r}
              onClick={() => onChange(r)}
              className={`px-2 py-1.5 border text-[11px] uppercase tracking-wider transition ${
                selected ? `${c.text} ${c.border} ${c.bg}` : "text-stone-500 border-stone-700 hover:border-stone-500 hover:text-stone-300 bg-transparent"
              }`}
              style={{
                fontFamily: "'Cinzel', serif",
                boxShadow: selected ? `0 0 12px ${c.glow}` : "none",
              }}
            >
              {r}
            </button>
          );
        })}
      </div>
    </div>
  );
}

function ItemEditor({ item, onChange }) {
  if (!item) return null;
  const addAffix = (a) => onChange({ ...item, affixes: [...item.affixes, a] });
  const removeAffix = (a) => onChange({ ...item, affixes: item.affixes.filter((x) => x !== a) });
  const rarityColor = RARITY_COLORS[item.rarity] ? RARITY_COLORS[item.rarity].hex : undefined;

  return (
    <Card title="The Item" subtitle={`${item.affixes.length} / ${SLOT_MAX_AFFIXES} affixes`} accentColor={rarityColor}>
      <div className="mb-4">
        <RaritySelector value={item.rarity} onChange={(v) => onChange({ ...item, rarity: v })} />
      </div>
      <div className="grid grid-cols-2 gap-3 mb-4">
        <LabeledSelect label="Slot" value={item.slot} options={SLOTS} format={fmtSlot} onChange={(v) => onChange({ ...item, slot: v })} />
        <LabeledSelect label="Class" value={item.class_context} options={CLASSES} format={fmtRarity} onChange={(v) => onChange({ ...item, class_context: v })} />
        <label className="flex items-center gap-2 col-span-2">
          <input
            type="checkbox"
            checked={item.is_ancestral}
            onChange={(e) => onChange({ ...item, is_ancestral: e.target.checked })}
            className="accent-amber-700"
          />
          <span className="text-xs uppercase tracking-widest text-amber-200/60" style={{ fontFamily: "'Cinzel', serif" }}>
            Ancestral
          </span>
        </label>
      </div>

      <div className="space-y-2">
        <div className="text-[10px] uppercase tracking-widest text-amber-200/40" style={{ fontFamily: "'Cinzel', serif" }}>
          Current Affixes
        </div>
        <div className="flex flex-wrap gap-1.5 min-h-[28px]">
          {item.affixes.length === 0 && <span className="text-xs text-stone-600 italic">No affixes — a blank slate.</span>}
          {item.affixes.map((a) => (
            <AffixChip key={a} affix={a} onRemove={() => removeAffix(a)} />
          ))}
        </div>
        {item.affixes.length < SLOT_MAX_AFFIXES && (
          <AffixPicker excluded={item.affixes} onPick={addAffix} placeholder="Type to add a current affix..." />
        )}
      </div>
    </Card>
  );
}

function TargetEditor({ target, onChange }) {
  const addRequired = (a) => onChange({ ...target, required: [...target.required, a], avoid: target.avoid.filter((x) => x !== a) });
  const removeRequired = (a) => onChange({ ...target, required: target.required.filter((x) => x !== a) });
  const addAvoid = (a) => onChange({ ...target, avoid: [...target.avoid, a], required: target.required.filter((x) => x !== a) });
  const removeAvoid = (a) => onChange({ ...target, avoid: target.avoid.filter((x) => x !== a) });

  return (
    <Card title="The Goal" subtitle="Required & Forbidden">
      <div className="space-y-3">
        <div>
          <div className="text-[10px] uppercase tracking-widest text-amber-200/60 mb-2" style={{ fontFamily: "'Cinzel', serif" }}>
            ◆ Must Have
          </div>
          <div className="flex flex-wrap gap-1.5 mb-2 min-h-[28px]">
            {target.required.length === 0 && <span className="text-xs text-stone-600 italic">Nothing yet required.</span>}
            {target.required.map((a) => (
              <AffixChip key={a} affix={a} variant="required" onRemove={() => removeRequired(a)} />
            ))}
          </div>
          <AffixPicker excluded={[...target.required, ...target.avoid]} onPick={addRequired} placeholder="Type to require an affix..." />
        </div>

        <div className="border-t border-stone-800 pt-3">
          <div className="text-[10px] uppercase tracking-widest text-red-300/60 mb-2" style={{ fontFamily: "'Cinzel', serif" }}>
            ✕ Must Avoid
          </div>
          <div className="flex flex-wrap gap-1.5 mb-2 min-h-[28px]">
            {target.avoid.length === 0 && <span className="text-xs text-stone-600 italic">Nothing forbidden.</span>}
            {target.avoid.map((a) => (
              <AffixChip key={a} affix={a} variant="avoid" onRemove={() => removeAvoid(a)} />
            ))}
          </div>
          <AffixPicker excluded={[...target.required, ...target.avoid]} onPick={addAvoid} placeholder="Type to forbid an affix..." />
        </div>
      </div>
    </Card>
  );
}

function RecommendationPanel({ rec, item, onApply }) {
  if (!item) {
    return (
      <Card title="The Cube Speaks">
        <p className="text-stone-500 italic text-sm">Define an item to receive guidance.</p>
      </Card>
    );
  }
  if (!rec) return null;

  if (rec.action === "no_target") {
    return (
      <Card title="The Cube Speaks">
        <div className="text-center py-4">
          <BookOpen className="mx-auto mb-2 text-amber-700/60" size={28} />
          <p className="text-stone-400 text-sm italic" style={{ fontFamily: "'EB Garamond', serif" }}>
            {rec.rationale}
          </p>
        </div>
      </Card>
    );
  }

  if (rec.action === "done") {
    return (
      <Card title="The Cube Speaks" accent>
        <div className="text-center py-4">
          <Sparkles className="mx-auto mb-2 text-amber-500" size={28} />
          <div className="text-amber-200 font-serif text-lg" style={{ fontFamily: "'Cinzel', serif" }}>
            The work is complete.
          </div>
          <p className="text-stone-400 text-sm mt-2 italic">{rec.rationale}</p>
        </div>
      </Card>
    );
  }

  const isTerminal = rec.action === "over_specified" || rec.action === "no_strategy";
  if (isTerminal) {
    return (
      <Card title="The Cube Speaks" accent>
        <div className="flex gap-3 items-start">
          <AlertTriangle className="text-red-400 shrink-0 mt-0.5" size={20} />
          <div>
            <div className="text-red-300 font-serif uppercase tracking-widest text-xs mb-1" style={{ fontFamily: "'Cinzel', serif" }}>
              The Cube refuses.
            </div>
            <p className="text-stone-300 text-sm italic">{rec.rationale}</p>
          </div>
        </div>
      </Card>
    );
  }

  const recipe = RECIPES[rec.recipe_id];

  return (
    <Card title="The Cube Speaks" accent>
      <div className="space-y-4">
        <div>
          <div className="text-[10px] uppercase tracking-widest text-amber-200/40 mb-1" style={{ fontFamily: "'Cinzel', serif" }}>
            Next Working
          </div>
          <div className="text-amber-200 text-2xl font-serif tracking-wide" style={{ fontFamily: "'Cinzel', serif" }}>
            {recipe.name}
          </div>
        </div>

        {rec.target_affix && (
          <div>
            <div className="text-[10px] uppercase tracking-widest text-amber-200/40 mb-1" style={{ fontFamily: "'Cinzel', serif" }}>
              Targeting
            </div>
            <AffixChip affix={rec.target_affix} />
          </div>
        )}

        {rec.prism && (
          <div>
            <div className="text-[10px] uppercase tracking-widest text-amber-200/40 mb-1" style={{ fontFamily: "'Cinzel', serif" }}>
              Tuning Prism
            </div>
            <span className="inline-block px-2.5 py-1 text-sm border border-amber-700/60 bg-amber-950/40 text-amber-200" style={{ fontFamily: "'EB Garamond', serif" }}>
              {PRISMS[rec.prism].name} Prism
            </span>
          </div>
        )}

        <div className="border-t border-stone-800 pt-3">
          <p className="text-stone-300 text-sm italic leading-relaxed" style={{ fontFamily: "'EB Garamond', serif" }}>
            {rec.rationale}
          </p>
        </div>

        {rec.hit_rate_estimate !== undefined && (
          <div className="flex items-baseline gap-2">
            <div className="text-[10px] uppercase tracking-widest text-amber-200/40" style={{ fontFamily: "'Cinzel', serif" }}>
              Estimated Hit Rate
            </div>
            <div className="text-amber-400 text-lg font-serif" style={{ fontFamily: "'Cinzel', serif" }}>
              {(rec.hit_rate_estimate * 100).toFixed(0)}%
            </div>
          </div>
        )}

        <div>
          <div className="text-[10px] uppercase tracking-widest text-amber-200/40 mb-1.5" style={{ fontFamily: "'Cinzel', serif" }}>
            Materials
          </div>
          <div className="space-y-1">
            {Object.entries(rec.materials).map(([k, v]) => (
              <div key={k} className="flex justify-between text-sm" style={{ fontFamily: "'EB Garamond', serif" }}>
                <span className="text-stone-400">{MATERIAL_NAMES[k]}</span>
                <span className="text-amber-200">×{v}</span>
              </div>
            ))}
          </div>
        </div>

        <button
          onClick={() => onApply(rec)}
          className="w-full mt-2 py-3 border-2 border-amber-700/60 bg-gradient-to-b from-amber-900/40 to-amber-950/60 text-amber-100 hover:from-amber-800/60 hover:to-amber-900/80 hover:border-amber-600 transition uppercase tracking-[0.25em] text-sm flex items-center justify-center gap-2 group"
          style={{ fontFamily: "'Cinzel', serif" }}
        >
          <Hammer size={16} className="group-hover:rotate-12 transition" />
          Perform the Working
          <ChevronRight size={16} />
        </button>
      </div>
    </Card>
  );
}

function ApplyOutcomeDialog({ rec, item, onConfirm, onCancel }) {
  const [chosenAffix, setChosenAffix] = useState("");
  if (!rec) return null;

  let candidates = [];
  if (rec.action === "add_affix") {
    if (rec.prism) candidates = PRISMS[rec.prism].affixes.filter((a) => !item.affixes.includes(a));
    else candidates = ALL_AFFIXES.filter((a) => !item.affixes.includes(a));
  } else if (rec.action === "chaotic_reroll") {
    if (rec.prism) candidates = PRISMS[rec.prism].affixes.filter((a) => !item.affixes.includes(a) || a === rec.target_affix);
    else candidates = ALL_AFFIXES.filter((a) => !item.affixes.includes(a));
  } else if (rec.action === "focused_reroll") {
    candidates = PRISMS[rec.prism].affixes.filter((a) => !item.affixes.includes(a));
  }

  const isDeterministic = rec.action === "remove_affix";

  return (
    <div className="fixed inset-0 z-50 flex items-center justify-center bg-black/70 backdrop-blur-sm" onClick={onCancel}>
      <div className="bg-stone-950 border border-amber-800/60 max-w-md w-full mx-4 shadow-2xl" onClick={(e) => e.stopPropagation()}>
        <div className="px-5 py-4 border-b border-amber-900/40">
          <h3 className="font-serif uppercase tracking-[0.2em] text-amber-200 text-sm" style={{ fontFamily: "'Cinzel', serif" }}>
            Record the Outcome
          </h3>
        </div>
        <div className="px-5 py-4 space-y-4">
          {isDeterministic ? (
            <p className="text-stone-300 text-sm italic" style={{ fontFamily: "'EB Garamond', serif" }}>
              Confirm that <span className="text-amber-200 not-italic">{AFFIX_NAMES[rec.target_affix]}</span> was removed from the item.
            </p>
          ) : (
            <React.Fragment>
              <p className="text-stone-300 text-sm italic" style={{ fontFamily: "'EB Garamond', serif" }}>
                Which affix did the Cube produce?
              </p>
              <div className="max-h-64 overflow-y-auto border border-stone-800">
                {candidates.map((a) => (
                  <button
                    key={a}
                    onClick={() => setChosenAffix(a)}
                    className={`w-full text-left px-3 py-2 text-sm border-b border-stone-800/60 transition ${
                      chosenAffix === a ? "bg-amber-950/60 text-amber-200" : "text-stone-300 hover:bg-stone-900"
                    }`}
                    style={{ fontFamily: "'EB Garamond', serif" }}
                  >
                    {AFFIX_NAMES[a]}
                  </button>
                ))}
              </div>
            </React.Fragment>
          )}
        </div>
        <div className="px-5 py-3 border-t border-amber-900/40 flex gap-2 justify-end">
          <button
            onClick={onCancel}
            className="px-4 py-1.5 text-xs uppercase tracking-widest text-stone-400 hover:text-stone-200"
            style={{ fontFamily: "'Cinzel', serif" }}
          >
            Cancel
          </button>
          <button
            onClick={() => onConfirm(isDeterministic ? null : chosenAffix)}
            disabled={!isDeterministic && !chosenAffix}
            className="px-4 py-1.5 text-xs uppercase tracking-widest border border-amber-700 bg-amber-900/40 text-amber-100 hover:bg-amber-800/60 disabled:opacity-30 disabled:cursor-not-allowed"
            style={{ fontFamily: "'Cinzel', serif" }}
          >
            Confirm
          </button>
        </div>
      </div>
    </div>
  );
}

function HistoryLog({ item, onUndo }) {
  if (!item || item.history.length === 0) return null;
  return (
    <Card title="The Journal" subtitle={`${item.history.length} workings`}>
      <div className="flex justify-end mb-3 -mt-1">
        <button
          onClick={onUndo}
          className="flex items-center gap-1.5 text-[11px] uppercase tracking-widest text-amber-200/60 hover:text-amber-200 border border-amber-900/40 hover:border-amber-700/60 px-2.5 py-1 transition"
          style={{ fontFamily: "'Cinzel', serif" }}
        >
          <Undo2 size={11} /> Undo Last
        </button>
      </div>
      <ol className="space-y-2.5">
        {item.history.slice().reverse().map((h, i) => (
          <li key={i} className="text-sm border-l-2 border-amber-900/40 pl-3" style={{ fontFamily: "'EB Garamond', serif" }}>
            <div className="text-amber-200/80">
              {RECIPES[h.recipe_id] ? RECIPES[h.recipe_id].name : h.recipe_id}
              {h.prism_used && <span className="text-amber-200/40 ml-2 text-xs">• {PRISMS[h.prism_used].name}</span>}
            </div>
            <div className="text-stone-400 text-xs italic">{h.outcome}</div>
          </li>
        ))}
      </ol>
    </Card>
  );
}

// ===========================================================
// STORAGE — localStorage helpers
// ===========================================================

const STORAGE_KEYS = {
  item: "horadric.active_item",
  target: "horadric.active_target",
};

function loadItem() {
  try {
    const v = localStorage.getItem(STORAGE_KEYS.item);
    return v ? JSON.parse(v) : newItem();
  } catch {
    return newItem();
  }
}
function loadTarget() {
  try {
    const v = localStorage.getItem(STORAGE_KEYS.target);
    return v ? JSON.parse(v) : { required: [], avoid: [] };
  } catch {
    return { required: [], avoid: [] };
  }
}

// ===========================================================
// APP
// ===========================================================

function App() {
  const [item, setItem] = useState(loadItem);
  const [target, setTarget] = useState(loadTarget);
  const [pendingRec, setPendingRec] = useState(null);
  const [showResetConfirm, setShowResetConfirm] = useState(false);

  useEffect(() => {
    if (item) localStorage.setItem(STORAGE_KEYS.item, JSON.stringify(item));
  }, [item]);
  useEffect(() => {
    localStorage.setItem(STORAGE_KEYS.target, JSON.stringify(target));
  }, [target]);

  const recommendation = useMemo(() => nextAction(item, target), [item, target]);

  const handleApply = (rec) => setPendingRec(rec);

  const handleConfirmOutcome = (newAffix) => {
    if (!pendingRec || !item) { setPendingRec(null); return; }
    const before = [...item.affixes];
    let after = [...item.affixes];
    let outcomeText = "";

    if (pendingRec.action === "remove_affix") {
      after = after.filter((a) => a !== pendingRec.target_affix);
      outcomeText = `Removed ${AFFIX_NAMES[pendingRec.target_affix]}`;
    } else if (pendingRec.action === "add_affix" && newAffix) {
      after.push(newAffix);
      outcomeText = `Added ${AFFIX_NAMES[newAffix]}`;
    } else if ((pendingRec.action === "chaotic_reroll" || pendingRec.action === "focused_reroll") && newAffix) {
      after = after.map((a) => (a === pendingRec.target_affix ? newAffix : a));
      outcomeText = `${AFFIX_NAMES[pendingRec.target_affix]} → ${AFFIX_NAMES[newAffix]}`;
    }

    const historyEntry = {
      timestamp: new Date().toISOString(),
      recipe_id: pendingRec.recipe_id,
      prism_used: pendingRec.prism || null,
      outcome: outcomeText,
      affixes_before: before,
      affixes_after: after,
    };
    setItem({ ...item, affixes: after, history: [...item.history, historyEntry] });
    setPendingRec(null);
  };

  const handleResetItem = () => setShowResetConfirm(true);
  const confirmReset = () => { setItem(newItem()); setShowResetConfirm(false); };

  const undoLastAction = () => {
    if (!item || item.history.length === 0) return;
    const last = item.history[item.history.length - 1];
    setItem({
      ...item,
      affixes: last.affixes_before,
      history: item.history.slice(0, -1),
    });
  };

  return (
    <React.Fragment>
      <div className="min-h-screen">
        <div className="max-w-6xl mx-auto px-4 py-8">
          <header className="text-center mb-6">
            <h1 className="text-2xl md:text-3xl tracking-[0.2em] text-amber-300/90 uppercase" style={{ fontFamily: "'Cinzel', serif", fontWeight: 500 }}>
              Ben's Amazing Horadric Cube App
            </h1>
            <p className="text-xs tracking-[0.4em] text-amber-200/40 uppercase mt-2" style={{ fontFamily: "'Cinzel', serif" }}>
              A Crafter's Companion
            </p>
            <Ornament />
          </header>

          <div className="grid grid-cols-1 lg:grid-cols-2 gap-5">
            <div className="space-y-5">
              <ItemEditor item={item} onChange={setItem} />
              <TargetEditor target={target} onChange={setTarget} />
              <div className="flex justify-end">
                <button
                  onClick={handleResetItem}
                  className="text-xs text-stone-500 hover:text-red-400 uppercase tracking-widest flex items-center gap-1.5"
                  style={{ fontFamily: "'Cinzel', serif" }}
                >
                  <Trash2 size={12} /> Reset Item
                </button>
              </div>
            </div>
            <div className="space-y-5">
              <RecommendationPanel rec={recommendation} item={item} onApply={handleApply} />
              <HistoryLog item={item} onUndo={undoLastAction} />
            </div>
          </div>

          <footer className="mt-12 text-center text-xs text-stone-700 tracking-widest" style={{ fontFamily: "'Cinzel', serif" }}>
            ✦ S13 · Lord of Hatred ✦
          </footer>
        </div>
      </div>

      <ApplyOutcomeDialog
        rec={pendingRec}
        item={item}
        onConfirm={handleConfirmOutcome}
        onCancel={() => setPendingRec(null)}
      />

      {showResetConfirm && (
        <div className="fixed inset-0 z-50 flex items-center justify-center bg-black/70 backdrop-blur-sm" onClick={() => setShowResetConfirm(false)}>
          <div className="bg-stone-950 border border-red-900/60 max-w-md w-full mx-4 shadow-2xl" onClick={(e) => e.stopPropagation()}>
            <div className="px-5 py-4 border-b border-red-900/40">
              <h3 className="font-serif uppercase tracking-[0.2em] text-red-300 text-sm" style={{ fontFamily: "'Cinzel', serif" }}>
                Abandon the Working?
              </h3>
            </div>
            <div className="px-5 py-4">
              <p className="text-stone-300 text-sm italic" style={{ fontFamily: "'EB Garamond', serif" }}>
                This item and its full journal will be cast into oblivion. The undertaking begins anew.
              </p>
            </div>
            <div className="px-5 py-3 border-t border-red-900/40 flex gap-2 justify-end">
              <button
                onClick={() => setShowResetConfirm(false)}
                className="px-4 py-1.5 text-xs uppercase tracking-widest text-stone-400 hover:text-stone-200"
                style={{ fontFamily: "'Cinzel', serif" }}
              >
                Keep It
              </button>
              <button
                onClick={confirmReset}
                className="px-4 py-1.5 text-xs uppercase tracking-widest border border-red-700 bg-red-900/40 text-red-100 hover:bg-red-800/60"
                style={{ fontFamily: "'Cinzel', serif" }}
              >
                Discard
              </button>
            </div>
          </div>
        </div>
      )}
    </React.Fragment>
  );
}

// ===========================================================
// MOUNT
// ===========================================================

const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(<App />);
