'use strict';

// ── State ─────────────────────────────────────────────────────────────
let DATA=null, META=null, ACTIVE_TAB='overview', QA_TYPE='joint';
let NP_CORE=[], NP_ALLOC={}, NP_SHARE={};
let _updateInfo=null;
let CURR='£'; // updated from META.currency.symbol
let _IS_FIRST_RUN=false; // true while going through initial wizard → period setup
let ACTIVATED=false; // set from META.activated

// ── Themes ───────────────────────────────────────────────────────────
const THEMES = {
  emerald: {
    name:'Emerald', swatch:'#00c48c',
    '--accent':'#00c48c','--accent2':'#00a376','--accent-dim':'rgba(0,196,140,0.12)',
    '--blue':'#3b82f6','--blue-dim':'rgba(59,130,246,0.12)',
    '--panel':'#161e27','--panel2':'#1c2636','--bg':'#0d1117','--bg2':'#131920',
    '--border':'#1f2d3d','--border2':'#253447',
  },
  ocean: {
    name:'Ocean', swatch:'#3b82f6',
    '--accent':'#3b82f6','--accent2':'#2563eb','--accent-dim':'rgba(59,130,246,0.12)',
    '--blue':'#06b6d4','--blue-dim':'rgba(6,182,212,0.12)',
    '--panel':'#0f172a','--panel2':'#1e293b','--bg':'#070e1a','--bg2':'#0c1526',
    '--border':'#1e3a5f','--border2':'#1e4080',
  },
  sunset: {
    name:'Sunset', swatch:'#f59e0b',
    '--accent':'#f59e0b','--accent2':'#d97706','--accent-dim':'rgba(245,158,11,0.12)',
    '--blue':'#fb923c','--blue-dim':'rgba(251,146,60,0.12)',
    '--panel':'#1c1710','--panel2':'#251e14','--bg':'#110f0a','--bg2':'#181410',
    '--border':'#3d2e14','--border2':'#4a3820',
  },
  rose: {
    name:'Rose', swatch:'#f43f5e',
    '--accent':'#f43f5e','--accent2':'#e11d48','--accent-dim':'rgba(244,63,94,0.12)',
    '--blue':'#a78bfa','--blue-dim':'rgba(167,139,250,0.12)',
    '--panel':'#1c1018','--panel2':'#251520','--bg':'#110a10','--bg2':'#180e16',
    '--border':'#3d1a2e','--border2':'#4a2038',
  },
};

function applyTheme(themeKey){
  const t=THEMES[themeKey]||THEMES.emerald;
  const root=document.documentElement;
  Object.entries(t).forEach(([k,v])=>{if(k.startsWith('--'))root.style.setProperty(k,v);});
}

function getActiveTheme(){
  return activeProfile().theme||'emerald';
}

function renderThemePicker(containerId, currentTheme, onSelect){
  const c=el(containerId);if(!c)return;
  c.innerHTML=Object.entries(THEMES).map(([key,t])=>`
    <div class="theme-swatch ${currentTheme===key?'active':''}" data-theme="${key}" onclick="${onSelect}('${key}')" title="${t.name}">
      <div style="width:20px;height:20px;border-radius:50%;background:${t.swatch};border:2px solid ${currentTheme===key?'#fff':'transparent'}"></div>
      <span style="font-size:10px;color:var(--text2);margin-top:3px">${t.name}</span>
    </div>`).join('');
}

function selectSettingsTheme(key){
  const prof=activeProfile();
  prof.theme=key;
  applyTheme(key);
  renderThemePicker('settings-theme-picker',key,'selectSettingsTheme');
}

function selectNewProfileTheme(key){
  window._newProfileTheme=key;
  renderThemePicker('new-profile-theme-picker',key,'selectNewProfileTheme');
}

// ── Helpers ───────────────────────────────────────────────────────────
const fval=v=>{const n=parseFloat(v);return isNaN(n)?0:n;};
const money=v=>`${CURR}${fval(v).toFixed(2).replace(/\B(?=(\d{3})+(?!\d))/g,',')}`;
const el=id=>document.getElementById(id);
const today=()=>new Date().toISOString().slice(0,10);
const todayDisp=()=>{const d=new Date();return`${String(d.getDate()).padStart(2,'0')}/${String(d.getMonth()+1).padStart(2,'0')}/${d.getFullYear()}`;};

function toast(msg,type='success'){
  const t=document.createElement('div');
  t.className=`toast ${type}`;t.textContent=msg;
  el('toast-container').appendChild(t);
  setTimeout(()=>t.remove(),3000);
}

async function api(url,body=null){
  const opts=body?{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify(body)}:{};
  const r=await fetch(url,opts);
  const j=await r.json();
  if(j.error){toast(j.error,'error');throw new Error(j.error);}
  return j;
}

// ── Confirm modal ─────────────────────────────────────────────────────
function showConfirm({title='Confirm',subtitle='Review changes below.',icon='✅',lines=[]}={}){
  return new Promise((resolve,reject)=>{
    el('confirm-title').textContent=title;
    el('confirm-subtitle').textContent=subtitle;
    el('confirm-icon').textContent=icon;
    el('confirm-lines').innerHTML=lines.map(l=>`<div style="padding:3px 0;border-bottom:1px solid var(--border)">${l}</div>`).join('')||'<div class="muted small">No preview available.</div>';
    window._confirmResolve=resolve;
    window._confirmReject=()=>reject(new Error('cancelled'));
    el('modal-confirm').classList.remove('hidden');
  });
}

// ── Premium gates ────────────────────────────────────────────────────
function isPremium(){return ACTIVATED;}

// Apply blur+lock overlay to all children of a container
function premiumBlur(containerId,msg='Premium Feature'){
  const c=document.getElementById(containerId);if(!c)return;
  // Wrap content if not already wrapped
  if(c.querySelector('.premium-locked'))return;
  const wrapper=document.createElement('div');
  wrapper.className='premium-locked';
  wrapper.innerHTML='<div class="premium-overlay"><div class="premium-lock-icon">🔒</div><div class="premium-lock-text">'+msg+'</div><button class="btn btn-primary btn-sm" onclick="openModal(&quot;modal-activate&quot;)">Activate Premium</button></div>';
  c.style.position='relative';
  c.appendChild(wrapper);
}

function clearPremiumBlur(containerId){
  const c=document.getElementById(containerId);if(!c)return;
  c.querySelectorAll('.premium-locked').forEach(e=>e.remove());
}

// Gate: if not premium, show activate modal and return false
function requirePremium(msg='This feature requires Clariqo Premium.'){
  if(ACTIVATED)return true;
  openModal('modal-activate');
  return false;
}

// ── Data access ───────────────────────────────────────────────────────
const activeProfile=()=>{const p=DATA.profiles||[];const i=DATA.active_profile||0;return p[i]||{};};
const currentPeriod=()=>{const n=DATA.current_period;return n&&DATA.periods[n]?DATA.periods[n]:null;};
const currentSlot=()=>{const p=currentPeriod();if(!p)return{};return p.profiles?.[String(DATA.active_profile||0)]||{};};

function myJointShare(p){
  if(!p)return 0;
  const alloc=p.joint_allocated||{};const share=p.joint_share_pct||{};
  return Object.keys(alloc).reduce((s,c)=>s+fval(alloc[c])*fval(share[c]??50)/100,0);
}

// ── Window controls ───────────────────────────────────────────────────
function winMinimize(){if(window.pywebview&&window.pywebview.api)window.pywebview.api.minimize();}
function winMaximize(){if(window.pywebview&&window.pywebview.api)window.pywebview.api.maximize_restore();}
function winClose(){if(window.pywebview&&window.pywebview.api)window.pywebview.api.close();else window.close();}

// ── Heartbeat ─────────────────────────────────────────────────────────
(function(){setInterval(async()=>{try{await fetch('/api/heartbeat',{method:'POST'});}catch(_){}},3000);})();

// ── Splash ────────────────────────────────────────────────────────────
function runSplash(onDone){
  window._splashOnDone=onDone;
  const bar=el('splash-bar'),status=el('splash-status');
  const steps=[{pct:20,msg:'Initialising...',delay:200},{pct:50,msg:'Loading data...',delay:400},{pct:75,msg:'Checking for updates...',delay:400}];
  let i=0;
  function next(){
    if(i>=steps.length){doUpdateCheck();return;}
    const s=steps[i++];bar.style.width=s.pct+'%';status.textContent=s.msg;setTimeout(next,s.delay);
  }
  next();
}
function doUpdateCheck(){
  const bar=el('splash-bar'),status=el('splash-status');
  // First run activation validity check (non-blocking, 4s timeout)
  fetch('/api/check_update').then(r=>r.json()).then(d=>{
    if(d.ok&&d.latest&&d.latest!==d.current){
      _updateInfo=d;
      status.textContent='Update available — choose below to continue';
      const notes=(d.notes||'').split('\n').slice(0,3).join(' ').slice(0,120);
      const banner=el('splash-update');
      banner.innerHTML=
        '<div class="upd-t">🚀 Update available — '+d.latest+'</div>'+
        '<div class="upd-s">'+(notes||'New version available on GitHub')+'</div>'+
        '<div style="display:flex;gap:10px;justify-content:center;margin-top:14px">'+
          '<button class="btn btn-primary btn-sm" onclick="doSplashUpdate()">⬇ Download & Apply</button>'+
          '<button class="btn btn-secondary btn-sm" onclick="skipSplashUpdate()">Skip for now</button>'+
        '</div>';
      banner.classList.remove('hidden');
      showUpdatePill(d);
    }else{bar.style.width='100%';status.textContent='Ready';setTimeout(window._splashOnDone,400);}
  }).catch(()=>{el('splash-bar').style.width='100%';el('splash-status').textContent='Ready';setTimeout(window._splashOnDone,400);});
}
async function doSplashUpdate(){
  const d=_updateInfo;const zipUrl=d?.zip_url||'';const releaseUrl=d?.url||'';
  const bar=el('splash-bar'),status=el('splash-status');
  if(!zipUrl){if(releaseUrl)window.open(releaseUrl,'_blank');skipSplashUpdate();return;}
  bar.style.width='80%';status.textContent='Downloading update...';
  el('splash-update').innerHTML='<div class="upd-s">Downloading and applying update...</div>';
  try{
    const res=await fetch('/api/update/download',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({zip_url:zipUrl,new_version:_updateInfo?.latest||''})});
    const r=await res.json();
    if(r.ok){
      bar.style.width='100%';status.textContent='Update applied — please restart Clariqo';
      el('splash-update').innerHTML='<div class="upd-t" style="color:var(--accent)">✅ Update applied!</div><div class="upd-s">Close and reopen Clariqo to use the new version.<br><br>Files updated: '+(r.extracted||[]).join(', ')+'</div>';
    }else{
      status.textContent='Update failed — launching anyway';
      el('splash-update').innerHTML='<div class="upd-s" style="color:var(--red)">Failed: '+(r.error||'Unknown')+'</div>';
      setTimeout(window._splashOnDone,2500);
    }
  }catch(e){status.textContent='Update failed — launching anyway';setTimeout(window._splashOnDone,2000);}
}
function skipSplashUpdate(){
  el('splash-update').classList.add('hidden');
  el('splash-bar').style.width='100%';el('splash-status').textContent='Ready';
  setTimeout(window._splashOnDone,300);
}
function showUpdatePill(info){
  const pill=el('titlebar-update-pill');if(!pill)return;
  pill.classList.remove('hidden');pill.style.display='';
  pill.title='Update '+(info?.latest||'')+' available';
  pill.onclick=()=>{if(info?.url)window.open(info.url,'_blank');};
}

// ── Init ──────────────────────────────────────────────────────────────
async function init(){
  if(localStorage.getItem('clariqo-theme')==='light')document.body.classList.add('light');
  runSplash(async()=>{
    try{
      [META,DATA]=await Promise.all([api('/api/meta'),api('/api/data')]);
      CURR=META.currency?.symbol||'£';
      ACTIVATED=META.activated||false;
      applyTheme(getActiveTheme());
      // Run activation validity check if activated — awaited so result is known before showApp
      if(ACTIVATED){
        try{
          const actCheck=await fetch('/api/check_activation',{method:'POST'});
          const actResult=await actCheck.json();
          if(actResult.ok&&actResult.status==='revoked'){
            ACTIVATED=false;
            META.activated=false;
            const rb=el('splash-revoked');
            if(rb)rb.classList.remove('hidden');
            window._showRevokedModal=true;
          }
        }catch(_){} // network failure — skip silently
      }
      el('splash-screen').classList.add('hidden');
      if(!DATA.setup_done){el('setup-screen').classList.remove('hidden');showSetupStep('terms');}
      else{showApp();}
    }catch(e){console.error(e);toast('Failed to load — is server running?','error');}
  });
}

// ── Setup flow ────────────────────────────────────────────────────────
function showSetupStep(step){
  ['terms','choice','wizard'].forEach(s=>el(`setup-step-${s}`)?.classList.add('hidden'));
  el(`setup-step-${step}`)?.classList.remove('hidden');
}
function showTermsTab(tab){
  ['privacy','disclaimer','licence'].forEach(t=>{
    el(`terms-${t}`)?.classList.add('hidden');
    const b=el(`ttab-${t}`);if(b){b.style.background='var(--panel2)';b.style.color='var(--text2)';}
  });
  el(`terms-${tab}`)?.classList.remove('hidden');
  const btn=el(`ttab-${tab}`);if(btn){btn.style.background='var(--accent)';btn.style.color='#000';}
}
function updateTermsBtn(){
  const all=el('chk-privacy').checked&&el('chk-disclaimer').checked&&el('chk-licence').checked;
  const b=el('terms-accept-btn');
  if(all){b.disabled=false;b.style.background='var(--accent)';b.style.color='#000';b.style.cursor='pointer';}
  else{b.disabled=true;b.style.background='var(--border)';b.style.color='var(--text3)';b.style.cursor='not-allowed';}
}
function acceptTerms(){showSetupStep('choice');}
function chooseSetup(type){
  if(type==='fresh')startWizard();
  else el('setup-import-file').click();
}
async function doImportSetup(event){
  const file=event.target.files[0];if(!file)return;
  try{
    const d=JSON.parse(await file.text());
    if(!d.periods){toast('Not a valid Clariqo backup','error');return;}
    await api('/api/save',d);DATA=await api('/api/data');META=await api('/api/meta');
    CURR=META.currency?.symbol||'£';
    el('setup-screen').classList.add('hidden');showApp();toast('Budget imported');
  }catch(e){toast('Import failed — check the file','error');}
}

// ── Setup Wizard ──────────────────────────────────────────────────────
const CURRENCIES=[
  {code:'GBP',symbol:'£',name:'British Pound'},
  {code:'USD',symbol:'$',name:'US Dollar'},
  {code:'EUR',symbol:'€',name:'Euro'},
  {code:'JPY',symbol:'¥',name:'Japanese Yen'},
  {code:'CAD',symbol:'$',name:'Canadian Dollar'},
  {code:'AUD',symbol:'$',name:'Australian Dollar'},
  {code:'CHF',symbol:'Fr',name:'Swiss Franc'},
  {code:'CNY',symbol:'¥',name:'Chinese Yuan'},
  {code:'SEK',symbol:'kr',name:'Swedish Krona'},
  {code:'NOK',symbol:'kr',name:'Norwegian Krone'},
  {code:'DKK',symbol:'kr',name:'Danish Krone'},
  {code:'PLN',symbol:'zł',name:'Polish Zloty'},
  {code:'CZK',symbol:'Kč',name:'Czech Koruna'},
  {code:'HUF',symbol:'Ft',name:'Hungarian Forint'},
  {code:'RON',symbol:'lei',name:'Romanian Leu'},
  {code:'TRY',symbol:'₺',name:'Turkish Lira'},
  {code:'INR',symbol:'₹',name:'Indian Rupee'},
  {code:'BRL',symbol:'R$',name:'Brazilian Real'},
  {code:'MXN',symbol:'$',name:'Mexican Peso'},
  {code:'ZAR',symbol:'R',name:'South African Rand'},
];

let WIZ={step:0,name:'',wage:0,currency:CURRENCIES[0],coreExpenses:[],cards:[],debts:[],jCats:[],pCats:[],savPots:[]};
const WIZ_STEPS=8;

function wizPercent(){return Math.round((WIZ.step/WIZ_STEPS)*100)+'%';}
function wizSetProgress(){el('wiz-progress').style.width=wizPercent();}

function startWizard(){
  WIZ={step:0,name:'',wage:0,currency:CURRENCIES[0],coreExpenses:[],cards:[],debts:[],jCats:[],pCats:[],savPots:[],_step6shown:false,_activated:false};
  showSetupStep('wizard');
  wizStep(1);
}

function wizStep(step){
  WIZ.step=step;wizSetProgress();
  const body=el('wiz-body');
  const nav=(back,next,nextLabel='Next →')=>`
    <div style="display:flex;gap:10px;margin-top:20px">
      ${back?`<button class="btn btn-secondary" style="flex:1;justify-content:center" onclick="wizStep(${back})">← Back</button>`:''}
      <button class="btn btn-primary" style="flex:2;justify-content:center;padding:11px" onclick="${next}">${nextLabel}</button>
    </div>`;
  const step_of=`<div style="font-size:11px;color:var(--text3);text-transform:uppercase;letter-spacing:0.6px;margin-bottom:4px">Step ${step} of ${WIZ_STEPS}</div>`;

  if(step===1){
    body.innerHTML=`
      ${step_of}
      <div style="font-size:22px;font-weight:700;margin-bottom:4px">👋 Welcome! What's your name?</div>
      <p class="muted small mb-16">This helps personalise your Clariqo experience.</p>
      <div class="form-group"><label class="form-label">Your first name</label><input type="text" id="wiz-name" placeholder="e.g. Alex" value="${WIZ.name}" style="user-select:text" autofocus></div>
      <div class="form-group"><label class="form-label">Monthly take-home pay</label><input type="number" id="wiz-wage" step="0.01" placeholder="0.00" value="${WIZ.wage||''}"></div>
      <div id="wiz-err" style="color:var(--red);font-size:12px;min-height:15px"></div>
      ${nav(null,'wizSave(1)')}`;
  }else if(step===2){
    const opts=CURRENCIES.map(c=>`<option value="${c.code}" ${c.code===WIZ.currency.code?'selected':''}>${c.symbol} — ${c.code} — ${c.name}</option>`).join('');
    body.innerHTML=`
      ${step_of}
      <div style="font-size:22px;font-weight:700;margin-bottom:4px">💱 Choose your currency</div>
      <p class="muted small mb-16">This sets how money is displayed throughout Clariqo.</p>
      <div class="form-group"><label class="form-label">Currency</label><select id="wiz-currency">${opts}</select></div>
      <div style="background:var(--amber-dim);border:1px solid rgba(245,158,11,0.3);border-radius:var(--radius-sm);padding:10px 14px;font-size:12px;color:var(--amber);margin-top:4px">
        ⚠ This setting <b>cannot be changed</b> after setup. Choose carefully.
      </div>
      ${nav(1,'wizSave(2)')}`;
  }else if(step===3){
    const actDone=WIZ._activated||ACTIVATED;
    const successHtml='<div style="background:rgba(0,196,140,0.1);border:1px solid rgba(0,196,140,0.35);border-radius:var(--radius-sm);padding:14px;text-align:center;margin-bottom:16px"><div style="font-size:28px;margin-bottom:6px">&#127881;</div><div style="font-weight:700;color:var(--accent);font-size:15px">Premium Activated!</div><div class="muted small mt-4">All features are unlocked. Joint budgeting is fully available.</div></div><div style="display:flex;gap:10px;margin-top:20px"><button class="btn btn-secondary" style="flex:1;justify-content:center" onclick="wizStep(2)">&#8592; Back</button><button class="btn btn-primary" style="flex:2;justify-content:center;padding:11px" onclick="wizStep(4)">Next &#8594;</button></div>';
    const formHtml='<div class="form-group"><label class="form-label">Your email</label><input type="email" id="wiz-act-email" placeholder="your@email.com" style="user-select:text;margin-bottom:6px"></div><div class="form-group"><label class="form-label">Activation code</label><input type="text" id="wiz-act-code" placeholder="XXXX-XXXX-XXXX" style="text-align:center;font-size:15px;font-weight:600;letter-spacing:2px;text-transform:uppercase;user-select:text" maxlength="14"></div><div id="wiz-act-err" style="color:var(--red);font-size:12px;min-height:16px;margin-bottom:4px"></div><div style="display:flex;gap:8px;margin-bottom:12px"><button class="btn btn-primary btn-sm" style="flex:1;justify-content:center" onclick="wizActivate()">&#128275; Activate</button></div><div style="display:flex;gap:10px;margin-top:8px"><button class="btn btn-secondary" style="flex:1;justify-content:center" onclick="wizStep(2)">&#8592; Back</button><button class="btn btn-primary" style="flex:2;justify-content:center;padding:11px" onclick="wizStep(4)">Skip &#8212; no code &#8594;</button></div>';
    body.innerHTML=step_of+
      '<div style="font-size:22px;font-weight:700;margin-bottom:4px">&#128272; Do you have an activation code?</div>'+
      '<p class="muted small mb-16">If you have a Clariqo Premium code, enter it now to unlock all features. You can always activate later in Settings.</p>'+
      (actDone?successHtml:formHtml);
    if(!actDone){
      setTimeout(()=>{
        const inp=el('wiz-act-code');
        if(inp){
          inp.addEventListener('input',function(){
            let v=this.value.replace(/[^A-Z0-9]/gi,'').toUpperCase();
            if(v.length>4)v=v.slice(0,4)+'-'+v.slice(4);
            if(v.length>9)v=v.slice(0,9)+'-'+v.slice(9);
            this.value=v.slice(0,14);
          });
          inp.addEventListener('keydown',e=>{if(e.key==='Enter')wizActivate();});
          inp.focus();
        }
      },50);
    }
  }else if(step===4){
    const rows=WIZ.coreExpenses.map((e,i)=>`
      <div class="flex-between" style="padding:6px 0;border-bottom:1px solid var(--border)">
        <span style="font-size:13px">${e.name}</span>
        <div class="flex"><span class="bold">${WIZ.currency.symbol}${fval(e.amount).toFixed(2)}</span>
        <button class="btn btn-danger btn-sm" onclick="WIZ.coreExpenses.splice(${i},1);wizStep(3)">×</button></div>
      </div>`).join('');
    body.innerHTML=`
      ${step_of}
      <div style="font-size:22px;font-weight:700;margin-bottom:4px">🏠 Core monthly expenses</div>
      <p class="muted small mb-12">These are fixed costs that come out every month — rent, subscriptions, phone etc. You can skip this and add them later.</p>
      ${rows||'<div class="muted small mb-12">No core expenses added yet.</div>'}
      <div class="form-row mt-8">
        <input type="text" id="wiz-ce-name" placeholder="Expense name" style="flex:2;user-select:text">
        <input type="number" id="wiz-ce-amt" placeholder="Amount" step="0.01" style="flex:1">
        <button class="btn btn-secondary btn-sm" onclick="wizAddCore()">+ Add</button>
      </div>
      ${nav(3,'wizStep(5)')}`;
  }else if(step===5){
    const rows=WIZ.cards.map((c,i)=>`
      <div class="flex-between" style="padding:6px 0;border-bottom:1px solid var(--border)">
        <span style="font-size:13px">${c.name} <span class="muted small">· ${c.type}</span></span>
        <div class="flex"><span class="bold red">${WIZ.currency.symbol}${fval(c.balance).toFixed(2)}</span>
        <button class="btn btn-danger btn-sm" onclick="WIZ.cards.splice(${i},1);wizStep(5)">×</button></div>
      </div>`).join('');
    body.innerHTML=`
      ${step_of}
      <div style="font-size:22px;font-weight:700;margin-bottom:4px">💳 Add your cards</div>
      <p class="muted small mb-12">Add credit cards, store cards or any card you spend on. You can skip this and add later.</p>
      ${rows||'<div class="muted small mb-12">No cards added yet.</div>'}
      <div class="form-row mt-8">
        <input type="text" id="wiz-c-name" placeholder="Card name" style="flex:2;user-select:text">
        <select id="wiz-c-type" style="flex:1"><option>Credit Card</option><option>Debit Card</option><option>Store Card</option></select>
        <input type="number" id="wiz-c-bal" placeholder="Balance" step="0.01" style="flex:1">
        <input type="number" id="wiz-c-limit" placeholder="Limit" step="0.01" style="flex:1">
        <button class="btn btn-secondary btn-sm" onclick="wizAddCard()">+ Add</button>
      </div>
      ${nav(4,'wizStep(6)')}`;
  }else if(step===6){
    const mkChips=(arr,type)=>arr.map((c,i)=>`<span class="cat-chip">${c}<button onclick="WIZ.${type}.splice(${i},1);wizStep(6)">×</button></span>`).join('');
    body.innerHTML=`
      ${step_of}
      <div style="font-size:22px;font-weight:700;margin-bottom:4px">🏷 Personal categories</div>
      <p class="muted small mb-12">Create categories for your own personal spending — e.g. Entertainment, Eating Out, Clothing. You can edit these later in Settings.</p>
      <div class="cat-chips mb-8">${mkChips(WIZ.pCats,'pCats')||'<span class="muted small">None added yet</span>'}</div>
      <div class="form-row">
        <input type="text" id="wiz-pcat" placeholder="Category name" style="flex:1;user-select:text" onkeydown="if(event.key==='Enter')wizAddCat('pCats','wiz-pcat')">
        <button class="btn btn-secondary btn-sm" onclick="wizAddCat('pCats','wiz-pcat')">+ Add</button>
      </div>
      ${nav(5,'wizStep(7)')}`;
  }else if(step===7){
    const mkChips=(arr,type)=>arr.map((c,i)=>`<span class="cat-chip">${c}<button onclick="WIZ.${type}.splice(${i},1);wizStep(7)">×</button></span>`).join('');
    const shown=WIZ._step6shown||false;
    body.innerHTML=`
      ${step_of}
      <div style="font-size:22px;font-weight:700;margin-bottom:4px">🤝 Joint categories & savings</div>
      <p class="muted small mb-10">Create categories for your shared budget and savings pots.</p>
      <div style="background:rgba(245,158,11,0.1);border:1px solid rgba(245,158,11,0.35);border-radius:var(--radius-sm);padding:11px 14px;margin-bottom:14px;font-size:12px;color:var(--amber);line-height:1.6">
        🔒 <b>Premium Feature</b> — Joint budgeting requires Clariqo Premium. You can set this up now so it is ready when you activate, but it will not be usable or trackable until then.
        ${!shown?`<div style="margin-top:10px;display:flex;gap:8px">
          <button class="btn btn-secondary btn-sm" onclick="wizStep(8)" style="flex:1;justify-content:center">Skip for now</button>
          <button class="btn btn-primary btn-sm" onclick="WIZ._step6shown=true;wizStep(7)" style="flex:1;justify-content:center">Set it up anyway →</button>
        </div>`:''}
      </div>
      ${shown?`
      <div class="card-title mt-4">Joint Categories</div>
      <div class="cat-chips mb-8">${mkChips(WIZ.jCats,'jCats')||'<span class="muted small">None added yet</span>'}</div>
      <div class="form-row mb-12">
        <input type="text" id="wiz-jcat" placeholder="Category name" style="flex:1;user-select:text" onkeydown="if(event.key==='Enter')wizAddCat('jCats','wiz-jcat')">
        <button class="btn btn-secondary btn-sm" onclick="wizAddCat('jCats','wiz-jcat')">+ Add</button>
      </div>
      <div class="sep"></div>
      <div class="card-title mt-8">Savings Pots</div>
      <div class="cat-chips mb-8">${mkChips(WIZ.savPots,'savPots')||'<span class="muted small">None added yet</span>'}</div>
      <div class="form-row">
        <input type="text" id="wiz-sav" placeholder="Pot name" style="flex:1;user-select:text" onkeydown="if(event.key==='Enter')wizAddCat('savPots','wiz-sav')">
        <button class="btn btn-secondary btn-sm" onclick="wizAddCat('savPots','wiz-sav')">+ Add</button>
      </div>
      ${nav(6,'wizStep(8)')}`:nav(6,'wizStep(8)')}`;
  }else if(step===8){
    const coreTotal=WIZ.coreExpenses.reduce((s,e)=>s+fval(e.amount),0);
    body.innerHTML=`
      ${step_of}
      <div style="font-size:22px;font-weight:700;margin-bottom:4px">✅ You're all set, ${WIZ.name}!</div>
      <p class="muted small mb-16">Here's a summary of your setup. Click <b>Start Budgeting</b> to create your first budget period.</p>
      <div style="background:var(--panel2);border:1px solid var(--border);border-radius:var(--radius);padding:14px;font-size:13px;line-height:2">
        <div class="flex-between"><span class="muted">Name</span><span class="bold">${WIZ.name}</span></div>
        <div class="flex-between"><span class="muted">Currency</span><span class="bold">${WIZ.currency.symbol} ${WIZ.currency.code}</span></div>
        <div class="flex-between"><span class="muted">Monthly wage</span><span class="bold">${WIZ.currency.symbol}${fval(WIZ.wage).toFixed(2)}</span></div>
        <div class="flex-between"><span class="muted">Core expenses</span><span class="bold">${WIZ.coreExpenses.length} (${WIZ.currency.symbol}${coreTotal.toFixed(2)}/mo)</span></div>
        <div class="flex-between"><span class="muted">Cards</span><span class="bold">${WIZ.cards.length}</span></div>
        <div class="flex-between"><span class="muted">Personal categories</span><span class="bold">${WIZ.pCats.length}</span></div>
        <div class="flex-between"><span class="muted">Joint categories</span><span class="bold">${WIZ.jCats.length}</span></div>
        <div class="flex-between"><span class="muted">Savings pots</span><span class="bold">${WIZ.savPots.length}</span></div>
      </div>
      ${nav(7,'finishWizard()','🚀 Start Budgeting')}`;
  }
}

function wizSave(step){
  if(step===1){
    const name=el('wiz-name').value.trim();const wage=fval(el('wiz-wage').value);
    if(!name){el('wiz-err').textContent='Please enter your name.';return;}
    WIZ.name=name;WIZ.wage=wage;wizStep(2);
  }else if(step===2){
    const sel=el('wiz-currency');const code=sel.value;
    WIZ.currency=CURRENCIES.find(c=>c.code===code)||CURRENCIES[0];
    wizStep(3);
  }
}
function wizAddCore(){
  const n=el('wiz-ce-name').value.trim();const a=fval(el('wiz-ce-amt').value);
  if(!n||a<=0){toast('Enter a name and amount','error');return;}
  WIZ.coreExpenses.push({name:n,amount:a});wizStep(4);
  // Refocus name input so user can keep adding
  setTimeout(()=>el('wiz-ce-name')?.focus(),50);
}
function wizAddCard(){
  const n=el('wiz-c-name').value.trim();
  if(!n){toast('Enter a card name','error');return;}
  WIZ.cards.push({name:n,type:el('wiz-c-type').value,balance:fval(el('wiz-c-bal').value),limit:fval(el('wiz-c-limit').value),status:'Active',payments:[],notes:''});
  wizStep(5);
}
function wizAddCat(arr,inputId){
  const inp=el(inputId);const val=inp.value.trim().toUpperCase();
  if(!val)return;if(WIZ[arr].includes(val)){toast('Already added','error');return;}
  WIZ[arr].push(val);inp.value='';wizStep(WIZ.step);
  // Refocus so user can keep adding without re-clicking
  setTimeout(()=>el(inputId)?.focus(),50);
}
async function wizActivate(){
  const code=el('wiz-act-code')?.value.trim().toUpperCase()||'';
  const errEl=el('wiz-act-err');
  const btn=document.querySelector('#wiz-body .btn-primary');
  if(errEl)errEl.textContent='';
  if(!code){if(errEl)errEl.textContent='Please enter a code.';return;}
  // Show loading state
  if(errEl){errEl.style.color='var(--accent)';errEl.textContent='🔄 Checking your code with the activation server...';}
  if(btn){btn.disabled=true;btn.textContent='Checking...';}
  try{
    let wiz_machine_id='';
  try{const mid=await fetch('/api/machine_id');const mj=await mid.json();wiz_machine_id=mj.machine_id||'';}catch(_){}
  const wiz_email=el('wiz-act-email')?.value?.trim()||'';
    await api('/api/activate',{code,email:wiz_email,machine_id:wiz_machine_id});
    ACTIVATED=true;META.activated=true;
    WIZ._activated=true;
    WIZ._step6shown=true;
    try{await fetch('/api/set_title',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({title:'Clariqo PREMIUM'})});}catch(_){}
    wizStep(3);
  }catch(e){
    if(btn){btn.disabled=false;btn.textContent='🔓 Activate';}
    if(errEl){errEl.style.color='var(--red)';errEl.textContent=e.message||'Invalid code.';}
  }
}

async function finishWizard(){
  await api('/api/setup',{
    user_name:WIZ.name,wage:WIZ.wage,currency:WIZ.currency,
    core_expenses:WIZ.coreExpenses,
    cards_debts:{cards:WIZ.cards,debts:[]},
    categories:{joint:WIZ.jCats,personal:WIZ.pCats,savings:WIZ.savPots},
  });
  DATA=await api('/api/data');META=await api('/api/meta');
  CURR=META.currency?.symbol||'£';
  el('setup-screen').classList.add('hidden');
  _IS_FIRST_RUN=true;
  showApp();
  // Open new period modal — pre-fill wage from wizard, hide the wage field
  openModal('modal-new-period');
  // Pre-fill wage from wizard so user doesn't enter it again
  const wageField=el('np-wage');if(wageField)wageField.value=WIZ.wage||'';
  const wageRow=el('np-wage-row');if(wageRow)wageRow.style.display='none';
}

// ── App init ──────────────────────────────────────────────────────────
function showApp(){
  el('app-shell').classList.remove('hidden');el('app-shell').style.display='flex';
  buildStaticUI();refreshAll();showTab('overview');
  initTooltips();renderProfileTabs();applyPremiumGates();
  updateVersionPill();
  checkNag();
  // Show revoked notification if activation was revoked on this startup
  if(window._showRevokedModal){window._showRevokedModal=false;setTimeout(()=>openModal('modal-revoked'),800);}
}

// ── Premium gate application ─────────────────────────────────────────
function applyPremiumGates(){
  if(ACTIVATED){
    // Clear all gates
    ['joint-cats-setting','bill-reminders-setting'].forEach(id=>clearPremiumBlur(id));
    // Remove full-tab locks
    ['tab-joint','tab-investments','tab-mortgage'].forEach(id=>{
      const t=el(id);if(t)t.querySelectorAll('.premium-locked').forEach(e=>e.remove());
    });
    // Clear sidebar blur
    ['sb-joint-row','sb-bills-row'].forEach(id=>{
      const r=el(id);if(r){r.style.filter='';r.style.pointerEvents='';}
    });
    renderActStatus();
    updateVersionPill();
    return;
  }
  // Joint tab nav — replace content with lock screen
  setTimeout(()=>{
    const jt=el('tab-joint');
    if(jt&&!jt.querySelector('.premium-locked')){
      jt.style.position='relative';
      const ov=document.createElement('div');
      ov.className='premium-locked premium-full-tab';
      ov.innerHTML='<div class="premium-overlay"><div class="premium-lock-icon" style="font-size:48px">🔒</div><div class="premium-lock-text" style="font-size:16px">Joint Budget</div><div class="premium-lock-sub">The joint budget is a Clariqo Premium feature. Activate to track shared spending with your partner.</div><button class="btn btn-primary" onclick="openModal(&quot;modal-activate&quot;)">🔓 Activate Premium</button></div>';
      jt.appendChild(ov);
    }
    // Mortgage tab
    const mt=el('tab-mortgage');
    if(mt&&!mt.querySelector('.premium-locked')){
      mt.style.position='relative';
      const ov=document.createElement('div');
      ov.className='premium-locked premium-full-tab';
      ov.innerHTML='<div class="premium-overlay"><div class="premium-lock-icon" style="font-size:48px">🔒</div><div class="premium-lock-text" style="font-size:16px">Mortgage Tracker</div><div class="premium-lock-sub">Mortgage tracking is a Clariqo Premium feature. Activate to track your LTV, monthly breakdown and rate history.</div><button class="btn btn-primary" onclick="openModal(&quot;modal-activate&quot;)">🔓 Activate Premium</button></div>';
      mt.appendChild(ov);
    }
    // Investments tab
    const it=el('tab-investments');
    if(it&&!it.querySelector('.premium-locked')){
      it.style.position='relative';
      const ov=document.createElement('div');
      ov.className='premium-locked premium-full-tab';
      ov.innerHTML='<div class="premium-overlay"><div class="premium-lock-icon" style="font-size:48px">🔒</div><div class="premium-lock-text" style="font-size:16px">Investments</div><div class="premium-lock-sub">Investment tracking is a Clariqo Premium feature. Activate to track your pots and contributions.</div><button class="btn btn-primary" onclick="openModal(&quot;modal-activate&quot;)">🔓 Activate Premium</button></div>';
      it.appendChild(ov);
    }
  },100);
  renderActStatus();
}

function renderActStatus(){
  const b=el('act-status-block');if(!b)return;
  if(ACTIVATED){
    b.innerHTML='<div style="display:flex;align-items:center;gap:10px;padding:6px 0"><span style="font-size:22px">✅</span><div><div style="font-weight:600;color:var(--accent)">Clariqo Premium Active</div><div class="muted small">All features unlocked. Thank you!</div></div></div>';
  }else{
    b.innerHTML='<div class="muted small mb-10">The free version includes personal budgeting, savings, and overview. Activate Premium to unlock joint budgeting, investments, additional profiles, and more.</div><button class="btn btn-primary btn-sm" onclick="openModal(&quot;modal-activate&quot;)">🔓 Enter Activation Code</button>';
  }
}

async function doActivate(){
  const code=el('act-code').value.trim().toUpperCase();
  const email=(el('act-email')?.value||'').trim();
  const errEl=el('act-err');
  const btn=document.querySelector('#modal-activate .btn-primary');
  errEl.textContent='';
  if(!email){errEl.style.color='var(--red)';errEl.textContent='Please enter your email address.';return;}
  if(!code){errEl.style.color='var(--red)';errEl.textContent='Please enter a code.';return;}
  // Fetch machine ID then activate
  let machine_id='';
  try{const mid=await fetch('/api/machine_id');const mj=await mid.json();machine_id=mj.machine_id||'';}catch(_){}
  // Show loading state
  errEl.style.color='var(--accent)';
  errEl.textContent='🔄 Checking your code with the activation server...';
  if(btn){btn.disabled=true;btn.textContent='Checking...';}
  try{
    await api('/api/activate',{code,email,machine_id});
    ACTIVATED=true;META.activated=true;
    closeModal('modal-activate');
    // Reset button for next time
    if(btn){btn.disabled=false;btn.textContent='🔓 Activate';}
    errEl.textContent='';errEl.style.color='var(--red)';
    updateVersionPill();
    // Update window title
    try{await fetch('/api/set_title',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({title:'Clariqo PREMIUM'})});}catch(_){}
    // Show celebration
    showCelebration();
  }catch(e){
    if(btn){btn.disabled=false;btn.textContent='🔓 Activate';}
    errEl.style.color='var(--red)';
    errEl.textContent=e.message||'Invalid code.';
  }
}

// ── Celebration & confetti ───────────────────────────────────────────
function showCelebration(){
  // Start confetti
  startConfetti();
  // Show modal
  el('modal-celebration').classList.remove('hidden');
}

function startConfetti(){
  const canvas=el('confetti-canvas');
  if(!canvas)return;
  canvas.style.display='block';
  canvas.width=window.innerWidth;
  canvas.height=window.innerHeight;
  const ctx=canvas.getContext('2d');
  const pieces=[];
  const colours=['#00c48c','#3b82f6','#f59e0b','#ef4444','#8b5cf6','#ec4899','#10b981'];
  for(let i=0;i<180;i++){
    pieces.push({
      x:Math.random()*canvas.width,
      y:Math.random()*canvas.height-canvas.height,
      w:Math.random()*10+6,
      h:Math.random()*6+4,
      colour:colours[Math.floor(Math.random()*colours.length)],
      rot:Math.random()*360,
      rotSpeed:(Math.random()-0.5)*4,
      vx:(Math.random()-0.5)*3,
      vy:Math.random()*4+2,
      opacity:1,
    });
  }
  let frame=0;
  function draw(){
    ctx.clearRect(0,0,canvas.width,canvas.height);
    let alive=false;
    pieces.forEach(p=>{
      p.x+=p.vx;p.y+=p.vy;p.rot+=p.rotSpeed;
      if(frame>180)p.opacity=Math.max(0,p.opacity-0.008);
      if(p.y<canvas.height+20&&p.opacity>0)alive=true;
      ctx.save();
      ctx.globalAlpha=p.opacity;
      ctx.translate(p.x,p.y);
      ctx.rotate(p.rot*Math.PI/180);
      ctx.fillStyle=p.colour;
      ctx.fillRect(-p.w/2,-p.h/2,p.w,p.h);
      ctx.restore();
    });
    frame++;
    if(alive)requestAnimationFrame(draw);
    else{canvas.style.display='none';}
  }
  draw();
}

async function doRestart(){
  // Update button to show restarting state
  const btn=document.querySelector('#modal-celebration .btn-primary');
  if(btn){btn.textContent='Restarting...';btn.disabled=true;}
  try{
    await fetch('/api/restart',{method:'POST'});
    // Backend will spawn new process and kill this one — nothing more to do
  }catch(_){
    // If fetch fails the backend is already restarting — that's fine
  }
}

// ── Version pill & nag ───────────────────────────────────────────────
function updateVersionPill(){
  const pill=el('version-pill');if(!pill)return;
  if(ACTIVATED){
    pill.textContent='✓ PREMIUM';
    pill.className='version-pill version-pill-premium';
    pill.onclick=null;
    pill.style.cursor='default';
    pill.title='Clariqo Premium — all features unlocked';
  }else{
    pill.textContent='FREE VERSION';
    pill.className='version-pill version-pill-free';
    pill.onclick=()=>openVersionPill();
    pill.title='Click to see what Premium unlocks';
  }
}

function openVersionPill(){
  if(!ACTIVATED)openModal('modal-premium-info');
}

function openGumroad(){
  window.open('https://budgiebird3.gumroad.com/l/pkvfnl','_blank');
  closeModal('modal-premium-info');
  closeModal('modal-nag');
}

function checkNag(){
  if(ACTIVATED)return;
  setTimeout(()=>openModal('modal-nag'),1500);
}

// ── Tab nav ───────────────────────────────────────────────────────────
function showTab(tab){
  // Intercept premium tabs for free users
  if(!ACTIVATED&&(tab==='joint'||tab==='investments'||tab==='mortgage')){
    ACTIVE_TAB=tab;
    document.querySelectorAll('.tab-content').forEach(t=>t.classList.add('hidden'));
    el(`tab-${tab}`)?.classList.remove('hidden');
    document.querySelectorAll('.nav-item').forEach(b=>b.classList.toggle('active',b.dataset.tab===tab));
    const titles={joint:'Joint Budget',investments:'Investments',mortgage:'Mortgage'};
    el('topbar-title').textContent=titles[tab]||tab;
    return; // show the lock screen content, don't refresh
  }
  ACTIVE_TAB=tab;
  document.querySelectorAll('.tab-content').forEach(t=>t.classList.add('hidden'));
  el(`tab-${tab}`)?.classList.remove('hidden');
  document.querySelectorAll('.nav-item').forEach(b=>b.classList.toggle('active',b.dataset.tab===tab));
  const titles={overview:'Overview',joint:'Joint Budget',personal:'Personal',mortgage:'Mortgage',savings:'Savings',investments:'Investments',settings:'Settings'};
  el('topbar-title').textContent=titles[tab]||tab;
  refreshTab(tab);
}
function refreshTab(tab){
  switch(tab){case 'overview':renderOverview();break;case 'joint':renderJoint();break;case 'personal':renderPersonal();break;case 'mortgage':renderMortgage();break;case 'savings':renderSavings();break;case 'investments':renderInvestments();break;case 'settings':renderSettings();break;}
  updateSidebarSnapshot();
}
function refreshAll(){updateSidebar();updateSidebarSnapshot();refreshTab(ACTIVE_TAB);}

// ── Sidebar ───────────────────────────────────────────────────────────
function updateSidebar(){
  const p=currentPeriod();
  el('sb-period-name').textContent=p?p.name:'No period';
  const sel=el('period-select');
  const periods=Object.keys(DATA.periods||{}).sort((a,b)=>(DATA.periods[b].created||'')<(DATA.periods[a].created||'')?-1:1);
  sel.innerHTML=periods.map(n=>`<option value="${n}" ${n===DATA.current_period?'selected':''}>${n}</option>`).join('');
  const lb=el('topbar-lock-badge');
  lb.innerHTML=p?.locked?'<span class="badge badge-amber">🔒 Locked</span>':'';
}

// ── Profile tabs (sidebar inline switcher) ────────────────────────────
function renderProfileTabs(){
  const profiles=DATA.profiles||[];const active=DATA.active_profile||0;
  const container=el('profile-tabs');if(!container)return;
  container.innerHTML=profiles.map((p,i)=>`
    <button onclick="quickSwitchProfile(${i})" style="flex:1;padding:4px 8px;font-size:11px;font-weight:600;border:1px solid ${i===active?'var(--accent)':'var(--border2)'};border-radius:20px;background:${i===active?'var(--accent-dim)':'var(--panel2)'};color:${i===active?'var(--accent)':'var(--text2)'};cursor:pointer;font-family:var(--font);transition:all 0.15s" title="${p.name||'Profile '+(i+1)}">
      ${p.name||'P'+(i+1)}
    </button>`).join('');
  if(profiles.length<2){
    container.innerHTML+=`<button onclick="openModal('modal-profile')" style="padding:4px 8px;font-size:11px;border:1px dashed var(--border2);border-radius:20px;background:none;color:var(--text3);cursor:pointer;font-family:var(--font)" title="Add profile">+ Add</button>`;
  }
}
async function quickSwitchProfile(idx){
  if(idx===DATA.active_profile)return;
  DATA.active_profile=idx;
  await api('/api/save',DATA);DATA=await api('/api/data');
  renderProfileTabs();refreshAll();
  applyTheme(DATA.profiles[idx]?.theme||'emerald');
  toast(`Switched to ${DATA.profiles[idx]?.name||'Profile '+(idx+1)}`,'success');
}

// ── Snapshot tooltips ─────────────────────────────────────────────────
function updateSidebarSnapshot(){
  const p=currentPeriod();const slot=currentSlot();if(!p||!slot)return;
  // Apply premium blur to joint/bills sidebar rows
  ['sb-joint-row','sb-bills-row'].forEach(id=>{
    const row=el(id);if(!row)return;
    if(!ACTIVATED){
      row.style.filter='blur(4px)';
      row.style.pointerEvents='none';
      row.title='Premium Feature';
    }else{
      row.style.filter='';
      row.style.pointerEvents='';
      row.title='';
    }
  });
  const wage=fval(slot.wage);
  const coreT=(slot.core_expenses||[]).reduce((s,e)=>s+fval(e.amount),0);
  const addExp=(slot.additional_expenses||[]).reduce((s,e)=>s+fval(e.amount),0);
  const addInc=(slot.additional_incomes||[]).reduce((s,e)=>s+fval(e.amount),0);
  const jShare=myJointShare(p);
  const avail=wage+addInc+fval(slot.personal_rollover)-coreT-addExp-jShare;
  const jAlloc=Object.values(p.joint_allocated||{}).reduce((s,v)=>s+fval(v),0);
  const jSpent=(p.joint_transactions||[]).reduce((s,t)=>s+fval(t.amount),0);
  const jRemain=jAlloc+fval(p.joint_leftover)-jSpent;
  const totalSav=Object.values(slot.savings||{}).reduce((s,v)=>s+fval(v),0);
  const today_d=new Date();
  const bills=(DATA.bill_reminders||[]).map(b=>{
    const due_day=parseInt(b.due_day)||1;
    // Use real month lengths for bill day calculation
    const year=today_d.getFullYear(),month=today_d.getMonth();
    const daysInMonth=new Date(year,month+1,0).getDate();
    const clamped=Math.min(due_day,daysInMonth);
    let due=new Date(year,month,clamped);
    if(due<=today_d){const nm=month===11?0:month+1,ny=month===11?year+1:year;const dim=new Date(ny,nm+1,0).getDate();due=new Date(ny,nm,Math.min(due_day,dim));}
    return{...b,days:Math.round((due-today_d)/(864e5))};
  }).sort((a,b)=>a.days-b.days);
  const nb=bills[0];
  const avEl=el('sb-personal-avail');if(avEl){avEl.textContent=money(avail);avEl.className='sb-snap-val '+(avail>=0?'green':'red');}
  const jrEl=el('sb-joint-remain');if(jrEl){jrEl.textContent=money(jRemain);jrEl.className='sb-snap-val '+(jRemain>=0?'blue':'red');}
  const svEl=el('sb-savings');if(svEl)svEl.textContent=money(totalSav);
  const nbEl=el('sb-next-bill');if(nbEl)nbEl.textContent=nb?(nb.days===0?'Today!':nb.days===1?'Tomorrow':nb.days+'d'):'None';
  window._snapData={avail,wage,coreT,addExp,addInc,jShare,jAlloc,jSpent,jRemain,totalSav,bills,slot,p};
}

function initTooltips(){
  const tt=el('snap-tooltip');
  document.querySelectorAll('.sb-snap-row').forEach(row=>{
    row.addEventListener('mouseenter',()=>{
      const type=row.dataset.snap;const d=window._snapData||{};let html='';
      if(type==='personal'){html=`<div class="stt-title">Personal Breakdown</div><div class="stt-row"><span class="stt-l">Take-home wage</span><span class="stt-r green">${money(d.wage)}</span></div><div class="stt-row"><span class="stt-l">Additional income</span><span class="stt-r green">${money(d.addInc)}</span></div><div class="stt-row"><span class="stt-l">Core expenses</span><span class="stt-r red">−${money(d.coreT)}</span></div><div class="stt-row"><span class="stt-l">Variable expenses</span><span class="stt-r red">−${money(d.addExp)}</span></div><div class="stt-row"><span class="stt-l">Joint share</span><span class="stt-r amber">−${money(d.jShare)}</span></div><div class="stt-row"><span class="stt-l" style="font-weight:700;color:var(--text)">Available</span><span class="stt-r ${d.avail>=0?'green':'red'}">${money(d.avail)}</span></div>`;}
      else if(type==='joint'){const cats=Object.keys(d.p?.joint_allocated||{});const spent={};(d.p?.joint_transactions||[]).forEach(t=>{spent[t.category]=(spent[t.category]||0)+fval(t.amount);});html=`<div class="stt-title">Joint Breakdown</div>`;cats.forEach(c=>{const a=fval(d.p.joint_allocated[c]);const s=fval(spent[c]);const rem=a-s;html+=`<div class="stt-row"><span class="stt-l">${c}</span><span class="stt-r ${rem>=0?'green':'red'}">${money(rem)}</span></div>`;});html+=`<div class="stt-row"><span class="stt-l" style="font-weight:700;color:var(--text)">Total remaining</span><span class="stt-r">${money(d.jRemain)}</span></div>`;}
      else if(type==='savings'){html=`<div class="stt-title">Savings Pots</div>`;(META.savings_types||[]).forEach(s=>{const bal=fval(d.slot?.savings?.[s]||0);html+=`<div class="stt-row"><span class="stt-l">${s}</span><span class="stt-r green">${money(bal)}</span></div>`;});html+=`<div class="stt-row"><span class="stt-l" style="font-weight:700;color:var(--text)">Total</span><span class="stt-r green">${money(d.totalSav)}</span></div>`;}
      else if(type==='bills'){html=`<div class="stt-title">Upcoming Bills</div>`;const up=(d.bills||[]).slice(0,6);if(up.length)up.forEach(b=>{html+=`<div class="stt-row"><span class="stt-l">${b.name}</span><span class="stt-r ${b.days<=3?'red':b.days<=7?'amber':''}">${b.days===0?'Today':b.days+'d · '+money(b.amount)}</span></div>`;});else html+=`<div class="stt-row"><span class="stt-l muted">No bills due soon</span></div>`;}
      tt.innerHTML=html;
      const rect=row.getBoundingClientRect();tt.style.left=(rect.right+10)+'px';tt.style.top=Math.min(rect.top,window.innerHeight-300)+'px';tt.classList.add('visible');
    });
    row.addEventListener('mouseleave',()=>tt.classList.remove('visible'));
  });
}

// ── Static UI ─────────────────────────────────────────────────────────
function buildStaticUI(){
  const jc=el('j-cat'),pc=el('p-cat');if(jc)jc.innerHTML='';if(pc)pc.innerHTML='';
  META.joint_categories.forEach(cat=>{const o=document.createElement('option');o.value=o.textContent=cat;jc?.appendChild(o);});
  META.personal_categories.forEach(cat=>{const o=document.createElement('option');o.value=o.textContent=cat;pc?.appendChild(o);});
}

async function selectPeriod(name){
  await api('/api/period/select',{name});DATA=await api('/api/data');refreshAll();
}

// ── OVERVIEW ──────────────────────────────────────────────────────────
function renderOverview(){
  const p=currentPeriod();const slot=currentSlot();
  if(!p){el('ov-stats').innerHTML='<div class="empty-state"><div class="empty-icon">📋</div>No period. Create one to get started.</div>';return;}
  const wage=fval(slot.wage);const coreT=(slot.core_expenses||[]).reduce((s,e)=>s+fval(e.amount),0);
  const addExp=(slot.additional_expenses||[]).reduce((s,e)=>s+fval(e.amount),0);
  const addInc=(slot.additional_incomes||[]).reduce((s,e)=>s+fval(e.amount),0);
  const jShare=myJointShare(p);const avail=wage+addInc+fval(slot.personal_rollover)-coreT-addExp-jShare;
  const jAlloc=Object.values(p.joint_allocated||{}).reduce((s,v)=>s+fval(v),0);
  const jSpent=(p.joint_transactions||[]).reduce((s,t)=>s+fval(t.amount),0);
  const jRemain=jAlloc+fval(p.joint_leftover)-jSpent;
  const totalSav=Object.values(slot.savings||{}).reduce((s,v)=>s+fval(v),0);
  const totalInv=(slot.investment_contributions||[]).reduce((s,c)=>s+fval(c.amount),0);
  const jCard=ACTIVATED?`<div class="stat-card ${jRemain>=0?'blue':'red'}"><div class="stat-label">Joint Remaining</div><div class="stat-val">${money(jRemain)}</div></div>`:`<div class="stat-card" style="position:relative;overflow:hidden"><div class="stat-label">Joint Remaining</div><div class="stat-val" style="filter:blur(6px)">£000.00</div><div style="position:absolute;inset:0;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:4px"><span style="font-size:18px">🔒</span><button class="btn btn-primary btn-sm" style="font-size:10px" onclick="openModal('modal-activate')">Premium</button></div></div>`;
  const invCard=ACTIVATED?`<div class="stat-card amber"><div class="stat-label">Total Invested</div><div class="stat-val">${money(totalInv)}</div></div>`:`<div class="stat-card" style="position:relative;overflow:hidden"><div class="stat-label">Total Invested</div><div class="stat-val" style="filter:blur(6px)">£000.00</div><div style="position:absolute;inset:0;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:4px"><span style="font-size:18px">🔒</span><button class="btn btn-primary btn-sm" style="font-size:10px" onclick="openModal('modal-activate')">Premium</button></div></div>`;
  el('ov-stats').innerHTML=`
    <div class="stat-card ${avail>=0?'green':'red'}"><div class="stat-label">Personal Available</div><div class="stat-val">${money(avail)}</div></div>
    ${jCard}
    <div class="stat-card green"><div class="stat-label">Total Savings</div><div class="stat-val">${money(totalSav)}</div></div>
    ${invCard}`;
  // Bills using server-side correct dates
  fetch('/api/bill/next_dates').then(r=>r.json()).then(bills=>{
    const upcoming=bills.filter(b=>b.days_until<=14);
    el('ov-bills').innerHTML=upcoming.length?upcoming.map(b=>`
      <div class="flex-between" style="padding:6px 0;border-bottom:1px solid var(--border)">
        <span style="font-size:13px">${b.name}</span>
        <span class="${b.days_until<=3?'red':b.days_until<=7?'amber':'muted'} small">${b.days_until===0?'Today!':b.days_until===1?'Tomorrow':b.days_until+'d'}</span>
        <span class="bold" style="font-size:13px">${money(b.amount)}</span>
      </div>`).join(''):'<div class="muted small">No bills due in the next 14 days.</div>';
  }).catch(()=>{});
  const goals=DATA.goals||[];
  el('ov-goals').innerHTML=goals.length?goals.map(g=>{const pct=g.target>0?Math.min(g.current/g.target*100,100):0;return`<div style="margin-bottom:9px"><div class="flex-between mb-4"><span style="font-size:13px;font-weight:500">${g.name}</span><span class="small muted">${money(g.current)} / ${money(g.target)}</span></div><div class="progress-bar"><div class="progress-fill ${pct>=100?'green':pct>=60?'amber':'red'}" style="width:${pct}%"></div></div></div>`}).join(''):'<div class="muted small">No goals set.</div>';
  const jtxns=[...(p.joint_transactions||[])].reverse().slice(0,8);
  el('ov-joint-txns').innerHTML=jtxns.length?jtxns.map(t=>`<tr><td class="muted small">${t.date||''}</td><td><span class="badge badge-blue" style="font-size:10px">${t.category}</span></td><td style="font-size:13px">${t.reason||''}</td><td class="amount red">${money(t.amount)}</td></tr>`).join(''):'<tr><td colspan="4" class="empty-state">No transactions yet.</td></tr>';
  const pexps=[...(slot.additional_expenses||[])].reverse().slice(0,8);
  el('ov-personal-txns').innerHTML=pexps.length?pexps.map(e=>`<tr><td class="muted small">${(e.date||'').replace(/-/g,'/')}</td><td><span class="badge badge-grey" style="font-size:10px">${e.category}</span></td><td style="font-size:13px">${e.reason||''}</td><td class="amount red">${money(e.amount)}</td></tr>`).join(''):'<tr><td colspan="4" class="empty-state">No expenses yet.</td></tr>';
  el('period-notes').value=p.notes||'';
}

// ── JOINT ─────────────────────────────────────────────────────────────
function renderJoint(){
  const p=currentPeriod();if(!p){el('j-alloc-rows').innerHTML='<div class="muted small">No period selected.</div>';return;}
  el('j-leftover').value=fval(p.joint_leftover).toFixed(2);
  const spent={};(p.joint_transactions||[]).forEach(t=>{spent[t.category]=(spent[t.category]||0)+fval(t.amount);});
  const alloc=p.joint_allocated||{};const share=p.joint_share_pct||{};
  let ta=0,ts=0;
  el('j-alloc-rows').innerHTML=META.joint_categories.map(cat=>{
    const a=fval(alloc[cat]);const s=fval(spent[cat]);const sp=fval(share[cat]??50);
    const rem=a-s;ta+=a;ts+=s;
    const pct=a>0?Math.min(s/a*100,100):0;const col=pct>=100?'red':pct>=80?'amber':'green';
    return`<div class="alloc-card">
      <div class="alloc-header">
        <span class="alloc-name">${cat}</span>
        <span class="alloc-remain ${col}">${money(rem)} left</span>
      </div>
      <div class="progress-bar mb-4"><div class="progress-fill ${col}" style="width:${pct}%"></div></div>
      <div class="alloc-meta">
        <span class="muted small">Spent ${money(s)} of ${money(a)}</span>
        <div class="alloc-inputs">
          <div class="alloc-field">
            <label class="alloc-label">Allocated</label>
            <input type="number" class="alloc-input" value="${a.toFixed(2)}" step="0.01" id="alloc-${cat.replace(/\s/g,'_')}" style="user-select:text">
          </div>
          <div class="alloc-field">
            <label class="alloc-label">My share %</label>
            <input type="number" class="alloc-input alloc-share" value="${sp.toFixed(0)}" step="1" min="0" max="100" id="share-${cat.replace(/\s/g,'_')}" style="user-select:text">
          </div>
          <button class="btn btn-primary btn-sm" onclick="setAlloc('${cat}')">Apply</button>
        </div>
      </div>
    </div>`;
  }).join('');
  const jShare=myJointShare(p);
  el('j-summary').innerHTML=`
    <div class="flex-between"><span class="muted small">Total allocated</span><span class="bold">${money(ta)}</span></div>
    <div class="flex-between"><span class="muted small">Total spent</span><span class="bold red">${money(ts)}</span></div>
    <div class="flex-between"><span class="muted small">My joint share</span><span class="bold amber">${money(jShare)}</span></div>`;
  const query=(el('j-search')?.value||'').toLowerCase();
  let txns=(p.joint_transactions||[]).map((t,i)=>({...t,_i:i}));
  if(query)txns=txns.filter(t=>JSON.stringify(t).toLowerCase().includes(query));
  txns=[...txns].reverse();
  el('j-txn-body').innerHTML=txns.length?txns.map(t=>`<tr>
    <td class="muted small">${t.date||''}</td>
    <td><span class="badge badge-blue" style="font-size:10px">${t.category}</span></td>
    <td style="font-size:13px">${t.reason||''}</td>
    <td class="muted small">${t.notes||''}</td>
    <td class="amount red">${money(t.amount)}</td>
    <td><button class="btn btn-danger btn-sm" onclick="delJointTxn(${t._i})">×</button></td>
  </tr>`).join(''):'<tr><td colspan="6" class="empty-state">No transactions.</td></tr>';
  const transfers=p.transfers||[];
  el('transfer-log').innerHTML=transfers.length?transfers.map((t,i)=>`
    <div class="transfer-item">
      <div><div style="font-size:13px;font-weight:500">${t.type||''}</div><div class="transfer-type">${t.date||''}${t.note?' · '+t.note:''}</div></div>
      <div class="flex"><span class="transfer-amount">${money(t.amount)}</span><button class="btn btn-danger btn-sm" onclick="delTransfer(${i})">×</button></div>
    </div>`).join(''):'<div class="muted small">No transfers this period.</div>';
}

// ── PERSONAL ──────────────────────────────────────────────────────────
function renderPersonal(){
  const p=currentPeriod();const slot=currentSlot();const prof=activeProfile();if(!p)return;
  const budgetCats=prof.personal_budget_categories||[];
  el('p-budget-cat').innerHTML='<option value="">None</option>'+budgetCats.map(c=>`<option value="${c.name}">${c.name}</option>`).join('');
  const cards=prof.cards||[];
  el('p-card').innerHTML='<option value="">None</option>'+cards.filter(c=>c.status!=='Closed').map(c=>`<option value="${c.name}">${c.name}</option>`).join('');
  const query=(el('p-search')?.value||'').toLowerCase();
  let exps=(slot.additional_expenses||[]).map((e,i)=>({...e,_i:i}));
  if(query)exps=exps.filter(e=>JSON.stringify(e).toLowerCase().includes(query));
  exps=[...exps].reverse();
  const total=(slot.additional_expenses||[]).reduce((s,e)=>s+fval(e.amount),0);
  el('p-exp-body').innerHTML=exps.length?exps.map(e=>{
    const d=(e.date||'').includes('-')?e.date.split('-').reverse().join('/'):e.date;
    return`<tr><td class="muted small">${d}</td><td><span class="badge badge-grey" style="font-size:10px">${e.category}</span></td><td style="font-size:13px">${e.reason||''}</td><td class="muted small">${e.paid_with_card||''}</td><td class="amount red">${money(e.amount)}</td><td><button class="btn btn-danger btn-sm" onclick="delPersonalExpense(${e._i})">×</button></td></tr>`;
  }).join(''):'<tr><td colspan="6" class="empty-state">No expenses yet.</td></tr>';
  el('p-total').innerHTML=`Total: <span class="red">${money(total)}</span>`;
  const expenses=slot.additional_expenses||[];
  el('p-budget-cats').innerHTML=budgetCats.length?budgetCats.map(bc=>{
    const spent=expenses.reduce((s,e)=>(e.budget_category===bc.name||(!e.budget_category&&e.category===bc.name))?s+fval(e.amount):s,0);
    const pct=bc.allocated>0?Math.min(spent/bc.allocated*100,100):0;const col=pct>=100?'red':pct>=(bc.warn_pct||80)?'amber':'green';
    return`<div class="cat-row"><div style="flex:1"><div class="flex-between mb-4"><span class="cat-name">${bc.name}</span><span class="small ${col}">${money(spent)} / ${money(bc.allocated)}</span></div><div class="progress-bar"><div class="progress-fill ${col}" style="width:${pct}%"></div></div></div><button class="btn btn-danger btn-sm" onclick="delBudgetCat('${bc.name}')">×</button></div>`;
  }).join(''):'<div class="muted small mb-8">No budget categories yet.</div>';
  const core=slot.core_expenses||[];
  const coreTotal=core.reduce((s,e)=>s+fval(e.amount),0);
  el('p-core-list').innerHTML=core.length?core.map((e,i)=>`
    <div class="flex-between" style="padding:6px 0;border-bottom:1px solid var(--border);font-size:13px">
      <span>${e.name||e.description||''}</span>
      <div class="flex"><span class="bold">${money(e.amount)}</span>
        <button class="btn btn-secondary btn-sm" onclick="openEditCore(${i})">✏</button>
        <button class="btn btn-danger btn-sm" onclick="delCoreExpense(${i})">×</button>
      </div>
    </div>`).join('')+`<div class="flex-between mt-8"><span class="muted small">Total</span><span class="bold">${money(coreTotal)}</span></div>`
    :'<div class="muted small">No fixed expenses.</div>';
  const incomes=slot.additional_incomes||[];
  el('ai-list').innerHTML=incomes.length?incomes.map((inc,i)=>`
    <div class="flex-between" style="padding:5px 0;border-bottom:1px solid var(--border)">
      <span style="font-size:12px">${inc.description||''}</span>
      <div class="flex"><span class="bold green small">${money(inc.amount)}</span><button class="btn btn-danger btn-sm" onclick="delIncome(${i})">×</button></div>
    </div>`).join(''):'';
  renderDebts(prof);renderCards(prof,slot);
}

function renderDebts(prof){
  const debts=prof.debts||[];const p=currentPeriod();const pStart=p?.period_start||'';const pEnd=p?.period_end||'';
  el('debt-list').innerHTML=debts.length?debts.map((d,i)=>{
    const orig=fval(d.original_balance);const curr=fval(d.current_balance);
    const pct=orig>0?Math.min((1-curr/orig)*100,100):100;const col=pct>=75?'green':pct>=40?'amber':'red';
    const periodPays=(d.payments||[]).filter(pay=>{try{const pd=pay.date?.includes('/')?pay.date.split('/').reverse().join('-'):pay.date;return pd>=pStart&&pd<=(pEnd||'9999');}catch{return false;}});
    return`<div class="debt-card"><div class="flex-between mb-8"><div class="debt-name">${d.name} <span class="muted small">· ${d.type}</span></div><span class="badge ${d.status==='Paid Off'?'badge-green':'badge-amber'}">${d.status}</span></div><div class="debt-meta"><div><div class="dm-label">Balance</div><div class="dm-val red">${money(curr)}</div></div><div><div class="dm-label">Original</div><div class="dm-val muted">${money(orig)}</div></div><div><div class="dm-label">APR</div><div class="dm-val amber">${fval(d.apr).toFixed(1)}%</div></div><div><div class="dm-label">Min/mo</div><div class="dm-val">${money(d.min_payment)}</div></div></div><div class="progress-bar"><div class="progress-fill ${col}" style="width:${pct}%"></div></div><div class="flex-between mt-4"><span class="small muted">${pct.toFixed(0)}% paid off</span>${periodPays.length?`<span class="paid-badge">✓ Paid · ${periodPays.map(pay=>money(pay.amount)).join(', ')}</span>`:''}</div><div class="btn-row"><button class="btn btn-primary btn-sm" onclick="openPayDebt(${i})">+ Payment</button><button class="btn btn-danger btn-sm" onclick="delDebt(${i})">🗑</button></div></div>`;
  }).join(''):'<div class="empty-state"><div class="empty-icon">✅</div>No debts.</div>';
}

function renderCards(prof,slot){
  const cards=prof.cards||[];const p=currentPeriod();const pStart=p?.period_start||'';const pEnd=p?.period_end||'';const snaps=slot.card_snapshots||{};
  el('card-list').innerHTML=cards.length?cards.map((c,i)=>{
    const bal=fval(snaps[c.name]??c.balance);const globBal=fval(c.balance);const lim=fval(c.limit);const util=lim>0?Math.min(bal/lim*100,100):0;const col=util<30?'green':util<50?'amber':'red';
    const repays=(c.payments||[]).filter(pay=>{if(pay.is_purchase)return false;try{const pd=pay.date?.includes('/')?pay.date.split('/').reverse().join('-'):pay.date;return pd>=pStart&&pd<=(pEnd||'9999');}catch{return false;}});
    const balChanged=snaps[c.name]!==undefined&&Math.round(bal*100)!==Math.round(globBal*100);
    const snapNote=balChanged?` <span style="font-weight:700;color:var(--accent)">Now: ${money(globBal)}</span>`:'';    
    const periodBalLabel=`<span style="color:var(--text2)">Period start: <b>${money(bal)}</b></span>`;
    const nowLabel=balChanged?`<span style="color:var(--accent);font-weight:700"> · Now: ${money(globBal)}</span>`:'';
    return`<div class="card-entry"><div class="flex-between mb-8"><div class="debt-name">${c.name} <span class="muted small">· ${c.type}</span></div><span class="badge ${c.status==='Active'?'badge-green':'badge-grey'}">${c.status}</span></div>${lim>0?`<div class="flex-between mb-4"><span class="small">${periodBalLabel}${nowLabel} / <span class="muted">${money(lim)}</span></span><span class="small ${col}">${util.toFixed(0)}% used</span></div><div class="progress-bar"><div class="progress-fill ${col}" style="width:${util}%"></div></div>`:`<div class="small mb-8">${periodBalLabel}${nowLabel}</div>`}${repays.length?`<div class="paid-badge mt-4">✓ Paid · ${repays.map(r=>money(r.amount)).join(', ')}</div>`:''}<div class="btn-row"><button class="btn btn-primary btn-sm" onclick="openPayCard(${i})">💳 Pay</button><button class="btn btn-danger btn-sm" onclick="delCard(${i})">🗑</button></div></div>`;
  }).join(''):'<div class="empty-state"><div class="empty-icon">💳</div>No cards.</div>';
}

// ── SAVINGS ───────────────────────────────────────────────────────────
function renderSavings(){
  const slot=currentSlot();const savings=slot.savings||{};const targets=slot.savings_targets||{};const savTypes=META.savings_types||[];
  el('sav-contrib-pot').innerHTML=savTypes.map(s=>`<option value="${s}">${s}</option>`).join('');
  let totalSav=0;
  el('savings-pots').innerHTML=savTypes.map(s=>{
    const bal=fval(savings[s]);const tgt=fval(targets[s]);totalSav+=bal;
    const pct=tgt>0?Math.min(bal/tgt*100,100):0;const col=pct>=100?'green':pct>=60?'amber':'red';
    return`<div class="savings-row"><div style="flex:1"><div class="flex-between mb-4"><span class="savings-name">${s}</span><span class="savings-bal">${money(bal)}</span></div>${tgt>0?`<div class="progress-bar"><div class="progress-fill ${col}" style="width:${pct}%"></div></div><div class="flex-between mt-4"><span class="small muted">Target: ${money(tgt)}</span><span class="small ${col}">${pct.toFixed(0)}%</span></div>`:''}</div><div style="display:flex;gap:4px;margin-left:8px"><input type="number" value="${bal.toFixed(2)}" step="0.01" style="width:85px;padding:4px 7px;font-size:12px;user-select:text" id="sav-bal-${s.replace(/\s/g,'-')}" onchange="updateSavingsBal('${s}')"><input type="number" value="${tgt.toFixed(2)}" step="0.01" style="width:75px;padding:4px 7px;font-size:12px;user-select:text" id="sav-tgt-${s.replace(/\s/g,'-')}" onchange="updateSavingsTgt('${s}')"></div></div>`;
  }).join('');
  el('savings-summary').innerHTML=`<div class="flex-between" style="font-size:15px;font-weight:700;margin-bottom:4px"><span>Total Savings</span><span class="green">${money(totalSav)}</span></div>${savTypes.map(s=>{const bal=fval(savings[s]);const tgt=fval(targets[s]);return`<div class="flex-between" style="padding:5px 0;border-bottom:1px solid var(--border);font-size:13px"><span>${s}</span><span class="bold">${money(bal)}${tgt>0?` / ${money(tgt)}`:''}</span></div>`;}).join('')}`;
}

// ── INVESTMENTS ───────────────────────────────────────────────────────
function renderInvestments(){
  const slot=currentSlot();const prof=activeProfile();const pots=prof.investment_pots||[];const savTypes=META.savings_types||[];
  el('inv-pots-list').innerHTML=pots.length?pots.map(pt=>`<div class="flex-between" style="padding:6px 0;border-bottom:1px solid var(--border);font-size:13px"><span class="bold">${pt.name}</span></div>`).join(''):'<div class="muted small">No pots yet.</div>';
  el('inv-pot-select').innerHTML=pots.map(pt=>`<option value="${pt.name}">${pt.name}</option>`).join('');
  el('inv-sav-pot').innerHTML=savTypes.map(s=>`<option value="${s}">${s}</option>`).join('');
  toggleInvSavPot();
  const contribs=slot.investment_contributions||[];let total=0;
  el('inv-history').innerHTML=contribs.length?contribs.map(c=>{total+=fval(c.amount);return`<tr><td class="muted small">${c.date||''}</td><td style="font-size:13px">${c.type||''}</td><td class="muted small">${c.source==='savings'&&c.source_pot?c.source_pot:c.source||''}</td><td class="amount green">${money(c.amount)}</td></tr>`;}).join(''):'<tr><td colspan="4" class="empty-state">No contributions.</td></tr>';
  el('inv-total').innerHTML=`Total this period: <span class="green">${money(total)}</span>`;
}
function toggleInvSavPot(){const s=el('inv-source');if(s)el('inv-sav-pot-row').style.display=s.value==='savings'?'':'none';}

// ── SETTINGS ──────────────────────────────────────────────────────────
function renderSettings(){
  el('set-name').value=DATA.user_name||'';
  renderThemePicker('settings-theme-picker',getActiveTheme(),'selectSettingsTheme');
  // Category chips
  renderCatChips('joint',   META.joint_categories,    'set-joint-cats-chips');
  renderCatChips('personal',META.personal_categories, 'set-personal-cats-chips');
  renderCatChips('savings', META.savings_types,       'set-savings-chips');
  // Apply premium blur to joint cats and bills for free users
  if(!ACTIVATED){
    setTimeout(()=>{
      premiumBlur('joint-cats-setting','Joint categories require Premium');
      premiumBlur('bill-reminders-setting','Bill reminders require Premium');
    },50);
  }else{
    clearPremiumBlur('joint-cats-setting');
    clearPremiumBlur('bill-reminders-setting');
  }
  renderActStatus();
  const periods=Object.keys(DATA.periods||{}).sort((a,b)=>(DATA.periods[b].created||'')<(DATA.periods[a].created||'')?-1:1);
  el('set-periods-list').innerHTML=periods.map(name=>{
    const p=DATA.periods[name];
    return`<div class="flex-between" style="padding:6px 0;border-bottom:1px solid var(--border)"><div><div style="font-size:13px;font-weight:${name===DATA.current_period?'700':'400'}">${name}</div><div class="small muted">${p.period_start||''} → ${p.period_end||''}</div></div><div class="flex"><span class="badge ${p.locked?'badge-amber':'badge-grey'}">${p.locked?'🔒':'◯'}</span><button class="btn btn-secondary btn-sm" onclick="toggleLock('${name}',${!p.locked})">${p.locked?'Unlock':'Lock'}</button><button class="btn btn-danger btn-sm" onclick="deletePeriod('${name}')">Delete</button></div></div>`;
  }).join('');
  // Bills using correct date calculation
  fetch('/api/bill/next_dates').then(r=>r.json()).then(bills=>{
    el('bill-list').innerHTML=bills.length?bills.map((b,i)=>`<div class="flex-between" style="padding:5px 0;border-bottom:1px solid var(--border)"><div><span style="font-size:13px">${b.name}</span><div class="small muted">Day ${b.due_day} each month · next: ${b.next_date} (${b.days_until===0?'today':b.days_until+'d'})</div></div><div class="flex"><span class="bold">${money(b.amount)}</span><button class="btn btn-danger btn-sm" onclick="delBill(${i})">×</button></div></div>`).join(''):'<div class="muted small">No bills.</div>';
  }).catch(()=>{});
}

function renderCatChips(type, cats, containerId){
  const container=el(containerId);if(!container)return;
  container.innerHTML=cats.length?cats.map((c,i)=>`
    <span class="cat-chip">${c}<button onclick="removeCatChip('${type}',${i})">×</button></span>`).join(''):'<span class="muted small">None added yet</span>';
}

function addCatChip(type){
  const inputId={'joint':'set-jcat-input','personal':'set-pcat-input','savings':'set-sav-input'}[type];
  const inp=el(inputId);const val=inp.value.trim().toUpperCase();
  if(!val){toast('Enter a category name','error');return;}
  const lists={'joint':META.joint_categories,'personal':META.personal_categories,'savings':META.savings_types};
  if(lists[type].includes(val)){toast('Already exists','error');return;}
  lists[type].push(val);inp.value='';
  saveCatChips(type).then(()=>setTimeout(()=>el(inputId)?.focus(),50));
}
async function removeCatChip(type,idx){
  const lists={'joint':META.joint_categories,'personal':META.personal_categories,'savings':META.savings_types};
  lists[type].splice(idx,1);
  await saveCatChips(type);
}
async function saveCatChips(type){
  const body={};
  if(type==='joint')body.joint_categories=META.joint_categories;
  else if(type==='personal')body.personal_categories=META.personal_categories;
  else if(type==='savings')body.savings_types=META.savings_types;
  await api('/api/config/update',body);
  META=await api('/api/meta');DATA=await api('/api/data');
  renderSettings();
  // Rebuild static dropdowns
  buildStaticUI();
  toast('Categories saved');
}

// ── New Period ────────────────────────────────────────────────────────
function updateNpPeriodName(){
  const s=el('np-start').value,e=el('np-end').value;
  const prev=el('np-period-preview');
  if(s&&e){
    try{
      const sd=new Date(s),ed=new Date(e);
      const fmt=d=>`${d.getDate()} ${d.toLocaleString('en-GB',{month:'long',year:'numeric'})}`;
      prev.textContent='Period name: '+fmt(sd)+' → '+fmt(ed);
    }catch{prev.textContent='';}
  }else{prev.textContent='Select start and end dates to preview the period name';}
}
function npSetStep(step){
  [1,2,3].forEach(s=>{el(`np-step-${s}`)?.classList.toggle('hidden',s!==step);const ind=el(`np-step-ind-${s}`);if(ind)ind.style.background=s<=step?'var(--accent)':'var(--border)';});
}
function npNext(from){
  if(from===1){
    if(!el('np-start').value||!el('np-end').value){toast('Select start and end dates','error');return;}
    const prof=activeProfile();NP_CORE=[...((prof.global_core_expenses)||[]).map(e=>({...e}))];
    renderNpCore();npSetStep(2);
  }else if(from===2){
    const p=currentPeriod();
    META.joint_categories.forEach(cat=>{NP_ALLOC[cat]=fval(p?.joint_allocated?.[cat]||0);NP_SHARE[cat]=fval(p?.joint_share_pct?.[cat]||50);});
    renderNpAlloc();npSetStep(3);
  }
}
function npBack(from){if(from===2)npSetStep(1);else if(from===3)npSetStep(2);}
function renderNpCore(){
  el('np-core-list').innerHTML=NP_CORE.map((e,i)=>`<div class="form-row" style="margin-bottom:6px"><input type="text" value="${e.name||e.description||''}" style="flex:2;user-select:text" onchange="NP_CORE[${i}].name=this.value"><input type="number" value="${e.amount}" step="0.01" style="flex:1;user-select:text" onchange="NP_CORE[${i}].amount=fval(this.value)"><button class="btn btn-danger btn-sm" onclick="NP_CORE.splice(${i},1);renderNpCore()">×</button></div>`).join('');
}
function npAddCore(){
  const n=el('np-ce-name').value.trim();const a=fval(el('np-ce-amt').value);
  if(!n)return;NP_CORE.push({name:n,amount:a});el('np-ce-name').value='';el('np-ce-amt').value='';
  renderNpCore();
  setTimeout(()=>el('np-ce-name')?.focus(),50);
}
function renderNpAlloc(){
  const p=currentPeriod();const spent={};
  if(p)(p.joint_transactions||[]).forEach(t=>{spent[t.category]=(spent[t.category]||0)+fval(t.amount);});
  el('np-alloc-rows').innerHTML=META.joint_categories.map(cat=>{
    const prevAlloc=fval(p?.joint_allocated?.[cat]||0);const prevSpent=fval(spent[cat]||0);const rec=Math.max(prevSpent,prevAlloc);
    return`<div style="margin-bottom:10px;padding-bottom:10px;border-bottom:1px solid var(--border)">
      <div class="flex-between mb-4"><span style="font-size:13px;font-weight:600">${cat}</span><span class="small muted">Last: spent ${money(prevSpent)}, alloc ${money(prevAlloc)}</span></div>
      <div class="form-row">
        <div class="form-group" style="flex:2"><label class="form-label">Allocate <span class="muted" style="font-size:10px">rec: ${money(rec)}</span></label><input type="number" id="np-alloc-${cat.replace(/\s/g,'_')}" value="${rec.toFixed(2)}" step="0.01" style="user-select:text" oninput="NP_ALLOC['${cat}']=fval(this.value)"></div>
        <div class="form-group" style="flex:1"><label class="form-label">My share %</label><input type="number" id="np-share-${cat.replace(/\s/g,'_')}" value="${NP_SHARE[cat]||50}" step="1" min="0" max="100" style="user-select:text" oninput="NP_SHARE['${cat}']=fval(this.value)"></div>
        <button class="btn btn-secondary btn-sm" style="margin-bottom:1px" onclick="useRec('${cat}',${rec})">Use rec</button>
      </div>
    </div>`;
  }).join('');
}
function useRec(cat,rec){const i=el(`np-alloc-${cat.replace(/\s/g,'_')}`);if(i){i.value=rec.toFixed(2);NP_ALLOC[cat]=rec;}}
async function createPeriod(){
  const start=el('np-start').value;const end=el('np-end').value;const wage=fval(el('np-wage').value);
  if(!start){toast('Enter start date','error');return;}
  const prev=DATA.current_period;
  if(prev&&DATA.periods[prev]&&!DATA.periods[prev].locked){
    if(confirm(`Lock previous period "${prev}"?`)){await api('/api/period/lock',{name:prev,locked:true});}
  }
  const result=await api('/api/period/new',{period_start:start,period_end:end,wage});
  DATA=await api('/api/data');
  const name=result.name||DATA.current_period;
  const p=DATA.periods[name];const slot=p?.profiles?.[String(DATA.active_profile||0)];
  if(slot){slot.core_expenses=[...NP_CORE];}
  if(p){META.joint_categories.forEach(cat=>{p.joint_allocated[cat]=NP_ALLOC[cat]||0;p.joint_share_pct[cat]=NP_SHARE[cat]||50;});}
  await api('/api/save',DATA);DATA=await api('/api/data');
  // Show wage row again for future period creation
  const wr=el('np-wage-row');if(wr)wr.style.display='';
  _IS_FIRST_RUN=false;
  closeModal('modal-new-period');npSetStep(1);updateSidebar();refreshTab(ACTIVE_TAB);
  toast(`Period "${name}" created`);
}

// ── CRUD actions ──────────────────────────────────────────────────────
async function addJointTxn(){
  const p=currentPeriod();if(!p){toast('No period','error');return;}if(p.locked){toast('Period locked','error');return;}
  const amt=fval(el('j-amount').value);if(amt<=0){toast('Enter an amount','error');return;}
  const cat=el('j-cat').value;const reason=el('j-reason').value;
  const alloc=fval(p.joint_allocated?.[cat]||0);const spent=(p.joint_transactions||[]).reduce((s,t)=>t.category===cat?s+fval(t.amount):s,0);const rem=alloc-spent;
  const lines=[`Add joint transaction: <b>${reason||'—'}</b>`,`Category: <b>${cat}</b>  ·  Amount: <b class="red">${money(amt)}</b>`,`Category remaining: <b>${money(rem)}</b> → <b class="${rem-amt<0?'red':'green'}">${money(rem-amt)}</b>`];
  if(alloc>0&&amt>rem)lines.push(`<span class="red">⚠ Over budget by ${money(amt-rem)}</span>`);
  try{await showConfirm({title:'Add Joint Transaction',subtitle:'This will be logged against the joint budget.',icon:'💰',lines});}catch{return;}
  await api('/api/joint/transaction/add',{category:cat,reason,amount:amt,notes:el('j-notes').value,date:todayDisp()});
  el('j-reason').value='';el('j-amount').value='';el('j-notes').value='';
  DATA=await api('/api/data');renderJoint();renderOverview();updateSidebarSnapshot();toast('Transaction added');
}
async function delJointTxn(idx){
  const p=currentPeriod();const t=p?.joint_transactions?.[idx];
  try{await showConfirm({title:'Delete Transaction',subtitle:'This cannot be undone.',icon:'🗑',lines:[`Remove: <b>${t?.reason||'—'}</b>  ·  <b class="red">${money(t?.amount)}</b>`,`<span class="muted">The joint budget remaining will increase accordingly.</span>`]});}catch{return;}
  await api('/api/joint/transaction/delete',{index:idx});DATA=await api('/api/data');renderJoint();renderOverview();updateSidebarSnapshot();
}
async function setAlloc(cat){
  await api('/api/joint/alloc/set',{category:cat,allocated:fval(el(`alloc-${cat.replace(/\s/g,'_')}`)?.value||0),share_pct:fval(el(`share-${cat.replace(/\s/g,'_')}`)?.value||50)});
  DATA=await api('/api/data');renderJoint();updateSidebarSnapshot();toast('Allocation updated');
}
async function setLeftover(){await api('/api/joint/leftover/set',{leftover:fval(el('j-leftover').value)});DATA=await api('/api/data');renderJoint();updateSidebarSnapshot();}

async function addPersonalExpense(){
  const p=currentPeriod();if(!p){toast('No period','error');return;}if(p.locked){toast('Locked','error');return;}
  const amt=fval(el('p-amount').value);if(amt<=0){toast('Enter an amount','error');return;}
  const cat=el('p-cat').value;const reason=el('p-reason').value;const card=el('p-card').value;const bc=el('p-budget-cat').value;
  const slot=currentSlot();const wage=fval(slot.wage);const core=(slot.core_expenses||[]).reduce((s,e)=>s+fval(e.amount),0);const prev=(slot.additional_expenses||[]).reduce((s,e)=>s+fval(e.amount),0);const inc=(slot.additional_incomes||[]).reduce((s,e)=>s+fval(e.amount),0);const jShare=myJointShare(p);const avail=wage+inc+fval(slot.personal_rollover)-core-prev-jShare;
  const lines=[`Add personal expense: <b>${reason||'—'}</b>`,`Category: <b>${cat}</b>${bc?' · Budget: <b>'+bc+'</b>':''}  ·  Amount: <b class="red">${money(amt)}</b>`,`Personal available: <b>${money(avail)}</b> → <b class="${avail-amt<0?'red':'green'}">${money(avail-amt)}</b>`];
  if(card)lines.push(`Card "<b>${card}</b>" balance will increase by <b>${money(amt)}</b>`);
  try{await showConfirm({title:'Add Personal Expense',subtitle:'This will be logged to your personal budget.',icon:'👤',lines});}catch{return;}
  await api('/api/personal/expense/add',{category:cat,budget_category:bc,reason,amount:amt,notes:el('p-notes').value,paid_with_card:card,is_purchase:!!card,date:today()});
  el('p-reason').value='';el('p-amount').value='';el('p-notes').value='';el('p-card').value='';
  DATA=await api('/api/data');renderPersonal();renderOverview();updateSidebarSnapshot();toast('Expense added');
}
async function delPersonalExpense(idx){
  const slot=currentSlot();const e=slot.additional_expenses?.[idx];
  try{await showConfirm({title:'Delete Expense',subtitle:'This cannot be undone.',icon:'🗑',lines:[`Remove: <b>${e?.reason||'—'}</b>  ·  <b class="red">${money(e?.amount)}</b>`,`<span class="muted">Personal available budget will increase accordingly.</span>`]});}catch{return;}
  await api('/api/personal/expense/delete',{index:idx});DATA=await api('/api/data');renderPersonal();renderOverview();updateSidebarSnapshot();
}
async function addIncome(){
  const amt=fval(el('ai-amount').value);const desc=el('ai-desc').value.trim();
  if(amt<=0||!desc){toast('Fill description and amount','error');return;}
  try{await showConfirm({title:'Add Additional Income',subtitle:'This increases your personal available budget.',icon:'💚',lines:[`Income: <b>${desc}</b>  ·  Amount: <b class="green">${money(amt)}</b>`]});}catch{return;}
  await api('/api/personal/income/add',{description:desc,amount:amt});
  el('ai-desc').value='';el('ai-amount').value='';
  DATA=await api('/api/data');renderPersonal();renderOverview();updateSidebarSnapshot();toast('Income added');
}
async function delIncome(idx){
  const slot=currentSlot();const inc=slot.additional_incomes?.[idx];
  try{await showConfirm({title:'Remove Income',subtitle:'This cannot be undone.',icon:'🗑',lines:[`Remove: <b>${inc?.description||'—'}</b>  ·  <b class="green">${money(inc?.amount)}</b>`]});}catch{return;}
  await api('/api/personal/income/delete',{index:idx});DATA=await api('/api/data');renderPersonal();renderOverview();updateSidebarSnapshot();
}

// Core expenses
async function addCoreExpense(){
  const name=el('ce-name').value.trim();const amt=fval(el('ce-amount').value);if(!name||amt<=0){toast('Fill name and amount','error');return;}
  await api('/api/core_expense/add',{name,amount:amt});el('ce-name').value='';el('ce-amount').value='';
  DATA=await api('/api/data');closeModal('modal-add-core');renderPersonal();renderOverview();updateSidebarSnapshot();toast('Core expense added');
}
function openEditCore(idx){
  const slot=currentSlot();const e=slot.core_expenses?.[idx];if(!e)return;
  el('ece-name').value=e.name||'';el('ece-amount').value=e.amount||'';el('ece-index').value=idx;
  openModal('modal-edit-core');
}
async function editCoreExpense(){
  const idx=parseInt(el('ece-index').value);
  await api('/api/core_expense/edit',{index:idx,name:el('ece-name').value.trim(),amount:fval(el('ece-amount').value)});
  DATA=await api('/api/data');closeModal('modal-edit-core');renderPersonal();renderOverview();updateSidebarSnapshot();toast('Updated');
}
async function delCoreExpense(idx){
  const slot=currentSlot();const e=slot.core_expenses?.[idx];
  try{await showConfirm({title:'Delete Core Expense',subtitle:'This cannot be undone.',icon:'🗑',lines:[`Remove: <b>${e?.name||'—'}</b>  ·  <b>${money(e?.amount)}</b>`,`<span class="muted">Will also be removed from your profile defaults.</span>`]});}catch{return;}
  await api('/api/core_expense/delete',{index:idx});DATA=await api('/api/data');renderPersonal();renderOverview();updateSidebarSnapshot();toast('Removed');
}

async function addBudgetCat(){const name=el('bc-name').value.trim();if(!name){toast('Enter a name','error');return;}await api('/api/budget_category/add',{name,allocated:fval(el('bc-alloc').value),warn_pct:parseInt(el('bc-warn').value)||80});el('bc-name').value='';el('bc-alloc').value='';DATA=await api('/api/data');renderPersonal();toast('Category added');}
async function delBudgetCat(name){
  try{await showConfirm({title:'Delete Budget Category',subtitle:'Expenses already logged will remain.',icon:'🗑',lines:[`Remove category: <b>${name}</b>`]});}catch{return;}
  await api('/api/budget_category/delete',{name});DATA=await api('/api/data');renderPersonal();
}
function openPayDebt(idx){const d=activeProfile().debts?.[idx];if(!d)return;el('pay-debt-title').textContent=`Pay — ${d.name}`;el('pd-amount').value=d.min_payment||'';el('pd-note').value='';el('pd-index').value=idx;openModal('modal-pay-debt');}
async function payDebt(){
  const idx=parseInt(el('pd-index').value);const amt=fval(el('pd-amount').value);if(amt<=0){toast('Enter amount','error');return;}
  const debt=activeProfile().debts?.[idx];const newBal=Math.max(0,fval(debt?.current_balance)-amt);
  try{await showConfirm({title:'Make Debt Payment',icon:'💸',lines:[`Debt: <b>${debt?.name||'—'}</b>`,`Payment: <b class="green">${money(amt)}</b>`,`Balance: <b class="red">${money(debt?.current_balance)}</b> → <b class="${newBal<=0?'green':'red'}">${money(newBal)}</b>`,newBal<=0?'<span class="green">🎉 This will mark the debt as Paid Off!</span>':''].filter(Boolean)});}catch{return;}
  await api('/api/debt/pay',{index:idx,amount:amt,note:el('pd-note').value});DATA=await api('/api/data');closeModal('modal-pay-debt');renderPersonal();renderOverview();updateSidebarSnapshot();toast('Payment recorded');
}
async function addDebt(){const name=el('d-name').value.trim();if(!name){toast('Enter name','error');return;}await api('/api/debt/add',{name,type:el('d-type').value,original_balance:fval(el('d-orig').value),current_balance:fval(el('d-curr').value),apr:fval(el('d-apr').value),min_payment:fval(el('d-min').value)});DATA=await api('/api/data');closeModal('modal-add-debt');renderPersonal();toast('Debt added');}
async function delDebt(idx){
  const d=activeProfile().debts?.[idx];
  try{await showConfirm({title:'Delete Debt',icon:'🗑',lines:[`Remove: <b>${d?.name||'—'}</b>  ·  Balance: <b class="red">${money(d?.current_balance)}</b>`,`<span class="muted">All payment history will also be removed.</span>`]});}catch{return;}
  await api('/api/debt/delete',{index:idx});DATA=await api('/api/data');renderPersonal();
}
function openPayCard(idx){const c=activeProfile().cards?.[idx];if(!c)return;el('pay-card-title').textContent=`Pay — ${c.name}`;el('pc-amount').value=c.balance||'';el('pc-note').value='';el('pc-index').value=idx;openModal('modal-pay-card');}
async function payCard(){
  const idx=parseInt(el('pc-index').value);const amt=fval(el('pc-amount').value);if(amt<=0){toast('Enter amount','error');return;}
  const card=activeProfile().cards?.[idx];const newBal=Math.max(0,fval(card?.balance)-amt);
  try{await showConfirm({title:'Make Card Payment',icon:'💳',lines:[`Card: <b>${card?.name||'—'}</b>`,`Payment: <b class="green">${money(amt)}</b>`,`Balance: <b class="red">${money(card?.balance)}</b> → <b class="${newBal<=0?'green':'amber'}">${money(newBal)}</b>`]});}catch{return;}
  await api('/api/card/pay',{index:idx,amount:amt,note:el('pc-note').value});DATA=await api('/api/data');closeModal('modal-pay-card');renderPersonal();renderOverview();updateSidebarSnapshot();toast('Payment recorded');
}
async function addCard(){const name=el('c-name').value.trim();if(!name){toast('Enter name','error');return;}await api('/api/card/add',{name,type:el('c-type').value,balance:fval(el('c-bal').value),limit:fval(el('c-limit').value),notes:el('c-notes').value});DATA=await api('/api/data');closeModal('modal-add-card');renderPersonal();toast('Card added');}
async function delCard(idx){
  const c=activeProfile().cards?.[idx];
  try{await showConfirm({title:'Delete Card',icon:'🗑',lines:[`Remove: <b>${c?.name||'—'}</b>  ·  Balance: <b class="red">${money(c?.balance)}</b>`]});}catch{return;}
  await api('/api/card/delete',{index:idx});DATA=await api('/api/data');renderPersonal();
}
async function updateSavingsBal(pot){const k=pot.replace(/\s/g,'-');const amt=fval(el(`sav-bal-${k}`)?.value||0);await api('/api/savings/update',{pot,amount:amt});DATA=await api('/api/data');renderSavings();renderOverview();updateSidebarSnapshot();}
async function updateSavingsTgt(pot){const k=pot.replace(/\s/g,'-');const tgt=fval(el(`sav-tgt-${k}`)?.value||0);const slot=currentSlot();slot.savings_targets=slot.savings_targets||{};slot.savings_targets[pot]=tgt;await api('/api/save',DATA);DATA=await api('/api/data');renderSavings();}
async function addSavingsContrib(){
  const pot=el('sav-contrib-pot').value;const amt=fval(el('sav-contrib-amt').value);const src=el('sav-contrib-src').value;if(amt<=0){toast('Enter amount','error');return;}
  const slot=currentSlot();const curBal=fval(slot.savings?.[pot]||0);
  try{await showConfirm({title:'Savings Contribution',icon:'🏦',lines:[`Add <b class="green">${money(amt)}</b> to "<b>${pot}</b>"`,`Pot balance: <b>${money(curBal)}</b> → <b class="green">${money(curBal+amt)}</b>`,src==='wage'?'<span class="muted">Deducted from personal available budget</span>':'<span class="muted">Transferred from another savings pot</span>']});}catch{return;}
  await api('/api/savings/contribute',{dest:pot,amount:amt,source:src});el('sav-contrib-amt').value='';DATA=await api('/api/data');renderSavings();renderPersonal();renderOverview();updateSidebarSnapshot();toast('Contribution added');
}
async function addInvPot(){const name=el('inv-pot-name').value.trim();if(!name){toast('Enter name','error');return;}await api('/api/investment/pot/add',{name});el('inv-pot-name').value='';DATA=await api('/api/data');renderInvestments();toast('Pot added');}
async function addInvContrib(){
  const type=el('inv-pot-select').value;const amt=fval(el('inv-amount').value);const src=el('inv-source').value;const pot=el('inv-sav-pot').value;
  if(!type||amt<=0){toast('Select pot and amount','error');return;}
  try{await showConfirm({title:'Investment Contribution',icon:'📈',lines:[`Invest <b class="green">${money(amt)}</b> into "<b>${type}</b>"`,src==='savings'&&pot?`Source: savings pot "<b>${pot}</b>"`:'Source: personal available budget']});}catch{return;}
  await api('/api/investment/contribute',{type,amount:amt,source:src,source_pot:src==='savings'?pot:''});el('inv-amount').value='';DATA=await api('/api/data');renderInvestments();renderPersonal();renderSavings();renderOverview();updateSidebarSnapshot();toast('Contribution added');
}

// ── Transfers ─────────────────────────────────────────────────────────
function openModal_j2p(){
  const p=currentPeriod();const prof=activeProfile();const alloc=p?.joint_allocated||{};const spent={};(p?.joint_transactions||[]).forEach(t=>{spent[t.category]=(spent[t.category]||0)+fval(t.amount);});
  el('j2p-cat').innerHTML=META.joint_categories.map(cat=>{const rem=fval(alloc[cat])-fval(spent[cat]);return`<option value="${cat}">${cat} (${money(rem)} left)</option>`;}).join('');
  el('j2p-dest').innerHTML=`<option value="Personal income">Personal income</option>`+META.savings_types.map(s=>`<option value="${s}">${s}</option>`).join('');
  el('j2p-card').innerHTML=`<option value="None">None</option>`+(prof.cards?.filter(c=>c.status!=='Closed')||[]).map(c=>`<option value="${c.name}">${c.name}</option>`).join('');
  updateJ2pPreview();
}
function updateJ2pPreview(){
  const amt=fval(el('j2p-amount')?.value||0);const dest=el('j2p-dest')?.value||'';const card=el('j2p-card')?.value||'None';
  const slot=currentSlot();const prof=activeProfile();const lines=[];
  if(dest==='Personal income')lines.push('→ Added to personal income');
  else if(dest){const cur=fval(slot.savings?.[dest]||0);lines.push(`→ Savings "${dest}": ${money(cur)} → ${money(cur+amt)}`);}
  if(card&&card!=='None'){const co=prof.cards?.find(c=>c.name===card);if(co){const cur=fval(co.balance);lines.push(`→ Card "${card}": ${money(cur)} → ${money(cur+amt)}`);}}
  el('j2p-preview').textContent=lines.join('  |  ')||'Select destination to preview';
}
async function doJ2P(){
  const amt=fval(el('j2p-amount').value);if(amt<=0){toast('Enter amount','error');return;}
  const cat=el('j2p-cat').value;const dest=el('j2p-dest').value;const card=el('j2p-card').value;
  const p=currentPeriod();const slot=currentSlot();const prof=activeProfile();
  const alloc=fval(p?.joint_allocated?.[cat]||0);const spent=(p?.joint_transactions||[]).reduce((s,t)=>t.category===cat?s+fval(t.amount):s,0);const rem=alloc-spent;
  const lines=[`Transfer <b class="green">${money(amt)}</b> from Joint <b>${cat}</b>`,`Category remaining: <b>${money(rem)}</b> → <b class="${rem-amt<0?'red':'blue'}">${money(rem-amt)}</b>`,dest==='Personal income'?'→ Added to personal income':'→ Added to savings pot "<b>'+dest+'</b>"'];
  if(card&&card!=='None'){const co=prof.cards?.find(c=>c.name===card);lines.push(`→ Card "<b>${card}</b>": <b class="red">${money(fval(co?.balance))}</b> → <b class="red">${money(fval(co?.balance)+amt)}</b>`);}
  try{await showConfirm({title:'Joint → Personal Transfer',icon:'🔄',lines});}catch{return;}
  await api('/api/transfer/joint_to_personal',{amount:amt,category:cat,dest,card,note:el('j2p-note').value});
  el('j2p-amount').value='';el('j2p-note').value='';
  DATA=await api('/api/data');closeModal('modal-j2p');renderJoint();renderPersonal();renderSavings();renderOverview();updateSidebarSnapshot();toast('Transfer complete');
}
function openModal_topup(){
  const alloc=currentPeriod()?.joint_allocated||{};const spent={};(currentPeriod()?.joint_transactions||[]).forEach(t=>{spent[t.category]=(spent[t.category]||0)+fval(t.amount);});
  el('tu-cat-rows').innerHTML=META.joint_categories.map(cat=>`<div class="form-row mb-8"><label style="flex:2;font-size:12px;color:var(--text2);align-self:center">${cat} (${money(fval(alloc[cat])-fval(spent[cat]))} now)</label><input type="number" id="tu-cat-${cat.replace(/\s/g,'-')}" step="0.01" placeholder="0" style="flex:1;padding:5px 8px;font-size:12px;user-select:text" oninput="updateTopupTotal()"></div>`).join('');
  updateTopupTotal();
}
function updateTopupTotal(){
  const me=fval(el('tu-me')?.value||0);const partner=fval(el('tu-partner')?.value||0);const total=me+partner;
  const alloc=META.joint_categories.reduce((s,cat)=>s+fval(el(`tu-cat-${cat.replace(/\s/g,'-')}`)?.value||0),0);
  const leftover=total-alloc;
  el('tu-total-lbl').textContent=`Total: ${money(total)}`;
  el('tu-alloc-lbl').innerHTML=`Allocated: <b>${money(alloc)}</b>  Leftover: <b style="color:${leftover<0?'var(--red)':'var(--accent)'}">${money(leftover)}</b>`;
}
async function doTopup(){
  const me=fval(el('tu-me').value);const partner=fval(el('tu-partner').value);if(me+partner<=0){toast('Enter an amount','error');return;}
  const allocations={};META.joint_categories.forEach(cat=>{const v=fval(el(`tu-cat-${cat.replace(/\s/g,'-')}`)?.value||0);if(v>0)allocations[cat]=v;});
  const total=me+partner;const allocTotal=Object.values(allocations).reduce((s,v)=>s+fval(v),0);const leftover=total-allocTotal;
  const lines=[me>0?`From me: <b class="red">${money(me)}</b>`:'',partner>0?`From partner: <b class="red">${money(partner)}</b>`:'',`Total: <b>${money(total)}</b>`,...Object.entries(allocations).map(([c,v])=>`→ <b>${c}</b> +<b>${money(v)}</b>`),leftover>0?`→ Leftover: <b>${money(leftover)}</b>`:''].filter(Boolean);
  try{await showConfirm({title:'Joint Top-Up',subtitle:'Deleting this from the transfer log later will reverse these allocations.',icon:'➕',lines});}catch{return;}
  await api('/api/transfer/topup',{me_amount:me,partner_amount:partner,allocations});
  el('tu-me').value='';el('tu-partner').value='';
  DATA=await api('/api/data');closeModal('modal-topup');renderJoint();renderPersonal();renderOverview();updateSidebarSnapshot();toast('Top-up applied');
}
async function delTransfer(idx){
  const t=currentPeriod()?.transfers?.[idx];
  const isTopup=t?.is_topup;
  try{await showConfirm({title:'Remove Transfer Log Entry',icon:'🗑',lines:[`Entry: <b>${t?.type||'—'}</b>  ·  <b>${money(t?.amount)}</b>`,isTopup?'<span class="amber">⚠ This will also reverse the joint allocation and personal expense contribution.</span>':'<span class="muted">Note: underlying funds are not automatically reversed.</span>']});}catch{return;}
  await api('/api/transfer/delete',{index:idx});DATA=await api('/api/data');renderJoint();renderPersonal();renderOverview();updateSidebarSnapshot();toast('Transfer removed');
}

// ── Quick Add ─────────────────────────────────────────────────────────
function qaSetType(type){
  QA_TYPE=type;['joint','personal','card','payoff'].forEach(t=>{el(`qa-btn-${t}`)?.classList.remove('btn-primary');el(`qa-btn-${t}`)?.classList.add('btn-secondary');});
  el(`qa-btn-${type}`)?.classList.remove('btn-secondary');el(`qa-btn-${type}`)?.classList.add('btn-primary');
  el('qa-submit-btn').style.display=type==='payoff'?'none':'';renderQAForm();
}
function renderQAForm(){
  const prof=activeProfile();
  if(QA_TYPE==='joint'){el('qa-form').innerHTML=`<div class="form-row"><div class="form-group" style="flex:1"><label class="form-label">Category</label><select id="qa-cat">${META.joint_categories.map(c=>`<option>${c}</option>`).join('')}</select></div><div class="form-group" style="flex:1"><label class="form-label">Reason</label><input type="text" id="qa-reason" placeholder="What for?" style="user-select:text"></div></div><div class="form-group"><label class="form-label">Amount</label><input type="number" id="qa-amount" step="0.01" placeholder="0.00"></div>`;}
  else if(QA_TYPE==='personal'){el('qa-form').innerHTML=`<div class="form-row"><div class="form-group" style="flex:1"><label class="form-label">Category</label><select id="qa-cat">${META.personal_categories.map(c=>`<option>${c}</option>`).join('')}</select></div><div class="form-group" style="flex:1"><label class="form-label">Reason</label><input type="text" id="qa-reason" placeholder="What for?" style="user-select:text"></div></div><div class="form-group"><label class="form-label">Amount</label><input type="number" id="qa-amount" step="0.01" placeholder="0.00"></div>`;}
  else if(QA_TYPE==='card'){const cards=prof.cards?.filter(c=>c.status!=='Closed')||[];el('qa-form').innerHTML=`<div class="form-group"><label class="form-label">Card</label><select id="qa-card">${cards.map(c=>`<option value="${c.name}">${c.name} — ${money(c.balance)}</option>`).join('')}</select></div><div class="form-group"><label class="form-label">What for</label><input type="text" id="qa-reason" placeholder="e.g. Amazon" style="user-select:text"></div><div class="form-group"><label class="form-label">Amount</label><input type="number" id="qa-amount" step="0.01" placeholder="0.00"></div><div class="form-group"><label class="form-label">Category</label><select id="qa-cat">${META.personal_categories.map(c=>`<option>${c}</option>`).join('')}</select></div>`;}
  else if(QA_TYPE==='payoff'){const debts=prof.debts?.filter(d=>d.status==='Active')||[];const cards=prof.cards?.filter(c=>c.status!=='Closed')||[];el('qa-form').innerHTML=`<div class="card-title">Debts</div>${debts.map((d,i)=>`<div class="flex-between" style="padding:7px 0;border-bottom:1px solid var(--border)"><span style="font-size:13px">${d.name}</span><div class="flex"><span class="red bold">${money(d.current_balance)}</span><button class="btn btn-primary btn-sm" onclick="quickPayDebt(${i})">Pay →</button></div></div>`).join('')}<div class="card-title mt-12">Cards</div>${cards.map((c,i)=>`<div class="flex-between" style="padding:7px 0;border-bottom:1px solid var(--border)"><span style="font-size:13px">${c.name}</span><div class="flex"><span class="red bold">${money(c.balance)}</span><button class="btn btn-primary btn-sm" onclick="quickPayCard(${i})">Pay →</button></div></div>`).join('')}`;}
}
async function submitQuickAdd(){
  const amt=fval(el('qa-amount')?.value||0);if(amt<=0){toast('Enter amount','error');return;}
  if(QA_TYPE==='joint')await api('/api/joint/transaction/add',{category:el('qa-cat').value,reason:el('qa-reason').value,amount:amt,date:todayDisp()});
  else if(QA_TYPE==='personal')await api('/api/personal/expense/add',{category:el('qa-cat').value,reason:el('qa-reason').value,amount:amt,date:today()});
  else if(QA_TYPE==='card'){const card=el('qa-card').value;await api('/api/personal/expense/add',{category:el('qa-cat').value,reason:el('qa-reason').value,amount:amt,paid_with_card:card,is_purchase:true,date:today()});}
  DATA=await api('/api/data');closeModal('modal-quick-add');refreshTab(ACTIVE_TAB);toast('Added');
}
function quickPayDebt(idx){closeModal('modal-quick-add');openPayDebt(idx);}
function quickPayCard(idx){closeModal('modal-quick-add');openPayCard(idx);}

// ── Settings actions ──────────────────────────────────────────────────
async function saveSettings(){
  const prof=activeProfile();
  await api('/api/config/update',{user_name:el('set-name').value.trim()});
  // Save theme to profile
  if(prof){await api('/api/save',DATA);}
  DATA=await api('/api/data');updateSidebar();toast('Saved');
}
async function addBill(){const name=el('bill-name').value.trim();const day=parseInt(el('bill-day').value)||1;const amt=fval(el('bill-amount').value);if(!name){toast('Enter name','error');return;}await api('/api/bill/add',{name,due_day:day,amount:amt});el('bill-name').value='';el('bill-day').value='';el('bill-amount').value='';DATA=await api('/api/data');renderSettings();renderOverview();updateSidebarSnapshot();toast('Bill added');}
async function delBill(idx){await api('/api/bill/delete',{index:idx});DATA=await api('/api/data');renderSettings();renderOverview();updateSidebarSnapshot();}
async function saveNotes(){const notes=el('period-notes').value;await api('/api/notes/update',{notes});if(DATA.current_period&&DATA.periods[DATA.current_period])DATA.periods[DATA.current_period].notes=notes;}
async function exportData(){
  const d=await api('/api/data');const blob=new Blob([JSON.stringify(d,null,2)],{type:'application/json'});const fname=`clariqo_backup_${today()}.json`;
  if(window.showSaveFilePicker){try{const fh=await window.showSaveFilePicker({suggestedName:fname,types:[{description:'Clariqo Backup',accept:{'application/json':['.json']}}]});const w=await fh.createWritable();await w.write(blob);await w.close();toast('Backup saved');return;}catch(e){if(e.name==='AbortError')return;}}
  const a=document.createElement('a');a.href=URL.createObjectURL(blob);a.download=fname;a.click();toast('Backup exported');
}
async function importData(event){const file=event.target.files[0];if(!file)return;if(!confirm('Replace all data?'))return;const d=JSON.parse(await file.text());await api('/api/save',d);DATA=await api('/api/data');META=await api('/api/meta');CURR=META.currency?.symbol||'£';refreshAll();toast('Imported');event.target.value='';}
async function toggleLock(name,locked){await api('/api/period/lock',{name,locked});DATA=await api('/api/data');renderSettings();updateSidebar();toast(locked?'Period locked':'Period unlocked');}
async function deletePeriod(name){if(!confirm(`Delete "${name}"?`))return;await api('/api/period/delete',{name});DATA=await api('/api/data');renderSettings();updateSidebar();refreshTab(ACTIVE_TAB);toast('Period deleted');}

// ── Profiles ──────────────────────────────────────────────────────────
function renderProfileList(){
  window._newProfileTheme=window._newProfileTheme||'emerald';
  renderThemePicker('new-profile-theme-picker',window._newProfileTheme,'selectNewProfileTheme');
  const profiles=DATA.profiles||[];const active=DATA.active_profile||0;
  el('profile-list').innerHTML=profiles.length?profiles.map((p,i)=>`
    <div style="display:flex;align-items:center;justify-content:space-between;padding:9px 0;border-bottom:1px solid var(--border)">
      <div><div style="font-size:13px;font-weight:${i===active?'700':'400'}">${p.name||'Profile '+(i+1)} ${i===active?'<span class="badge badge-green">Active</span>':''}</div><div class="small muted">Wage: ${money(fval(p.global_wage))}/mo</div></div>
      <div style="display:flex;gap:5px;align-items:center">
        ${i!==active?`<button class="btn btn-secondary btn-sm" onclick="quickSwitchProfile(${i})">Switch</button>`:''}
        <button class="btn btn-secondary btn-sm" onclick="openEditProfile(${i})">✏</button>
        ${profiles.length>1?`<button class="btn btn-danger btn-sm" onclick="deleteProfile(${i})">🗑</button>`:''}
      </div>
    </div>`).join(''):'<div class="muted small">No profiles.</div>';
}
async function addProfile(){
  if(!requirePremium('Additional profiles require Clariqo Premium.'))return;
  const name=el('new-profile-name').value.trim();const wage=fval(el('new-profile-wage').value);
  if(!name){toast('Enter a name','error');return;}if((DATA.profiles||[]).length>=2){toast('Max 2 profiles','error');return;}
  DATA.profiles=DATA.profiles||[];DATA.profiles.push({name,accent:'#00c48c',global_wage:wage,global_core_expenses:[],investment_pots:[],personal_budget_categories:[],debts:[],cards:[]});
  const p=currentPeriod();if(p){const newIdx=DATA.profiles.length-1;p.profiles=p.profiles||{};p.profiles[String(newIdx)]={wage,core_expenses:[],additional_expenses:[],additional_incomes:[],savings:{},savings_targets:{},savings_withdrawals:[],savings_deposits:[],debt_snapshots:{},card_snapshots:{},investment_contributions:[],personal_rollover:0,budget_categories:[]};}
  await api('/api/save',DATA);DATA=await api('/api/data');
  el('new-profile-name').value='';el('new-profile-wage').value='';
  renderProfileList();renderProfileTabs();toast(`Profile "${name}" added`);
}

// ── MORTGAGE ─────────────────────────────────────────────────────────
function renderMortgage(){
  // Ensure mortgage exists in DATA
  if(!DATA.mortgage)DATA.mortgage={};
  const m=DATA.mortgage;
  const hasMortgage=fval(m.current_balance)>0||fval(m.original_loan)>0;
  const setupForm=el('mortgage-setup-form');
  const display=el('mortgage-display');
  const actionBtns=el('mortgage-action-btns');
  const breakdownCard=el('mortgage-breakdown-card');
  const historyCard=el('mortgage-history-card');
  if(!hasMortgage){
    if(display)display.innerHTML='<p class="muted small mb-12">No mortgage set up yet. Enter your details below to start tracking.</p>';
    if(setupForm)setupForm.style.display='';
    if(actionBtns)actionBtns.style.display='none';
    if(breakdownCard)breakdownCard.style.display='none';
    if(historyCard)historyCard.style.display='none';
    // Populate core expense link dropdown
    const coreDropdown=el('m-linked-core');
    if(coreDropdown){
      const core=activeProfile().global_core_expenses||[];
      const opts=core.map(e=>`<option value="${e.name}">${e.name} (${money(fval(e.amount))})</option>`).join('');
      coreDropdown.innerHTML='<option value="">None — not tracked as core expense</option>'+opts;
    }
    return;
  }
  // Has mortgage — show summary
  if(setupForm)setupForm.style.display='none';
  if(actionBtns)actionBtns.style.display='';
  const propVal=fval(m.property_value);
  const origLoan=fval(m.original_loan);
  const balance=fval(m.current_balance);
  const rate=fval(m.interest_rate);
  const payment=fval(m.monthly_payment);
  const term=parseInt(m.remaining_term)||0;
  const ltv=propVal>0?Math.min(balance/propVal*100,100):0;
  const ltvCol=ltv<60?'green':ltv<80?'amber':'red';
  const equity=propVal-balance;
  // Monthly interest/capital split
  const monthlyInterest=balance*(rate/100/12);
  const monthlyCapital=payment-monthlyInterest;
  if(display)display.innerHTML=`
    <div class="flex-between mb-8"><span class="muted small">Property value</span><span class="bold">${money(propVal)}</span></div>
    <div class="flex-between mb-8"><span class="muted small">Outstanding balance</span><span class="bold red">${money(balance)}</span></div>
    <div class="flex-between mb-8"><span class="muted small">Equity</span><span class="bold green">${money(equity)}</span></div>
    <div class="flex-between mb-8"><span class="muted small">Interest rate</span><span class="bold amber">${rate.toFixed(2)}%</span></div>
    <div class="flex-between mb-8"><span class="muted small">Remaining term</span><span class="bold">${term} months (${(term/12).toFixed(1)} yrs)</span></div>
    <div class="sep"></div>
    <div class="flex-between mb-4"><span class="muted small">LTV</span><span class="bold ${ltvCol}">${ltv.toFixed(1)}%</span></div>
    <div class="progress-bar"><div class="progress-fill ${ltvCol}" style="width:${ltv}%"></div></div>`;
  // Breakdown card
  const linked=m.linked_core_expense||'';
  const coreExpenses=activeProfile().global_core_expenses||[];
  const linkedExp=linked?coreExpenses.find(e=>e.name===linked):null;
  if(breakdownCard){
    breakdownCard.style.display='';
    el('mortgage-breakdown').innerHTML=`
      <div class="flex-between" style="padding:8px 0;border-bottom:1px solid var(--border)"><span class="muted small">Monthly payment</span><span class="bold">${money(payment)}</span></div>
      <div class="flex-between" style="padding:8px 0;border-bottom:1px solid var(--border)"><span class="muted small">Interest portion</span><span class="bold red">${money(Math.max(0,monthlyInterest))}</span></div>
      <div class="flex-between" style="padding:8px 0;border-bottom:1px solid var(--border)"><span class="muted small">Capital repaid</span><span class="bold green">${money(Math.max(0,monthlyCapital))}</span></div>
      ${linkedExp?`<div class="flex-between" style="padding:8px 0;border-bottom:1px solid var(--border)"><span class="muted small">Tracked via core expense</span><span class="bold">${linkedExp.name} · ${money(linkedExp.amount)}</span></div>`:''}
      <div style="margin-top:12px;font-size:12px;color:var(--text2)">Based on current balance and rate. Actual figures may vary slightly.</div>
      ${linkedExp?'':'<div style="font-size:12px;color:var(--amber);margin-top:4px">💡 Tip: Link this to a core expense in Edit Details to track the payment automatically.</div>'}`;
  }
  // Rate history
  const history=m.rate_history||[];
  if(historyCard){
    historyCard.style.display=history.length?'':'none';
    if(history.length){
      el('mortgage-rate-history').innerHTML=[...history].reverse().map(h=>`
        <div class="flex-between" style="padding:6px 0;border-bottom:1px solid var(--border)">
          <div><div style="font-size:13px;font-weight:500">${h.interest_rate}% — ${money(h.monthly_payment)}/mo</div><div class="small muted">${h.date}${h.note?' · '+h.note:''} · ${h.remaining_term} months remaining</div></div>
        </div>`).join('');
    }
  }
}

async function saveMortgage(){
  const propVal=fval(el('m-prop-val').value);
  const origLoan=fval(el('m-orig-loan').value);
  const balance=fval(el('m-balance').value);
  const rate=fval(el('m-rate').value);
  const payment=fval(el('m-payment').value);
  const term=parseInt(el('m-term').value)||0;
  const linked=el('m-linked-core')?.value||'';
  if(balance<=0||rate<=0){toast('Enter at least a balance and interest rate','error');return;}
  await api('/api/mortgage/save',{property_value:propVal,original_loan:origLoan,current_balance:balance,interest_rate:rate,monthly_payment:payment,remaining_term:term,linked_core_expense:linked});
  DATA=await api('/api/data');renderMortgage();toast('Mortgage saved');
}

function openEditMortgage(){
  const m=DATA.mortgage||{};
  el('me-prop-val').value=fval(m.property_value)||'';
  el('me-balance').value=fval(m.current_balance)||'';
  el('me-payment').value=fval(m.monthly_payment)||'';
  el('me-term').value=parseInt(m.remaining_term)||'';
  // Populate core expense link in edit modal
  const sel=el('me-linked-core');
  if(sel){
    const core=activeProfile().global_core_expenses||[];
    sel.innerHTML='<option value="">None</option>'+core.map(e=>`<option value="${e.name}" ${m.linked_core_expense===e.name?'selected':''}>${e.name} (${money(fval(e.amount))})</option>`).join('');
  }
  openModal('modal-edit-mortgage');
}

async function updateMortgage(){
  await api('/api/mortgage/update',{
    property_value:fval(el('me-prop-val').value),
    current_balance:fval(el('me-balance').value),
    monthly_payment:fval(el('me-payment').value),
    remaining_term:parseInt(el('me-term').value)||0,
    linked_core_expense:el('me-linked-core')?.value||'',
  });
  DATA=await api('/api/data');closeModal('modal-edit-mortgage');renderMortgage();toast('Mortgage updated');
}

async function doRemortgage(){
  const rate=fval(el('rm-rate').value);
  const term=parseInt(el('rm-term').value)||0;
  const payment=fval(el('rm-payment').value);
  if(!rate){toast('Enter new interest rate','error');return;}
  try{await showConfirm({title:'Apply Remortgage',subtitle:'This will log the current rate to your history.',icon:'🔄',lines:[
    `New rate: <b class="amber">${rate}%</b>`,
    payment?`New monthly payment: <b>${money(payment)}</b>`:'',
    term?`New term: <b>${term} months</b>`:'',
    `<span class="muted">Previous rate will be saved to your rate history.</span>`,
  ].filter(Boolean)});}catch{return;}
  await api('/api/mortgage/remortgage',{interest_rate:rate,remaining_term:term,monthly_payment:payment,note:el('rm-note').value});
  el('rm-rate').value='';el('rm-term').value='';el('rm-payment').value='';el('rm-note').value='';
  DATA=await api('/api/data');closeModal('modal-remortgage');renderMortgage();toast('Remortgage applied');
}

// ── Profile edit/delete ───────────────────────────────────────────────
function openEditProfile(idx){
  const p=DATA.profiles?.[idx];if(!p)return;
  el('ep-name').value=p.name||'';
  el('ep-index').value=idx;
  openModal('modal-edit-profile');
}
async function saveProfileEdit(){
  const idx=parseInt(el('ep-index').value);
  const name=el('ep-name').value.trim();
  if(!name){toast('Enter a name','error');return;}
  await api('/api/profile/edit',{index:idx,name});
  DATA=await api('/api/data');
  closeModal('modal-edit-profile');
  renderProfileList();renderProfileTabs();
  toast('Profile updated');
}
async function deleteProfile(idx){
  const p=DATA.profiles?.[idx];
  try{
    await showConfirm({
      title:'Delete Profile',
      subtitle:'This is permanent and cannot be undone.',
      icon:'⚠️',
      lines:[
        `Delete profile: <b>${p?.name||'Profile '+(idx+1)}</b>`,
        `<span class="red">All personal expenses, income, debts, cards, savings and budget categories for this profile will be permanently removed from every period.</span>`,
        `<span class="muted">Joint budget and transactions are unaffected.</span>`,
      ]
    });
  }catch{return;}
  await api('/api/profile/delete',{index:idx});
  DATA=await api('/api/data');
  ACTIVATED=DATA.activated||false;
  renderProfileList();renderProfileTabs();refreshAll();
  toast('Profile deleted');
}

// ── Modals ────────────────────────────────────────────────────────────
function openModal(id){
  el(id)?.classList.remove('hidden');
  if(id==='modal-quick-add')qaSetType('joint');
  if(id==='modal-j2p')openModal_j2p();
  if(id==='modal-topup')openModal_topup();
  if(id==='modal-new-period'){
    npSetStep(1);
    // Show wage row when opened manually (not from first-run wizard)
    if(!_IS_FIRST_RUN){
      const wr=el('np-wage-row');if(wr)wr.style.display='';
      const wf=el('np-wage');if(wf)wf.value='';
    }
  }
  if(id==='modal-profile')renderProfileList();
  if(id==='modal-edit-mortgage')openEditMortgage();
}
function closeModal(id){el(id)?.classList.add('hidden');}
function closeModalOuter(e,id){if(e.target.id===id)closeModal(id);}

// ── Boot ──────────────────────────────────────────────────────────────
document.addEventListener('DOMContentLoaded',()=>{
  init();
  // Auto-format activation code input as user types
  const inp=el('act-code');
  if(inp){
    inp.addEventListener('input',function(){
      let v=this.value.replace(/[^A-Z0-9]/gi,'').toUpperCase();
      if(v.length>4)v=v.slice(0,4)+'-'+v.slice(4);
      if(v.length>9)v=v.slice(0,9)+'-'+v.slice(9);
      this.value=v.slice(0,14);
    });
    inp.addEventListener('keydown',e=>{if(e.key==='Enter')doActivate();});
  }
});
