/* ============================================================
   Root app — state, persistence, navigation, shell
   ============================================================ */
const LS_KEY = "dnd2024_character_v1";

function loadSaved(){
  try{ const r=localStorage.getItem(LS_KEY); if(r) return JSON.parse(r); }catch(e){}
  return null;
}

function stepComplete(char, key){
  const cls=DND.klass(char.classKey), sp=DND.species(char.speciesKey), bg=DND.background(char.backgroundKey);
  const coreDone = ()=> stepComplete(char,"class") && stepComplete(char,"species")
    && stepComplete(char,"background") && stepComplete(char,"abilities");
  switch(key){
    case "class": return !!char.classKey && !(char.level>=DND.SUBCLASS_LEVEL && !char.subclass);
    case "species":
      if(!sp) return false;
      if(sp.choice && !(char.speciesChoice||{})[sp.choice.id]) return false;
      if(sp.key==="elf" && !char.elfKeenSense) return false;
      if(sp.key==="human" && !char.humanSkill) return false;
      return true;
    case "background": return !!bg;
    case "abilities":
      if(!char.asiMode) return false;
      if(char.asiMode==="2-1" && (!char.asiPrimary||!char.asiSecondary)) return false;
      if(char.abilityMethod==="pointbuy") return DND.pointBuySpent(char.baseScores)<=27;
      // array/roll: all six assigned
      return DND.ABILS.every(a=> (char.assignMap||{})[a]!=null);
    case "skills":{
      if(!coreDone()) return false;
      const need=cls?cls.skillCount:0;
      if((char.classSkills||[]).length<need) return false;
      if(char.classKey==="rogue" && (char.expertise||[]).length<2) return false;
      return true;
    }
    case "equipment": return coreDone();
    case "spells":{
      if(!coreDone()) return false;
      if(!cls||!cls.spellcasting) return true;
      if((char.cantrips||[]).length<DND.cantripsKnownFor(char)) return false;
      if((char.spells||[]).length<DND.spellsToPick(char)) return false;
      return true;
    }
    case "review": return coreDone() && stepComplete(char,"skills") && stepComplete(char,"spells");
    default: return false;
  }
}

function App(){
  const [char,setChar]=useState(()=> loadSaved()?.char || DND.blankChar());
  const [view,setView]=useState(()=> loadSaved()?.view || "build");
  const [step,setStep]=useState(()=> loadSaved()?.step || 0);

  // persist (char + ui state + theme)
  useEffect(()=>{
    const t=setTimeout(()=>{ try{ localStorage.setItem(LS_KEY, JSON.stringify({char,view,step,tw})); }catch(e){} }, 200);
    return ()=>clearTimeout(t);
  },[char,view,step,tw]);

  const patch=(obj)=>setChar(c=>({...c,...obj}));

  // ---- Tweaks (theme / density / ability method) — persisted in localStorage ----
  const TWEAK_DEFAULTS={ accent:["#4F46E5","#312E81","#EEF0FE","#E4E7FD"], density:"spacious" };
  const [tw,setTweak]=useTweaks(loadSaved()?.tw || TWEAK_DEFAULTS);
  useEffect(()=>{
    const root=document.documentElement;
    if(tw.accent){
      const [a,ink,soft,soft2]=tw.accent;
      root.style.setProperty("--accent",a);
      root.style.setProperty("--accent-ink",ink);
      root.style.setProperty("--accent-soft",soft);
      root.style.setProperty("--accent-soft-2",soft2);
    }
    document.body.classList.toggle("density-compact", tw.density==="compact");
  },[tw]);

  const setMethodFromTweak=(m)=>{
    patch({ abilityMethod:m, assignMap:{}, rolledPool:null,
      baseScores:{str:8,dex:8,con:8,int:8,wis:8,cha:8} });
    if(view==="build") setStep(3);
  };

  const steps=STEP_META;
  const curKey=steps[step].key;
  const goNext=()=>{ if(curKey==="review"){ setView("sheet"); } else setStep(s=>Math.min(steps.length-1,s+1)); };
  const goPrev=()=>setStep(s=>Math.max(0,s-1));
  const canNext=stepComplete(char,curKey);

  // furthest reachable: allow nav to any step <= first incomplete +1
  const firstIncomplete=steps.findIndex(s=>!stepComplete(char,s.key));
  const reachable=(i)=> firstIncomplete===-1 ? true : i<=firstIncomplete;

  const reset=()=>{ if(confirm("Start a new character? This clears the current one.")){ setChar(DND.blankChar()); setStep(0); setView("build"); } };

  const exportChar=()=>{
    const slug=(char.name||"character").replace(/[^a-z0-9]/gi,"_").toLowerCase();
    const data=JSON.stringify({version:"dnd2024_v1",char},null,2);
    const a=Object.assign(document.createElement("a"),{
      href:URL.createObjectURL(new Blob([data],{type:"application/json"})),
      download:`${slug}-dnd2024.json`
    });
    a.click(); URL.revokeObjectURL(a.href);
  };

  const importChar=()=>{
    const input=Object.assign(document.createElement("input"),{type:"file",accept:".json,application/json"});
    input.onchange=(e)=>{
      const file=e.target.files[0]; if(!file) return;
      const reader=new FileReader();
      reader.onload=(ev)=>{
        try{
          const parsed=JSON.parse(ev.target.result);
          const loaded=parsed.char||parsed; // support both wrapped and raw
          setChar({...DND.blankChar(),...loaded});
          setView("build"); setStep(0);
        }catch{ alert("Couldn't import — file doesn't look like a valid character JSON."); }
      };
      reader.readAsText(file);
    };
    input.click();
  };

  const renderStep=()=>{
    const p={char,patch};
    switch(curKey){
      case "class": return <ClassStep {...p}/>;
      case "species": return <SpeciesStep {...p}/>;
      case "background": return <BackgroundStep {...p}/>;
      case "abilities": return <AbilitiesStep {...p}/>;
      case "skills": return <SkillsStep {...p}/>;
      case "equipment": return <EquipmentStep {...p}/>;
      case "spells": return <SpellsStep {...p}/>;
      case "review": return <ReviewStep {...p} openSheet={()=>setView("sheet")}/>;
    }
  };

  const pickLabel=(key)=>{
    const cls=DND.klass(char.classKey), sp=DND.species(char.speciesKey), bg=DND.background(char.backgroundKey);
    if(key==="class") return cls?.name;
    if(key==="species") return sp?.name;
    if(key==="background") return bg?.name;
    if(key==="abilities" && char.asiMode) return char.abilityMethod==="pointbuy"?"Point Buy":char.abilityMethod==="array"?"Array":"Rolled";
    if(key==="skills" && (char.classSkills||[]).length) return `${(char.classSkills||[]).length} picked`;
    return null;
  };

  return (
    <div className="app">
      <div className="topbar">
        <div className="brand">
          <span className="mark serif">d</span>
          <span className="name">Character<b>Sheet</b> <span style={{color:"var(--faint)",fontWeight:400}}>· D&amp;D 2024</span></span>
        </div>
        <div className="spacer"></div>
        {view==="sheet" ? (
          <React.Fragment>
            <button className="tb-btn" onClick={()=>setView("build")}><Ic.edit/> Edit build</button>
            <button className="tb-btn ghost" onClick={exportChar}><Ic.download/> Export</button>
            <button className="tb-btn" onClick={()=>window.print()}><Ic.print/> Print / PDF</button>
          </React.Fragment>
        ) : (
          <React.Fragment>
            <button className="tb-btn ghost" onClick={reset}><Ic.reset/> New</button>
            <button className="tb-btn ghost" onClick={importChar}><Ic.upload/> Import</button>
            <button className="tb-btn" disabled={!DND.isComplete(char)} onClick={()=>setView("sheet")}><Ic.sheet/> View sheet</button>
          </React.Fragment>
        )}
      </div>

      {view==="build" ? (
        <div className="work">
          <div className="rail">
            <div className="rail-progress">
              <div className="bar"><i style={{width:((steps.filter(s=>stepComplete(char,s.key)).length)/steps.length*100)+"%"}}></i></div>
              <div className="cap">{steps.filter(s=>stepComplete(char,s.key)).length} of {steps.length} steps complete</div>
            </div>
            <div className="rail-h">Build steps</div>
            <div className="steps">
              {steps.map((s,i)=>{
                const done=stepComplete(char,s.key);
                const active=i===step;
                const ok=reachable(i);
                return (
                  <button key={s.key} className={"stepbtn"+(active?" active":"")+(done?" done":"")} disabled={!ok}
                    onClick={()=>ok&&setStep(i)}>
                    <span className="ix mono">{String(i+1).padStart(2,"0")}</span>
                    <span className="lab">{s.label}</span>
                    {done && !active && <span className="tick"><Ic.check/></span>}
                    {active && pickLabel(s.key) && <span className="pick">{pickLabel(s.key)}</span>}
                  </button>
                );
              })}
            </div>
          </div>

          <div className="main">
            <div className="canvas">
              {renderStep()}
              <div className="stepnav">
                <button className="btn" onClick={goPrev} disabled={step===0}><Ic.chevL/> Back</button>
                <span className="spacer"></span>
                {!canNext && curKey!=="review" && <span className="hint">Complete this step to continue</span>}
                {curKey!=="review" && (
                  <button className="btn primary" onClick={goNext} disabled={!canNext}>Next: {steps[step+1].label} <Ic.chevR/></button>
                )}
              </div>
            </div>
          </div>
        </div>
      ) : (
        <div className="main" style={{overflow:"visible"}}>
          <CharacterSheet char={char} patch={patch}/>
        </div>
      )}

      {/* ── Persistent accent color picker ── */}
      {(()=>{
        const presets=[
          ["#4F46E5","#312E81","#EEF0FE","#E4E7FD"],
          ["#B91C1C","#7F1D1D","#FCECEC","#F8D9D9"],
          ["#1E7A52","#10492F","#E7F5EE","#CFEADD"],
          ["#B45309","#713F12","#FDF1DF","#F8E4BE"],
          ["#0E7490","#0B3E4E","#E3F4F8","#C5E7F0"],
        ];
        return (
          <div className="color-picker-dock" style={{position:"fixed",bottom:20,right:20,zIndex:50,display:"flex",gap:7,padding:"7px 10px",background:"rgba(255,255,255,.92)",borderRadius:99,boxShadow:"0 2px 16px rgba(0,0,0,.14)",backdropFilter:"blur(8px)"}}>
            {presets.map((p,i)=>{
              const sel=tw.accent&&tw.accent[0]===p[0];
              return (
                <button key={i} onClick={()=>setTweak('accent',p)} title={p[0]}
                  style={{width:20,height:20,borderRadius:"50%",padding:0,cursor:"pointer",
                    background:p[0],border:`2px solid ${sel?"#fff":"transparent"}`,
                    outline:sel?`2.5px solid ${p[0]}`:"none",outlineOffset:1.5,
                    boxShadow:"0 1px 3px rgba(0,0,0,.2)",transition:"transform .12s"}}
                  onMouseEnter={e=>e.target.style.transform="scale(1.2)"}
                  onMouseLeave={e=>e.target.style.transform="scale(1)"}
                />
              );
            })}
          </div>
        );
      })()}

      <TweaksPanel>
        <TweakSection label="Theme" />
        <TweakColor label="Accent" value={tw.accent}
          options={[
            ["#4F46E5","#312E81","#EEF0FE","#E4E7FD"],
            ["#B91C1C","#7F1D1D","#FCECEC","#F8D9D9"],
            ["#1E7A52","#10492F","#E7F5EE","#CFEADD"],
            ["#B45309","#713F12","#FDF1DF","#F8E4BE"],
            ["#0E7490","#0B3E4E","#E3F4F8","#C5E7F0"]
          ]}
          onChange={(v)=>setTweak('accent',v)} />
        <TweakSection label="Layout" />
        <TweakRadio label="Density" value={tw.density} options={["compact","spacious"]}
          onChange={(v)=>setTweak('density',v)} />
        <TweakSection label="Ability scores" />
        <TweakRadio label="Method" value={char.abilityMethod} options={["pointbuy","array","roll"]}
          onChange={setMethodFromTweak} />
      </TweaksPanel>
    </div>
  );
}

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