const __vite__mapDeps=(i,m=__vite__mapDeps,d=(m.f||(m.f=["assets/pdf-export-vzOHm8wb.js","assets/jspdf-Dzj2Osmy.js","assets/openlayers-CvK8xBSr.js","assets/openlayers-BtPuoxOl.css"])))=>i.map(i=>d[i]); import{_ as ft,h as M,F as I,j as k,k as ae,m as Bt,b as R,V as A,L as ie,D as Ee,P as Ke,Q as rt,n as se,U as ve,M as Kt,W as cn,X,Y as dn,S as un,G as pn,Z as fn,o as Xe,O as ge,$ as Ue,a0 as st,a1 as xe,A as hn,T as ee,a2 as _e,a3 as Vt,a4 as ce,a5 as Xt,e as gn,u as _t,s as mn,a6 as Lo,a7 as yn}from"./openlayers-CvK8xBSr.js";import{M as Gt}from"./bootstrap-D1-uvFxm.js";import{o as bn,a as wn,b as vn,c as _n,d as Ct,e as Yt,f as qe,g as En,h as me,i as xn,j as Sn}from"./ol-ext-BR0zF6aa.js";(function(){const e=document.createElement("link").relList;if(e&&e.supports&&e.supports("modulepreload"))return;for(const o of document.querySelectorAll('link[rel="modulepreload"]'))n(o);new MutationObserver(o=>{for(const a of o)if(a.type==="childList")for(const s of a.addedNodes)s.tagName==="LINK"&&s.rel==="modulepreload"&&n(s)}).observe(document,{childList:!0,subtree:!0});function t(o){const a={};return o.integrity&&(a.integrity=o.integrity),o.referrerPolicy&&(a.referrerPolicy=o.referrerPolicy),o.crossOrigin==="use-credentials"?a.credentials="include":o.crossOrigin==="anonymous"?a.credentials="omit":a.credentials="same-origin",a}function n(o){if(o.ep)return;o.ep=!0;const a=t(o);fetch(o.href,a)}})();const Zt="function",Ae="64e10b34-2bf7-4616-9668-f99de5aa046e",Ln="get",Tn="has",kn="set",{isArray:tt}=Array;let{SharedArrayBuffer:at,window:Pn}=globalThis,{notify:To,wait:ko,waitAsync:it}=Atomics,Po=null;it||(it=r=>({value:new Promise(e=>{let t=new Worker("data:application/javascript,onmessage%3D(%7Bdata%3Ab%7D)%3D%3E(Atomics.wait(b%2C0)%2CpostMessage(0))");t.onmessage=e,t.postMessage(r)})}));try{new at(4)}catch{at=ArrayBuffer;const e=new WeakMap;if(Pn){const t=new Map,{prototype:{postMessage:n}}=Worker,o=a=>{const s=a.data?.[Ae];if(!tt(s)){a.stopImmediatePropagation();const{id:i,sb:l}=s;t.get(i)(l)}};Po=function(a,...s){const i=a?.[Ae];if(tt(i)){const[l,c]=i;e.set(c,l),this.addEventListener("message",o)}return n.call(this,a,...s)},it=a=>({value:new Promise(s=>{t.set(e.get(a),s)}).then(s=>{t.delete(e.get(a)),e.delete(a);for(let i=0;i({[Ae]:{id:n,sb:o}});To=n=>{postMessage(t(e.get(n),n))},addEventListener("message",n=>{const o=n.data?.[Ae];if(tt(o)){const[a,s]=o;e.set(s,a)}})}}/*! (c) Andrea Giammarchi - ISC */const{Int32Array:At,Map:Jt,Uint16Array:Dt}=globalThis,{BYTES_PER_ELEMENT:Qt}=At,{BYTES_PER_ELEMENT:Mn}=Dt,In=(r,e,t)=>{for(;ko(r,0,0,e)==="timed-out";)t()},Ft=new WeakSet,Et=new WeakMap,Cn={value:{then:r=>r()}};let An=0;const qt=(r,{parse:e=JSON.parse,stringify:t=JSON.stringify,transform:n,interrupt:o}=JSON)=>{if(!Et.has(r)){const a=Po||r.postMessage,s=(p,...h)=>a.call(r,{[Ae]:h},{transfer:p}),i=typeof o===Zt?o:o?.handler,l=o?.delay||42,c=new TextDecoder("utf-16"),d=(p,h)=>p?it(h,0):(i?In(h,l,i):ko(h,0),Cn);let u=!1;Et.set(r,new Proxy(new Jt,{[Tn]:(p,h)=>typeof h=="string"&&!h.startsWith("_"),[Ln]:(p,h)=>h==="then"?null:((...f)=>{const b=An++;let g=new At(new at(Qt*2)),m=[];Ft.has(f.at(-1)||m)&&Ft.delete(m=f.pop()),s(m,b,g,h,n?f.map(n):f);const y=r!==globalThis;let w=0;return u&&y&&(w=setTimeout(console.warn,1e3,`πŸ’€πŸ”’ - Possible deadlock if proxy.${h}(...args) is awaited`)),d(y,g).value.then(()=>{clearTimeout(w);const S=g[1];if(!S)return;const x=Mn*S;return g=new At(new at(x+x%Qt)),s([],b,g),d(y,g).value.then(()=>e(c.decode(new Dt(g.buffer).slice(0,S))))})}),[kn](p,h,f){const b=typeof f;if(b!==Zt)throw new Error(`Unable to assign ${h} as ${b}`);if(!p.size){const g=new Jt;r.addEventListener("message",async m=>{const y=m.data?.[Ae];if(tt(y)){m.stopImmediatePropagation();const[w,S,...x]=y;let v;if(x.length){const[L,P]=x;if(p.has(L)){u=!0;try{const T=await p.get(L)(...P);if(T!==void 0){const z=t(n?n(T):T);g.set(w,z),S[1]=z.length}}catch(T){v=T}finally{u=!1}}else v=new Error(`Unsupported action: ${L}`);S[0]=1}else{const L=g.get(w);g.delete(w);for(let P=new Dt(S.buffer),T=0;T(Ft.add(r),r);function eo(){let r,e;return{lock:async()=>{for(;r;)await r;r=new Promise(o=>{e=o})},unlock:async()=>{const o=e;r=void 0,e=void 0,o?.()}}}async function Mo(r,e){let t;if(r instanceof Blob?t=r.stream():t=r,t instanceof ReadableStream&&e){const o=t.getReader();switch(e){case"callback":return async()=>(await o.read()).value;case"buffer":const a=[];let s=!1;for(;!s;){const d=await o.read();d.value&&a.push(d.value),s=d.done}const i=a.reduce((d,u)=>d+u.length,0),l=new Uint8Array(i);let c=0;return a.forEach(d=>{l.set(d,c),c+=d.length}),l.buffer}}else return t}class lt{constructor(e){Object.defineProperty(this,"sqlite3InitModule",{enumerable:!0,configurable:!0,writable:!0,value:e}),Object.defineProperty(this,"sqlite3",{enumerable:!0,configurable:!0,writable:!0,value:void 0}),Object.defineProperty(this,"db",{enumerable:!0,configurable:!0,writable:!0,value:void 0}),Object.defineProperty(this,"config",{enumerable:!0,configurable:!0,writable:!0,value:void 0}),Object.defineProperty(this,"pointers",{enumerable:!0,configurable:!0,writable:!0,value:[]}),Object.defineProperty(this,"writeCallbacks",{enumerable:!0,configurable:!0,writable:!0,value:new Set}),Object.defineProperty(this,"storageType",{enumerable:!0,configurable:!0,writable:!0,value:"memory"})}async init(e){const{databasePath:t}=e,n=this.getFlags(e);if(!this.sqlite3InitModule){const{default:o}=await ft(async()=>{const{default:a}=await import("./index-DTMgZTfd.js");return{default:a}},[]);this.sqlite3InitModule=o}this.sqlite3||(this.sqlite3=await this.sqlite3InitModule()),this.db&&await this.destroy(),this.db=new this.sqlite3.oo1.DB(t,n),this.config=e,this.initWriteHook()}onWrite(e){return this.writeCallbacks.add(e),()=>{this.writeCallbacks.delete(e)}}async exec(e){if(!this.db)throw new Error("Driver not initialized");return this.execOnDb(this.db,e)}async execBatch(e){if(!this.db)throw new Error("Driver not initialized");const t=[];return this.db.transaction(n=>{const o=new Map;try{for(let a of e){let s=o.get(a.sql);if(!s){const c=n.prepare(a.sql);o.set(a.sql,c),s=c}a.params?.length&&s.bind(a.params);let i=[],l=[];for(;s.step();)i=s.getColumnNames([]),l.push(s.get([]));t.push({columns:i,rows:l}),s.reset()}}finally{o.forEach(a=>{a.finalize()})}}),t}async isDatabasePersisted(){return!1}async getDatabaseSizeBytes(){const t=(await this.exec({sql:`SELECT page_count * page_size AS size FROM pragma_page_count(), pragma_page_size()`,method:"get"}))?.rows?.[0];if(typeof t!="number")throw new Error("Failed to query database size");return t}async createFunction(e){if(!this.db)throw new Error("Driver not initialized");switch(e.type){case"callback":case"scalar":this.db.createFunction({name:e.name,xFunc:(t,...n)=>e.func(...n),arity:-1});break;case"aggregate":this.db.createFunction({name:e.name,xStep:(t,...n)=>e.func.step(...n),xFinal:(t,...n)=>e.func.final(...n),arity:-1});break}}async import(e){if(!this.sqlite3||!this.db||!this.config)throw new Error("Driver not initialized");const t=await Mo(e,"buffer"),n=this.sqlite3.wasm.allocFromTypedArray(t);this.pointers.push(n);const o=this.sqlite3.capi.sqlite3_deserialize(this.db,"main",n,t.byteLength,t.byteLength,this.config.readOnly?this.sqlite3.capi.SQLITE_DESERIALIZE_READONLY:this.sqlite3.capi.SQLITE_DESERIALIZE_RESIZEABLE);this.db.checkRc(o)}async export(){if(!this.sqlite3||!this.db)throw new Error("Driver not initialized");return{name:"database.sqlite3",data:this.sqlite3.capi.sqlite3_js_db_export(this.db)}}async clear(){}async destroy(){this.closeDb(),this.pointers.forEach(e=>this.sqlite3?.wasm.dealloc(e)),this.pointers=[],this.writeCallbacks.clear()}getFlags(e){const{readOnly:t,verbose:n}=e;return[t===!0?"r":"cw",n===!0?"t":""].join("")}execOnDb(e,t){const n={rows:[],columns:[]},o=e.exec({sql:t.sql,bind:t.params,returnValue:"resultRows",rowMode:"array",columnNames:n.columns});switch(t.method){case"run":break;case"get":n.rows=o[0]??[];break;case"all":default:n.rows=o;break}return n}initWriteHook(){if(!this.config?.reactive)return;if(!this.sqlite3||!this.db)throw new Error("Driver not initialized");const e={[this.sqlite3.capi.SQLITE_INSERT]:"insert",[this.sqlite3.capi.SQLITE_UPDATE]:"update",[this.sqlite3.capi.SQLITE_DELETE]:"delete"};this.sqlite3.capi.sqlite3_update_hook(this.db,(t,n,o,a,s)=>{this.writeCallbacks.forEach(i=>{i({table:a,rowid:s,operation:e[n]})})},0)}closeDb(){this.db&&(this.db.close(),this.db=void 0)}}function Dn(r,e,t){let n,o,a,s,i,l,c=0,d=!1,u=!1,p=!0;if(typeof r!="function")throw new TypeError("Expected a function");e=Number(e)||0,typeof t=="object"&&t!==null&&(d=!!t.leading,u="maxWait"in t,a=u?Math.max(Number(t.maxWait)||0,e):0,p="trailing"in t?!!t.trailing:p);function h(v){const L=n,P=o;return n=o=void 0,c=v,s=r.apply(P,L),s}function f(v){return c=v,i=setTimeout(m,e),d?h(v):s}function b(v){const L=v-(l??0),P=v-c,T=e-L;return u?Math.min(T,a-P):T}function g(v){const L=v-(l??0),P=v-c;return l===void 0||L>=e||L<0||u&&P>=a}function m(){const v=Date.now();if(g(v))return y(v);i=setTimeout(m,b(v))}function y(v){return i=void 0,p&&n?h(v):(n=o=void 0,s)}function w(){i!==void 0&&clearTimeout(i),c=0,n=l=o=i=void 0}function S(){return i===void 0?s:y(Date.now())}function x(){const v=Date.now(),L=g(v);if(n=arguments,o=this,l=v,L){if(i===void 0)return f(l);if(u)return i=setTimeout(m,e),h(l)}return i===void 0&&(i=setTimeout(m,e)),s}return x.cancel=w,x.flush=S,x}function ot(){return crypto.randomUUID()}function Io(r,e){switch(r){case"session":case":sessionStorage:":let t=sessionStorage._sqlocal_session_key;return t||(t=ot(),sessionStorage._sqlocal_session_key=t),`session:${t}`;case"local":case":localStorage:":return"local";case":memory:":return`memory:${e}`;default:return`path:${r}`}}class Ye{constructor(e){Object.defineProperty(this,"driver",{enumerable:!0,configurable:!0,writable:!0,value:void 0}),Object.defineProperty(this,"config",{enumerable:!0,configurable:!0,writable:!0,value:{}}),Object.defineProperty(this,"userFunctions",{enumerable:!0,configurable:!0,writable:!0,value:new Map}),Object.defineProperty(this,"initMutex",{enumerable:!0,configurable:!0,writable:!0,value:eo()}),Object.defineProperty(this,"transactionMutex",{enumerable:!0,configurable:!0,writable:!0,value:eo()}),Object.defineProperty(this,"transactionKey",{enumerable:!0,configurable:!0,writable:!0,value:null}),Object.defineProperty(this,"proxy",{enumerable:!0,configurable:!0,writable:!0,value:void 0}),Object.defineProperty(this,"dirtyTables",{enumerable:!0,configurable:!0,writable:!0,value:new Set}),Object.defineProperty(this,"effectsChannel",{enumerable:!0,configurable:!0,writable:!0,value:void 0}),Object.defineProperty(this,"reinitChannel",{enumerable:!0,configurable:!0,writable:!0,value:void 0}),Object.defineProperty(this,"onmessage",{enumerable:!0,configurable:!0,writable:!0,value:void 0}),Object.defineProperty(this,"init",{enumerable:!0,configurable:!0,writable:!0,value:async o=>{if(!(!this.config.databasePath||!this.config.clientKey)){await this.initMutex.lock();try{try{await this.driver.init(this.config)}catch{console.warn(`Persistence failed, so ${this.config.databasePath} will not be saved. For origin private file system persistence, make sure your web server is configured to use the correct HTTP response headers (See https://sqlocal.dev/guide/setup#cross-origin-isolation).`),this.config.databasePath=":memory:",this.driver=new lt,await this.driver.init(this.config)}const a=Io(this.config.databasePath,this.config.clientKey);this.reinitChannel=new BroadcastChannel(`_sqlocal_reinit_(${a})`),this.reinitChannel.onmessage=s=>{const i=s.data;if(this.config.clientKey!==i.clientKey)switch(i.type){case"reinit":this.init(i.reason);break;case"close":this.driver.destroy();break}},this.config.reactive&&(this.effectsChannel=new BroadcastChannel(`_sqlocal_effects_(${a})`),this.driver.onWrite(async s=>{this.dirtyTables.add(s.table),await this.transactionMutex.lock(),this.emitEffectsDebounced(),await this.transactionMutex.unlock()})),await Promise.all(Array.from(this.userFunctions.values()).map(s=>this.initUserFunction(s))),await this.execInitStatements(),this.emitMessage({type:"event",event:"connect",reason:o})}catch(a){this.emitMessage({type:"error",error:a,queryKey:null}),await this.destroy()}finally{await this.initMutex.unlock()}}}}),Object.defineProperty(this,"postMessage",{enumerable:!0,configurable:!0,writable:!0,value:async(o,a)=>{const s=o instanceof MessageEvent?o.data:o;switch(await this.initMutex.lock(),s.type){case"config":this.editConfig(s);break;case"query":case"batch":case"transaction":this.exec(s);break;case"function":this.createUserFunction(s);break;case"getinfo":this.getDatabaseInfo(s);break;case"import":this.importDb(s);break;case"export":this.exportDb(s);break;case"delete":this.deleteDb(s);break;case"destroy":this.destroy(s);break}await this.initMutex.unlock()}}),Object.defineProperty(this,"emitMessage",{enumerable:!0,configurable:!0,writable:!0,value:(o,a=[])=>{this.onmessage&&this.onmessage(o,a)}}),Object.defineProperty(this,"emitEffects",{enumerable:!0,configurable:!0,writable:!0,value:()=>{!this.effectsChannel||this.dirtyTables.size===0||(this.effectsChannel.postMessage({type:"effects",tables:[...this.dirtyTables]}),this.dirtyTables.clear())}}),Object.defineProperty(this,"emitEffectsDebounced",{enumerable:!0,configurable:!0,writable:!0,value:Dn(()=>this.emitEffects(),32,{maxWait:180})}),Object.defineProperty(this,"editConfig",{enumerable:!0,configurable:!0,writable:!0,value:o=>{this.config=o.config,this.init("initial")}}),Object.defineProperty(this,"exec",{enumerable:!0,configurable:!0,writable:!0,value:async o=>{try{const a={type:"data",queryKey:o.queryKey,data:[]};switch(o.type){case"query":const s=this.transactionKey!==null&&this.transactionKey===o.transactionKey;try{s||await this.transactionMutex.lock();const i=await this.driver.exec(o);a.data.push(i)}finally{s||await this.transactionMutex.unlock()}break;case"batch":try{await this.transactionMutex.lock();const i=await this.driver.execBatch(o.statements);a.data.push(...i)}finally{await this.transactionMutex.unlock()}break;case"transaction":if(o.action==="begin"&&(await this.transactionMutex.lock(),this.transactionKey=o.transactionKey,await this.driver.exec({sql:"BEGIN"})),(o.action==="commit"||o.action==="rollback")&&this.transactionKey!==null&&this.transactionKey===o.transactionKey){const i=o.action==="commit"?"COMMIT":"ROLLBACK";await this.driver.exec({sql:i}),this.transactionKey=null,await this.transactionMutex.unlock()}break}this.emitMessage(a)}catch(a){this.emitMessage({type:"error",error:a,queryKey:o.queryKey})}}}),Object.defineProperty(this,"execInitStatements",{enumerable:!0,configurable:!0,writable:!0,value:async()=>{if(this.config.onInitStatements)for(let o of this.config.onInitStatements)await this.driver.exec(o)}}),Object.defineProperty(this,"getDatabaseInfo",{enumerable:!0,configurable:!0,writable:!0,value:async o=>{try{this.emitMessage({type:"info",queryKey:o.queryKey,info:{databasePath:this.config.databasePath,storageType:this.driver.storageType,databaseSizeBytes:await this.driver.getDatabaseSizeBytes(),persisted:await this.driver.isDatabasePersisted()}})}catch(a){this.emitMessage({type:"error",queryKey:o.queryKey,error:a})}}}),Object.defineProperty(this,"createUserFunction",{enumerable:!0,configurable:!0,writable:!0,value:async o=>{const{functionName:a,functionType:s,queryKey:i}=o;let l;if(this.userFunctions.has(a)){this.emitMessage({type:"error",error:new Error(`A user-defined function with the name "${a}" has already been created for this SQLocal instance.`),queryKey:i});return}switch(s){case"callback":l={type:s,name:a,func:(...c)=>{this.emitMessage({type:"callback",name:a,args:c})}};break;case"scalar":l={type:s,name:a,func:this.proxy[`_sqlocal_func_${a}`]};break;case"aggregate":l={type:s,name:a,func:{step:this.proxy[`_sqlocal_func_${a}_step`],final:this.proxy[`_sqlocal_func_${a}_final`]}};break}try{await this.initUserFunction(l),this.emitMessage({type:"success",queryKey:i})}catch(c){this.emitMessage({type:"error",error:c,queryKey:i})}}}),Object.defineProperty(this,"initUserFunction",{enumerable:!0,configurable:!0,writable:!0,value:async o=>{await this.driver.createFunction(o),this.userFunctions.set(o.name,o)}}),Object.defineProperty(this,"importDb",{enumerable:!0,configurable:!0,writable:!0,value:async o=>{const{queryKey:a,database:s}=o;let i=!1;try{await this.driver.import(s),this.driver.storageType==="memory"&&await this.execInitStatements()}catch(l){this.emitMessage({type:"error",error:l,queryKey:a}),i=!0}finally{this.driver.storageType!=="memory"&&await this.init("overwrite")}i||this.emitMessage({type:"success",queryKey:a})}}),Object.defineProperty(this,"exportDb",{enumerable:!0,configurable:!0,writable:!0,value:async o=>{const{queryKey:a}=o;try{const{name:s,data:i}=await this.driver.export();this.emitMessage({type:"buffer",queryKey:a,bufferName:s,buffer:i},[i])}catch(s){this.emitMessage({type:"error",error:s,queryKey:a})}}}),Object.defineProperty(this,"deleteDb",{enumerable:!0,configurable:!0,writable:!0,value:async o=>{const{queryKey:a}=o;let s=!1;try{await this.driver.clear()}catch(i){this.emitMessage({type:"error",error:i,queryKey:a}),s=!0}finally{await this.init("delete")}s||this.emitMessage({type:"success",queryKey:a})}}),Object.defineProperty(this,"destroy",{enumerable:!0,configurable:!0,writable:!0,value:async o=>{await this.driver.exec({sql:"PRAGMA optimize"}),await this.driver.destroy(),this.effectsChannel&&(this.emitEffectsDebounced.flush(),this.effectsChannel.close(),this.effectsChannel=void 0),this.reinitChannel&&(this.reinitChannel.close(),this.reinitChannel=void 0),o&&this.emitMessage({type:"success",queryKey:o.queryKey})}});const n=typeof WorkerGlobalScope<"u"&&globalThis instanceof WorkerGlobalScope?qt(globalThis):globalThis;this.proxy=n,this.driver=e}}function ct(r,...e){return{sql:r.join("?"),params:e}}function Fn(r){return!r.some(e=>!Array.isArray(e))}function xt(r,e){let t;return Fn(r)?t=r:t=[r],t.map(n=>{const o={};return e.forEach((a,s)=>{o[a]=n[s]}),o})}function On(r){return typeof r=="object"&&r!==null&&"getSQL"in r&&typeof r.getSQL=="function"}function Rn(r){return typeof r=="object"&&r!==null&&"sql"in r&&typeof r.sql=="string"&&"params"in r}function to(r){if(typeof r=="function"&&(r=r(ct)),On(r))try{if(!("toSQL"in r&&typeof r.toSQL=="function"))throw 1;const n=r.toSQL();if(!Rn(n))throw 2;const o="all"in r&&typeof r.all=="function"?r.all:void 0;return{...n,exec:o?()=>o():void 0}}catch{throw new Error("The passed statement could not be parsed.")}const e=r.sql;let t=[];return"params"in r?t=r.params:"parameters"in r&&(t=r.parameters),{sql:e,params:t}}function oo(r,e){let t;return typeof r=="string"?t={sql:r,params:e}:t=ct(r,...e),t}async function Ze(r,e,t,n){return!e&&"locks"in navigator?navigator.locks.request(`_sqlocal_mutation_(${t.databasePath})`,{mode:r},n):n()}class no extends lt{constructor(e,t){super(t),Object.defineProperty(this,"storageType",{enumerable:!0,configurable:!0,writable:!0,value:e})}async init(e){const t=this.getFlags(e);if(e.readOnly)throw new Error(`SQLite storage type "${this.storageType}" does not support read-only mode.`);if(!this.sqlite3InitModule){const{default:n}=await ft(async()=>{const{default:o}=await import("./index-DTMgZTfd.js");return{default:o}},[]);this.sqlite3InitModule=n}this.sqlite3||(this.sqlite3=await this.sqlite3InitModule()),this.db&&await this.destroy(),this.db=new this.sqlite3.oo1.JsStorageDb({filename:this.storageType,flags:t}),this.config=e,this.initWriteHook()}async isDatabasePersisted(){return navigator.storage?.persisted()}async getDatabaseSizeBytes(){if(!this.db)throw new Error("Driver not initialized");return this.db.storageSize()}async import(e){const t=new lt;await t.init({}),await t.import(e),await this.clear(),await t.exec({sql:`VACUUM INTO 'file:${this.storageType}?vfs=kvvfs'`}),await t.destroy()}async clear(){if(!this.db)throw new Error("Driver not initialized");this.db.clearStorage()}async destroy(){this.closeDb(),this.writeCallbacks.clear()}}var Co,Ao;class $n{constructor(e){Object.defineProperty(this,"config",{enumerable:!0,configurable:!0,writable:!0,value:void 0}),Object.defineProperty(this,"clientKey",{enumerable:!0,configurable:!0,writable:!0,value:void 0}),Object.defineProperty(this,"processor",{enumerable:!0,configurable:!0,writable:!0,value:void 0}),Object.defineProperty(this,"isDestroyed",{enumerable:!0,configurable:!0,writable:!0,value:!1}),Object.defineProperty(this,"bypassMutationLock",{enumerable:!0,configurable:!0,writable:!0,value:!1}),Object.defineProperty(this,"transactionQueryKeyQueue",{enumerable:!0,configurable:!0,writable:!0,value:[]}),Object.defineProperty(this,"userCallbacks",{enumerable:!0,configurable:!0,writable:!0,value:new Map}),Object.defineProperty(this,"queriesInProgress",{enumerable:!0,configurable:!0,writable:!0,value:new Map}),Object.defineProperty(this,"proxy",{enumerable:!0,configurable:!0,writable:!0,value:void 0}),Object.defineProperty(this,"reinitChannel",{enumerable:!0,configurable:!0,writable:!0,value:void 0}),Object.defineProperty(this,"effectsChannel",{enumerable:!0,configurable:!0,writable:!0,value:void 0}),Object.defineProperty(this,"processMessageEvent",{enumerable:!0,configurable:!0,writable:!0,value:c=>{const d=c instanceof MessageEvent?c.data:c,u=this.queriesInProgress;switch(d.type){case"success":case"data":case"buffer":case"info":case"error":if(d.queryKey&&u.has(d.queryKey)){const[h,f]=u.get(d.queryKey);d.type==="error"?f(d.error):h(d),u.delete(d.queryKey)}else if(d.type==="error")throw d.error;break;case"callback":const p=this.userCallbacks.get(d.name);p&&p(...d.args??[]);break;case"event":this.config.onConnect?.(d.reason);break}}}),Object.defineProperty(this,"createQuery",{enumerable:!0,configurable:!0,writable:!0,value:async c=>Ze("shared",this.bypassMutationLock||c.type==="import"||c.type==="delete",this.config,async()=>{if(this.isDestroyed===!0)throw new Error("This SQLocal client has been destroyed. You will need to initialize a new client in order to make further queries.");const d=ot();switch(c.type){case"import":this.processor.postMessage({...c,queryKey:d},[c.database]);break;default:this.processor.postMessage({...c,queryKey:d});break}return new Promise((u,p)=>{this.queriesInProgress.set(d,[u,p])})})}),Object.defineProperty(this,"broadcast",{enumerable:!0,configurable:!0,writable:!0,value:c=>{this.reinitChannel.postMessage(c)}}),Object.defineProperty(this,"exec",{enumerable:!0,configurable:!0,writable:!0,value:async(c,d,u="all",p)=>{const h=await this.createQuery({type:"query",transactionKey:p,sql:c,params:d,method:u}),f={rows:[],columns:[]};return h.type==="data"&&(f.rows=h.data[0]?.rows??[],f.columns=h.data[0]?.columns??[]),f}}),Object.defineProperty(this,"execBatch",{enumerable:!0,configurable:!0,writable:!0,value:async c=>{const d=await this.createQuery({type:"batch",statements:c}),u=new Array(c.length).fill({rows:[],columns:[]});return d.type==="data"&&d.data.forEach((p,h)=>{u[h]=p}),u}}),Object.defineProperty(this,"sql",{enumerable:!0,configurable:!0,writable:!0,value:async(c,...d)=>{const u=oo(c,d),{rows:p,columns:h}=await this.exec(u.sql,u.params,"all");return xt(p,h)}}),Object.defineProperty(this,"batch",{enumerable:!0,configurable:!0,writable:!0,value:async c=>{const d=c(ct);return(await this.execBatch(d)).map(({rows:p,columns:h})=>xt(p,h))}}),Object.defineProperty(this,"beginTransaction",{enumerable:!0,configurable:!0,writable:!0,value:async()=>{const c=ot();await this.createQuery({type:"transaction",transactionKey:c,action:"begin"});const d=async f=>{const b=to(f);if(b.exec)return this.transactionQueryKeyQueue.push(c),b.exec();const{rows:g,columns:m}=await this.exec(b.sql,b.params,"all",c);return xt(g,m)};return{query:d,sql:async(f,...b)=>{const g=oo(f,b);return await d(g)},commit:async()=>{await this.createQuery({type:"transaction",transactionKey:c,action:"commit"})},rollback:async()=>{await this.createQuery({type:"transaction",transactionKey:c,action:"rollback"})}}}}),Object.defineProperty(this,"transaction",{enumerable:!0,configurable:!0,writable:!0,value:async c=>Ze("exclusive",!1,this.config,async()=>{let d;this.bypassMutationLock=!0;try{d=await this.beginTransaction();const u=await c({sql:d.sql,query:d.query});return await d.commit(),u}catch(u){throw await d?.rollback(),u}finally{this.bypassMutationLock=!1}})}),Object.defineProperty(this,"reactiveQuery",{enumerable:!0,configurable:!0,writable:!0,value:c=>{let d=[],u=!1,p=!1,h=0;const f=to(c),b=new Set,g=new Set,m=new Set,y=async()=>{try{const S=++h;if(b.size===0){const v=await this.sql("SELECT name, wr FROM tables_used(?) WHERE type = 'table'",f.sql),L=new Set,P=new Set;if(v.forEach(T=>{typeof T.name=="string"&&(T.wr?P.add(T.name):L.add(T.name))}),L.size===0)throw new Error("The passed SQL does not read any tables.");if(Array.from(P).some(T=>L.has(T)))throw new Error("The passed SQL would mutate one or more of the tables that it reads. Doing this in a reactive query would create an infinite loop.");L.forEach(T=>b.add(T))}const x=f.exec?await f.exec():await this.sql(f.sql,...f.params);S===h&&(d=x,u=!0,g.forEach(v=>v(d)))}catch(S){m.forEach(x=>{x(S instanceof Error?S:new Error(String(S)))})}},w=S=>{S.data.tables.some(x=>b.has(x))&&y()};return{get value(){return d},subscribe:(S,x)=>{if(!this.effectsChannel)throw new Error('This SQLocal instance is not configured for reactive queries. Set the "reactive" option to enable them.');return x||(x=v=>{throw v}),g.add(S),m.add(x),p?u&&S(d):(this.effectsChannel.addEventListener("message",w),p=!0,y()),{unsubscribe:()=>{g.delete(S),m.delete(x),g.size===0&&(this.effectsChannel?.removeEventListener("message",w),p=!1)}}}}}}),Object.defineProperty(this,"createCallbackFunction",{enumerable:!0,configurable:!0,writable:!0,value:async(c,d)=>{await this.createQuery({type:"function",functionName:c,functionType:"callback"}),this.userCallbacks.set(c,d)}}),Object.defineProperty(this,"createScalarFunction",{enumerable:!0,configurable:!0,writable:!0,value:async(c,d)=>{const u=`_sqlocal_func_${c}`,p=()=>{this.proxy[u]=d};this.proxy===globalThis&&p(),await this.createQuery({type:"function",functionName:c,functionType:"scalar"}),this.proxy!==globalThis&&p()}}),Object.defineProperty(this,"createAggregateFunction",{enumerable:!0,configurable:!0,writable:!0,value:async(c,d)=>{const u=`_sqlocal_func_${c}`,p=()=>{this.proxy[`${u}_step`]=d.step,this.proxy[`${u}_final`]=d.final};this.proxy===globalThis&&p(),await this.createQuery({type:"function",functionName:c,functionType:"aggregate"}),this.proxy!==globalThis&&p()}}),Object.defineProperty(this,"getDatabaseInfo",{enumerable:!0,configurable:!0,writable:!0,value:async()=>{const c=await this.createQuery({type:"getinfo"});if(c.type==="info")return c.info;throw new Error("The database failed to return valid information.")}}),Object.defineProperty(this,"getDatabaseFile",{enumerable:!0,configurable:!0,writable:!0,value:async()=>{const c=await this.createQuery({type:"export"});if(c.type==="buffer")return new File([c.buffer],c.bufferName,{type:"application/x-sqlite3"});throw new Error("The database failed to export.")}}),Object.defineProperty(this,"overwriteDatabaseFile",{enumerable:!0,configurable:!0,writable:!0,value:async(c,d)=>{await Ze("exclusive",!1,this.config,async()=>{try{this.broadcast({type:"close",clientKey:this.clientKey});const u=await Mo(c,"buffer");await this.createQuery({type:"import",database:u}),typeof d=="function"&&(this.bypassMutationLock=!0,await d()),this.broadcast({type:"reinit",clientKey:this.clientKey,reason:"overwrite"})}finally{this.bypassMutationLock=!1}})}}),Object.defineProperty(this,"deleteDatabaseFile",{enumerable:!0,configurable:!0,writable:!0,value:async c=>{await Ze("exclusive",!1,this.config,async()=>{try{this.broadcast({type:"close",clientKey:this.clientKey}),await this.createQuery({type:"delete"}),typeof c=="function"&&(this.bypassMutationLock=!0,await c()),this.broadcast({type:"reinit",clientKey:this.clientKey,reason:"delete"})}finally{this.bypassMutationLock=!1}})}}),Object.defineProperty(this,"destroy",{enumerable:!0,configurable:!0,writable:!0,value:async()=>{await this.createQuery({type:"destroy"}),typeof globalThis.Worker<"u"&&this.processor instanceof Worker&&(this.processor.removeEventListener("message",this.processMessageEvent),this.processor.terminate()),this.queriesInProgress.clear(),this.userCallbacks.clear(),this.reinitChannel.close(),this.effectsChannel?.close(),this.isDestroyed=!0}}),Object.defineProperty(this,Co,{enumerable:!0,configurable:!0,writable:!0,value:()=>{this.destroy()}}),Object.defineProperty(this,Ao,{enumerable:!0,configurable:!0,writable:!0,value:async()=>{await this.destroy()}});const t=typeof e=="string"?{databasePath:e}:e,{onInit:n,onConnect:o,processor:a,...s}=t,{databasePath:i}=s;this.config=t,this.clientKey=ot();const l=Io(i,this.clientKey);if(this.reinitChannel=new BroadcastChannel(`_sqlocal_reinit_(${l})`),s.reactive&&(this.effectsChannel=new BroadcastChannel(`_sqlocal_effects_(${l})`)),typeof a<"u")this.processor=a;else if(i==="local"||i===":localStorage:"){const c=new no("local");this.processor=new Ye(c)}else if(i==="session"||i===":sessionStorage:"){const c=new no("session");this.processor=new Ye(c)}else if(typeof globalThis.Worker<"u"&&i!==":memory:")this.processor=new Worker(new URL("/assets/worker-CuIBOSaM.js",import.meta.url),{type:"module"});else{const c=new lt;this.processor=new Ye(c)}this.processor instanceof Ye?(this.processor.onmessage=c=>this.processMessageEvent(c),this.proxy=globalThis):(this.processor.addEventListener("message",this.processMessageEvent),this.proxy=qt(this.processor)),this.processor.postMessage({type:"config",config:{...s,clientKey:this.clientKey,onInitStatements:n?.(ct)??[]}})}}Co=Symbol.dispose,Ao=Symbol.asyncDispose;const zt="lupmis2.db",Nn="lupmis-db-sync",Do=new $n(zt),{sql:E}=Do;console.log("[Database] SQLocal instance created for:",zt);const Fo=new BroadcastChannel(Nn);let Oo=!1,Ro,$o;const ro=new Promise((r,e)=>{Ro=r,$o=e}),dt=new Set;function Bn(r){return dt.add(r),()=>dt.delete(r)}Fo.onmessage=r=>{const{type:e,payload:t}=r.data;if(e==="DB_CHANGE")for(const n of dt)try{n(t)}catch(o){console.error("[Database] Change listener error:",o)}};function Te(r,e,t=null){Fo.postMessage({type:"DB_CHANGE",payload:{table:r,action:e,id:t,timestamp:Date.now()}});for(const n of dt)try{n({table:r,action:e,id:t,timestamp:Date.now(),local:!0})}catch(o){console.error("[Database] Change listener error:",o)}}async function Gn(){try{console.log("[Database] Initializing schema...");const r=await E`SELECT sqlite_version() as version`;console.log("[Database] SQLite version:",r[0]?.version),console.log("[Database] Creating locations table..."),await E` CREATE TABLE IF NOT EXISTS locations ( id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL, longitude REAL NOT NULL, latitude REAL NOT NULL, description TEXT, category TEXT DEFAULT 'default', created_at TEXT DEFAULT CURRENT_TIMESTAMP, updated_at TEXT DEFAULT CURRENT_TIMESTAMP, synced INTEGER DEFAULT 0 ) `;const e=await E`SELECT name FROM sqlite_master WHERE type='table' AND name='locations'`;console.log("[Database] Locations table exists:",e.length>0),console.log("[Database] Creating sync_log table..."),await E` CREATE TABLE IF NOT EXISTS sync_log ( id INTEGER PRIMARY KEY AUTOINCREMENT, table_name TEXT NOT NULL, record_id INTEGER NOT NULL, action TEXT NOT NULL, timestamp TEXT DEFAULT CURRENT_TIMESTAMP, synced INTEGER DEFAULT 0 ) `,console.log("[Database] Creating remote_data table..."),await E` CREATE TABLE IF NOT EXISTS remote_data ( key TEXT PRIMARY KEY, data TEXT NOT NULL, fetched_at TEXT DEFAULT CURRENT_TIMESTAMP ) `,console.log("[Database] Creating collector_zones table..."),await E` CREATE TABLE IF NOT EXISTS collector_zones ( id INTEGER PRIMARY KEY, zone_name TEXT, geometry_wkt TEXT, properties TEXT, fetched_at TEXT DEFAULT CURRENT_TIMESTAMP ) `,console.log("[Database] Creating parcels table...");try{const n=await E`PRAGMA table_info(parcels)`;n.length>0&&!n.some(o=>o.name==="upn")&&(console.log("[Database] Migrating parcels table to lu_parcels structure (dropping old cache)..."),await E`DROP TABLE parcels`)}catch(n){console.warn("[Database] parcels migration check failed:",n)}await E` CREATE TABLE IF NOT EXISTS parcels ( id INTEGER PRIMARY KEY, upn TEXT, style INTEGER, landuse TEXT, zone_code TEXT, zone_name TEXT, sector TEXT, block TEXT, parcel_no TEXT, prop_no TEXT, st_name TEXT, prop_add TEXT, fac_name TEXT, min_height INTEGER, max_height INTEGER, eff_date TEXT, lp_name TEXT, locality TEXT, mmda TEXT, last_update TEXT, remarks TEXT, geometry_wkt TEXT, created_at TEXT, updated_at TEXT, districtid INTEGER, status TEXT DEFAULT 'verified', fetched_at TEXT DEFAULT CURRENT_TIMESTAMP ) `,console.log("[Database] Creating building_footprints table..."),await E` CREATE TABLE IF NOT EXISTS building_footprints ( id INTEGER PRIMARY KEY, geometry_wkt TEXT, properties TEXT, fetched_at TEXT DEFAULT CURRENT_TIMESTAMP ) `,console.log("[Database] Creating osm_roads table..."),await E` CREATE TABLE IF NOT EXISTS osm_roads ( osm_id INTEGER PRIMARY KEY, geometry_wkt TEXT, properties TEXT, fetched_at TEXT DEFAULT CURRENT_TIMESTAMP ) `,console.log("[Database] Creating gps_trails table..."),await E` CREATE TABLE IF NOT EXISTS gps_trails ( id INTEGER PRIMARY KEY AUTOINCREMENT, client_uuid TEXT UNIQUE, name TEXT, district_id TEXT, started_at TEXT NOT NULL, ended_at TEXT, status TEXT NOT NULL DEFAULT 'recording', point_count INTEGER NOT NULL DEFAULT 0, distance_m REAL NOT NULL DEFAULT 0, synced INTEGER NOT NULL DEFAULT 0, remote_id TEXT, created_at TEXT DEFAULT CURRENT_TIMESTAMP ) `,console.log("[Database] Creating gps_trail_points table..."),await E` CREATE TABLE IF NOT EXISTS gps_trail_points ( id INTEGER PRIMARY KEY AUTOINCREMENT, trail_id INTEGER NOT NULL, seq INTEGER NOT NULL, longitude REAL NOT NULL, latitude REAL NOT NULL, altitude REAL, accuracy REAL, altitude_accuracy REAL, heading REAL, speed REAL, satellites INTEGER, recorded_at TEXT NOT NULL ) `,await E`CREATE INDEX IF NOT EXISTS idx_locations_category ON locations(category)`,await E`CREATE INDEX IF NOT EXISTS idx_locations_synced ON locations(synced)`,await E`CREATE INDEX IF NOT EXISTS idx_gps_trails_synced ON gps_trails(synced, status)`,await E`CREATE INDEX IF NOT EXISTS idx_gps_trail_points_trail ON gps_trail_points(trail_id, seq)`;const t=await E`SELECT name FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%'`;console.log("[Database] All tables:",t.map(n=>n.name)),Oo=!0,Ro(!0),console.log("[Database] βœ“ Schema initialized")}catch(r){throw console.error("[Database] βœ— Schema init failed:",r),$o(r),r}}async function qn(r,e,t,n={}){const{description:o=null,category:a="default"}=n;console.log("[Database] Adding location:",r,e,t,a);try{const s=await E`SELECT name FROM sqlite_master WHERE type='table' AND name='locations'`;if(console.log("[Database] Table check before insert:",s),s.length===0)throw console.error("[Database] βœ— locations table does not exist!"),new Error("locations table does not exist");console.log("[Database] Executing INSERT..."),await E` INSERT INTO locations (name, longitude, latitude, description, category) VALUES (${r}, ${e}, ${t}, ${o}, ${a}) `,console.log("[Database] INSERT completed");const l=(await E`SELECT last_insert_rowid() as id`)[0]?.id;console.log("[Database] New ID:",l);const c=await E`SELECT * FROM locations WHERE id = ${l}`;if(console.log("[Database] Verify insert:",c),c.length===0)throw console.error("[Database] βœ— Insert verification failed - row not found!"),new Error("Insert verification failed");return await E` INSERT INTO sync_log (table_name, record_id, action) VALUES ('locations', ${l}, 'INSERT') `,Te("locations","INSERT",l),console.log("[Database] βœ“ Location added:",l),{id:l}}catch(s){throw console.error("[Database] βœ— Failed to add location:",s),s}}async function No(r={}){const{category:e=null,limit:t=1e3}=r;try{const n=await E`SELECT name FROM sqlite_master WHERE type='table' AND name='locations'`;if(console.log("[Database] getLocations - table exists:",n.length>0),n.length===0)return console.warn("[Database] locations table does not exist yet"),[];let o;return e?o=await E` SELECT * FROM locations WHERE category = ${e} ORDER BY created_at DESC LIMIT ${t} `:o=await E` SELECT * FROM locations ORDER BY created_at DESC LIMIT ${t} `,console.log("[Database] getLocations returned",o.length,"rows"),o}catch(n){return console.error("[Database] getLocations error:",n),[]}}async function zn(){try{return(await E`SELECT COUNT(*) as count FROM locations`)[0]?.count??0}catch(r){return console.error("[Database] getLocationCount error:",r),0}}async function Bo(r,e){try{const t=JSON.stringify(e);await E` INSERT OR REPLACE INTO remote_data (key, data, fetched_at) VALUES (${r}, ${t}, CURRENT_TIMESTAMP) `,console.log("[Database] βœ“ Remote data cached:",r)}catch(t){throw console.error("[Database] βœ— Failed to cache remote data:",r,t),t}}async function Go(r){try{const e=await E`SELECT data, fetched_at FROM remote_data WHERE key = ${r}`;if(e.length===0)return null;const t=JSON.parse(e[0].data);return console.log("[Database] βœ“ Remote data loaded from cache:",r,"(fetched",e[0].fetched_at+")"),t}catch(e){return console.error("[Database] βœ— Failed to read cached remote data:",r,e),null}}async function jn(r){try{await E`DELETE FROM collector_zones`;for(const e of r){const t=JSON.stringify(e);await E` INSERT INTO collector_zones (id, zone_name, geometry_wkt, properties, fetched_at) VALUES (${e.colzonenr||e.id}, ${e.colzonename||e.zone_name||""}, ${e.polygon||e.boundary||""}, ${t}, CURRENT_TIMESTAMP) `}console.log("[Database] βœ“ Saved",r.length,"collector zones")}catch(e){throw console.error("[Database] βœ— Failed to save collector zones:",e),e}}async function Un(){try{const r=await E`SELECT properties FROM collector_zones ORDER BY id`;return r.length===0?null:r.map(e=>JSON.parse(e.properties))}catch(r){return console.error("[Database] βœ— Failed to read local collector zones:",r),null}}function Y(r){if(r===""||r===null||r===void 0)return null;const e=Number(r);return Number.isNaN(e)?null:e}async function Hn(r){try{await E`BEGIN`,await E`DELETE FROM parcels`;let e=0;for(const t of r){const n=t.id??t.parcelid??t.parcel_id??null;if(n==null)continue;const o=t.boundary||t.geometry_wkt||t.polygon||t.wkt||(typeof t.geom=="string"?t.geom:"");await E` INSERT OR REPLACE INTO parcels ( id, upn, style, landuse, zone_code, zone_name, sector, block, parcel_no, prop_no, st_name, prop_add, fac_name, min_height, max_height, eff_date, lp_name, locality, mmda, last_update, remarks, geometry_wkt, created_at, updated_at, districtid, status, fetched_at ) VALUES ( ${n}, ${t.upn??null}, ${Y(t.style)}, ${t.landuse??null}, ${t.zone_code??null}, ${t.zone_name??null}, ${t.sector??null}, ${t.block??null}, ${t.parcel_no??null}, ${t.prop_no??null}, ${t.st_name??null}, ${t.prop_add??null}, ${t.fac_name??null}, ${Y(t.min_height)}, ${Y(t.max_height)}, ${t.eff_date??null}, ${t.lp_name??null}, ${t.locality??null}, ${t.mmda??null}, ${t.last_update??null}, ${t.remarks??null}, ${o}, ${t.created_at??null}, ${t.updated_at??null}, ${Y(t.districtid)}, 'verified', CURRENT_TIMESTAMP ) `,e++}await E`COMMIT`,console.log("[Database] βœ“ Saved",e,"parcels (from",r.length,"rows,",r.length-e,"skipped/replaced)")}catch(e){try{await E`ROLLBACK`}catch{}throw console.error("[Database] βœ— Failed to save parcels:",e),e}}async function Wn(){try{const r=await E`SELECT * FROM parcels ORDER BY id`;return r.length===0?null:r}catch(r){return console.error("[Database] βœ— Failed to read local parcels:",r),null}}async function Kn(r,e){try{await E` UPDATE parcels SET upn = ${e.upn??null}, style = ${Y(e.style)}, landuse = ${e.landuse??null}, zone_code = ${e.zone_code??null}, zone_name = ${e.zone_name??null}, sector = ${e.sector??null}, block = ${e.block??null}, parcel_no = ${e.parcel_no??null}, prop_no = ${e.prop_no??null}, st_name = ${e.st_name??null}, prop_add = ${e.prop_add??null}, fac_name = ${e.fac_name??null}, min_height = ${Y(e.min_height)}, max_height = ${Y(e.max_height)}, eff_date = ${e.eff_date??null}, lp_name = ${e.lp_name??null}, locality = ${e.locality??null}, mmda = ${e.mmda??null}, last_update = ${e.last_update??null}, remarks = ${e.remarks??null}, districtid = ${Y(e.districtid)}, updated_at = CURRENT_TIMESTAMP WHERE id = ${r} `,console.log("[Database] βœ“ Parcel updated:",r),Te("parcels","UPDATE",r)}catch(t){throw console.error("[Database] βœ— Failed to update parcel:",r,t),t}}async function Vn(r,e={}){try{await E` INSERT INTO parcels ( id, upn, style, landuse, zone_code, zone_name, sector, block, parcel_no, prop_no, st_name, prop_add, fac_name, min_height, max_height, eff_date, lp_name, locality, mmda, last_update, remarks, geometry_wkt, created_at, updated_at, districtid, status, fetched_at ) VALUES ( NULL, ${e.upn??null}, ${Y(e.style)}, ${e.landuse??null}, ${e.zone_code??null}, ${e.zone_name??null}, ${e.sector??null}, ${e.block??null}, ${e.parcel_no??null}, ${e.prop_no??null}, ${e.st_name??null}, ${e.prop_add??null}, ${e.fac_name??null}, ${Y(e.min_height)}, ${Y(e.max_height)}, ${e.eff_date??null}, ${e.lp_name??null}, ${e.locality??null}, ${e.mmda??null}, ${e.last_update??null}, ${e.remarks??null}, ${r}, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP, ${Y(e.districtid)}, 'new', CURRENT_TIMESTAMP ) `;const n=(await E`SELECT last_insert_rowid() as id`)[0]?.id;return console.log("[Database] βœ“ New parcel inserted:",n,"(status: new)"),Te("parcels","INSERT",n),{id:n}}catch(t){throw console.error("[Database] βœ— Failed to insert new parcel:",t),t}}async function Xn(r){try{if(r.length>0){const e=r[0],t={};for(const[n,o]of Object.entries(e))t[n]=o===null?"null":typeof o;console.log("[Database] First footprint field types:",t)}await E`DELETE FROM building_footprints`;for(const e of r){const t=JSON.stringify(e);let n=e.polygon||e.boundary||e.geom||e.wkt||e.footprint||"";const o=typeof n=="object"?JSON.stringify(n):String(n);let a=e.id||e.footprint_id||e.building_id||null;await E` INSERT INTO building_footprints (id, geometry_wkt, properties, fetched_at) VALUES (${a!==null&&typeof a=="object"?null:a}, ${o}, ${t}, CURRENT_TIMESTAMP) `}console.log("[Database] βœ“ Saved",r.length,"building footprints")}catch(e){throw console.error("[Database] βœ— Failed to save building footprints:",e),e}}async function Yn(){try{const r=await E`SELECT properties FROM building_footprints ORDER BY id`;return r.length===0?null:r.map(e=>JSON.parse(e.properties))}catch(r){return console.error("[Database] βœ— Failed to read local building footprints:",r),null}}async function Zn(r){try{if(r.length>0){const e=r[0],t={};for(const[n,o]of Object.entries(e))t[n]=o===null?"null":typeof o;console.log("[Database] First road field types:",t)}await E`DELETE FROM osm_roads`;for(const e of r){const t=JSON.stringify(e);let n=e.geom||e.geometry||e.wkt||e.road||e.line||"";const o=typeof n=="object"?JSON.stringify(n):String(n);let a=e.osm_id??e.osmid??e.id??null;await E` INSERT OR REPLACE INTO osm_roads (osm_id, geometry_wkt, properties, fetched_at) VALUES (${a!==null&&typeof a=="object"?null:a}, ${o}, ${t}, CURRENT_TIMESTAMP) `}console.log("[Database] βœ“ Saved",r.length,"OSM roads")}catch(e){throw console.error("[Database] βœ— Failed to save OSM roads:",e),e}}async function Jn(){try{const r=await E`SELECT properties FROM osm_roads ORDER BY osm_id`;return r.length===0?null:r.map(e=>JSON.parse(e.properties))}catch(r){return console.error("[Database] βœ— Failed to read local OSM roads:",r),null}}async function Qn(){return Do.getDatabaseFile()}async function er(r="lupmis-backup.sqlite3"){const e=await Qn(),t=new Blob([e],{type:"application/x-sqlite3"}),n=URL.createObjectURL(t),o=document.createElement("a");o.href=n,o.download=r,o.click(),URL.revokeObjectURL(n)}async function tr(){return{type:"FeatureCollection",features:(await No()).map(e=>({type:"Feature",properties:{id:e.id,name:e.name,category:e.category,notes:e.notes,created_at:e.created_at},geometry:{type:"Point",coordinates:[e.lon,e.lat]}}))}}async function jt(){try{const r=await E` SELECT name FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%' ORDER BY name `,e=await zn();return{ready:Oo,databasePath:zt,tables:r.map(t=>t.name),locationCount:e}}catch(r){return{ready:!1,error:r.message}}}const qo=Object.freeze(["parcels","building_footprints","osm_roads","collector_zones","remote_data"]);function zo(r){return qo.includes(r)}async function jo(r){if(!zo(r))throw new Error(`Refusing to clear "${r}" β€” not a known cached-layer table`);const t=(await E(`SELECT COUNT(*) AS n FROM "${r}"`))[0]?.n??0;return await E(`DELETE FROM "${r}"`),console.log(`[Database] βœ“ Cleared "${r}" (${t} rows)`),Te(r,"CLEAR",null),t}async function or(){const r=await E` SELECT name FROM sqlite_master WHERE type='table' AND name IN ( 'parcels', 'building_footprints', 'osm_roads', 'collector_zones', 'remote_data' ) `,e=new Set(r.map(o=>o.name)),t=[];for(const o of qo)if(e.has(o))try{const a=await jo(o);t.push({table:o,count:a})}catch(a){console.error(`[Database] Failed to clear ${o}:`,a),t.push({table:o,count:0,error:a.message})}const n=t.reduce((o,a)=>o+a.count,0);return console.log(`[Database] βœ“ Cleared all cached layers: ${n} rows across ${t.length} tables`),t}async function nr(){const r=await E` SELECT name FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%' ORDER BY name `;if(r.length===0)return[];const e=r.map(t=>`SELECT '${t.name}' AS name, COUNT(*) AS count FROM "${t.name}"`).join(" UNION ALL ");return E(e)}async function rr(r,e=200){if((await E` SELECT name FROM sqlite_master WHERE type='table' AND name = ${r} `).length===0)throw new Error(`Table "${r}" does not exist`);const n=await E(`SELECT * FROM "${r}" LIMIT ${e}`);return{columns:n.length>0?Object.keys(n[0]):[],rows:n}}async function sr(){console.log("=== DATABASE TEST ===");try{const r=await E`SELECT sqlite_version() as v`;console.log("1. SQLite version:",r[0].v);const e=await E`SELECT name FROM sqlite_master WHERE type='table'`;console.log("2. Tables:",e.map(o=>o.name)),console.log("3. Inserting test row..."),await E`INSERT INTO locations (name, longitude, latitude, category) VALUES ('TEST', -1.0, 7.0, 'test')`;const t=await E`SELECT * FROM locations WHERE name = 'TEST'`;console.log("4. Test row:",t);const n=await E`SELECT COUNT(*) as c FROM locations`;return console.log("5. Total rows:",n[0].c),await E`DELETE FROM locations WHERE name = 'TEST'`,console.log("6. Test row deleted"),console.log("=== TEST PASSED ==="),!0}catch(r){return console.error("=== TEST FAILED ===",r),!1}}typeof window<"u"&&(window.testDatabase=sr,window.dbStatus=jt);async function ar(r){const{uuid:e,name:t=null,startedAt:n,districtId:o=null}=r;await E` INSERT INTO gps_trails (client_uuid, name, district_id, started_at, status) VALUES (${e}, ${t}, ${o}, ${n}, 'recording') `;const s=(await E`SELECT last_insert_rowid() as id`)[0]?.id;return Te("gps_trails","insert",s),s}async function ir(r,e){const{seq:t,lon:n,lat:o,altitude:a=null,accuracy:s=null,altitudeAccuracy:i=null,heading:l=null,speed:c=null,satellites:d=null,timestamp:u}=e,p=typeof u=="number"?new Date(u).toISOString():u||new Date().toISOString();await E` INSERT INTO gps_trail_points (trail_id, seq, longitude, latitude, altitude, accuracy, altitude_accuracy, heading, speed, satellites, recorded_at) VALUES (${r}, ${t}, ${n}, ${o}, ${a}, ${s}, ${i}, ${l}, ${c}, ${d}, ${p}) `}async function lr(r,e){const{endedAt:t,pointCount:n=0,distanceM:o=0}=e;await E` UPDATE gps_trails SET ended_at = ${t}, point_count = ${n}, distance_m = ${o}, status = 'completed' WHERE id = ${r} `,Te("gps_trails","update",r)}async function cr(){return E`SELECT * FROM gps_trails WHERE synced = 0 AND status = 'completed' ORDER BY started_at ASC`}async function dr(r){return E`SELECT * FROM gps_trail_points WHERE trail_id = ${r} ORDER BY seq ASC`}async function ur(r,e=null){await E`UPDATE gps_trails SET synced = 1, remote_id = ${e} WHERE id = ${r}`,Te("gps_trails","update",r)}const Uo=3.28084,Ho=621371e-9,Wo=10.7639,Ko=247105e-9,Vo=3861e-10;function ht(){return localStorage.getItem("measurement-system")||"metric"}function ut(r){if(ht()==="imperial"){const e=r*Uo;return e>=5280?Math.round(r*Ho*100)/100+" mi":Math.round(e)+" ft"}return r>1e3?Math.round(r/1e3*100)/100+" km":Math.round(r*100)/100+" m"}function pr(r){if(ht()==="imperial"){const e=r*Uo,t=r*Ho;return e>=5280?`${t.toFixed(2)} mi (${e.toLocaleString("en",{maximumFractionDigits:0})} ft)`:`${e.toLocaleString("en",{maximumFractionDigits:1})} ft`}return r>=1e3?`${(r/1e3).toFixed(2)} km (${r.toLocaleString("en",{maximumFractionDigits:0})} m)`:`${r.toLocaleString("en",{maximumFractionDigits:1})} m`}function He(r){if(ht()==="imperial"){const e=r*Ko;return e>=640?Math.round(r*Vo*100)/100+" miΒ²":e>=1?Math.round(e*100)/100+" acres":Math.round(r*Wo).toLocaleString("en")+" ftΒ²"}return r>1e6?Math.round(r/1e6*100)/100+" kmΒ²":Math.round(r*100)/100+" mΒ²"}function fr(r){if(ht()==="imperial"){const e=r*Wo,t=r*Ko,n=r*Vo;return t>=640?`${n.toFixed(2)} miΒ² (${t.toLocaleString("en",{maximumFractionDigits:0})} acres)`:t>=1?`${t.toLocaleString("en",{maximumFractionDigits:1})} acres (${e.toLocaleString("en",{maximumFractionDigits:0})} ftΒ²)`:`${e.toLocaleString("en",{maximumFractionDigits:0})} ftΒ²`}return r>1e6?`${(r/1e6).toFixed(2)} kmΒ² (${r.toLocaleString("en",{maximumFractionDigits:0})} mΒ²)`:`${r.toLocaleString("en",{maximumFractionDigits:0})} mΒ²`}function hr(r){return He(Math.PI*r*r)}function gr(r,e,t,n,o=1e-10){const a=e[0]-r[0],s=e[1]-r[1],i=n[0]-t[0],l=n[1]-t[1],c=a*l-s*i;if(Math.abs(c)1+o||h<-o||h>1+o?null:{point:[r[0]+p*a,r[1]+p*s],t:Math.max(0,Math.min(1,p)),u:Math.max(0,Math.min(1,h))}}function Xo(r){let e=0;for(let t=0,n=r.length;tr[1]!=l>r[1]&&r[0]<(i-a)*(r[1]-s)/(l-s)+a&&(t=!t)}return t}function Ve(r,e){return(r[0]-e[0])**2+(r[1]-e[1])**2}function mr(r,e){const t=[];for(let o=0;oo.lineSegIdx!==a.lineSegIdx?o.lineSegIdx-a.lineSegIdx:o.lineT-a.lineT),t}function yr(r,e){const t=e.map((a,s)=>({...a,origOrder:s}));t.sort((a,s)=>a.ringSegIdx!==s.ringSegIdx?a.ringSegIdx-s.ringSegIdx:a.ringT-s.ringT);const n=r.slice(),o=new Array(t.length);for(let a=t.length-1;a>=0;a--){const s=t[a],i=s.ringSegIdx+1,l=1e-6;if(Ve(s.point,n[s.ringSegIdx])=i&&o[t[c].origOrder]++}return{ring:n,indices:o}}function so(r,e,t){const n=r.length-1,o=(e%n+n)%n,a=(t%n+n)%n,s=[];let i=o;for(;s.push(r[i]),i!==a;)i=(i+1)%n;return s}function ao(r,e,t){const n=[e.point],o=e.lineSegIdx,a=t.lineSegIdx;for(let s=o+1;s<=a;s++)n.push(r[s]);return Ve(n[n.length-1],t.point)>1e-10&&n.push(t.point),n}function io(r,e){const t=Xo(r);return e&&t<0||!e&&t>0?r.slice().reverse():r}function lo(r){if(r.length<2)return r;const e=r[0],t=r[r.length-1];return Ve(e,t)>1e-10?[...r,e.slice()]:r}function br(r,e){let t=1/0,n=1/0,o=-1/0,a=-1/0;for(const c of e)c[0]o&&(o=c[0]),c[1]>a&&(a=c[1]);const s=Math.sqrt((o-t)**2+(a-n)**2)||1,i=r.slice();if(Ot(i[0],e)){const c=i[0],d=i[1],u=c[0]-d[0],p=c[1]-d[1],h=Math.sqrt(u*u+p*p)||1,f=s*2/h;i[0]=[c[0]+u*f,c[1]+p*f]}const l=i.length-1;if(Ot(i[l],e)){const c=i[l],d=i[l-1],u=c[0]-d[0],p=c[1]-d[1],h=Math.sqrt(u*u+p*p)||1,f=s*2/h;i[l]=[c[0]+u*f,c[1]+p*f]}return i}function nt(r,e){const t=r[0],n=r.slice(1),o=br(e,t),a=mr(t,o);if(a.length!==2)return console.warn(`[polygonSplit] Expected 2 intersections, got ${a.length}`),null;const[s,i]=a,{ring:l,indices:c}=yr(t,a),d=c[0],u=c[1],[p,h]=d0,x=io(m,S),v=io(w,S),L=[x],P=[v];for(const T of n){const z=wr(T);Ot(z,x)?L.push(T):P.push(T)}return[L,P]}function wr(r){let e=0,t=0;const n=r.length-1;for(let o=0;o{a.style.opacity="1",a.style.transform="translateY(0)"});const s=()=>{a.style.opacity="0",a.style.transform="translateY(-8px)",setTimeout(()=>a.remove(),300)};a.addEventListener("click",s),setTimeout(s,t)}const Je=[{stroke:"#ef4444",fill:"rgba(239,68,68,0.25)"},{stroke:"#3b82f6",fill:"rgba(59,130,246,0.25)"}],_r=new M({stroke:new k({color:"#0ea5e9",width:3}),fill:new I({color:"rgba(14,165,233,0.15)"})}),Er=new M({stroke:new k({color:"#f43f5e",width:2,lineDash:[8,6]}),image:new ae({radius:5,fill:new I({color:"#f43f5e"}),stroke:new k({color:"#fff",width:1.5})})});class xr extends Bt{constructor(e={}){super({handleEvent:t=>this._handleEvent(t)}),this.snapDistance_=e.snapDistance||25,this._sources=e.sources?Array.isArray(e.sources)?e.sources:[e.sources]:null,this._phase="select",this._selectedFeature=null,this._selectedSource=null,this._drawInteraction=null,this._splitFeatures=null,this._overlaySource=new R({useSpatialIndex:!1}),this._overlayLayer=new A({source:this._overlaySource,displayInLayerSwitcher:!1,style:_r})}setMap(e){this.getMap()&&(this.getMap().removeLayer(this._overlayLayer),this._removeDrawInteraction()),super.setMap(e),e&&this._overlayLayer.setMap(e)}setActive(e){super.setActive(e),e||this._reset()}_getSources(){if(this._sources)return this._sources;if(!this.getMap())return[];const e=[],t=n=>{n.forEach(o=>{o.getVisible()&&(o.getSource&&o.getSource()instanceof R?e.push(o.getSource()):o.getLayers&&t(o.getLayers()))})};return t(this.getMap().getLayers()),e}_handleEvent(e){if(!this.getActive())return!0;if(this._phase==="select"){if(e.type==="pointermove")return this._onSelectMove(e);if(e.type==="singleclick")return this._onSelectClick(e)}if(this._phase==="draw"&&e.type==="keydown"&&e.originalEvent?.key==="Escape")return this._cancelDraw(),!1;if(this._phase==="pick"){if(e.type==="pointermove")return this._onPickMove(e);if(e.type==="singleclick")return this._onPickClick(e);if(e.type==="keydown"&&e.originalEvent?.key==="Escape")return this._reset(),!1}return!0}_onSelectMove(e){const t=this.getMap();if(!t)return!0;this._overlaySource.clear();const n=this._closestPolygon(e);if(n){const o=n.feature.clone();this._overlaySource.addFeature(o),t.getTargetElement().style.cursor="pointer"}else t.getTargetElement().style.cursor="";return!0}_onSelectClick(e){const t=this._closestPolygon(e);if(!t)return!0;this._selectedFeature=t.feature,this._selectedSource=t.source,this._overlaySource.clear();const n=t.feature.clone();return this._overlaySource.addFeature(n),this._startDrawPhase(),!1}_closestPolygon(e){let t=null,n=this.snapDistance_+1;for(const o of this._getSources()){const a=o.getClosestFeatureToCoordinate(e.coordinate);if(!a)continue;const s=a.getGeometry();if(!s)continue;const i=s.getType();if(i!=="Polygon"&&i!=="MultiPolygon")continue;const l=s.getClosestPoint(e.coordinate),d=new ie([e.coordinate,l]).getLength()/e.frameState.viewState.resolution;d{const n=t.feature.getGeometry().getCoordinates();this._performSplit(n)}),e.addInteraction(this._drawInteraction))}_removeDrawInteraction(){this._drawInteraction&&this.getMap()&&this.getMap().removeInteraction(this._drawInteraction),this._drawInteraction=null}_cancelDraw(){this._removeDrawInteraction(),this._reset()}_performSplit(e){const t=this._selectedFeature,n=this._selectedSource,o=t.getGeometry();let a;o.getType()==="Polygon"?a=o.getCoordinates():o.getType()==="MultiPolygon"&&(a=o.getCoordinates()[0]);const s=nt(a,e);if(!s){console.warn("[PolygonSplit] Split failed β€” line must cross the polygon boundary at exactly 2 points."),this._removeDrawInteraction(),this._startDrawPhase();return}const[i,l]=s,c=t.clone();c.setGeometry(new Ke(i)),c.setStyle(new M({stroke:new k({color:Je[0].stroke,width:2.5}),fill:new I({color:Je[0].fill})}));const d=t.clone();d.setGeometry(new Ke(l)),d.setStyle(new M({stroke:new k({color:Je[1].stroke,width:2.5}),fill:new I({color:Je[1].fill})}));const u=[c,d];if(this.dispatchEvent({type:"beforesplit",original:t,features:u}),n.dispatchEvent({type:"beforesplit",original:t,features:u}),n.removeFeature(t),n.addFeature(c),n.addFeature(d),this.dispatchEvent({type:"aftersplit",original:t,features:u}),n.dispatchEvent({type:"aftersplit",original:t,features:u}),this._removeDrawInteraction(),t.get("_layerType")==="parcel"){this._splitFeatures=u,this._phase="pick",this._overlaySource.clear();const h=this.getMap();h&&(h.getTargetElement().style.cursor=""),O("Click the polygon that should keep the original identifier.","info",5e3),this.dispatchEvent({type:"splitparcel",features:u,originalProps:t.getProperties(),source:n})}else this._reset()}_onPickMove(e){const t=this.getMap();if(!t)return!0;this._overlaySource.clear();const n=this._closestSplitPiece(e);if(n){const o=n.clone();this._overlaySource.addFeature(o),t.getTargetElement().style.cursor="pointer"}else t.getTargetElement().style.cursor="";return!0}_onPickClick(e){const t=this._closestSplitPiece(e);return t?(this.dispatchEvent({type:"splitpick",picked:t,features:this._splitFeatures}),this._reset(),!1):!0}_closestSplitPiece(e){if(!this._splitFeatures)return null;let t=null,n=this.snapDistance_+1;for(const o of this._splitFeatures){const a=o.getGeometry();if(!a)continue;const s=a.getClosestPoint(e.coordinate),l=new ie([e.coordinate,s]).getLength()/e.frameState.viewState.resolution;lr[1]!=l>r[1]&&r[0]<(i-a)*(r[1]-s)/(l-s)+a&&(t=!t)}return t}function Lr(r,e){const t=ze(r);return e&&t<0||!e&&t>0?r.slice().reverse():r}function Tr(r){return r.length<2?r:Fe(r[0],r[r.length-1])>1e-10?[...r,r[0].slice()]:r}function De(r,e,t){const n=t[0]-e[0],o=t[1]-e[1],a=n*n+o*o;if(a<1e-20)return Fe(r,e);let s=((r[0]-e[0])*n+(r[1]-e[1])*o)/a;s=Math.max(0,Math.min(1,s));const i=e[0]+s*n,l=e[1]+s*o;return(r[0]-i)**2+(r[1]-l)**2}function uo(r,e){let t=0,n=1/0;const o=r.length-1;for(let a=0;a0;){const v=(y+1)%a,L=g?(S-1+s)%s:(S+1)%s;if(v===m||L===w)break;if(Me(r[v],e[L],i)){y=v,S=L;continue}if(De(r[v],e[S],e[L])0;){const v=(m-1+a)%a,L=g?(w+1)%s:(w-1+s)%s;if(v===y||L===S)break;if(Me(r[v],e[L],i)){m=v,w=L;continue}if(De(r[v],e[w],e[L])n+1)););return o}function Pr(r,e,t,n,o=5){const a=r[0],s=e[0],i=r.slice(1),l=e.slice(1),c=uo(a,t),d=uo(s,n),u=kr(a,s,c.segIdx,d.segIdx,o);if(!u)return console.warn("[polygonMerge] Could not find shared boundary between polygons β€” seed edges are not near the other ring"),{coords:null,error:"The selected edges are not on a shared boundary. Click edges that lie on the common border between the two polygons."};const{startA:p,endA:h,startB:f,endB:b,reversed:g}=u;a.length-1,s.length-1;const m=St(a,h,p);let y;g?y=St(s,f,b):y=St(s,b,f);const w=[...m,...y.slice(1)],S=o*o;w.length>2&&Fe(w[w.length-1],w[0])T*1.5)return console.warn(`[polygonMerge] Area mismatch: A=${v.toFixed(1)}, B=${L.toFixed(1)}, merged=${P.toFixed(1)}, expectedβ‰ˆ${T.toFixed(1)}`),{coords:null,error:"Merge produced an invalid polygon (area mismatch). The polygons may not be truly adjacent β€” try clicking closer to the shared boundary."};const z=ze(a)>0,U=Lr(x,z),D=[...i,...l].filter(ne=>{const V=ne.reduce((G,ue)=>G+ue[0],0)/(ne.length-1),de=ne.reduce((G,ue)=>G+ue[1],0)/(ne.length-1);return Sr([V,de],U)});return{coords:[U,...D]}}const po=new M({stroke:new k({color:"#0ea5e9",width:3}),fill:new I({color:"rgba(14,165,233,0.15)"})}),Mr=new M({stroke:new k({color:"#f59e0b",width:3}),fill:new I({color:"rgba(245,158,11,0.15)"})}),Ir=new M({stroke:new k({color:"#0ea5e9",width:3}),fill:new I({color:"rgba(14,165,233,0.15)"}),text:new rt({text:"A",font:"bold 22px Exo, sans-serif",fill:new I({color:"#0ea5e9"}),stroke:new k({color:"#fff",width:4}),overflow:!0})}),Cr=new M({stroke:new k({color:"#f59e0b",width:3}),fill:new I({color:"rgba(245,158,11,0.15)"}),text:new rt({text:"B",font:"bold 22px Exo, sans-serif",fill:new I({color:"#f59e0b"}),stroke:new k({color:"#fff",width:4}),overflow:!0})}),Ar=new M({stroke:new k({color:"#ec4899",width:4,lineDash:[10,6]})}),Dr=new M({stroke:new k({color:"#10b981",width:2.5}),fill:new I({color:"rgba(16,185,129,0.3)"})});class Fr extends Bt{constructor(e={}){super({handleEvent:t=>this._handleEvent(t)}),this.snapDistance_=e.snapDistance||25,this.tolerance_=e.tolerance||5,this._phase="select_a",this._featureA=null,this._sourceA=null,this._featureB=null,this._sourceB=null,this._edgeClickA=null,this._edgeClickB=null,this._highlightSource=new R({useSpatialIndex:!1}),this._highlightLayer=new A({source:this._highlightSource,displayInLayerSwitcher:!1,style:t=>t.get("_highlightStyle")||po}),this._edgeSource=new R({useSpatialIndex:!1}),this._edgeLayer=new A({source:this._edgeSource,displayInLayerSwitcher:!1,style:Ar})}setMap(e){this.getMap()&&(this.getMap().removeLayer(this._highlightLayer),this.getMap().removeLayer(this._edgeLayer)),super.setMap(e),e&&(this._highlightLayer.setMap(e),this._edgeLayer.setMap(e))}setActive(e){super.setActive(e),e||this._reset()}_getSources(){if(!this.getMap())return[];const e=[],t=n=>{n.forEach(o=>{o.getVisible()&&(o.getSource&&o.getSource()instanceof R?e.push(o.getSource()):o.getLayers&&t(o.getLayers()))})};return t(this.getMap().getLayers()),e}_handleEvent(e){if(!this.getActive())return!0;if(e.type==="keydown"&&e.originalEvent?.key==="Escape")return this._reset(),!1;switch(this._phase){case"select_a":if(e.type==="pointermove")return this._onSelectMove(e,null);if(e.type==="singleclick")return this._onSelectAClick(e);break;case"select_b":if(e.type==="pointermove")return this._onSelectMove(e,this._featureA);if(e.type==="singleclick")return this._onSelectBClick(e);break;case"click_edge_a":if(e.type==="pointermove")return this._onEdgeMove(e,this._featureA);if(e.type==="singleclick")return this._onEdgeAClick(e);break;case"click_edge_b":if(e.type==="pointermove")return this._onEdgeMove(e,this._featureB);if(e.type==="singleclick")return this._onEdgeBClick(e);break}return!0}_onSelectMove(e,t){const n=this.getMap();if(!n)return!0;this._highlightSource.clear(),this._edgeSource.clear(),this._rebuildHighlights();const o=this._closestPolygon(e,t);if(o){const a=this._phase==="select_a"?po:Mr,s=o.feature.clone();s.set("_highlightStyle",a),this._highlightSource.addFeature(s),n.getTargetElement().style.cursor="pointer"}else n.getTargetElement().style.cursor="";return!0}_onSelectAClick(e){const t=this._closestPolygon(e,null);return t?(this._featureA=t.feature,this._sourceA=t.source,this._phase="select_b",this._rebuildHighlights(),!1):!0}_onSelectBClick(e){const t=this._closestPolygon(e,this._featureA);return t?(this._featureB=t.feature,this._sourceB=t.source,this._phase="click_edge_a",this._rebuildHighlights(),this.getMap().getTargetElement().style.cursor="crosshair",!1):!0}_closestPolygon(e,t){let n=null,o=this.snapDistance_+1;for(const a of this._getSources()){const s=a.getClosestFeatureToCoordinate(e.coordinate);if(!s||t&&s===t)continue;const i=s.getGeometry();if(!i)continue;const l=i.getType();if(l!=="Polygon"&&l!=="MultiPolygon")continue;const c=i.getClosestPoint(e.coordinate),u=new ie([e.coordinate,c]).getLength()/e.frameState.viewState.resolution;u{t.get("_permanent")&&e.push(t)}),e.forEach(t=>this._highlightSource.removeFeature(t)),this._featureA){const t=this._featureA.clone();t.set("_highlightStyle",Ir),t.set("_permanent",!0),this._highlightSource.addFeature(t)}if(this._featureB){const t=this._featureB.clone();t.set("_highlightStyle",Cr),t.set("_permanent",!0),this._highlightSource.addFeature(t)}}_reset(){this._phase="select_a",this._featureA=null,this._sourceA=null,this._featureB=null,this._sourceB=null,this._edgeClickA=null,this._edgeClickB=null,this._highlightSource.clear(),this._edgeSource.clear();const e=this.getMap();e&&(e.getTargetElement().style.cursor="")}}function Or(r,e){return(r[0]-e[0])**2+(r[1]-e[1])**2}function fo(r){let e=0;for(let t=0,n=r.length;tt&&(t=d,n=c)}const o=r[n],a=r[n+1],s=Math.sqrt(t),i=[(a[0]-o[0])/s,(a[1]-o[1])/s],l=[-i[1],i[0]];return{p0:o,p1:a,along:i,perp:l}}function Lt(r,e,t,n,o){const a=r[0]+n*e[0],s=r[1]+n*e[1];return[[a-o*t[0],s-o*t[1]],[a+o*t[0],s+o*t[1]]]}function Ie(r,e,t){const n=r[0],o=n.length-1;let a=0,s=0;for(let c=0;cu&&(u=w)}const p=(u-d)*1.5,h=[];let f=r,b=e;for(let g=0;gv&&(v=V)}let L=x,P=v,T=null,z=null,U=1/0;for(let K=0;K<40;K++){const D=(L+P)/2,ne=Lt(l,s,i,D,p),V=nt(f,ne);if(!V){const C=(P-L)*.01,q=Lt(l,s,i,D+C,p),Q=nt(f,q);if(Q){const[be,we]=Q,Re=Ie(be,l,s),$e=Ie(we,l,s),Ne=Re<$e?be:we,wt=Re<$e?we:be,vt=Ge(Ne),Be=Math.abs(vt-y);Bethis._handleEvent(t)}),this.snapDistance_=e.snapDistance||25,this._sources=e.sources?Array.isArray(e.sources)?e.sources:[e.sources]:null,this._phase="select",this._selectedFeature=null,this._selectedSource=null,this._selectedEdge=null,this._dividedFeatures=null,this._overlaySource=new R({useSpatialIndex:!1}),this._overlayLayer=new A({source:this._overlaySource,displayInLayerSwitcher:!1,style:Nr}),this._edgeSource=new R({useSpatialIndex:!1}),this._edgeLayer=new A({source:this._edgeSource,displayInLayerSwitcher:!1,style:Br})}setMap(e){this.getMap()&&(this.getMap().removeLayer(this._overlayLayer),this.getMap().removeLayer(this._edgeLayer)),super.setMap(e),e&&(this._overlayLayer.setMap(e),this._edgeLayer.setMap(e))}setActive(e){super.setActive(e),e||this._reset()}_getSources(){if(this._sources)return this._sources;if(!this.getMap())return[];const e=[],t=n=>{n.forEach(o=>{o.getVisible()&&(o.getSource&&o.getSource()instanceof R?e.push(o.getSource()):o.getLayers&&t(o.getLayers()))})};return t(this.getMap().getLayers()),e}_handleEvent(e){if(!this.getActive())return!0;if(e.type==="keydown"&&e.originalEvent?.key==="Escape")return this._phase==="form"?this.cancelDivide():this._reset(),!1;if(this._phase==="select"){if(e.type==="pointermove")return this._onSelectMove(e);if(e.type==="singleclick")return this._onSelectClick(e)}if(this._phase==="edge"){if(e.type==="pointermove")return this._onEdgeMove(e);if(e.type==="singleclick")return this._onEdgeClick(e)}if(this._phase==="pick"){if(e.type==="pointermove")return this._onPickMove(e);if(e.type==="singleclick")return this._onPickClick(e)}return!0}_onSelectMove(e){const t=this.getMap();if(!t)return!0;this._overlaySource.clear();const n=this._closestPolygon(e);if(n){const o=n.feature.clone();this._overlaySource.addFeature(o),t.getTargetElement().style.cursor="pointer"}else t.getTargetElement().style.cursor="";return!0}_onSelectClick(e){const t=this._closestPolygon(e);if(!t)return!0;this._selectedFeature=t.feature,this._selectedSource=t.source,this._overlaySource.clear();const n=t.feature.clone();return n.set("_permanent",!0),this._overlaySource.addFeature(n),this._phase="edge",O("Click the edge to divide along.","info",3e3),!1}_closestPolygon(e){let t=null,n=this.snapDistance_+1;for(const o of this._getSources()){const a=o.getClosestFeatureToCoordinate(e.coordinate);if(!a)continue;const s=a.getGeometry();if(!s)continue;const i=s.getType();if(i!=="Polygon"&&i!=="MultiPolygon")continue;const l=s.getClosestPoint(e.coordinate),d=new ie([e.coordinate,l]).getLength()/e.frameState.viewState.resolution;d{const f=t.clone();return f.setGeometry(new Ke(p)),f.setStyle(new M({stroke:new k({color:i[h].stroke,width:2.5}),fill:new I({color:i[h].fill})})),f}),c={type:"beforedivide",original:t,features:l};this.dispatchEvent(c),n.dispatchEvent({...c}),n.removeFeature(t);for(const p of l)n.addFeature(p);const d={type:"afterdivide",original:t,features:l};this.dispatchEvent(d),n.dispatchEvent({...d}),t.get("_layerType")==="parcel"?(this._dividedFeatures=l,this._phase="pick",O("Click the polygon that should keep the original identifier.","info",5e3),this.dispatchEvent({type:"dividedparcel",features:l,originalProps:t.getProperties(),source:n})):(O(`Polygon divided into ${e} equal pieces.`,"success"),this._reset())}_onPickMove(e){const t=this.getMap();if(!t)return!0;this._overlaySource.clear();const n=this._closestDividedPiece(e);if(n){const o=n.clone();this._overlaySource.addFeature(o),t.getTargetElement().style.cursor="pointer"}else t.getTargetElement().style.cursor="";return!0}_onPickClick(e){const t=this._closestDividedPiece(e);return t?(this.dispatchEvent({type:"dividepick",picked:t,features:this._dividedFeatures}),this._reset(),!1):!0}_closestDividedPiece(e){if(!this._dividedFeatures)return null;let t=null,n=this.snapDistance_+1;for(const o of this._dividedFeatures){const a=o.getGeometry();if(!a)continue;const s=a.getClosestPoint(e.coordinate),l=new ie([e.coordinate,s]).getLength()/e.frameState.viewState.resolution;l{const l=this.categoryEmojis[i];return l?l.emoji:"πŸ“"},this.getCategoryOptionsHtml=()=>Object.entries(this.categoryEmojis).map(([i,{emoji:l,label:c}])=>``).join(` `),this.createEmojiStyle=(i,l=24)=>new M({text:new rt({text:i,font:`${l}px sans-serif`,textBaseline:"bottom",textAlign:"center",offsetY:-5})}),this.defaultStyle=this.createEmojiStyle("πŸ“",32),this.selectedStyle=this.createEmojiStyle("πŸ“",42),this.categoryStyles={};for(const[i,{emoji:l}]of Object.entries(this.categoryEmojis))this.categoryStyles[i]=this.createEmojiStyle(l,32);const n=this.createBaseLayers(t.basemap||"topo");this.markersLayer=new A({title:"Markers",source:this.markerSource,style:i=>this.getFeatureStyle(i),visible:!1}),this.overlayGroup=new ve({title:"Overlays"}),this.map=new Kt({target:e,layers:[n,this.markersLayer,this.overlayGroup],view:new cn({center:X(t.center||[0,0]),zoom:t.zoom||2,minZoom:t.minZoom||2,maxZoom:t.maxZoom||19})});const o=new bn({collapsed:!0,mouseover:!0,extent:!0,trash:!1,oninfo:null});this.map.addControl(o),queueMicrotask(()=>{const i=o.element?.querySelector(":scope > button");if(i){const l="/".replace(/\/?$/,"/");i.style.backgroundImage=`url('${l}app-icons/luspa-72x72.png')`}});let a=!1;o.on("drawlist",i=>{this._decorateLayerListItem(i.layer,i.li),a||(a=!0,queueMicrotask(()=>{a=!1,this._refreshLayerSwitcherChrome(o)}))}),this.map.getLayers().on("change",()=>{this._refreshLayerSwitcherChrome(o)}),this._wireLayerSwitcherVisibilityHooks(o),this._createAddLayerDialog(),this._createLegendPanel(),this.scaleBar=new dn({bar:!0,steps:4,text:!0,minWidth:140}),this.map.addControl(this.scaleBar),this._initGpsRendering(),this._createLocationControl(),this._createBaseMapPicker();const s=new wn({placeholder:"Search location...",typing:300,minLength:3,maxItems:10,collapsed:!0});this.map.addControl(s),s.on("select",i=>{const l=i.search;if(l){const c=parseFloat(l.lon),d=parseFloat(l.lat),u=[c,d],p=X(u);this.navigateTo(c,d,14);const h={coordinate:p,lonLat:u,name:l.display_name||l.name||"Unknown",searchResult:l};this.searchSelectCallbacks.forEach(f=>f(h))}}),this.searchNominatim=s,this.searchSelectCallbacks=[],this.selectedFeature=null,this.createPopup(),this.createInfoPopup(),this.createAddLocationPopup(),this.createParcelEditPopup(),this.createDrawnPolygonPopup(),this.createMergePopup(),this.createDividePopup(),this.dblClickCallbacks=[],this.editBar=null,this.drawingsSource=null,this.drawingsLayer=null,this.touchCursor=null,this._editBarActive=!1}initEditBar(){this.drawingsSource=new R,this.drawingsLayer=new A({title:"sketches",source:this.drawingsSource,style:new M({stroke:new k({color:"#f59e0b",width:2.5}),fill:new I({color:"rgba(245,158,11,0.15)"}),image:new ae({radius:6,fill:new I({color:"#f59e0b"}),stroke:new k({color:"#fff",width:1.5})})})}),this._drawingsGroup=new ve({title:"Drawings",layers:[this.drawingsLayer]});const e=this.map.getLayers(),t=e.getArray().indexOf(this.overlayGroup);e.insertAt(t>=0?t:e.getLength(),this._drawingsGroup),this._selectInteraction=new un({condition:pn,filter:(f,b)=>!!b,layers:f=>f instanceof A}),this._selectInteraction.setActive(!1),this.map.addInteraction(this._selectInteraction),this._modifyInteraction=new vn({features:this._selectInteraction.getFeatures()}),this._modifyInteraction.setActive(!1),this._undoRedo=new _n,this.map.addInteraction(this._undoRedo),this.editBar=new Ct({source:this.drawingsSource,interactions:{Select:this._selectInteraction,ModifySelect:this._modifyInteraction,DrawPoint:!0,DrawLine:!0,DrawPolygon:!0,DrawRegular:!0,DrawHole:!0,Delete:!0,Info:!0,Transform:!0,Split:!1}}),this.map.addControl(this.editBar),this._setupVertexOverlay();const n=new Yt({group:!0,className:"ol-editbar-actions",controls:[new qe({html:'',className:"ol-undo",title:"Undo",handleClick:()=>{this._undoRedo.hasUndo()&&this._undoRedo.undo()}}),new qe({html:'',className:"ol-redo",title:"Redo",handleClick:()=>{this._undoRedo.hasRedo()&&this._undoRedo.redo()}}),new qe({html:'',className:"ol-save",title:"Save drawings",handleClick:()=>{this.dispatchEditEvent("save")}})]});this.editBar.addControl(n),this._lineSplitInteraction=new En,this._polygonSplitInteraction=new xr,this.map.addInteraction(this._lineSplitInteraction),this.map.addInteraction(this._polygonSplitInteraction),this._lineSplitInteraction.setActive(!1),this._polygonSplitInteraction.setActive(!1),this._polygonSplitInteraction.on("splitpick",f=>{const b=["UPN","upn","id","parcelid","parcel_id","PARCELID","PARCEL_ID","ID"];for(const g of f.features)if(g!==f.picked)for(const m of b)g.get(m)!==void 0&&g.set(m,"")}),this._polygonDivideInteraction=new qr,this.map.addInteraction(this._polygonDivideInteraction),this._polygonDivideInteraction.setActive(!1);const o=new me({html:'',className:"ol-split-line",title:"Split Lines",name:"SplitLine",interaction:this._lineSplitInteraction,autoActivate:!0}),a=new me({html:'',className:"ol-split-polygon",title:"Split Polygons",name:"SplitPolygon",interaction:this._polygonSplitInteraction}),s=new me({html:'',className:"ol-split-divide",title:"Divide Polygon",name:"DividePolygon",interaction:this._polygonDivideInteraction}),i=new Yt({toggleOne:!0,autoDeactivate:!0,controls:[o,a,s]}),l=new me({className:"ol-split",title:"Split",name:"Split",bar:i,onToggle:f=>{f||(this._lineSplitInteraction.setActive(!1),this._polygonSplitInteraction.setActive(!1),this._polygonDivideInteraction.setActive(!1))}});this.editBar.addControl(l),this._polygonDivideInteraction.on("divideform",f=>{this.showDividePopup(f.feature,f.source,f.coordinate)}),this._polygonDivideInteraction.on("dividecancel",()=>{this.hideDividePopup()}),this._polygonDivideInteraction.on("dividepick",f=>{const b=["UPN","upn","id","parcelid","parcel_id","PARCELID","PARCEL_ID","ID"];for(const g of f.features)if(g!==f.picked)for(const m of b)g.get(m)!==void 0&&g.set(m,"")}),this._polygonMergeInteraction=new Fr,this.map.addInteraction(this._polygonMergeInteraction),this._polygonMergeInteraction.setActive(!1);const c=new me({html:'',className:"ol-merge",title:"Merge Polygons",name:"Merge",interaction:this._polygonMergeInteraction});this.editBar.addControl(c),this._polygonMergeInteraction.on("mergedparcel",f=>{this.showMergeIdentifierPopup(f.merged,f.propsA,f.propsB,f.coordinate)});const d=this.editBar.element;if(d&&n.element&&n.element.parentNode===d){const f=document.createElement("div");f.className="ol-editbar-break",d.insertBefore(f,n.element)}this._snapGuidesEnabled=localStorage.getItem("snap-guides-enabled")==="1",this._snapGuides=new xn({pixelTolerance:10,vectorClass:fn}),this.map.addInteraction(this._snapGuides);const u=["DrawPoint","DrawLine","DrawPolygon","DrawHole","DrawRegular"];for(const f of u){const b=this.editBar.getInteraction(f);b&&b.on("change:active",()=>{b.getActive()&&this._snapGuides.setDrawInteraction(b)})}this._modifyInteraction&&this._snapGuides.setModifyInteraction(this._modifyInteraction);const p=new qe({html:'',className:"ol-snap-toggle"+(this._snapGuidesEnabled?" ol-active":""),title:"Toggle Snap Guides",handleClick:()=>{this._snapGuidesEnabled=!this._snapGuidesEnabled,localStorage.setItem("snap-guides-enabled",this._snapGuidesEnabled?"1":"0"),p.element.classList.toggle("ol-active",this._snapGuidesEnabled),this._snapGuides&&this._snapGuides.setActive(this._snapGuidesEnabled&&this._editBarActive),console.log("[MapView] Snap guides:",this._snapGuidesEnabled?"ON":"OFF")}});this._snapToggleBtn=p,n.addControl(p),this.setEditMode(!1),this._drawingsGroup.on("change:visible",()=>{const f=this._drawingsGroup.getVisible();this.setEditMode(f)}),("ontouchstart"in window||navigator.maxTouchPoints>0||navigator.msMaxTouchPoints>0)&&(this.touchCursor=new Sn({className:"ol-editbar-cursor"}),this.map.addInteraction(this.touchCursor),this.touchCursor.setActive(!1),console.log("[MapView] Touch device detected β€” TouchCursor added")),this.drawingsSource.on("addfeature",f=>{const b=f.feature,g=b.getGeometry();if(!g||g.getType()!=="Polygon")return;const m=g.getInteriorPoint().getCoordinates();this.showDrawnPolygonPopup(b,m)}),console.log("[MapView] EditBar initialised with Drawings group, UndoRedo and SnapGuides (default:",this._snapGuidesEnabled?"ON":"OFF",")")}dispatchEditEvent(e){if(!this._editEventListeners)return;const t=this._editEventListeners[e];t&&t.forEach(n=>n())}onEditEvent(e,t){this._editEventListeners||(this._editEventListeners={}),this._editEventListeners[e]||(this._editEventListeners[e]=[]),this._editEventListeners[e].push(t)}setEditMode(e){this._editBarActive=!!e,this.editBar&&(this.editBar.setVisible(this._editBarActive),this._editBarActive||this.editBar.deactivateControls()),this._selectInteraction&&(this._editBarActive||this._selectInteraction.getFeatures().clear(),this._selectInteraction.setActive(this._editBarActive)),this._modifyInteraction&&this._modifyInteraction.setActive(this._editBarActive),this._snapGuides&&this._snapGuides.setActive(this._snapGuidesEnabled&&this._editBarActive),this.touchCursor&&this.touchCursor.setActive(this._editBarActive),!this._editBarActive&&this._vertexOverlaySource&&this._vertexOverlaySource.clear(),console.log("[MapView] Edit mode:",this._editBarActive?"ON":"OFF")}isEditMode(){return this._editBarActive}_setupVertexOverlay(){this._vertexOverlaySource=new R,this._vertexOverlayLayer=new A({title:"__vertex_highlight__",source:this._vertexOverlaySource,zIndex:990,style:new M({image:new ae({radius:4,fill:new I({color:"rgba(14,165,233,0.85)"}),stroke:new k({color:"#fff",width:1.2})})})}),this._vertexOverlayLayer.set("displayInLayerSwitcher",!1),this.map.addLayer(this._vertexOverlayLayer),this._onSelectedFeatureGeomChange=()=>this._refreshVertexOverlay(),this._vertexTrackedFeatures=new Set,this._selectInteraction.on("select",()=>this._refreshVertexOverlay())}_refreshVertexOverlay(){if(!this._vertexOverlaySource)return;if(this._vertexOverlaySource.clear(),this._vertexTrackedFeatures){for(const t of this._vertexTrackedFeatures)t.un("change",this._onSelectedFeatureGeomChange);this._vertexTrackedFeatures.clear()}if(!this._editBarActive||!this._selectInteraction)return;const e=this._selectInteraction.getFeatures().getArray();for(const t of e){const n=t.getGeometry();if(!n)continue;const o=n.getType();if(!["Polygon","MultiPolygon","LineString","MultiLineString"].includes(o))continue;const a=this._collectAllVertices(n);for(const s of a)this._vertexOverlaySource.addFeature(new se(new Xe(s)));t.on("change",this._onSelectedFeatureGeomChange),this._vertexTrackedFeatures.add(t)}}_collectAllVertices(e){const t=[],n=i=>Array.isArray(i)&&typeof i[0]=="number",o=(i,l)=>{const c=l&&i.length>1?i.length-1:i.length;for(let d=0;d{if(n(l))t.push(l);else if(Array.isArray(l))for(const c of l)i(c)};i(s)}return t}getDrawingsLayer(){return this.drawingsLayer}getDrawingsSource(){return this.drawingsSource}getEditBar(){return this.editBar}setScaleBarUnits(e){this.scaleBar&&this.scaleBar.setUnits(e==="imperial"?"imperial":"metric")}createPopup(){this.popupElement=document.createElement("div"),this.popupElement.className="map-popup",this.popupElement.style.cssText=` position: absolute; background: var(--card, #fff); color: var(--card-foreground, #1e1a4b); border-radius: 8px; padding: 10px 14px; box-shadow: 0 2px 8px rgba(0,0,0,0.25); font-family: var(--font-body, 'Exo', sans-serif); font-size: 13px; min-width: 150px; max-width: 280px; pointer-events: none; z-index: 1000; border: 1px solid var(--border, #1e1a4b1f); `,this.popup=new ge({element:this.popupElement,positioning:"bottom-center",offset:[0,-15],stopEvent:!1}),this.map.addOverlay(this.popup),this.setupHoverPopup()}setupHoverPopup(){let e=null;this.map.on("pointermove",t=>{if(t.dragging){this.hidePopup();return}const n=this.map.forEachFeatureAtPixel(t.pixel,o=>o.get("name")?o:null);n&&n!==e?(e=n,this.showPopup(n,t.coordinate)):!n&&e&&(e=null,this.hidePopup()),this.map.getTargetElement().style.cursor=n?"pointer":""}),this.map.getTargetElement().addEventListener("mouseleave",()=>{this.hidePopup(),e=null})}showPopup(e,t){const n=e.get("name")||"Unnamed",o=e.get("category")||"default",a=e.get("description"),s=e.get("lon"),i=e.get("lat");let c=`
${this.getEmoji(o)} ${this.escapeHtml(n)}
`;const u={water:"#3b82f6",school:"#f59e0b",health:"#ef4444",market:"#8b5cf6",default:"#2d5016",other:"#6b7280"}[o]||"#6b7280";c+=`
${o}
`,a&&(c+=`
${this.escapeHtml(a)}
`),s!==void 0&&i!==void 0&&(c+=`
${Number(s).toFixed(5)}, ${Number(i).toFixed(5)}
`),this.popupElement.innerHTML=c,this.popup.setPosition(t)}hidePopup(){this.popup.setPosition(void 0)}createInfoPopup(){this.infoPopupElement=document.createElement("div"),this.infoPopupElement.className="map-info-popup",this.infoPopupElement.style.cssText=` position: absolute; background: var(--card, #fff); color: var(--card-foreground, #1e1a4b); border-radius: 10px; padding: 0; box-shadow: 0 4px 16px rgba(0,0,0,0.3); font-family: var(--font-body, 'Exo', sans-serif); font-size: 13px; min-width: 220px; max-width: 320px; max-height: 70vh; display: flex; flex-direction: column; z-index: 1001; border: 1px solid var(--border, #1e1a4b1f); overflow: hidden; `,this.infoPopup=new ge({element:this.infoPopupElement,positioning:"bottom-center",offset:[0,-10],stopEvent:!0,autoPan:!0,autoPanAnimation:{duration:250}}),this.map.addOverlay(this.infoPopup)}showInfoPopup(e,t,n={}){const{title:o="Feature Info",color:a="#e11d48"}=n,s=e.getProperties(),i=e.getGeometry(),l=i.getType(),c=["geometry","_layerType"];let d="";for(const[p,h]of Object.entries(s))c.includes(p)||h===void 0||h===null||(d+=` ${this.escapeHtml(p)} ${this.escapeHtml(String(h))} `);if(l==="Polygon"||l==="MultiPolygon"){const p=Ue(i,{projection:"EPSG:3857"}),h=fr(p);d+=` area ${h} `}else if(l==="LineString"||l==="MultiLineString"){const p=st(i,{projection:"EPSG:3857"}),h=pr(p);d+=` length ${h} `}else if(l==="Point"){const p=xe(i.getCoordinates()),h=p[0].toFixed(6),f=p[1].toFixed(6);d+=` longitude ${h} latitude ${f} `}const u=`
${this.escapeHtml(o)}
${d}
`;this.infoPopupElement.innerHTML=u,this.infoPopup.setPosition(t),this.infoPopupElement.querySelector("#info-popup-close").addEventListener("click",()=>{this.hideInfoPopup()})}hideInfoPopup(){this.infoPopup.setPosition(void 0)}_collectIntersectionRows(e,t,n){const o=[];if(e.length>0&&o.push({label:"Parcels",value:String(e.length),color:"#0ea5e9"}),t.length>0){const a=t.map(s=>s.get("colzonename")||s.get("zone_name")||s.get("name")||"unnamed");o.push({label:"Zones",value:String(t.length),color:"#7c3aed"}),o.push({label:"Zone Names",value:a.map(s=>this.escapeHtml(s)).join(", "),color:"#7c3aed"})}for(const[a,s]of Object.entries(n))o.push({label:this.escapeHtml(a),value:`${s.length} feature(s)`});return o.length===0&&o.push({label:"",value:"No intersecting features found",empty:!0}),o}_buildAnalysisPopupHtml(e,t,n){let o="";for(const a of n){if(a.empty){o+=` ${a.value} `;continue}const s=a.color||"var(--muted-foreground, #7a7a7a)",i=a._first?"":"border-top:1px solid var(--border, #1e1a4b1f);";o+=` ${a.label} ${a.value} `}return`
${e} ${t}
${o}
`}_showAnalysisPopup(e,t,n,o){this.infoPopupElement.innerHTML=this._buildAnalysisPopupHtml(e,t,n),this.infoPopup.setPosition(o),this.infoPopupElement.querySelector("#info-popup-close").addEventListener("click",()=>{this.hideInfoPopup()}),this.infoPopupElement.querySelector("#info-popup-export-pdf")?.addEventListener("click",()=>{const a=n.filter(s=>!s.empty).map(s=>({label:s.label,value:s.value.replace(/<[^>]*>/g,"")}));ft(async()=>{const{exportAnalysisPDF:s}=await import("./pdf-export-vzOHm8wb.js");return{exportAnalysisPDF:s}},__vite__mapDeps([0,1,2,3])).then(({exportAnalysisPDF:s})=>{s({title:t,rows:a})}).catch(s=>{console.error("[MapView] PDF export failed:",s)})})}showCircleIntersectionPopup(e,t){const n=e.getGeometry();if(!n||typeof n.getCenter!="function")return;const o=hn(n,64),a=o.getExtent(),s=e.get("_radius")||n.getRadius(),i=[],l=[],c={},d=g=>{const m=g.getGeometry();if(!m)return!1;const y=m.getExtent();return y[2]a[2]||y[3]a[3]?!1:o.intersectsExtent(y)&&this._geometriesIntersect(o,m)},u=(g,m)=>{g.getLayers().forEach(y=>{if(y instanceof ve)u(y,y.get("title")||m);else if(y instanceof A&&y.getVisible()){const w=y.get("title")||m||"Unknown",S=y.getSource();if(!S)return;const x=S.getFeaturesInExtent(a);for(const v of x){const L=v.get("_layerType");L==="measure_circle"||L==="measure_circle_radius"||d(v)&&(L==="parcel"?i.push(v):L==="collector_zone"?l.push(v):(c[w]||(c[w]=[]),c[w].push(v)))}}})};u(this.overlayGroup,"Overlays");const p=ut(s),h=Math.PI*s*s,f=He(h),b=[{label:"Radius",value:p,_first:!0},{label:"Area",value:f},...this._collectIntersectionRows(i,l,c)];this._showAnalysisPopup("β­•","Circle Analysis",b,t)}showAreaIntersectionPopup(e,t){const n=e.getGeometry();if(!n)return;const o=n.getExtent(),a=Ue(n,{projection:"EPSG:3857"}),s=He(a),i=st(n,{projection:"EPSG:3857"}),l=ut(i),c=[],d=[],u={},p=b=>{const g=b.getGeometry();if(!g)return!1;const m=g.getExtent();return m[2]o[2]||m[3]o[3]?!1:n.intersectsExtent(m)&&this._geometriesIntersect(n,g)},h=(b,g)=>{b.getLayers().forEach(m=>{if(m instanceof ve)h(m,m.get("title")||g);else if(m instanceof A&&m.getVisible()){const y=m.get("title")||g||"Unknown",w=m.getSource();if(!w)return;const S=w.getFeaturesInExtent(o);for(const x of S){const v=x.get("_layerType");v==="measure_area"||v==="measure_circle"||v==="measure_circle_radius"||p(x)&&(v==="parcel"?c.push(x):v==="collector_zone"?d.push(x):(u[y]||(u[y]=[]),u[y].push(x)))}}})};h(this.overlayGroup,"Overlays");const f=[{label:"Area",value:s,_first:!0},{label:"Perimeter",value:l},...this._collectIntersectionRows(c,d,u)];this._showAnalysisPopup("πŸ“","Area Analysis",f,t)}_geometriesIntersect(e,t){const n=t.getType();if(n==="Polygon"||n==="MultiPolygon"){const o=t.getFlatCoordinates(),a=t.getStride();for(let l=0;l `}const s=`
✏️ Edit Parcel
${a}
`;this.parcelEditElement.innerHTML=s,this.parcelEditPopup.setPosition(t),this.parcelEditElement.querySelector(".parcel-edit-close").addEventListener("click",()=>{this.hideParcelEditPopup()}),this.parcelEditElement.querySelector(".parcel-edit-cancel").addEventListener("click",()=>{this.hideParcelEditPopup()});const i=this.parcelEditElement.querySelector(".parcel-edit-form");i.addEventListener("submit",l=>{l.preventDefault();const c=new FormData(i),d={};for(const[u,p]of c.entries())d[u]=p;d._layerType="parcel";for(const[u,p]of Object.entries(d))this._parcelEditFeature.set(u,p);for(const u of this._parcelEditCallbacks)u(this._parcelEditFeature,d);this.hideParcelEditPopup()})}hideParcelEditPopup(){this.parcelEditPopup.setPosition(void 0),this._parcelEditFeature=null}onParcelEdit(e){this._parcelEditCallbacks.push(e)}createMergePopup(){this.mergePopupElement=document.createElement("div"),this.mergePopupElement.className="map-merge-popup",this.mergePopupElement.style.cssText=` position: absolute; background: var(--card, #fff); color: var(--card-foreground, #1e1a4b); border-radius: 10px; box-shadow: 0 4px 20px rgba(0,0,0,0.3); font-family: var(--font-body, 'Exo', sans-serif); font-size: 13px; min-width: 280px; max-width: 360px; z-index: 1002; border: 2px solid #10b981; overflow: hidden; display: flex; flex-direction: column; `,this.mergePopup=new ge({element:this.mergePopupElement,positioning:"bottom-center",offset:[0,-10],stopEvent:!0,autoPan:!0,autoPanAnimation:{duration:250}}),this.map.addOverlay(this.mergePopup)}showMergeIdentifierPopup(e,t,n,o){const a=["UPN","upn","id","parcelid","parcel_id","PARCELID","PARCEL_ID","ID"],s=f=>{for(const b of a)if(f[b]!==void 0&&f[b]!==null&&String(f[b]).trim())return{field:b,value:String(f[b])};return{field:"id",value:"Unknown"}},i=s(t),l=s(n),c=`
πŸ”— Merged Parcel β€” Choose Identifier

Select which parcel's attributes the merged polygon should keep:

`;this.mergePopupElement.innerHTML=c,this.mergePopup.setPosition(o);const d=()=>{this.mergePopup.setPosition(void 0)};this.mergePopupElement.querySelector(".merge-popup-close").addEventListener("click",d),this.mergePopupElement.querySelector(".merge-popup-cancel").addEventListener("click",d),this.mergePopupElement.querySelector(".merge-popup-confirm").addEventListener("click",()=>{const b=this.mergePopupElement.querySelector('input[name="merge-choice"]:checked').value==="A"?t:n,g=["geometry"];for(const[m,y]of Object.entries(b))g.includes(m)||e.set(m,y);e.set("_layerType","parcel");for(const m of this._parcelEditCallbacks)m(e,b);d()});const u=this.mergePopupElement.querySelectorAll("label"),p=this.mergePopupElement.querySelectorAll('input[name="merge-choice"]'),h=()=>{u.forEach(f=>{const b=f.querySelector("input");f.style.borderColor=b.checked?b.value==="A"?"#0ea5e9":"#f59e0b":"var(--border, #1e1a4b1f)"})};p.forEach(f=>f.addEventListener("change",h)),h()}createDividePopup(){this.dividePopupElement=document.createElement("div"),this.dividePopupElement.className="map-divide-popup",this.dividePopupElement.style.cssText=` position: absolute; background: var(--card, #fff); color: var(--card-foreground, #1e1a4b); border-radius: 10px; box-shadow: 0 4px 20px rgba(0,0,0,0.3); font-family: var(--font-body, 'Exo', sans-serif); font-size: 13px; min-width: 260px; max-width: 320px; z-index: 1002; border: 2px solid #8b5cf6; overflow: hidden; display: flex; flex-direction: column; `,this.dividePopup=new ge({element:this.dividePopupElement,positioning:"bottom-center",offset:[0,-10],stopEvent:!0,autoPan:!0,autoPanAnimation:{duration:250}}),this.map.addOverlay(this.dividePopup)}showDividePopup(e,t,n){const o=`
Divide Polygon

Enter the number of equal pieces:

`;this.dividePopupElement.innerHTML=o,this.dividePopup.setPosition(n);const a=this.dividePopupElement.querySelector(".divide-input");a.focus(),a.select();const s=()=>{this.hideDividePopup(),this._polygonDivideInteraction.cancelDivide()};this.dividePopupElement.querySelector(".divide-popup-close").addEventListener("click",s),this.dividePopupElement.querySelector(".divide-popup-cancel").addEventListener("click",s),this.dividePopupElement.querySelector(".divide-popup-confirm").addEventListener("click",()=>{const i=parseInt(a.value,10);if(!i||i<2){a.style.borderColor="#ef4444";return}this.hideDividePopup(),this._polygonDivideInteraction.performDivide(i)}),a.addEventListener("keydown",i=>{i.key==="Enter"&&(i.preventDefault(),this.dividePopupElement.querySelector(".divide-popup-confirm").click())})}hideDividePopup(){this.dividePopup.setPosition(void 0)}createDrawnPolygonPopup(){this.drawnPolygonElement=document.createElement("div"),this.drawnPolygonElement.className="map-drawn-polygon-popup",this.drawnPolygonElement.style.cssText=` position: absolute; background: var(--card, #fff); border-radius: var(--radius-xl, 0.75rem); box-shadow: 0 4px 20px rgba(0,0,0,0.2); font-family: var(--font-body, 'Exo', sans-serif); font-size: 13px; min-width: 280px; max-width: 360px; max-height: 420px; z-index: 1002; border: 2px solid var(--success, #006b3f); overflow: hidden; display: flex; flex-direction: column; `,this.drawnPolygonPopup=new ge({element:this.drawnPolygonElement,positioning:"bottom-center",offset:[0,-10],stopEvent:!0,autoPan:!0,autoPanAnimation:{duration:250}}),this.map.addOverlay(this.drawnPolygonPopup),this._drawnPolygonCallbacks=[],this._drawnPolygonFeature=null}getParcelAttributeKeys(){const e=["geometry","_layerType"],t=[],n=o=>{t.length>0||o.getLayers().forEach(a=>{if(!(t.length>0)){if(a instanceof ve)n(a);else if(a instanceof A){const s=a.getSource();if(!s)return;for(const i of s.getFeatures()){if(i.get("_layerType")!=="parcel")continue;const l=i.getProperties();for(const c of Object.keys(l))e.includes(c)||t.push(c);return}}}})};return n(this.overlayGroup),t}showDrawnPolygonPopup(e,t){this._drawnPolygonFeature=e;const n=this.getParcelAttributeKeys();if(n.length===0){console.warn("[MapView] No parcel attributes found β€” cannot build form");return}let o="";for(const d of n){const u=this.escapeHtml(d);o+=`
`}const a=e.getGeometry(),s=Ue(a,{projection:"EPSG:3857"}),l=`
πŸ“ Polygon Attributes
Area: ${He(s)}
${o}
`;this.drawnPolygonElement.innerHTML=l,this.drawnPolygonPopup.setPosition(t),this.drawnPolygonElement.querySelector(".drawn-polygon-close").addEventListener("click",()=>{this.hideDrawnPolygonPopup()}),this.drawnPolygonElement.querySelector(".drawn-polygon-cancel").addEventListener("click",()=>{this.hideDrawnPolygonPopup()});const c=this.drawnPolygonElement.querySelector(".drawn-polygon-form");c.addEventListener("submit",d=>{d.preventDefault();const u=new FormData(c),p={};for(const[h,f]of u.entries())p[h]=f;for(const[h,f]of Object.entries(p))this._drawnPolygonFeature.set(h,f);this._drawnPolygonFeature.set("_layerType","parcel");for(const h of this._drawnPolygonCallbacks)h(this._drawnPolygonFeature,p);this.hideDrawnPolygonPopup()})}hideDrawnPolygonPopup(){this.drawnPolygonPopup.setPosition(void 0),this._drawnPolygonFeature=null}onDrawnPolygonSave(e){this._drawnPolygonCallbacks.push(e)}onDblClick(e){return this.dblClickCallbacks.push(e),this.dblClickCallbacks.length===1&&this.map.on("dblclick",t=>{const[n,o]=xe(t.coordinate);let a=null;this.map.forEachFeatureAtPixel(t.pixel,s=>(a=s,!0)),a&&(t.preventDefault(),t.stopPropagation());for(const s of this.dblClickCallbacks)s(n,o,a,t);if(a)return!1}),()=>{const t=this.dblClickCallbacks.indexOf(e);t>-1&&this.dblClickCallbacks.splice(t,1)}}escapeHtml(e){if(!e)return"";const t=document.createElement("div");return t.textContent=e,t.innerHTML}createAddLocationPopup(){this.addLocationPopupElement=document.createElement("div"),this.addLocationPopupElement.className="map-add-location-popup",this.addLocationPopupElement.innerHTML=`
βž• Add Location
πŸ“
`,this.addLocationPopup=new ge({element:this.addLocationPopupElement,positioning:"bottom-center",offset:[0,-10],stopEvent:!0,autoPan:!0,autoPanAnimation:{duration:250}}),this.map.addOverlay(this.addLocationPopup),this.addLocationCoords=null,this.addLocationPopupElement.querySelector(".add-location-popup-close").addEventListener("click",()=>{this.hideAddLocationPopup()}),this.addLocationCallbacks=[]}showAddLocationPopup(e){const[t,n]=xe(e);this.addLocationCoords={lon:t,lat:n};const o=this.addLocationPopupElement.querySelector("#map-location-coords");o.textContent=`${t.toFixed(6)}, ${n.toFixed(6)}`,this.addLocationPopupElement.querySelector("#map-add-location-form").reset(),this.addLocationPopup.setPosition(e)}hideAddLocationPopup(){this.addLocationPopup.setPosition(void 0),this.addLocationCoords=null}onAddLocation(e){if(this.addLocationCallbacks.push(e),this.addLocationCallbacks.length===1){const t=this.addLocationPopupElement.querySelector("#map-add-location-form");t.addEventListener("submit",n=>{if(n.preventDefault(),!this.addLocationCoords)return;const o=new FormData(t),a={name:o.get("name"),category:o.get("category"),description:o.get("description"),lon:this.addLocationCoords.lon,lat:this.addLocationCoords.lat};this.addLocationCallbacks.forEach(s=>s(a)),this.hideAddLocationPopup()})}}createBaseLayers(e){const t=new ee({title:"Topographic",type:"base",zIndex:-100,visible:e==="topo",source:new _e({url:"https://{a-c}.tile.opentopomap.org/{z}/{x}/{y}.png",attributions:"Map data: Β© OpenTopoMap",maxZoom:17,crossOrigin:"anonymous"})});t.set("basemapKey","topo");const n=new ee({title:"Carto Light",type:"base",zIndex:-100,visible:e==="carto-light",source:new _e({url:"https://{a-c}.basemaps.cartocdn.com/light_all/{z}/{x}/{y}.png",attributions:"Β© CARTO",maxZoom:19,crossOrigin:"anonymous"})});n.set("basemapKey","carto-light");const o=new ee({title:"Carto Dark",type:"base",zIndex:-100,visible:e==="carto-dark",source:new _e({url:"https://{a-c}.basemaps.cartocdn.com/dark_all/{z}/{x}/{y}.png",attributions:"Β© CARTO",maxZoom:19,crossOrigin:"anonymous"})});o.set("basemapKey","carto-dark");const a=new ee({title:"OSM Cycle map",type:"base",zIndex:-100,visible:!1,source:new Vt({url:"https://tile.thunderforest.com/cycle/{z}/{x}/{y}.png?apikey=ae1339c46dd3446b9c491e7336d38760"})});a.set("basemapKey","cycle");const s=new ee({title:"Satellite",type:"base",zIndex:-100,visible:e==="satellite",source:new _e({url:"https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}",attributions:"Tiles Β© Esri",maxZoom:19,crossOrigin:"anonymous"})});s.set("basemapKey","satellite");const i=new ee({title:"Google Sat",type:"base",zIndex:-100,visible:e==="googlesat",source:new _e({url:"http://mt0.google.com/vt/lyrs=y&hl=en&x={x}&y={y}&z={z}&s=Ga",attributions:"Tiles Β© Google",maxZoom:19,crossOrigin:"anonymous"})});i.set("basemapKey","googlesat");const l=new ee({title:"OpenStreetMap",type:"base",zIndex:-100,visible:e==="osm",source:new Vt});l.set("basemapKey","osm"),this._baseMapLayers=[n,o,a,s,i,l,t];const c=new ve({title:"Base Maps",layers:[n,o,s,a,i,l,t]});return c.set("displayInLayerSwitcher",!1),c}setBaseMap(e){if(!this._baseMapLayers)return!1;if(e==="none"){for(const n of this._baseMapLayers)n.setVisible(!1);return console.log("[MapView] Base map switched off (none)"),this.map.dispatchEvent({type:"basemapchange",key:"none"}),!0}let t=!1;for(const n of this._baseMapLayers){const o=n.get("basemapKey")===e;n.setVisible(o),o&&(t=!0)}return t&&(console.log("[MapView] Base map switched to:",e),this.map.dispatchEvent({type:"basemapchange",key:e})),t}_createBaseMapPicker(){const e=[{key:"topo",label:"Topographic",grad:"linear-gradient(135deg,#e8d5b7,#a67c52)"},{key:"osm",label:"OpenStreetMap",grad:"linear-gradient(135deg,#d4e6f1,#85c1e9)"},{key:"satellite",label:"Satellite",grad:"linear-gradient(135deg,#1b4332,#40916c)"},{key:"googlesat",label:"Google Sat",grad:"linear-gradient(135deg,#2a5d3d,#4a8c5a)"},{key:"carto-light",label:"Carto Light",grad:"linear-gradient(135deg,#f5f5f5,#d4d4d4)"},{key:"carto-dark",label:"Carto Dark",grad:"linear-gradient(135deg,#1a1a2e,#0f3460)"},{key:"none",label:"None",grad:"repeating-conic-gradient(#e5e7eb 0 25%, #fff 0 50%) 50% / 12px 12px"}],t=this.map.getTargetElement();if(!t)return;const n=document.createElement("button");n.type="button",n.className="ls-basemap-toggle",n.title="Switch base map",n.setAttribute("aria-label","Switch base map"),n.innerHTML='',t.appendChild(n);const o=document.createElement("div");o.className="ls-basemap-panel",o.innerHTML='
Base Map
'+e.map(s=>` `).join("")+"
",t.appendChild(o),this._basemapPanel=o,this._basemapToggle=n;const a=s=>{const i=s||this._baseMapLayers?.find(l=>l.getVisible())?.get("basemapKey");o.querySelectorAll('input[name="lupmis-basemap"]').forEach(l=>{l.checked=l.value===i})};a(),n.addEventListener("click",s=>{s.stopPropagation();const i=!o.classList.contains("open");o.classList.toggle("open",i),n.classList.toggle("active",i),i&&a()}),document.addEventListener("click",s=>{o.classList.contains("open")&&(o.contains(s.target)||n.contains(s.target)||(o.classList.remove("open"),n.classList.remove("active")))}),o.addEventListener("change",s=>{const i=s.target.closest('input[type=radio][name="lupmis-basemap"]');if(!i)return;const l=i.value;this.setBaseMap(l);try{localStorage.setItem("default-basemap",l)}catch{}o.classList.remove("open"),n.classList.remove("active")}),this.map.on("basemapchange",s=>a(s.key))}_initGpsRendering(){this._gpsPositionSource=new R,this._gpsTrailSource=new R,this._gpsTrailCoords=[],this._gpsTrailLayer=new A({source:this._gpsTrailSource,zIndex:940,style:new M({stroke:new k({color:"#ff6d00",width:4,lineCap:"round",lineJoin:"round"})}),properties:{title:"GPS Trail",displayInLayerSwitcher:!1}}),this._gpsPositionLayer=new A({source:this._gpsPositionSource,zIndex:950,style:e=>e.get("_kind")==="accuracy"?new M({fill:new I({color:"rgba(0,94,184,0.12)"}),stroke:new k({color:"rgba(0,94,184,0.35)",width:1})}):new M({image:new ae({radius:7,fill:new I({color:"#005eb8"}),stroke:new k({color:"#ffffff",width:2.5})})}),properties:{title:"GPS Position",displayInLayerSwitcher:!1}}),this.map.addLayer(this._gpsTrailLayer),this.map.addLayer(this._gpsPositionLayer),this._gpsCallbacks={locate:[],record:[]},this._gpsRecording=!1}onLocateMe(e){this._gpsCallbacks.locate.push(e)}onToggleRecording(e){this._gpsCallbacks.record.push(e)}showCurrentPosition(e,t,n=null){if(e==null||t==null)return;const o=X([e,t]);if(this._gpsPositionSource.clear(),n&&n>0){const s=n/Math.cos(t*Math.PI/180),i=new se({geometry:new Ke([this._circleRing(o,s)])});i.set("_kind","accuracy"),this._gpsPositionSource.addFeature(i)}const a=new se({geometry:new Xe(o)});a.set("_kind","dot"),this._gpsPositionSource.addFeature(a)}_circleRing(e,t,n=48){const o=[],s=t/1;for(let i=0;i<=n;i++){const l=i/n*2*Math.PI;o.push([e[0]+s*Math.cos(l),e[1]+s*Math.sin(l)])}return o}centerOn(e,t,n=16){this.map.getView().animate({center:X([e,t]),zoom:n,duration:500})}startTrailRender(){this._gpsTrailCoords=[],this._gpsTrailSource.clear()}appendTrailPoint(e,t){e==null||t==null||(this._gpsTrailCoords.push(X([e,t])),this._gpsTrailSource.clear(),this._gpsTrailCoords.length>=2&&this._gpsTrailSource.addFeature(new se({geometry:new ie(this._gpsTrailCoords)})))}clearTrailRender(){this._gpsTrailCoords=[],this._gpsTrailSource.clear()}setRecordingState(e){this._gpsRecording=!!e,this._recordBtn&&(this._recordBtn.classList.toggle("recording",this._gpsRecording),this._recordBtn.title=this._gpsRecording?"Stop trail recording":"Record GPS trail",this._recordBtn.innerHTML=this._gpsRecording?'':''),this._locateToggle&&this._locateToggle.classList.toggle("recording",this._gpsRecording)}_createLocationControl(){const e=this.map.getTargetElement();if(!e)return;const t=document.createElement("button");t.type="button",t.className="ls-locate-toggle",t.title="My Location",t.setAttribute("aria-label","My Location"),t.innerHTML='',e.appendChild(t);const n=document.createElement("div");n.className="ls-locate-actions",n.innerHTML='',e.appendChild(n),this._locateToggle=t,this._locateActions=n,this._locateMeBtn=n.querySelector(".ls-locate-me"),this._recordBtn=n.querySelector(".ls-locate-record");const o=()=>{n.classList.remove("open"),t.classList.remove("active")},a=()=>{n.classList.add("open"),t.classList.add("active")};t.addEventListener("click",s=>{s.stopPropagation(),n.classList.contains("open")?o():a()}),document.addEventListener("click",s=>{n.classList.contains("open")&&(n.contains(s.target)||t.contains(s.target)||this._gpsRecording||o())}),this._locateMeBtn.addEventListener("click",s=>{s.stopPropagation();for(const i of this._gpsCallbacks.locate)try{i()}catch(l){console.error(l)}this._gpsRecording||o()}),this._recordBtn.addEventListener("click",s=>{s.stopPropagation();const i=!this._gpsRecording;for(const l of this._gpsCallbacks.record)try{l(i)}catch(c){console.error(c)}})}getFeatureStyle(e){const t=e.get("category")||"default",n=this.getEmoji(t);if(e===this.selectedFeature)return[new M({image:new ae({radius:22,fill:new I({color:"rgba(220, 38, 38, 0.25)"}),stroke:new k({color:"#dc2626",width:3})})}),new M({text:new rt({text:n,font:"40px sans-serif",textBaseline:"bottom",textAlign:"center",offsetY:-5})})];const o=e.get("style");return o||(this.categoryStyles[t]?this.categoryStyles[t]:this.defaultStyle)}setCategoryStyles(e){for(const[t,n]of Object.entries(e)){n.emoji&&(this.categoryEmojis[t]?(this.categoryEmojis[t].emoji=n.emoji,n.label&&(this.categoryEmojis[t].label=n.label)):this.categoryEmojis[t]={emoji:n.emoji,label:n.label||t});const o=this.getEmoji(t),a=n.fontSize||28;this.categoryStyles[t]=this.createEmojiStyle(o,a)}this.markerSource.changed()}addMarker(e,t,n={}){console.log("[MapView] Adding marker at",e,t,"with properties:",n);const o=new se({geometry:new Xe(X([e,t])),...n});return o.set("lon",e),o.set("lat",t),this.markerSource.addFeature(o),console.log("[MapView] Marker added, total features:",this.markerSource.getFeatures().length),o}addMarkers(e){console.log("[MapView] Adding",e.length,"markers");const t=e.map(n=>new se({geometry:new Xe(X([n.longitude,n.latitude])),id:n.id,name:n.name,description:n.description,category:n.category,lon:n.longitude,lat:n.latitude}));return this.markerSource.addFeatures(t),console.log("[MapView] Markers added, total features:",this.markerSource.getFeatures().length),t}clearMarkers(){this.markerSource.clear(),this.selectedFeature=null}removeMarker(e){if(typeof e=="object")this.markerSource.removeFeature(e);else{const t=this.markerSource.getFeatures().find(n=>n.get("id")===e);t&&this.markerSource.removeFeature(t)}}getMarkers(){return this.markerSource.getFeatures()}findMarker(e){return this.markerSource.getFeatures().find(t=>t.get("id")===e)}selectMarker(e){return typeof e=="object"?this.selectedFeature=e:this.selectedFeature=this.findMarker(e),this.markerSource.changed(),this.selectedFeature}clearSelection(){this.selectedFeature=null,this.markerSource.changed()}zoomTo(e,t,n=15){this.map.getView().animate({center:X([e,t]),zoom:n,duration:500})}fitToMarkers(e=50){const t=this.markerSource.getExtent();t&&t[0]!==1/0&&this.map.getView().fit(t,{padding:[e,e,e,e],duration:500,maxZoom:16})}getCenter(){const e=this.map.getView().getCenter();return xe(e)}getZoom(){return this.map.getView().getZoom()}setCenter(e,t){this.map.getView().setCenter(X([e,t]))}setZoom(e){this.map.getView().setZoom(e)}onClick(e){return this.clickCallbacks.push(e),this.clickCallbacks.length===1&&(this._clickTimer=null,this.map.on("dblclick",()=>{this._clickTimer&&(clearTimeout(this._clickTimer),this._clickTimer=null)}),this.map.on("click",t=>{this._clickTimer&&(clearTimeout(this._clickTimer),this._clickTimer=null),!this._editBarActive&&this._selectInteraction&&this._selectInteraction.getFeatures().clear();let n=!1,o=!1,a=null;if(this.map.forEachFeatureAtPixel(t.pixel,l=>{l.get("_layerType")==="parcel"&&(o=!0),l.get("name")&&(a=l),n=!0}),n&&!o&&!a)return;const[s,i]=xe(t.coordinate);this._clickTimer=setTimeout(()=>{this._clickTimer=null;let l=null;this.map.forEachFeatureAtPixel(t.pixel,c=>{if(c.get("name"))return l=c,!0});for(const c of this.clickCallbacks)c(s,i,l,t)},300)})),()=>{const t=this.clickCallbacks.indexOf(e);t>-1&&this.clickCallbacks.splice(t,1)}}onPointerMove(e){this.map.on("pointermove",t=>{if(t.dragging)return;const[n,o]=xe(t.coordinate);let a=null;this.map.forEachFeatureAtPixel(t.pixel,s=>{if(s.get("name"))return a=s,!0}),this.map.getTargetElement().style.cursor=a?"pointer":"",e(n,o,a,t)})}enableHoverCursor(){}addGeoJSONLayer(e,t,n={},o=null){const{strokeColor:a="#3b82f6",strokeWidth:s=2,fillColor:i="rgba(59,130,246,0.1)",lineCasingColor:l=null,lineCasingWidth:c=null,pointRadius:d=5,pointFillColor:u=null,pointStrokeColor:p="#ffffff",pointStrokeWidth:h=1.5}=n,f=new R({features:new ce().readFeatures(e,{featureProjection:"EPSG:3857"})}),b=new I({color:i}),g=new ae({radius:d,fill:new I({color:u||a}),stroke:new k({color:p,width:h})});let m;if(l){const x=c??s+2;m=[new M({stroke:new k({color:l,width:x})}),new M({stroke:new k({color:a,width:s}),fill:b,image:g})]}else m=new M({stroke:new k({color:a,width:s}),fill:b,image:g});const y=new A({title:t,source:f,style:m});y.set("typeTag",n.typeTag||"VEC");const w=x=>x?x.includes("Polygon")?"Vector / Polygon":x.includes("LineString")?"Vector / Line":x.includes("Point")?"Vector / Point":"Vector":null;if(n.typeDescription)y.set("typeDescription",n.typeDescription);else{const x=f.getFeatures(),v=w(x[0]?.getGeometry?.()?.getType?.());if(v)y.set("typeDescription",v);else{const L=P=>{const T=w(P.feature.getGeometry?.()?.getType?.());T&&y.set("typeDescription",T),f.un("addfeature",L)};f.on("addfeature",L)}}return(o||this.overlayGroup).getLayers().push(y),console.log("[MapView] GeoJSON layer added:",t,"β†’",f.getFeatures().length,"features",o?`(in group "${o.get("title")}")`:""),y}addLayerGroup(e,t,n=""){const o=new ve({title:t.trim()});return o.set("layerId",e),o.set("description",n),this.overlayGroup.getLayers().push(o),console.log("[MapView] Layer group added:",t.trim(),"(id:",e+")"),o}addWMSLayer(e,t,n,o,a={}){const s=this.getLayerGroupByTitle(e);if(!s)return console.warn(`[MapView] Layer group "${e}" not found β€” cannot add WMS layer "${t}"`),null;const i={LAYERS:o,TILED:!0,WIDTH:256,HEIGHT:256};a.style!==void 0&&(i.STYLES=a.style);const l=new Xt({url:n,params:i,serverType:a.serverType!==void 0?a.serverType:"geoserver",crossOrigin:"anonymous",hidpi:!1,attributions:a.attributions}),c=new ee({title:t,visible:a.visible!==void 0?a.visible:!0,source:l,opacity:a.opacity!==void 0?a.opacity:1,zIndex:a.zIndex});if(c.set("typeTag","WMS"),c.set("typeDescription","WMS / Raster"),l.on("tileloaderror",()=>{O(`WMS layer "${t}" β€” tile load error. Check the URL and layer name.`,"warning",5e3)}),s.getLayers().push(c),a.legendUrl)try{this._registerLegend(c,t,a.legendUrl)}catch(d){console.warn(`[MapView] Could not register legend for "${t}":`,d)}return a.onlineOnly&&this._attachOnlineOnlyHandler(c,t),console.log(`[MapView] WMS layer added: "${t}" β†’ group "${e}"`),c}addXYZLayer(e,t,n,o={}){const a=this.getLayerGroupByTitle(e);if(!a)return console.warn(`[MapView] Layer group "${e}" not found β€” cannot add XYZ layer "${t}"`),null;const s=new _e({url:n,crossOrigin:"anonymous",maxZoom:o.maxZoom!==void 0?o.maxZoom:19,attributions:o.attributions}),i=new ee({title:t,visible:o.visible!==void 0?o.visible:!0,source:s,opacity:o.opacity!==void 0?o.opacity:1,zIndex:o.zIndex});if(i.set("typeTag","XYZ"),i.set("typeDescription","XYZ / Tile"),s.on("tileloaderror",()=>{O(`XYZ layer "${t}" β€” tile load error. Check the URL.`,"warning",5e3)}),a.getLayers().push(i),o.legendUrl)try{this._registerLegend(i,t,o.legendUrl)}catch(l){console.warn(`[MapView] Could not register legend for "${t}":`,l)}return o.onlineOnly&&this._attachOnlineOnlyHandler(i,t),console.log(`[MapView] XYZ layer added: "${t}" β†’ group "${e}"`),i}_createAddLayerDialog(){this._addLayerDialog=document.createElement("div"),this._addLayerDialog.className="map-add-layer-dialog",this._addLayerDialog.style.cssText=` display:none;position:absolute;top:0;left:0;right:0;bottom:0; z-index:1100;background:rgba(0,0,0,0.4); align-items:center;justify-content:center; `;const e=document.createElement("div");e.style.cssText=` background:var(--card, #fff);color:var(--card-foreground, #1e1a4b); border-radius:12px;box-shadow:0 8px 30px rgba(0,0,0,0.35); font-family:var(--font-body, 'Exo', sans-serif);font-size:13px; width:340px;max-width:90vw;border:2px solid #10b981;overflow:hidden; `,e.innerHTML=`
Add External Layer
WMS LAYERS parameter (e.g. workspace:layer)
`,this._addLayerDialog.appendChild(e),this.map.getTargetElement().appendChild(this._addLayerDialog);const t=e.querySelector(".add-layer-name-row"),n=e.querySelector(".add-layer-name-hint"),o=e.querySelector(".add-layer-url");e.querySelectorAll('input[name="add-layer-type"]').forEach(s=>{s.addEventListener("change",()=>{const i=s.value;i==="xyz"?(t.style.display="none",o.placeholder="https://example.com/tiles/{z}/{x}/{y}.png"):(t.style.display="",o.placeholder=i==="wms"?"https://example.com/wms":"https://example.com/wfs",n.textContent=i==="wms"?"WMS LAYERS parameter (e.g. workspace:layer)":"WFS typename (e.g. workspace:layer)")})});const a=()=>this._hideAddLayerDialog();e.querySelector(".add-layer-close").addEventListener("click",a),e.querySelector(".add-layer-cancel").addEventListener("click",a),this._addLayerDialog.addEventListener("click",s=>{s.target===this._addLayerDialog&&a()}),e.querySelector(".add-layer-confirm").addEventListener("click",()=>{const s=e.querySelector('input[name="add-layer-type"]:checked').value,i=e.querySelector(".add-layer-url").value.trim(),l=e.querySelector(".add-layer-name").value.trim(),c=e.querySelector(".add-layer-title").value.trim();if(!i){e.querySelector(".add-layer-url").style.borderColor="#ef4444";return}if((s==="wms"||s==="wfs")&&!l){e.querySelector(".add-layer-name").style.borderColor="#ef4444";return}if(!c){e.querySelector(".add-layer-title").style.borderColor="#ef4444";return}this._addExternalLayer(s,i,l,c),this._hideAddLayerDialog()}),e.addEventListener("keydown",s=>{s.key==="Enter"&&(s.preventDefault(),e.querySelector(".add-layer-confirm").click()),s.key==="Escape"&&(s.preventDefault(),a())})}showAddLayerDialog(){const e=this._addLayerDialog;e.querySelector(".add-layer-url").value="",e.querySelector(".add-layer-name").value="",e.querySelector(".add-layer-title").value="",e.querySelectorAll('input[name="add-layer-type"]')[0].checked=!0,e.querySelector(".add-layer-name-row").style.display="",e.querySelector(".add-layer-url").placeholder="https://example.com/wms",e.querySelector(".add-layer-name-hint").textContent="WMS LAYERS parameter (e.g. workspace:layer)",e.querySelectorAll('input[type="text"]').forEach(t=>{t.style.borderColor="var(--border, #1e1a4b1f)"}),e.style.display="flex",e.querySelector(".add-layer-url").focus()}_hideAddLayerDialog(){this._addLayerDialog.style.display="none"}_addExternalLayer(e,t,n,o){const a=this._externalSourceGroup;if(!a){O('Layer group "External Source" not found.',"error",4e3);return}let s;switch(e){case"wms":{const i=new Xt({url:t,params:{LAYERS:n,TILED:!0,WIDTH:256,HEIGHT:256},serverType:"geoserver",crossOrigin:"anonymous",hidpi:!1});s=new ee({title:o,visible:!0,source:i}),i.on("tileloaderror",()=>{O(`WMS "${o}" β€” tile load error. Check URL and layer name.`,"warning",5e3)});break}case"wfs":{const i=`${t}${t.includes("?")?"&":"?"}service=WFS&version=1.1.0&request=GetFeature&typename=${encodeURIComponent(n)}&outputFormat=application/json&srsname=EPSG:3857`,l=new R({url:i,format:new ce});l.on("featuresloaderror",()=>{O(`WFS "${o}" β€” load error. Check URL and layer name.`,"warning",5e3)}),s=new A({title:o,visible:!0,source:l,style:new M({stroke:new k({color:"#e11d48",width:2}),fill:new I({color:"rgba(225,29,72,0.15)"})})});break}case"xyz":s=new ee({title:o,visible:!0,source:new _e({url:t,crossOrigin:"anonymous"})}),s.getSource().on("tileloaderror",()=>{O(`XYZ "${o}" β€” tile load error. Check the URL template.`,"warning",5e3)});break;default:O(`Unknown layer type: ${e}`,"error",4e3);return}s.set("typeTag",e.toUpperCase()),s.set("typeDescription",{wms:"WMS / Raster",wfs:"WFS / Vector",xyz:"XYZ / Tile"}[e]||e.toUpperCase()),s.set("removable",!0),a.getLayers().push(s),O(`Layer "${o}" added to External Source.`,"success",3e3),console.log(`[MapView] External ${e.toUpperCase()} layer added: "${o}"`)}_decorateLayerListItem(e,t){const n=e.get("typeTag");if(n){const l=t.querySelector(":scope > .li-content > label > span");if(l&&!l.querySelector(":scope > .ls-type-tag")){const c=document.createElement("span");c.className=`ls-type-tag ls-type-tag-${String(n).toLowerCase()}`,c.textContent=String(n),c.title=`${n} layer`,l.appendChild(c)}}const o=t.querySelector(":scope > .ol-layerswitcher-buttons");if(o){const l=o.querySelector(":scope > .expend-layers, :scope > .collapse-layers");l&&!l.querySelector(":scope > svg.ls-chevron-svg")&&(l.innerHTML='')}const a=t.querySelector(":scope > .li-content"),s=()=>{if(!a)return;const l=e.get("typeDescription");let c=a.querySelector(":scope > .ls-layer-subtitle");if(!l){c&&c.remove();return}if(!c){c=document.createElement("div"),c.className="ls-layer-subtitle";const d=a.querySelector(":scope > label");d&&d.nextSibling?a.insertBefore(c,d.nextSibling):a.appendChild(c)}c.textContent=l};if(s(),e._lsSubtitleHooked||(e._lsSubtitleHooked=!0,e.on("change:typeDescription",()=>{s()})),e.get("removable")===!0&&o&&!o.querySelector(":scope > .ls-remove-btn")){const l=document.createElement("button");l.type="button",l.className="ls-remove-btn",l.title="Remove this layer",l.setAttribute("aria-label","Remove layer"),l.innerHTML='',l.addEventListener("click",c=>{c.stopPropagation(),this._removeLayer(e)}),o.appendChild(l)}if((e.get("title")||"").toLowerCase().includes("external")&&(this._externalSourceGroup=e,o&&!o.querySelector(".ol-add-layer"))){const l=document.createElement("span");l.className="ol-add-layer",l.title="Add external layer",l.textContent="+",l.style.cssText=` display:inline-flex !important;align-items:center;justify-content:center; width:22px !important;height:22px !important;border-radius:50%; background:#41b6a6 !important;color:#fff !important; font-size:15px !important;font-weight:700; cursor:pointer;line-height:1 !important; margin:0 4px 0 0;vertical-align:middle; transition:background 0.2s;box-sizing:border-box;border:none; `,l.addEventListener("mouseenter",()=>{l.style.background="#329686"}),l.addEventListener("mouseleave",()=>{l.style.background="#41b6a6"}),l.addEventListener("click",c=>{c.stopPropagation(),this.showAddLayerDialog()}),o.prepend(l)}}_removeLayer(e){const t=e.get("title")||"this layer";if(!confirm(`Remove "${t}" from the map? This only affects the current session β€” built-in layers cannot be removed.`))return;const n=a=>{const s=a.getLayers();if(s.getArray().includes(e))return s.remove(e),!0;let i=!1;return s.forEach(l=>{!i&&l.getLayers&&(i=n(l))}),i};n(this.overlayGroup)?(console.log(`[MapView] Removed layer "${t}"`),O(`Removed "${t}" from the map.`,"info",3e3)):console.warn(`[MapView] Could not find layer "${t}" in any group`)}_refreshLayerSwitcherChrome(e){const t=e.element?.querySelector(".panel-container"),n=e.element?.querySelector("ul.panel");if(!t||!n)return;let o=t.querySelector(":scope > .ls-active-badge");o||(o=document.createElement("div"),o.className="ls-active-badge",o.innerHTML=` Layers 0 active `,t.insertBefore(o,n));let a=t.querySelector(":scope > .ls-footer-row");a||(a=document.createElement("div"),a.className="ls-footer-row",a.innerHTML=` β€” layers total `,t.appendChild(a),a.querySelector(".ls-footer-btn").addEventListener("click",i=>{i.stopPropagation(),this._resetAllOverlays()}));const s=this._countLayers();o.querySelector(".ls-active-badge-count").textContent=`${s.activeOverlays} active`,a.querySelector(".ls-footer-note").textContent=`${s.totalOverlays} overlay${s.totalOverlays===1?"":"s"}`}_countLayers(){let e=0,t=0;const n=new Set(["__vertex_highlight__"]),o=a=>{a.getLayers().forEach(s=>{s.get("displayInLayerSwitcher")!==!1&&(n.has(s.get("title"))||(s.getLayers?o(s):(e++,s.getVisible()&&t++)))})};return this.overlayGroup&&o(this.overlayGroup),{totalOverlays:e,activeOverlays:t}}_resetAllOverlays(){const e=new Set(["__vertex_highlight__"]),t=n=>{n.getLayers().forEach(o=>{o.get("displayInLayerSwitcher")!==!1&&(e.has(o.get("title"))||(o.getLayers?t(o):o.setVisible(!1)))})};this.overlayGroup&&t(this.overlayGroup),console.log("[MapView] Reset overlays β€” all hidden")}_wireLayerSwitcherVisibilityHooks(e){const t=()=>this._refreshLayerSwitcherChrome(e),n=a=>{a._lsVisHooked||(a._lsVisHooked=!0,a.on("change:visible",t))},o=a=>{a.getLayers().forEach(s=>{s.getLayers?(o(s),a._lsAddHooked||(a._lsAddHooked=!0,a.getLayers().on("add",i=>{const l=i.element;l.getLayers?o(l):n(l),t()}))):n(s)})};this.overlayGroup&&o(this.overlayGroup)}_attachOnlineOnlyHandler(e,t){e.set("onlineOnly",!0),e.on("change:visible",()=>{e.getVisible()&&!navigator.onLine&&O(`"${t}" requires an internet connection. Connect to view this layer.`,"info",5e3)})}_createLegendPanel(){this._legendPanel=document.createElement("div"),this._legendPanel.className="map-legend-panel",this._legendPanel.style.cssText=` position:absolute;right:10px;bottom:40px;z-index:900; display:none;flex-direction:column;gap:6px; background:var(--card, #fff);color:var(--card-foreground, #1e1a4b); border:1px solid var(--border, #1e1a4b1f);border-radius:8px; box-shadow:0 4px 12px rgba(0,0,0,0.15); font-family:var(--font-body, 'Exo', sans-serif);font-size:11px; max-width:220px;max-height:60%;overflow-y:auto; padding:8px 10px; `,this.map.getTargetElement().appendChild(this._legendPanel),this._legendEntries=new Kt}_registerLegend(e,t,n){if(!this._legendPanel)return;const o=document.createElement("div");o.className="map-legend-entry",o.style.cssText="border-bottom:1px solid var(--border, #1e1a4b1f);padding-bottom:6px;",o.innerHTML=`
${this._escapeHtml(t)}
${this._escapeHtml(t)} legend `,this._legendEntries.set(e,o);const a=()=>{try{this._updateLegendPanel()}catch(s){console.warn("[MapView] legend panel update failed:",s)}};e.on("change:visible",a),a()}_updateLegendPanel(){if(!this._legendPanel)return;const e=[];for(const[t,n]of this._legendEntries)t.getVisible()&&e.push(n);this._legendEntries.forEach(t=>{t.style.borderBottom="1px solid var(--border, #1e1a4b1f)",t.style.paddingBottom="6px"}),e.length>0&&(e[e.length-1].style.borderBottom="none",e[e.length-1].style.paddingBottom="0"),this._legendPanel.replaceChildren(...e),this._legendPanel.style.display=e.length>0?"flex":"none"}_escapeHtml(e){return String(e).replace(/&/g,"&").replace(//g,">").replace(/"/g,""").replace(/'/g,"'")}getLayerGroup(e){let t=null;return this.overlayGroup.getLayers().forEach(n=>{n.get("layerId")===e&&(t=n)}),t}getLayerGroupByTitle(e){let t=null;return this.overlayGroup.getLayers().forEach(n=>{n.get("title")===e&&(t=n)}),t}getOverlayGroup(){return this.overlayGroup}getMap(){return this.map}getCurrentViewExtent(){const e=this.map.getView(),t=this.map.getSize();return t?e.calculateExtent(t):null}getDistrictBoundaryExtent(){let e=null;const t=n=>{n.getLayers().forEach(o=>{if(o.getLayers)t(o);else if(o.get("title")==="District Boundary"){const a=o.getSource&&o.getSource();if(a&&typeof a.getExtent=="function"){const s=a.getExtent();s&&Number.isFinite(s[0])&&(e={extent:s,title:o.get("title")})}}})};return t(this.overlayGroup),e}getMarkerSource(){return this.markerSource}getMarkersLayer(){return this.markersLayer}updateSize(){this.map.updateSize()}onSearchSelect(e){this.searchSelectCallbacks.push(e)}navigateTo(e,t,n=14,o=500){const a=X([e,t]);this.map.getView().animate({center:a,zoom:n,duration:o})}}class jr{constructor(e,t={}){this.map=e,this.options=t,this.measureSource=new R,this.measureLayer=new A({source:this.measureSource,style:this.getMeasureStyle(),title:"Measurements",zIndex:100}),this.drawSource=new R,this.drawLayer=new A({source:this.drawSource,style:this.getDrawStyle(),title:"Draw sketches",displayInLayerSwitcher:!1,zIndex:99});const n=this.map.getLayers();let o=n.getArray().findIndex(a=>a.get("title")==="Overlays");o<0&&(o=n.getLength()),n.insertAt(o,this.drawLayer),n.insertAt(o,this.measureLayer),this.activeInteraction=null,this.measureTooltip=null,this.measureTooltipElement=null,this.onMeasureCompleteCallbacks=[],this.onDrawCompleteCallbacks=[]}getMeasureStyle(){return new M({fill:new I({color:"rgba(255, 233, 106, 0.2)"}),stroke:new k({color:"#8B008B",lineDash:[10,10],width:2}),image:new ae({radius:5,stroke:new k({color:"#8B008B"}),fill:new I({color:"rgba(255, 233, 106, 0.5)"})})})}getDrawStyle(){return new M({fill:new I({color:"rgba(255, 233, 106, 0.3)"}),stroke:new k({color:"#8B008B",width:2}),image:new ae({radius:6,stroke:new k({color:"#8B008B",width:2}),fill:new I({color:"#FFE96A"})})})}createMeasureTooltip(){this.measureTooltipElement&&this.measureTooltipElement.parentNode.removeChild(this.measureTooltipElement),this.measureTooltipElement=document.createElement("div"),this.measureTooltipElement.className="measure-tooltip",this.measureTooltip=new ge({element:this.measureTooltipElement,offset:[15,0],positioning:"center-left",stopEvent:!1}),this.map.addOverlay(this.measureTooltip)}deactivate(){this.activeInteraction&&(this.map.removeInteraction(this.activeInteraction),this.activeInteraction=null),this.measureTooltip&&(this.map.removeOverlay(this.measureTooltip),this.measureTooltip=null),this.measureTooltipElement&&this.measureTooltipElement.parentNode&&(this.measureTooltipElement.parentNode.removeChild(this.measureTooltipElement),this.measureTooltipElement=null)}startCircleMeasure(){this.deactivate(),this.createMeasureTooltip();const e=new Ee({source:this.measureSource,type:"Circle",style:new M({fill:new I({color:"rgba(255, 233, 106, 0.2)"}),stroke:new k({color:"rgba(139, 0, 139, 0.7)",lineDash:[10,10],width:2}),image:new ae({radius:5,stroke:new k({color:"rgba(139, 0, 139, 0.7)"}),fill:new I({color:"rgba(255, 233, 106, 0.5)"})})})});this.activeInteraction=e,this.map.addInteraction(e);let t;return e.on("drawstart",n=>{t=n.feature.getGeometry().on("change",a=>{const s=a.target;if(s instanceof gn){const i=s.getRadius(),l=hr(i),d=`${ut(i)}
${l}`;this.measureTooltipElement.innerHTML=d,this.measureTooltip.setPosition(s.getLastCoordinate())}})}),e.on("drawend",n=>{const o=n.feature,a=o.getGeometry(),s=a.getCenter(),i=a.getRadius();o.set("_layerType","measure_circle"),o.set("_radius",i),o.set("_center",s);const l=new se({geometry:new ie([s,[s[0]+i,s[1]]])});l.set("_layerType","measure_circle_radius"),this.measureSource.addFeature(l),this.measureTooltipElement.className="measure-tooltip measure-tooltip-static",this.measureTooltip.setOffset([0,-7]),this.measureTooltipElement=null,this.createMeasureTooltip(),_t(t);const c={type:"circle",center:s,radius:i,area:Math.PI*i*i,feature:o};this.onMeasureCompleteCallbacks.forEach(d=>d(c))}),e}startLineMeasure(){this.deactivate(),this.createMeasureTooltip();const e=new Ee({source:this.measureSource,type:"LineString",style:this.getMeasureStyle()});this.activeInteraction=e,this.map.addInteraction(e);let t;return e.on("drawstart",n=>{t=n.feature.getGeometry().on("change",a=>{const s=a.target,i=st(s),l=ut(i);this.measureTooltipElement.innerHTML=l,this.measureTooltip.setPosition(s.getLastCoordinate())})}),e.on("drawend",n=>{const o=n.feature,a=o.getGeometry(),s=st(a);this.measureTooltipElement.className="measure-tooltip measure-tooltip-static",this.measureTooltipElement=null,this.createMeasureTooltip(),_t(t);const i={type:"line",length:s,feature:o};this.onMeasureCompleteCallbacks.forEach(l=>l(i))}),e}startAreaMeasure(){this.deactivate(),this.createMeasureTooltip();const e=new Ee({source:this.measureSource,type:"Polygon",style:this.getMeasureStyle()});this.activeInteraction=e,this.map.addInteraction(e);let t;return e.on("drawstart",n=>{t=n.feature.getGeometry().on("change",a=>{const s=a.target,i=Ue(s),l=He(i);this.measureTooltipElement.innerHTML=l,this.measureTooltip.setPosition(s.getInteriorPoint().getCoordinates())})}),e.on("drawend",n=>{const o=n.feature,a=o.getGeometry(),s=Ue(a);o.set("_layerType","measure_area"),o.set("_area",s),this.measureTooltipElement.className="measure-tooltip measure-tooltip-static",this.measureTooltipElement=null,this.createMeasureTooltip(),_t(t);const i={type:"polygon",area:s,feature:o,coordinate:a.getInteriorPoint().getCoordinates()};this.onMeasureCompleteCallbacks.forEach(l=>l(i))}),e}startDrawPoint(){this.deactivate();const e=new Ee({source:this.drawSource,type:"Point",style:this.getDrawStyle()});return this.activeInteraction=e,this.map.addInteraction(e),e.on("drawend",t=>{const n={type:"point",feature:t.feature};this.onDrawCompleteCallbacks.forEach(o=>o(n))}),e}startDrawLine(){this.deactivate();const e=new Ee({source:this.drawSource,type:"LineString",style:this.getDrawStyle()});return this.activeInteraction=e,this.map.addInteraction(e),e.on("drawend",t=>{const n={type:"line",feature:t.feature};this.onDrawCompleteCallbacks.forEach(o=>o(n))}),e}startDrawPolygon(){this.deactivate();const e=new Ee({source:this.drawSource,type:"Polygon",style:this.getDrawStyle()});return this.activeInteraction=e,this.map.addInteraction(e),e.on("drawend",t=>{const n={type:"polygon",feature:t.feature};this.onDrawCompleteCallbacks.forEach(o=>o(n))}),e}clearMeasurements(){this.measureSource.clear(),document.querySelectorAll(".measure-tooltip-static").forEach(t=>t.parentNode.removeChild(t))}clearDrawings(){this.drawSource.clear()}clearAll(){this.clearMeasurements(),this.clearDrawings()}onMeasureComplete(e){this.onMeasureCompleteCallbacks.push(e)}onDrawComplete(e){this.onDrawCompleteCallbacks.push(e)}createControlBar(e={}){e.position;const t=new Ct({group:!0,className:"map-tools-bar"}),n=new Ct({toggleOne:!0,group:!0}),o=new me({html:'β­•',title:"Measure Circle (radius & area)",className:"measure-circle-btn",onToggle:l=>{l?this.startCircleMeasure():this.deactivate()}});n.addControl(o);const a=new me({html:'πŸ“',title:"Measure Distance",className:"measure-line-btn",onToggle:l=>{l?this.startLineMeasure():this.deactivate()}});n.addControl(a);const s=new me({html:'⬛',title:"Measure Area",className:"measure-area-btn",onToggle:l=>{l?this.startAreaMeasure():this.deactivate()}});n.addControl(s);const i=new qe({html:'πŸ—‘οΈ',title:"Clear Measurements",className:"clear-measure-btn",handleClick:()=>{this.clearMeasurements(),o.setActive(!1),a.setActive(!1),s.setActive(!1)}});return n.addControl(i),t.addControl(n),t}getMeasureLayer(){return this.measureLayer}getDrawLayer(){return this.drawLayer}getMeasureSource(){return this.measureSource}getDrawSource(){return this.drawSource}isActive(){return this.activeInteraction!==null}}let Le=null;async function Ur(){if(!("serviceWorker"in navigator))return console.warn("[PWA] Service Workers not supported"),null;try{return Le=await navigator.serviceWorker.register("/sw.js",{scope:"/"}),console.log("[PWA] Service Worker registered:",Le.scope),Le.addEventListener("updatefound",()=>{const r=Le.installing;r.addEventListener("statechange",()=>{r.state==="installed"&&navigator.serviceWorker.controller&&(console.log("[PWA] New version available"),Vr())})}),Le}catch(r){return console.error("[PWA] Service Worker registration failed:",r),null}}let Ce=null,fe=null;function Hr(r="#install-btn"){if(fe=typeof r=="string"?document.querySelector(r):r,!fe){console.warn("[PWA] Install button not found:",r);return}fe.style.display="none",window.addEventListener("beforeinstallprompt",e=>{e.preventDefault(),Ce=e,fe.style.display="block",console.log("[PWA] Install prompt ready")}),fe.addEventListener("click",async()=>{if(!Ce){Wr();return}Ce.prompt();const{outcome:e}=await Ce.userChoice;console.log("[PWA] Install prompt outcome:",e),Ce=null,fe.style.display="none"}),window.addEventListener("appinstalled",()=>{console.log("[PWA] App installed"),Ce=null,fe.style.display="none"}),window.matchMedia("(display-mode: standalone)").matches&&(fe.style.display="none")}function Wr(){const r=/iPad|iPhone|iPod/.test(navigator.userAgent),e=/^((?!chrome|android).)*safari/i.test(navigator.userAgent);let t=`To install this app: `;r?(t+=`1. Tap the Share button (square with arrow) `,t+='2. Scroll down and tap "Add to Home Screen"'):e?(t+=`1. Click File menu `,t+='2. Click "Add to Dock"'):(t+=`1. Click the menu button (three dots) `,t+='2. Click "Install" or "Add to Home Screen"'),alert(t)}let Rt=null;const $t=new Set;function Kr(r="#offline-indicator"){Rt=typeof r=="string"?document.querySelector(r):r,Tt(!navigator.onLine),window.addEventListener("online",()=>{console.log("[PWA] Back online"),Tt(!1),ho(!1)}),window.addEventListener("offline",()=>{console.log("[PWA] Gone offline"),Tt(!0),ho(!0)})}function Tt(r){Rt&&(Rt.style.display=r?"block":"none"),document.body.classList.toggle("is-offline",r)}function Yo(r){return $t.add(r),r(!navigator.onLine),()=>$t.delete(r)}function ho(r){for(const e of $t)try{e(r)}catch(t){console.error("[PWA] Offline listener error:",t)}}function W(){return navigator.onLine}function Vr(){confirm("A new version is available. Reload now?")&&Xr()}function Xr(){Le?.waiting&&Le.waiting.postMessage({type:"SKIP_WAITING"}),window.location.reload()}async function Yr({timeoutMs:r=1e4}={}){if(!("serviceWorker"in navigator))throw new Error("Service Workers not supported in this browser");if(navigator.serviceWorker.controller)return navigator.serviceWorker.controller;const e=navigator.serviceWorker.ready,t=new Promise((a,s)=>setTimeout(()=>s(new Error("Service-worker readiness timeout")),r)),n=await Promise.race([e,t]),o=navigator.serviceWorker.controller||n.active;if(!o)throw new Error("No active service worker available");return o}function Zr(r){if(!("serviceWorker"in navigator))return()=>{};const e=()=>{try{r()}catch(t){console.error("[PWA] controllerchange handler error:",t)}};return navigator.serviceWorker.addEventListener("controllerchange",e),()=>navigator.serviceWorker.removeEventListener("controllerchange",e)}async function Ut(r,e,t={},n=5e3,o=1e4){const a=await Yr({timeoutMs:o});return new Promise((s,i)=>{const l=new MessageChannel,c=setTimeout(()=>{l.port1.close(),i(new Error(`Service-worker reply "${e}" timed out`))},n);l.port1.onmessage=d=>{if(d.data?.type===e){clearTimeout(c),l.port1.close();const{type:u,...p}=d.data;s(p)}},a.postMessage({type:r,...t},[l.port2])})}async function Jr(){try{return(await Ut("GET_TILE_STATS","TILE_STATS")).stats}catch(r){return console.warn("[PWA] getTileCacheStats failed:",r),null}}async function Qr(){try{return await Ut("CLEAR_TILE_CACHES","TILE_CACHES_CLEARED"),!0}catch(r){return console.warn("[PWA] clearTileCaches failed:",r),!1}}async function es(r){if(!r)return!1;try{return!!(await Ut("CLEAR_TILE_CACHE","TILE_CACHE_CLEARED",{cacheName:r})).deleted}catch(e){return console.warn(`[PWA] clearTileCacheForProvider(${r}) failed:`,e),!1}}async function ts(){if(!navigator.storage?.estimate)return null;try{const{usage:r,quota:e}=await navigator.storage.estimate();return{usage:r||0,quota:e||0}}catch(r){return console.warn("[PWA] getStorageEstimate failed:",r),null}}async function os(r={}){const{installButton:e="#install-btn",offlineIndicator:t="#offline-indicator",autoRegisterSW:n=!0}=r;n&&await Ur(),Hr(e),Kr(t),console.log("[PWA] Initialized")}const Zo={topo:{url:"https://a.tile.opentopomap.org/{z}/{x}/{y}.png",label:"Topographic",maxZoom:17,cacheKey:"tiles-topo"},osm:{url:"https://a.tile.openstreetmap.org/{z}/{x}/{y}.png",label:"OpenStreetMap",maxZoom:19,cacheKey:"tiles-osm"}},ns=30*1024,pt=2*Math.PI*6378137/2;function go(r,e){const t=r/pt*180;let n=e/pt*180;return n=180/Math.PI*(2*Math.atan(Math.exp(n*Math.PI/180))-Math.PI/2),[t,n]}function mo(r,e,t){const n=Math.pow(2,t),o=Math.floor((r+180)/360*n),a=e*Math.PI/180,s=Math.floor((1-Math.log(Math.tan(a)+1/Math.cos(a))/Math.PI)/2*n);return{x:o,y:s}}function Jo(r,e){const[t,n,o,a]=r,[s,i]=go(t,n),[l,c]=go(o,a),d=mo(s,c,e),u=mo(l,i,e),p=Math.pow(2,e),h=Math.max(0,Math.min(d.x,u.x)),f=Math.min(p-1,Math.max(d.x,u.x)),b=Math.max(0,Math.min(d.y,u.y)),g=Math.min(p-1,Math.max(d.y,u.y));return{z:e,minX:h,maxX:f,minY:b,maxY:g,count:(f-h+1)*(g-b+1)}}function rs(r,e,t){let n=0;for(let o=e;o<=t;o++)n+=Jo(r,o).count;return n}function ss(r,e,t){const n=[];for(let o=e;o<=t;o++){const a=Jo(r,o);for(let s=a.minX;s<=a.maxX;s++)for(let i=a.minY;i<=a.maxY;i++)n.push({z:o,x:s,y:i})}return n}function as(r,{z:e,x:t,y:n}){return r.replace("{z}",e).replace("{x}",t).replace("{y}",n)}class is{constructor({baseMap:e,extent3857:t,minZoom:n,maxZoom:o,concurrency:a=2,interBatchDelayMs:s=50,onProgress:i=()=>{}}){const l=Zo[e];if(!l)throw new Error(`Unknown base map: ${e}`);o>l.maxZoom&&(console.warn(`[OfflineTiles] ${e}: maxZoom ${o} > supported ${l.maxZoom}; clamping`),o=l.maxZoom),this.baseMap=e,this.template=l.url,this.extent=t,this.minZoom=n,this.maxZoom=o,this.concurrency=Math.max(1,Math.min(a,6)),this.interBatchDelayMs=s,this.onProgress=i,this._abortCtrl=null,this._cancelled=!1}async start(){if(this._abortCtrl)throw new Error("Downloader already started");this._abortCtrl=new AbortController,this._cancelled=!1;const e=ss(this.extent,this.minZoom,this.maxZoom),t=e.length,n=Date.now();let o=0,a=0,s=0,i=0;const l=c=>{const d=Date.now()-n,u=o>0?Math.round(d/o*(t-o)):null;this.onProgress({phase:c,done:o,total:t,ok:a,failed:s,cached:i,elapsedMs:d,etaMs:u})};l("running");for(let c=0;c{if(this._cancelled)return;const p=as(this.template,u);try{const h=await fetch(p,{signal:this._abortCtrl.signal,cache:"default"});h.ok?(a++,h.body&&h.body.cancel().catch(()=>{})):(h.status,s++)}catch(h){h.name==="AbortError"||s++}o++})),l("running"),this.interBatchDelayMs>0&&c+this.concurrencysetTimeout(u,this.interBatchDelayMs))}return l(this._cancelled?"cancelled":"done"),{phase:this._cancelled?"cancelled":"done",done:o,total:t,ok:a,failed:s,cached:i,elapsedMs:Date.now()-n}}cancel(){this._cancelled=!0,this._abortCtrl&&this._abortCtrl.abort()}}const ls=(()=>{const r=(n,o)=>{const a=n*pt/180,s=Math.log(Math.tan((90+o)*Math.PI/360))/(Math.PI/180);return[a,s*pt/180]},e=r(-3.3,4.5),t=r(1.2,11.2);return[e[0],e[1],t[0],t[1]]})();function cs(r){return r*ns}const Qo="https://api.lupmis4luspa.org/api/spatial_planning",kt="1",ds="1c46538c712e9b5b";function us(){try{if(typeof window>"u")return kt;const r=window.LUPMIS_SESSION;if(!r||typeof r!="object")return kt;const e=r.district_id;return e==null||String(e).length===0?null:String(e)}catch{}return kt}const en={get district_id(){return us()},api_token:ds};function tn(){if(typeof window<"u"&&window.LUPMIS_SESSION&&window.LUPMIS_SESSION.user_id)return window.LUPMIS_SESSION;try{const r=localStorage.getItem("dev-session");if(r){const e=JSON.parse(r);if(e&&e.user_id)return e}}catch{}return null}typeof window<"u"&&(window.lupmisDevSession=r=>{r==null?(localStorage.removeItem("dev-session"),console.log("[Dev] Session override cleared. Reload to apply.")):(localStorage.setItem("dev-session",JSON.stringify(r)),console.log("[Dev] Session override saved. Reload to apply:",r))});const ps=3e4,fs=5e3;let Se=null;async function hs(r=!1){if(Se!==null&&!r)return Se;const e=new AbortController,t=setTimeout(()=>e.abort(),fs);try{Se=(await fetch(`${Qo}/get_layers.php`,{method:"POST",headers:{"Content-Type":"application/json",Accept:"application/json"},body:JSON.stringify(en),signal:e.signal})).ok}catch{Se=!1}finally{clearTimeout(t)}return console.log("[RemoteDB] Server reachable:",Se),Se}function ke(){return Se}function gs(r,e=ps){const t=new AbortController,n=setTimeout(()=>t.abort(),e);return r.signal&&r.signal.addEventListener("abort",()=>t.abort()),{signal:t.signal,clear:()=>clearTimeout(n)}}async function ye(r,e={},t={}){const n=`${Qo}/${r}`,o={...en,...e};console.log("[RemoteDB] POST",n);const a=gs(t);try{const s=await fetch(n,{method:"POST",headers:{"Content-Type":"application/json",Accept:"application/json"},body:JSON.stringify(o),...t,signal:a.signal});if(!s.ok)throw new Error(`HTTP ${s.status}: ${s.statusText}`);const i=await s.json();return console.log("[RemoteDB] POST response:",r,"β†’",typeof i=="object"?`${Array.isArray(i)?i.length+" items":"object"}`:i),i}catch(s){throw s.name==="AbortError"?(console.error("[RemoteDB] POST timed out:",r),new Error(`Request timed out: ${r}`)):(console.error("[RemoteDB] POST failed:",r,s),s)}finally{a.clear()}}async function ms(){return ye("get_district_boundary.php")}async function ys(){return ye("get_layers.php")}async function bs(){return ye("get_all_collector_zone_per_district.php")}async function ws(){return ye("get_parcels_per_district.php")}async function vs(){return ye("get_all_footprint_per_district.php")}async function _s(){return ye("get_contours_hillshade.php")}async function Es(){return ye("get_osm_roads.php")}async function xs(r,e){const t={client_uuid:r.client_uuid,name:r.name??null,started_at:r.started_at,ended_at:r.ended_at,point_count:r.point_count??e.length,distance_m:r.distance_m??0,points:(e||[]).map(o=>({seq:o.seq,longitude:o.longitude,latitude:o.latitude,altitude:o.altitude??null,accuracy:o.accuracy??null,altitude_accuracy:o.altitude_accuracy??null,heading:o.heading??null,speed:o.speed??null,satellites:o.satellites??null,recorded_at:o.recorded_at}))},n=await ye("save_gps_trail.php",t);return{remoteId:n?.id??n?.remote_id??null}}const Ss=63710088e-1,et=Math.PI/180;function Ls(r,e,t,n){const o=(n-e)*et,a=(t-r)*et,s=Math.sin(o/2)**2+Math.cos(e*et)*Math.cos(n*et)*Math.sin(a/2)**2;return 2*Ss*Math.asin(Math.min(1,Math.sqrt(s)))}function yo(r,e=5){return r==null||Number.isNaN(r)?"β€”":r.toFixed(e)}function Ts(r){return r==null||Number.isNaN(r)?"β€”":r<1e3?`${Math.round(r)} m`:`${(r/1e3).toFixed(2)} km`}function ks(r){return r==null||Number.isNaN(r)?"β€”":`Β±${Math.round(r)} m`}function Ps(r){return r==null||Number.isNaN(r)?"none":r<=10?"good":r<=30?"fair":"poor"}const Ms={minDistanceM:5,minIntervalMs:1e3,heartbeatMs:2e4,maxAccuracyM:50,enableHighAccuracy:!0,timeoutMs:15e3,maximumAgeMs:0};class We{constructor(e={}){this.opts={...Ms,...e},this.storage=e.storage||null,this.sync=e.sync||null,this._geo=e.geolocation||(typeof navigator<"u"?navigator.geolocation:null),this._state="idle",this._watchId=null,this._live=!1,this._recording=!1,this._activeTrailId=null,this._activeTrailUuid=null,this._lastRecorded=null,this._lastRecordedAt=0,this._distanceM=0,this._pointCount=0,this._lastFix=null,this._listeners=Object.create(null)}on(e,t){return(this._listeners[e]||(this._listeners[e]=new Set)).add(t),()=>this._listeners[e]?.delete(t)}_emit(e,t){const n=this._listeners[e];if(n)for(const o of n)try{o(t)}catch(a){console.error(`[GeoTracker] listener for "${e}" threw`,a)}}get state(){return this._state}get isRecording(){return this._recording}get lastFix(){return this._lastFix}get isSupported(){return!!this._geo}_setState(e){this._state!==e&&(this._state=e,this._emit("statechange",e))}startLive(){if(!this._geo){this._emit("error",new Error("Geolocation not supported"));return}this._live=!0,this._ensureWatch()}stopLive(){this._live=!1,this._recording||this._teardownWatch()}getCurrentPosition(){return new Promise((e,t)=>{if(!this._geo){t(new Error("Geolocation not supported"));return}this._geo.getCurrentPosition(n=>{const o=We.normalize(n);this._lastFix=o,this._emit("position",o),e(o)},n=>{this._emit("error",n),t(n)},{enableHighAccuracy:this.opts.enableHighAccuracy,timeout:this.opts.timeoutMs,maximumAge:this.opts.maximumAgeMs})})}async startRecording(e={}){if(!this._geo)throw new Error("Geolocation not supported");if(!this.storage)throw new Error("GeoTracker: no storage adapter configured");if(this._recording)return{trailId:this._activeTrailId,uuid:this._activeTrailUuid};const t=We.uuid(),n=new Date().toISOString(),o={uuid:t,name:e.name||null,startedAt:n,...e},a=await this.storage.createTrail(o);return this._activeTrailId=a,this._activeTrailUuid=t,this._lastRecorded=null,this._lastRecordedAt=0,this._distanceM=0,this._pointCount=0,this._recording=!0,this._ensureWatch(),this._setState("recording"),this._emit("trailstart",{trailId:a,uuid:t,startedAt:n}),{trailId:a,uuid:t}}async stopRecording(){if(!this._recording)return null;const e=this._activeTrailId,n={endedAt:new Date().toISOString(),pointCount:this._pointCount,distanceM:this._distanceM};this._recording=!1,this._live||this._teardownWatch(),this._setState(this._live?"watching":"idle");try{await this.storage.finishTrail(e,n)}catch(a){this._emit("error",a)}this._emit("trailstop",{trailId:e,...n});let o=!1;if(this.sync)try{o=await this._syncTrail(e)}catch(a){this._emit("error",a)}return this._activeTrailId=null,this._activeTrailUuid=null,{trailId:e,pointCount:n.pointCount,distanceM:n.distanceM,synced:o}}async syncPending(){if(!this.sync||!this.storage)return{pushed:0,failed:0};if(this.sync.isOnline&&!this.sync.isOnline())return{pushed:0,failed:0};let e=0,t=0;const n=await this.storage.getUnsyncedTrails();for(const o of n)try{await this._syncTrail(o.id??o.trailId,o)?e++:t++}catch(a){t++,this._emit("error",a)}return this._emit("syncstatus",{pushed:e,failed:t}),{pushed:e,failed:t}}async _syncTrail(e,t){const n=await this.storage.getTrailPoints(e),o=t||{id:e},a=await this.sync.pushTrail(o,n),s=a&&(a.remoteId??a.id??null);return await this.storage.markTrailSynced(e,s),!0}_ensureWatch(){if(this._watchId!=null||!this._geo){this._state==="idle"&&this._live&&this._setState("watching");return}this._watchId=this._geo.watchPosition(e=>this._onFix(e),e=>this._emit("error",e),{enableHighAccuracy:this.opts.enableHighAccuracy,timeout:this.opts.timeoutMs,maximumAge:this.opts.maximumAgeMs}),this._recording||this._setState("watching")}_teardownWatch(){this._watchId!=null&&this._geo&&this._geo.clearWatch(this._watchId),this._watchId=null}async _onFix(e){const t=We.normalize(e);if(this._lastFix=t,this._emit("position",t),!this._recording)return;const{minIntervalMs:n,minDistanceM:o,heartbeatMs:a,maxAccuracyM:s}=this.opts,i=t.timestamp;if(this._lastRecordedAt&&i-this._lastRecordedAt0&&t.accuracy!=null&&t.accuracy>s&&this._lastRecorded)return;let l=!1,c=0;if(!this._lastRecorded)l=!0;else{c=Ls(this._lastRecorded.lon,this._lastRecorded.lat,t.lon,t.lat);const d=i-this._lastRecordedAt;(c>=o||d>=a)&&(l=!0)}if(l){this._lastRecorded&&(this._distanceM+=c),this._pointCount+=1,this._lastRecorded={lon:t.lon,lat:t.lat,timestamp:i},this._lastRecordedAt=i;try{await this.storage.addPoint(this._activeTrailId,{...t,seq:this._pointCount}),this._emit("point",{trailId:this._activeTrailId,seq:this._pointCount,point:t,distanceM:this._distanceM,pointCount:this._pointCount})}catch(d){this._emit("error",d)}}}static normalize(e){const t=e.coords||{},n=o=>o!=null&&!Number.isNaN(o)?o:null;return{lon:t.longitude,lat:t.latitude,accuracy:n(t.accuracy),altitude:n(t.altitude),altitudeAccuracy:n(t.altitudeAccuracy),heading:n(t.heading),speed:n(t.speed),satellites:null,timestamp:e.timestamp||Date.now()}}static uuid(){return typeof crypto<"u"&&crypto.randomUUID?crypto.randomUUID():"xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g,e=>{const t=Math.random()*16|0;return(e==="x"?t:t&3|8).toString(16)})}}const Is={async createTrail(r){const e=r.districtId??tn()?.district_id??null;return ar({...r,districtId:e!=null?String(e):null})},addPoint:(r,e)=>ir(r,e),finishTrail:(r,e)=>lr(r,e),getUnsyncedTrails:()=>cr(),getTrailPoints:r=>dr(r),markTrailSynced:(r,e)=>ur(r,e)},Cs={pushTrail:(r,e)=>xs(r,e),isOnline:()=>W()},he=new We({storage:Is,sync:Cs,minDistanceM:5,minIntervalMs:1e3,heartbeatMs:2e4,maxAccuracyM:50,enableHighAccuracy:!0}),As=new Set(["set:view","set:selected","clear:selected","set:basemap"]);function Ds({mapView:r,embedConfig:e}){const t=r.getMap(),n=window.parent&&window.parent!==window?window.parent:null,o=new R,a=new A({source:o,zIndex:9999,style:new M({stroke:new k({color:"#f97316",width:3}),fill:new I({color:"rgba(249,115,22,0.18)"})}),properties:{title:"Permit selection",displayInLayerSwitcher:!1}});t.addLayer(a);let s=null,i=e?.upn?String(e.upn):null,l=!1;function c(g){if(!n){console.warn("[embed-bridge] No parent window β€” would have sent:",g);return}try{n.postMessage(g,"*")}catch(m){console.warn("[embed-bridge] postMessage failed:",m)}}function d(g,m){c({type:"error",code:g,message:m})}function u(){l||(l=!0,c({type:"ready"}))}function p(g,m,y){const w=g.getProperties();let S=m,x=y;if(S==null||x==null){const v=g.getGeometry()?.getExtent();if(v){const[L,P]=xe(mn(v));S=L,x=P}}return{type:"parcel:select",upn:w.upn??null,parcel_id:w.id??null,lon:S??null,lat:x??null,zone_code:w.zone_code??null,zone_name:w.zone_name??null,landuse:w.landuse??null,min_height:w.min_height??null,max_height:w.max_height??null}}function h(g){if(o.clear(),g){const m=g.clone();o.addFeature(m)}}r.onClick((g,m,y,w)=>{let S=null;t.forEachFeatureAtPixel(w.pixel,x=>{if(x.get("_layerType")==="parcel")return S=x,!0}),S?(h(S),c(p(S,g,m))):(h(null),c({type:"parcel:cleared"}))}),window.addEventListener("message",g=>{const m=g.data;if(!(!m||typeof m!="object"||!As.has(m.type)))try{switch(m.type){case"set:view":{if(typeof m.lon=="number"&&typeof m.lat=="number"){const y=t.getView();y.setCenter(X([m.lon,m.lat])),typeof m.zoom=="number"&&y.setZoom(m.zoom)}break}case"set:selected":m.upn&&f(String(m.upn));break;case"clear:selected":h(null),i=null;break;case"set:basemap":m.key&&typeof r.setBaseMap=="function"&&r.setBaseMap(m.key);break}}catch(y){d("COMMAND_FAILED",`Failed to handle ${m.type}: ${y.message}`)}});function f(g){if(!s){i=g;return}const y=s.getSource().getFeatures().find(S=>String(S.get("upn")??"")===g);if(!y){i=g;return}i=null,h(y);const w=y.getGeometry()?.getExtent();w&&t.getView().fit(w,{padding:[50,50,50,50],duration:400,maxZoom:17}),c(p(y,null,null))}function b(g){s=g;const m=g.getSource(),y=()=>{queueMicrotask(()=>{i&&f(i),u()})};if(m.getFeatures().length>0)y();else{let w=!1;m.on("addfeature",()=>{w||(w=!0,queueMicrotask(()=>{w=!1,i&&f(i),u()}))})}}if(e?.basemap&&typeof r.setBaseMap=="function"&&r.setBaseMap(e.basemap),typeof e?.lon=="number"&&typeof e?.lat=="number"){const g=t.getView();g.setCenter(X([e.lon,e.lat])),g.setZoom(typeof e?.zoom=="number"?e.zoom:15)}return{attachParcelsLayer:b,emitError:d}}let Pt=null;async function bo(){if(!Pt){const r=await ft(()=>import("./shpjs-CNrRgkgn.js"),[]);Pt=r.default||r}return Pt}let _=null,te=null,oe=null,Mt=null;const Nt=typeof window<"u"&&window.LUPMIS_EMBED||null,je=!!(Nt&&Nt.mode==="permit");let B=je?"embed-permit":"addLocation";function Fs(){const r=typeof window<"u"?window.LUPMIS_SESSION:null;if(!r||typeof r!="object")return!1;const e=r.district_id;if(e!=null&&String(e).length>0)return!1;console.warn("[App] Authenticated user has no district assigned; halting init.");const t=document.createElement("div");t.id="no-district-overlay",t.setAttribute("role","alertdialog"),t.setAttribute("aria-modal","true"),t.style.cssText="position:fixed;inset:0;z-index:99999;display:flex;align-items:center;justify-content:center;background:rgba(255,255,255,0.98);padding:24px;";const n=r.full_name||r.username||"You";return t.innerHTML=`
πŸ›‘

No district assigned

${$(n)}, your user profile is not associated with any district. LUPMIS2 cannot load the relevant map data without one.

Please contact the system administrator to have a district assigned to your account.

`,document.body.appendChild(t),t.querySelector("#no-district-portal-btn")?.addEventListener("click",()=>{window.location.href="https://lupmis4luspa.org/"}),!0}async function wo(){if(console.log("[App] Initializing..."),Fs())return;await os({installButton:"#install-btn",offlineIndicator:"#offline-indicator",autoRegisterSW:!0});const r=localStorage.getItem("default-basemap")||"topo";_=new zr("map",{center:[-1.5,7.5],zoom:7,basemap:r}),te=new jr(_.getMap()),ia(),te.onMeasureComplete(t=>{console.log("[MapTools] Measurement complete:",t),t.type==="polygon"&&t.coordinate&&t.feature?.get("_layerType")!=="measure_area"&&_?.showDrawnPolygonPopup(t.feature,t.coordinate)}),je&&(Mt=Ds({mapView:_,embedConfig:Nt})),_.onClick((t,n,o,a)=>{if(je||(console.log("[MapClick] Clicked at:",t.toFixed(4),n.toFixed(4)),console.log("[MapClick] currentMode =",B),B==="draw"||B.startsWith("measure")))return;let s=null;if(_.getMap().forEachFeatureAtPixel(a.pixel,i=>{if(i.get("_layerType")==="parcel")return s=i,!0}),s){console.log("[MapClick] Clicked on parcel β†’ Edit Attributes"),_.showParcelEditPopup(s,a.coordinate);return}B==="addLocation"&&(o?(console.log("[MapClick] Clicked on marker:",o.getId()),_.selectMarker(o),Rs(o)):(console.log("[MapClick] Empty space β†’ Add Location popup"),_.clearSelection(),_.showAddLocationPopup(a.coordinate)))}),_.onDblClick((t,n,o,a)=>{if(je||!o)return;const s=o.get("_layerType");if(console.log("[App] Double-click on feature, _layerType:",s||"none"),s==="measure_circle")_.showCircleIntersectionPopup(o,a.coordinate);else{if(s==="measure_circle_radius")return;s==="measure_area"?_.showAreaIntersectionPopup(o,a.coordinate):s==="collector_zone"?_.showInfoPopup(o,a.coordinate,{title:"Zone Info",color:"#7c3aed"}):s==="parcel"?_.showInfoPopup(o,a.coordinate,{title:"Parcel Info",color:"#0ea5e9"}):_.showInfoPopup(o,a.coordinate,{title:"Feature Info",color:"#e11d48"})}}),_.onAddLocation(async t=>{console.log("[App] Add location from map popup:",t);try{const n=await qn(t.name,t.lon,t.lat,{description:t.description||null,category:t.category||"default"});console.log("[App] Location added:",t.name,"id:",n.id),await It(),_?.zoomTo(t.lon,t.lat,14),n.id&&_?.selectMarker(n.id),le("Location added successfully")}catch(n){console.error("[App] Failed to add location:",n),N("Failed to add location: "+n.message)}}),_.onParcelEdit(async(t,n)=>{const o=n.id||n.parcelid||n.parcel_id;if(console.log("[App] Parcel edit saved:",o,n),!o){console.warn("[App] No parcel ID found in updated properties β€” skipping local save");return}try{await Kn(o,n),le("Parcel updated locally")}catch(a){console.error("[App] Failed to save parcel update:",a),N("Failed to save parcel: "+a.message)}});const e=new Lo;_.onDrawnPolygonSave(async(t,n)=>{console.log("[App] Drawn polygon attributes saved:",n);try{const o=e.writeGeometry(t.getGeometry(),{dataProjection:"EPSG:4326",featureProjection:"EPSG:3857"}),a=await Vn(o,n);console.log("[App] New parcel inserted with id:",a.id),le("New parcel saved (pending verification)")}catch(o){console.error("[App] Failed to save new parcel:",o),N("Failed to save parcel: "+o.message)}});try{console.log("[App] Initializing database..."),await Gn(),console.log("[App] Database ready");const t=await jt();console.log("[App] Database status:",t),W()&&(await hs()||(console.warn("[App] API server unreachable β€” using local data only"),la("Server not responding β€” loading cached data."))),await Qs(),_?.initEditBar(),Ws(),Ks(),Vs(),je&&Mt&&oe&&(oe.setVisible(!0),Mt.attachParcelsLayer(oe)),Xs(),Ys(),Zs(),Js()}catch(t){console.error("[App] Database initialization failed:",t),N("Failed to initialize database. Please refresh the page.");return}Os(),await It(),Bn(t=>{if(console.log("[App] Database change:",t),t.table==="locations"&&!t.local&&It(),t.table==="parcels"){const n=document.getElementById("local-data-stats");n&&!n.classList.contains("d-none")&>()}}),Yo(t=>{t?console.log("[App] Working offline - data will sync when back online"):(console.log("[App] Back online - syncing data..."),ea())}),ca(),ua(),da(),pa(),fa(),ha(),ga(),console.log("[App] Initialized successfully")}function Os(){console.log("[initUI] Starting UI initialization..."),aa();const r=document.getElementById("export-btn");r&&r.addEventListener("click",Gs);const e=document.getElementById("local-data-btn");e&&e.addEventListener("click",()=>gt());const t=document.getElementById("import-shp-btn"),n=document.getElementById("shp-file-input");t&&n&&(t.addEventListener("click",()=>n.click()),n.addEventListener("change",sn));const o=document.getElementById("import-geojson-btn"),a=document.getElementById("geojson-file-input");o&&a&&(o.addEventListener("click",()=>a.click()),a.addEventListener("change",an));const s=document.getElementById("import-kml-btn"),i=document.getElementById("kml-file-input");s&&i&&(s.addEventListener("click",()=>i.click()),i.addEventListener("change",ln)),ra();const l=document.getElementById("exportGeoJSON-btn");l&&l.addEventListener("click",qs);const c=document.getElementById("status-btn");c&&c.addEventListener("click",zs);const d=document.getElementById("fit-btn");d&&d.addEventListener("click",()=>_?.fitToMarkers());const u=document.getElementById("dock-btn-add-location"),p=document.getElementById("dock-btn-measure-circle"),h=document.getElementById("dock-btn-measure-line"),f=document.getElementById("dock-btn-measure-area"),b=document.getElementById("dock-btn-draw"),g=document.getElementById("dock-btn-clear");console.log("[initUI] Buttons found:",{addLocation:!!u,measureCircle:!!p,measureLine:!!h,measureArea:!!f,draw:!!b,clear:!!g});const m=[u,p,h,f,b],y=(w,S)=>{switch(console.log("[setMode] Changing mode from",B,"to",w),B=w,console.log("[setMode] currentMode is now:",B),m.forEach(x=>{x&&x.classList.toggle("active",x===S)}),te?.deactivate(),w!=="draw"&&_?.setEditMode(!1),w!=="addLocation"&&_?.hideAddLocationPopup(),w){case"measureCircle":te?.startCircleMeasure();break;case"measureLine":te?.startLineMeasure();break;case"measureArea":te?.startAreaMeasure();break;case"draw":_?.setEditMode(!0);break}};u&&u.addEventListener("click",()=>{console.log("[Button] Add Location clicked"),y("addLocation",u)}),p&&p.addEventListener("click",()=>{console.log("[Button] Circle clicked, currentMode is:",B),B==="measureCircle"?y("addLocation",u):y("measureCircle",p)}),h&&h.addEventListener("click",()=>{console.log("[Button] Line clicked, currentMode is:",B),B==="measureLine"?y("addLocation",u):y("measureLine",h)}),f&&f.addEventListener("click",()=>{console.log("[Button] Area clicked, currentMode is:",B),B==="measureArea"?y("addLocation",u):y("measureArea",f)}),b&&b.addEventListener("click",()=>{console.log("[Button] Draw clicked, currentMode is:",B),B==="draw"?y("addLocation",u):y("draw",b)}),g&&g.addEventListener("click",()=>{if(te?.clearMeasurements(),B.startsWith("measure"))switch(te?.deactivate(),B){case"measureCircle":te?.startCircleMeasure();break;case"measureLine":te?.startLineMeasure();break;case"measureArea":te?.startAreaMeasure();break}})}async function It(){try{console.log("[App] Loading locations...");const r=await No();console.log("[App] Locations loaded:",r),$s(r),_&&(_.clearMarkers(),r.length>0&&(_.addMarkers(r),console.log("[App] Added",r.length,"markers to map")));const e=document.getElementById("location-count");e&&(e.textContent=r.length)}catch(r){console.error("[App] Failed to load locations:",r)}}function Rs(r){const e=r.get("name"),t=r.get("description"),n=r.get("category"),o=r.get("lon")||r.get("longitude"),a=r.get("lat")||r.get("latitude");console.log("[App] Selected location:",{name:e,description:t,category:n,lon:o,lat:a})}function $s(r){const e=document.getElementById("locations-list");if(!e)return;const t=document.getElementById("location-count-mobile");if(t&&(t.textContent=r.length),r.length===0){e.innerHTML=`

No locations yet.

Click the map or fill the form above!
`;return}const n={water:"πŸ’§",school:"🏫",health:"πŸ₯",market:"πŸͺ",default:"πŸ“",other:"πŸ“Œ"};e.innerHTML=r.map(o=>{const a=n[o.category]||"πŸ“";return`
${a} ${$(o.name)}
${o.latitude.toFixed(5)}, ${o.longitude.toFixed(5)}
${o.category}
${o.description?`${$(o.description)}`:""}
`}).join(""),e.querySelectorAll(".location-item").forEach(o=>{o.addEventListener("click",a=>{a.preventDefault();const s=parseFloat(o.dataset.lon),i=parseFloat(o.dataset.lat),l=parseInt(o.dataset.id);_?.zoomTo(s,i,14),_?.selectMarker(l)})})}async function gt(){const r=document.getElementById("local-data-stats"),e=document.getElementById("local-data-tbody"),t=document.getElementById("clear-all-cached-btn");if(!(!r||!e)){try{const n=await nr();e.innerHTML=n.map(o=>{const s=zo(o.name)?``:"";return` ${$(o.name)} ${o.count} ${s} `}).join(""),r.classList.remove("d-none"),e.querySelectorAll(".table-name-link").forEach(o=>{o.addEventListener("click",a=>{a.preventDefault(),Bs(o.dataset.table)})}),e.querySelectorAll(".table-clear-btn").forEach(o=>{o.addEventListener("click",async a=>{a.preventDefault();const s=o.dataset.table;if(confirm(`Clear local cache for "${s}"? The data will be re-downloaded from the server on the next app start.`))try{const i=await jo(s);le(`Cleared ${i} row${i===1?"":"s"} from "${s}". It will re-download on next start.`),await gt()}catch(i){console.error("[App] Per-table clear failed:",i),N(`Could not clear "${s}": ${i.message}`)}})})}catch(n){console.error("[App] Failed to load table stats:",n),e.innerHTML='Failed to load',r.classList.remove("d-none")}t&&!t._wired&&(t._wired=!0,t.addEventListener("click",Ns))}}async function Ns(){if(confirm(`Delete all cached map layers from this device? The next time the app starts (or after a reload), every layer will be re-downloaded from the server. Your locally drawn data is not affected.`))try{const r=await or(),e=r.reduce((t,n)=>t+n.count,0);le(`Cleared ${e} row${e===1?"":"s"} across ${r.length} table${r.length===1?"":"s"}.`),await gt(),confirm("Reload the app now to re-download the layers fresh from the server?")&&window.location.reload()}catch(r){console.error("[App] Clear-all failed:",r),N("Failed to clear cached layers: "+r.message)}}async function Bs(r){const e=document.getElementById("tableContentModalLabel"),t=document.getElementById("table-content-body"),n=document.getElementById("table-content-info");e.textContent=`Table: ${r}`,t.innerHTML=`
Loading...
`,n.textContent="",new Gt(document.getElementById("tableContentModal")).show();try{const{columns:a,rows:s}=await rr(r);if(s.length===0){t.innerHTML='
Table is empty
',n.textContent="0 rows";return}const i=a.map(c=>`${$(c)}`).join(""),l=s.map(c=>`${a.map(u=>{let p=c[u];if(p==null)return'NULL';p=String(p);const h=p.length>120?p.substring(0,120)+"...":p;return`${$(h)}`}).join("")}`).join("");t.innerHTML=`
${i}${l}
`,n.textContent=`${s.length}${s.length>=200?"+":""} row(s), ${a.length} column(s)`}catch(a){console.error("[App] Failed to load table content:",a),t.innerHTML=`
Failed to load: ${$(a.message)}
`}}async function Gs(){try{await er("lupmis-backup.sqlite3"),le("Database exported successfully")}catch(r){console.error("[App] Export failed:",r),N("Export failed: "+r.message)}}async function qs(){try{const r=await tr(),e=new Blob([JSON.stringify(r,null,2)],{type:"application/json"}),t=URL.createObjectURL(e),n=document.createElement("a");n.href=t,n.download="locations.geojson",n.click(),URL.revokeObjectURL(t),le(`Exported ${r.features.length} location(s)`)}catch(r){console.error("[App] GeoJSON Export failed:",r),N("GeoJSON Export failed: "+r.message)}}async function zs(){try{const r=await jt(),e=document.getElementById("status-content");e&&(e.innerHTML=`
Ready: ${r.ready?"Yes":"No"}
Online: ${W()?"Yes":"Offline"}
Database: ${r.databasePath||"N/A"}
Tables: ${r.tables.map(n=>`${n}`).join("")}
Locations: ${r.locationCount}
`),new Gt(document.getElementById("statusModal")).show()}catch(r){console.error("[App] Failed to get status:",r),N("Failed to get status")}}function on(r){return r.replace(/^\(+/,"").replace(/\)+$/,"").split(",").map(e=>{const[t,n]=e.trim().split(/\s+/).map(Number);return[t,n]})}function js(r){return{type:"Polygon",coordinates:r.trim().replace(/^POLYGON\s*\(\s*/i,"").replace(/\s*\)$/,"").split("),(").map(on)}}function Us(r){return{type:"MultiPolygon",coordinates:r.trim().replace(/^MULTIPOLYGON\s*\(\s*/i,"").replace(/\s*\)$/,"").split(")),((").map(o=>o.replace(/^\(+/,"").replace(/\)+$/,"").split("),(").map(on))}}function mt(r){if(!r)return null;const e=r.trim().toUpperCase();return e.startsWith("MULTIPOLYGON")?Us(r):e.startsWith("POLYGON")?js(r):(console.warn("[App] Unsupported WKT type:",e.substring(0,30)),null)}function Hs(r){if(!r?.success||!r?.data?.boundary)return console.warn("[App] API response missing success or boundary data"),null;const{boundary:e,districtid:t,district_name:n}=r.data,o=mt(e);return{type:"FeatureCollection",features:[{type:"Feature",properties:{districtid:t,district_name:n},geometry:o}]}}function vo(r){if(!Array.isArray(r)||r.length===0)return null;const e=[];for(const t of r){const n=t.polygon||t.boundary,o=mt(n);if(!o)continue;const a={_layerType:"collector_zone"};for(const[s,i]of Object.entries(t))s==="polygon"||s==="boundary"||(a[s]=i);e.push({type:"Feature",properties:a,geometry:o})}return e.length===0?null:{type:"FeatureCollection",features:e}}async function Ws(){const r="district_boundary",t={strokeColor:"#e11d48",strokeWidth:2.5,fillColor:"rgba(225,29,72,0.08)",typeDescription:"Vector / Polygon"},n=_?.getLayerGroup(1)||null;function o(s){if(!s)return;const i=s.getLayers(),l=[];i.forEach(c=>{c.get("title")==="District Boundary"&&l.push(c)}),l.forEach(c=>i.remove(c))}function a(s){if(!s||!_)return;const i=s.getSource().getExtent();i&&i[0]!==1/0&&_.getMap().getView().fit(i,{padding:[40,40,40,40],duration:600})}try{const s=await Go(r);if(s){console.log("[App] District boundary loaded from local cache");const i=_?.addGeoJSONLayer(s,"District Boundary",t,n);a(i)}if(W()&&ke()){console.log("[App] Fetching district boundary from API...");const i=await ms(),l=Hs(i);if(!l){console.warn("[App] Could not convert API response to GeoJSON");return}console.log("[App] District boundary:",l.features[0]?.properties?.district_name,"β†’",l.features[0]?.geometry?.coordinates?.length,"polygon(s)"),await Bo(r,l),s&&o(n||_?.getOverlayGroup());const c=_?.addGeoJSONLayer(l,"District Boundary",t,n);a(c),console.log("[App] District boundary loaded from API")}else s||console.log("[App] District boundary not available β€” offline and no local cache")}catch(s){console.error("[App] Failed to load district boundary:",s)}}async function Ks(){const e={strokeColor:"#7c3aed",strokeWidth:1.5,fillColor:"rgba(124,58,237,0.12)",typeDescription:"Vector / Polygon"},t=_?.getLayerGroup(1)||null;console.log("[App] loadCollectorZones β€” adminGroup:",t?t.get("title"):"null");const n={type:"FeatureCollection",features:[]},o=_?.addGeoJSONLayer(n,"Zones",e,t);if(!o){console.warn("[App] Could not create Zones layer");return}o.setVisible(!1),o.on("change:visible",()=>{o.getVisible()&&o.getSource().getFeatures().length===0&&N("No collector zones available locally. Connect to the internet to download zone data.")});function a(s){const i=new ce().readFeatures(s,{featureProjection:"EPSG:3857"});o.getSource().clear(),o.getSource().addFeatures(i)}try{const s=await Un();if(s){const i=vo(s);i&&(console.log("[App] Collector zones loaded from local cache:",i.features.length,"zones"),a(i))}if(W()&&ke()){console.log("[App] Fetching collector zones from API...");const i=await bs();if(!i?.success||!Array.isArray(i?.data)){console.warn("[App] getCollectorZones API response invalid:",i);return}const l=i.data;console.log("[App] Collector zones from API:",l.length,"entries"),await jn(l);const c=vo(l);if(!c){console.warn("[App] Could not convert zones to GeoJSON");return}a(c),console.log("[App] Collector zones updated from API:",c.features.length,"zones")}else s||console.log("[App] Collector zones not available β€” offline and no local cache")}catch(s){console.error("[App] Failed to load collector zones:",s)}}function _o(r){if(!Array.isArray(r)||r.length===0)return null;const e=new Set,t=[];for(const n of r){const o=n.id||n.parcelid||n.parcel_id;if(o!=null){if(e.has(o))continue;e.add(o)}let a=null;if(n.geom&&n.geom.type&&n.geom.coordinates)a={type:n.geom.type,coordinates:n.geom.coordinates};else if(n.sp_boundary&&n.sp_boundary.type&&n.sp_boundary.coordinates)a={type:n.sp_boundary.type,coordinates:n.sp_boundary.coordinates};else{const l=n.boundary||n.geometry_wkt||n.polygon||n.wkt;a=mt(l)}if(!a)continue;const s=new Set(["polygon","boundary","geom","geometry_wkt","wkt","textboundary","sp_boundary","fetched_at"]),i={_layerType:"parcel"};for(const[l,c]of Object.entries(n))s.has(l)||(i[l]=c);t.push({type:"Feature",properties:i,geometry:a})}return t.length===0?null:{type:"FeatureCollection",features:t}}async function Vs(){const e={strokeColor:"#0ea5e9",strokeWidth:1.5,fillColor:"rgba(14,165,233,0.12)",typeDescription:"Vector / Polygon"},t=_?.getLayerGroup(4)||null;console.log("[App] loadParcels β€” landUseGroup:",t?t.get("title"):"null");const n={type:"FeatureCollection",features:[]};if(oe=_?.addGeoJSONLayer(n,"Parcels",e,t),!oe){console.warn("[App] Could not create Parcels layer");return}oe.setVisible(!1),oe.on("change:visible",()=>{oe.getVisible()&&oe.getSource().getFeatures().length===0&&N("No parcels available locally. Connect to the internet to download parcel data.")});function o(a){const s=new ce().readFeatures(a,{featureProjection:"EPSG:3857"});oe.getSource().clear(),oe.getSource().addFeatures(s)}try{const a=await Wn();if(a){const s=_o(a);s&&(console.log("[App] Parcels loaded from local cache:",s.features.length,"parcels"),o(s))}if(W()&&ke()){console.log("[App] Fetching parcels from API...");const s=await ws();if(!s?.success||!Array.isArray(s?.data)){console.warn("[App] getDistrictParcels API response invalid:",s);return}const i=s.data;console.log("[App] Parcels from API:",i.length,"entries"),i.length>0&&console.log("[App] First parcel keys:",Object.keys(i[0])),await Hn(i);const l=_o(i);if(!l){console.warn("[App] Could not convert parcels to GeoJSON");return}o(l),console.log("[App] Parcels updated from API:",l.features.length,"parcels")}else a||console.log("[App] Parcels not available β€” offline and no local cache")}catch(a){console.error("[App] Failed to load parcels:",a)}}function Eo(r){if(!Array.isArray(r)||r.length===0)return null;const e=["polygon","boundary","geom","wkt","footprint"],t=[];for(const n of r){const o=n.polygon||n.boundary||n.geom||n.wkt||n.footprint;let a;if(typeof o=="object"&&o!==null&&o.type?a=o:a=mt(o),!a)continue;const s={_layerType:"building_footprint"};for(const[i,l]of Object.entries(n))e.includes(i)||typeof l=="object"&&l!==null||(s[i]=l);t.push({type:"Feature",properties:s,geometry:a})}return t.length===0?null:{type:"FeatureCollection",features:t}}async function Xs(){const e={strokeColor:"#8b6f47",strokeWidth:1,fillColor:"rgba(139,111,71,0.18)",typeDescription:"Vector / Polygon"},t=_?.getLayerGroup(5)||null;console.log("[App] loadBuildingFootprints β€” physInfraGroup:",t?t.get("title"):"null");const n={type:"FeatureCollection",features:[]},o=_?.addGeoJSONLayer(n,"Building footprints",e,t);if(!o){console.warn("[App] Could not create Building footprints layer");return}o.setVisible(!1),o.on("change:visible",()=>{o.getVisible()&&o.getSource().getFeatures().length===0&&N("No building footprints available locally. Connect to the internet to download footprint data.")});function a(s){const i=new ce().readFeatures(s,{featureProjection:"EPSG:3857"});o.getSource().clear(),o.getSource().addFeatures(i)}try{const s=await Yn();if(s){const i=Eo(s);i&&(console.log("[App] Building footprints loaded from local cache:",i.features.length,"footprints"),a(i))}if(W()&&ke()){console.log("[App] Fetching building footprints from API...");const i=await vs();if(!i?.success||!Array.isArray(i?.data)){console.warn("[App] getBuildingFootprints API response invalid:",i);return}const l=i.data;console.log("[App] Building footprints from API:",l.length,"entries"),l.length>0&&console.log("[App] First footprint keys:",Object.keys(l[0])),await Xn(l);const c=Eo(l);if(!c){console.warn("[App] Could not convert building footprints to GeoJSON");return}a(c),console.log("[App] Building footprints updated from API:",c.features.length,"footprints")}else s||console.log("[App] Building footprints not available β€” offline and no local cache")}catch(s){console.error("[App] Failed to load building footprints:",s)}}function nn(r,e){if(!Array.isArray(r)||r.length===0)return null;const t=new Lo,n=new ce,o=["geom","geometry","wkt","polygon","boundary","road","line"],a=[];for(const s of r){const i=s.geom||s.geometry||s.wkt||s.polygon||s.boundary||s.road||s.line;if(!i)continue;let l;try{if(typeof i=="object"&&i!==null&&i.type){a.push({type:"Feature",properties:xo(s,o,e),geometry:i});continue}l=t.readGeometry(i)}catch(d){console.warn(`[App] Could not parse WKT for ${e}:`,d,i?.toString().slice(0,60));continue}const c=JSON.parse(n.writeGeometry(l));a.push({type:"Feature",properties:xo(s,o,e),geometry:c})}return a.length===0?null:{type:"FeatureCollection",features:a}}function xo(r,e,t){const n={_layerType:t};for(const[o,a]of Object.entries(r))e.includes(o)||typeof a=="object"&&a!==null||(n[o]=a);return n}async function Ys(){const r={strokeColor:"#78716c",strokeWidth:.8,typeDescription:"Vector / Line",fillColor:"rgba(0,0,0,0)"},e=_?.getLayerGroupByTitle("Biophysical Environment");console.log("[App] loadContoursHillshade β€” group:",e?e.get("title"):"null");const t={type:"FeatureCollection",features:[]},n=_?.addGeoJSONLayer(t,"Contours hillshade",r,e);if(!n){console.warn("[App] Could not create Contours hillshade layer");return}if(n.setVisible(!1),n.on("change:visible",()=>{n.getVisible()&&n.getSource().getFeatures().length===0&&N("No Contours hillshade data available. Connect to the internet to download it.")}),!W()||!ke()){console.log("[App] Contours hillshade not available β€” offline or server unreachable");return}try{console.log("[App] Fetching contours_hillshade from API...");const o=await _s();if(!o?.success||!Array.isArray(o?.data)){console.warn("[App] getContoursHillshade API response invalid:",o);return}const a=o.data;console.log("[App] Contours hillshade from API:",a.length,"rows"),a.length>0&&console.log("[App] First row keys:",Object.keys(a[0]));const s=nn(a,"contours_hillshade");if(!s){console.warn("[App] Could not convert contours to GeoJSON");return}const i=new ce().readFeatures(s,{featureProjection:"EPSG:3857"});n.getSource().clear(),n.getSource().addFeatures(i),console.log("[App] Contours hillshade loaded:",i.length,"features")}catch(o){console.error("[App] Failed to load contours_hillshade:",o)}}async function Zs(){const e={strokeColor:"#F0F1F0",strokeWidth:1.5,lineCasingColor:"#000000",lineCasingWidth:3.5,fillColor:"rgba(0,0,0,0)",typeDescription:"Vector / Line"},t=_?.getLayerGroup(5)||null;console.log("[App] loadOSMRoads β€” group:",t?t.get("title"):"null");const n={type:"FeatureCollection",features:[]},o=_?.addGeoJSONLayer(n,"OSM_roads",e,t);if(!o){console.warn("[App] Could not create OSM_roads layer");return}o.setVisible(!1),o.on("change:visible",()=>{o.getVisible()&&o.getSource().getFeatures().length===0&&N("No OSM roads available locally. Connect to the internet to download them.")});function a(s){const i=nn(s,"osm_road");if(!i)return console.warn("[App] Could not convert OSM roads to GeoJSON"),0;const l=new ce().readFeatures(i,{featureProjection:"EPSG:3857"});return o.getSource().clear(),o.getSource().addFeatures(l),l.length}try{const s=await Jn();if(s){const i=a(s);console.log("[App] OSM_roads loaded from local cache:",i,"features")}if(W()&&ke()){console.log("[App] Fetching OSM_roads from API...");const i=await Es();if(!i?.success||!Array.isArray(i?.data)){console.warn("[App] getOSMRoads API response invalid:",i);return}const l=i.data;console.log("[App] OSM_roads from API:",l.length,"rows"),l.length>0&&console.log("[App] First row keys:",Object.keys(l[0])),await Zn(l);const c=a(l);console.log("[App] OSM_roads updated from API:",c,"features")}else s||console.log("[App] OSM_roads not available β€” offline and no local cache")}catch(s){console.error("[App] Failed to load OSM_roads:",s)}}function Js(){_?.addWMSLayer("Biophysical Environment","DEAfrica Coastlines v0.4","https://geoserver.digitalearth.africa/geoserver/wms","coastlines:DEAfrica_Coastlines",{serverType:"geoserver",visible:!1,onlineOnly:!0}),_?.addWMSLayer("Biophysical Environment","DEAfrica Slope (SRTM 30m)","https://ows.digitalearth.africa/wms","srtm_deriv",{serverType:null,style:"style_slope",visible:!1,opacity:.5,zIndex:-50,onlineOnly:!0,attributions:'© Digital Earth Africa β€” SRTM-derived Slope',legendUrl:"https://ows.digitalearth.africa/legend/srtm_deriv/style_slope/legend.png"})}async function Qs(){const r="layer_categories";function e(t){const n=[...t].sort((o,a)=>a.id-o.id);for(const o of n)_?.addLayerGroup(o.id,o.name,o.description||"");console.log("[App] Created",t.length,"layer groups on map")}try{const t=await Go(r);if(t&&(console.log("[App] Layer categories loaded from local cache:",t.length,"entries"),e(t)),W()&&ke()){console.log("[App] Fetching layer categories from API...");const n=await ys();if(!n?.success||!Array.isArray(n?.data)){console.warn("[App] getLayers API response invalid:",n);return}const o=n.data;if(console.log("[App] Layer categories from API:",o.length,"entries"),await Bo(r,o),t){const a=_?.getOverlayGroup()?.getLayers();if(a){const s=[];a.forEach(i=>{i.get("layerId")!==void 0&&s.push(i)}),s.forEach(i=>a.remove(i))}}e(o),console.log("[App] Layer categories refreshed from API")}else t||console.log("[App] Layer categories not available β€” offline and no local cache")}catch(t){console.error("[App] Failed to load layer categories:",t)}}async function ea(){if(!W()){console.log("[App] Cannot sync - offline");return}console.log("[App] Sync placeholder - implement based on your backend")}const re=[],ta={strokeColor:"#e11d48",strokeWidth:2,fillColor:"rgba(225,29,72,0.12)"};function Z(r){yt("error",r);const e=document.getElementById("file-import-alert");e&&(e.querySelector(".message-text").textContent=r,e.classList.remove("d-none"),setTimeout(()=>e.classList.add("d-none"),8e3))}function Ht(r,e,t){const n=Array.isArray(r)?r:[r];let o=0;for(const s of n){if(!s||s.type!=="FeatureCollection"||!s.features?.length)continue;const i=s.fileName?s.fileName.replace(/\.[^/.]+$/,""):e,l=_?.addGeoJSONLayer(s,i,ta);l&&(l.set("removable",!0),l.set("typeTag","GEO"),re.push(l),o+=s.features.length)}if(o===0){Z("No features found in the file.");return}console.log(`[${t}] Added ${o} feature(s) from ${n.length} layer(s)`);const a=re[re.length-1];if(a){const s=a.getSource().getExtent();_?.getMap().getView().fit(s,{padding:[50,50,50,50],maxZoom:18})}Wt()}function Wt(){const r=document.getElementById("imported-layers-info");if(!r)return;if(re.length===0){r.innerHTML="",r.classList.add("d-none");return}r.innerHTML=`
Imported Layers
    `;const e=r.querySelector("#imported-layers-list");re.forEach((t,n)=>{const o=document.createElement("li");o.className="list-group-item d-flex justify-content-between align-items-center py-2",o.innerHTML=`${$(t.get("title"))} ${t.getSource().getFeatures().length} `,e.appendChild(o)}),r.classList.remove("d-none"),r.querySelectorAll("[data-remove-idx]").forEach(t=>{t.addEventListener("click",()=>{oa(Number(t.dataset.removeIdx))})}),r.querySelector("#remove-imported-layers")?.addEventListener("click",()=>{na()})}function oa(r){if(r<0||r>=re.length)return;const e=re[r],t=_?.getOverlayGroup();t&&t.getLayers().remove(e),re.splice(r,1),Wt(),console.log("[FileImport] Removed layer:",e.get("title"))}function na(){const r=_?.getOverlayGroup();if(r)for(const e of re)r.getLayers().remove(e);re.length=0,Wt(),console.log("[FileImport] All imported layers removed")}function rn(r){const e={};for(const t of r){const n=t.name.split(".").pop().toLowerCase();e[n]=t}return e}async function sn(r){const e=r.target.files;if(!e||e.length===0)return;const t=200*1024*1024,n=Array.from(e).reduce((o,a)=>o+a.size,0);if(n>t){const o=(n/1048576).toFixed(0);Z(`Files too large (${o} MB total). Maximum supported size is 200 MB.`),r.target.value="";return}try{let o,a;const s=rn(e);if(s.zip){const i=s.zip;a=i.name.replace(/\.zip$/i,""),console.log("[ShpImport] Parsing zip",i.name,"("+(i.size/1024).toFixed(1)+" KB)"),o=await(await bo())(await i.arrayBuffer())}else if(s.shp){a=s.shp.name.replace(/\.shp$/i,"");const l=["dbf","shx","prj"].filter(u=>!s[u]);if(l.length>0){Z("Missing required file(s): "+l.map(u=>"."+u).join(", ")+". Please select .shp, .dbf, .shx and .prj together."),r.target.value="";return}const c={};c.shp=await s.shp.arrayBuffer(),c.dbf=await s.dbf.arrayBuffer(),c.prj=await new Response(s.prj).text(),s.cpg&&(c.cpg=await new Response(s.cpg).text()),console.log("[ShpImport] Parsing loose files:",Object.keys(s).map(u=>"."+u).join(", "),"("+(s.shp.size/1024).toFixed(1)+" KB .shp)"),o=await(await bo())(c)}else{Z("Please select a .zip or at least a .shp file."),r.target.value="";return}Ht(o,a,"ShpImport")}catch(o){console.error("[ShpImport] Failed:",o),Z("Failed to parse shapefile: "+o.message)}r.target.value=""}async function an(r){const e=r.target.files?.[0];if(!e)return;const t=200*1024*1024;if(e.size>t){const n=(e.size/1048576).toFixed(0);Z(`File too large (${n} MB). Maximum supported size is 200 MB. Consider splitting the file into smaller tiles with ogr2ogr or QGIS.`),r.target.value="";return}try{const n=await e.text();console.log("[GeoJSONImport] Parsing",e.name,"("+(e.size/1024).toFixed(1)+" KB)");const o=JSON.parse(n);let a;if(o.type==="FeatureCollection")a=o;else if(o.type==="Feature")a={type:"FeatureCollection",features:[o]};else if(o.type&&o.coordinates)a={type:"FeatureCollection",features:[{type:"Feature",geometry:o,properties:{}}]};else{Z("The file does not contain valid GeoJSON."),r.target.value="";return}const s=e.name.replace(/\.(geo)?json$/i,"");Ht(a,s,"GeoJSONImport")}catch(n){console.error("[GeoJSONImport] Failed:",n);const o=(e.size/(1024*1024)).toFixed(1);Z(`Failed to import "${e.name}" (${o} MB): ${n.message}`)}r.target.value=""}async function ln(r){const e=r.target.files?.[0];if(!e)return;const t=200*1024*1024;if(e.size>t){const n=(e.size/1048576).toFixed(0);Z(`File too large (${n} MB). Maximum supported size is 200 MB.`),r.target.value="";return}try{const n=await e.text();console.log("[KMLImport] Parsing",e.name,"("+(e.size/1024).toFixed(1)+" KB)");const a=new yn({extractStyles:!1}).readFeatures(n,{featureProjection:"EPSG:3857"});if(!a||a.length===0){Z("No features found in the KML file."),r.target.value="";return}const s=new ce,i=JSON.parse(s.writeFeatures(a,{featureProjection:"EPSG:3857",dataProjection:"EPSG:4326"})),l=e.name.replace(/\.kml$/i,"");Ht(i,l,"KMLImport")}catch(n){console.error("[KMLImport] Failed:",n);const o=(e.size/(1024*1024)).toFixed(1);Z(`Failed to import "${e.name}" (${o} MB): ${n.message}`)}r.target.value=""}function ra(){const r=document.querySelector(".map-container");if(!r)return;let e=0;r.addEventListener("dragenter",t=>{t.preventDefault(),e++,r.classList.add("drag-over")}),r.addEventListener("dragover",t=>{t.preventDefault()}),r.addEventListener("dragleave",t=>{t.preventDefault(),e--,e<=0&&(e=0,r.classList.remove("drag-over"))}),r.addEventListener("drop",t=>{t.preventDefault(),e=0,r.classList.remove("drag-over");const n=t.dataTransfer?.files;if(!n||n.length===0)return;const o=rn(n),a=Object.keys(o);if(o.zip||o.shp){const s={target:{files:n,value:""}};Object.defineProperty(s.target,"value",{writable:!0}),sn(s)}else if(o.geojson||o.json){const i={target:{files:[o.geojson||o.json],value:""}};Object.defineProperty(i.target,"value",{writable:!0}),an(i)}else if(o.kml){const s={target:{files:[o.kml],value:""}};Object.defineProperty(s.target,"value",{writable:!0}),ln(s)}else Z("Unsupported file type(s): "+a.map(s=>"."+s).join(", ")+". Drop .zip, .shp, .geojson, .json, or .kml files.")}),console.log("[FileImport] Map drop zone initialised")}function $(r){const e=document.createElement("div");return e.textContent=r,e.innerHTML}const sa=50,So={error:{icon:"bi-x-circle-fill",color:"var(--destructive, #dc3545)"},warning:{icon:"bi-exclamation-triangle-fill",color:"var(--warning, #ffc107)"},success:{icon:"bi-check-circle-fill",color:"var(--success, #198754)"},info:{icon:"bi-info-circle-fill",color:"var(--primary, #0d6efd)"}};function yt(r,e){const t=So[r]||So.info;(r==="error"?console.error:r==="warning"?console.warn:console.log)("[App]",e);const o=document.getElementById("message-log");if(!o)return;const a=o.querySelector(".text-muted");a&&a.remove();const s=document.createElement("div");s.className="list-group-item message-log-entry py-2 px-3";const l=new Date().toLocaleTimeString([],{hour:"2-digit",minute:"2-digit",second:"2-digit"});for(s.innerHTML=`
    ${$(e)}
    ${l}
    `,o.prepend(s);o.children.length>sa;)o.lastElementChild.remove()}function aa(){const r=document.getElementById("clear-message-log");r&&r.addEventListener("click",()=>{const e=document.getElementById("message-log");e&&(e.innerHTML='
    No messages yet.
    ')})}function ia(){const r=document.getElementById("gps-readout"),e=document.getElementById("gps-coords"),t=document.getElementById("gps-accuracy"),n=document.getElementById("gps-sats");if(!he.isSupported){e&&(e.textContent="No GPS");return}he.on("position",a=>{e&&(e.textContent=`${yo(a.lat)}, ${yo(a.lon)}`),t&&(t.textContent=ks(a.accuracy)),n&&(n.textContent=`${a.satellites!=null?a.satellites:"β€”"} sat`),r&&(r.classList.add("active"),r.classList.remove("quality-good","quality-fair","quality-poor"),r.classList.add("quality-"+Ps(a.accuracy))),_?.showCurrentPosition(a.lon,a.lat,a.accuracy)}),he.on("point",a=>{_?.appendTrailPoint(a.point.lon,a.point.lat)}),he.on("error",a=>{console.warn("[GPS]",a?.message||a),a&&a.code===1&&N("Location permission denied. Enable location access to use GPS.")}),_.onLocateMe(async()=>{try{const a=await he.getCurrentPosition();_.centerOn(a.lon,a.lat,16)}catch(a){N("Could not get your location: "+(a?.message||a))}}),_.onToggleRecording(async a=>{if(a)try{await ro,_.startTrailRender(),_.setRecordingState(!0),r?.classList.add("recording"),await he.startRecording({name:`Trail ${new Date().toLocaleString()}`}),le("GPS trail recording started")}catch(s){_.setRecordingState(!1),r?.classList.remove("recording"),N("Could not start recording: "+(s?.message||s))}else try{const s=await he.stopRecording();if(_.setRecordingState(!1),r?.classList.remove("recording"),s){const i=`Trail saved: ${s.pointCount} points, ${Ts(s.distanceM)}`+(s.synced?" β€” synced":" β€” will sync when online");le(i)}}catch(s){N("Error stopping recording: "+(s?.message||s))}});const o=async()=>{if(W())try{await ro;const a=await he.syncPending();a.pushed&&console.log(`[GPS] Synced ${a.pushed} pending trail(s)`)}catch(a){console.warn("[GPS] pending-sync error",a)}};o(),Yo(a=>{a||o()})}function N(r){yt("error",r);const e=document.getElementById("error-message");e&&(e.querySelector(".message-text").textContent=r,e.classList.remove("d-none"),setTimeout(()=>e.classList.add("d-none"),5e3))}function le(r){yt("success",r);const e=document.getElementById("success-message");e&&(e.querySelector(".message-text").textContent=r,e.classList.remove("d-none"),setTimeout(()=>e.classList.add("d-none"),3e3))}function la(r){yt("warning",r);const e=document.getElementById("warning-message");e&&(e.querySelector(".message-text").textContent=r,e.classList.remove("d-none"),setTimeout(()=>e.classList.add("d-none"),5e3))}function ca(){const r=document.getElementById("fieldwork-mode-toggle");if(!r)return;localStorage.getItem("fieldwork-mode")==="true"&&(document.documentElement.classList.add("fieldwork-mode"),r.checked=!0),r.addEventListener("change",()=>{document.documentElement.classList.toggle("fieldwork-mode",r.checked),localStorage.setItem("fieldwork-mode",r.checked),console.log("[Settings] Fieldwork mode",r.checked?"ON":"OFF")})}function da(){const r=document.getElementById("dark-mode-toggle");if(!r)return;function e(n){document.documentElement.classList.toggle("dark-mode",n),document.documentElement.setAttribute("data-bs-theme",n?"dark":"light")}localStorage.getItem("dark-mode")==="true"&&(r.checked=!0,e(!0)),r.addEventListener("change",()=>{e(r.checked),localStorage.setItem("dark-mode",r.checked),console.log("[Settings] Dark mode",r.checked?"ON":"OFF")})}function ua(){const r=document.getElementById("measurement-system-toggle"),e=document.getElementById("measurement-system-label");if(!r)return;function t(){e&&(e.textContent=r.checked?"Imperial":"Metric")}const n=localStorage.getItem("measurement-system");n==="imperial"&&(r.checked=!0),t(),_?.setScaleBarUnits(n||"metric"),r.addEventListener("change",()=>{const o=r.checked?"imperial":"metric";localStorage.setItem("measurement-system",o),t(),_?.setScaleBarUnits(o),console.log("[Settings] Measurement system:",o)})}function pa(){const r=document.getElementById("default-basemap-select");if(!r)return;const e=localStorage.getItem("default-basemap")||"topo";r.value=e,r.addEventListener("change",()=>{const t=r.value;localStorage.setItem("default-basemap",t),_?.setBaseMap(t),console.log("[Settings] Default base map:",t)}),_?.getMap()?.on("basemapchange",t=>{if(t?.key&&r.value!==t.key){r.value=t.key;try{localStorage.setItem("default-basemap",t.key)}catch{}}})}function fa(){const r=document.getElementById("tile-cache-stats"),e=document.getElementById("clear-tiles-btn"),t=document.getElementById("offcanvasBottom");if(!r||!e||!t)return;function n(s){return s?s<1024*1024?(s/1024).toFixed(0)+" KB":s<1024*1024*1024?(s/(1024*1024)).toFixed(1)+" MB":(s/(1024*1024*1024)).toFixed(2)+" GB":"0 KB"}let o=null;async function a(){if(o)return o;const s=!!navigator.serviceWorker?.controller;return r.innerHTML=s?'
    Loading…
    ':'
    Initialising service worker…
    ',o=(async()=>{try{const i=await Jr();if(!i){r.innerHTML=`
    Tile cache stats unavailable. Try reloading the page if this persists.
    `;return}const l=i.totals,c=i.byProvider.filter(p=>p.count>0).map(p=>` ${$(p.label)} ${p.count.toLocaleString()} / ${p.limit.toLocaleString()} ${n(p.estBytes)} `).join("");let d="";const u=await ts();if(u&&u.quota>0){const p=(u.usage/u.quota*100).toFixed(1);d=`
    Total app storage: ${n(u.usage)} of ${n(u.quota)} available (${p}%)
    `}if(l.count===0){r.innerHTML=`
    No tiles cached yet. Pan and zoom the map to start caching tiles automatically.
    ${d}`,e.disabled=!0;return}r.innerHTML=`
    ${l.count.toLocaleString()} tiles cached, ~${n(l.estBytes)} on this device
    ${c}
    Base map Cached / limit Approx. size
    ${d}`,e.disabled=!1,r.querySelectorAll(".provider-clear-btn").forEach(p=>{p.addEventListener("click",async h=>{h.preventDefault();const f=p.dataset.cache,b=p.dataset.label||f;if(!confirm(`Clear cached "${b}" tiles? Other providers are not affected. The tiles will re-download as you browse online.`))return;p.disabled=!0,await es(f)?console.log(`[Settings] Cleared tile cache for ${b}`):console.warn(`[Settings] Could not clear tile cache for ${b}`),await a()})})}finally{o=null}})(),o}e.addEventListener("click",async()=>{if(!confirm("Clear all cached map tiles from this device? You will need to be online to view them again."))return;e.disabled=!0,await Qr()?console.log("[Settings] Tile caches cleared"):console.warn("[Settings] Tile-cache clear failed"),await a()}),t.addEventListener("show.bs.offcanvas",a),Zr(()=>{console.log("[Settings] SW controller changed β†’ refreshing tile-cache stats"),a()}),a()}function ha(){const r=document.getElementById("download-tiles-btn"),e=document.getElementById("offline-download-modal");if(!r||!e)return;const t=Gt.getOrCreateInstance(e),n=document.getElementById("offline-download-form-view"),o=document.getElementById("offline-download-progress-view"),a=document.getElementById("offline-download-done-view"),s=document.getElementById("offline-download-cancel-btn"),i=document.getElementById("offline-download-start-btn"),l=document.getElementById("offline-download-close-done-btn"),c=document.getElementById("offline-download-close-btn"),d=document.getElementById("offline-basemap-select"),u=document.getElementById("offline-min-zoom"),p=document.getElementById("offline-max-zoom"),h=document.getElementById("offline-ack-check"),f=document.getElementById("offline-estimate-detail"),b=document.getElementById("offline-estimate"),g=document.getElementById("offline-area-view"),m=document.getElementById("offline-area-district"),y=document.getElementById("offline-area-ghana"),w=document.getElementById("offline-area-view-info"),S=document.getElementById("offline-area-district-info"),x=document.getElementById("offline-progress-bar"),v=document.getElementById("offline-progress-percent"),L=document.getElementById("offline-progress-counts"),P=document.getElementById("offline-progress-ok"),T=document.getElementById("offline-progress-failed"),z=document.getElementById("offline-progress-eta"),U=document.getElementById("offline-done-title"),K=document.getElementById("offline-done-detail");let D=null;function ne(F){return F?F<1024*1024?(F/1024).toFixed(0)+" KB":F<1024*1024*1024?(F/(1024*1024)).toFixed(1)+" MB":(F/(1024*1024*1024)).toFixed(2)+" GB":"0 KB"}function V(F){if(!F||F<1e3)return"< 1 s";const H=Math.round(F/1e3);if(H<60)return H+" s";const j=Math.floor(H/60),J=H%60;return j<60?`${j} min ${J} s`:`${Math.floor(j/60)} h ${j%60} min`}function de(){return g.checked?_?.getCurrentViewExtent()||null:m.checked?_?.getDistrictBoundaryExtent()?.extent||null:y.checked?ls:null}function G(){const F=d.value,H=parseInt(u.value,10),j=parseInt(p.value,10);if(Number.isNaN(H)||Number.isNaN(j)||H>j){f.textContent="Invalid zoom range",b.classList.replace("alert-info","alert-warning"),i.disabled=!0;return}const J=de();if(!J){f.textContent="Selected area is not available.",b.classList.replace("alert-info","alert-warning"),i.disabled=!0;return}const C=Zo[F]?.maxZoom??19,q=Math.min(j,C),Q=rs(J,H,q),bt=cs(Q);let pe="";qZoom ${j} is above this provider's max (${C}); will clamp to ${C}.`),Q>8e3&&(pe+='
    More than 8 000 tiles β€” exceeds the per-provider cache limit. Earlier tiles will be evicted as new ones arrive.'),f.innerHTML=`${Q.toLocaleString()} tiles Β· ~${ne(bt)}`+pe,b.classList.toggle("alert-warning",!!pe),b.classList.toggle("alert-info",!pe),i.disabled=!h.checked||Q===0}function ue(){_?.getCurrentViewExtent()?w.textContent=" Β· ready":w.textContent="",_?.getDistrictBoundaryExtent()?(S.textContent="",m.disabled=!1):(S.textContent=" (not loaded β€” connect online to fetch)",m.disabled=!0,m.checked&&(g.checked=!0))}function Oe(){n.classList.remove("d-none"),o.classList.add("d-none"),a.classList.add("d-none"),i.classList.remove("d-none"),s.classList.remove("d-none"),s.textContent="Cancel",l.classList.add("d-none"),c.disabled=!1,h.checked=!1,i.disabled=!0,D=null}r.addEventListener("click",()=>{Oe(),ue(),G(),t.show()}),d.addEventListener("change",G),u.addEventListener("input",G),p.addEventListener("input",G),g.addEventListener("change",G),m.addEventListener("change",G),y.addEventListener("change",G),h.addEventListener("change",G),i.addEventListener("click",async()=>{const F=d.value,H=parseInt(u.value,10),j=parseInt(p.value,10),J=de();if(!J)return;n.classList.add("d-none"),o.classList.remove("d-none"),i.classList.add("d-none"),s.textContent="Cancel download",c.disabled=!0,x.style.width="0%",x.setAttribute("aria-valuenow","0"),v.textContent="0%",L.textContent="0 of 0 tiles",P.textContent="0",T.textContent="0",z.textContent="β€”",D=new is({baseMap:F,extent3857:J,minZoom:H,maxZoom:j,onProgress:q=>{if(q.total>0){const Q=Math.min(100,Math.round(q.done/q.total*100));x.style.width=Q+"%",x.setAttribute("aria-valuenow",String(Q)),v.textContent=Q+"%",L.textContent=`${q.done.toLocaleString()} of ${q.total.toLocaleString()} tiles`}P.textContent=q.ok.toLocaleString(),T.textContent=q.failed.toLocaleString(),z.textContent=q.etaMs!=null?V(q.etaMs):"β€”"}});let C;try{C=await D.start()}catch(q){console.error("[OfflineDownload] failed:",q),C={phase:"error",done:0,total:0,ok:0,failed:0}}o.classList.add("d-none"),a.classList.remove("d-none"),s.classList.add("d-none"),l.classList.remove("d-none"),c.disabled=!1,C.phase==="cancelled"?(U.textContent="Download cancelled",K.innerHTML=`Stopped after ${C.done.toLocaleString()} of ${C.total.toLocaleString()} tiles.
    ${C.ok.toLocaleString()} fetched Β· ${C.failed.toLocaleString()} failed.`):C.phase==="error"?(U.textContent="Download failed",K.textContent="See console for details."):(U.textContent="Download complete",K.innerHTML=`${C.ok.toLocaleString()} tiles cached`+(C.failed>0?`, ${C.failed.toLocaleString()} failed`:"")+`.
    Took ${V(C.elapsedMs)}.`)}),s.addEventListener("click",()=>{D&&D.cancel()}),e.addEventListener("hidden.bs.modal",()=>{D&&D.cancel(),Oe()})}function ga(){const r=tn(),e=document.getElementById("menu-btn"),t=document.getElementById("menu-user-avatar"),n=document.getElementById("menu-user-name"),o=document.getElementById("menu-user-email"),a=document.getElementById("menu-user-detail"),s=document.getElementById("menu-signout-btn"),i=document.getElementById("menu-signin-link"),l=document.getElementById("menu-no-session-note");if(!e||!t||!n||!o||!a||!s){console.warn("[AccountMenu] One or more elements missing β€” shell may be stale. Hard-refresh.");return}if(!!r&&!!r.user_id){const d=[r.title,r.full_name].filter(Boolean).join(" ").trim()||r.username||"Authenticated user",u=(r.full_name||r.username||"?").trim().charAt(0).toUpperCase();t.textContent=u,t.style.background="var(--brand-navy, #1e1a4b)",n.textContent=d,o.textContent=r.email||"";const p=[];r.district_id!=null&&p.push(`District ${$(String(r.district_id))}`),r.region_id!=null&&p.push(`Region ${$(String(r.region_id))}`),r.ua_position&&p.push($(r.ua_position)),a.innerHTML=p.join(" Β· ")||"No district info",s.classList.remove("d-none"),s.addEventListener("click",()=>ma(r),{once:!1}),i?.classList.add("d-none"),l?.classList.add("d-none"),e.removeAttribute("data-state"),e.setAttribute("title",`Menu β€” ${d}`)}else typeof window.LUPMIS_SESSION>"u"?(t.innerHTML='',t.style.background="var(--brand-orange-warm, #ff9e1b)",n.textContent="No session injected",o.textContent="",a.textContent="",s.classList.add("d-none"),i?.classList.add("d-none"),l?.classList.remove("d-none"),e.dataset.state="no-session",e.setAttribute("title","Menu (no session β€” dev mode)")):(t.innerHTML='',t.style.background="var(--brand-gray-medium, #7a7a7a)",n.textContent="Not signed in",o.textContent="",a.textContent="",s.classList.add("d-none"),i?.classList.remove("d-none"),l?.classList.add("d-none"),e.dataset.state="unauthenticated",e.setAttribute("title","Menu (not signed in)"))}async function ma(r){if(!confirm(`Return to Landing Page, ${r?.full_name||r?.username||"user"}?`))return;const e=document.cookie.split(";").map(n=>n.trim()).find(n=>n.startsWith("sso_auth_token="))?.split("=")[1];if(e)try{await fetch("https://lupmis4luspa.org/sso/logout?token="+encodeURIComponent(e),{method:"GET",mode:"no-cors",credentials:"include",cache:"no-store"})}catch(n){console.warn("[Signout] Best-effort SSO logout call failed:",n)}const t="Thu, 01 Jan 1970 00:00:00 GMT";document.cookie=`sso_auth_token=; expires=${t}; path=/; domain=.lupmis4luspa.org`,document.cookie=`sso_auth_token=; expires=${t}; path=/; domain=lupmis4luspa.org`,document.cookie=`sso_auth_token=; expires=${t}; path=/`,window.location.href="https://lupmis4luspa.org/"}document.readyState==="loading"?document.addEventListener("DOMContentLoaded",wo):wo(); //# sourceMappingURL=index-YjHYbDyk.js.map