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 dt,h as M,F as I,j as k,k as re,m as Dt,b as N,V as F,L as se,D as ve,P as Ue,Q as tt,n as ne,U as be,M as qt,W as on,X as te,Y as nn,S as rn,G as sn,Z as an,o as We,O as he,$ as qe,a0 as ot,a1 as Le,A as ln,T as Z,a2 as we,a3 as jt,a4 as ae,a5 as zt,e as cn,u as bt,a6 as wo,a7 as dn}from"./openlayers-CvK8xBSr.js";import{M as Ft}from"./bootstrap-D1-uvFxm.js";import{o as un,a as pn,b as hn,c as fn,d as Tt,e as Ut,f as $e,g as gn,h as fe,i as mn,j as yn}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 i of o)if(i.type==="childList")for(const s of i.addedNodes)s.tagName==="LINK"&&s.rel==="modulepreload"&&n(s)}).observe(document,{childList:!0,subtree:!0});function t(o){const i={};return o.integrity&&(i.integrity=o.integrity),o.referrerPolicy&&(i.referrerPolicy=o.referrerPolicy),o.crossOrigin==="use-credentials"?i.credentials="include":o.crossOrigin==="anonymous"?i.credentials="omit":i.credentials="same-origin",i}function n(o){if(o.ep)return;o.ep=!0;const i=t(o);fetch(o.href,i)}})();const Ht="function",Ie="64e10b34-2bf7-4616-9668-f99de5aa046e",bn="get",wn="has",vn="set",{isArray:Ze}=Array;let{SharedArrayBuffer:nt,window:_n}=globalThis,{notify:vo,wait:_o,waitAsync:rt}=Atomics,Eo=null;rt||(rt=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 nt(4)}catch{nt=ArrayBuffer;const e=new WeakMap;if(_n){const t=new Map,{prototype:{postMessage:n}}=Worker,o=i=>{const s=i.data?.[Ie];if(!Ze(s)){i.stopImmediatePropagation();const{id:a,sb:l}=s;t.get(a)(l)}};Eo=function(i,...s){const a=i?.[Ie];if(Ze(a)){const[l,c]=a;e.set(c,l),this.addEventListener("message",o)}return n.call(this,i,...s)},rt=i=>({value:new Promise(s=>{t.set(e.get(i),s)}).then(s=>{t.delete(e.get(i)),e.delete(i);for(let a=0;a({[Ie]:{id:n,sb:o}});vo=n=>{postMessage(t(e.get(n),n))},addEventListener("message",n=>{const o=n.data?.[Ie];if(Ze(o)){const[i,s]=o;e.set(s,i)}})}}/*! (c) Andrea Giammarchi - ISC */const{Int32Array:kt,Map:Wt,Uint16Array:Pt}=globalThis,{BYTES_PER_ELEMENT:Vt}=kt,{BYTES_PER_ELEMENT:En}=Pt,xn=(r,e,t)=>{for(;_o(r,0,0,e)==="timed-out";)t()},Mt=new WeakSet,wt=new WeakMap,Sn={value:{then:r=>r()}};let Ln=0;const Ot=(r,{parse:e=JSON.parse,stringify:t=JSON.stringify,transform:n,interrupt:o}=JSON)=>{if(!wt.has(r)){const i=Eo||r.postMessage,s=(p,...f)=>i.call(r,{[Ie]:f},{transfer:p}),a=typeof o===Ht?o:o?.handler,l=o?.delay||42,c=new TextDecoder("utf-16"),d=(p,f)=>p?rt(f,0):(a?xn(f,l,a):_o(f,0),Sn);let u=!1;wt.set(r,new Proxy(new Wt,{[wn]:(p,f)=>typeof f=="string"&&!f.startsWith("_"),[bn]:(p,f)=>f==="then"?null:((...h)=>{const m=Ln++;let g=new kt(new nt(Vt*2)),y=[];Mt.has(h.at(-1)||y)&&Mt.delete(y=h.pop()),s(y,m,g,f,n?h.map(n):h);const b=r!==globalThis;let E=0;return u&&b&&(E=setTimeout(console.warn,1e3,`πŸ’€πŸ”’ - Possible deadlock if proxy.${f}(...args) is awaited`)),d(b,g).value.then(()=>{clearTimeout(E);const L=g[1];if(!L)return;const x=En*L;return g=new kt(new nt(x+x%Vt)),s([],m,g),d(b,g).value.then(()=>e(c.decode(new Pt(g.buffer).slice(0,L))))})}),[vn](p,f,h){const m=typeof h;if(m!==Ht)throw new Error(`Unable to assign ${f} as ${m}`);if(!p.size){const g=new Wt;r.addEventListener("message",async y=>{const b=y.data?.[Ie];if(Ze(b)){y.stopImmediatePropagation();const[E,L,...x]=b;let w;if(x.length){const[S,P]=x;if(p.has(S)){u=!0;try{const T=await p.get(S)(...P);if(T!==void 0){const j=t(n?n(T):T);g.set(E,j),L[1]=j.length}}catch(T){w=T}finally{u=!1}}else w=new Error(`Unsupported action: ${S}`);L[0]=1}else{const S=g.get(E);g.delete(E);for(let P=new Pt(L.buffer),T=0;T(Mt.add(r),r);function Kt(){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 xo(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 i=[];let s=!1;for(;!s;){const d=await o.read();d.value&&i.push(d.value),s=d.done}const a=i.reduce((d,u)=>d+u.length,0),l=new Uint8Array(a);let c=0;return i.forEach(d=>{l.set(d,c),c+=d.length}),l.buffer}}else return t}class st{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 dt(async()=>{const{default:i}=await import("./index-DTMgZTfd.js");return{default:i}},[]);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 i of e){let s=o.get(i.sql);if(!s){const c=n.prepare(i.sql);o.set(i.sql,c),s=c}i.params?.length&&s.bind(i.params);let a=[],l=[];for(;s.step();)a=s.getColumnNames([]),l.push(s.get([]));t.push({columns:a,rows:l}),s.reset()}}finally{o.forEach(i=>{i.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 xo(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,i,s)=>{this.writeCallbacks.forEach(a=>{a({table:i,rowid:s,operation:e[n]})})},0)}closeDb(){this.db&&(this.db.close(),this.db=void 0)}}function Tn(r,e,t){let n,o,i,s,a,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,i=u?Math.max(Number(t.maxWait)||0,e):0,p="trailing"in t?!!t.trailing:p);function f(w){const S=n,P=o;return n=o=void 0,c=w,s=r.apply(P,S),s}function h(w){return c=w,a=setTimeout(y,e),d?f(w):s}function m(w){const S=w-(l??0),P=w-c,T=e-S;return u?Math.min(T,i-P):T}function g(w){const S=w-(l??0),P=w-c;return l===void 0||S>=e||S<0||u&&P>=i}function y(){const w=Date.now();if(g(w))return b(w);a=setTimeout(y,m(w))}function b(w){return a=void 0,p&&n?f(w):(n=o=void 0,s)}function E(){a!==void 0&&clearTimeout(a),c=0,n=l=o=a=void 0}function L(){return a===void 0?s:b(Date.now())}function x(){const w=Date.now(),S=g(w);if(n=arguments,o=this,l=w,S){if(a===void 0)return h(l);if(u)return a=setTimeout(y,e),f(l)}return a===void 0&&(a=setTimeout(y,e)),s}return x.cancel=E,x.flush=L,x}function Qe(){return crypto.randomUUID()}function So(r,e){switch(r){case"session":case":sessionStorage:":let t=sessionStorage._sqlocal_session_key;return t||(t=Qe(),sessionStorage._sqlocal_session_key=t),`session:${t}`;case"local":case":localStorage:":return"local";case":memory:":return`memory:${e}`;default:return`path:${r}`}}class Ve{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:Kt()}),Object.defineProperty(this,"transactionMutex",{enumerable:!0,configurable:!0,writable:!0,value:Kt()}),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 st,await this.driver.init(this.config)}const i=So(this.config.databasePath,this.config.clientKey);this.reinitChannel=new BroadcastChannel(`_sqlocal_reinit_(${i})`),this.reinitChannel.onmessage=s=>{const a=s.data;if(this.config.clientKey!==a.clientKey)switch(a.type){case"reinit":this.init(a.reason);break;case"close":this.driver.destroy();break}},this.config.reactive&&(this.effectsChannel=new BroadcastChannel(`_sqlocal_effects_(${i})`),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(i){this.emitMessage({type:"error",error:i,queryKey:null}),await this.destroy()}finally{await this.initMutex.unlock()}}}}),Object.defineProperty(this,"postMessage",{enumerable:!0,configurable:!0,writable:!0,value:async(o,i)=>{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,i=[])=>{this.onmessage&&this.onmessage(o,i)}}),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:Tn(()=>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 i={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 a=await this.driver.exec(o);i.data.push(a)}finally{s||await this.transactionMutex.unlock()}break;case"batch":try{await this.transactionMutex.lock();const a=await this.driver.execBatch(o.statements);i.data.push(...a)}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 a=o.action==="commit"?"COMMIT":"ROLLBACK";await this.driver.exec({sql:a}),this.transactionKey=null,await this.transactionMutex.unlock()}break}this.emitMessage(i)}catch(i){this.emitMessage({type:"error",error:i,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(i){this.emitMessage({type:"error",queryKey:o.queryKey,error:i})}}}),Object.defineProperty(this,"createUserFunction",{enumerable:!0,configurable:!0,writable:!0,value:async o=>{const{functionName:i,functionType:s,queryKey:a}=o;let l;if(this.userFunctions.has(i)){this.emitMessage({type:"error",error:new Error(`A user-defined function with the name "${i}" has already been created for this SQLocal instance.`),queryKey:a});return}switch(s){case"callback":l={type:s,name:i,func:(...c)=>{this.emitMessage({type:"callback",name:i,args:c})}};break;case"scalar":l={type:s,name:i,func:this.proxy[`_sqlocal_func_${i}`]};break;case"aggregate":l={type:s,name:i,func:{step:this.proxy[`_sqlocal_func_${i}_step`],final:this.proxy[`_sqlocal_func_${i}_final`]}};break}try{await this.initUserFunction(l),this.emitMessage({type:"success",queryKey:a})}catch(c){this.emitMessage({type:"error",error:c,queryKey:a})}}}),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:i,database:s}=o;let a=!1;try{await this.driver.import(s),this.driver.storageType==="memory"&&await this.execInitStatements()}catch(l){this.emitMessage({type:"error",error:l,queryKey:i}),a=!0}finally{this.driver.storageType!=="memory"&&await this.init("overwrite")}a||this.emitMessage({type:"success",queryKey:i})}}),Object.defineProperty(this,"exportDb",{enumerable:!0,configurable:!0,writable:!0,value:async o=>{const{queryKey:i}=o;try{const{name:s,data:a}=await this.driver.export();this.emitMessage({type:"buffer",queryKey:i,bufferName:s,buffer:a},[a])}catch(s){this.emitMessage({type:"error",error:s,queryKey:i})}}}),Object.defineProperty(this,"deleteDb",{enumerable:!0,configurable:!0,writable:!0,value:async o=>{const{queryKey:i}=o;let s=!1;try{await this.driver.clear()}catch(a){this.emitMessage({type:"error",error:a,queryKey:i}),s=!0}finally{await this.init("delete")}s||this.emitMessage({type:"success",queryKey:i})}}),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?Ot(globalThis):globalThis;this.proxy=n,this.driver=e}}function it(r,...e){return{sql:r.join("?"),params:e}}function kn(r){return!r.some(e=>!Array.isArray(e))}function vt(r,e){let t;return kn(r)?t=r:t=[r],t.map(n=>{const o={};return e.forEach((i,s)=>{o[i]=n[s]}),o})}function Pn(r){return typeof r=="object"&&r!==null&&"getSQL"in r&&typeof r.getSQL=="function"}function Mn(r){return typeof r=="object"&&r!==null&&"sql"in r&&typeof r.sql=="string"&&"params"in r}function Yt(r){if(typeof r=="function"&&(r=r(it)),Pn(r))try{if(!("toSQL"in r&&typeof r.toSQL=="function"))throw 1;const n=r.toSQL();if(!Mn(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 Xt(r,e){let t;return typeof r=="string"?t={sql:r,params:e}:t=it(r,...e),t}async function Ke(r,e,t,n){return!e&&"locks"in navigator?navigator.locks.request(`_sqlocal_mutation_(${t.databasePath})`,{mode:r},n):n()}class Jt extends st{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 dt(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 st;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 Lo,To;class In{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[f,h]=u.get(d.queryKey);d.type==="error"?h(d.error):f(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=>Ke("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=Qe();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 f=await this.createQuery({type:"query",transactionKey:p,sql:c,params:d,method:u}),h={rows:[],columns:[]};return f.type==="data"&&(h.rows=f.data[0]?.rows??[],h.columns=f.data[0]?.columns??[]),h}}),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,f)=>{u[f]=p}),u}}),Object.defineProperty(this,"sql",{enumerable:!0,configurable:!0,writable:!0,value:async(c,...d)=>{const u=Xt(c,d),{rows:p,columns:f}=await this.exec(u.sql,u.params,"all");return vt(p,f)}}),Object.defineProperty(this,"batch",{enumerable:!0,configurable:!0,writable:!0,value:async c=>{const d=c(it);return(await this.execBatch(d)).map(({rows:p,columns:f})=>vt(p,f))}}),Object.defineProperty(this,"beginTransaction",{enumerable:!0,configurable:!0,writable:!0,value:async()=>{const c=Qe();await this.createQuery({type:"transaction",transactionKey:c,action:"begin"});const d=async h=>{const m=Yt(h);if(m.exec)return this.transactionQueryKeyQueue.push(c),m.exec();const{rows:g,columns:y}=await this.exec(m.sql,m.params,"all",c);return vt(g,y)};return{query:d,sql:async(h,...m)=>{const g=Xt(h,m);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=>Ke("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,f=0;const h=Yt(c),m=new Set,g=new Set,y=new Set,b=async()=>{try{const L=++f;if(m.size===0){const w=await this.sql("SELECT name, wr FROM tables_used(?) WHERE type = 'table'",h.sql),S=new Set,P=new Set;if(w.forEach(T=>{typeof T.name=="string"&&(T.wr?P.add(T.name):S.add(T.name))}),S.size===0)throw new Error("The passed SQL does not read any tables.");if(Array.from(P).some(T=>S.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.");S.forEach(T=>m.add(T))}const x=h.exec?await h.exec():await this.sql(h.sql,...h.params);L===f&&(d=x,u=!0,g.forEach(w=>w(d)))}catch(L){y.forEach(x=>{x(L instanceof Error?L:new Error(String(L)))})}},E=L=>{L.data.tables.some(x=>m.has(x))&&b()};return{get value(){return d},subscribe:(L,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=w=>{throw w}),g.add(L),y.add(x),p?u&&L(d):(this.effectsChannel.addEventListener("message",E),p=!0,b()),{unsubscribe:()=>{g.delete(L),y.delete(x),g.size===0&&(this.effectsChannel?.removeEventListener("message",E),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 Ke("exclusive",!1,this.config,async()=>{try{this.broadcast({type:"close",clientKey:this.clientKey});const u=await xo(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 Ke("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,Lo,{enumerable:!0,configurable:!0,writable:!0,value:()=>{this.destroy()}}),Object.defineProperty(this,To,{enumerable:!0,configurable:!0,writable:!0,value:async()=>{await this.destroy()}});const t=typeof e=="string"?{databasePath:e}:e,{onInit:n,onConnect:o,processor:i,...s}=t,{databasePath:a}=s;this.config=t,this.clientKey=Qe();const l=So(a,this.clientKey);if(this.reinitChannel=new BroadcastChannel(`_sqlocal_reinit_(${l})`),s.reactive&&(this.effectsChannel=new BroadcastChannel(`_sqlocal_effects_(${l})`)),typeof i<"u")this.processor=i;else if(a==="local"||a===":localStorage:"){const c=new Jt("local");this.processor=new Ve(c)}else if(a==="session"||a===":sessionStorage:"){const c=new Jt("session");this.processor=new Ve(c)}else if(typeof globalThis.Worker<"u"&&a!==":memory:")this.processor=new Worker(new URL("/assets/worker-CuIBOSaM.js",import.meta.url),{type:"module"});else{const c=new st;this.processor=new Ve(c)}this.processor instanceof Ve?(this.processor.onmessage=c=>this.processMessageEvent(c),this.proxy=globalThis):(this.processor.addEventListener("message",this.processMessageEvent),this.proxy=Ot(this.processor)),this.processor.postMessage({type:"config",config:{...s,clientKey:this.clientKey,onInitStatements:n?.(it)??[]}})}}Lo=Symbol.dispose,To=Symbol.asyncDispose;const Rt="lupmis2.db",Cn="lupmis-db-sync",ko=new In(Rt),{sql:_}=ko;console.log("[Database] SQLocal instance created for:",Rt);const Po=new BroadcastChannel(Cn);let Mo=!1,Io,Co;const Zt=new Promise((r,e)=>{Io=r,Co=e}),at=new Set;function An(r){return at.add(r),()=>at.delete(r)}Po.onmessage=r=>{const{type:e,payload:t}=r.data;if(e==="DB_CHANGE")for(const n of at)try{n(t)}catch(o){console.error("[Database] Change listener error:",o)}};function xe(r,e,t=null){Po.postMessage({type:"DB_CHANGE",payload:{table:r,action:e,id:t,timestamp:Date.now()}});for(const n of at)try{n({table:r,action:e,id:t,timestamp:Date.now(),local:!0})}catch(o){console.error("[Database] Change listener error:",o)}}async function Dn(){try{console.log("[Database] Initializing schema...");const r=await _`SELECT sqlite_version() as version`;console.log("[Database] SQLite version:",r[0]?.version),console.log("[Database] Creating locations table..."),await _` 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 _`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 _` 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 _` 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 _` 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..."),await _` CREATE TABLE IF NOT EXISTS parcels ( id INTEGER PRIMARY KEY, geometry_wkt TEXT, properties TEXT, status TEXT DEFAULT 'verified', fetched_at TEXT DEFAULT CURRENT_TIMESTAMP ) `;try{await _`SELECT status FROM parcels LIMIT 1`}catch{console.log("[Database] Adding status column to parcels table..."),await _`ALTER TABLE parcels ADD COLUMN status TEXT DEFAULT 'verified'`}console.log("[Database] Creating building_footprints table..."),await _` 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 _` 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 _` 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 _` 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 _`CREATE INDEX IF NOT EXISTS idx_locations_category ON locations(category)`,await _`CREATE INDEX IF NOT EXISTS idx_locations_synced ON locations(synced)`,await _`CREATE INDEX IF NOT EXISTS idx_gps_trails_synced ON gps_trails(synced, status)`,await _`CREATE INDEX IF NOT EXISTS idx_gps_trail_points_trail ON gps_trail_points(trail_id, seq)`;const t=await _`SELECT name FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%'`;console.log("[Database] All tables:",t.map(n=>n.name)),Mo=!0,Io(!0),console.log("[Database] βœ“ Schema initialized")}catch(r){throw console.error("[Database] βœ— Schema init failed:",r),Co(r),r}}async function Fn(r,e,t,n={}){const{description:o=null,category:i="default"}=n;console.log("[Database] Adding location:",r,e,t,i);try{const s=await _`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 _` INSERT INTO locations (name, longitude, latitude, description, category) VALUES (${r}, ${e}, ${t}, ${o}, ${i}) `,console.log("[Database] INSERT completed");const l=(await _`SELECT last_insert_rowid() as id`)[0]?.id;console.log("[Database] New ID:",l);const c=await _`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 _` INSERT INTO sync_log (table_name, record_id, action) VALUES ('locations', ${l}, 'INSERT') `,xe("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 Ao(r={}){const{category:e=null,limit:t=1e3}=r;try{const n=await _`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 _` SELECT * FROM locations WHERE category = ${e} ORDER BY created_at DESC LIMIT ${t} `:o=await _` 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 On(){try{return(await _`SELECT COUNT(*) as count FROM locations`)[0]?.count??0}catch(r){return console.error("[Database] getLocationCount error:",r),0}}async function Do(r,e){try{const t=JSON.stringify(e);await _` 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 Fo(r){try{const e=await _`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 Rn(r){try{await _`DELETE FROM collector_zones`;for(const e of r){const t=JSON.stringify(e);await _` 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 Bn(){try{const r=await _`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}}async function Nn(r){try{await _`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=JSON.stringify(t),i=t.boundary||t.polygon||t.geom||t.wkt||"";await _` INSERT OR REPLACE INTO parcels (id, geometry_wkt, properties, fetched_at) VALUES (${n}, ${i}, ${o}, CURRENT_TIMESTAMP) `,e++}console.log("[Database] βœ“ Saved",e,"parcels (from",r.length,"rows,",r.length-e,"duplicates replaced)")}catch(e){throw console.error("[Database] βœ— Failed to save parcels:",e),e}}async function $n(){try{const r=await _`SELECT properties FROM parcels 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 parcels:",r),null}}async function Gn(r,e){try{const t=JSON.stringify(e);await _`UPDATE parcels SET properties = ${t} WHERE id = ${r}`,console.log("[Database] βœ“ Parcel updated:",r),xe("parcels","UPDATE",r)}catch(t){throw console.error("[Database] βœ— Failed to update parcel:",r,t),t}}async function qn(r,e){try{const t=JSON.stringify(e);await _` INSERT INTO parcels (id, geometry_wkt, properties, status, fetched_at) VALUES (NULL, ${r}, ${t}, 'new', CURRENT_TIMESTAMP) `;const o=(await _`SELECT last_insert_rowid() as id`)[0]?.id;return console.log("[Database] βœ“ New parcel inserted:",o,"(status: new)"),xe("parcels","INSERT",o),{id:o}}catch(t){throw console.error("[Database] βœ— Failed to insert new parcel:",t),t}}async function jn(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 _`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 i=e.id||e.footprint_id||e.building_id||null;await _` INSERT INTO building_footprints (id, geometry_wkt, properties, fetched_at) VALUES (${i!==null&&typeof i=="object"?null:i}, ${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 zn(){try{const r=await _`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 Un(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 _`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 i=e.osm_id??e.osmid??e.id??null;await _` INSERT OR REPLACE INTO osm_roads (osm_id, geometry_wkt, properties, fetched_at) VALUES (${i!==null&&typeof i=="object"?null:i}, ${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 Hn(){try{const r=await _`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 Wn(){return ko.getDatabaseFile()}async function Vn(r="lupmis-backup.sqlite3"){const e=await Wn(),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 Kn(){return{type:"FeatureCollection",features:(await Ao()).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 Bt(){try{const r=await _` SELECT name FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%' ORDER BY name `,e=await On();return{ready:Mo,databasePath:Rt,tables:r.map(t=>t.name),locationCount:e}}catch(r){return{ready:!1,error:r.message}}}const Oo=Object.freeze(["parcels","building_footprints","osm_roads","collector_zones","remote_data"]);function Ro(r){return Oo.includes(r)}async function Bo(r){if(!Ro(r))throw new Error(`Refusing to clear "${r}" β€” not a known cached-layer table`);const t=(await _(`SELECT COUNT(*) AS n FROM "${r}"`))[0]?.n??0;return await _(`DELETE FROM "${r}"`),console.log(`[Database] βœ“ Cleared "${r}" (${t} rows)`),xe(r,"CLEAR",null),t}async function Yn(){const r=await _` 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 Oo)if(e.has(o))try{const i=await Bo(o);t.push({table:o,count:i})}catch(i){console.error(`[Database] Failed to clear ${o}:`,i),t.push({table:o,count:0,error:i.message})}const n=t.reduce((o,i)=>o+i.count,0);return console.log(`[Database] βœ“ Cleared all cached layers: ${n} rows across ${t.length} tables`),t}async function Xn(){const r=await _` 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)}async function Jn(r,e=200){if((await _` SELECT name FROM sqlite_master WHERE type='table' AND name = ${r} `).length===0)throw new Error(`Table "${r}" does not exist`);const n=await _(`SELECT * FROM "${r}" LIMIT ${e}`);return{columns:n.length>0?Object.keys(n[0]):[],rows:n}}async function Zn(){console.log("=== DATABASE TEST ===");try{const r=await _`SELECT sqlite_version() as v`;console.log("1. SQLite version:",r[0].v);const e=await _`SELECT name FROM sqlite_master WHERE type='table'`;console.log("2. Tables:",e.map(o=>o.name)),console.log("3. Inserting test row..."),await _`INSERT INTO locations (name, longitude, latitude, category) VALUES ('TEST', -1.0, 7.0, 'test')`;const t=await _`SELECT * FROM locations WHERE name = 'TEST'`;console.log("4. Test row:",t);const n=await _`SELECT COUNT(*) as c FROM locations`;return console.log("5. Total rows:",n[0].c),await _`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=Zn,window.dbStatus=Bt);async function Qn(r){const{uuid:e,name:t=null,startedAt:n,districtId:o=null}=r;await _` INSERT INTO gps_trails (client_uuid, name, district_id, started_at, status) VALUES (${e}, ${t}, ${o}, ${n}, 'recording') `;const s=(await _`SELECT last_insert_rowid() as id`)[0]?.id;return xe("gps_trails","insert",s),s}async function er(r,e){const{seq:t,lon:n,lat:o,altitude:i=null,accuracy:s=null,altitudeAccuracy:a=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 _` INSERT INTO gps_trail_points (trail_id, seq, longitude, latitude, altitude, accuracy, altitude_accuracy, heading, speed, satellites, recorded_at) VALUES (${r}, ${t}, ${n}, ${o}, ${i}, ${s}, ${a}, ${l}, ${c}, ${d}, ${p}) `}async function tr(r,e){const{endedAt:t,pointCount:n=0,distanceM:o=0}=e;await _` UPDATE gps_trails SET ended_at = ${t}, point_count = ${n}, distance_m = ${o}, status = 'completed' WHERE id = ${r} `,xe("gps_trails","update",r)}async function or(){return _`SELECT * FROM gps_trails WHERE synced = 0 AND status = 'completed' ORDER BY started_at ASC`}async function nr(r){return _`SELECT * FROM gps_trail_points WHERE trail_id = ${r} ORDER BY seq ASC`}async function rr(r,e=null){await _`UPDATE gps_trails SET synced = 1, remote_id = ${e} WHERE id = ${r}`,xe("gps_trails","update",r)}const No=3.28084,$o=621371e-9,Go=10.7639,qo=247105e-9,jo=3861e-10;function ut(){return localStorage.getItem("measurement-system")||"metric"}function lt(r){if(ut()==="imperial"){const e=r*No;return e>=5280?Math.round(r*$o*100)/100+" mi":Math.round(e)+" ft"}return r>1e3?Math.round(r/1e3*100)/100+" km":Math.round(r*100)/100+" m"}function sr(r){if(ut()==="imperial"){const e=r*No,t=r*$o;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 je(r){if(ut()==="imperial"){const e=r*qo;return e>=640?Math.round(r*jo*100)/100+" miΒ²":e>=1?Math.round(e*100)/100+" acres":Math.round(r*Go).toLocaleString("en")+" ftΒ²"}return r>1e6?Math.round(r/1e6*100)/100+" kmΒ²":Math.round(r*100)/100+" mΒ²"}function ir(r){if(ut()==="imperial"){const e=r*Go,t=r*qo,n=r*jo;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 ar(r){return je(Math.PI*r*r)}function lr(r,e,t,n,o=1e-10){const i=e[0]-r[0],s=e[1]-r[1],a=n[0]-t[0],l=n[1]-t[1],c=i*l-s*a;if(Math.abs(c)1+o||f<-o||f>1+o?null:{point:[r[0]+p*i,r[1]+p*s],t:Math.max(0,Math.min(1,p)),u:Math.max(0,Math.min(1,f))}}function zo(r){let e=0;for(let t=0,n=r.length;tr[1]!=l>r[1]&&r[0]<(a-i)*(r[1]-s)/(l-s)+i&&(t=!t)}return t}function He(r,e){return(r[0]-e[0])**2+(r[1]-e[1])**2}function cr(r,e){const t=[];for(let o=0;oo.lineSegIdx!==i.lineSegIdx?o.lineSegIdx-i.lineSegIdx:o.lineT-i.lineT),t}function dr(r,e){const t=e.map((i,s)=>({...i,origOrder:s}));t.sort((i,s)=>i.ringSegIdx!==s.ringSegIdx?i.ringSegIdx-s.ringSegIdx:i.ringT-s.ringT);const n=r.slice(),o=new Array(t.length);for(let i=t.length-1;i>=0;i--){const s=t[i],a=s.ringSegIdx+1,l=1e-6;if(He(s.point,n[s.ringSegIdx])=a&&o[t[c].origOrder]++}return{ring:n,indices:o}}function Qt(r,e,t){const n=r.length-1,o=(e%n+n)%n,i=(t%n+n)%n,s=[];let a=o;for(;s.push(r[a]),a!==i;)a=(a+1)%n;return s}function eo(r,e,t){const n=[e.point],o=e.lineSegIdx,i=t.lineSegIdx;for(let s=o+1;s<=i;s++)n.push(r[s]);return He(n[n.length-1],t.point)>1e-10&&n.push(t.point),n}function to(r,e){const t=zo(r);return e&&t<0||!e&&t>0?r.slice().reverse():r}function oo(r){if(r.length<2)return r;const e=r[0],t=r[r.length-1];return He(e,t)>1e-10?[...r,e.slice()]:r}function ur(r,e){let t=1/0,n=1/0,o=-1/0,i=-1/0;for(const c of e)c[0]o&&(o=c[0]),c[1]>i&&(i=c[1]);const s=Math.sqrt((o-t)**2+(i-n)**2)||1,a=r.slice();if(It(a[0],e)){const c=a[0],d=a[1],u=c[0]-d[0],p=c[1]-d[1],f=Math.sqrt(u*u+p*p)||1,h=s*2/f;a[0]=[c[0]+u*h,c[1]+p*h]}const l=a.length-1;if(It(a[l],e)){const c=a[l],d=a[l-1],u=c[0]-d[0],p=c[1]-d[1],f=Math.sqrt(u*u+p*p)||1,h=s*2/f;a[l]=[c[0]+u*h,c[1]+p*h]}return a}function et(r,e){const t=r[0],n=r.slice(1),o=ur(e,t),i=cr(t,o);if(i.length!==2)return console.warn(`[polygonSplit] Expected 2 intersections, got ${i.length}`),null;const[s,a]=i,{ring:l,indices:c}=dr(t,i),d=c[0],u=c[1],[p,f]=d0,x=to(y,L),w=to(E,L),S=[x],P=[w];for(const T of n){const j=pr(T);It(j,x)?S.push(T):P.push(T)}return[S,P]}function pr(r){let e=0,t=0;const n=r.length-1;for(let o=0;o{i.style.opacity="1",i.style.transform="translateY(0)"});const s=()=>{i.style.opacity="0",i.style.transform="translateY(-8px)",setTimeout(()=>i.remove(),300)};i.addEventListener("click",s),setTimeout(s,t)}const Ye=[{stroke:"#ef4444",fill:"rgba(239,68,68,0.25)"},{stroke:"#3b82f6",fill:"rgba(59,130,246,0.25)"}],fr=new M({stroke:new k({color:"#0ea5e9",width:3}),fill:new I({color:"rgba(14,165,233,0.15)"})}),gr=new M({stroke:new k({color:"#f43f5e",width:2,lineDash:[8,6]}),image:new re({radius:5,fill:new I({color:"#f43f5e"}),stroke:new k({color:"#fff",width:1.5})})});class mr extends Dt{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 N({useSpatialIndex:!1}),this._overlayLayer=new F({source:this._overlaySource,displayInLayerSwitcher:!1,style:fr})}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 N?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 i=o.getClosestFeatureToCoordinate(e.coordinate);if(!i)continue;const s=i.getGeometry();if(!s)continue;const a=s.getType();if(a!=="Polygon"&&a!=="MultiPolygon")continue;const l=s.getClosestPoint(e.coordinate),d=new se([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 i;o.getType()==="Polygon"?i=o.getCoordinates():o.getType()==="MultiPolygon"&&(i=o.getCoordinates()[0]);const s=et(i,e);if(!s){console.warn("[PolygonSplit] Split failed β€” line must cross the polygon boundary at exactly 2 points."),this._removeDrawInteraction(),this._startDrawPhase();return}const[a,l]=s,c=t.clone();c.setGeometry(new Ue(a)),c.setStyle(new M({stroke:new k({color:Ye[0].stroke,width:2.5}),fill:new I({color:Ye[0].fill})}));const d=t.clone();d.setGeometry(new Ue(l)),d.setStyle(new M({stroke:new k({color:Ye[1].stroke,width:2.5}),fill:new I({color:Ye[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 f=this.getMap();f&&(f.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 i=o.getGeometry();if(!i)continue;const s=i.getClosestPoint(e.coordinate),l=new se([e.coordinate,s]).getLength()/e.frameState.viewState.resolution;lr[1]!=l>r[1]&&r[0]<(a-i)*(r[1]-s)/(l-s)+i&&(t=!t)}return t}function br(r,e){const t=Ge(r);return e&&t<0||!e&&t>0?r.slice().reverse():r}function wr(r){return r.length<2?r:Ae(r[0],r[r.length-1])>1e-10?[...r,r[0].slice()]:r}function Ce(r,e,t){const n=t[0]-e[0],o=t[1]-e[1],i=n*n+o*o;if(i<1e-20)return Ae(r,e);let s=((r[0]-e[0])*n+(r[1]-e[1])*o)/i;s=Math.max(0,Math.min(1,s));const a=e[0]+s*n,l=e[1]+s*o;return(r[0]-a)**2+(r[1]-l)**2}function ro(r,e){let t=0,n=1/0;const o=r.length-1;for(let i=0;i0;){const w=(b+1)%i,S=g?(L-1+s)%s:(L+1)%s;if(w===y||S===E)break;if(ke(r[w],e[S],a)){b=w,L=S;continue}if(Ce(r[w],e[L],e[S])0;){const w=(y-1+i)%i,S=g?(E+1)%s:(E-1+s)%s;if(w===b||S===L)break;if(ke(r[w],e[S],a)){y=w,E=S;continue}if(Ce(r[w],e[E],e[S])n+1)););return o}function _r(r,e,t,n,o=5){const i=r[0],s=e[0],a=r.slice(1),l=e.slice(1),c=ro(i,t),d=ro(s,n),u=vr(i,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:f,startB:h,endB:m,reversed:g}=u;i.length-1,s.length-1;const y=_t(i,f,p);let b;g?b=_t(s,h,m):b=_t(s,m,h);const E=[...y,...b.slice(1)],L=o*o;E.length>2&&Ae(E[E.length-1],E[0])T*1.5)return console.warn(`[polygonMerge] Area mismatch: A=${w.toFixed(1)}, B=${S.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 j=Ge(i)>0,U=br(x,j),A=[...a,...l].filter(ee=>{const K=ee.reduce((G,ce)=>G+ce[0],0)/(ee.length-1),le=ee.reduce((G,ce)=>G+ce[1],0)/(ee.length-1);return yr([K,le],U)});return{coords:[U,...A]}}const so=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:"#f59e0b",width:3}),fill:new I({color:"rgba(245,158,11,0.15)"})}),xr=new M({stroke:new k({color:"#0ea5e9",width:3}),fill:new I({color:"rgba(14,165,233,0.15)"}),text:new tt({text:"A",font:"bold 22px Exo, sans-serif",fill:new I({color:"#0ea5e9"}),stroke:new k({color:"#fff",width:4}),overflow:!0})}),Sr=new M({stroke:new k({color:"#f59e0b",width:3}),fill:new I({color:"rgba(245,158,11,0.15)"}),text:new tt({text:"B",font:"bold 22px Exo, sans-serif",fill:new I({color:"#f59e0b"}),stroke:new k({color:"#fff",width:4}),overflow:!0})}),Lr=new M({stroke:new k({color:"#ec4899",width:4,lineDash:[10,6]})}),Tr=new M({stroke:new k({color:"#10b981",width:2.5}),fill:new I({color:"rgba(16,185,129,0.3)"})});class kr extends Dt{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 N({useSpatialIndex:!1}),this._highlightLayer=new F({source:this._highlightSource,displayInLayerSwitcher:!1,style:t=>t.get("_highlightStyle")||so}),this._edgeSource=new N({useSpatialIndex:!1}),this._edgeLayer=new F({source:this._edgeSource,displayInLayerSwitcher:!1,style:Lr})}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 N?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 i=this._phase==="select_a"?so:Er,s=o.feature.clone();s.set("_highlightStyle",i),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 i of this._getSources()){const s=i.getClosestFeatureToCoordinate(e.coordinate);if(!s||t&&s===t)continue;const a=s.getGeometry();if(!a)continue;const l=a.getType();if(l!=="Polygon"&&l!=="MultiPolygon")continue;const c=a.getClosestPoint(e.coordinate),u=new se([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",xr),t.set("_permanent",!0),this._highlightSource.addFeature(t)}if(this._featureB){const t=this._featureB.clone();t.set("_highlightStyle",Sr),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 Pr(r,e){return(r[0]-e[0])**2+(r[1]-e[1])**2}function io(r){let e=0;for(let t=0,n=r.length;tt&&(t=d,n=c)}const o=r[n],i=r[n+1],s=Math.sqrt(t),a=[(i[0]-o[0])/s,(i[1]-o[1])/s],l=[-a[1],a[0]];return{p0:o,p1:i,along:a,perp:l}}function Et(r,e,t,n,o){const i=r[0]+n*e[0],s=r[1]+n*e[1];return[[i-o*t[0],s-o*t[1]],[i+o*t[0],s+o*t[1]]]}function Pe(r,e,t){const n=r[0],o=n.length-1;let i=0,s=0;for(let c=0;cu&&(u=E)}const p=(u-d)*1.5,f=[];let h=r,m=e;for(let g=0;gw&&(w=K)}let S=x,P=w,T=null,j=null,U=1/0;for(let V=0;V<40;V++){const A=(S+P)/2,ee=Et(l,s,a,A,p),K=et(h,ee);if(!K){const C=(P-S)*.01,q=Et(l,s,a,A+C,p),J=et(h,q);if(J){const[me,ye]=J,Fe=Pe(me,l,s),Oe=Pe(ye,l,s),Re=Fethis._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 N({useSpatialIndex:!1}),this._overlayLayer=new F({source:this._overlaySource,displayInLayerSwitcher:!1,style:Cr}),this._edgeSource=new N({useSpatialIndex:!1}),this._edgeLayer=new F({source:this._edgeSource,displayInLayerSwitcher:!1,style:Ar})}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 N?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 i=o.getClosestFeatureToCoordinate(e.coordinate);if(!i)continue;const s=i.getGeometry();if(!s)continue;const a=s.getType();if(a!=="Polygon"&&a!=="MultiPolygon")continue;const l=s.getClosestPoint(e.coordinate),d=new se([e.coordinate,l]).getLength()/e.frameState.viewState.resolution;d{const h=t.clone();return h.setGeometry(new Ue(p)),h.setStyle(new M({stroke:new k({color:a[f].stroke,width:2.5}),fill:new I({color:a[f].fill})})),h}),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 i=o.getGeometry();if(!i)continue;const s=i.getClosestPoint(e.coordinate),l=new se([e.coordinate,s]).getLength()/e.frameState.viewState.resolution;l{const l=this.categoryEmojis[a];return l?l.emoji:"πŸ“"},this.getCategoryOptionsHtml=()=>Object.entries(this.categoryEmojis).map(([a,{emoji:l,label:c}])=>``).join(` `),this.createEmojiStyle=(a,l=24)=>new M({text:new tt({text:a,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[a,{emoji:l}]of Object.entries(this.categoryEmojis))this.categoryStyles[a]=this.createEmojiStyle(l,32);const n=this.createBaseLayers(t.basemap||"topo");this.markersLayer=new F({title:"Markers",source:this.markerSource,style:a=>this.getFeatureStyle(a),visible:!1}),this.overlayGroup=new be({title:"Overlays"}),this.map=new qt({target:e,layers:[n,this.markersLayer,this.overlayGroup],view:new on({center:te(t.center||[0,0]),zoom:t.zoom||2,minZoom:t.minZoom||2,maxZoom:t.maxZoom||19})});const o=new un({collapsed:!0,mouseover:!0,extent:!0,trash:!1,oninfo:null});this.map.addControl(o),queueMicrotask(()=>{const a=o.element?.querySelector(":scope > button");if(a){const l="/".replace(/\/?$/,"/");a.style.backgroundImage=`url('${l}app-icons/luspa-72x72.png')`}});let i=!1;o.on("drawlist",a=>{this._decorateLayerListItem(a.layer,a.li),i||(i=!0,queueMicrotask(()=>{i=!1,this._refreshLayerSwitcherChrome(o)}))}),this.map.getLayers().on("change",()=>{this._refreshLayerSwitcherChrome(o)}),this._wireLayerSwitcherVisibilityHooks(o),this._createAddLayerDialog(),this._createLegendPanel(),this.scaleBar=new nn({bar:!0,steps:4,text:!0,minWidth:140}),this.map.addControl(this.scaleBar),this._initGpsRendering(),this._createLocationControl(),this._createBaseMapPicker();const s=new pn({placeholder:"Search location...",typing:300,minLength:3,maxItems:10,collapsed:!0});this.map.addControl(s),s.on("select",a=>{const l=a.search;if(l){const c=parseFloat(l.lon),d=parseFloat(l.lat),u=[c,d],p=te(u);this.navigateTo(c,d,14);const f={coordinate:p,lonLat:u,name:l.display_name||l.name||"Unknown",searchResult:l};this.searchSelectCallbacks.forEach(h=>h(f))}}),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 N,this.drawingsLayer=new F({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 re({radius:6,fill:new I({color:"#f59e0b"}),stroke:new k({color:"#fff",width:1.5})})})}),this._drawingsGroup=new be({title:"Drawings",layers:[this.drawingsLayer]});const e=this.map.getLayers(),t=e.getLength()-1;e.insertAt(t,this._drawingsGroup),this._selectInteraction=new rn({condition:sn,filter:(h,m)=>!!m,layers:h=>h instanceof F}),this._selectInteraction.setActive(!1),this.map.addInteraction(this._selectInteraction),this._modifyInteraction=new hn({features:this._selectInteraction.getFeatures()}),this._modifyInteraction.setActive(!1),this._undoRedo=new fn,this.map.addInteraction(this._undoRedo),this.editBar=new Tt({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 Ut({group:!0,className:"ol-editbar-actions",controls:[new $e({html:'',className:"ol-undo",title:"Undo",handleClick:()=>{this._undoRedo.hasUndo()&&this._undoRedo.undo()}}),new $e({html:'',className:"ol-redo",title:"Redo",handleClick:()=>{this._undoRedo.hasRedo()&&this._undoRedo.redo()}}),new $e({html:'',className:"ol-save",title:"Save drawings",handleClick:()=>{this.dispatchEditEvent("save")}})]});this.editBar.addControl(n),this._lineSplitInteraction=new gn,this._polygonSplitInteraction=new mr,this.map.addInteraction(this._lineSplitInteraction),this.map.addInteraction(this._polygonSplitInteraction),this._lineSplitInteraction.setActive(!1),this._polygonSplitInteraction.setActive(!1),this._polygonSplitInteraction.on("splitpick",h=>{const m=["UPN","upn","id","parcelid","parcel_id","PARCELID","PARCEL_ID","ID"];for(const g of h.features)if(g!==h.picked)for(const y of m)g.get(y)!==void 0&&g.set(y,"")}),this._polygonDivideInteraction=new Fr,this.map.addInteraction(this._polygonDivideInteraction),this._polygonDivideInteraction.setActive(!1);const o=new fe({html:'',className:"ol-split-line",title:"Split Lines",name:"SplitLine",interaction:this._lineSplitInteraction,autoActivate:!0}),i=new fe({html:'',className:"ol-split-polygon",title:"Split Polygons",name:"SplitPolygon",interaction:this._polygonSplitInteraction}),s=new fe({html:'',className:"ol-split-divide",title:"Divide Polygon",name:"DividePolygon",interaction:this._polygonDivideInteraction}),a=new Ut({toggleOne:!0,autoDeactivate:!0,controls:[o,i,s]}),l=new fe({className:"ol-split",title:"Split",name:"Split",bar:a,onToggle:h=>{h||(this._lineSplitInteraction.setActive(!1),this._polygonSplitInteraction.setActive(!1),this._polygonDivideInteraction.setActive(!1))}});this.editBar.addControl(l),this._polygonDivideInteraction.on("divideform",h=>{this.showDividePopup(h.feature,h.source,h.coordinate)}),this._polygonDivideInteraction.on("dividecancel",()=>{this.hideDividePopup()}),this._polygonDivideInteraction.on("dividepick",h=>{const m=["UPN","upn","id","parcelid","parcel_id","PARCELID","PARCEL_ID","ID"];for(const g of h.features)if(g!==h.picked)for(const y of m)g.get(y)!==void 0&&g.set(y,"")}),this._polygonMergeInteraction=new kr,this.map.addInteraction(this._polygonMergeInteraction),this._polygonMergeInteraction.setActive(!1);const c=new fe({html:'',className:"ol-merge",title:"Merge Polygons",name:"Merge",interaction:this._polygonMergeInteraction});this.editBar.addControl(c),this._polygonMergeInteraction.on("mergedparcel",h=>{this.showMergeIdentifierPopup(h.merged,h.propsA,h.propsB,h.coordinate)});const d=this.editBar.element;if(d&&n.element&&n.element.parentNode===d){const h=document.createElement("div");h.className="ol-editbar-break",d.insertBefore(h,n.element)}this._snapGuidesEnabled=localStorage.getItem("snap-guides-enabled")==="1",this._snapGuides=new mn({pixelTolerance:10,vectorClass:an}),this.map.addInteraction(this._snapGuides);const u=["DrawPoint","DrawLine","DrawPolygon","DrawHole","DrawRegular"];for(const h of u){const m=this.editBar.getInteraction(h);m&&m.on("change:active",()=>{m.getActive()&&this._snapGuides.setDrawInteraction(m)})}this._modifyInteraction&&this._snapGuides.setModifyInteraction(this._modifyInteraction);const p=new $e({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 h=this._drawingsGroup.getVisible();this.setEditMode(h)}),("ontouchstart"in window||navigator.maxTouchPoints>0||navigator.msMaxTouchPoints>0)&&(this.touchCursor=new yn({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",h=>{const m=h.feature,g=m.getGeometry();if(!g||g.getType()!=="Polygon")return;const y=g.getInteriorPoint().getCoordinates();this.showDrawnPolygonPopup(m,y)}),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 N,this._vertexOverlayLayer=new F({title:"__vertex_highlight__",source:this._vertexOverlaySource,zIndex:990,style:new M({image:new re({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 i=this._collectAllVertices(n);for(const s of i)this._vertexOverlaySource.addFeature(new ne(new We(s)));t.on("change",this._onSelectedFeatureGeomChange),this._vertexTrackedFeatures.add(t)}}_collectAllVertices(e){const t=[],n=a=>Array.isArray(a)&&typeof a[0]=="number",o=(a,l)=>{const c=l&&a.length>1?a.length-1:a.length;for(let d=0;d{if(n(l))t.push(l);else if(Array.isArray(l))for(const c of l)a(c)};a(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 he({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",i=e.get("description"),s=e.get("lon"),a=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}
`,i&&(c+=`
${this.escapeHtml(i)}
`),s!==void 0&&a!==void 0&&(c+=`
${Number(s).toFixed(5)}, ${Number(a).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 he({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:i="#e11d48"}=n,s=e.getProperties(),a=e.getGeometry(),l=a.getType(),c=["geometry","_layerType"];let d="";for(const[p,f]of Object.entries(s))c.includes(p)||f===void 0||f===null||(d+=` ${this.escapeHtml(p)} ${this.escapeHtml(String(f))} `);if(l==="Polygon"||l==="MultiPolygon"){const p=qe(a,{projection:"EPSG:3857"}),f=ir(p);d+=` area ${f} `}else if(l==="LineString"||l==="MultiLineString"){const p=ot(a,{projection:"EPSG:3857"}),f=sr(p);d+=` length ${f} `}else if(l==="Point"){const p=Le(a.getCoordinates()),f=p[0].toFixed(6),h=p[1].toFixed(6);d+=` longitude ${f} latitude ${h} `}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 i=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:i.map(s=>this.escapeHtml(s)).join(", "),color:"#7c3aed"})}for(const[i,s]of Object.entries(n))o.push({label:this.escapeHtml(i),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 i of n){if(i.empty){o+=` ${i.value} `;continue}const s=i.color||"var(--muted-foreground, #7a7a7a)",a=i._first?"":"border-top:1px solid var(--border, #1e1a4b1f);";o+=` ${i.label} ${i.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 i=n.filter(s=>!s.empty).map(s=>({label:s.label,value:s.value.replace(/<[^>]*>/g,"")}));dt(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:i})}).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=ln(n,64),i=o.getExtent(),s=e.get("_radius")||n.getRadius(),a=[],l=[],c={},d=g=>{const y=g.getGeometry();if(!y)return!1;const b=y.getExtent();return b[2]i[2]||b[3]i[3]?!1:o.intersectsExtent(b)&&this._geometriesIntersect(o,y)},u=(g,y)=>{g.getLayers().forEach(b=>{if(b instanceof be)u(b,b.get("title")||y);else if(b instanceof F&&b.getVisible()){const E=b.get("title")||y||"Unknown",L=b.getSource();if(!L)return;const x=L.getFeaturesInExtent(i);for(const w of x){const S=w.get("_layerType");S==="measure_circle"||S==="measure_circle_radius"||d(w)&&(S==="parcel"?a.push(w):S==="collector_zone"?l.push(w):(c[E]||(c[E]=[]),c[E].push(w)))}}})};u(this.overlayGroup,"Overlays");const p=lt(s),f=Math.PI*s*s,h=je(f),m=[{label:"Radius",value:p,_first:!0},{label:"Area",value:h},...this._collectIntersectionRows(a,l,c)];this._showAnalysisPopup("β­•","Circle Analysis",m,t)}showAreaIntersectionPopup(e,t){const n=e.getGeometry();if(!n)return;const o=n.getExtent(),i=qe(n,{projection:"EPSG:3857"}),s=je(i),a=ot(n,{projection:"EPSG:3857"}),l=lt(a),c=[],d=[],u={},p=m=>{const g=m.getGeometry();if(!g)return!1;const y=g.getExtent();return y[2]o[2]||y[3]o[3]?!1:n.intersectsExtent(y)&&this._geometriesIntersect(n,g)},f=(m,g)=>{m.getLayers().forEach(y=>{if(y instanceof be)f(y,y.get("title")||g);else if(y instanceof F&&y.getVisible()){const b=y.get("title")||g||"Unknown",E=y.getSource();if(!E)return;const L=E.getFeaturesInExtent(o);for(const x of L){const w=x.get("_layerType");w==="measure_area"||w==="measure_circle"||w==="measure_circle_radius"||p(x)&&(w==="parcel"?c.push(x):w==="collector_zone"?d.push(x):(u[b]||(u[b]=[]),u[b].push(x)))}}})};f(this.overlayGroup,"Overlays");const h=[{label:"Area",value:s,_first:!0},{label:"Perimeter",value:l},...this._collectIntersectionRows(c,d,u)];this._showAnalysisPopup("πŸ“","Area Analysis",h,t)}_geometriesIntersect(e,t){const n=t.getType();if(n==="Polygon"||n==="MultiPolygon"){const o=t.getFlatCoordinates(),i=t.getStride();for(let l=0;l `}const s=`
✏️ Edit Parcel
${i}
`;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 a=this.parcelEditElement.querySelector(".parcel-edit-form");a.addEventListener("submit",l=>{l.preventDefault();const c=new FormData(a),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 he({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 i=["UPN","upn","id","parcelid","parcel_id","PARCELID","PARCEL_ID","ID"],s=h=>{for(const m of i)if(h[m]!==void 0&&h[m]!==null&&String(h[m]).trim())return{field:m,value:String(h[m])};return{field:"id",value:"Unknown"}},a=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 m=this.mergePopupElement.querySelector('input[name="merge-choice"]:checked').value==="A"?t:n,g=["geometry"];for(const[y,b]of Object.entries(m))g.includes(y)||e.set(y,b);e.set("_layerType","parcel");for(const y of this._parcelEditCallbacks)y(e,m);d()});const u=this.mergePopupElement.querySelectorAll("label"),p=this.mergePopupElement.querySelectorAll('input[name="merge-choice"]'),f=()=>{u.forEach(h=>{const m=h.querySelector("input");h.style.borderColor=m.checked?m.value==="A"?"#0ea5e9":"#f59e0b":"var(--border, #1e1a4b1f)"})};p.forEach(h=>h.addEventListener("change",f)),f()}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 he({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 i=this.dividePopupElement.querySelector(".divide-input");i.focus(),i.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 a=parseInt(i.value,10);if(!a||a<2){i.style.borderColor="#ef4444";return}this.hideDividePopup(),this._polygonDivideInteraction.performDivide(a)}),i.addEventListener("keydown",a=>{a.key==="Enter"&&(a.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 he({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(i=>{if(!(t.length>0)){if(i instanceof be)n(i);else if(i instanceof F){const s=i.getSource();if(!s)return;for(const a of s.getFeatures()){if(a.get("_layerType")!=="parcel")continue;const l=a.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 i=e.getGeometry(),s=qe(i,{projection:"EPSG:3857"}),l=`
πŸ“ Polygon Attributes
Area: ${je(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[f,h]of u.entries())p[f]=h;for(const[f,h]of Object.entries(p))this._drawnPolygonFeature.set(f,h);this._drawnPolygonFeature.set("_layerType","parcel");for(const f of this._drawnPolygonCallbacks)f(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]=Le(t.coordinate);let i=null;this.map.forEachFeatureAtPixel(t.pixel,s=>(i=s,!0)),i&&(t.preventDefault(),t.stopPropagation());for(const s of this.dblClickCallbacks)s(n,o,i,t);if(i)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 he({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]=Le(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),i={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(i)),this.hideAddLocationPopup()})}}createBaseLayers(e){const t=new Z({title:"Topographic",type:"base",zIndex:-100,visible:e==="topo",source:new we({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 Z({title:"Carto Light",type:"base",zIndex:-100,visible:e==="carto-light",source:new we({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 Z({title:"Carto Dark",type:"base",zIndex:-100,visible:e==="carto-dark",source:new we({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 i=new Z({title:"OSM Cycle map",type:"base",zIndex:-100,visible:!1,source:new jt({url:"https://tile.thunderforest.com/cycle/{z}/{x}/{y}.png?apikey=ae1339c46dd3446b9c491e7336d38760"})});i.set("basemapKey","cycle");const s=new Z({title:"Satellite",type:"base",zIndex:-100,visible:e==="satellite",source:new we({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 a=new Z({title:"Google Sat",type:"base",zIndex:-100,visible:e==="googlesat",source:new we({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"})});a.set("basemapKey","googlesat");const l=new Z({title:"OpenStreetMap",type:"base",zIndex:-100,visible:e==="osm",source:new jt});l.set("basemapKey","osm"),this._baseMapLayers=[n,o,i,s,a,l,t];const c=new be({title:"Base Maps",layers:[n,o,s,i,a,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 i=s=>{const a=s||this._baseMapLayers?.find(l=>l.getVisible())?.get("basemapKey");o.querySelectorAll('input[name="lupmis-basemap"]').forEach(l=>{l.checked=l.value===a})};i(),n.addEventListener("click",s=>{s.stopPropagation();const a=!o.classList.contains("open");o.classList.toggle("open",a),n.classList.toggle("active",a),a&&i()}),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 a=s.target.closest('input[type=radio][name="lupmis-basemap"]');if(!a)return;const l=a.value;this.setBaseMap(l);try{localStorage.setItem("default-basemap",l)}catch{}o.classList.remove("open"),n.classList.remove("active")}),this.map.on("basemapchange",s=>i(s.key))}_initGpsRendering(){this._gpsPositionSource=new N,this._gpsTrailSource=new N,this._gpsTrailCoords=[],this._gpsTrailLayer=new F({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 F({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 re({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=te([e,t]);if(this._gpsPositionSource.clear(),n&&n>0){const s=n/Math.cos(t*Math.PI/180),a=new ne({geometry:new Ue([this._circleRing(o,s)])});a.set("_kind","accuracy"),this._gpsPositionSource.addFeature(a)}const i=new ne({geometry:new We(o)});i.set("_kind","dot"),this._gpsPositionSource.addFeature(i)}_circleRing(e,t,n=48){const o=[],s=t/1;for(let a=0;a<=n;a++){const l=a/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:te([e,t]),zoom:n,duration:500})}startTrailRender(){this._gpsTrailCoords=[],this._gpsTrailSource.clear()}appendTrailPoint(e,t){e==null||t==null||(this._gpsTrailCoords.push(te([e,t])),this._gpsTrailSource.clear(),this._gpsTrailCoords.length>=2&&this._gpsTrailSource.addFeature(new ne({geometry:new se(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")},i=()=>{n.classList.add("open"),t.classList.add("active")};t.addEventListener("click",s=>{s.stopPropagation(),n.classList.contains("open")?o():i()}),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 a of this._gpsCallbacks.locate)try{a()}catch(l){console.error(l)}this._gpsRecording||o()}),this._recordBtn.addEventListener("click",s=>{s.stopPropagation();const a=!this._gpsRecording;for(const l of this._gpsCallbacks.record)try{l(a)}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 re({radius:22,fill:new I({color:"rgba(220, 38, 38, 0.25)"}),stroke:new k({color:"#dc2626",width:3})})}),new M({text:new tt({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),i=n.fontSize||28;this.categoryStyles[t]=this.createEmojiStyle(o,i)}this.markerSource.changed()}addMarker(e,t,n={}){console.log("[MapView] Adding marker at",e,t,"with properties:",n);const o=new ne({geometry:new We(te([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 ne({geometry:new We(te([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:te([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 Le(e)}getZoom(){return this.map.getView().getZoom()}setCenter(e,t){this.map.getView().setCenter(te([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,i=null;if(this.map.forEachFeatureAtPixel(t.pixel,l=>{l.get("_layerType")==="parcel"&&(o=!0),l.get("name")&&(i=l),n=!0}),n&&!o&&!i)return;const[s,a]=Le(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,a,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]=Le(t.coordinate);let i=null;this.map.forEachFeatureAtPixel(t.pixel,s=>{if(s.get("name"))return i=s,!0}),this.map.getTargetElement().style.cursor=i?"pointer":"",e(n,o,i,t)})}enableHoverCursor(){}addGeoJSONLayer(e,t,n={},o=null){const{strokeColor:i="#3b82f6",strokeWidth:s=2,fillColor:a="rgba(59,130,246,0.1)",lineCasingColor:l=null,lineCasingWidth:c=null,pointRadius:d=5,pointFillColor:u=null,pointStrokeColor:p="#ffffff",pointStrokeWidth:f=1.5}=n,h=new N({features:new ae().readFeatures(e,{featureProjection:"EPSG:3857"})}),m=new I({color:a}),g=new re({radius:d,fill:new I({color:u||i}),stroke:new k({color:p,width:f})});let y;if(l){const x=c??s+2;y=[new M({stroke:new k({color:l,width:x})}),new M({stroke:new k({color:i,width:s}),fill:m,image:g})]}else y=new M({stroke:new k({color:i,width:s}),fill:m,image:g});const b=new F({title:t,source:h,style:y});b.set("typeTag",n.typeTag||"VEC");const E=x=>x?x.includes("Polygon")?"Vector / Polygon":x.includes("LineString")?"Vector / Line":x.includes("Point")?"Vector / Point":"Vector":null;if(n.typeDescription)b.set("typeDescription",n.typeDescription);else{const x=h.getFeatures(),w=E(x[0]?.getGeometry?.()?.getType?.());if(w)b.set("typeDescription",w);else{const S=P=>{const T=E(P.feature.getGeometry?.()?.getType?.());T&&b.set("typeDescription",T),h.un("addfeature",S)};h.on("addfeature",S)}}return(o||this.overlayGroup).getLayers().push(b),console.log("[MapView] GeoJSON layer added:",t,"β†’",h.getFeatures().length,"features",o?`(in group "${o.get("title")}")`:""),b}addLayerGroup(e,t,n=""){const o=new be({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,i={}){const s=this.getLayerGroupByTitle(e);if(!s)return console.warn(`[MapView] Layer group "${e}" not found β€” cannot add WMS layer "${t}"`),null;const a={LAYERS:o,TILED:!0,WIDTH:256,HEIGHT:256};i.style!==void 0&&(a.STYLES=i.style);const l=new zt({url:n,params:a,serverType:i.serverType!==void 0?i.serverType:"geoserver",crossOrigin:"anonymous",hidpi:!1,attributions:i.attributions}),c=new Z({title:t,visible:i.visible!==void 0?i.visible:!0,source:l,opacity:i.opacity!==void 0?i.opacity:1,zIndex:i.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),i.legendUrl)try{this._registerLegend(c,t,i.legendUrl)}catch(d){console.warn(`[MapView] Could not register legend for "${t}":`,d)}return i.onlineOnly&&this._attachOnlineOnlyHandler(c,t),console.log(`[MapView] WMS layer added: "${t}" β†’ group "${e}"`),c}addXYZLayer(e,t,n,o={}){const i=this.getLayerGroupByTitle(e);if(!i)return console.warn(`[MapView] Layer group "${e}" not found β€” cannot add XYZ layer "${t}"`),null;const s=new we({url:n,crossOrigin:"anonymous",maxZoom:o.maxZoom!==void 0?o.maxZoom:19,attributions:o.attributions}),a=new Z({title:t,visible:o.visible!==void 0?o.visible:!0,source:s,opacity:o.opacity!==void 0?o.opacity:1,zIndex:o.zIndex});if(a.set("typeTag","XYZ"),a.set("typeDescription","XYZ / Tile"),s.on("tileloaderror",()=>{O(`XYZ layer "${t}" β€” tile load error. Check the URL.`,"warning",5e3)}),i.getLayers().push(a),o.legendUrl)try{this._registerLegend(a,t,o.legendUrl)}catch(l){console.warn(`[MapView] Could not register legend for "${t}":`,l)}return o.onlineOnly&&this._attachOnlineOnlyHandler(a,t),console.log(`[MapView] XYZ layer added: "${t}" β†’ group "${e}"`),a}_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 a=s.value;a==="xyz"?(t.style.display="none",o.placeholder="https://example.com/tiles/{z}/{x}/{y}.png"):(t.style.display="",o.placeholder=a==="wms"?"https://example.com/wms":"https://example.com/wfs",n.textContent=a==="wms"?"WMS LAYERS parameter (e.g. workspace:layer)":"WFS typename (e.g. workspace:layer)")})});const i=()=>this._hideAddLayerDialog();e.querySelector(".add-layer-close").addEventListener("click",i),e.querySelector(".add-layer-cancel").addEventListener("click",i),this._addLayerDialog.addEventListener("click",s=>{s.target===this._addLayerDialog&&i()}),e.querySelector(".add-layer-confirm").addEventListener("click",()=>{const s=e.querySelector('input[name="add-layer-type"]:checked').value,a=e.querySelector(".add-layer-url").value.trim(),l=e.querySelector(".add-layer-name").value.trim(),c=e.querySelector(".add-layer-title").value.trim();if(!a){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,a,l,c),this._hideAddLayerDialog()}),e.addEventListener("keydown",s=>{s.key==="Enter"&&(s.preventDefault(),e.querySelector(".add-layer-confirm").click()),s.key==="Escape"&&(s.preventDefault(),i())})}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 i=this._externalSourceGroup;if(!i){O('Layer group "External Source" not found.',"error",4e3);return}let s;switch(e){case"wms":{const a=new zt({url:t,params:{LAYERS:n,TILED:!0,WIDTH:256,HEIGHT:256},serverType:"geoserver",crossOrigin:"anonymous",hidpi:!1});s=new Z({title:o,visible:!0,source:a}),a.on("tileloaderror",()=>{O(`WMS "${o}" β€” tile load error. Check URL and layer name.`,"warning",5e3)});break}case"wfs":{const a=`${t}${t.includes("?")?"&":"?"}service=WFS&version=1.1.0&request=GetFeature&typename=${encodeURIComponent(n)}&outputFormat=application/json&srsname=EPSG:3857`,l=new N({url:a,format:new ae});l.on("featuresloaderror",()=>{O(`WFS "${o}" β€” load error. Check URL and layer name.`,"warning",5e3)}),s=new F({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 Z({title:o,visible:!0,source:new we({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),i.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 i=t.querySelector(":scope > .li-content"),s=()=>{if(!i)return;const l=e.get("typeDescription");let c=i.querySelector(":scope > .ls-layer-subtitle");if(!l){c&&c.remove();return}if(!c){c=document.createElement("div"),c.className="ls-layer-subtitle";const d=i.querySelector(":scope > label");d&&d.nextSibling?i.insertBefore(c,d.nextSibling):i.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=i=>{const s=i.getLayers();if(s.getArray().includes(e))return s.remove(e),!0;let a=!1;return s.forEach(l=>{!a&&l.getLayers&&(a=n(l))}),a};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 i=t.querySelector(":scope > .ls-footer-row");i||(i=document.createElement("div"),i.className="ls-footer-row",i.innerHTML=` β€” layers total `,t.appendChild(i),i.querySelector(".ls-footer-btn").addEventListener("click",a=>{a.stopPropagation(),this._resetAllOverlays()}));const s=this._countLayers();o.querySelector(".ls-active-badge-count").textContent=`${s.activeOverlays} active`,i.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=i=>{i.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=i=>{i._lsVisHooked||(i._lsVisHooked=!0,i.on("change:visible",t))},o=i=>{i.getLayers().forEach(s=>{s.getLayers?(o(s),i._lsAddHooked||(i._lsAddHooked=!0,i.getLayers().on("add",a=>{const l=a.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 qt}_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 i=()=>{try{this._updateLegendPanel()}catch(s){console.warn("[MapView] legend panel update failed:",s)}};e.on("change:visible",i),i()}_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 i=o.getSource&&o.getSource();if(i&&typeof i.getExtent=="function"){const s=i.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 i=te([e,t]);this.map.getView().animate({center:i,zoom:n,duration:o})}}class Rr{constructor(e,t={}){this.map=e,this.options=t,this.measureSource=new N,this.measureLayer=new F({source:this.measureSource,style:this.getMeasureStyle(),title:"Measurements",zIndex:100}),this.drawSource=new N,this.drawLayer=new F({source:this.drawSource,style:this.getDrawStyle(),title:"Draw sketches",displayInLayerSwitcher:!1,zIndex:99});const n=this.map.getLayers(),o=n.getLength()-1;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 re({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 re({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 he({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 ve({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 re({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",i=>{const s=i.target;if(s instanceof cn){const a=s.getRadius(),l=ar(a),d=`${lt(a)}
${l}`;this.measureTooltipElement.innerHTML=d,this.measureTooltip.setPosition(s.getLastCoordinate())}})}),e.on("drawend",n=>{const o=n.feature,i=o.getGeometry(),s=i.getCenter(),a=i.getRadius();o.set("_layerType","measure_circle"),o.set("_radius",a),o.set("_center",s);const l=new ne({geometry:new se([s,[s[0]+a,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(),bt(t);const c={type:"circle",center:s,radius:a,area:Math.PI*a*a,feature:o};this.onMeasureCompleteCallbacks.forEach(d=>d(c))}),e}startLineMeasure(){this.deactivate(),this.createMeasureTooltip();const e=new ve({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",i=>{const s=i.target,a=ot(s),l=lt(a);this.measureTooltipElement.innerHTML=l,this.measureTooltip.setPosition(s.getLastCoordinate())})}),e.on("drawend",n=>{const o=n.feature,i=o.getGeometry(),s=ot(i);this.measureTooltipElement.className="measure-tooltip measure-tooltip-static",this.measureTooltipElement=null,this.createMeasureTooltip(),bt(t);const a={type:"line",length:s,feature:o};this.onMeasureCompleteCallbacks.forEach(l=>l(a))}),e}startAreaMeasure(){this.deactivate(),this.createMeasureTooltip();const e=new ve({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",i=>{const s=i.target,a=qe(s),l=je(a);this.measureTooltipElement.innerHTML=l,this.measureTooltip.setPosition(s.getInteriorPoint().getCoordinates())})}),e.on("drawend",n=>{const o=n.feature,i=o.getGeometry(),s=qe(i);o.set("_layerType","measure_area"),o.set("_area",s),this.measureTooltipElement.className="measure-tooltip measure-tooltip-static",this.measureTooltipElement=null,this.createMeasureTooltip(),bt(t);const a={type:"polygon",area:s,feature:o,coordinate:i.getInteriorPoint().getCoordinates()};this.onMeasureCompleteCallbacks.forEach(l=>l(a))}),e}startDrawPoint(){this.deactivate();const e=new ve({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 ve({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 ve({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 Tt({group:!0,className:"map-tools-bar"}),n=new Tt({toggleOne:!0,group:!0}),o=new fe({html:'β­•',title:"Measure Circle (radius & area)",className:"measure-circle-btn",onToggle:l=>{l?this.startCircleMeasure():this.deactivate()}});n.addControl(o);const i=new fe({html:'πŸ“',title:"Measure Distance",className:"measure-line-btn",onToggle:l=>{l?this.startLineMeasure():this.deactivate()}});n.addControl(i);const s=new fe({html:'⬛',title:"Measure Area",className:"measure-area-btn",onToggle:l=>{l?this.startAreaMeasure():this.deactivate()}});n.addControl(s);const a=new $e({html:'πŸ—‘οΈ',title:"Clear Measurements",className:"clear-measure-btn",handleClick:()=>{this.clearMeasurements(),o.setActive(!1),i.setActive(!1),s.setActive(!1)}});return n.addControl(a),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 Ee=null;async function Br(){if(!("serviceWorker"in navigator))return console.warn("[PWA] Service Workers not supported"),null;try{return Ee=await navigator.serviceWorker.register("/sw.js",{scope:"/"}),console.log("[PWA] Service Worker registered:",Ee.scope),Ee.addEventListener("updatefound",()=>{const r=Ee.installing;r.addEventListener("statechange",()=>{r.state==="installed"&&navigator.serviceWorker.controller&&(console.log("[PWA] New version available"),qr())})}),Ee}catch(r){return console.error("[PWA] Service Worker registration failed:",r),null}}let Me=null,ue=null;function Nr(r="#install-btn"){if(ue=typeof r=="string"?document.querySelector(r):r,!ue){console.warn("[PWA] Install button not found:",r);return}ue.style.display="none",window.addEventListener("beforeinstallprompt",e=>{e.preventDefault(),Me=e,ue.style.display="block",console.log("[PWA] Install prompt ready")}),ue.addEventListener("click",async()=>{if(!Me){$r();return}Me.prompt();const{outcome:e}=await Me.userChoice;console.log("[PWA] Install prompt outcome:",e),Me=null,ue.style.display="none"}),window.addEventListener("appinstalled",()=>{console.log("[PWA] App installed"),Me=null,ue.style.display="none"}),window.matchMedia("(display-mode: standalone)").matches&&(ue.style.display="none")}function $r(){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 Ct=null;const At=new Set;function Gr(r="#offline-indicator"){Ct=typeof r=="string"?document.querySelector(r):r,xt(!navigator.onLine),window.addEventListener("online",()=>{console.log("[PWA] Back online"),xt(!1),ao(!1)}),window.addEventListener("offline",()=>{console.log("[PWA] Gone offline"),xt(!0),ao(!0)})}function xt(r){Ct&&(Ct.style.display=r?"block":"none"),document.body.classList.toggle("is-offline",r)}function Uo(r){return At.add(r),r(!navigator.onLine),()=>At.delete(r)}function ao(r){for(const e of At)try{e(r)}catch(t){console.error("[PWA] Offline listener error:",t)}}function W(){return navigator.onLine}function qr(){confirm("A new version is available. Reload now?")&&jr()}function jr(){Ee?.waiting&&Ee.waiting.postMessage({type:"SKIP_WAITING"}),window.location.reload()}async function zr({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((i,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 Ur(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 Nt(r,e,t={},n=5e3,o=1e4){const i=await zr({timeoutMs:o});return new Promise((s,a)=>{const l=new MessageChannel,c=setTimeout(()=>{l.port1.close(),a(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)}},i.postMessage({type:r,...t},[l.port2])})}async function Hr(){try{return(await Nt("GET_TILE_STATS","TILE_STATS")).stats}catch(r){return console.warn("[PWA] getTileCacheStats failed:",r),null}}async function Wr(){try{return await Nt("CLEAR_TILE_CACHES","TILE_CACHES_CLEARED"),!0}catch(r){return console.warn("[PWA] clearTileCaches failed:",r),!1}}async function Vr(r){if(!r)return!1;try{return!!(await Nt("CLEAR_TILE_CACHE","TILE_CACHE_CLEARED",{cacheName:r})).deleted}catch(e){return console.warn(`[PWA] clearTileCacheForProvider(${r}) failed:`,e),!1}}async function Kr(){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 Yr(r={}){const{installButton:e="#install-btn",offlineIndicator:t="#offline-indicator",autoRegisterSW:n=!0}=r;n&&await Br(),Nr(e),Gr(t),console.log("[PWA] Initialized")}const Ho={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"}},Xr=30*1024,ct=2*Math.PI*6378137/2;function lo(r,e){const t=r/ct*180;let n=e/ct*180;return n=180/Math.PI*(2*Math.atan(Math.exp(n*Math.PI/180))-Math.PI/2),[t,n]}function co(r,e,t){const n=Math.pow(2,t),o=Math.floor((r+180)/360*n),i=e*Math.PI/180,s=Math.floor((1-Math.log(Math.tan(i)+1/Math.cos(i))/Math.PI)/2*n);return{x:o,y:s}}function Wo(r,e){const[t,n,o,i]=r,[s,a]=lo(t,n),[l,c]=lo(o,i),d=co(s,c,e),u=co(l,a,e),p=Math.pow(2,e),f=Math.max(0,Math.min(d.x,u.x)),h=Math.min(p-1,Math.max(d.x,u.x)),m=Math.max(0,Math.min(d.y,u.y)),g=Math.min(p-1,Math.max(d.y,u.y));return{z:e,minX:f,maxX:h,minY:m,maxY:g,count:(h-f+1)*(g-m+1)}}function Jr(r,e,t){let n=0;for(let o=e;o<=t;o++)n+=Wo(r,o).count;return n}function Zr(r,e,t){const n=[];for(let o=e;o<=t;o++){const i=Wo(r,o);for(let s=i.minX;s<=i.maxX;s++)for(let a=i.minY;a<=i.maxY;a++)n.push({z:o,x:s,y:a})}return n}function Qr(r,{z:e,x:t,y:n}){return r.replace("{z}",e).replace("{x}",t).replace("{y}",n)}class es{constructor({baseMap:e,extent3857:t,minZoom:n,maxZoom:o,concurrency:i=2,interBatchDelayMs:s=50,onProgress:a=()=>{}}){const l=Ho[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(i,6)),this.interBatchDelayMs=s,this.onProgress=a,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=Zr(this.extent,this.minZoom,this.maxZoom),t=e.length,n=Date.now();let o=0,i=0,s=0,a=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:i,failed:s,cached:a,elapsedMs:d,etaMs:u})};l("running");for(let c=0;c{if(this._cancelled)return;const p=Qr(this.template,u);try{const f=await fetch(p,{signal:this._abortCtrl.signal,cache:"default"});f.ok?(i++,f.body&&f.body.cancel().catch(()=>{})):(f.status,s++)}catch(f){f.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:i,failed:s,cached:a,elapsedMs:Date.now()-n}}cancel(){this._cancelled=!0,this._abortCtrl&&this._abortCtrl.abort()}}const ts=(()=>{const r=(n,o)=>{const i=n*ct/180,s=Math.log(Math.tan((90+o)*Math.PI/360))/(Math.PI/180);return[i,s*ct/180]},e=r(-3.3,4.5),t=r(1.2,11.2);return[e[0],e[1],t[0],t[1]]})();function os(r){return r*Xr}const Vo="https://api.lupmis4luspa.org/api/spatial_planning",ns="1",rs="1c46538c712e9b5b";function ss(){try{const r=typeof window<"u"&&window.LUPMIS_SESSION?.district_id;if(r!=null&&String(r).length>0)return String(r)}catch{}return ns}const Ko={get district_id(){return ss()},api_token:rs};function Yo(){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 is=3e4,as=5e3;let _e=null;async function ls(r=!1){if(_e!==null&&!r)return _e;const e=new AbortController,t=setTimeout(()=>e.abort(),as);try{_e=(await fetch(`${Vo}/get_layers.php`,{method:"POST",headers:{"Content-Type":"application/json",Accept:"application/json"},body:JSON.stringify(Ko),signal:e.signal})).ok}catch{_e=!1}finally{clearTimeout(t)}return console.log("[RemoteDB] Server reachable:",_e),_e}function Se(){return _e}function cs(r,e=is){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 ge(r,e={},t={}){const n=`${Vo}/${r}`,o={...Ko,...e};console.log("[RemoteDB] POST",n);const i=cs(t);try{const s=await fetch(n,{method:"POST",headers:{"Content-Type":"application/json",Accept:"application/json"},body:JSON.stringify(o),...t,signal:i.signal});if(!s.ok)throw new Error(`HTTP ${s.status}: ${s.statusText}`);const a=await s.json();return console.log("[RemoteDB] POST response:",r,"β†’",typeof a=="object"?`${Array.isArray(a)?a.length+" items":"object"}`:a),a}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{i.clear()}}async function ds(){return ge("get_district_boundary.php")}async function us(){return ge("get_layers.php")}async function ps(){return ge("get_all_collector_zone_per_district.php")}async function hs(){return ge("get_parcels_per_district.php")}async function fs(){return ge("get_all_footprint_per_district.php")}async function gs(){return ge("get_contours_hillshade.php")}async function ms(){return ge("get_osm_roads.php")}async function ys(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 ge("save_gps_trail.php",t);return{remoteId:n?.id??n?.remote_id??null}}const bs=63710088e-1,Je=Math.PI/180;function ws(r,e,t,n){const o=(n-e)*Je,i=(t-r)*Je,s=Math.sin(o/2)**2+Math.cos(e*Je)*Math.cos(n*Je)*Math.sin(i/2)**2;return 2*bs*Math.asin(Math.min(1,Math.sqrt(s)))}function uo(r,e=5){return r==null||Number.isNaN(r)?"β€”":r.toFixed(e)}function vs(r){return r==null||Number.isNaN(r)?"β€”":r<1e3?`${Math.round(r)} m`:`${(r/1e3).toFixed(2)} km`}function _s(r){return r==null||Number.isNaN(r)?"β€”":`Β±${Math.round(r)} m`}function Es(r){return r==null||Number.isNaN(r)?"none":r<=10?"good":r<=30?"fair":"poor"}const xs={minDistanceM:5,minIntervalMs:1e3,heartbeatMs:2e4,maxAccuracyM:50,enableHighAccuracy:!0,timeoutMs:15e3,maximumAgeMs:0};class ze{constructor(e={}){this.opts={...xs,...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(i){console.error(`[GeoTracker] listener for "${e}" threw`,i)}}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=ze.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=ze.uuid(),n=new Date().toISOString(),o={uuid:t,name:e.name||null,startedAt:n,...e},i=await this.storage.createTrail(o);return this._activeTrailId=i,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:i,uuid:t,startedAt:n}),{trailId:i,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(i){this._emit("error",i)}this._emit("trailstop",{trailId:e,...n});let o=!1;if(this.sync)try{o=await this._syncTrail(e)}catch(i){this._emit("error",i)}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(i){t++,this._emit("error",i)}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},i=await this.sync.pushTrail(o,n),s=i&&(i.remoteId??i.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=ze.normalize(e);if(this._lastFix=t,this._emit("position",t),!this._recording)return;const{minIntervalMs:n,minDistanceM:o,heartbeatMs:i,maxAccuracyM:s}=this.opts,a=t.timestamp;if(this._lastRecordedAt&&a-this._lastRecordedAt0&&t.accuracy!=null&&t.accuracy>s&&this._lastRecorded)return;let l=!1,c=0;if(!this._lastRecorded)l=!0;else{c=ws(this._lastRecorded.lon,this._lastRecorded.lat,t.lon,t.lat);const d=a-this._lastRecordedAt;(c>=o||d>=i)&&(l=!0)}if(l){this._lastRecorded&&(this._distanceM+=c),this._pointCount+=1,this._lastRecorded={lon:t.lon,lat:t.lat,timestamp:a},this._lastRecordedAt=a;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 Ss={async createTrail(r){const e=r.districtId??Yo()?.district_id??null;return Qn({...r,districtId:e!=null?String(e):null})},addPoint:(r,e)=>er(r,e),finishTrail:(r,e)=>tr(r,e),getUnsyncedTrails:()=>or(),getTrailPoints:r=>nr(r),markTrailSynced:(r,e)=>rr(r,e)},Ls={pushTrail:(r,e)=>ys(r,e),isOnline:()=>W()},pe=new ze({storage:Ss,sync:Ls,minDistanceM:5,minIntervalMs:1e3,heartbeatMs:2e4,maxAccuracyM:50,enableHighAccuracy:!0});let St=null;async function po(){if(!St){const r=await dt(()=>import("./shpjs-CNrRgkgn.js"),[]);St=r.default||r}return St}let v=null,Q=null,B="addLocation";async function ho(){console.log("[App] Initializing..."),await Yr({installButton:"#install-btn",offlineIndicator:"#offline-indicator",autoRegisterSW:!0});const r=localStorage.getItem("default-basemap")||"topo";v=new Or("map",{center:[-1.5,7.5],zoom:7,basemap:r}),Q=new Rr(v.getMap()),Zs(),Q.onMeasureComplete(t=>{console.log("[MapTools] Measurement complete:",t),t.type==="polygon"&&t.coordinate&&t.feature?.get("_layerType")!=="measure_area"&&v?.showDrawnPolygonPopup(t.feature,t.coordinate)}),v.onClick((t,n,o,i)=>{if(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(v.getMap().forEachFeatureAtPixel(i.pixel,a=>{if(a.get("_layerType")==="parcel")return s=a,!0}),s){console.log("[MapClick] Clicked on parcel β†’ Edit Attributes"),v.showParcelEditPopup(s,i.coordinate);return}B==="addLocation"&&(o?(console.log("[MapClick] Clicked on marker:",o.getId()),v.selectMarker(o),ks(o)):(console.log("[MapClick] Empty space β†’ Add Location popup"),v.clearSelection(),v.showAddLocationPopup(i.coordinate)))}),v.onDblClick((t,n,o,i)=>{if(!o)return;const s=o.get("_layerType");if(console.log("[App] Double-click on feature, _layerType:",s||"none"),s==="measure_circle")v.showCircleIntersectionPopup(o,i.coordinate);else{if(s==="measure_circle_radius")return;s==="measure_area"?v.showAreaIntersectionPopup(o,i.coordinate):s==="collector_zone"?v.showInfoPopup(o,i.coordinate,{title:"Zone Info",color:"#7c3aed"}):s==="parcel"?v.showInfoPopup(o,i.coordinate,{title:"Parcel Info",color:"#0ea5e9"}):v.showInfoPopup(o,i.coordinate,{title:"Feature Info",color:"#e11d48"})}}),v.onAddLocation(async t=>{console.log("[App] Add location from map popup:",t);try{const n=await Fn(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 Lt(),v?.zoomTo(t.lon,t.lat,14),n.id&&v?.selectMarker(n.id),ie("Location added successfully")}catch(n){console.error("[App] Failed to add location:",n),R("Failed to add location: "+n.message)}}),v.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 Gn(o,n),ie("Parcel updated locally")}catch(i){console.error("[App] Failed to save parcel update:",i),R("Failed to save parcel: "+i.message)}});const e=new wo;v.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"}),i=await qn(o,n);console.log("[App] New parcel inserted with id:",i.id),ie("New parcel saved (pending verification)")}catch(o){console.error("[App] Failed to save new parcel:",o),R("Failed to save parcel: "+o.message)}});try{console.log("[App] Initializing database..."),await Dn(),console.log("[App] Database ready");const t=await Bt();console.log("[App] Database status:",t),W()&&(await ls()||(console.warn("[App] API server unreachable β€” using local data only"),Qs("Server not responding β€” loading cached data."))),await Us(),v?.initEditBar(),Bs(),Ns(),$s(),Gs(),qs(),js(),zs()}catch(t){console.error("[App] Database initialization failed:",t),R("Failed to initialize database. Please refresh the page.");return}Ts(),await Lt(),An(t=>{if(console.log("[App] Database change:",t),t.table==="locations"&&!t.local&&Lt(),t.table==="parcels"){const n=document.getElementById("local-data-stats");n&&!n.classList.contains("d-none")&&pt()}}),Uo(t=>{t?console.log("[App] Working offline - data will sync when back online"):(console.log("[App] Back online - syncing data..."),Hs())}),ei(),oi(),ti(),ni(),ri(),si(),ii(),console.log("[App] Initialized successfully")}function Ts(){console.log("[initUI] Starting UI initialization..."),Js();const r=document.getElementById("export-btn");r&&r.addEventListener("click",Cs);const e=document.getElementById("local-data-btn");e&&e.addEventListener("click",()=>pt());const t=document.getElementById("import-shp-btn"),n=document.getElementById("shp-file-input");t&&n&&(t.addEventListener("click",()=>n.click()),n.addEventListener("change",Qo));const o=document.getElementById("import-geojson-btn"),i=document.getElementById("geojson-file-input");o&&i&&(o.addEventListener("click",()=>i.click()),i.addEventListener("change",en));const s=document.getElementById("import-kml-btn"),a=document.getElementById("kml-file-input");s&&a&&(s.addEventListener("click",()=>a.click()),a.addEventListener("change",tn)),Ys();const l=document.getElementById("exportGeoJSON-btn");l&&l.addEventListener("click",As);const c=document.getElementById("status-btn");c&&c.addEventListener("click",Ds);const d=document.getElementById("fit-btn");d&&d.addEventListener("click",()=>v?.fitToMarkers());const u=document.getElementById("dock-btn-add-location"),p=document.getElementById("dock-btn-measure-circle"),f=document.getElementById("dock-btn-measure-line"),h=document.getElementById("dock-btn-measure-area"),m=document.getElementById("dock-btn-draw"),g=document.getElementById("dock-btn-clear");console.log("[initUI] Buttons found:",{addLocation:!!u,measureCircle:!!p,measureLine:!!f,measureArea:!!h,draw:!!m,clear:!!g});const y=[u,p,f,h,m],b=(E,L)=>{switch(console.log("[setMode] Changing mode from",B,"to",E),B=E,console.log("[setMode] currentMode is now:",B),y.forEach(x=>{x&&x.classList.toggle("active",x===L)}),Q?.deactivate(),E!=="draw"&&v?.setEditMode(!1),E!=="addLocation"&&v?.hideAddLocationPopup(),E){case"measureCircle":Q?.startCircleMeasure();break;case"measureLine":Q?.startLineMeasure();break;case"measureArea":Q?.startAreaMeasure();break;case"draw":v?.setEditMode(!0);break}};u&&u.addEventListener("click",()=>{console.log("[Button] Add Location clicked"),b("addLocation",u)}),p&&p.addEventListener("click",()=>{console.log("[Button] Circle clicked, currentMode is:",B),B==="measureCircle"?b("addLocation",u):b("measureCircle",p)}),f&&f.addEventListener("click",()=>{console.log("[Button] Line clicked, currentMode is:",B),B==="measureLine"?b("addLocation",u):b("measureLine",f)}),h&&h.addEventListener("click",()=>{console.log("[Button] Area clicked, currentMode is:",B),B==="measureArea"?b("addLocation",u):b("measureArea",h)}),m&&m.addEventListener("click",()=>{console.log("[Button] Draw clicked, currentMode is:",B),B==="draw"?b("addLocation",u):b("draw",m)}),g&&g.addEventListener("click",()=>{if(Q?.clearMeasurements(),B.startsWith("measure"))switch(Q?.deactivate(),B){case"measureCircle":Q?.startCircleMeasure();break;case"measureLine":Q?.startLineMeasure();break;case"measureArea":Q?.startAreaMeasure();break}})}async function Lt(){try{console.log("[App] Loading locations...");const r=await Ao();console.log("[App] Locations loaded:",r),Ps(r),v&&(v.clearMarkers(),r.length>0&&(v.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 ks(r){const e=r.get("name"),t=r.get("description"),n=r.get("category"),o=r.get("lon")||r.get("longitude"),i=r.get("lat")||r.get("latitude");console.log("[App] Selected location:",{name:e,description:t,category:n,lon:o,lat:i})}function Ps(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 i=n[o.category]||"πŸ“";return`
${i} ${$(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",i=>{i.preventDefault();const s=parseFloat(o.dataset.lon),a=parseFloat(o.dataset.lat),l=parseInt(o.dataset.id);v?.zoomTo(s,a,14),v?.selectMarker(l)})})}async function pt(){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 Xn();e.innerHTML=n.map(o=>{const s=Ro(o.name)?``:"";return` ${$(o.name)} ${o.count} ${s} `}).join(""),r.classList.remove("d-none"),e.querySelectorAll(".table-name-link").forEach(o=>{o.addEventListener("click",i=>{i.preventDefault(),Is(o.dataset.table)})}),e.querySelectorAll(".table-clear-btn").forEach(o=>{o.addEventListener("click",async i=>{i.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 a=await Bo(s);ie(`Cleared ${a} row${a===1?"":"s"} from "${s}". It will re-download on next start.`),await pt()}catch(a){console.error("[App] Per-table clear failed:",a),R(`Could not clear "${s}": ${a.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",Ms))}}async function Ms(){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 Yn(),e=r.reduce((t,n)=>t+n.count,0);ie(`Cleared ${e} row${e===1?"":"s"} across ${r.length} table${r.length===1?"":"s"}.`),await pt(),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),R("Failed to clear cached layers: "+r.message)}}async function Is(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 Ft(document.getElementById("tableContentModal")).show();try{const{columns:i,rows:s}=await Jn(r);if(s.length===0){t.innerHTML='
Table is empty
',n.textContent="0 rows";return}const a=i.map(c=>`${$(c)}`).join(""),l=s.map(c=>`${i.map(u=>{let p=c[u];if(p==null)return'NULL';p=String(p);const f=p.length>120?p.substring(0,120)+"...":p;return`${$(f)}`}).join("")}`).join("");t.innerHTML=`
${a}${l}
`,n.textContent=`${s.length}${s.length>=200?"+":""} row(s), ${i.length} column(s)`}catch(i){console.error("[App] Failed to load table content:",i),t.innerHTML=`
Failed to load: ${$(i.message)}
`}}async function Cs(){try{await Vn("lupmis-backup.sqlite3"),ie("Database exported successfully")}catch(r){console.error("[App] Export failed:",r),R("Export failed: "+r.message)}}async function As(){try{const r=await Kn(),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),ie(`Exported ${r.features.length} location(s)`)}catch(r){console.error("[App] GeoJSON Export failed:",r),R("GeoJSON Export failed: "+r.message)}}async function Ds(){try{const r=await Bt(),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 Ft(document.getElementById("statusModal")).show()}catch(r){console.error("[App] Failed to get status:",r),R("Failed to get status")}}function Xo(r){return r.replace(/^\(+/,"").replace(/\)+$/,"").split(",").map(e=>{const[t,n]=e.trim().split(/\s+/).map(Number);return[t,n]})}function Fs(r){return{type:"Polygon",coordinates:r.trim().replace(/^POLYGON\s*\(\s*/i,"").replace(/\s*\)$/,"").split("),(").map(Xo)}}function Os(r){return{type:"MultiPolygon",coordinates:r.trim().replace(/^MULTIPOLYGON\s*\(\s*/i,"").replace(/\s*\)$/,"").split(")),((").map(o=>o.replace(/^\(+/,"").replace(/\)+$/,"").split("),(").map(Xo))}}function ht(r){if(!r)return null;const e=r.trim().toUpperCase();return e.startsWith("MULTIPOLYGON")?Os(r):e.startsWith("POLYGON")?Fs(r):(console.warn("[App] Unsupported WKT type:",e.substring(0,30)),null)}function Rs(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=ht(e);return{type:"FeatureCollection",features:[{type:"Feature",properties:{districtid:t,district_name:n},geometry:o}]}}function fo(r){if(!Array.isArray(r)||r.length===0)return null;const e=[];for(const t of r){const n=t.polygon||t.boundary,o=ht(n);if(!o)continue;const i={_layerType:"collector_zone"};for(const[s,a]of Object.entries(t))s==="polygon"||s==="boundary"||(i[s]=a);e.push({type:"Feature",properties:i,geometry:o})}return e.length===0?null:{type:"FeatureCollection",features:e}}async function Bs(){const r="district_boundary",t={strokeColor:"#e11d48",strokeWidth:2.5,fillColor:"rgba(225,29,72,0.08)",typeDescription:"Vector / Polygon"},n=v?.getLayerGroup(1)||null;function o(s){if(!s)return;const a=s.getLayers(),l=[];a.forEach(c=>{c.get("title")==="District Boundary"&&l.push(c)}),l.forEach(c=>a.remove(c))}function i(s){if(!s||!v)return;const a=s.getSource().getExtent();a&&a[0]!==1/0&&v.getMap().getView().fit(a,{padding:[40,40,40,40],duration:600})}try{const s=await Fo(r);if(s){console.log("[App] District boundary loaded from local cache");const a=v?.addGeoJSONLayer(s,"District Boundary",t,n);i(a)}if(W()&&Se()){console.log("[App] Fetching district boundary from API...");const a=await ds(),l=Rs(a);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 Do(r,l),s&&o(n||v?.getOverlayGroup());const c=v?.addGeoJSONLayer(l,"District Boundary",t,n);i(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 Ns(){const e={strokeColor:"#7c3aed",strokeWidth:1.5,fillColor:"rgba(124,58,237,0.12)",typeDescription:"Vector / Polygon"},t=v?.getLayerGroup(1)||null;console.log("[App] loadCollectorZones β€” adminGroup:",t?t.get("title"):"null");const n={type:"FeatureCollection",features:[]},o=v?.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&&R("No collector zones available locally. Connect to the internet to download zone data.")});function i(s){const a=new ae().readFeatures(s,{featureProjection:"EPSG:3857"});o.getSource().clear(),o.getSource().addFeatures(a)}try{const s=await Bn();if(s){const a=fo(s);a&&(console.log("[App] Collector zones loaded from local cache:",a.features.length,"zones"),i(a))}if(W()&&Se()){console.log("[App] Fetching collector zones from API...");const a=await ps();if(!a?.success||!Array.isArray(a?.data)){console.warn("[App] getCollectorZones API response invalid:",a);return}const l=a.data;console.log("[App] Collector zones from API:",l.length,"entries"),await Rn(l);const c=fo(l);if(!c){console.warn("[App] Could not convert zones to GeoJSON");return}i(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 go(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 i=null;if(n.sp_boundary&&n.sp_boundary.type&&n.sp_boundary.coordinates)i={type:n.sp_boundary.type,coordinates:n.sp_boundary.coordinates};else{const l=n.boundary||n.polygon||n.geom||n.wkt;i=ht(l)}if(!i)continue;const s=new Set(["polygon","boundary","geom","wkt","textboundary","sp_boundary"]),a={_layerType:"parcel"};for(const[l,c]of Object.entries(n))s.has(l)||(a[l]=c);t.push({type:"Feature",properties:a,geometry:i})}return t.length===0?null:{type:"FeatureCollection",features:t}}async function $s(){const e={strokeColor:"#0ea5e9",strokeWidth:1.5,fillColor:"rgba(14,165,233,0.12)",typeDescription:"Vector / Polygon"},t=v?.getLayerGroup(4)||null;console.log("[App] loadParcels β€” landUseGroup:",t?t.get("title"):"null");const n={type:"FeatureCollection",features:[]},o=v?.addGeoJSONLayer(n,"Parcels",e,t);if(!o){console.warn("[App] Could not create Parcels layer");return}o.setVisible(!1),o.on("change:visible",()=>{o.getVisible()&&o.getSource().getFeatures().length===0&&R("No parcels available locally. Connect to the internet to download parcel data.")});function i(s){const a=new ae().readFeatures(s,{featureProjection:"EPSG:3857"});o.getSource().clear(),o.getSource().addFeatures(a)}try{const s=await $n();if(s){const a=go(s);a&&(console.log("[App] Parcels loaded from local cache:",a.features.length,"parcels"),i(a))}if(W()&&Se()){console.log("[App] Fetching parcels from API...");const a=await hs();if(!a?.success||!Array.isArray(a?.data)){console.warn("[App] getDistrictParcels API response invalid:",a);return}const l=a.data;console.log("[App] Parcels from API:",l.length,"entries"),l.length>0&&console.log("[App] First parcel keys:",Object.keys(l[0])),await Nn(l);const c=go(l);if(!c){console.warn("[App] Could not convert parcels to GeoJSON");return}i(c),console.log("[App] Parcels updated from API:",c.features.length,"parcels")}else s||console.log("[App] Parcels not available β€” offline and no local cache")}catch(s){console.error("[App] Failed to load parcels:",s)}}function mo(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 i;if(typeof o=="object"&&o!==null&&o.type?i=o:i=ht(o),!i)continue;const s={_layerType:"building_footprint"};for(const[a,l]of Object.entries(n))e.includes(a)||typeof l=="object"&&l!==null||(s[a]=l);t.push({type:"Feature",properties:s,geometry:i})}return t.length===0?null:{type:"FeatureCollection",features:t}}async function Gs(){const e={strokeColor:"#8b6f47",strokeWidth:1,fillColor:"rgba(139,111,71,0.18)",typeDescription:"Vector / Polygon"},t=v?.getLayerGroup(5)||null;console.log("[App] loadBuildingFootprints β€” physInfraGroup:",t?t.get("title"):"null");const n={type:"FeatureCollection",features:[]},o=v?.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&&R("No building footprints available locally. Connect to the internet to download footprint data.")});function i(s){const a=new ae().readFeatures(s,{featureProjection:"EPSG:3857"});o.getSource().clear(),o.getSource().addFeatures(a)}try{const s=await zn();if(s){const a=mo(s);a&&(console.log("[App] Building footprints loaded from local cache:",a.features.length,"footprints"),i(a))}if(W()&&Se()){console.log("[App] Fetching building footprints from API...");const a=await fs();if(!a?.success||!Array.isArray(a?.data)){console.warn("[App] getBuildingFootprints API response invalid:",a);return}const l=a.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 jn(l);const c=mo(l);if(!c){console.warn("[App] Could not convert building footprints to GeoJSON");return}i(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 Jo(r,e){if(!Array.isArray(r)||r.length===0)return null;const t=new wo,n=new ae,o=["geom","geometry","wkt","polygon","boundary","road","line"],i=[];for(const s of r){const a=s.geom||s.geometry||s.wkt||s.polygon||s.boundary||s.road||s.line;if(!a)continue;let l;try{if(typeof a=="object"&&a!==null&&a.type){i.push({type:"Feature",properties:yo(s,o,e),geometry:a});continue}l=t.readGeometry(a)}catch(d){console.warn(`[App] Could not parse WKT for ${e}:`,d,a?.toString().slice(0,60));continue}const c=JSON.parse(n.writeGeometry(l));i.push({type:"Feature",properties:yo(s,o,e),geometry:c})}return i.length===0?null:{type:"FeatureCollection",features:i}}function yo(r,e,t){const n={_layerType:t};for(const[o,i]of Object.entries(r))e.includes(o)||typeof i=="object"&&i!==null||(n[o]=i);return n}async function qs(){const r={strokeColor:"#78716c",strokeWidth:.8,typeDescription:"Vector / Line",fillColor:"rgba(0,0,0,0)"},e=v?.getLayerGroupByTitle("Biophysical Environment");console.log("[App] loadContoursHillshade β€” group:",e?e.get("title"):"null");const t={type:"FeatureCollection",features:[]},n=v?.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&&R("No Contours hillshade data available. Connect to the internet to download it.")}),!W()||!Se()){console.log("[App] Contours hillshade not available β€” offline or server unreachable");return}try{console.log("[App] Fetching contours_hillshade from API...");const o=await gs();if(!o?.success||!Array.isArray(o?.data)){console.warn("[App] getContoursHillshade API response invalid:",o);return}const i=o.data;console.log("[App] Contours hillshade from API:",i.length,"rows"),i.length>0&&console.log("[App] First row keys:",Object.keys(i[0]));const s=Jo(i,"contours_hillshade");if(!s){console.warn("[App] Could not convert contours to GeoJSON");return}const a=new ae().readFeatures(s,{featureProjection:"EPSG:3857"});n.getSource().clear(),n.getSource().addFeatures(a),console.log("[App] Contours hillshade loaded:",a.length,"features")}catch(o){console.error("[App] Failed to load contours_hillshade:",o)}}async function js(){const e={strokeColor:"#F0F1F0",strokeWidth:1.5,lineCasingColor:"#000000",lineCasingWidth:3.5,fillColor:"rgba(0,0,0,0)",typeDescription:"Vector / Line"},t=v?.getLayerGroup(5)||null;console.log("[App] loadOSMRoads β€” group:",t?t.get("title"):"null");const n={type:"FeatureCollection",features:[]},o=v?.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&&R("No OSM roads available locally. Connect to the internet to download them.")});function i(s){const a=Jo(s,"osm_road");if(!a)return console.warn("[App] Could not convert OSM roads to GeoJSON"),0;const l=new ae().readFeatures(a,{featureProjection:"EPSG:3857"});return o.getSource().clear(),o.getSource().addFeatures(l),l.length}try{const s=await Hn();if(s){const a=i(s);console.log("[App] OSM_roads loaded from local cache:",a,"features")}if(W()&&Se()){console.log("[App] Fetching OSM_roads from API...");const a=await ms();if(!a?.success||!Array.isArray(a?.data)){console.warn("[App] getOSMRoads API response invalid:",a);return}const l=a.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 Un(l);const c=i(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 zs(){v?.addWMSLayer("Biophysical Environment","DEAfrica Coastlines v0.4","https://geoserver.digitalearth.africa/geoserver/wms","coastlines:DEAfrica_Coastlines",{serverType:"geoserver",visible:!1,onlineOnly:!0}),v?.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 Us(){const r="layer_categories";function e(t){const n=[...t].sort((o,i)=>i.id-o.id);for(const o of n)v?.addLayerGroup(o.id,o.name,o.description||"");console.log("[App] Created",t.length,"layer groups on map")}try{const t=await Fo(r);if(t&&(console.log("[App] Layer categories loaded from local cache:",t.length,"entries"),e(t)),W()&&Se()){console.log("[App] Fetching layer categories from API...");const n=await us();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 Do(r,o),t){const i=v?.getOverlayGroup()?.getLayers();if(i){const s=[];i.forEach(a=>{a.get("layerId")!==void 0&&s.push(a)}),s.forEach(a=>i.remove(a))}}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 Hs(){if(!W()){console.log("[App] Cannot sync - offline");return}console.log("[App] Sync placeholder - implement based on your backend")}const oe=[],Ws={strokeColor:"#e11d48",strokeWidth:2,fillColor:"rgba(225,29,72,0.12)"};function Y(r){ft("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 $t(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 a=s.fileName?s.fileName.replace(/\.[^/.]+$/,""):e,l=v?.addGeoJSONLayer(s,a,Ws);l&&(l.set("removable",!0),l.set("typeTag","GEO"),oe.push(l),o+=s.features.length)}if(o===0){Y("No features found in the file.");return}console.log(`[${t}] Added ${o} feature(s) from ${n.length} layer(s)`);const i=oe[oe.length-1];if(i){const s=i.getSource().getExtent();v?.getMap().getView().fit(s,{padding:[50,50,50,50],maxZoom:18})}Gt()}function Gt(){const r=document.getElementById("imported-layers-info");if(!r)return;if(oe.length===0){r.innerHTML="",r.classList.add("d-none");return}r.innerHTML=`
Imported Layers
    `;const e=r.querySelector("#imported-layers-list");oe.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",()=>{Vs(Number(t.dataset.removeIdx))})}),r.querySelector("#remove-imported-layers")?.addEventListener("click",()=>{Ks()})}function Vs(r){if(r<0||r>=oe.length)return;const e=oe[r],t=v?.getOverlayGroup();t&&t.getLayers().remove(e),oe.splice(r,1),Gt(),console.log("[FileImport] Removed layer:",e.get("title"))}function Ks(){const r=v?.getOverlayGroup();if(r)for(const e of oe)r.getLayers().remove(e);oe.length=0,Gt(),console.log("[FileImport] All imported layers removed")}function Zo(r){const e={};for(const t of r){const n=t.name.split(".").pop().toLowerCase();e[n]=t}return e}async function Qo(r){const e=r.target.files;if(!e||e.length===0)return;const t=200*1024*1024,n=Array.from(e).reduce((o,i)=>o+i.size,0);if(n>t){const o=(n/1048576).toFixed(0);Y(`Files too large (${o} MB total). Maximum supported size is 200 MB.`),r.target.value="";return}try{let o,i;const s=Zo(e);if(s.zip){const a=s.zip;i=a.name.replace(/\.zip$/i,""),console.log("[ShpImport] Parsing zip",a.name,"("+(a.size/1024).toFixed(1)+" KB)"),o=await(await po())(await a.arrayBuffer())}else if(s.shp){i=s.shp.name.replace(/\.shp$/i,"");const l=["dbf","shx","prj"].filter(u=>!s[u]);if(l.length>0){Y("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 po())(c)}else{Y("Please select a .zip or at least a .shp file."),r.target.value="";return}$t(o,i,"ShpImport")}catch(o){console.error("[ShpImport] Failed:",o),Y("Failed to parse shapefile: "+o.message)}r.target.value=""}async function en(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);Y(`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 i;if(o.type==="FeatureCollection")i=o;else if(o.type==="Feature")i={type:"FeatureCollection",features:[o]};else if(o.type&&o.coordinates)i={type:"FeatureCollection",features:[{type:"Feature",geometry:o,properties:{}}]};else{Y("The file does not contain valid GeoJSON."),r.target.value="";return}const s=e.name.replace(/\.(geo)?json$/i,"");$t(i,s,"GeoJSONImport")}catch(n){console.error("[GeoJSONImport] Failed:",n);const o=(e.size/(1024*1024)).toFixed(1);Y(`Failed to import "${e.name}" (${o} MB): ${n.message}`)}r.target.value=""}async function tn(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);Y(`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 i=new dn({extractStyles:!1}).readFeatures(n,{featureProjection:"EPSG:3857"});if(!i||i.length===0){Y("No features found in the KML file."),r.target.value="";return}const s=new ae,a=JSON.parse(s.writeFeatures(i,{featureProjection:"EPSG:3857",dataProjection:"EPSG:4326"})),l=e.name.replace(/\.kml$/i,"");$t(a,l,"KMLImport")}catch(n){console.error("[KMLImport] Failed:",n);const o=(e.size/(1024*1024)).toFixed(1);Y(`Failed to import "${e.name}" (${o} MB): ${n.message}`)}r.target.value=""}function Ys(){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=Zo(n),i=Object.keys(o);if(o.zip||o.shp){const s={target:{files:n,value:""}};Object.defineProperty(s.target,"value",{writable:!0}),Qo(s)}else if(o.geojson||o.json){const a={target:{files:[o.geojson||o.json],value:""}};Object.defineProperty(a.target,"value",{writable:!0}),en(a)}else if(o.kml){const s={target:{files:[o.kml],value:""}};Object.defineProperty(s.target,"value",{writable:!0}),tn(s)}else Y("Unsupported file type(s): "+i.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 Xs=50,bo={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 ft(r,e){const t=bo[r]||bo.info;(r==="error"?console.error:r==="warning"?console.warn:console.log)("[App]",e);const o=document.getElementById("message-log");if(!o)return;const i=o.querySelector(".text-muted");i&&i.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>Xs;)o.lastElementChild.remove()}function Js(){const r=document.getElementById("clear-message-log");r&&r.addEventListener("click",()=>{const e=document.getElementById("message-log");e&&(e.innerHTML='
    No messages yet.
    ')})}function Zs(){const r=document.getElementById("gps-readout"),e=document.getElementById("gps-coords"),t=document.getElementById("gps-accuracy"),n=document.getElementById("gps-sats");if(!pe.isSupported){e&&(e.textContent="No GPS");return}pe.on("position",i=>{e&&(e.textContent=`${uo(i.lat)}, ${uo(i.lon)}`),t&&(t.textContent=_s(i.accuracy)),n&&(n.textContent=`${i.satellites!=null?i.satellites:"β€”"} sat`),r&&(r.classList.add("active"),r.classList.remove("quality-good","quality-fair","quality-poor"),r.classList.add("quality-"+Es(i.accuracy))),v?.showCurrentPosition(i.lon,i.lat,i.accuracy)}),pe.on("point",i=>{v?.appendTrailPoint(i.point.lon,i.point.lat)}),pe.on("error",i=>{console.warn("[GPS]",i?.message||i),i&&i.code===1&&R("Location permission denied. Enable location access to use GPS.")}),v.onLocateMe(async()=>{try{const i=await pe.getCurrentPosition();v.centerOn(i.lon,i.lat,16)}catch(i){R("Could not get your location: "+(i?.message||i))}}),v.onToggleRecording(async i=>{if(i)try{await Zt,v.startTrailRender(),v.setRecordingState(!0),r?.classList.add("recording"),await pe.startRecording({name:`Trail ${new Date().toLocaleString()}`}),ie("GPS trail recording started")}catch(s){v.setRecordingState(!1),r?.classList.remove("recording"),R("Could not start recording: "+(s?.message||s))}else try{const s=await pe.stopRecording();if(v.setRecordingState(!1),r?.classList.remove("recording"),s){const a=`Trail saved: ${s.pointCount} points, ${vs(s.distanceM)}`+(s.synced?" β€” synced":" β€” will sync when online");ie(a)}}catch(s){R("Error stopping recording: "+(s?.message||s))}});const o=async()=>{if(W())try{await Zt;const i=await pe.syncPending();i.pushed&&console.log(`[GPS] Synced ${i.pushed} pending trail(s)`)}catch(i){console.warn("[GPS] pending-sync error",i)}};o(),Uo(i=>{i||o()})}function R(r){ft("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 ie(r){ft("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 Qs(r){ft("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 ei(){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 ti(){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 oi(){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(),v?.setScaleBarUnits(n||"metric"),r.addEventListener("change",()=>{const o=r.checked?"imperial":"metric";localStorage.setItem("measurement-system",o),t(),v?.setScaleBarUnits(o),console.log("[Settings] Measurement system:",o)})}function ni(){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),v?.setBaseMap(t),console.log("[Settings] Default base map:",t)}),v?.getMap()?.on("basemapchange",t=>{if(t?.key&&r.value!==t.key){r.value=t.key;try{localStorage.setItem("default-basemap",t.key)}catch{}}})}function ri(){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 i(){if(o)return o;const s=!!navigator.serviceWorker?.controller;return r.innerHTML=s?'
    Loading…
    ':'
    Initialising service worker…
    ',o=(async()=>{try{const a=await Hr();if(!a){r.innerHTML=`
    Tile cache stats unavailable. Try reloading the page if this persists.
    `;return}const l=a.totals,c=a.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 Kr();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 f=>{f.preventDefault();const h=p.dataset.cache,m=p.dataset.label||h;if(!confirm(`Clear cached "${m}" tiles? Other providers are not affected. The tiles will re-download as you browse online.`))return;p.disabled=!0,await Vr(h)?console.log(`[Settings] Cleared tile cache for ${m}`):console.warn(`[Settings] Could not clear tile cache for ${m}`),await i()})})}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 Wr()?console.log("[Settings] Tile caches cleared"):console.warn("[Settings] Tile-cache clear failed"),await i()}),t.addEventListener("show.bs.offcanvas",i),Ur(()=>{console.log("[Settings] SW controller changed β†’ refreshing tile-cache stats"),i()}),i()}function si(){const r=document.getElementById("download-tiles-btn"),e=document.getElementById("offline-download-modal");if(!r||!e)return;const t=Ft.getOrCreateInstance(e),n=document.getElementById("offline-download-form-view"),o=document.getElementById("offline-download-progress-view"),i=document.getElementById("offline-download-done-view"),s=document.getElementById("offline-download-cancel-btn"),a=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"),f=document.getElementById("offline-ack-check"),h=document.getElementById("offline-estimate-detail"),m=document.getElementById("offline-estimate"),g=document.getElementById("offline-area-view"),y=document.getElementById("offline-area-district"),b=document.getElementById("offline-area-ghana"),E=document.getElementById("offline-area-view-info"),L=document.getElementById("offline-area-district-info"),x=document.getElementById("offline-progress-bar"),w=document.getElementById("offline-progress-percent"),S=document.getElementById("offline-progress-counts"),P=document.getElementById("offline-progress-ok"),T=document.getElementById("offline-progress-failed"),j=document.getElementById("offline-progress-eta"),U=document.getElementById("offline-done-title"),V=document.getElementById("offline-done-detail");let A=null;function ee(D){return D?D<1024*1024?(D/1024).toFixed(0)+" KB":D<1024*1024*1024?(D/(1024*1024)).toFixed(1)+" MB":(D/(1024*1024*1024)).toFixed(2)+" GB":"0 KB"}function K(D){if(!D||D<1e3)return"< 1 s";const H=Math.round(D/1e3);if(H<60)return H+" s";const z=Math.floor(H/60),X=H%60;return z<60?`${z} min ${X} s`:`${Math.floor(z/60)} h ${z%60} min`}function le(){return g.checked?v?.getCurrentViewExtent()||null:y.checked?v?.getDistrictBoundaryExtent()?.extent||null:b.checked?ts:null}function G(){const D=d.value,H=parseInt(u.value,10),z=parseInt(p.value,10);if(Number.isNaN(H)||Number.isNaN(z)||H>z){h.textContent="Invalid zoom range",m.classList.replace("alert-info","alert-warning"),a.disabled=!0;return}const X=le();if(!X){h.textContent="Selected area is not available.",m.classList.replace("alert-info","alert-warning"),a.disabled=!0;return}const C=Ho[D]?.maxZoom??19,q=Math.min(z,C),J=Jr(X,H,q),gt=os(J);let de="";qZoom ${z} is above this provider's max (${C}); will clamp to ${C}.`),J>8e3&&(de+='
    More than 8 000 tiles β€” exceeds the per-provider cache limit. Earlier tiles will be evicted as new ones arrive.'),h.innerHTML=`${J.toLocaleString()} tiles Β· ~${ee(gt)}`+de,m.classList.toggle("alert-warning",!!de),m.classList.toggle("alert-info",!de),a.disabled=!f.checked||J===0}function ce(){v?.getCurrentViewExtent()?E.textContent=" Β· ready":E.textContent="",v?.getDistrictBoundaryExtent()?(L.textContent="",y.disabled=!1):(L.textContent=" (not loaded β€” connect online to fetch)",y.disabled=!0,y.checked&&(g.checked=!0))}function De(){n.classList.remove("d-none"),o.classList.add("d-none"),i.classList.add("d-none"),a.classList.remove("d-none"),s.classList.remove("d-none"),s.textContent="Cancel",l.classList.add("d-none"),c.disabled=!1,f.checked=!1,a.disabled=!0,A=null}r.addEventListener("click",()=>{De(),ce(),G(),t.show()}),d.addEventListener("change",G),u.addEventListener("input",G),p.addEventListener("input",G),g.addEventListener("change",G),y.addEventListener("change",G),b.addEventListener("change",G),f.addEventListener("change",G),a.addEventListener("click",async()=>{const D=d.value,H=parseInt(u.value,10),z=parseInt(p.value,10),X=le();if(!X)return;n.classList.add("d-none"),o.classList.remove("d-none"),a.classList.add("d-none"),s.textContent="Cancel download",c.disabled=!0,x.style.width="0%",x.setAttribute("aria-valuenow","0"),w.textContent="0%",S.textContent="0 of 0 tiles",P.textContent="0",T.textContent="0",j.textContent="β€”",A=new es({baseMap:D,extent3857:X,minZoom:H,maxZoom:z,onProgress:q=>{if(q.total>0){const J=Math.min(100,Math.round(q.done/q.total*100));x.style.width=J+"%",x.setAttribute("aria-valuenow",String(J)),w.textContent=J+"%",S.textContent=`${q.done.toLocaleString()} of ${q.total.toLocaleString()} tiles`}P.textContent=q.ok.toLocaleString(),T.textContent=q.failed.toLocaleString(),j.textContent=q.etaMs!=null?K(q.etaMs):"β€”"}});let C;try{C=await A.start()}catch(q){console.error("[OfflineDownload] failed:",q),C={phase:"error",done:0,total:0,ok:0,failed:0}}o.classList.add("d-none"),i.classList.remove("d-none"),s.classList.add("d-none"),l.classList.remove("d-none"),c.disabled=!1,C.phase==="cancelled"?(U.textContent="Download cancelled",V.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",V.textContent="See console for details."):(U.textContent="Download complete",V.innerHTML=`${C.ok.toLocaleString()} tiles cached`+(C.failed>0?`, ${C.failed.toLocaleString()} failed`:"")+`.
    Took ${K(C.elapsedMs)}.`)}),s.addEventListener("click",()=>{A&&A.cancel()}),e.addEventListener("hidden.bs.modal",()=>{A&&A.cancel(),De()})}function ii(){const r=Yo(),e=document.getElementById("menu-btn"),t=document.getElementById("menu-user-avatar"),n=document.getElementById("menu-user-name"),o=document.getElementById("menu-user-email"),i=document.getElementById("menu-user-detail"),s=document.getElementById("menu-signout-btn"),a=document.getElementById("menu-signin-link"),l=document.getElementById("menu-no-session-note");if(!e||!t||!n||!o||!i||!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)),i.innerHTML=p.join(" Β· ")||"No district info",s.classList.remove("d-none"),s.addEventListener("click",()=>ai(r),{once:!1}),a?.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="",i.textContent="",s.classList.add("d-none"),a?.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="",i.textContent="",s.classList.add("d-none"),a?.classList.remove("d-none"),l?.classList.add("d-none"),e.dataset.state="unauthenticated",e.setAttribute("title","Menu (not signed in)"))}async function ai(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",ho):ho(); //# sourceMappingURL=index-DJ2WL3EC.js.map