export default class PersistenceImpl extends Common.Object{constructor(workspace,breakpointManager){super();this._workspace=workspace;this._breakpointManager=breakpointManager;this._filePathPrefixesToBindingCount=new Map();this._subscribedBindingEventListeners=new Platform.Multimap();const linkDecorator=new Persistence.PersistenceUtils.LinkDecorator(this);Components.Linkifier.setLinkDecorator(linkDecorator);this._mapping=new Persistence.Automapping(this._workspace,this._onStatusAdded.bind(this),this._onStatusRemoved.bind(this));}
addNetworkInterceptor(interceptor){this._mapping.addNetworkInterceptor(interceptor);}
refreshAutomapping(){this._mapping.scheduleRemap();}
addBinding(binding){this._innerAddBinding(binding);}
addBindingForTest(binding){this._innerAddBinding(binding);}
removeBinding(binding){this._innerRemoveBinding(binding);}
removeBindingForTest(binding){this._innerRemoveBinding(binding);}
_innerAddBinding(binding){binding.network[_binding]=binding;binding.fileSystem[_binding]=binding;binding.fileSystem.forceLoadOnCheckContent();binding.network.addEventListener(Workspace.UISourceCode.Events.WorkingCopyCommitted,this._onWorkingCopyCommitted,this);binding.fileSystem.addEventListener(Workspace.UISourceCode.Events.WorkingCopyCommitted,this._onWorkingCopyCommitted,this);binding.network.addEventListener(Workspace.UISourceCode.Events.WorkingCopyChanged,this._onWorkingCopyChanged,this);binding.fileSystem.addEventListener(Workspace.UISourceCode.Events.WorkingCopyChanged,this._onWorkingCopyChanged,this);this._addFilePathBindingPrefixes(binding.fileSystem.url());this._moveBreakpoints(binding.fileSystem,binding.network);console.assert(!binding.fileSystem.isDirty()||!binding.network.isDirty());if(binding.fileSystem.isDirty()){this._syncWorkingCopy(binding.fileSystem);}else if(binding.network.isDirty()){this._syncWorkingCopy(binding.network);}else if(binding.network.hasCommits()&&binding.network.content()!==binding.fileSystem.content()){binding.network.setWorkingCopy(binding.network.content());this._syncWorkingCopy(binding.network);}
this._notifyBindingEvent(binding.network);this._notifyBindingEvent(binding.fileSystem);this.dispatchEventToListeners(Events.BindingCreated,binding);}
_innerRemoveBinding(binding){if(binding.network[_binding]!==binding){return;}
console.assert(binding.network[_binding]===binding.fileSystem[_binding],'ERROR: inconsistent binding for networkURL '+binding.network.url());binding.network[_binding]=null;binding.fileSystem[_binding]=null;binding.network.removeEventListener(Workspace.UISourceCode.Events.WorkingCopyCommitted,this._onWorkingCopyCommitted,this);binding.fileSystem.removeEventListener(Workspace.UISourceCode.Events.WorkingCopyCommitted,this._onWorkingCopyCommitted,this);binding.network.removeEventListener(Workspace.UISourceCode.Events.WorkingCopyChanged,this._onWorkingCopyChanged,this);binding.fileSystem.removeEventListener(Workspace.UISourceCode.Events.WorkingCopyChanged,this._onWorkingCopyChanged,this);this._removeFilePathBindingPrefixes(binding.fileSystem.url());this._breakpointManager.copyBreakpoints(binding.network.url(),binding.fileSystem);this._notifyBindingEvent(binding.network);this._notifyBindingEvent(binding.fileSystem);this.dispatchEventToListeners(Events.BindingRemoved,binding);}
_onStatusAdded(status){const binding=new PersistenceBinding(status.network,status.fileSystem);status[_binding]=binding;this._innerAddBinding(binding);}
_onStatusRemoved(status){const binding=(status[_binding]);this._innerRemoveBinding(binding);}
_onWorkingCopyChanged(event){const uiSourceCode=(event.data);this._syncWorkingCopy(uiSourceCode);}
_syncWorkingCopy(uiSourceCode){const binding=uiSourceCode[_binding];if(!binding||binding[_muteWorkingCopy]){return;}
const other=binding.network===uiSourceCode?binding.fileSystem:binding.network;if(!uiSourceCode.isDirty()){binding[_muteWorkingCopy]=true;other.resetWorkingCopy();binding[_muteWorkingCopy]=false;this._contentSyncedForTest();return;}
const target=Bindings.NetworkProject.targetForUISourceCode(binding.network);if(target.type()===SDK.Target.Type.Node){const newContent=uiSourceCode.workingCopy();other.requestContent().then(()=>{const nodeJSContent=PersistenceImpl.rewrapNodeJSContent(other,other.workingCopy(),newContent);setWorkingCopy.call(this,()=>nodeJSContent);});return;}
setWorkingCopy.call(this,()=>uiSourceCode.workingCopy());function setWorkingCopy(workingCopyGetter){binding[_muteWorkingCopy]=true;other.setWorkingCopyGetter(workingCopyGetter);binding[_muteWorkingCopy]=false;this._contentSyncedForTest();}}
_onWorkingCopyCommitted(event){const uiSourceCode=(event.data.uiSourceCode);const newContent=(event.data.content);this.syncContent(uiSourceCode,newContent,event.data.encoded);}
syncContent(uiSourceCode,newContent,encoded){const binding=uiSourceCode[_binding];if(!binding||binding[_muteCommit]){return;}
const other=binding.network===uiSourceCode?binding.fileSystem:binding.network;const target=Bindings.NetworkProject.targetForUISourceCode(binding.network);if(target.type()===SDK.Target.Type.Node){other.requestContent().then(currentContent=>{const nodeJSContent=PersistenceImpl.rewrapNodeJSContent(other,currentContent.content,newContent);setContent.call(this,nodeJSContent);});return;}
setContent.call(this,newContent);function setContent(newContent){binding[_muteCommit]=true;other.setContent(newContent,encoded);binding[_muteCommit]=false;this._contentSyncedForTest();}}
static rewrapNodeJSContent(uiSourceCode,currentContent,newContent){if(uiSourceCode.project().type()===Workspace.projectTypes.FileSystem){if(newContent.startsWith(_NodePrefix)&&newContent.endsWith(_NodeSuffix)){newContent=newContent.substring(_NodePrefix.length,newContent.length-_NodeSuffix.length);}
if(currentContent.startsWith(_NodeShebang)){newContent=_NodeShebang+newContent;}}else{if(newContent.startsWith(_NodeShebang)){newContent=newContent.substring(_NodeShebang.length);}
if(currentContent.startsWith(_NodePrefix)&&currentContent.endsWith(_NodeSuffix)){newContent=_NodePrefix+newContent+_NodeSuffix;}}
return newContent;}
_contentSyncedForTest(){}
_moveBreakpoints(from,to){const breakpoints=this._breakpointManager.breakpointLocationsForUISourceCode(from).map(breakpointLocation=>breakpointLocation.breakpoint);for(const breakpoint of breakpoints){breakpoint.remove(false);this._breakpointManager.setBreakpoint(to,breakpoint.lineNumber(),breakpoint.columnNumber(),breakpoint.condition(),breakpoint.enabled());}}
hasUnsavedCommittedChanges(uiSourceCode){if(this._workspace.hasResourceContentTrackingExtensions()){return false;}
if(uiSourceCode.project().canSetFileContent()){return false;}
if(uiSourceCode[_binding]){return false;}
return!!uiSourceCode.hasCommits();}
binding(uiSourceCode){return uiSourceCode[_binding]||null;}
subscribeForBindingEvent(uiSourceCode,listener){this._subscribedBindingEventListeners.set(uiSourceCode,listener);}
unsubscribeFromBindingEvent(uiSourceCode,listener){this._subscribedBindingEventListeners.delete(uiSourceCode,listener);}
_notifyBindingEvent(uiSourceCode){if(!this._subscribedBindingEventListeners.has(uiSourceCode)){return;}
const listeners=Array.from(this._subscribedBindingEventListeners.get(uiSourceCode));for(const listener of listeners){listener.call(null);}}
fileSystem(uiSourceCode){const binding=this.binding(uiSourceCode);return binding?binding.fileSystem:null;}
network(uiSourceCode){const binding=this.binding(uiSourceCode);return binding?binding.network:null;}
_addFilePathBindingPrefixes(filePath){let relative='';for(const token of filePath.split('/')){relative+=token+'/';const count=this._filePathPrefixesToBindingCount.get(relative)||0;this._filePathPrefixesToBindingCount.set(relative,count+1);}}
_removeFilePathBindingPrefixes(filePath){let relative='';for(const token of filePath.split('/')){relative+=token+'/';const count=this._filePathPrefixesToBindingCount.get(relative);if(count===1){this._filePathPrefixesToBindingCount.delete(relative);}else{this._filePathPrefixesToBindingCount.set(relative,count-1);}}}
filePathHasBindings(filePath){if(!filePath.endsWith('/')){filePath+='/';}
return this._filePathPrefixesToBindingCount.has(filePath);}}
const _binding=Symbol('Persistence.Binding');const _muteCommit=Symbol('Persistence.MuteCommit');const _muteWorkingCopy=Symbol('Persistence.MuteWorkingCopy');const _NodePrefix='(function (exports, require, module, __filename, __dirname) { ';const _NodeSuffix='\n});';const _NodeShebang='#!/usr/bin/env node';export const Events={BindingCreated:Symbol('BindingCreated'),BindingRemoved:Symbol('BindingRemoved')};export class PathEncoder{constructor(){this._encoder=new Common.CharacterIdMap();}
encode(path){return path.split('/').map(token=>this._encoder.toChar(token)).join('');}
decode(path){return path.split('').map(token=>this._encoder.fromChar(token)).join('/');}}
export class PersistenceBinding{constructor(network,fileSystem){this.network=network;this.fileSystem=fileSystem;}}
self.Persistence=self.Persistence||{};Persistence=Persistence||{};Persistence.Persistence=PersistenceImpl;Persistence.Persistence.Events=Events;Persistence.Persistence._NodeShebang=_NodeShebang;Persistence.Persistence._NodePrefix=_NodePrefix;Persistence.Persistence._NodeSuffix=_NodeSuffix;Persistence.PathEncoder=PathEncoder;Persistence.PersistenceBinding=PersistenceBinding;Persistence.persistence;