const { Button, Input, Badge, Card, Icon, Tag, Tabs, StatusDot, DataTable, Select, InsightCard,
        ActionBar, ShellPanel, LayerList, Legend, CalciteNotice, MapScaleBar, BasemapToggle, MapSearch } = window.KKPMarineDigitalDesignSystem_4fef83;

function ArcgisLayerListPanel({view,mapReady,onWorkspaceLayerChange}){
  const ref=React.useRef(null);
  React.useEffect(()=>{
    const container=ref.current;if(!container||!view||!mapReady)return;
    let widget=null,disposed=false;
    window.require(['esri/widgets/LayerList','esri/layers/GroupLayer'],(LayerListWidget,GroupLayer)=>{
      if(disposed)return;
      widget=new LayerListWidget({container,view,dragEnabled:true,minDragEnabledItems:2,visibleElements:{filter:true,statusIndicators:true,errors:true},listItemCreatedFunction:({item})=>{
        if(item.layer?.type==='group')item.open=item.layer.title==='OMS_Lautra_d';
        item.actionsSections=[[{title:'Zoom to layer',className:'esri-icon-zoom-in-magnifying-glass',id:'zoom-to-layer'},{title:'Show properties',className:'esri-icon-description',id:'show-properties'}],[{title:'Rename',className:'esri-icon-edit',id:'rename-layer'},{title:'Remove',className:'esri-icon-trash',id:'remove-layer'}],[{title:'Group',className:'esri-icon-collection',id:'group-layer'},{title:'Ungroup',className:'esri-icon-ungroup',id:'ungroup-layer'}]];
        if(item.layer?.watch)item.layer.watch('visible',visible=>onWorkspaceLayerChange?.('visibility',item.layer,{visible}));
      }});
      widget.on('trigger-action',async event=>{
        const {action,item}=event,layer=item?.layer;if(!action||!layer)return;
        if(action.id==='zoom-to-layer'){try{const extent=layer.fullExtent||(layer.queryExtent?(await layer.queryExtent()).extent:null);if(extent)await view.goTo(extent.expand?extent.expand(1.12):extent,{duration:800});}catch(_){}}
        else if(action.id==='show-properties'){const url=layer.url||layer.portalItem?.url;if(url)window.open(url+(String(url).includes('?')?'&':'?')+'f=pjson','_blank','noopener');}
        else if(action.id==='rename-layer'){const title=window.prompt('Nama layer',layer.title||'Layer');if(title?.trim()){layer.title=title.trim();onWorkspaceLayerChange?.('rename',layer,{title:layer.title});}}
        else if(action.id==='remove-layer'){const parent=layer.parent;if(parent?.type==='group')parent.layers.remove(layer);else view.map.remove(layer);onWorkspaceLayerChange?.('remove',layer,{});}
        else if(action.id==='group-layer'&&layer.type!=='group'){if(layer.parent?.type==='group')return;view.map.remove(layer);view.map.add(new GroupLayer({title:'Grup layer',layers:[layer]}));}
        else if(action.id==='ungroup-layer'&&layer.type==='group'){const children=layer.layers.toArray();layer.removeAll();view.map.remove(layer);children.reverse().forEach(child=>view.map.add(child));}
      });
    });
    return()=>{disposed=true;if(widget)widget.destroy();};
  },[view,mapReady,onWorkspaceLayerChange]);
  return <div ref={ref} style={{minHeight:180,maxHeight:'100%',background:'transparent'}} />;
}

const SEED_QUESTION = 'Profil Laut Sawu: EVIKA dan Blue Carbon';

function llmProviderLabel(provider){
  return provider === 'groq' ? 'Groq' : provider === 'gemini' ? 'Google Gemini' : 'OpenAI';
}

const OMS_SPATIAL_EXTENT = { xmin:95, ymin:-11, xmax:141, ymax:6, spatialReference:{wkid:4326} };
const LAUT_SAWU_POINT = { lat:-10.25, lon:123.50, zoom:7, label:'Laut Sawu', provinceName:'Nusa Tenggara Timur', valueText:'Koordinat perkiraan: -10.25°, 123.50°' };
const OMS_LAUTRA_FEATURESERVER = 'https://services8.arcgis.com/mpSDBlkEzjS62WgX/arcgis/rest/services/OMS_Lautra_d/FeatureServer';
const OMS_LAUTRA_LAYERS = { mangrove:OMS_LAUTRA_FEATURESERVER+'/0', substrate:OMS_LAUTRA_FEATURESERVER+'/1', macroalgae:OMS_LAUTRA_FEATURESERVER+'/2', seagrass:OMS_LAUTRA_FEATURESERVER+'/3', coral:OMS_LAUTRA_FEATURESERVER+'/4', conservation:OMS_LAUTRA_FEATURESERVER+'/6' };
const GILI_MATRA_EXTENT = { xmin:115.9, ymin:-8.5, xmax:116.2, ymax:-8.15, spatialReference:{wkid:4326} };
const EVIKA_DEMO_OFFSETS = {2021:-12.8,2022:-9.4,2023:-6.7,2024:-3.1,2025:0};

async function omsLautraQuery(layerId, params={}){
  const query = new URLSearchParams({f:'json',where:'1=1',returnGeometry:'false',...params});
  const res = await fetch(OMS_LAUTRA_LAYERS[layerId]+'/query?'+query.toString());
  if(!res.ok) throw new Error('ArcGIS Feature Service tidak tersedia');
  const data = await res.json();
  if(data.error) throw new Error(data.error.message||'Query ArcGIS gagal');
  return data;
}
async function omsEvikaRows(year){
  try{return await window.fetchEvikaAreaValue(year);}
  catch(_){const baseline=await window.fetchEvikaAreaValue(2025),offset=EVIKA_DEMO_OFFSETS[year]||0;return baseline.map(row=>({...row,year,score:Math.max(0,Math.min(100,row.score+offset)),source:'SIMULASI prototype · baseline SIDAKO 2025',simulation:true}));}
}

function buildAnswer(query){
  const { province, topic, wpp } = query;
  if (!province) {
    return {
      ok:false,
      text:'Sebutkan nama provinsi agar saya bisa menariknya dari Portal Satu Data KKP — misalnya "jumlah nelayan di Jawa Timur" atau "pembudidaya di Maluku".' +
        (wpp ? (' (WPP ' + wpp + ' mencakup beberapa provinsi pesisir — saya perlu satu nama provinsi untuk mengambil angkanya.') : ''),
    };
  }
  return { ok:true, province, topic, wpp };
}

/* Drag-resizable two-pane split (chat/results | map). The map pane's width is
   tracked as a percentage of the container; dragging the handle between the
   panes updates it live. Falls back to a single full-width pane when the map
   is closed. */
function SplitPane({ children, rightPct, onRightPctChange, minPct=22, maxPct=72, collapsed }){
  const [left, right] = React.Children.toArray(children);
  const containerRef = React.useRef(null);
  const draggingRef = React.useRef(false);

  React.useEffect(()=>{
    const onMove = (e) => {
      if (!draggingRef.current || !containerRef.current) return;
      const rect = containerRef.current.getBoundingClientRect();
      const pct = ((rect.right - e.clientX) / rect.width) * 100;
      onRightPctChange(Math.min(maxPct, Math.max(minPct, pct)));
    };
    const onUp = () => { draggingRef.current = false; document.body.style.cursor=''; document.body.style.userSelect=''; };
    window.addEventListener('mousemove', onMove);
    window.addEventListener('mouseup', onUp);
    return () => { window.removeEventListener('mousemove', onMove); window.removeEventListener('mouseup', onUp); };
  }, [minPct, maxPct, onRightPctChange]);

  const startDrag = (e) => { draggingRef.current = true; document.body.style.cursor = 'col-resize'; document.body.style.userSelect = 'none'; e.preventDefault(); };

  if (collapsed) return <div style={{flex:1,minWidth:0,minHeight:0,display:'flex',flexDirection:'column'}}>{left}</div>;

  return (
    <div ref={containerRef} style={{display:'flex',minWidth:0,minHeight:0,height:'100%',width:'100%',maxWidth:'100%'}}>
      <div style={{flex:'1 1 auto',minWidth:0,minHeight:0,display:'flex',flexDirection:'column'}}>{left}</div>
      <div onMouseDown={startDrag} title="Seret untuk mengubah ukuran"
        style={{flex:'none',width:10,cursor:'col-resize',position:'relative',background:'transparent'}}>
        <div style={{position:'absolute',top:0,bottom:0,left:'50%',width:1,background:'var(--border-subtle)'}} />
        <div style={{position:'absolute',top:'50%',left:'50%',transform:'translate(-50%,-50%)',width:4,height:32,borderRadius:'var(--radius-pill)',background:'var(--border-subtle)'}} />
      </div>
      <div style={{flex:'0 0 '+rightPct+'%',minWidth:0,minHeight:0,display:'flex',flexDirection:'column'}}>{right}</div>
    </div>
  );
}

function Stage({ s }){
  return (
    <div style={{display:'flex',gap:'var(--space-3)',alignItems:'flex-start',padding:'var(--space-2) 0'}}>
      <span style={{color:s.pending?'var(--text-muted)':'var(--status-good)',marginTop:1}}>
        <Icon name={s.pending?'clock':'check-circle'} size={14} />
      </span>
      <span style={{fontSize:'var(--type-body-sm-size)'}}>
        <span style={{fontWeight:600,color:'var(--text-heading)'}}>{s.t}</span>
        <span style={{display:'block',color:'var(--text-muted)',fontSize:'var(--type-caption-size)'}}>{s.d}</span>
      </span>
    </div>
  );
}

/* Map viewer: docked beside the conversation by default, can expand to fill the screen.
   Renders a real ArcGIS JS API MapView with the WPP-RI boundary and thematic layers
   served live from Portal Satu Data KKP's GeoServer (portaldata.kkp.go.id/gis/wms),
   plus a marker for whatever the AI agent last looked up. */
function MapViewer({ expanded, onToggleExpand, onClose, onAddContext, initialTool, focus, workspace, setWorkspace }){
  const [tool,setTool] = React.useState(initialTool || null);
  const [shareUrl,setShareUrl] = React.useState('');
  const [shareStatus,setShareStatus] = React.useState('');
  const [aoi,setAoi] = React.useState(null);
  const [aoiDrawing,setAoiDrawing] = React.useState(false);
  const [measureMode,setMeasureMode] = React.useState('distance');
  const [mapReady,setMapReady] = React.useState(false);
  const [mapError,setMapError] = React.useState(null);
  const [is3D,setIs3D] = React.useState(workspace?.view?.mode==='3d');
  const [layers,setLayers] = React.useState(()=>[
    {id:'wpp',label:'Batas WPP-NRI',meta:'GeoServer KKP · live',icon:'polygon-vertices',visible:true},
    ...((workspace?.layers||[]).map(l=>({id:l.id,label:l.title,meta:l.source==='catalog'?'Katalog Data KKP':l.source==='ai'?'Hasil Agentic AI':'Workspace',icon:'map',visible:l.visible!==false})))
  ]);
  const [catalogOpen,setCatalogOpen] = React.useState(false);
  const [catalogRows,setCatalogRows] = React.useState(null);
  const [catalogQuery,setCatalogQuery] = React.useState('');
  const containerRef = React.useRef(null);
  const measurementNodeRef = React.useRef(null);
  const esri = React.useRef({});
  const toggle = id => setLayers(ls=>ls.map(l=>l.id===id?{...l,visible:!l.visible}:l));
  const persistLayer = descriptor => setWorkspace&&setWorkspace(ws=>{
    const exists=(ws.layers||[]).some(l=>l.key===descriptor.key);
    return exists?ws:{...ws,layers:[...(ws.layers||[]),descriptor]};
  });
  const removePersistedLayer = id => setWorkspace&&setWorkspace(ws=>({...ws,layers:(ws.layers||[]).filter(l=>l.id!==id)}));
  const handleWorkspaceLayerChange=React.useCallback((action,layer,patch)=>{
    const id=layer?.__omsWorkspaceId;if(!id||!setWorkspace)return;
    if(action==='remove'){removePersistedLayer(id);setLayers(ls=>ls.filter(item=>item.id!==id));return;}
    setWorkspace(ws=>({...ws,layers:(ws.layers||[]).map(item=>item.id===id?{...item,...patch}:item)}));
    if(action==='rename')setLayers(ls=>ls.map(item=>item.id===id?{...item,label:patch.title}:item));
    if(action==='visibility')setLayers(ls=>ls.map(item=>item.id===id?{...item,visible:patch.visible}:item));
  },[setWorkspace]);
  const selectBasemap = id => {
    if(esri.current.map)esri.current.map.basemap=id;
    setWorkspace&&setWorkspace(ws=>({...ws,basemap:id}));
  };
  const buildShareUrl = () => {
    const e=esri.current,view=e.view;
    const center=view?.center?[view.center.longitude,view.center.latitude]:(workspace?.view?.center||[121.9,-0.4]);
    const snapshot={
      v:1,basemap:e.map?.basemap?.id||workspace?.basemap||'oceans',
      view:{center,zoom:view?.zoom||workspace?.view?.zoom||4,mode:view?.type==='3d'?'3d':'2d'},
      layers:(workspace?.layers||[]).map(({id,key,title,source,type,url,wmsLayer,definitionExpression,visible,opacity,graphics})=>({id,key,title,source,type,url,wmsLayer,definitionExpression,visible,opacity,graphics})),
    };
    const base64=btoa(unescape(encodeURIComponent(JSON.stringify(snapshot)))).replace(/\+/g,'-').replace(/\//g,'_').replace(/=+$/,'');
    const url=new URL('/oms/assistant',window.location.origin);url.searchParams.set('webmap',base64);
    return url.toString();
  };
  const copyShareUrl = async () => {
    try{
      const url=buildShareUrl();setShareUrl(url);
      await navigator.clipboard.writeText(url);
      setShareStatus('Tautan webmap disalin.');
    }catch(err){setShareStatus('Tautan dibuat, tetapi clipboard tidak tersedia. Salin manual dari kolom di bawah.');}
  };
  const startAoiDrawing = () => {
    const sketch=esri.current.sketchViewModel;
    if(!sketch||!mapReady)return;
    esri.current.aoiLayer?.removeAll();
    setAoi(null);setAoiDrawing(true);
    sketch.cancel();
    sketch.create('polygon');
  };
  const addAoiToConversation = () => {
    if(!aoi)return;
    onAddContext&&onAddContext(aoi);
    setTool(null);
  };
  React.useEffect(()=>{if(catalogOpen&&!catalogRows)window.fetchLayerCatalog().then(setCatalogRows).catch(()=>setCatalogRows([]));},[catalogOpen]);
  const addCatalogLayer = row => {
    const e=esri.current;if(!e.map)return;
    const key=row.featureServiceUrl?'feature|'+row.featureServiceUrl+'|1=1':'wms|'+row.wmsLayer;
    const existing=(workspace?.layers||[]).find(l=>l.key===key);
    if(existing){setLayers(ls=>ls.map(l=>l.id===existing.id?{...l,visible:true}:l));if(row.extent&&e.view)e.view.goTo({type:'extent',...row.extent,spatialReference:{wkid:4326}},{duration:800}).catch(()=>{});setCatalogOpen(false);return;}
    const id='catalog-layer-'+Date.now(),layer=row.featureServiceUrl?e.makeFeatureLayer(row.featureServiceUrl,row.title,'1=1'):e.makeLayer(row.wmsLayer);layer.title=row.title;layer.visible=true;layer.__omsWorkspaceId=id;e.map.add(layer,Math.max(1,e.map.layers.length-1));e.agentLayers[id]=layer;
    setLayers(ls=>[...ls,{id,label:row.title,meta:'Katalog Data KKP',icon:'map',visible:true}]);
    persistLayer({id,key,title:row.title,source:'catalog',type:row.featureServiceUrl?'feature':'wms',url:row.featureServiceUrl||null,wmsLayer:row.wmsLayer||null,definitionExpression:'1=1',visible:true,opacity:.9});
    if(row.featureServiceUrl)layer.when(()=>{layer.popupTemplate={title:row.title,content:[{type:'fields',fieldInfos:(layer.fields||[]).filter(f=>f.type!=='geometry').map(f=>({fieldName:f.name,label:f.alias||f.name}))}]};return layer.queryExtent();}).then(result=>{if(result?.extent)e.view.goTo(result.extent.expand(1.1),{duration:800}).catch(()=>{});}).catch(()=>{});
    if(row.extent&&e.view)e.view.goTo({type:'extent',...row.extent,spatialReference:{wkid:4326}},{duration:800}).catch(()=>{});
    setCatalogOpen(false);
  };

  // Boot the ArcGIS MapView once. The KKP layers are drawn through a WebTileLayer
  // subclass that builds WMS GetMap URLs itself: WebTileLayer loads tiles as plain
  // <img> requests (no CORS needed, unlike WMSLayer's fetch()-based compositing),
  // and skips fetching GeoServer's multi-megabyte capabilities document entirely.
  React.useEffect(()=>{
    let cancelled = false;
    if (!window.require) { setMapError('SDK ArcGIS gagal dimuat.'); return; }
    window.require(['esri/Map','esri/views/MapView','esri/views/SceneView','esri/layers/WebTileLayer','esri/layers/FeatureLayer','esri/layers/GraphicsLayer','esri/layers/GroupLayer','esri/Graphic','esri/widgets/Sketch/SketchViewModel','esri/geometry/geometryEngine','esri/geometry/support/webMercatorUtils','esri/widgets/Measurement'],
      (Map, MapView, SceneView, WebTileLayer, FeatureLayer, GraphicsLayer, GroupLayer, Graphic, SketchViewModel, geometryEngine, webMercatorUtils, Measurement) => {
        if (cancelled || !containerRef.current) return;
        const KkpWmsTileLayer = WebTileLayer.createSubclass({
          properties: { kkpLayerName: null },
          getTileUrl: function(level, row, col){
            return window.tileToWmsUrl(window.KKP_GIS_BASE, this.kkpLayerName, level, row, col, this.tileInfo.size[0]);
          },
        });
        const lods = [];
        for (let i=0;i<20;i++){
          const resolution = 156543.03392804097 / Math.pow(2,i);
          lods.push({ level:i, resolution, scale: resolution*3779.5275590551185 });
        }
        const makeLayer = (layerName) => new KkpWmsTileLayer({
          kkpLayerName: layerName,
          urlTemplate: window.KKP_GIS_BASE + '?dummy',
          tileInfo: {
            size:[256,256], dpi:96, format:'png32',
            origin:{ x:-20037508.342787, y:20037508.342787 },
            spatialReference:{ wkid:102100 }, lods,
          },
          fullExtent: { xmin:9000000, ymin:-1700000, xmax:17200000, ymax:1050000, spatialReference:{ wkid:102100 } },
          spatialReference: { wkid:102100 },
        });
        const makeFeatureLayer = (url,title,where) => new FeatureLayer({url,title,definitionExpression:where||'1=1',outFields:['*'],popupEnabled:true,opacity:.9,renderer:{type:'simple',symbol:{type:'simple-fill',color:[29,191,118,.42],outline:{color:[7,88,66,.95],width:1.4}}}});
        const wppLayer = makeLayer('demo:WPPNRI_LN');wppLayer.title='Batas WPP-NRI';
        const omsLautraGroup=new GroupLayer({title:'OMS_Lautra_d',visibilityMode:'independent',visible:true,layers:[
          ['Mangrove',0],['Substrat Dasar',1],['Makroalga',2],['Lamun',3],['Terumbu Karang',4],['Lokasi Indikatif OECM',5],['Kawasan Konservasi',6],['Kawasan yang ditetapkan',7],['Provincial Marine Zones (WKLP)',8],
        ].reverse().map(([title,id])=>new FeatureLayer({url:'https://services8.arcgis.com/mpSDBlkEzjS62WgX/ArcGIS/rest/services/OMS_Lautra_d/FeatureServer/'+id,title,visible:false,outFields:['*'],popupEnabled:true}))});
        const graphicsLayer = new GraphicsLayer({title:'Hasil sementara',listMode:'hide'});
        const aoiLayer = new GraphicsLayer({title:'Area of Interest',listMode:'hide'});
        const restoredLayers=[];
        (workspace?.layers||[]).forEach(d=>{
          try{
            let layer=d.type==='feature'?makeFeatureLayer(d.url,d.title,d.definitionExpression):d.type==='wms'?makeLayer(d.wmsLayer):null;
            if(d.type==='graphics'){
              layer=new GraphicsLayer({title:d.title});
              (d.graphics||[]).forEach(g=>layer.add(new Graphic({geometry:g.geometry,symbol:g.symbol,attributes:g.attributes,popupTemplate:g.popupTemplate})));
            }
            if(layer){layer.title=d.title;layer.visible=d.visible!==false;layer.opacity=d.opacity??.9;layer.__omsWorkspaceId=d.id;restoredLayers.push(layer);}
          }catch(_){}
        });
        const map = new Map({ basemap:workspace?.basemap||'oceans', ground:'world-elevation', layers:[wppLayer,omsLautraGroup,...restoredLayers,graphicsLayer,aoiLayer] });
        const initialMode=workspace?.view?.mode==='3d'?'3d':'2d';
        const InitialViewClass=initialMode==='3d'?SceneView:MapView;
        const initialCenter=workspace?.view?.center||[121.9,-0.4],initialZoom=workspace?.view?.zoom||4;
        const view = new InitialViewClass({
          container: containerRef.current, map, center:initialCenter, zoom:initialZoom,
          constraints:{ snapToZoom:false }, popupEnabled:true, popup:{ dockEnabled:false,defaultPopupTemplateEnabled:true },
          ...(initialMode==='3d'?{camera:{position:{longitude:initialCenter[0],latitude:initialCenter[1],z:initialZoom>6?150000:900000},tilt:55}}:{}),
        });
        view.ui.remove('zoom');
        const sketchViewModel=new SketchViewModel({
          view,layer:aoiLayer,
          polygonSymbol:{type:'simple-fill',color:[0,158,232,.18],outline:{color:[0,158,232,1],width:2}},
          defaultCreateOptions:{hasZ:false},
        });
        const sketchHandle=sketchViewModel.on('create',event=>{
          if(event.state==='start')setAoiDrawing(true);
          if(event.state==='cancel'){setAoiDrawing(false);return;}
          if(event.state!=='complete')return;
          setAoiDrawing(false);
          const geometry=event.graphic.geometry;
          const geographicGeometry=geometry.spatialReference?.isWebMercator?webMercatorUtils.webMercatorToGeographic(geometry):geometry;
          const center=geographicGeometry.extent.center;
          const areaHa=Math.abs(geometryEngine.geodesicArea(geometry,'hectares'));
          const context={
            type:'polygon',geometry:geographicGeometry.toJSON(),
            center:[center.longitude,center.latitude],
            areaHa:Number.isFinite(areaHa)?Number(areaHa.toFixed(2)):null,
            bbox:[geographicGeometry.extent.xmin,geographicGeometry.extent.ymin,geographicGeometry.extent.xmax,geographicGeometry.extent.ymax],
            spatialReference:4326,
            label:'Area of Interest pengguna',createdAt:new Date().toISOString(),
          };
          setAoi(context);
        });
        view.when(()=>{ if (!cancelled){setMapReady(true);esri.current.viewHandle=view.watch('stationary',stationary=>{if(!stationary||!setWorkspace)return;setWorkspace(ws=>({...ws,view:{center:[view.center.longitude,view.center.latitude],zoom:view.zoom,mode:initialMode}}));});} }, err=>{ if(!cancelled) setMapError(String(err && err.message || err)); });
        const restoredMap={};(workspace?.layers||[]).forEach((d,i)=>{if(restoredLayers[i])restoredMap[d.id]=restoredLayers[i];});
        esri.current = { Map, MapView, SceneView, Measurement, makeLayer, makeFeatureLayer, Graphic, GraphicsLayer, GroupLayer, map, view, wppLayer, omsLautraGroup, graphicsLayer, aoiLayer, sketchViewModel, sketchHandle, measurementWidget:null, agentLayers:restoredMap, layerSeq:0, viewHandle:null };
      }, err => { if(!cancelled) setMapError(String(err && err.message || err)); });
    return () => {
      cancelled = true;
      if(esri.current.sketchHandle)esri.current.sketchHandle.remove();
      if(esri.current.sketchViewModel)esri.current.sketchViewModel.destroy();
      if(esri.current.measurementWidget)esri.current.measurementWidget.destroy();
      if (esri.current.viewHandle)esri.current.viewHandle.remove();
      if (esri.current.view) { esri.current.view.destroy(); esri.current = {}; }
    };
  }, []);

  React.useEffect(()=>{
    const e=esri.current;
    if(e.measurementWidget){e.measurementWidget.clear();e.measurementWidget.destroy();e.measurementWidget=null;}
    if(tool!=='measure'||!mapReady||!measurementNodeRef.current||!e.Measurement||!e.view)return;
    const activeTool=measureMode==='area'?'area':(e.view.type==='3d'?'direct-line':'distance');
    e.measurementWidget=new e.Measurement({view:e.view,container:measurementNodeRef.current,activeTool});
    return ()=>{if(e.measurementWidget){e.measurementWidget.clear();e.measurementWidget.destroy();e.measurementWidget=null;}};
  },[tool,is3D,mapReady,expanded]);

  React.useEffect(()=>{
    const e=esri.current;
    if(tool!=='measure'||!e.measurementWidget||!e.view)return;
    e.measurementWidget.clear();
    e.measurementWidget.activeTool=measureMode==='area'?'area':(e.view.type==='3d'?'direct-line':'distance');
  },[measureMode,is3D,tool]);

  // Swap the 2D MapView for a 3D SceneView (or back) in place, reusing the same
  // Map instance — every layer (WPP boundary, thematic overlay, graphics) is
  // attached to the Map, not the View, so nothing needs re-adding.
  const toggle3D = () => {
    const e = esri.current;
    if (!e.view || !e.map) return;
    const center = e.view.center;
    const zoom = e.view.zoom;
    const nextIs3D = !is3D;
    const oldView = e.view;
    oldView.container = null;
    oldView.map = null; // detach before destroy so it doesn't cascade-destroy the shared Map
    oldView.destroy();
    const ViewClass = nextIs3D ? e.SceneView : e.MapView;
    const newView = new ViewClass({
      container: containerRef.current, map: e.map,
      center: [center.longitude, center.latitude], zoom,
      constraints:{ snapToZoom:false }, popupEnabled:true, popup:{ dockEnabled:false,defaultPopupTemplateEnabled:true },
      ...(nextIs3D ? { camera:{ position:{ longitude:center.longitude, latitude:center.latitude, z:zoom>6?150000:900000 }, tilt:55 } } : {}),
    });
    newView.ui.remove('zoom');
    e.view = newView;
    if(e.sketchViewModel)e.sketchViewModel.view=newView;
    setIs3D(nextIs3D);
    setWorkspace&&setWorkspace(ws=>({...ws,view:{...(ws.view||{}),center:[center.longitude,center.latitude],zoom,mode:nextIs3D?'3d':'2d'}}));
  };

  // Keep the ArcGIS layers synchronized with every Layer List checkbox.
  React.useEffect(()=>{
    const e=esri.current;
    if (e.wppLayer) e.wppLayer.visible = layers.find(l=>l.id==='wpp')?.visible !== false;
    Object.entries(e.agentLayers||{}).forEach(([id,layer])=>{const row=layers.find(item=>item.id===id);if(row)layer.visible=row.visible!==false;});
    if(setWorkspace)setWorkspace(ws=>({...ws,layers:(ws.layers||[]).map(item=>{const row=layers.find(l=>l.id===item.id);return row?{...item,visible:row.visible}:item;})}));
  }, [layers]);

  // Whenever the agent resolves a new query, fly the map there, swap in the
  // matching KKP thematic WMS layer (if one exists for that topic), and drop a
  // marker carrying the real figure that was just fetched.
  React.useEffect(()=>{
    const e = esri.current;
    if (!mapReady || !e.view || !focus) return;

    if (focus.extent) {
      e.view.goTo({ type:'extent', ...focus.extent, spatialReference:{ wkid:4326 } }, { duration:900 }).catch(()=>{});
    } else {
      e.view.goTo({ center:[focus.lon, focus.lat], zoom:focus.zoom || 6 }, { duration:900 }).catch(()=>{});
    }

    if (focus.wmsLayer || focus.featureServiceUrl) {
      const key=focus.featureServiceUrl?'feature|'+focus.featureServiceUrl+'|'+(focus.definitionExpression||'1=1'):'wms|'+focus.wmsLayer;
      const existing=(workspace?.layers||[]).find(item=>item.key===key);
      if(existing&&e.agentLayers[existing.id]){
        e.agentLayers[existing.id].visible=true;
        setLayers(ls=>ls.map(item=>item.id===existing.id?{...item,visible:true}:item));
      }else{
        const layer = focus.featureServiceUrl ? e.makeFeatureLayer(focus.featureServiceUrl,focus.topicLabel,focus.definitionExpression) : e.makeLayer(focus.wmsLayer);
        e.map.add(layer, 1);
        const layerId='agent-layer-'+Date.now()+'-'+(++e.layerSeq);layer.__omsWorkspaceId=layerId;e.agentLayers[layerId]=layer;
        if(focus.featureServiceUrl) layer.when(()=>{layer.popupTemplate={title:focus.topicLabel||layer.title,content:[{type:'fields',fieldInfos:(layer.fields||[]).filter(field=>field.type!=='geometry').map(field=>({fieldName:field.name,label:field.alias||field.name}))}]};return focus.zoomToLayer?layer.queryExtent({where:focus.definitionExpression||'1=1'}):null;}).then(result=>{if(result?.extent)e.view.goTo(result.extent.expand(1.15),{duration:900}).catch(()=>{});}).catch(()=>{});
        setLayers(ls => [...ls, { id:layerId, label:focus.topicLabel||focus.label||'Layer hasil agent', meta:focus.featureServiceUrl?'ArcGIS Feature Service · live':'GeoServer KKP · live', icon:'graph-bar', visible:true }]);
        persistLayer({id:layerId,key,title:focus.topicLabel||focus.label||'Layer hasil agent',source:focus.catalogSource?'catalog':'ai',type:focus.featureServiceUrl?'feature':'wms',url:focus.featureServiceUrl||null,wmsLayer:focus.wmsLayer||null,definitionExpression:focus.definitionExpression||'1=1',visible:true,opacity:.9});
      }
    }

    const addGraphicLayer=(graphic,label)=>{const resultLayer=new e.GraphicsLayer({title:label||'Hasil prompt'}),layerId='agent-layer-'+Date.now()+'-'+(++e.layerSeq);resultLayer.__omsWorkspaceId=layerId;resultLayer.add(graphic);e.map.add(resultLayer);e.agentLayers[layerId]=resultLayer;setLayers(ls=>[...ls,{id:layerId,label:label||'Hasil prompt',meta:'Hasil Agentic AI',icon:'map',visible:true}]);persistLayer({id:layerId,key:'graphics|'+layerId,title:label||'Hasil prompt',source:'ai',type:'graphics',visible:true,opacity:1,graphics:[{geometry:graphic.geometry?.toJSON?graphic.geometry.toJSON():graphic.geometry,symbol:graphic.symbol?.toJSON?graphic.symbol.toJSON():graphic.symbol,attributes:graphic.attributes,popupTemplate:graphic.popupTemplate?.toJSON?graphic.popupTemplate.toJSON():graphic.popupTemplate}]});};
    if (focus.polygon) {
      const graphic = new e.Graphic({
        geometry:{ type:'polygon', rings:[focus.polygon] },
        symbol:{ type:'simple-fill', color:[175,212,34,0.22], outline:{ color:[175,212,34,0.95], width:2 } },
        attributes:{ title:focus.label, value:focus.valueText },
        popupTemplate:{ title:'{title}', content:'{value}' },
      });
      addGraphicLayer(graphic,focus.label||'Area hasil analisis');
      e.view.openPopup({ location:graphic.geometry.extent.center, features:[graphic] });
    } else if (!focus.extent && !focus.suppressMarker) {
      const graphic = new e.Graphic({
        geometry:{ type:'point', longitude:focus.lon, latitude:focus.lat },
        symbol:{ type:'simple-marker', color:[175,212,34,0.95], size:15, outline:{ color:[11,18,32,0.9], width:1.5 } },
        attributes:{ title:focus.label, value:focus.valueText },
        popupTemplate:{ title:'{title}', content:'{value}' },
      });
      addGraphicLayer(graphic,focus.label||'Lokasi hasil prompt');
      e.view.openPopup({ location:graphic.geometry, features:[graphic] });
    }
  }, [focus, mapReady]);

  return (
    <div className={expanded?'calcite-mode-dark calcite-surface':undefined}
      style={{position:expanded?'fixed':'relative',inset:expanded?0:undefined,zIndex:expanded?'var(--z-modal)':undefined,
        display:'flex',flexDirection:'column',height:'100%',background:expanded?undefined:'var(--kkp-indigo-900)',borderRadius:0,overflow:'hidden'}}>

      <div style={{flex:1,display:'flex',minHeight:0,position:'relative'}}>
        <div style={{position:'absolute',right:8,top:8,zIndex:6,display:'flex',gap:6}}>
          <button onClick={onToggleExpand} aria-label={expanded?'Kecilkan peta':'Perbesar peta penuh'} title={expanded?'Kecilkan peta':'Perbesar peta penuh'}
            style={{width:30,height:30,borderRadius:'var(--radius-sm)',border:'none',cursor:'pointer',display:'flex',alignItems:'center',justifyContent:'center',
              background:'rgba(11,18,32,.85)',color:'#fff',boxShadow:'0 4px 16px rgba(0,0,0,.3)'}}>
            <Icon name={expanded?'contract':'extent'} size={15} style={{filter:'invert(1)'}} />
          </button>
          {onClose ? (
            <button onClick={onClose} aria-label="Tutup peta" title="Tutup peta"
              style={{width:30,height:30,borderRadius:'var(--radius-sm)',border:'none',cursor:'pointer',display:'flex',alignItems:'center',justifyContent:'center',
                background:'rgba(11,18,32,.85)',color:'#fff',boxShadow:'0 4px 16px rgba(0,0,0,.3)'}}>
              <Icon name="x" size={15} style={{filter:'invert(1)'}} />
            </button>
          ) : null}
        </div>
        {!expanded ? (
          <div style={{position:'absolute',left:8,top:8,zIndex:5,display:'flex',flexDirection:'column',gap:6,padding:'6px',background:'rgba(11,18,32,.85)',borderRadius:'var(--radius-sm)',boxShadow:'0 4px 16px rgba(0,0,0,.3)'}}>
            {[['layers','Layer'],['adddata','Tambah data'],['basemap','Basemap'],['aoi','Area of Interest'],['measure','Ukur jarak & luas']].map(([id,l])=>(
              <button key={id} onClick={()=>setTool(t=>t===id?null:id)} aria-label={l} title={l}
                style={{width:30,height:30,borderRadius:'var(--radius-sm)',border:'none',cursor:'pointer',display:'flex',alignItems:'center',justifyContent:'center',
                  background:tool===id?'rgba(0,158,232,.3)':'transparent',color:'#fff'}}>
                <Icon name={id==='layers'?'layers':id==='adddata'?'plus':id==='basemap'?'map':id==='measure'?'measure':'crop'} size={15} style={{filter:'invert(1)'}} />
              </button>
            ))}
            <div style={{height:1,background:'rgba(255,255,255,.18)',margin:'2px 4px'}} />
            <button onClick={toggle3D} aria-label="Tampilan 3D" title={is3D?'Kembali ke 2D':'Tampilan 3D'}
              style={{width:30,height:30,borderRadius:'var(--radius-sm)',border:'none',cursor:'pointer',display:'flex',alignItems:'center',justifyContent:'center',
                background:is3D?'rgba(175,212,34,.35)':'transparent',color:'#fff',fontSize:10,fontWeight:700}}>
              {is3D?'2D':'3D'}
            </button>
            <button onClick={()=>setTool(t=>t==='share'?null:'share')} aria-label="Share Webmap" title="Share Webmap"
              style={{width:30,height:30,borderRadius:'var(--radius-sm)',border:'none',cursor:'pointer',display:'flex',alignItems:'center',justifyContent:'center',
                background:tool==='share'?'rgba(175,212,34,.35)':'transparent',color:'#fff'}}>
              <Icon name="share" size={15} style={{filter:'invert(1)'}} />
            </button>
          </div>
        ) : null}
        {!expanded && tool==='layers' ? (
          <div className="calcite-mode-dark" style={{position:'absolute',left:52,top:8,zIndex:5,width:320,background:'var(--kkp-ink-800)',color:'#fff',overflow:'auto',borderRadius:'var(--radius-sm)',boxShadow:'0 4px 16px rgba(0,0,0,.3)',maxHeight:'calc(100% - 16px)'}}>
            <ArcgisLayerListPanel view={esri.current.view} mapReady={mapReady} onWorkspaceLayerChange={handleWorkspaceLayerChange}/>
          </div>
        ) : null}
        {!expanded && tool==='aoi' ? (
          <div style={{position:'absolute',left:52,top:8,zIndex:5,width:200,background:'var(--kkp-ink-800)',color:'#fff',padding:'var(--space-3)',overflow:'auto',borderRadius:'var(--radius-sm)',boxShadow:'0 4px 16px rgba(0,0,0,.3)',maxHeight:'calc(100% - 16px)'}}>
            <div style={{fontSize:11,fontWeight:700,letterSpacing:'.06em',textTransform:'uppercase',color:'rgba(255,255,255,.6)',marginBottom:8}}>Area of Interest</div>
            <p style={{fontSize:12,color:'rgba(255,255,255,.75)',margin:'0 0 10px'}}>{aoiDrawing?'Klik beberapa titik pada peta, lalu klik ganda untuk menyelesaikan poligon.':aoi?'AOI siap · '+(aoi.areaHa!=null?window.formatNumber(aoi.areaHa)+' ha':'luas belum tersedia'):'Gambar poligon untuk membatasi konteks Ask AI.'}</p>
            <button className="kkp-btn kkp-btn--secondary kkp-btn--s kkp-btn--block" onClick={startAoiDrawing}>{aoi?'Gambar ulang AOI':'Gambar AOI'}</button>
            <button className="kkp-btn kkp-btn--primary kkp-btn--s kkp-btn--block" disabled={!aoi||aoiDrawing} style={{marginTop:6}} onClick={addAoiToConversation}>Tambahkan ke Ask AI</button>
          </div>
        ) : null}
        {!expanded && tool==='basemap' ? (
          <div style={{position:'absolute',left:52,top:8,zIndex:7,width:220,background:'var(--kkp-ink-800)',color:'#fff',padding:12,borderRadius:'var(--radius-sm)',boxShadow:'0 4px 16px rgba(0,0,0,.3)'}}>
            <div style={{fontSize:11,fontWeight:800,marginBottom:8}}>BASEMAP</div>
            {[['oceans','Oceans'],['satellite','Imagery'],['topo-vector','Topographic'],['streets-navigation-vector','Navigation'],['dark-gray-vector','Dark Gray']].map(([id,label])=><button key={id} onClick={()=>selectBasemap(id)} style={{display:'block',width:'100%',textAlign:'left',padding:'8px',marginBottom:4,border:'1px solid rgba(255,255,255,.12)',borderRadius:5,background:workspace?.basemap===id?'rgba(0,158,232,.28)':'rgba(255,255,255,.04)',color:'#fff',cursor:'pointer'}}>{label}</button>)}
          </div>
        ) : null}
        {!expanded && tool==='adddata' ? (
          <div style={{position:'absolute',left:52,top:8,zIndex:7,width:300,maxHeight:'calc(100% - 16px)',overflow:'auto',background:'var(--kkp-ink-800)',color:'#fff',padding:12,borderRadius:'var(--radius-sm)',boxShadow:'0 4px 16px rgba(0,0,0,.3)'}}>
            <div style={{fontSize:11,fontWeight:800,marginBottom:8}}>TAMBAH DATA DARI KATALOG</div>
            <input value={catalogQuery} onChange={e=>setCatalogQuery(e.target.value)} onFocus={()=>setCatalogOpen(true)} placeholder="Cari layer KKP…" style={{width:'100%',boxSizing:'border-box',padding:'8px 10px',borderRadius:5,border:'1px solid rgba(255,255,255,.2)',background:'rgba(255,255,255,.08)',color:'#fff',marginBottom:8}} />
            {!catalogRows?<button onClick={()=>setCatalogOpen(true)} style={{width:'100%',padding:8}}>Muat katalog data</button>:(catalogRows.filter(r=>!catalogQuery||r.title.toLowerCase().includes(catalogQuery.toLowerCase())||r.name.toLowerCase().includes(catalogQuery.toLowerCase())).slice(0,12).map(r=><button key={r.id} onClick={()=>addCatalogLayer(r)} style={{display:'block',width:'100%',textAlign:'left',padding:'8px',marginBottom:4,border:'1px solid rgba(255,255,255,.1)',borderRadius:5,background:'rgba(255,255,255,.04)',color:'#fff',cursor:'pointer'}}><span style={{display:'block',fontWeight:700,fontSize:11}}>{r.title}</span><span style={{fontSize:9,opacity:.55}}>{r.workspace}</span></button>))}
          </div>
        ) : null}
        {!expanded && tool==='share' ? (
          <div style={{position:'absolute',left:52,top:8,zIndex:7,width:280,background:'var(--kkp-ink-800)',color:'#fff',padding:12,borderRadius:'var(--radius-sm)',boxShadow:'0 4px 16px rgba(0,0,0,.3)'}}>
            <div style={{fontSize:11,fontWeight:800,marginBottom:6}}>SHARE WEBMAP</div>
            <p style={{fontSize:10.5,lineHeight:1.45,color:'rgba(255,255,255,.65)',margin:'0 0 9px'}}>Bagikan basemap, posisi kamera, mode 2D/3D, dan layer aktif.</p>
            <button className="kkp-btn kkp-btn--primary kkp-btn--s kkp-btn--block" onClick={copyShareUrl}>Buat &amp; salin tautan</button>
            {shareUrl?<input readOnly value={shareUrl} onFocus={e=>e.target.select()} style={{width:'100%',boxSizing:'border-box',marginTop:8,padding:'6px 7px',fontSize:9,borderRadius:4,border:'1px solid rgba(255,255,255,.18)'}}/>:null}
            {shareStatus?<div style={{fontSize:9.5,color:'#dff486',marginTop:6}}>{shareStatus}</div>:null}
          </div>
        ) : null}
        {!expanded && tool==='measure' ? (
          <div style={{position:'absolute',left:52,top:8,zIndex:7,width:300,maxHeight:'calc(100% - 16px)',overflow:'auto',background:'var(--kkp-ink-800)',color:'#fff',padding:12,borderRadius:'var(--radius-sm)',boxShadow:'0 4px 16px rgba(0,0,0,.3)'}}>
            <div style={{fontSize:11,fontWeight:800,marginBottom:8}}>MEASURE</div>
            <div style={{display:'grid',gridTemplateColumns:'1fr 1fr',gap:6,marginBottom:8}}>
              <button onClick={()=>setMeasureMode('distance')} style={{padding:7,border:'1px solid rgba(255,255,255,.18)',borderRadius:5,background:measureMode==='distance'?'rgba(0,158,232,.3)':'rgba(255,255,255,.05)',color:'#fff',cursor:'pointer'}}>Jarak</button>
              <button onClick={()=>setMeasureMode('area')} style={{padding:7,border:'1px solid rgba(255,255,255,.18)',borderRadius:5,background:measureMode==='area'?'rgba(0,158,232,.3)':'rgba(255,255,255,.05)',color:'#fff',cursor:'pointer'}}>Luas</button>
            </div>
            <div ref={measurementNodeRef} className="calcite-mode-dark" />
          </div>
        ) : null}
        {expanded ? (
          <>
            <ActionBar active={is3D?'twin':tool} onSelect={id=>id==='twin'?toggle3D():setTool(t=>t===id?null:id)} actions={[
              {id:'layers',icon:'layers',label:'Layer'},{id:'adddata',icon:'plus',label:'Tambah data dari katalog'},{id:'basemap',icon:'map',label:'Basemap'},{id:'legend',icon:'legend',label:'Legenda'},
              {id:'aoi',icon:'crop',label:'Pilih Area of Interest'},
              {id:'measure',icon:'measure',label:'Ukur jarak & luas'},
              {id:'twin',icon:'globe',label:is3D?'Kembali ke tampilan 2D':'Tampilan 3D / digital twin'},
              {id:'share',icon:'share',label:'Share Webmap'}]} />
            {tool==='layers' ? (
              <ShellPanel heading="Layer" icon="layers" summary="ArcGIS Layer List" onClose={()=>setTool(null)} width={360}>
                <ArcgisLayerListPanel view={esri.current.view} mapReady={mapReady} onWorkspaceLayerChange={handleWorkspaceLayerChange}/>
              </ShellPanel>
            ) : tool==='adddata' ? (
              <ShellPanel heading="Tambah Data" icon="plus" summary="Katalog Data KKP" onClose={()=>setTool(null)}>
                <input value={catalogQuery} onChange={e=>setCatalogQuery(e.target.value)} onFocus={()=>setCatalogOpen(true)} placeholder="Cari layer KKP…" style={{width:'100%',boxSizing:'border-box',padding:8,marginBottom:8}} />
                {!catalogRows?<Button block size="s" onClick={()=>setCatalogOpen(true)}>Muat katalog</Button>:(catalogRows.filter(r=>!catalogQuery||r.title.toLowerCase().includes(catalogQuery.toLowerCase())||r.name.toLowerCase().includes(catalogQuery.toLowerCase())).slice(0,15).map(r=><button key={r.id} onClick={()=>addCatalogLayer(r)} className="calcite-list__row" style={{width:'100%',cursor:'pointer'}}><span style={{flex:1,textAlign:'left'}}>{r.title}</span><Icon name="plus" size={13}/></button>))}
              </ShellPanel>
            ) : tool==='basemap' ? (
              <ShellPanel heading="Basemap" icon="map" onClose={()=>setTool(null)}>
                {[['oceans','Oceans'],['satellite','Imagery'],['topo-vector','Topographic'],['streets-navigation-vector','Navigation'],['dark-gray-vector','Dark Gray']].map(([id,label])=><button key={id} onClick={()=>selectBasemap(id)} className="calcite-list__row" style={{width:'100%',cursor:'pointer',fontWeight:workspace?.basemap===id?800:400}}><span style={{flex:1,textAlign:'left'}}>{label}</span>{workspace?.basemap===id?<Icon name="check" size={13}/>:null}</button>)}
              </ShellPanel>
            ) : tool==='legend' ? (
              <ShellPanel heading="Legenda" icon="legend" onClose={()=>setTool(null)}>
                <Legend items={[{label:'Batas WPP-NRI',color:'var(--viz-3)',symbol:'line'},{label:'Titik hasil kueri agen',color:'var(--kkp-lime-500)',symbol:'square'}]} />
              </ShellPanel>
            ) : tool==='aoi' ? (
              <ShellPanel heading="Area of Interest" icon="crop" summary="Gambar area untuk menambah konteks" onClose={()=>setTool(null)}
                footer={<><button className="calcite-btn" disabled={!aoi||aoiDrawing} style={{flex:1}} onClick={addAoiToConversation}>Tambahkan ke Ask AI</button></>}>
                <p style={{fontSize:'var(--calcite-font-size--1)',color:'var(--calcite-color-text-2)'}}>Pilih poligon pada peta untuk membatasi konteks yang diambil asisten AI ke wilayah ini.</p>
                <CalciteNotice kind={aoi?'success':'info'} title={aoi?'Area of Interest siap':aoiDrawing?'Sedang menggambar':'Belum ada area dipilih'}>
                  {aoi?'Luas ±'+window.formatNumber(aoi.areaHa||0)+' ha · siap ditambahkan sebagai konteks Ask AI.':aoiDrawing?'Klik beberapa titik pada peta dan klik ganda untuk selesai.':'Klik tombol Gambar AOI untuk mulai.'}
                </CalciteNotice>
                <Button block size="s" variant="secondary" iconStart="crop" onClick={startAoiDrawing}>{aoi?'Gambar ulang AOI':'Gambar AOI'}</Button>
              </ShellPanel>
            ) : tool==='measure' ? (
              <ShellPanel heading="Measure" icon="measure" summary={measureMode==='area'?'Mengukur luas':'Mengukur jarak'} onClose={()=>setTool(null)}>
                <div style={{display:'grid',gridTemplateColumns:'1fr 1fr',gap:7,marginBottom:10}}>
                  <Button size="s" variant={measureMode==='distance'?'primary':'secondary'} iconStart="measure" onClick={()=>setMeasureMode('distance')}>Jarak</Button>
                  <Button size="s" variant={measureMode==='area'?'primary':'secondary'} iconStart="crop" onClick={()=>setMeasureMode('area')}>Luas</Button>
                </div>
                <div ref={measurementNodeRef} />
              </ShellPanel>
            ) : tool==='share' ? (
              <ShellPanel heading="Share Webmap" icon="share" summary="Bagikan konfigurasi peta aktif" onClose={()=>setTool(null)}
                footer={<button className="calcite-btn" style={{flex:1}} onClick={copyShareUrl}>Buat &amp; salin tautan</button>}>
                <p style={{fontSize:'var(--calcite-font-size--1)',color:'var(--calcite-color-text-2)'}}>Tautan memuat posisi peta, basemap, mode 2D/3D, dan layer workspace yang aktif.</p>
                {shareUrl?<input readOnly value={shareUrl} onFocus={e=>e.target.select()} style={{width:'100%',boxSizing:'border-box',padding:8,fontSize:10}}/>:null}
                {shareStatus?<CalciteNotice kind="success" title="Webmap siap dibagikan">{shareStatus}</CalciteNotice>:null}
              </ShellPanel>
            ) : null}
          </>
        ) : null}
        <div style={{position:'relative',flex:1,overflow:'hidden'}}>
        <>
          <div ref={containerRef} style={{position:'absolute',inset:0}} />
          {!mapReady && !mapError ? (
            <div style={{position:'absolute',inset:0,display:'flex',alignItems:'center',justifyContent:'center',gap:8,color:'rgba(255,255,255,.7)',fontSize:12,background:'var(--kkp-indigo-900)'}}>
              Memuat peta ArcGIS…
            </div>
          ) : null}
          {mapError ? (
            <div style={{position:'absolute',inset:0,display:'flex',alignItems:'center',justifyContent:'center',padding:24,textAlign:'center',color:'rgba(255,255,255,.75)',fontSize:12,background:'var(--kkp-indigo-900)'}}>
              Peta tidak dapat dimuat: {mapError}
            </div>
          ) : null}
          {!expanded && mapReady ? (
            <div style={{position:'absolute',left:10,bottom:10,color:'rgba(255,255,255,.6)',fontSize:11,fontFamily:'var(--kkp-font-sans)'}}>
              {focus ? focus.provinceName : 'Indonesia · sumber: portaldata.kkp.go.id'}
            </div>
          ) : null}
        </>
        </div>
      </div>
      {expanded && mapReady ? <MapScaleBar scale="1:15.000.000" lat="-2.500000" lon="118.000000" projection="WGS 84" /> : null}
    </div>
  );
}

function OmsAssistant({ go, request, role, mapWorkspace, setMapWorkspace }){
  const [draft,setDraft] = React.useState('');
  const [expanded,setExpanded] = React.useState(false);
  const [mapClosed,setMapClosed] = React.useState(false);
  const [mapPct,setMapPct] = React.useState(40);
  const [aoiContext,setAoiContext] = React.useState(null);
  const [mapTool,setMapTool] = React.useState(null);
  const [focus,setFocus] = React.useState(null);
  const [contextItems,setContextItems] = React.useState([]);
  const [messages,setMessages] = React.useState(()=>{try{return JSON.parse(localStorage.getItem('oms-assistant-history-v2')||'[]');}catch(_){return [];}});
  const [busy,setBusy] = React.useState(false);
  const [llmStatus,setLlmStatus] = React.useState({loading:true,configured:false,model:null});
  const [kbStatus,setKbStatus] = React.useState({loading:true,error:null,counts:null});
  const [agentPlan,setAgentPlan] = React.useState(null);
  const [saveTarget,setSaveTarget] = React.useState(null);
  const [savedResult,setSavedResult] = React.useState(null);
  const [workspaceOpen,setWorkspaceOpen] = React.useState(false);
  const [newChatConfirm,setNewChatConfirm] = React.useState(false);
  const [clearAfterSave,setClearAfterSave] = React.useState(false);
  const [mapSessionKey,setMapSessionKey] = React.useState(0);
  const bootedRef = React.useRef(false);
  const planRef = React.useRef(null);
  const lastEvikaAreaRef = React.useRef(null);

  React.useEffect(()=>{
    fetch('/api/assistant/status').then(r=>r.json()).then(s=>setLlmStatus({loading:false,...s}))
      .catch(()=>setLlmStatus({loading:false,configured:false,model:null}));
  },[]);
  React.useEffect(()=>{try{localStorage.setItem('oms-assistant-history-v2',JSON.stringify(messages.slice(-40)));}catch(_){}},[messages]);
  React.useEffect(()=>{
    window.loadOmsKnowledge().then(kb=>setKbStatus({loading:false,error:null,counts:{applications:kb.applications.length,tools:kb.tools.length,datasets:kb.datasets.length,playbooks:kb.playbooks.length}}))
      .catch(e=>setKbStatus({loading:false,error:e.message,counts:null}));
  },[]);

  const pushTurn = (trimmed, stages) => {
    const userMsg = { role:'user', text:trimmed, id:Date.now() };
    const aiId = Date.now()+1;
    setMessages(m => [...m, userMsg, { role:'ai', id:aiId, knowledgePlan:planRef.current, stages:stages.map(s=>({...s})), text:null, cites:[], loading:true }]);
    const advance = (i) => setMessages(m => m.map(x => x.id!==aiId ? x : {
      ...x, stages: x.stages.map((s,si)=> si===i ? {...s,pending:false} : s),
    }));
    const finish = (patch) => setMessages(m => m.map(x => x.id!==aiId ? x : { ...x, loading:false, cites:[], ...patch }));
    return { aiId, advance, finish };
  };

  const runWebMapMangrove = async (trimmed) => {
    const lower=trimmed.toLowerCase(),isGili=/gili\s*matra|gili/i.test(lower),isRare=/jarang|langka/i.test(lower),isStock=/stok|stock|karbon/i.test(lower),isTrade=/trade|perdagang/i.test(lower);
    const {advance,finish}=pushTurn(trimmed,[
      {t:'Membaca katalog WebMap OMS Lautra',d:'ArcGIS Feature Service · layer 0 Mangrove',pending:true},
      {t:'Menerapkan filter spasial dan atribut',d:isGili?'Gili Matra · Mangrove Jarang':'Layer mangrove',pending:true},
      {t:'Menambahkan hasil ke map viewer',d:'Feature layer ArcGIS ditampilkan pada peta',pending:true},
    ]);
    advance(0);
    const where=isRare?"kttj = 'MANGROVE JARANG'":'1=1';
    const query={where,returnGeometry:'false',outFields:'OBJECTID_1,kttj,wadmpr,soc,value,luas'};
    if(isGili) Object.assign(query,{geometry:'115.9,-8.5,116.2,-8.15',geometryType:'esriGeometryEnvelope',spatialRel:'esriSpatialRelIntersects',inSR:'4326'});
    try{
      const countData=await omsLautraQuery('mangrove',{...query,returnCountOnly:'true'});
      const count=Number(countData.count||0);advance(1);
      let statText='',valueText=count+' feature mangrove';
      if(isStock){const stats=await omsLautraQuery('mangrove',{...query,outStatistics:JSON.stringify([{statisticType:'sum',onStatisticField:'soc',outStatisticFieldName:'total_soc'}])});const total=Number(stats.features?.[0]?.attributes?.total_soc||0);statText=' Total **stock of carbon**: **'+window.formatNumber(total.toFixed(2))+'** (akumulasi field `soc`).';valueText=window.formatNumber(total.toFixed(2))+' stock of carbon';}
      if(isTrade){const stats=await omsLautraQuery('mangrove',{...query,outStatistics:JSON.stringify([{statisticType:'sum',onStatisticField:'value',outStatisticFieldName:'total_value'}])});const total=Number(stats.features?.[0]?.attributes?.total_value||0);statText=' Total **carbon trade indicator**: **'+window.formatNumber(total)+'** (akumulasi field `value`).';valueText=window.formatNumber(total)+' carbon trade indicator';}
      const filterLabel=isRare?(isGili?'Mangrove jarang · Gili Matra':'Mangrove jarang'):'Mangrove';
      setFocus({...(isGili?{lat:-8.35,lon:116.06,zoom:12,suppressMarker:true}:{extent:OMS_SPATIAL_EXTENT,zoom:5}),label:filterLabel,provinceName:isGili?'Gili Matra':'Indonesia',valueText,featureServiceUrl:OMS_LAUTRA_LAYERS.mangrove,definitionExpression:where,topicLabel:filterLabel,zoomToLayer:!isGili});setMapClosed(false);advance(2);
      finish({text:'Data **Mangrove** tersedia pada WebMap OMS Lautra: **'+count+' feature**.'+statText+'\n\nREST service: '+OMS_LAUTRA_LAYERS.mangrove+'\n\nFilter aktif: `'+where+'`'+(isGili?' dengan extent Gili Matra.':'')+'.',cites:[['Mangrove · OMS_Lautra_d · layer 0','ArcGIS Feature Service · '+OMS_LAUTRA_LAYERS.mangrove]]});
      setContextItems(c=>[{t:filterLabel,m:valueText,i:'leaf'},...c].slice(0,4));
    }catch(err){finish({text:'Gagal membaca layer Mangrove dari ArcGIS Feature Service: '+(err?.message||'kesalahan tidak diketahui')+'.'});}
    setBusy(false);
  };

  const runUnderwater3D = async (trimmed) => {
    const locationParts=trimmed.split(/\s+di\s+/i);
    const rawLocation=(locationParts.length>1?locationParts.pop():'').replace(/[?.!,]+$/,'').trim();
    if(!rawLocation || /^\[?lokasi\]?$/i.test(rawLocation)){
      const {finish}=pushTurn(trimmed,[{t:'Menentukan wilayah kajian',d:'Nama lokasi diperlukan untuk membuka tampilan 3D',pending:false}]);
      finish({text:'Sebutkan wilayah kajian, misalnya **“lihat objek bawah laut secara 3D di Wakatobi”**.'});
      setBusy(false);return;
    }
    const known=[
      {test:/gili\s*matra/i,name:'Gili Matra',center:[116.06,-8.35]},
      {test:/wakatobi/i,name:'Wakatobi',center:[123.6,-5.3]},
      {test:/raja\s*ampat/i,name:'Raja Ampat',center:[130.7,-0.5]},
      {test:/laut\s*sawu|sawu/i,name:'Laut Sawu',center:[122.3,-10.0]},
    ];
    const {advance,finish}=pushTurn(trimmed,[
      {t:'Mengenali wilayah kajian',d:rawLocation,pending:true},
      {t:'Menentukan koordinat',d:'Mencocokkan lokasi dengan referensi spasial',pending:true},
      {t:'Membuka Marine Ecosystem Digital Twin',d:'SceneView 3D · eksplorasi bawah laut',pending:true},
    ]);
    advance(0);
    let place=known.find(item=>item.test.test(rawLocation));
    if(!place){
      try{
        const url='https://geocode.arcgis.com/arcgis/rest/services/World/GeocodeServer/findAddressCandidates?f=json&maxLocations=1&category=Populated%20Place&singleLine='+encodeURIComponent(rawLocation+', Indonesia');
        const data=await fetch(url).then(r=>{if(!r.ok)throw new Error('HTTP '+r.status);return r.json();});
        const candidate=data.candidates?.[0];
        if(candidate)place={name:rawLocation,center:[candidate.location.x,candidate.location.y]};
      }catch(_){/* handled below */}
    }
    if(!place){finish({text:'Lokasi **'+rawLocation+'** belum dapat ditemukan. Gunakan nama wilayah yang lebih spesifik, misalnya Wakatobi, Raja Ampat, Gili Matra, atau Laut Sawu.'});setBusy(false);return;}
    advance(1);advance(2);
    finish({text:'Membuka **Marine Ecosystem Digital Twin** dalam mode 3D di **'+place.name+'**.'});
    setBusy(false);
    window.setTimeout(()=>go('digitaltwin',{area:place.name,center:place.center,ecosystem:'Terumbu Karang',mode:'3d',underwater:true,source:'assistant'}),300);
  };

  const runHistoricalGiliMatra = async (trimmed) => {
    const lower=trimmed.toLowerCase(),isZoom=/zoom\s*in|perbesar|lebih\s*dekat/i.test(lower),isList=/ada\s*data|data\s*saja|list\s*layer/i.test(lower),isCompare=/banding|2025\s*(dan|vs|ke)\s*2026/i.test(lower),is3d=/3d|bawah\s*laut|objek/i.test(lower);
    const {advance,finish}=pushTurn(trimmed,[
      {t:'Menavigasi konteks spasial',d:'Gili Matra · Nusa Tenggara Barat',pending:true},
      {t:'Membaca layer ekosistem',d:'Mangrove, lamun, terumbu karang, dan kawasan konservasi',pending:true},
      {t:'Memperbarui map viewer',d:is3d?'Menyiapkan handoff visualisasi 3D':'Menambahkan layer terumbu karang',pending:true},
    ]);
    advance(0);advance(1);
    const zoom=isZoom?13:11;
    setFocus({extent:GILI_MATRA_EXTENT,zoom,label:isZoom?'Gili Matra · zoom detail':'Gili Matra',provinceName:'Nusa Tenggara Barat',valueText:isZoom?'Tampilan detail area Gili Matra':'Layer ekosistem tersedia',featureServiceUrl:OMS_LAUTRA_LAYERS.coral,definitionExpression:'1=1',topicLabel:'Terumbu Karang · OMS Lautra'});setMapClosed(false);advance(2);
    const services='- Mangrove: '+OMS_LAUTRA_LAYERS.mangrove+'\n- Lamun: '+OMS_LAUTRA_LAYERS.seagrass+'\n- Terumbu Karang: '+OMS_LAUTRA_LAYERS.coral+'\n- Kawasan Konservasi: '+OMS_LAUTRA_LAYERS.conservation;
    if(isCompare){finish({text:'Perbandingan **Terumbu Karang Gili Matra / TSNB**:\n\n| Tahun | objectid | luas_ha |\n| --- | ---: | ---: |\n| 2025 | 1 | 3.000 |\n| 2026 | 1 | 4.000 |\n\n_2026 adalah data **SIMULASI prototype** sampai layer historis resmi diterbitkan. Layer terumbu karang aktif pada map viewer._',cites:[['Perbandingan Terumbu Karang 2025–2026','OMS Prototype · historical-gili-matra-demo · 2026 simulasi']]});}
    else if(isList){finish({text:'Data spasial yang tersedia di area Gili Matra:\n\n'+services+'\n\nLayer terumbu karang sudah ditampilkan di map viewer.',cites:[['OMS Lautra Feature Service','ArcGIS Online · OMS_Lautra_d']]});}
    else if(is3d){finish({text:'Membuka **Marine Ecosystem Digital Twin** dalam mode 3D untuk menampilkan kondisi dan objek bawah laut di Gili Matra.',cites:[['Terumbu Karang · OMS Lautra','ArcGIS Feature Service · layer 4']]});window.setTimeout(()=>go('digitaltwin',{area:'Gili Matra',ecosystem:'Terumbu Karang',mode:'3d',underwater:true,source:'assistant'}),350);}
    else if(isZoom){finish({text:'Map viewer sudah diperbesar ke area **Gili Matra**. Gunakan layer Terumbu Karang yang aktif untuk inspeksi lebih lanjut.',cites:[['Navigasi Gili Matra','OMS Lautra Feature Service']]});}
    else {finish({text:'Map viewer telah dinavigasikan ke **Gili Matra**. Layer terumbu karang aktif; Anda dapat meminta daftar layer, perbandingan 2025–2026, atau tampilan bawah laut 3D.',cites:[['Navigasi Gili Matra','OMS Lautra Feature Service']]});}
    setContextItems(c=>[{t:'Gili Matra',m:isCompare?'Terumbu 2025–2026 · simulasi':'Ekosistem historis',i:'map'},...c].slice(0,4));setBusy(false);
  };

  const runBlueCarbon = async (trimmed) => {
    const { advance, finish } = pushTurn(trimmed, [
      { t:'Menafsirkan intent', d:'Terdeteksi: permintaan estimasi blue carbon', pending:true },
      { t:'Menentukan area analisis', d:'Sample box di sekitar lokasi yang disebut', pending:true },
      { t:'Mengirim tugas ke SIDAKO ocean-accounts-api', d:'POST /area-analysis/analyze (async)', pending:true },
      { t:'Menunggu hasil analisis', d:'Polling job hingga selesai', pending:true },
    ]);
    const province = window.detectProvince(trimmed);
    const usesAoi=!!aoiContext?.geometry?.rings?.length;
    if (!province && !usesAoi) {
      advance(0);
      finish({ text:'Sebutkan lokasi atau gambar Area of Interest pada peta untuk dianalisis.' });
      setBusy(false);
      return;
    }
    advance(0);
    const box = usesAoi ? {
      ring:aoiContext.geometry.rings[0],
      geojson:{type:'Polygon',coordinates:aoiContext.geometry.rings},
      extent:{xmin:aoiContext.bbox[0],ymin:aoiContext.bbox[1],xmax:aoiContext.bbox[2],ymax:aoiContext.bbox[3]},
    } : window.sampleBoxAround(province.lat, province.lon, 0.4);
    const areaLabel=usesAoi?'Area of Interest pengguna':'area contoh ±0,4°×0,4° di sekitar '+titleCase(province.name);
    advance(1);
    try {
      const submitted = await window.submitBlueCarbonAnalysis(box.geojson);
      advance(2);
      const job = await window.pollBlueCarbonJob(submitted.job_id);
      advance(3);
      const payload = job.result || job.partial_result;
      if (job.status === 'failed' || !payload) {
        finish({ text:'SIDAKO gagal menyelesaikan analisis blue carbon: ' + (job.error || 'tidak diketahui') + '.' });
        setBusy(false);
        return;
      }
      const year = payload.primary_year || Object.keys(payload.baselines||{})[0];
      const baseline = (payload.baselines||{})[year] || {};
      const mangrove = baseline.mangrove || {};
      const seagrass = baseline.seagrass || {};
      const totalCarbon = (mangrove.total_carbon_tc||0) + (seagrass.total_carbon_tc||0);
      const answerText =
        'Estimasi blue carbon untuk **' + areaLabel + '** (tahun ' + year + '):\n\n' +
        '- Mangrove: **' + window.formatNumber(mangrove.total_area_ha||0) + ' ha**, stok karbon **' + window.formatNumber(Math.round(mangrove.total_carbon_tc||0)) + ' tC** (biomassa ' + window.formatNumber(Math.round(mangrove.biomass_carbon_tc||0)) + ' tC + tanah ' + window.formatNumber(Math.round(mangrove.soil_carbon_tc||0)) + ' tC)\n' +
        '- Lamun (seagrass): **' + window.formatNumber(seagrass.total_area_ha||0) + ' ha**, stok karbon **' + window.formatNumber(Math.round(seagrass.total_carbon_tc||0)) + ' tC**\n' +
        '- Total stok karbon biru terdeteksi: **' + window.formatNumber(Math.round(totalCarbon)) + ' tC**\n\n' +
        (usesAoi?'_Analisis menggunakan poligon AOI yang digambar pada peta.':'_Catatan: area ini kotak contoh, bukan batas administratif/ekosistem asli._')+' Terumbu karang dihitung terpisah (bukan ekosistem karbon biru).';
      finish({
        text: answerText,
        action:{screen:'bluecarbon',label:'Lempar ke Blue Carbon Intelligence',request:{area:usesAoi?'AOI pengguna':titleCase(province.name),year,geometry:usesAoi?aoiContext.geometry:null}},
        cites: [['Blue Carbon Sequestration — ' + areaLabel, 'SIDAKO Ocean Accounting Platform · ocean-accounts-api']],
      });
      setContextItems(c => [{ t:'Blue carbon — '+areaLabel, m:window.formatNumber(Math.round(totalCarbon))+' tC', i:'graph-time-series' }, ...c].slice(0,4));
      setFocus({
        polygon: box.ring, extent: box.extent, zoom:9,
        label:usesAoi?'AOI blue carbon':'Area contoh blue carbon', provinceName:usesAoi?'AOI pengguna':titleCase(province.name)+' (area contoh)',
        valueText: window.formatNumber(Math.round(totalCarbon))+' tC total',
      });
      setMapClosed(false);
    } catch (err) {
      finish({ text:'Gagal menjalankan analisis blue carbon SIDAKO: ' + (err && err.message || 'kesalahan tidak diketahui') + '.' });
    }
    setBusy(false);
  };

  const runLautSawuConservation = async (trimmed) => {
    const { advance, finish } = pushTurn(trimmed, [
      { t:'Menafsirkan intent', d:'Terdeteksi: permintaan geometri kawasan konservasi Laut Sawu', pending:true },
      { t:'Mengambil geometri resmi', d:'SIDAKO GeoServer · konservasi:data_kk_2025 · WFS', pending:true },
      { t:'Menambahkan layer ke map viewer', d:'Memfokuskan extent feature Laut Sawu', pending:true },
    ]);
    advance(0);
    try {
      const url='/sidako-gis/konservasi/ows?service=WFS&version=2.0.0&request=GetFeature&typeNames=konservasi:data_kk_2025&outputFormat=application%2Fjson&srsName=EPSG%3A4326&propertyName=objectid_1,nama_kk,luas_kk_ha,id_mpa';
      const geo=await fetch(url).then(r=>{if(!r.ok)throw new Error('WFS kawasan konservasi tidak tersedia');return r.json();});
      const feature=(geo.features||[]).find(f=>/laut\s+sawu/i.test(String(f.properties?.nama_kk||'')));
      advance(1);
      if(!feature) throw new Error('Feature Laut Sawu tidak ditemukan pada data_kk_2025');
      const detail=await fetch('/sidako-gis/konservasi/ows?service=WFS&version=2.0.0&request=GetFeature&typeNames=konservasi:data_kk_2025&outputFormat=application%2Fjson&srsName=EPSG%3A4326&count=1&CQL_FILTER='+encodeURIComponent('objectid_1='+feature.properties.objectid_1)).then(r=>r.ok?r.json():null);
      const geometry=detail?.features?.[0]?.geometry||feature.geometry;
      const points=[];(function walk(v){if(Array.isArray(v)){if(typeof v[0]==='number'&&typeof v[1]==='number')points.push(v);else v.forEach(walk);}})(geometry?.coordinates);
      const xs=points.map(p=>p[0]),ys=points.map(p=>p[1]);
      if(!xs.length) throw new Error('Geometri Laut Sawu kosong');
      const extent={xmin:Math.min(...xs),ymin:Math.min(...ys),xmax:Math.max(...xs),ymax:Math.max(...ys),spatialReference:{wkid:4326}};
      const area=Number(feature.properties?.luas_kk_ha)||3355352.82;
      setFocus({extent,zoom:7,label:'Kawasan Konservasi Laut Sawu',provinceName:'Nusa Tenggara Timur',valueText:window.formatNumber(area)+' ha',wmsLayer:'konservasi:data_kk_2025',featureId:feature.properties?.objectid_1});
      setMapClosed(false);advance(2);
      finish({text:'Geometri resmi **Kawasan Konservasi Perairan Nasional Laut Sawu dan Sekitarnya** telah ditambahkan ke map viewer. Luas feature: **'+window.formatNumber(area)+' ha**. Layer: **konservasi:data_kk_2025** dari SIDAKO GeoServer.',cites:[['Geometri kawasan konservasi Laut Sawu','SIDAKO GeoServer · WFS · data_kk_2025']]});
    } catch(err) {
      finish({text:'Gagal mengambil geometri Laut Sawu dari SIDAKO: '+(err?.message||'kesalahan tidak diketahui')+'.'});
    }
    setBusy(false);
  };

  const runEvika = async (trimmed) => {
    const asksPrevious=/tahun\s+lalu|sebelumnya/i.test(trimmed),asksTrend=/5\s*tahun|lima\s*tahun|ke\s*belakang|tren/i.test(trimmed);
    const requestedYear = Number((trimmed.match(/\b(202[1-5])\b/)||[])[1]) || (asksPrevious?2024:2025);
    const { advance, finish } = pushTurn(trimmed, [
      { t:'Menafsirkan intent', d:'Terdeteksi: permintaan status EVIKA', pending:true },
      { t:'Membaca data EVIKA SIDAKO', d:'Endpoint area_value · tahun penilaian '+requestedYear, pending:true },
      { t:'Mencocokkan nama kawasan', d:'Mencari di 127 kawasan konservasi', pending:true },
    ]);
    advance(0);
    try {
      const rows = await omsEvikaRows(requestedYear);
      advance(1);
      const fragment = trimmed
        .replace(/\b(evika|status|pengelolaan|efektivitas|analisis|bagaimana|berapa|apa|kawasan|konservasi|di|tahun|202[1-5])\b|\?/gi, ' ')
        .replace(/\s+/g, ' ')
        .trim();
      let match = fragment ? window.findEvikaArea(rows, fragment) : null;
      if(!match && lastEvikaAreaRef.current) match=rows.find(r=>r.name===lastEvikaAreaRef.current)||window.findEvikaArea(rows,lastEvikaAreaRef.current);
      advance(2);
      const snapshotNote = rows[0]?.simulation ? '\n\n_Data EVIKA '+requestedYear+' adalah **SIMULASI prototype** berbasis baseline SIDAKO 2025 karena endpoint historis belum tersedia._' : '\n\n_Data EVIKA '+requestedYear+' diambil dari endpoint resmi SIDAKO melalui gateway OMS._';
      if(asksTrend && match){
        const years=[2021,2022,2023,2024,2025],series=await Promise.all(years.map(async year=>{const items=await omsEvikaRows(year);return {year,row:items.find(r=>r.name===match.name)||window.findEvikaArea(items,match.name)};}));
        lastEvikaAreaRef.current=match.name;
        const table=series.map(item=>'| '+item.year+' | '+(item.row?item.row.score.toFixed(2)+'%':'—')+' |').join('\n'),hasSimulation=series.some(item=>item.row?.simulation);
        finish({text:'Tren nilai akhir EVIKA **'+match.name+'**:\n\n| Tahun | Nilai akhir |\n| --- | ---: |\n'+table+'\n\n'+(hasSimulation?'_Tahun yang endpoint-nya belum tersedia memakai **SIMULASI prototype** berbasis baseline SIDAKO 2025._':'_Seluruh tahun pada tabel berhasil diperoleh dari endpoint SIDAKO._')+snapshotNote,action:{screen:'evika',label:'Buka Dashboard EVIKA',request:{year:2025,area:match.name}},cites:[['Tren EVIKA 2021–2025 — '+match.name,hasSimulation?'SIDAKO baseline 2025 · sebagian histori simulasi prototype':'SIDAKO · area_value · 2021–2025']]});
        setContextItems(c=>[{t:'Tren EVIKA — '+match.name,m:'2021–2025',i:'graph-time-series'},...c].slice(0,4));setBusy(false);return;
      }
      if (match) {
        lastEvikaAreaRef.current=match.name;
        const answerText =
          'Status EVIKA **' + match.name + '** ('+requestedYear+'): nilai akhir **' + match.score.toFixed(2) + '%**, ' +
          'status pengelolaan **' + window.EVIKA_STATUS_LABEL[match.status] + '**, luas **' + window.formatNumber(match.luasHa) + ' ha**.' + snapshotNote;
        finish({ text:answerText, action:{screen:'evika',label:'Lempar ke Dashboard EVIKA',request:{year:requestedYear,area:match.name}}, cites:[['Status EVIKA — '+match.name, 'SIDAKO · Direktorat Konservasi Ekosistem KKP · '+requestedYear]] });
        setContextItems(c => [{ t:'EVIKA — '+match.name, m:match.score.toFixed(2)+'% · '+window.EVIKA_STATUS_LABEL[match.status], i:'graph-bar' }, ...c].slice(0,4));
      } else {
        const top3 = [...rows].sort((a,b)=>b.score-a.score).slice(0,/tertinggi|paling tinggi/i.test(trimmed)?1:3);
        if(top3[0]&&/tertinggi|paling tinggi/i.test(trimmed)) lastEvikaAreaRef.current=top3[0].name;
        const list = top3.map((r,i)=>(i+1)+'. '+r.name+' — **'+r.score.toFixed(2)+'%** ('+window.EVIKA_STATUS_LABEL[r.status]+')').join('\n');
        finish({
          text:'Saya tidak menemukan nama kawasan spesifik di pertanyaan Anda. Berikut '+top3.length+' kawasan dengan nilai EVIKA tertinggi tahun '+requestedYear+':\n\n'+list+snapshotNote,
          action:{screen:'evika',label:'Lempar ke Dashboard EVIKA',request:{year:requestedYear}},
          cites:[['Peringkat EVIKA '+requestedYear, 'SIDAKO · Direktorat Konservasi Ekosistem KKP']],
        });
      }
    } catch (err) {
      finish({ text:'Gagal membaca data EVIKA: ' + (err && err.message || 'kesalahan tidak diketahui') + '.' });
    }
    setBusy(false);
  };

  const runProvinceStat = async (trimmed) => {
    const { advance, finish } = pushTurn(trimmed, [
      { t:'Menafsirkan intent', d:'Mengekstrak entitas wilayah dan indikator dari pertanyaan', pending:true },
      { t:'Memeriksa peran & akses', d:'Peran Analis Kelautan · Portal Satu Data KKP (terbuka)', pending:true },
      { t:'Mengambil data dari portaldata.kkp.go.id', d:'GET /api/statistik/provinsi', pending:true },
    ]);
    const query = window.parseQuery(trimmed);
    advance(0); advance(1);
    try {
      const parsed = buildAnswer(query);
      if (!parsed.ok) {
        advance(2);
        finish({ text:parsed.text });
        setBusy(false);
        return;
      }
      const stats = await window.fetchProvinceStats();
      advance(2);
      const row = stats.find(d => d.provinsi === parsed.province.name);
      const topic = parsed.topic;
      const bucket = row && row[topic.field];
      const rawValue = bucket ? bucket[topic.sub] : null;
      if (!row || rawValue == null) {
        finish({ text:'Data "'+topic.label+'" untuk '+parsed.province.name+' tidak tersedia di Portal Satu Data KKP saat ini.' });
        setBusy(false);
        return;
      }
      const valueText = window.formatNumber(rawValue) + ' ' + topic.unit;
      const trend = bucket.kenaikan ? (bucket.kenaikan==='naik' ? 'naik' : 'turun') + ' dibanding periode sebelumnya ('+window.formatNumber(bucket.year_prev)+' '+topic.unit+')' : null;
      const answerText = topic.label+' di **'+titleCase(parsed.province.name)+'**: **'+valueText+'**' + (trend ? ', '+trend+'.' : '.');
      finish({ text:answerText, action:{screen:'search',label:'Buka Katalog Data KKP',request:{query:trimmed}}, cites:[[topic.label+' — '+titleCase(parsed.province.name), 'Portal Satu Data KKP · statistik/provinsi']] });
      setContextItems(c => [{ t:topic.label+' — '+titleCase(parsed.province.name), m:valueText, i:'data' }, ...c].slice(0,4));
      setFocus({
        lat:parsed.province.lat, lon:parsed.province.lon, zoom:7,
        label:topic.label, provinceName:titleCase(parsed.province.name),
        valueText, wmsLayer:topic.wmsLayer, topicLabel:topic.label,
      });
      setMapClosed(false);
    } catch (err) {
      finish({ text:'Gagal mengambil data dari Portal Satu Data KKP: '+(err && err.message || 'kesalahan tidak diketahui')+'.' });
    }
    setBusy(false);
  };

  const runLlm = async (trimmed, plan) => {
    const { advance, finish } = pushTurn(trimmed, [
      { t:'Memahami pertanyaan', d:'Mengidentifikasi konteks kelautan dan kebutuhan jawaban', pending:true },
      { t:'Menyusun konteks percakapan', d:'Mengirim maksimum 10 pesan terakhir tanpa kunci API di browser', pending:true },
      { t:'Meminta respons LLM', d:'Provider LLM melalui gateway server OMS', pending:true },
    ]);
    advance(0);
    const history = [...messages, {role:'user',text:trimmed}].slice(-10).map(m=>({
      role:m.role==='ai'?'assistant':'user', content:m.text || '',
    })).filter(m=>m.content);
    advance(1);
    try {
      const knowledgeContext = plan ? {playbook:plan.playbook,hits:plan.hits.map(h=>({type:h.type,item:h.item}))} : null;
      const spatialContext = aoiContext ? {label:aoiContext.label,type:aoiContext.type,center:aoiContext.center,bbox:aoiContext.bbox,areaHa:aoiContext.areaHa,geometry:aoiContext.geometry} : null;
      const res = await fetch('/api/assistant', { method:'POST', headers:{'content-type':'application/json'}, body:JSON.stringify({messages:history,knowledgeContext,spatialContext}) });
      const payload = await res.json().catch(()=>({}));
      advance(2);
      if (!res.ok) throw new Error(payload.error || 'Layanan LLM tidak tersedia');
      finish({
        text:payload.text,
        model:payload.model,
        action:plan?.playbook?.handoff?.app ? {screen:plan.playbook.handoff.app,label:'Buka '+((plan.hits.find(h=>h.type==='application'&&h.item.id===plan.playbook.handoff.app)||{}).item?.name||'aplikasi terkait'),request:{seedQuestion:trimmed,query:trimmed}} : null,
        cites:[['Respons generatif · '+(payload.model||'model LLM'), llmProviderLabel(payload.provider)+' API · tanpa sumber data eksternal otomatis']],
      });
    } catch (err) {
      const p=plan?.playbook,appHit=p&&plan.hits.find(h=>h.type==='application'&&h.item.id===p.handoff?.app);
      finish({
        text:p ? 'Knowledge base OMS memilih playbook **'+p.name+'**. Alur yang disiapkan: '+p.steps.map(s=>s.action+' melalui '+s.target).join(' → ')+'.\n\n_Layanan generatif belum aktif, tetapi orkestrasi tool dan handoff aplikasi tetap dapat disimulasikan dari registry lokal._' : 'Asisten generatif belum dapat digunakan: '+(err && err.message || 'kesalahan tidak diketahui')+'. Statistik KKP dan analisis SIDAKO yang terhubung langsung tetap dapat digunakan.',
        action:p?.handoff?.app?{screen:p.handoff.app,label:'Buka '+(appHit?.item?.name||'aplikasi terkait'),request:{seedQuestion:trimmed,query:trimmed}}:null,
      });
    }
    setBusy(false);
  };

  const runQuery = async (text) => {
    const trimmed = (text||'').trim();
    if (!trimmed || busy) return;
    setDraft('');
    setBusy(true);
    // Spatial-first: setiap percakapan memiliki konteks peta, meskipun belum
    // ada geometri spesifik yang dikembalikan oleh sumber data.
    setMapClosed(false);
    if (!focus) setFocus({extent:OMS_SPATIAL_EXTENT, zoom:4, label:'Konteks spasial OMS', provinceName:'Wilayah kerja OMS', valueText:'Extent Indonesia'});
    if (/laut\s+sawu|sawu/i.test(trimmed)) setFocus(LAUT_SAWU_POINT);
    let plan=null;
    try{plan=await window.planOmsAgent(trimmed);planRef.current=plan;setAgentPlan(plan);}catch(_){planRef.current=null;setAgentPlan(null);}
    const requiredTool=plan?.playbook?.steps?.find(s=>['call-tool','subscribe','poll'].includes(s.action))?.target;
    if(requiredTool&&!window.canRoleUseTool(role,requiredTool)){
      const {advance,finish}=pushTurn(trimmed,[{t:'Memeriksa peran & akses',d:role.name+' · '+role.unit,pending:true},{t:'Mengevaluasi izin tool',d:requiredTool,pending:true}]);advance(0);advance(1);finish({text:'Permintaan dikenali sebagai **'+plan.playbook.name+'**, tetapi peran **'+role.name+'** tidak memiliki izin menggunakan tool **'+requiredTool+'**. Agent menghentikan orkestrasi sebelum data dipanggil. Gunakan role yang sesuai atau ajukan akses kepada Administrator OMS.'});setBusy(false);return;
    }
    if (/laut\s+sawu|sawu/i.test(trimmed) && /konservasi|geometri|polygon|peta|layer/i.test(trimmed)) return runLautSawuConservation(trimmed);
    if (/mangrove|stok\s*(of\s*)?karbon|stock\s*(of\s*)?carbon|carbon\s*trade|karbon\s*trade|OMS_Lautra_d|FeatureServer\/0/i.test(trimmed)) return runWebMapMangrove(trimmed);
    if (/(?:3d.*(?:bawah\s*laut|objek)|(?:bawah\s*laut|objek).*3d)/i.test(trimmed)) return runUnderwater3D(trimmed);
    if (/gili\s*matra|terumbu\s*karang|bawah\s*laut|zoom\s*in/i.test(trimmed)) return runHistoricalGiliMatra(trimmed);
    if (window.detectBlueCarbon(trimmed)) return runBlueCarbon(trimmed);
    if (window.detectEvika(trimmed) || (lastEvikaAreaRef.current && /tahun\s+lalu|sebelumnya|5\s*tahun|lima\s*tahun|ke\s*belakang|peringkat/i.test(trimmed))) return runEvika(trimmed);
    if (window.detectProvince(trimmed) && window.detectTopic(trimmed)) return runProvinceStat(trimmed);
    return runLlm(trimmed,plan);
  };

  const clearChatAndMap = () => {
    setMessages([]);
    setDraft('');
    setContextItems([]);
    setAoiContext(null);
    setAgentPlan(null);
    setFocus(null);
    setMapTool(null);
    setMapClosed(false);
    setMapWorkspace({basemap:'oceans',layers:[],view:{center:[121.9,-0.4],zoom:4,mode:'2d'}});
    setMapSessionKey(key=>key+1);
    setNewChatConfirm(false);
  };

  const startNewChat = () => {
    if (busy) return;
    const hasMapContent=(mapWorkspace?.layers||[]).length>0;
    if(messages.length||contextItems.length||focus||hasMapContent){setNewChatConfirm(true);return;}
    clearChatAndMap();
  };

  const saveBeforeNewChat = () => {
    const latest=messages.filter(m=>m.role==='ai'&&!m.loading).slice(-1)[0];
    if(!latest)return;
    const transcript=messages.filter(m=>m.text).map(m=>(m.role==='user'?'Pengguna: ':'AI: ')+m.text).join('\n\n');
    setSaveTarget({...latest,text:transcript||latest.text,workspaceSnapshot:mapWorkspace});
    setClearAfterSave(true);
    setNewChatConfirm(false);
  };

  React.useEffect(()=>{
    if (bootedRef.current) return;
    bootedRef.current = true;
    // Jangan menjalankan contoh pertanyaan secara otomatis saat halaman dibuka.
    // Query hanya dijalankan jika ada handoff eksplisit dari aplikasi lain.
    if (request?.seedQuestion) runQuery(request.seedQuestion);
  }, []);

  return (
    <div style={{'--oms-left-pane':'300px',display:'grid',gridTemplateColumns:'var(--oms-left-pane) 1fr',height:'calc(100vh - var(--oms-content-offset, 60px))',minHeight:0,background:'radial-gradient(circle at 10% 0%,#7dd3fc3d,transparent 45%),radial-gradient(circle at 90% 100%,#a5f3a53d,transparent 45%),var(--surface-page)'}}>
      <aside style={{minHeight:0,borderRight:'1px solid rgba(255,255,255,.4)',background:'rgba(255,255,255,.45)',backdropFilter:'blur(20px) saturate(180%)',WebkitBackdropFilter:'blur(20px) saturate(180%)',padding:'var(--space-5)',overflow:'auto'}}>
        <div style={{display:'grid',gridTemplateColumns:'1fr 1fr',gap:4,padding:4,borderRadius:'var(--radius-md)',background:'rgba(224,242,254,.5)',border:'1px solid rgba(56,132,255,.2)',marginBottom:'var(--space-5)'}}>
          <button onClick={()=>setWorkspaceOpen(false)} style={{border:0,cursor:'pointer',borderRadius:'var(--radius-sm)',padding:'8px 6px',font:'inherit',fontSize:10.5,fontWeight:workspaceOpen?600:800,color:workspaceOpen?'var(--text-muted)':'var(--kkp-indigo-800)',background:workspaceOpen?'transparent':'rgba(255,255,255,.82)'}}>Riwayat percakapan</button>
          <button onClick={()=>setWorkspaceOpen(true)} style={{border:0,cursor:'pointer',borderRadius:'var(--radius-sm)',padding:'8px 6px',font:'inherit',fontSize:10.5,fontWeight:workspaceOpen?800:600,color:workspaceOpen?'var(--kkp-indigo-800)':'var(--text-muted)',background:workspaceOpen?'rgba(255,255,255,.82)':'transparent',display:'flex',alignItems:'center',justifyContent:'center',gap:5}}><Icon name="portal" size={13}/>Workspace saya</button>
        </div>
        {workspaceOpen ? <EnterpriseWorkspacePanel role={role} embedded onClose={()=>setWorkspaceOpen(false)} message={messages.filter(m=>m.role==='ai'&&!m.loading).slice(-1)[0]} focus={focus} onSaveWorkspace={()=>{const latest=messages.filter(m=>m.role==='ai'&&!m.loading).slice(-1)[0];if(latest)setSaveTarget(latest);}} /> : null}
        {!workspaceOpen ? <>
        <div className="kkp-eyebrow">Riwayat percakapan</div>
        <div style={{display:'flex',flexDirection:'column',gap:'var(--space-2)',marginTop:'var(--space-3)'}}>
          {[SEED_QUESTION,'Jumlah pembudidaya di Maluku','Hitung blue carbon di Jawa Tengah','Status EVIKA Raja Ampat'].map((t,i)=>(
            <button key={t} onClick={()=>runQuery(t)} style={{padding:'var(--space-3)',textAlign:'left',font:'inherit',cursor:'pointer',borderRadius:'var(--radius-md)',backdropFilter:'blur(10px)',WebkitBackdropFilter:'blur(10px)',
              fontSize:'var(--type-body-sm-size)',fontWeight:600,color:i===0?'var(--kkp-azure-800)':'var(--text-body)',
              border:i===0?'1px solid rgba(56,132,255,.4)':'1px solid rgba(255,255,255,.5)',background:i===0?'rgba(224,242,254,.55)':'rgba(255,255,255,.4)'}}>{t}</button>
          ))}
        </div>
        <div className="kkp-eyebrow" style={{marginTop:'var(--space-6)'}}>Cakupan data</div>
        <div style={{display:'flex',flexWrap:'wrap',gap:'var(--space-2)',marginTop:'var(--space-3)'}}>
          {['Portal Satu Data KKP','SIDAKO Blue Carbon','SIDAKO EVIKA'].map(t=><Tag key={t}>{t}</Tag>)}
        </div>
        <div style={{marginTop:'var(--space-3)',padding:'10px 11px',borderRadius:'var(--radius-sm)',border:'1px solid '+role.color+'55',background:role.color+'16',display:'flex',gap:9,alignItems:'center'}}><span style={{width:29,height:29,borderRadius:'50%',display:'grid',placeItems:'center',background:role.color,color:'#101540',fontSize:8,fontWeight:800}}>{role.initials}</span><span><b style={{display:'block',fontSize:10.5,color:'var(--text-heading)'}}>{role.name}</b><small style={{fontSize:8.5,color:'var(--text-muted)'}}>{role.readOnly?'Mode baca · ':''}{role.tools.includes('*')?'Seluruh tools':role.tools.length+' tools diizinkan'} · {role.canApprove?'dapat menyetujui':'tanpa hak persetujuan'}</small></span></div>
        <div style={{marginTop:'var(--space-4)',padding:12,borderRadius:'var(--radius-md)',border:'1px solid rgba(56,132,255,.25)',background:'rgba(224,242,254,.48)'}}>
          <div style={{display:'flex',alignItems:'center',gap:8}}><Icon name="organization" size={15}/><div><b style={{display:'block',fontSize:11,color:'var(--text-heading)'}}>Agentic knowledge base</b><small style={{fontSize:9.5,color:'var(--text-muted)'}}>{kbStatus.loading?'Memuat registry…':kbStatus.error?'Registry tidak tersedia':kbStatus.counts.applications+' aplikasi · '+kbStatus.counts.tools+' tools · '+kbStatus.counts.datasets+' dataset'}</small></div><span style={{marginLeft:'auto',width:7,height:7,borderRadius:'50%',background:kbStatus.error?'#d9911d':kbStatus.loading?'#8a94a6':'#24995a'}}/></div>
          {agentPlan ? <div style={{marginTop:10,paddingTop:9,borderTop:'1px solid rgba(56,132,255,.18)'}}>
            <small style={{display:'block',fontSize:8.5,textTransform:'uppercase',letterSpacing:'.08em',color:'var(--text-muted)'}}>Playbook terpilih</small>
            <b style={{display:'block',fontSize:10.5,color:'var(--kkp-indigo-800)',marginTop:3}}>{agentPlan.playbook?.name||'Retrieval umum'}</b>
            <div style={{display:'flex',flexWrap:'wrap',gap:4,marginTop:7}}>{agentPlan.hits.slice(0,4).map(h=><span key={h.type+window.kbNodeLabel(h)} style={{padding:'3px 6px',borderRadius:10,background:'rgba(255,255,255,.62)',fontSize:8.5,color:'var(--text-body)'}}>{h.type} · {window.kbNodeLabel(h)}</span>)}</div>
          </div>:null}
        </div>
        <div style={{marginTop:'var(--space-3)',padding:'10px 11px',borderRadius:'var(--radius-sm)',border:'1px solid '+(llmStatus.configured?'rgba(36,153,90,.28)':'rgba(224,147,30,.3)'),background:llmStatus.configured?'rgba(221,248,232,.62)':'rgba(255,244,214,.62)',display:'flex',alignItems:'center',gap:8}}>
          <span style={{width:8,height:8,borderRadius:'50%',background:llmStatus.loading?'#8a94a6':llmStatus.configured?'#24995a':'#d9911d'}} />
          <span style={{fontSize:11,color:'var(--text-body)'}}>
            <strong style={{display:'block'}}>{llmStatus.loading?'Memeriksa LLM…':llmStatus.configured?'LLM tersambung':'LLM belum dikonfigurasi'}</strong>
            {!llmStatus.loading ? <span style={{color:'var(--text-muted)'}}>{llmStatus.configured?(llmProviderLabel(llmStatus.provider)+' · '+llmStatus.model):'Set GROQ_API_KEY pada server'}</span> : null}
          </span>
        </div>
        <div style={{marginTop:'var(--space-6)',display:'flex',flexDirection:'column',gap:'var(--space-4)'}}>
        <div>
          <div className="kkp-eyebrow">Konteks yang digunakan</div>
          <div style={{display:'flex',flexDirection:'column',gap:'var(--space-2)',marginTop:'var(--space-3)'}}>
            {contextItems.length===0 && !aoiContext ? (
              <p style={{fontSize:'var(--type-body-sm-size)',color:'var(--text-muted)',margin:0}}>Belum ada — tanyakan sesuatu untuk mulai mengambil data.</p>
            ) : contextItems.map(({t,m,i})=>(
              <div key={t} style={{display:'flex',gap:'var(--space-3)',alignItems:'flex-start',padding:'var(--space-3)',border:'1px solid rgba(255,255,255,.5)',background:'rgba(255,255,255,.35)',backdropFilter:'blur(10px)',WebkitBackdropFilter:'blur(10px)',borderRadius:'var(--radius-sm)'}}>
                <Icon name={i} size={16} />
                <span style={{fontSize:'var(--type-body-sm-size)'}}>
                  <span style={{fontWeight:600,color:'var(--text-heading)',display:'block'}}>{t}</span>
                  <span style={{color:'var(--text-muted)',fontSize:'var(--type-caption-size)'}}>{m}</span>
                </span>
              </div>
            ))}
            {aoiContext ? (
              <div style={{display:'flex',gap:'var(--space-3)',alignItems:'flex-start',padding:'var(--space-3)',border:'1px solid rgba(56,132,255,.4)',background:'rgba(224,242,254,.5)',backdropFilter:'blur(10px)',WebkitBackdropFilter:'blur(10px)',borderRadius:'var(--radius-sm)'}}>
                <Icon name="crop" size={16} />
                <span style={{fontSize:'var(--type-body-sm-size)'}}>
                  <span style={{fontWeight:600,color:'var(--text-heading)',display:'block'}}>Area of Interest (peta)</span>
                  <span style={{color:'var(--text-muted)',fontSize:'var(--type-caption-size)'}}>{aoiContext.areaHa!=null?'±'+window.formatNumber(aoiContext.areaHa)+' ha · ':''}aktif untuk pertanyaan berikutnya</span>
                </span>
              </div>
            ) : null}
          </div>
        </div>
        <InsightCard eyebrow="Sumber data" title="Portal Satu Data KKP" source="portaldata.kkp.go.id">
          Statistik dan lapisan peta pada layar ini diambil langsung dari API publik dan GeoServer resmi KKP, bukan data simulasi.
        </InsightCard>
        </div>
        </> : null}
      </aside>

      <SplitPane rightPct={mapPct} onRightPctChange={setMapPct} collapsed={mapClosed}>
        <main style={{display:'flex',flexDirection:'column',minWidth:0,minHeight:0,borderRight:mapClosed?'none':'1px solid rgba(255,255,255,.4)',position:'relative'}}>
          <div style={{padding:'var(--space-4) var(--space-6)',borderBottom:'1px solid rgba(255,255,255,.55)',background:'rgba(255,255,255,.6)',display:'flex',alignItems:'center'}}>
            <button onClick={startNewChat} disabled={busy} style={{display:'inline-flex',alignItems:'center',gap:9,padding:'9px 18px',border:'1px solid var(--kkp-azure-600)',borderRadius:8,background:'#fff',color:'var(--kkp-azure-700)',font:'inherit',fontSize:13,fontWeight:600,cursor:busy?'wait':'pointer',boxShadow:'0 2px 6px rgba(8,125,193,.08)'}}>
              <Icon name="pencil" size={17} /> New Chat
            </button>
            <span style={{marginLeft:'auto',fontSize:11,color:'var(--text-muted)'}}>Asisten AI OMS</span>
          </div>
          <div style={{flex:1,overflow:'auto',padding:'var(--space-6)',display:'flex',flexDirection:'column',gap:'var(--space-5)'}}>
            {messages.length===0 ? (
              <div style={{flex:1,display:'flex',flexDirection:'column',alignItems:'center',justifyContent:'center',padding:'var(--space-8)',textAlign:'center',minHeight:420}}>
                <img src="/assets/oms-ai-bot.svg" alt="AI Assistant" style={{width:76,height:72,objectFit:'contain',marginBottom:'var(--space-5)'}} />
                <h1 style={{margin:'0 0 10px',fontSize:'clamp(26px,3vw,38px)',fontWeight:500,letterSpacing:'-.02em',color:'var(--text-heading)'}}>Bagaimana saya dapat membantu?</h1>
                <p style={{maxWidth:620,margin:'0 auto var(--space-6)',fontSize:15,lineHeight:1.6,color:'var(--text-muted)'}}>Tanyakan apa saja tentang data KKP, laporan, regulasi, atau analisis spasial. Didukung oleh Kementerian Kelautan dan Perikanan.</p>
                <div style={{display:'grid',gridTemplateColumns:'repeat(2,minmax(190px,280px))',gap:12,width:'100%',justifyContent:'center'}}>
                  {['Tampilkan geometri kawasan konservasi Laut Sawu','Bandingkan tren EVIKA 2021–2025','Hitung blue carbon pada area mangrove','Cari data kelautan dari Portal KKP'].map(q=><button key={q} onClick={()=>runQuery(q)} style={{minHeight:64,padding:'14px 16px',textAlign:'left',border:'1px solid #dce5ed',borderRadius:12,background:'rgba(255,255,255,.72)',color:'var(--text-body)',font:'inherit',fontSize:13,cursor:'pointer',boxShadow:'0 2px 8px rgba(11,18,32,.04)'}}>{q}</button>)}
                </div>
              </div>
            ) : messages.map((t) => t.role==='user' ? (
              <div key={t.id} style={{alignSelf:'flex-end',maxWidth:460,background:'var(--kkp-indigo-700)',color:'#fff',padding:'var(--space-4) var(--space-5)',borderRadius:'var(--radius-lg) var(--radius-lg) var(--radius-xs) var(--radius-lg)'}}>
                {t.text}
              </div>
            ) : (
              <div key={t.id} style={{display:'flex',gap:'var(--space-3)'}}>
                <span style={{width:36,height:36,flex:'none',borderRadius:'var(--radius-pill)',background:'#fff',display:'flex',alignItems:'center',justifyContent:'center',border:'1px solid rgba(56,132,255,.2)',boxShadow:'0 2px 8px rgba(11,18,32,.1)',overflow:'hidden'}}>
                  <img src="/assets/oms-ai-bot.svg" alt="AI" style={{width:34,height:32}} />
                </span>
                <div style={{flex:1,minWidth:0}}>
                  <div style={{padding:'var(--space-4) var(--space-5)',marginBottom:'var(--space-3)',background:'rgba(255,255,255,.35)',backdropFilter:'blur(16px) saturate(180%)',WebkitBackdropFilter:'blur(16px) saturate(180%)',border:'1px solid rgba(255,255,255,.5)',borderRadius:'var(--radius-lg)'}}>
                    <div className="kkp-eyebrow">Langkah agen</div>
                    {t.knowledgePlan?.playbook ? <div style={{display:'flex',alignItems:'center',gap:5,flexWrap:'wrap',margin:'9px 0 6px',padding:'8px 9px',borderRadius:'var(--radius-sm)',background:'rgba(224,242,254,.58)',border:'1px solid rgba(56,132,255,.22)'}}>
                      <span style={{fontSize:9,fontWeight:700,color:'var(--kkp-indigo-800)'}}>KB · {t.knowledgePlan.playbook.name}</span>
                      {t.knowledgePlan.playbook.steps.map((s,i)=><React.Fragment key={s.action+s.target}><span style={{fontSize:9,color:'var(--text-muted)'}}>→</span><span style={{fontSize:8.5,padding:'2px 5px',borderRadius:8,background:'rgba(255,255,255,.72)',color:'var(--text-body)'}}>{s.action} · {s.target}</span></React.Fragment>)}
                    </div>:null}
                    {t.stages.map(s=><Stage key={s.t} s={s} />)}
                  </div>
                  {t.loading ? (
                    <div style={{padding:'var(--space-5)',background:'rgba(255,255,255,.55)',backdropFilter:'blur(20px) saturate(180%)',WebkitBackdropFilter:'blur(20px) saturate(180%)',border:'1px solid rgba(255,255,255,.6)',borderRadius:'var(--radius-lg)',color:'var(--text-muted)',fontSize:'var(--type-body-sm-size)'}}>
                      Mengambil data…
                    </div>
                  ) : (
                    <div style={{padding:'var(--space-5)',background:'rgba(255,255,255,.55)',backdropFilter:'blur(20px) saturate(180%)',WebkitBackdropFilter:'blur(20px) saturate(180%)',border:'1px solid rgba(255,255,255,.6)',borderRadius:'var(--radius-lg)',boxShadow:'0 8px 32px rgba(11,18,32,.1)'}}>
                      {t.model ? <div style={{display:'flex',alignItems:'center',gap:6,marginBottom:10,fontSize:10.5,color:'var(--text-muted)'}}><span style={{width:6,height:6,borderRadius:'50%',background:'#24995a'}} />Respons langsung · {t.model}</div> : null}
                      <p style={{margin:0,color:'var(--text-body)',whiteSpace:'pre-line'}} dangerouslySetInnerHTML={{__html:formatAssistantText(t.text||'')}} />
                      {t.cites && t.cites.length ? (
                        <div style={{marginTop:'var(--space-4)',paddingTop:'var(--space-4)',borderTop:'1px solid var(--border-subtle)'}}>
                          <div className="kkp-eyebrow">Sumber terverifikasi</div>
                          {t.cites.map(([c,m],n)=>(
                            <div key={c} style={{display:'flex',gap:'var(--space-3)',alignItems:'flex-start',marginTop:'var(--space-3)',paddingBottom:'var(--space-3)',borderBottom:n<t.cites.length-1?'1px solid var(--border-subtle)':'none'}}>
                              <span style={{width:20,height:20,flex:'none',borderRadius:'var(--radius-xs)',background:'var(--kkp-indigo-100)',color:'var(--kkp-indigo-700)',fontSize:10,fontWeight:700,display:'flex',alignItems:'center',justifyContent:'center'}}>{n+1}</span>
                              <div style={{flex:1,minWidth:0}}>
                                <span style={{fontSize:'var(--type-body-sm-size)'}}>
                                  <a href="https://portaldata.kkp.go.id" target="_blank" rel="noreferrer" style={{fontWeight:600}}>{c}</a>
                                  <span style={{display:'block',color:'var(--text-muted)',fontSize:'var(--type-caption-size)'}}>{m}</span>
                                </span>
                                <div style={{display:'flex',gap:'var(--space-2)',marginTop:'var(--space-2)',flexWrap:'wrap',alignItems:'center'}}>
                                  <Button variant="ghost" size="s" iconStart="search" onClick={()=>go('search')}>Cari di katalog data</Button>
                                  <Button variant="ghost" size="s" iconStart="crop" onClick={()=>{setExpanded(true);setMapTool('table');}}>Lihat di map viewer</Button>
                                </div>
                              </div>
                            </div>
                          ))}
                        </div>
                      ) : null}
                    </div>
                  )}
                </div>
              </div>
            ))}
          </div>
          <form onSubmit={e=>{e.preventDefault(); runQuery(draft);}} style={{position:'sticky',bottom:0,zIndex:8,flex:'none',borderTop:'1px solid rgba(255,255,255,.55)',background:'rgba(255,255,255,.82)',backdropFilter:'blur(20px) saturate(180%)',WebkitBackdropFilter:'blur(20px) saturate(180%)',padding:'var(--space-4) var(--space-6)',boxShadow:'0 -6px 18px rgba(11,18,32,.06)'}}>
            <div style={{display:'flex',gap:'var(--space-2)',alignItems:'center',border:'1.5px solid var(--kkp-azure-500)',borderRadius:28,padding:'5px 7px 5px 14px',background:'#fff',boxShadow:'0 3px 12px rgba(8,125,193,.08)'}}>
              <Input style={{flex:1,border:0,boxShadow:'none',background:'transparent'}} icon="speech-bubble" value={draft} onChange={e=>setDraft(e.target.value)}
                placeholder="Tanyakan apa saja tentang data KKP…" />
              <Button type="submit" variant="primary" iconEnd="chevron-right" disabled={busy} style={{width:38,height:38,padding:0,borderRadius:'50%'}}> </Button>
            </div>
          </form>
          {mapClosed ? (
            <button onClick={()=>setMapClosed(false)} title="Buka map viewer"
              style={{position:'absolute',top:'var(--space-4)',right:'var(--space-4)',zIndex:5,border:'none',cursor:'pointer',
                background:'var(--kkp-indigo-900)',color:'#fff',borderRadius:'var(--radius-pill)',padding:'8px 14px',display:'flex',alignItems:'center',gap:6,
                fontSize:12,fontWeight:600,boxShadow:'0 4px 16px rgba(0,0,0,.25)'}}>
              <Icon name="map" size={14} style={{filter:'invert(1)'}} />Buka peta
            </button>
          ) : null}
        </main>

        <div style={{padding:0,minWidth:0,minHeight:0,height:'100%',display:mapClosed?'none':'flex',flexDirection:'column'}}>
          <MapViewer key={mapSessionKey} expanded={expanded} onToggleExpand={()=>setExpanded(e=>!e)} onClose={()=>setMapClosed(true)} onAddContext={context=>setAoiContext(context)} initialTool={mapTool} focus={focus} workspace={mapWorkspace} setWorkspace={setMapWorkspace} />
        </div>
      </SplitPane>
      {newChatConfirm?<div className="kkp-modal__scrim" style={{zIndex:890}} onClick={()=>setNewChatConfirm(false)}>
        <div className="kkp-modal" role="dialog" aria-modal="true" aria-labelledby="new-chat-title" style={{maxWidth:540}} onClick={e=>e.stopPropagation()}>
          <div className="kkp-modal__head"><div><h3 id="new-chat-title" style={{margin:0}}>Mulai percakapan baru?</h3><p style={{margin:'5px 0 0',fontSize:12,color:'var(--text-muted)'}}>Percakapan dan Map Viewer saat ini masih memiliki konteks aktif.</p></div><button onClick={()=>setNewChatConfirm(false)} aria-label="Tutup" style={{border:0,background:'transparent',cursor:'pointer'}}><Icon name="x" size={16}/></button></div>
          <div className="kkp-modal__body">
            <div style={{padding:13,border:'1px solid rgba(214,78,78,.24)',borderRadius:'var(--radius-md)',background:'rgba(255,238,238,.72)',display:'flex',gap:10}}><Icon name="warning" size={18}/><div><b style={{display:'block',fontSize:12}}>Hapus sesi aktif</b><span style={{display:'block',fontSize:11,color:'var(--text-muted)',marginTop:3}}>Riwayat chat, konteks analisis, area fokus, dan semua layer pada Map Viewer akan dibersihkan.</span></div></div>
            <div style={{padding:13,marginTop:10,border:'1px solid rgba(56,132,255,.24)',borderRadius:'var(--radius-md)',background:'rgba(224,242,254,.58)',display:'flex',gap:10}}><Icon name="save" size={18}/><div><b style={{display:'block',fontSize:12}}>Simpan terlebih dahulu</b><span style={{display:'block',fontSize:11,color:'var(--text-muted)',marginTop:3}}>Simpan percakapan, hasil analisis, konteks spasial, dan susunan layer ke Workspace sebelum membuat sesi baru.</span></div></div>
          </div>
          <div className="kkp-modal__foot" style={{flexWrap:'wrap'}}><Button variant="secondary" onClick={()=>setNewChatConfirm(false)}>Batal</Button><Button variant="secondary" iconStart="save" disabled={!messages.some(m=>m.role==='ai'&&!m.loading)||role?.readOnly} onClick={saveBeforeNewChat}>Simpan di Workspace</Button><Button variant="primary" iconStart="trash" onClick={clearChatAndMap}>Hapus chat &amp; bersihkan peta</Button></div>
        </div>
      </div>:null}
      <EnterpriseSaveDialog open={!!saveTarget} role={role} message={saveTarget} focus={focus} onClose={()=>{setSaveTarget(null);setClearAfterSave(false);}} onSaved={result=>{setSavedResult(result);setSaveTarget(null);if(clearAfterSave){setClearAfterSave(false);clearChatAndMap();}setWorkspaceOpen(true);}} />
      {savedResult?<div style={{position:'fixed',right:24,bottom:24,zIndex:950,width:340,padding:14,borderRadius:'var(--radius-md)',background:'#fff',border:'1px solid rgba(36,153,90,.35)',boxShadow:'var(--shadow-lg)',display:'flex',gap:10}}><Icon name="check-circle" size={18}/><div style={{flex:1}}><b style={{display:'block',fontSize:12}}>Tersimpan di workspace pengguna</b><small style={{display:'block',color:'var(--text-muted)',marginTop:3}}>{savedResult.runId} · owner {savedResult.owner}</small></div><button onClick={()=>setSavedResult(null)} aria-label="Tutup" style={{border:0,background:'transparent',cursor:'pointer'}}><Icon name="x" size={13}/></button></div>:null}
    </div>
  );
}

function titleCase(s){
  return String(s).toLowerCase().replace(/(^|\s|\.)([a-z])/g, (m,pre,c)=>pre+c.toUpperCase());
}

function formatAssistantText(text){
  return String(text).replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;')
    .replace(/\*\*(.+?)\*\*/g,'<strong style="color:var(--text-heading)">$1</strong>');
}

/* Collapsible facet section styled after geoportal-server-catalog's search
   sidebar (dark header bar for the sections open by default, plain rows for
   the rest, chevron toggling collapsed state). */
function FilterSection({ title, defaultOpen, dark, editable, children }){
  const [open,setOpen] = React.useState(!!defaultOpen);
  return (
    <div style={{borderBottom:'1px solid var(--border-subtle)'}}>
      <button onClick={()=>setOpen(o=>!o)} style={{width:'100%',display:'flex',alignItems:'center',justifyContent:'space-between',
        padding:'10px 12px',border:'none',cursor:'pointer',font:'inherit',textAlign:'left',
        background:dark?'var(--kkp-indigo-900)':'transparent',color:dark?'#fff':'var(--text-heading)',
        fontSize:13,fontWeight:600}}>
        <span style={{display:'flex',alignItems:'center',gap:6}}>
          <Icon name={open?'chevron-down':'chevron-right'} size={13} style={dark?{filter:'invert(1)'}:undefined} />
          {title}
        </span>
        {editable ? <Icon name="pencil" size={12} style={dark?{filter:'invert(1)',opacity:.7}:{opacity:.5}} /> : null}
      </button>
      {open ? <div style={{padding:'10px 12px'}}>{children}</div> : null}
    </div>
  );
}

function FacetRow({ label, count, active, onClick }){
  return (
    <button onClick={onClick} disabled={!onClick} style={{display:'flex',width:'100%',justifyContent:'space-between',gap:8,
      padding:'4px 0',border:'none',background:'transparent',font:'inherit',fontSize:12.5,textAlign:'left',
      cursor:onClick?'pointer':'default',color:active?'var(--kkp-azure-700)':'var(--text-body)',fontWeight:active?700:400}}>
      <span>{label}</span>
      {count!=null ? <span className="kkp-mono" style={{color:'var(--text-muted)'}}>({count})</span> : null}
    </button>
  );
}

function OmsSearch({ go, request, mapWorkspace, setMapWorkspace }){
  const [expanded,setExpanded] = React.useState(false);
  const [mapClosed,setMapClosed] = React.useState(false);
  // Dengan panel filter 300px, 32% layar menghasilkan lebar peta yang sama
  // dengan map pane Ask AI (40% dari area setelah panel kiri).
  const [mapPct,setMapPct] = React.useState(32);
  const [activeId,setActiveId] = React.useState(null);
  const [q,setQ] = React.useState(()=>request?.query||'');
  const [page,setPage] = React.useState(1);
  const [pageSize,setPageSize] = React.useState(10);
  const [category,setCategory] = React.useState('Semua kategori');
  const [workspaceFilter,setWorkspaceFilter] = React.useState(null);
  const [catalog,setCatalog] = React.useState(null);
  const [catalogError,setCatalogError] = React.useState(null);
  const [sidakoDocuments,setSidakoDocuments] = React.useState([]);
  const [sidakoDocumentsError,setSidakoDocumentsError] = React.useState(null);
  const [sidakoDocumentsLoading,setSidakoDocumentsLoading] = React.useState(true);
  const [focus,setFocus] = React.useState(null);

  React.useEffect(()=>{
    if(request?.query!=null){setQ(String(request.query));setPage(1);}
  },[request?.query]);

  React.useEffect(()=>{
    let cancelled = false;
    window.fetchLayerCatalog()
      .then(rows => { if (!cancelled) setCatalog(rows); })
      .catch(err => { if (!cancelled) setCatalogError(err.message); });
    return () => { cancelled = true; };
  }, []);

  React.useEffect(()=>{
    let cancelled = false;
    fetch('/sidako-proxy/api/dokumen?dataTable=true&orderField=date&orderValue=desc&start=0&length=100', {
      headers:{'X-localization':'id'}
    }).then(res=>{
      if(!res.ok) throw new Error('SIDAKO merespons '+res.status);
      return res.json();
    }).then(payload=>{
      if(!cancelled) setSidakoDocuments((Array.isArray(payload.data)?payload.data:[]).filter(doc=>doc.status==='published' && doc.name && doc.file));
    }).catch(err=>{
      if(!cancelled) setSidakoDocumentsError(err.message);
    }).finally(()=>{
      if(!cancelled) setSidakoDocumentsLoading(false);
    });
    return ()=>{cancelled=true;};
  }, []);

  const combinedCatalog = React.useMemo(()=>{
    const layers=catalog||[];
    const documents=sidakoDocuments.map(doc=>({
      id:'sidako-document:'+doc.id,
      name:doc.name,
      title:doc.name,
      description:String(doc.description||'').replace(/\[OPEN_TAG\][^\[]*\[CLOSE_TAG\]/g,' ').replace(/\s+/g,' ').trim(),
      categoryLabel:doc.categories?.name||'Dokumen',
      workspace:'Dokumen SIDAKO',
      resourceType:'Document',
      documentUrl:'https://sidako.kkp.go.id/storage/'+String(doc.file).split('/').map(encodeURIComponent).join('/'),
      documentDate:doc.date,
      documentFocus:doc.focus?.name,
      documentAuthor:doc.author,
    }));
    if(!layers.length) return documents;
    const merged=[];
    layers.forEach((layer,index)=>{
      merged.push(layer);
      if(index%4===3 && documents.length) merged.push(documents.shift());
    });
    return merged.concat(documents);
  },[catalog,sidakoDocuments]);

  const categoryFacets = React.useMemo(() => {
    const counts = {};
    combinedCatalog.forEach(r => { counts[r.categoryLabel] = (counts[r.categoryLabel]||0)+1; });
    return Object.entries(counts).sort((a,b)=>b[1]-a[1]);
  }, [combinedCatalog]);

  const workspaceFacets = React.useMemo(() => {
    const counts = {};
    combinedCatalog.forEach(r => { counts[r.workspace] = (counts[r.workspace]||0)+1; });
    return Object.entries(counts).sort((a,b)=>b[1]-a[1]);
  }, [combinedCatalog]);

  const results = React.useMemo(() => {
    const query = q.trim().toLowerCase();
    return combinedCatalog.filter(r =>
      (category==='Semua kategori' || r.categoryLabel===category) &&
      (!workspaceFilter || r.workspace===workspaceFilter) &&
      (!query || [r.title,r.name,r.description,r.workspace,r.categoryLabel,r.documentAuthor,r.documentFocus].some(value=>String(value||'').toLowerCase().includes(query)))
    );
  }, [combinedCatalog, q, category, workspaceFilter]);
  const pageCount=Math.max(1,Math.ceil(results.length/pageSize));
  const currentPage=Math.min(page,pageCount);
  const pageResults=results.slice((currentPage-1)*pageSize,currentPage*pageSize);
  const pageNumbers=Array.from({length:pageCount},(_,index)=>index+1)
    .filter(number=>number===1||number===pageCount||Math.abs(number-currentPage)<=1);

  const openOnMap = (r) => {
    setActiveId(r.id);
    if(r.documentUrl) return;
    if (r.extent) {
      setFocus({ extent:r.extent, wmsLayer:r.wmsLayer, featureServiceUrl:r.featureServiceUrl, definitionExpression:'1=1', zoomToLayer:!!r.featureServiceUrl, topicLabel:r.title, catalogSource:true });
    }
  };

  return (
    <SplitPane rightPct={mapPct} onRightPctChange={setMapPct} collapsed={mapClosed} minPct={22} maxPct={72}>
    <div style={{'--oms-left-pane':'300px',minWidth:0,minHeight:'calc(100vh - var(--oms-content-offset, 60px))',padding:0,position:'relative'}}>
      {mapClosed ? (
        <button onClick={()=>setMapClosed(false)} title="Buka map viewer"
          style={{position:'sticky',top:'var(--space-4)',float:'right',zIndex:5,border:'none',cursor:'pointer',
            background:'var(--kkp-indigo-900)',color:'#fff',borderRadius:'var(--radius-pill)',padding:'8px 14px',display:'flex',alignItems:'center',gap:6,
            fontSize:12,fontWeight:600,boxShadow:'0 4px 16px rgba(0,0,0,.25)',marginBottom:'var(--space-4)'}}>
          <Icon name="map" size={14} style={{filter:'invert(1)'}} />Buka peta
        </button>
      ) : null}
      <div className="oms-search-cols" style={{display:'grid',gridTemplateColumns:'var(--oms-left-pane) minmax(0,1fr)',gap:0,alignItems:'start',minHeight:'100%'}}>
        <aside className="kkp-card" style={{padding:0,position:'sticky',top:0,height:'calc(100vh - var(--oms-content-offset, 60px))',borderRadius:0,borderTop:0,borderBottom:0,borderLeft:0,overflow:'auto'}}>
          <FilterSection title="Map" dark defaultOpen editable>
            <div style={{position:'relative',borderRadius:'var(--radius-sm)',overflow:'hidden',border:'1px solid var(--border-subtle)',height:120,background:'linear-gradient(135deg,#dce9d5,#cfe3ea)'}}>
              <div style={{position:'absolute',top:8,left:8,right:8,display:'flex',alignItems:'center',gap:6,background:'#fff',borderRadius:'var(--radius-pill)',padding:'4px 10px',boxShadow:'var(--shadow-sm)'}}>
                <Icon name="search" size={12} />
                <span style={{fontSize:11,color:'var(--text-muted)'}}>Cari alamat atau tempat</span>
              </div>
              <span style={{position:'absolute',bottom:8,right:10,fontSize:10,color:'rgba(0,0,0,.4)'}}>peta contoh</span>
            </div>
            <div style={{display:'flex',gap:'var(--space-4)',marginTop:8,fontSize:12}}>
              {['Any','Intersects','Within'].map((v,i)=>(
                <label key={v} style={{display:'flex',alignItems:'center',gap:4,cursor:'pointer',color:'var(--text-body)'}}>
                  <input type="radio" name="spatial-op" defaultChecked={i===0} disabled /> {v}
                </label>
              ))}
            </div>
          </FilterSection>
          <FilterSection title="Collections">
            <p style={{fontSize:12,color:'var(--text-muted)',margin:0}}>Tidak tersedia untuk sumber data ini.</p>
          </FilterSection>
          <FilterSection title="Time Period">
            <p style={{fontSize:12,color:'var(--text-muted)',margin:0}}>Tidak tersedia untuk sumber data ini.</p>
          </FilterSection>
          <FilterSection title="Date">
            <p style={{fontSize:12,color:'var(--text-muted)',margin:0}}>Tidak tersedia untuk sumber data ini.</p>
          </FilterSection>
          <FilterSection title="Owner">
            <p style={{fontSize:12,color:'var(--text-muted)',margin:0}}>Tidak tersedia untuk sumber data ini.</p>
          </FilterSection>
          <FilterSection title="Topic Category">
            <FacetRow label="Semua kategori" count={combinedCatalog.length} active={category==='Semua kategori'} onClick={()=>{setCategory('Semua kategori');setPage(1);}} />
            {categoryFacets.map(([label,count])=>(
              <FacetRow key={label} label={label} count={count} active={category===label} onClick={()=>{setCategory(category===label?'Semua kategori':label);setPage(1);}} />
            ))}
          </FilterSection>
          <FilterSection title="Metadata Type" dark defaultOpen editable>
            {workspaceFacets.map(([ws,count])=>(
              <FacetRow key={ws} label={ws} count={count} active={workspaceFilter===ws} onClick={()=>{setWorkspaceFilter(workspaceFilter===ws?null:ws);setPage(1);}} />
            ))}
          </FilterSection>
          <FilterSection title="Organizations">
            <FacetRow label="Kementerian Kelautan dan Perikanan RI" count={combinedCatalog.length} />
          </FilterSection>
          <FilterSection title="Keywords">
            <div style={{display:'flex',flexWrap:'wrap',gap:6}}>
              {['KKP','kelautan dan perikanan','GeoServer','WMS','SIDAKO','dokumen'].map(k=><Tag key={k}>{k}</Tag>)}
            </div>
          </FilterSection>
          <FilterSection title="Origin Type">
            <FacetRow label="Live Service (WMS)" count={(catalog||[]).length} />
            <FacetRow label="Dokumen (PDF)" count={sidakoDocuments.length} />
          </FilterSection>
          <div style={{padding:'12px'}}>
            <Button block variant="secondary" size="s" onClick={()=>{setCategory('Semua kategori');setWorkspaceFilter(null);setQ('');setPage(1);}}>Reset filter</Button>
          </div>
          <div style={{padding:'0 12px 12px',fontSize:10.5,color:'var(--text-muted)'}}>
            Katalog menggabungkan layer dari Portal Satu Data KKP dan dokumen publik dari <a href="https://sidako.kkp.go.id/sidako/dokumen" target="_blank" rel="noreferrer">SIDAKO</a>, dipetakan ke skema metadata ala <a href="https://github.com/Esri/geoportal-server-catalog" target="_blank" rel="noreferrer">geoportal-server-catalog</a>.
          </div>
        </aside>
        <div style={{minWidth:0,padding:'var(--space-6) var(--layout-gutter)'}}>
          <div style={{display:'flex',gap:'var(--space-3)',alignItems:'flex-end',flexWrap:'wrap'}}>
            <Input style={{flex:'1 1 200px',minWidth:160}} icon="search" label="Cari katalog OMS" value={q} onChange={e=>{setQ(e.target.value);setPage(1);}} placeholder="mis. nelayan, WPP, konsumsi ikan" />
            <Button variant="primary" iconStart="search">Telusuri</Button>
          </div>
          <div style={{marginTop:'var(--space-6)'}}>
          <div className="kkp-eyebrow" style={{marginBottom:'var(--space-4)'}}>
            {catalog || !sidakoDocumentsLoading ? results.length+' item ditemukan' : 'Memuat katalog…'}
          </div>
          {catalogError ? (
            <div className="kkp-card" style={{padding:'var(--space-5)',color:'var(--status-critical)'}}>Layer Portal Satu Data KKP belum dapat dimuat: {catalogError}</div>
          ) : null}
          {sidakoDocumentsError ? <div className="kkp-card" style={{padding:'var(--space-4)',marginBottom:'var(--space-4)',fontSize:12,color:'var(--status-critical)'}}>Dokumen SIDAKO belum dapat dimuat: {sidakoDocumentsError}. <a href="https://sidako.kkp.go.id/sidako/dokumen" target="_blank" rel="noopener noreferrer">Buka sumber</a></div> : null}
          {activeId ? (() => { const r = combinedCatalog.find(x=>x.id===activeId); if (!r) return null;
            const rec = window.toGeoportalRecord(r);
            return (
            <div className="kkp-card" style={{padding:'var(--space-5)',marginBottom:'var(--space-4)',border:'1px solid var(--kkp-azure-300)',background:'var(--surface-accent-soft)'}}>
              <div style={{display:'flex',justifyContent:'space-between',gap:'var(--space-3)'}}>
                <div className="kkp-eyebrow">Metadata (skema geoportal-server-catalog)</div>
                <button onClick={()=>setActiveId(null)} aria-label="Tutup" style={{border:'none',background:'transparent',cursor:'pointer',color:'var(--text-muted)'}}><Icon name="x" size={14} /></button>
              </div>
              <div style={{fontWeight:700,color:'var(--text-heading)',marginTop:6}}>{rec.title}</div>
              <div style={{fontSize:'var(--type-body-sm-size)',color:'var(--text-muted)',marginTop:4}}>{rec.description}</div>
              <div style={{display:'grid',gridTemplateColumns:'1fr 1fr',gap:'var(--space-3)',marginTop:'var(--space-4)',fontSize:12}}>
                <div><span style={{color:'var(--text-muted)'}}>identifier</span><div style={{fontWeight:600,color:'var(--text-heading)'}} className="kkp-mono">{rec.identifier}</div></div>
                <div><span style={{color:'var(--text-muted)'}}>resourceType</span><div style={{fontWeight:600,color:'var(--text-heading)'}}>{rec.resourceType}</div></div>
                <div><span style={{color:'var(--text-muted)'}}>publisher</span><div style={{fontWeight:600,color:'var(--text-heading)'}}>{rec.publisher}</div></div>
                <div><span style={{color:'var(--text-muted)'}}>akses</span><div><Badge tone="good">Terbuka</Badge></div></div>
              </div>
              <div style={{marginTop:'var(--space-3)'}}>
                <span style={{fontSize:12,color:'var(--text-muted)'}}>keywords</span>
                <div style={{display:'flex',gap:'var(--space-2)',flexWrap:'wrap',marginTop:4}}>
                  {rec.keywords.map(k=><Tag key={k}>{k}</Tag>)}
                </div>
              </div>
              {rec.spatial ? (
                <div style={{marginTop:'var(--space-3)',fontSize:12}}>
                  <span style={{color:'var(--text-muted)'}}>spatial (envelope, EPSG:4326)</span>
                  <div className="kkp-mono" style={{fontWeight:600,color:'var(--text-heading)'}}>
                    [{rec.spatial.coordinates[0][0].toFixed(2)}, {rec.spatial.coordinates[0][1].toFixed(2)}] – [{rec.spatial.coordinates[1][0].toFixed(2)}, {rec.spatial.coordinates[1][1].toFixed(2)}]
                  </div>
                </div>
              ) : null}
              <div style={{marginTop:'var(--space-3)',fontSize:12}}>
                <span style={{color:'var(--text-muted)'}}>distribution</span>
                <div style={{display:'flex',flexDirection:'column',gap:4,marginTop:4}}>
                  {rec.distribution.map(d=>(
                    <a key={d.format} href={d.accessURL} target="_blank" rel="noreferrer" className="kkp-mono" style={{fontSize:11,color:'var(--kkp-azure-700)',wordBreak:'break-all'}}>
                      [{d.format}] {d.accessURL}
                    </a>
                  ))}
                </div>
              </div>
              <div style={{display:'flex',gap:'var(--space-2)',marginTop:'var(--space-4)',flexWrap:'wrap'}}>
                <Button variant="secondary" size="s" iconStart="speech-bubble" onClick={()=>go('assistant')}>Tanya asisten AI</Button>
              </div>
            </div>
          ); })() : null}
          <div style={{display:'flex',flexDirection:'column',gap:'var(--space-3)'}}>
            {pageResults.map(r=>(
              <div key={r.id} className="kkp-card kkp-card--interactive" style={{padding:'var(--space-5)',display:'flex',gap:'var(--space-4)',alignItems:'flex-start',
                  border:activeId===r.id?'1px solid var(--kkp-azure-400)':undefined}}
                onClick={()=>openOnMap(r)}>
                <span style={{width:40,height:40,flex:'none',borderRadius:'var(--radius-md)',background:'var(--surface-accent-soft)',display:'flex',alignItems:'center',justifyContent:'center'}}>
                  <Icon name={r.documentUrl?'file-text':'map'} size={18} />
                </span>
                <div style={{flex:1,minWidth:0}}>
                  <div style={{display:'flex',gap:'var(--space-3)',alignItems:'center'}}>
                    <span style={{fontWeight:700,color:'var(--text-heading)'}}>{r.title}</span>
                    <Badge tone="good">Terbuka</Badge>
                  </div>
                  <div style={{fontSize:'var(--type-body-sm-size)',color:'var(--text-muted)',marginTop:2}}>{r.categoryLabel}</div>
                  <div style={{display:'flex',gap:'var(--space-4)',marginTop:'var(--space-3)',fontSize:'var(--type-caption-size)',color:'var(--text-muted)'}}>
                    <span>{r.documentUrl?'SIDAKO KKP':r.featureServiceUrl?'ArcGIS Online · OMS Lautra':'Portal Satu Data KKP'}</span><span className="kkp-mono">{r.documentUrl?'PDF · '+[r.documentFocus,r.documentDate].filter(Boolean).join(' · '):r.wmsLayer||r.featureServiceUrl}</span>
                  </div>
                  <div style={{display:'flex',gap:'var(--space-2)',marginTop:'var(--space-3)',flexWrap:'wrap'}}>
                    {r.documentUrl?<a href={r.documentUrl} target="_blank" rel="noopener noreferrer" onClick={e=>e.stopPropagation()} style={{fontSize:12,fontWeight:700,color:'var(--kkp-azure-700)'}}>Buka dokumen ↗</a>:<Button variant="ghost" size="s" iconStart="map" onClick={e=>{e.stopPropagation();openOnMap(r);}}>Tampilkan di peta</Button>}
                  </div>
                </div>
                <Icon name="chevron-right" size={16} style={{opacity:.4}} />
              </div>
            ))}
            {(catalog || !sidakoDocumentsLoading) && results.length===0 ? <p style={{color:'var(--text-muted)',fontSize:'var(--type-body-sm-size)'}}>Tidak ada item katalog yang cocok.</p> : null}
          </div>
          {results.length>0 ? <nav aria-label="Halaman katalog data" style={{display:'flex',justifyContent:'space-between',alignItems:'center',gap:12,flexWrap:'wrap',marginTop:'var(--space-5)',fontSize:12,color:'var(--text-muted)'}}>
            <span>Menampilkan {(currentPage-1)*pageSize+1}–{Math.min(currentPage*pageSize,results.length)} dari {results.length} item</span>
            <div style={{display:'flex',alignItems:'center',gap:6,flexWrap:'wrap'}}>
              <label htmlFor="oms-catalog-page-size">Per halaman</label>
              <select id="oms-catalog-page-size" value={pageSize} onChange={e=>{setPageSize(Number(e.target.value));setPage(1);}} style={{padding:'6px 8px',border:'1px solid var(--border-subtle)',borderRadius:6,background:'var(--surface-card)',color:'var(--text-heading)'}}>
                {[10,20,40].map(size=><option key={size} value={size}>{size}</option>)}
              </select>
              <button type="button" disabled={currentPage===1} onClick={()=>setPage(currentPage-1)} style={{padding:'6px 10px',border:'1px solid var(--border-subtle)',borderRadius:6,background:'var(--surface-card)',cursor:currentPage===1?'not-allowed':'pointer',opacity:currentPage===1?0.5:1}}>Sebelumnya</button>
              {pageNumbers.map((number,index)=><React.Fragment key={number}>
                {index>0 && number-pageNumbers[index-1]>1?<span aria-hidden="true" style={{padding:'0 3px'}}>…</span>:null}
                <button type="button" aria-label={'Halaman '+number} aria-current={currentPage===number?'page':undefined} onClick={()=>setPage(number)} style={{minWidth:30,padding:'6px 8px',border:'1px solid '+(currentPage===number?'var(--kkp-azure-500)':'var(--border-subtle)'),borderRadius:6,background:currentPage===number?'var(--surface-accent-soft)':'var(--surface-card)',color:'var(--text-heading)',fontWeight:currentPage===number?700:400,cursor:'pointer'}}>{number}</button>
              </React.Fragment>)}
              <button type="button" disabled={currentPage===pageCount} onClick={()=>setPage(currentPage+1)} style={{padding:'6px 10px',border:'1px solid var(--border-subtle)',borderRadius:6,background:'var(--surface-card)',cursor:currentPage===pageCount?'not-allowed':'pointer',opacity:currentPage===pageCount?0.5:1}}>Berikutnya</button>
            </div>
          </nav> : null}
          <div style={{display:'flex',flexDirection:'column',gap:'var(--space-4)',marginTop:'var(--space-6)'}}>
            <div className="kkp-card" style={{padding:'var(--space-5)'}}>
              <div className="kkp-eyebrow">Butuh bantuan menyusun permintaan?</div>
              <p style={{fontSize:'var(--type-body-sm-size)',color:'var(--text-muted)',margin:'var(--space-3) 0'}}>
                Asisten agentik dapat menafsirkan pertanyaan dan mengambil data langsung dari Portal Satu Data KKP untuk Anda.
              </p>
              <Button block variant="brand" iconStart="speech-bubble" onClick={()=>go('assistant')}>Tanya Asisten AI</Button>
            </div>
            <div className="kkp-card" style={{padding:'var(--space-5)'}}>
              <div className="kkp-eyebrow">Status layanan</div>
              <div style={{display:'flex',flexDirection:'column',gap:'var(--space-3)',marginTop:'var(--space-3)'}}>
                <StatusDot tone="good" label="Portal Satu Data KKP" live />
                <StatusDot tone="good" label="GeoServer WMS KKP" />
              </div>
            </div>
          </div>
          </div>
        </div>
      </div>
    </div>
    <div style={{minWidth:0,position:'sticky',top:60,height:expanded?'100vh':'calc(100vh - 60px)',zIndex:expanded?'var(--z-modal)':undefined}}>
      <MapViewer expanded={expanded} onToggleExpand={()=>setExpanded(e=>!e)} onClose={()=>setMapClosed(true)} focus={focus} workspace={mapWorkspace} setWorkspace={setMapWorkspace} />
      {activeId && !combinedCatalog.find(r=>r.id===activeId)?.documentUrl && !expanded ? (
        <div style={{position:'absolute',left:12,bottom:12,right:12,fontSize:'var(--type-caption-size)',color:'#fff',background:'rgba(11,18,32,.75)',padding:'6px 10px',borderRadius:'var(--radius-sm)'}}>
          Menampilkan di peta: <strong>{(results.find(x=>x.id===activeId)||{}).title || activeId}</strong>
        </div>
      ) : null}
    </div>
    </SplitPane>
  );
}
Object.assign(window, { OmsAssistant, OmsSearch, MapViewer });
