(function(){'use strict';const base=window.projectsBaseUrl||'',csrf=window.projectsCsrfToken||'',modalEl=document.getElementById('projectHistoryModal');if(!modalEl)return;const modal=bootstrap.Modal.getOrCreateInstance(modalEl);let card=null,state=null;
async function read(res){const t=await res.text();try{return JSON.parse(t)}catch(_){throw new Error(t||`Request failed (${res.status}).`)}}async function post(path,payload){const res=await fetch(base+path,{method:'POST',headers:{'Content-Type':'application/json',Accept:'application/json'},body:JSON.stringify({...payload,_token:csrf})}),data=await read(res);if(!res.ok||!data.success)throw new Error(data.message||'Request failed.');return data}function busy(btn,on,label){if(on){btn.dataset.html=btn.innerHTML;btn.disabled=true;btn.innerHTML=`<span class="spinner-border spinner-border-sm me-1"></span>${label}`}else{btn.disabled=false;btn.innerHTML=btn.dataset.html||btn.innerHTML}}
document.getElementById('projectsCollection')?.addEventListener('click',e=>{const a=e.target.closest('[data-project-action="history"]');if(!a)return;e.preventDefault();card=a.closest('[data-project-card]');if(!card)return;document.getElementById('historyProjectName').textContent=card.dataset.name;modal.show();load()});document.querySelectorAll('[data-history-tab]').forEach(btn=>btn.addEventListener('click',()=>{document.querySelectorAll('[data-history-tab]').forEach(x=>x.classList.toggle('active',x===btn));document.querySelectorAll('[data-history-pane]').forEach(x=>x.classList.toggle('active',x.dataset.historyPane===btn.dataset.historyTab))}));
async function load(){if(!card)return;try{const res=await fetch(`${base}/project/${encodeURIComponent(card.dataset.projectSlug)}/history`,{headers:{Accept:'application/json'}}),data=await read(res);if(!res.ok||!data.success)throw new Error(data.message);state=data;renderVersions();renderGit()}catch(e){Swal.fire('Could not load history',e.message,'error')}}document.getElementById('historyRefresh')?.addEventListener('click',load);
function renderVersions(){const list=document.getElementById('checkpointList');list.replaceChildren();document.getElementById('checkpointEmpty').classList.toggle('show',!state.versions.length);state.versions.forEach(v=>{const row=document.createElement('article');row.className='checkpoint-item';row.dataset.versionId=v.id;const date=new Date(v.created_at).toLocaleString();row.innerHTML=`<span class="checkpoint-dot"><i class="bi ${v.kind==='pre_restore'?'bi-shield-check':'bi-camera'}"></i></span><div class="checkpoint-copy"><strong></strong><span></span><span class="checkpoint-kind"></span></div><div class="checkpoint-actions"><button class="btn btn-sm btn-light border" data-version-action="compare" title="Compare with current"><i class="bi bi-file-diff"></i></button><button class="btn btn-sm btn-outline-primary" data-version-action="restore" title="Restore"><i class="bi bi-arrow-counterclockwise"></i></button><button class="btn btn-sm btn-outline-danger" data-version-action="delete" title="Delete"><i class="bi bi-trash3"></i></button></div>`;row.querySelector('strong').textContent=v.label;row.querySelector('.checkpoint-copy>span').textContent=`${date} · ${format(v.package_size)}${v.notes?' · '+v.notes:''}`;row.querySelector('.checkpoint-kind').textContent=v.kind.replace('_',' ');list.append(row)})}function format(n){return n>1048576?(n/1048576).toFixed(1)+' MB':Math.max(1,Math.round(n/1024))+' KB'}
document.getElementById('checkpointForm')?.addEventListener('submit',async e=>{e.preventDefault();if(!card||!e.currentTarget.reportValidity())return;const btn=document.getElementById('checkpointSubmit');busy(btn,true,'Capturing…');try{const data=await post(`/project/${encodeURIComponent(card.dataset.projectSlug)}/history/checkpoint`,Object.fromEntries(new FormData(e.currentTarget).entries()));e.currentTarget.reset();await load();Swal.fire('Checkpoint created',data.message,'success')}catch(err){Swal.fire('Could not create checkpoint',err.message,'error')}finally{busy(btn,false)}});
document.getElementById('checkpointList')?.addEventListener('click',async e=>{const a=e.target.closest('[data-version-action]');if(!a||!card)return;const row=a.closest('[data-version-id]'),id=row.dataset.versionId,type=a.dataset.versionAction;if(type==='compare'){busy(a,true,'');try{const res=await fetch(`${base}/project/${encodeURIComponent(card.dataset.projectSlug)}/history/${id}/compare`,{headers:{Accept:'application/json'}}),data=await read(res);if(!res.ok||!data.success)throw new Error(data.message);renderCompare(data)}catch(err){Swal.fire('Comparison failed',err.message,'error')}finally{busy(a,false)}return}if(type==='restore'){const result=await Swal.fire({title:'Restore this checkpoint?',html:'<p class="small text-muted">Current editable settings and Custom Files will be replaced. A safety checkpoint is created first. Database settings and data stay untouched.</p>',input:'text',inputLabel:`Type “${card.dataset.name}” to confirm`,showCancelButton:true,confirmButtonText:'Restore checkpoint',preConfirm:v=>{if(v!==card.dataset.name){Swal.showValidationMessage('Project name does not match.');return false}return v}});if(!result.isConfirmed)return;try{const data=await post(`/project/${encodeURIComponent(card.dataset.projectSlug)}/history/${id}/restore`,{confirm:result.value});await load();Swal.fire('Checkpoint restored',data.message,'success')}catch(err){Swal.fire('Restore failed',err.message,'error')}return}if(type==='delete'){const result=await Swal.fire({title:'Delete checkpoint?',text:'This recovery point will no longer be available.',icon:'warning',showCancelButton:true,confirmButtonColor:'#dc2626',confirmButtonText:'Delete'});if(!result.isConfirmed)return;try{const data=await post(`/project/${encodeURIComponent(card.dataset.projectSlug)}/history/${id}/delete`,{});await load();Swal.fire('Deleted',data.message,'success')}catch(err){Swal.fire('Delete failed',err.message,'error')}}});
function renderCompare(data){const p=document.getElementById('checkpointComparison'),c=data.comparison;p.classList.remove('d-none');p.replaceChildren();const h=document.createElement('div');h.className='history-section-head';h.innerHTML='<div><span class="history-eyebrow">Current vs checkpoint</span><h6></h6></div><button class="btn btn-sm btn-light border" type="button">Close</button>';h.querySelector('h6').textContent=data.checkpoint.label;h.querySelector('button').onclick=()=>p.classList.add('d-none');const s=document.createElement('div');s.className='comparison-summary';['added','changed','removed'].forEach(k=>{const x=document.createElement('span');x.className=k;x.textContent=`${c.counts[k]} ${k}`;s.append(x)});const files=document.createElement('div');files.className='comparison-files';['added','changed','removed'].forEach(k=>{const col=document.createElement('div'),title=document.createElement('strong'),ul=document.createElement('ul');title.textContent=k[0].toUpperCase()+k.slice(1);(c[k]||[]).slice(0,12).forEach(f=>{const li=document.createElement('li');li.textContent=f.label;ul.append(li)});if(!(c[k]||[]).length){const li=document.createElement('li');li.textContent='None';ul.append(li)}col.append(title,ul);files.append(col)});p.append(h,s,files)}
function renderGit(){const g=state.git||{},s=state.settings||{},initialized=g.initialized;document.getElementById('gitStatusTitle').textContent=!g.available?'Git unavailable':initialized?(g.dirty_count?`${g.dirty_count} uncommitted change${g.dirty_count===1?'':'s'}`:'Working tree clean'):'Repository not initialized';document.getElementById('gitStatusCopy').textContent=initialized?(g.remote||'Local repository only'):'Initialize a credential-safe repository for editable project files.';document.getElementById('gitBranch').textContent=g.branch||s.default_branch||'main';const form=document.getElementById('gitSettingsForm');['author_name','author_email','remote_url','default_branch'].forEach(k=>form.elements[k].value=s[k]||'');form.elements.auto_checkpoint.checked=!!Number(s.auto_checkpoint);document.getElementById('gitInitialize').classList.toggle('d-none',initialized);document.getElementById('gitCommitForm').classList.toggle('d-none',!initialized);const last=document.getElementById('gitLastCommit');last.textContent=g.last_commit?`Last commit ${g.last_commit.short} · ${g.last_commit.subject} · ${new Date(g.last_commit.date).toLocaleString()}`:'No commits yet.';const changes=document.getElementById('gitChanges');changes.replaceChildren();(g.changes||[]).slice(0,20).forEach(change=>{const code=document.createElement('code');code.textContent=change;changes.append(code)});if(g.sensitive_tracked){const warn=document.createElement('div');warn.className='alert alert-danger py-2 mb-0';warn.textContent='Warning: config/database.php is already tracked by this repository. Remove it from Git history before publishing.';changes.prepend(warn)}}
function settingsPayload(){const f=document.getElementById('gitSettingsForm');const p=Object.fromEntries(new FormData(f).entries());p.auto_checkpoint=f.elements.auto_checkpoint.checked;return p}document.getElementById('gitSettingsForm')?.addEventListener('submit',async e=>{e.preventDefault();try{const data=await post(`/project/${encodeURIComponent(card.dataset.projectSlug)}/version-control`,settingsPayload());state.settings=data.settings;Swal.fire('Settings saved',data.message,'success')}catch(err){Swal.fire('Could not save settings',err.message,'error')}});document.getElementById('gitInitialize')?.addEventListener('click',async e=>{busy(e.currentTarget,true,'Initializing…');try{const data=await post(`/project/${encodeURIComponent(card.dataset.projectSlug)}/version-control/init`,settingsPayload());await load();Swal.fire('Git ready',data.message,'success')}catch(err){Swal.fire('Git initialization failed',err.message,'error')}finally{busy(e.currentTarget,false)}});document.getElementById('gitCommitForm')?.addEventListener('submit',async e=>{e.preventDefault();if(!e.currentTarget.reportValidity())return;const btn=document.getElementById('gitCommit');busy(btn,true,'Committing…');try{const data=await post(`/project/${encodeURIComponent(card.dataset.projectSlug)}/version-control/commit`,Object.fromEntries(new FormData(e.currentTarget).entries()));e.currentTarget.reset();await load();Swal.fire('Git',data.message,'success')}catch(err){Swal.fire('Commit failed',err.message,'error')}finally{busy(btn,false)}});
})();
