/* ============ Function Map — IPCS 智慧工地 × 智慧 BIM 功能地圖 ============
   桌機以 SVG 環狀圖呈現，手機改用卡片式流程，點任一節點開啟模組說明遮罩。
   模組文案來自需求方提供的 moduleData，未改寫。
   ========================================================================== */

const FM_MODULES = {
  entryCluster:{title:'進場與人員掌握',category:'上午｜進場管理',summary:'包含刷臉進場、查看當日進場人員與施工人力掌握。',desc:'施工人員透過門禁進場後，系統取得進出場紀錄，管理者可查看即時在場人數、進場廠商與工種分布。',features:['刷臉進場紀錄','即時在場人數','廠商與工種統計','回帶日報與管理統計'],pain:['人工簽到容易漏記或代簽。','現場實際人力與管理資料不同步。','承攬商與工種配置難即時掌握。'],related:['工地人員管理','介接門禁管理','進出人員統計表'],type:'智慧工地模組'},
  safetyCluster:{title:'職安宣導與開工確認',category:'上午｜開工前確認',summary:'確認當日施工項目、作業風險與安全注意事項，並進行職安宣導。',desc:'施工者可掃描工種 QRCode，線上閱讀職安宣導內容後簽名，也可填寫危害告知單。',features:['施工項目與風險宣導','工種 QRCode','線上閱讀與簽名','危害告知單數位化'],pain:['職安宣導紙本簽名不易保存。','是否完成宣導常需要人工追蹤。','不同工種安全告知內容容易混用。'],related:['職安管理','工地人員管理','簽核專區'],type:'智慧工地模組'},
  workCluster:{title:'施工執行',category:'白天｜施工執行',summary:'承攬商及施工人員依當日排定工項與施工區域開始作業。',desc:'系統可依排定進度掌握各工項的預定開始日、完成日及目前執行情形，也可確認高危作業項目是否已完成申請。',features:['對應工項與施工區域','掌握預定與實際狀態','追蹤工項執行情形','確認高危作業申請狀態'],pain:['施工現況與排程表不同步。','高危作業是否已申請不易查核。','工班實際作業範圍難確認。'],related:['工地人員管理','職安管理','甘特圖'],type:'智慧工地模組'},
  checkCluster:{title:'查驗、巡檢與設備定檢',category:'白天｜品質與安全管理',summary:'整合工程查驗、職安巡檢與設備定檢，形成白天現場管理主軸。',desc:'工程師可進行自主檢查與分級查驗；職安人員可使用手機或平板填寫巡檢表單；設備則可掃描 QR Code 執行定檢並查看履歷。',features:['工程查驗與簽核紀錄','職安巡檢與缺失改善','設備 QR Code 定檢','照片、期限與複查結果留存'],pain:['查驗、巡檢與設備紀錄分散。','缺失改善與責任人追蹤困難。','設備證照與檢查狀態不易掌握。'],related:['工程查驗','職安管理','設備管理'],type:'智慧工地模組'},
  trackCluster:{title:'待辦、圖說標繪與任務追蹤',category:'白天｜問題追蹤',summary:'現場發現問題後，可建立待辦、圖說標繪、會議裁示與追蹤工項。',desc:'使用圖說協作管理直接標示圖說位置，並由標繪內容指派任務，設定負責人與完成期限；一般待辦與會議裁示也可集中追蹤。',features:['建立追蹤工項','圖說協作標繪','會議裁示轉任務','待處理、逾期與完成狀態'],pain:['現場問題靠口頭或通訊軟體容易遺漏。','圖說問題位置難描述。','不同來源任務缺乏集中追蹤。'],related:['會議管理','圖說協作管理','工項追蹤'],type:'管理／共用模組'},
  duskCluster:{title:'黃昏收班與工程日報',category:'黃昏｜收班整理',summary:'包含刷臉離場、收工會議、隔日施工指派、工程日報與實際進度彙整。',desc:'離場紀錄形成完整進出場資料；收工會議記錄隔日施工項目、位置、負責廠商與預定人力；日報可多人共同填寫，並由門禁系統自動帶入人力與機具紀錄。',features:['刷臉離場與在場時間統計','收工會議與隔日指派','多人共同填報日報','依施作數量計算實際進度'],pain:['隔日施工安排靠口頭交代。','日報需重複填寫人力與機具。','實際進度統計仰賴人工彙整。'],related:['會議管理','日報表','甘特圖'],type:'智慧工地模組'},
  nightCluster:{title:'夜間加班與點工管理',category:'夜間｜加班人力',summary:'掌握夜間加班、點工、工時、出勤與人工成本。',desc:'可管理不同班次與工種的點工紀錄，並與出勤資料互相對照，讓人工成本與作業時數更清楚。',features:['點工與工時計算','夜間班次與出勤管理','人工成本控管','可搭配門禁與日報資訊'],pain:['夜間加班時數容易漏記。','零工或臨時人力成本難統計。','承攬商與現場紀錄容易不一致。'],related:['點工管理','工地人員管理','工程日報表'],type:'智慧工地模組'},
  bimCluster:{title:'智慧 BIM 與 3D 應用',category:'智慧 BIM 模組',summary:'包含建築管理 BIM、施工進度 BIM 與建築 3D GIS。',desc:'在瀏覽器中查看 BIM 模型、構件屬性與設備位置，也可結合施工進度，透過模型構件套色呈現施工狀態。',features:['BIM 模型瀏覽','構件屬性查詢','施工進度 BIM 套色','BIM 與 3D GIS 整合'],pain:['現場人員無法直接使用原生 BIM 軟體。','模型資訊與現場資料無法串起來。','進度表不易對照實際構件位置。'],related:['建築管理 BIM','施工進度 BIM','建築 3D GIS'],type:'智慧 BIM 模組'},

  gate:{title:'人員刷臉進入工地',category:'上午｜進場管理',summary:'施工人員透過人臉辨識門禁進場，記錄實際進場時間、所屬廠商與工種。',desc:'介接工地門禁系統，取得人員實際進出場紀錄，並依廠商、工種與人數進行統計。',features:['人臉辨識門禁進場','記錄進場時間、廠商與工種','統計在場人數','回帶日報與管理統計'],pain:['人工簽到容易漏記或代簽。','現場實際人數與管理端不同步。','承攬商與工種配置難即時掌握。'],related:['工地人員管理','介接門禁管理','進出人員統計表'],type:'智慧工地模組'},
  attendance:{title:'查看當日進場人員',category:'上午｜現場掌握',summary:'工地管理者確認當日實際進場人數、施工廠商及各工種配置。',desc:'查看即時在場人數、進場廠商、工種分布及人員進出場狀況，掌握當日實際施工人力。',features:['即時在場人數','廠商與工種分布','進出場狀況查詢','支援主管快速掌握現場動態'],pain:['主管無法即時知道現場實際人數。','外包與本工人員難統一統計。','紙本或 Excel 更新速度慢。'],related:['工地人員管理','介接門禁管理','工程日報表'],type:'智慧工地模組'},
  safetyBriefing:{title:'確認施工項目與職安宣導',category:'上午｜開工前確認',summary:'針對當日施工項目、作業風險及安全注意事項進行職安宣導。',desc:'對施工者進行職安宣導，掃描工種 QRCODE，線上閱讀職安宣導內容後簽名，並可填寫危害告知單。',features:['施工項目與風險宣導','掃描工種 QRCODE','線上閱讀與簽名','危害告知單數位化'],pain:['紙本宣導與簽名留存分散。','新進人員是否完成教育常難追蹤。','不同工種安全告知內容容易混用。'],related:['職安管理','工地人員管理','簽核專區'],type:'智慧工地模組'},
  startWork:{title:'施工人員開始施工',category:'白天｜施工執行',summary:'承攬商與施工人員依當日排定的工項及施工區域開始作業。',desc:'依排定進度掌握各工項的預定開始日、完成日及目前執行情形，也可確認高危作業是否已申請。',features:['對應排定工項','掌握預定與實際狀態','追蹤目前執行情形','確認高危作業申請狀態'],pain:['施工現況與排程表不同步。','高危作業申請狀態難查核。','工班實際作業範圍難確認。'],related:['工地人員管理','職安管理','甘特圖'],type:'智慧工地模組'},
  inspection:{title:'執行工程查驗',category:'白天｜品質管理',summary:'工地工程師依當日施工項目進行自主檢查、分級查驗及品質確認。',desc:'依工程查驗流程執行一級、二級及三級查驗，記錄查驗結果、照片、文件及簽核紀錄。',features:['自主檢查與分級查驗','查驗結果與照片留存','文件與簽核紀錄管理','查驗階段與狀態追蹤'],pain:['查驗資料散在紙本與通訊軟體。','查驗結果、照片與簽核紀錄難串連。','後續追查品質歷程耗時。'],related:['工程查驗','工程查驗單','簽核專區'],type:'智慧工地模組'},
  safetyPatrol:{title:'執行職安巡檢',category:'白天｜安全巡檢',summary:'職安人員依當日施工項目及現場作業狀況進行安全巡檢。',desc:'使用手機或平板填寫巡檢表單，記錄檢查結果、缺失內容、現場照片、改善期限及複查結果。',features:['手機／平板巡檢表單','缺失內容與照片紀錄','改善期限追蹤','複查結果管理'],pain:['巡檢紀錄無法即時回報。','缺失改善狀態追蹤困難。','照片與檢查項目缺乏關聯。'],related:['職安管理','職安檢查單','工項追蹤'],type:'智慧工地模組'},
  equipmentCheck:{title:'執行設備定檢',category:'白天｜設備安全',summary:'職安人員針對施工機具、設備及安全設施進行定期檢查。',desc:'掃描設備專屬 QR Code 後執行定檢，查看設備資料、歷次檢查紀錄及目前檢查狀態／證照狀態。',features:['掃描設備 QR Code','設備資料與履歷查詢','歷次檢查紀錄','檢查與證照狀態掌握'],pain:['設備資料與定檢紀錄分散。','證照有效性不易即時確認。','設備異常與維護歷程難追溯。'],related:['職安管理','設備管理','建築 3D GIS'],type:'智慧工地模組'},
  todo:{title:'發現現場待辦事項',category:'白天｜問題追蹤',summary:'工地管理者發現需要改善、協調或持續追蹤的工作事項。',desc:'建立追蹤工項，設定工作內容、負責人、完成期限及處理狀態，持續追蹤至結案。',features:['建立追蹤工項','設定負責人與期限','記錄處理狀態','追蹤至結案'],pain:['現場待辦靠口頭或通訊軟體容易遺漏。','責任人與期限不清楚。','改善過程缺少可查證紀錄。'],related:['會議管理','圖說協作管理','工項追蹤'],type:'管理／共用模組'},
  pdfMarkup:{title:'圖說發現施工問題',category:'白天｜圖說協作',summary:'工程師在 PDF 圖說上發現施工錯誤、缺失或需要協調的位置。',desc:'使用圖說協作管理直接標示圖說位置，並由標繪內容指派任務、設定負責人及完成期限。',features:['圖說協作標繪','標示問題位置','由標繪內容指派任務','設定負責人與期限'],pain:['圖說問題位置不易描述。','標註、任務與責任人分散管理。','後續追蹤缺少圖說位置依據。'],related:['圖說協作管理','工項追蹤','會議管理'],type:'管理／共用模組'},
  workMeeting:{title:'召開工務會議',category:'白天｜協調會議',summary:'各部門針對工程進度、施工界面、品質、職安或現場問題召開會議。',desc:'建立會議議程、出席人員、會議紀錄及相關附件，集中保存工地各類會議資料。',features:['會議議程與出席人員','會議紀錄與附件保存','會議裁示建立工作指派','與工項追蹤連動'],pain:['會議紀錄與待辦事項分離。','會議裁示後續執行不易追蹤。','不同部門會議資料散落。'],related:['會議管理','圖說協作管理','工項追蹤'],type:'管理／共用模組'},
  taskFollow:{title:'追蹤現場任務執行情況',category:'下午｜追蹤控管',summary:'工地管理者查看各部門、承攬商及人員的任務處理情況。',desc:'分別追蹤一般工作項目、會議裁示及圖說協作管理指派任務的待處理、處理中、逾期及完成狀態。',features:['一般工作項目追蹤','會議裁示追蹤','圖說協作任務追蹤','待處理、處理中、逾期、完成狀態'],pain:['不同來源任務難集中掌握。','逾期項目不易即時提醒。','主管需要人工彙整各部門進度。'],related:['工項追蹤','會議管理','圖說協作管理'],type:'管理／共用模組'},
  defectFollow:{title:'追蹤查驗與職安缺失改善',category:'下午｜改善確認',summary:'確認當日查驗進度、不合格項目、職安缺失與改善情形。',desc:'查看查驗案件的查驗階段、簽核狀態、不合格項目與改善情形；職安缺失可查看責任人、改善期限、照片、複查結果與結案狀態。',features:['查驗階段與簽核狀態','不合格項目追蹤','職安缺失責任人與期限','改善照片與複查結果'],pain:['查驗與職安缺失分別管理，難統整。','改善期限與責任人不易追蹤。','結案依據不完整。'],related:['工程查驗','職安管理','工項追蹤'],type:'智慧工地模組'},
  leaveSite:{title:'人員刷臉離開工地',category:'黃昏｜離場管理',summary:'施工人員完成當日工作後，透過人臉辨識門禁離場。',desc:'記錄實際離場時間，形成完整的進出場紀錄，並統計人員在場時間。',features:['刷臉離場紀錄','形成完整進出場紀錄','統計人員在場時間','支援日報與點工資料'],pain:['下班離場紀錄不完整。','實際在場時間難人工計算。','人力統計與日報資料容易不一致。'],related:['工地人員管理','門禁管理','日報表'],type:'智慧工地模組'},
  closeMeeting:{title:'召開收工會議',category:'黃昏｜收班整理',summary:'工地管理者與各部門確認隔日預定施工項目。',desc:'建立收工會議紀錄，記錄隔日施工項目、施工位置、負責廠商及預定投入人力，也可透過會議裁示指派隔日施工項目。',features:['收工會議紀錄','確認隔日施工項目','指派負責廠商與人員','形成隔日施工依據'],pain:['隔日施工安排靠口頭交代。','責任廠商與投入人力不明確。','今日問題與明日安排缺乏連續性。'],related:['會議管理','工項追蹤','甘特圖'],type:'管理／共用模組'},
  dailyReport:{title:'工程日報與進度彙整',category:'黃昏｜日報與進度',summary:'各工項負責人填寫當日施工項目、施作數量、工程進度及現場重要事項。',desc:'日報表可由多人共同編輯，各自填寫負責工項的施工內容及實際進度。人力與機具紀錄可由門禁系統自動帶入，並依詳細價目表及每日施作數量計算實際進度。',features:['多人共同填報日報','人力與機具資料自動帶入','依施作數量計算實際進度','比對預定與實際進度'],pain:['日報需重複填寫人力與機具資料。','實際進度統計仰賴人工彙整。','預定與實際進度差異不易即時掌握。'],related:['日報表','甘特圖','進出人員統計表'],type:'智慧工地模組'},
  overtime:{title:'夜間加班與點工管理',category:'夜間｜加班人力',summary:'提供點工、工時、出勤及人工成本管理，掌握夜間加班與臨時人力。',desc:'可管理不同班次與工種的點工紀錄，並與出勤資料互相對照，讓人工成本與作業時數更清楚。',features:['點工與工時計算','夜間班次與出勤管理','人工成本控管','可搭配門禁與日報資訊'],pain:['夜間加班時數容易漏記。','零工或臨時人力成本難統計。','承攬商與現場紀錄容易不一致。'],related:['點工管理','工地人員管理','工程日報表'],type:'智慧工地模組'},

  dashboard:{title:'儀表板',category:'管理層｜主管視角',summary:'提供視覺化統計分析、自訂個人化儀表板、跨專案資料整合與分享功能。',desc:'可依不同對象建立儀表板與多專案監控，也能透過分享連結提供他人查看，快速掌握專案進度與管理指標。',features:['個人化儀表板','跨專案資料整合','年度與月份查詢','可分享儀表板連結'],pain:['管理資訊分散在多個報表。','主管需要花時間彙整才看得到重點。','不同角色需要不同檢視角度。'],related:['進出人員統計','甘特圖','工程日報表'],type:'管理／共用模組'},
  gantt:{title:'甘特圖',category:'管理層｜主管視角',summary:'提供專案流程、里程碑及管理節點設定，支援主、次甘特圖與權限管理。',desc:'可檢視分包商進度，建立甘特圖間任務關聯與影響提醒，並與會議管理、工項追蹤連動。',features:['主甘特圖與子甘特圖','任務關聯與影響提醒','分包商進度管理','可與會議／工項追蹤連動'],pain:['進度表與現場執行情形常分開管理。','分包商進度不易統整。','進度延誤對其他工作影響不易被看見。'],related:['行事曆','會議管理','工項追蹤'],type:'管理／共用模組'},
  approval:{title:'簽核專區',category:'管理／共用模組',summary:'提供電子簽核管理機制，可建立申請表單、審核流程及表單管理。',desc:'簽核流程可彈性設定送審節點，搭配電子簽名、自訂表單與線上填報，讓行政與工程流程集中管理。',features:['電子簽核','彈性送審流程','自訂表單','可搭配電子簽名與行動填報'],pain:['紙本送簽流程慢，狀態不透明。','不同專案流程差異大，難以共用。','跨部門溝通容易卡住。'],related:['權限管理','職安教育','檔案管理'],type:'管理／共用模組'},
  file:{title:'檔案管理',category:'管理／共用模組',summary:'提供視覺化資料夾管理、批次上傳、自動版本控管及全文檢索。',desc:'系統可自動管理文件版次，保留文件上傳、改版及刪除紀錄，支援全文檢索，包含 DWG 與 DGN 圖檔中的文字，也支援 PDF 行動查閱與線上註記。',features:['版本控管','全文檢索','PDF 行動查閱與註記','可指派人員與追蹤註記'],pain:['文件版本混亂，容易拿錯資料。','圖說與附件查找耗時。','交付與回查過程缺乏一致性。'],related:['設計階段','竣工階段','檔案交付'],type:'管理／共用模組'},
  permission:{title:'權限管理',category:'管理／共用模組',summary:'依組織職責建立不同群組，並針對各功能模組、專案及操作權限進行彈性設定。',desc:'支援跨專案差異化授權，同一位使用者可在不同專案套用不同角色權限，兼顧管理效率與資訊安全。',features:['群組與角色管理','跨專案差異化授權','模組級權限控制','適用管理者、作業者與瀏覽者'],pain:['不同承攬商與部門權限難控管。','同一帳號在不同專案角色不同。','過度授權帶來資訊安全風險。'],related:['簽核專區','檔案管理','儀表板'],type:'管理／共用模組'},
  buildingBim:{title:'建築管理 BIM',category:'智慧 BIM 模組',summary:'提供 BIM 模型管理、模型瀏覽、構件屬性查詢及模型資訊展示。',desc:'適用於設計、施工、竣工與維運各階段，協助使用者在瀏覽器中查看 BIM 模型、樓層、機電與構件屬性，並可連結文件與協作資訊。',features:['模型瀏覽','樓層／圖層切換','構件屬性查詢','可連結文件與協作資訊'],pain:['現場人員無法直接使用原生 BIM 軟體。','模型資訊與現場資料無法串起來。','文件與設備位置缺乏共同檢視介面。'],related:['設計階段','施工階段','竣工階段','維運階段'],type:'智慧 BIM 模組'},
  progressBim:{title:'施工進度 BIM',category:'智慧 BIM 模組',summary:'結合 BIM 模型與施工進度，提供施工階段展示與工程進度管理。',desc:'適用於鋼構、帷幕或其他組裝式施工管理，可依進度資料對模型構件套色，讓現場與主管更快掌握實際施作狀態。',features:['模型進度套色','構件與工項對應','施工階段視覺化','可搭配 Excel 進度資料'],pain:['傳統進度表不易對照實際構件。','延遲位置與影響範圍不直觀。','會議與追蹤缺乏共同視圖。'],related:['施工階段','工程查驗','甘特圖'],type:'智慧 BIM 模組'},
  gis3d:{title:'建築 3D GIS',category:'智慧 BIM 模組',summary:'結合 BIM 與 GIS 平台，提供建築空間定位、模型查詢及空間資訊整合應用。',desc:'適合多棟建物、基地、園區與維運應用，可在 3D 地圖環境中快速定位建築與設備，形成室內外整合視角。',features:['BIM 與 GIS 整合','基地與建物定位','空間資訊整合','可延伸到設備與維運應用'],pain:['多棟建物或園區缺乏整體定位視角。','室內模型與外部空間資訊分離。','維運單位難快速定位設備所在。'],related:['維運階段','設備管理','建築管理 BIM'],type:'智慧 BIM 模組'}
};

/* 內圈：作業端 —— 依日班／黃昏／夜班分段 */
/* labelAt：標籤角度刻意避開外圈管理端節點（節點在 45/90/135/225/270/315） */
const FM_OPS_BANDS = [
  { key:'day',   label:'日班', from:335, to:205, labelAt:112.5 },
  { key:'dusk',  label:'黃昏', from:205, to:275, labelAt:247.5 },
  { key:'night', label:'夜班', from:275, to:335, labelAt:292.5 }
];
const FM_OPS_NODES = [
  { id:'entryCluster',  label:['進場','人員'], angle:350 },
  { id:'safetyCluster', label:['職安','宣導'], angle: 35 },
  { id:'workCluster',   label:['施工','執行'], angle: 80 },
  { id:'checkCluster',  label:['查驗','巡檢'], angle:125 },
  { id:'trackCluster',  label:['追蹤','協調'], angle:170 },
  { id:'duskCluster',   label:['黃昏','收班'], angle:240 },
  { id:'nightCluster',  label:['夜間','點工'], angle:305 }
];

/* 中圈：管理端 —— 頂端留白放區塊標籤 */
const FM_MANAGE_RING = [
  { id:'dashboard',  label:'儀表板',   angle: 45 },
  { id:'gantt',      label:'甘特圖',   angle: 90 },
  { id:'approval',   label:'簽核專區', angle:135 },
  { id:'taskFollow', label:'工項追蹤', angle:225 },
  { id:'file',       label:'檔案管理', angle:270 },
  { id:'permission', label:'權限管理', angle:315 }
];

/* 外圈：建築生命週期 */
const FM_STAGES = [
  { key:'plan',    label:'規劃', from:-36, to: 36, chips:[['甘特圖','gantt'],['行事曆','gantt']] },
  { key:'design',  label:'設計', from: 36, to:108, chips:[['建築管理 BIM','buildingBim'],['檔案管理','file']] },
  { key:'build',   label:'施工', from:108, to:180, chips:[['建築管理 BIM','buildingBim'],['施工進度 BIM','progressBim'],['工程查驗','inspection'],['職安巡檢','safetyPatrol']] },
  { key:'finish',  label:'竣工', from:180, to:252, chips:[['建築管理 BIM','buildingBim'],['收工會議','closeMeeting'],['日報彙整','dailyReport'],['檔案管理','file']] },
  { key:'operate', label:'維運', from:252, to:324, chips:[['建築管理 BIM','buildingBim'],['建築 3D GIS','gis3d'],['設備管理','equipmentCheck']] }
];

/* 手機版：卡片式流程 */
const FM_MOBILE = [
  { title:'作業端｜日班 · 進場與開工前', tone:'day', items:['gate','attendance','safetyBriefing'] },
  { title:'作業端｜日班 · 施工、查驗、巡檢與追蹤', tone:'day', items:['startWork','inspection','safetyPatrol','equipmentCheck','todo','pdfMarkup','workMeeting','taskFollow','defectFollow'] },
  { title:'作業端｜黃昏 · 離場、收工會議與日報', tone:'dusk', items:['leaveSite','closeMeeting','dailyReport'] },
  { title:'作業端｜夜班 · 加班人力掌握', tone:'night', items:['overtime'] },
  { title:'管理端｜主管視角', tone:'manage', items:['dashboard','gantt','approval','file','permission'] },
  { title:'建築生命週期｜智慧 BIM', tone:'bim', items:['buildingBim','progressBim','gis3d'] }
];

/* 下方模組索引 */
const FM_INDEX = [
  { title:'工地現場作業流程', items:['gate','attendance','safetyBriefing','startWork','inspection','safetyPatrol','equipmentCheck','todo','pdfMarkup','workMeeting','taskFollow','defectFollow','leaveSite','closeMeeting','dailyReport','overtime'] },
  { title:'管理端／共用模組', items:['dashboard','gantt','approval','file','permission'] },
  { title:'智慧 BIM 模組', items:['buildingBim','progressBim','gis3d'] }
];

const FM_C = { x: 600, y: 470 };
const fmPolar = (r, deg) => {
  const rad = (deg - 90) * Math.PI / 180;
  return { x: FM_C.x + r * Math.cos(rad), y: FM_C.y + r * Math.sin(rad) };
};
const fmWedge = (r1, r2, from, to) => {
  let s = from, e = to;
  if (e < s) e += 360;
  const p1 = fmPolar(r2, s), p2 = fmPolar(r2, e), p3 = fmPolar(r1, e), p4 = fmPolar(r1, s);
  const large = (e - s) > 180 ? 1 : 0;
  return `M ${p1.x} ${p1.y} A ${r2} ${r2} 0 ${large} 1 ${p2.x} ${p2.y} L ${p3.x} ${p3.y} A ${r1} ${r1} 0 ${large} 0 ${p4.x} ${p4.y} Z`;
};

function FunctionMap({ open, onClose }) {
  const { useState: uS, useEffect: uE, useRef: uR } = React;
  const [active, setActive] = uS(null);
  const scrollRef = uR(null);

  // 開啟時鎖住底層捲動，並把捲軸拉回頂端
  uE(() => {
    if (!open) return;
    document.body.style.overflow = 'hidden';
    document.body.classList.add('is-pd-open');
    if (scrollRef.current) scrollRef.current.scrollTop = 0;
    return () => {
      document.body.style.overflow = '';
      document.body.classList.remove('is-pd-open');
    };
  }, [open]);

  // Esc：先關模組遮罩，再關整頁
  uE(() => {
    if (!open) return;
    const h = (e) => {
      if (e.key !== 'Escape') return;
      if (active) setActive(null); else onClose();
    };
    window.addEventListener('keydown', h);
    return () => window.removeEventListener('keydown', h);
  }, [open, active, onClose]);

  const mod = active ? FM_MODULES[active] : null;

  return (
    <div className={`lx-fm ${open ? 'is-open' : ''}`} ref={scrollRef}>
      <div className="lx-pd-head">
        <button className="lx-pd-close" onClick={onClose}>
          <span>← CLOSE</span>
          <span style={{ color:'var(--lx-ink-4)', fontWeight:400, letterSpacing:'1px' }}>ESC</span>
        </button>
        <div className="lx-pd-crumb">FUNCTION MAP &nbsp;/&nbsp; <span>功能地圖</span></div>
      </div>

      <header className="lx-fm-hero">
        <span className="lx-eyebrow">IPCS 智慧工地 × 智慧 BIM</span>
        <h1>工地的一天，就是一張功能地圖</h1>
        <p>
          從作業端的一天、管理端的視角，到建築生命週期三個角度，看見 IPCS 與智慧 BIM
          在每一個環節扮演的角色。點選任一項目，可查看功能說明、特色與它解決的現場痛點。
        </p>
        <div className="lx-fm-legend">
          <span><i className="is-site" />作業端｜日班 · 黃昏 · 夜班</span>
          <span><i className="is-manage" />管理端｜主管視角</span>
          <span><i className="is-stage" />建築生命週期</span>
        </div>
      </header>

      {/* ---------- 桌機：環狀圖 ---------- */}
      <div className="lx-fm-ring">
        <svg viewBox="170 40 860 860" role="img" aria-label="IPCS 功能地圖">
          {/* 外圈：生命週期 */}
          {FM_STAGES.map((s) => {
            const mid = (s.from + s.to) / 2;
            const p = fmPolar(378, mid);
            return (
              <g key={s.key} className="lx-fm-stage">
                <path d={fmWedge(340, 416, s.from, s.to)} />
                <text x={p.x} y={p.y + 7}>{s.label}</text>
              </g>
            );
          })}

          {/* 管理端圈：底色帶 + 標籤 */}
          <circle className="lx-fm-band is-manage" cx={FM_C.x} cy={FM_C.y} r={292} />
          <text className="lx-fm-bandlabel is-manage" x={FM_C.x} y={fmPolar(292, 0).y + 6}>管理端</text>

          {/* 作業端圈：日班／黃昏／夜班三段 */}
          {FM_OPS_BANDS.map((band) => {
            const p = fmPolar(235, band.labelAt);
            return (
              <g key={band.key} className={`lx-fm-opsband is-${band.key}`}>
                <path d={fmWedge(218, 252, band.from, band.to)} />
                <text x={p.x} y={p.y + 6}>{band.label}</text>
              </g>
            );
          })}

          {/* 管理端節點 */}
          {FM_MANAGE_RING.map((n) => {
            const p = fmPolar(292, n.angle);
            return (
              <g key={n.id} className="lx-fm-node is-manage" onClick={() => setActive(n.id)} role="button" tabIndex={0}
                onKeyDown={(e) => { if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); setActive(n.id); } }}>
                <rect x={p.x - 52} y={p.y - 19} width="104" height="38" rx="19" />
                <text x={p.x} y={p.y + 5}>{n.label}</text>
              </g>
            );
          })}

          {/* 作業端節點 */}
          {FM_OPS_NODES.map((n) => {
            const p = fmPolar(165, n.angle);
            return (
              <g key={n.id} className="lx-fm-node is-site"
                onClick={() => setActive(n.id)} role="button" tabIndex={0}
                onKeyDown={(e) => { if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); setActive(n.id); } }}>
                <circle cx={p.x} cy={p.y} r="46" />
                <text x={p.x} y={p.y - 3}>
                  <tspan x={p.x}>{n.label[0]}</tspan>
                  <tspan x={p.x} dy="19">{n.label[1]}</tspan>
                </text>
              </g>
            );
          })}

          {/* 中心 */}
          <g className="lx-fm-core">
            <circle cx={FM_C.x} cy={FM_C.y} r="96" />
            <text x={FM_C.x} y={FM_C.y - 10}>
              <tspan x={FM_C.x}>IPCS 智慧工地</tspan>
              <tspan x={FM_C.x} dy="26">×</tspan>
              <tspan x={FM_C.x} dy="26">智慧 BIM</tspan>
            </text>
          </g>
        </svg>

        <div className="lx-fm-stagecards">
          {FM_STAGES.map((s) =>
            <div key={s.key} className="lx-fm-stagecard">
              <h4>{s.label}階段</h4>
              <div className="lx-fm-chips">
                {s.chips.map(([label, id], i) =>
                  <button key={i} onClick={() => setActive(id)}>{label}</button>
                )}
              </div>
            </div>
          )}
        </div>
      </div>

      {/* ---------- 手機：卡片流程 ---------- */}
      <div className="lx-fm-cards">
        {FM_MOBILE.map((g) =>
          <section key={g.title} className={`lx-fm-cardgroup is-${g.tone}`}>
            <h3>{g.title}</h3>
            <div className="lx-fm-cardlist">
              {g.items.map((id) => {
                const m = FM_MODULES[id];
                if (!m) return null;
                return (
                  <button key={id} className="lx-fm-card" onClick={() => setActive(id)}>
                    <span className="lx-fm-card-cat">{m.category}</span>
                    <span className="lx-fm-card-title">{m.title}</span>
                    <span className="lx-fm-card-sum">{m.summary}</span>
                  </button>
                );
              })}
            </div>
          </section>
        )}
      </div>

      {/* ---------- 模組索引 ---------- */}
      <div className="lx-fm-index">
        {FM_INDEX.map((g) =>
          <section key={g.title}>
            <h3>{g.title}</h3>
            <div className="lx-fm-chips">
              {g.items.map((id) =>
                FM_MODULES[id] ? <button key={id} onClick={() => setActive(id)}>{FM_MODULES[id].title}</button> : null
              )}
            </div>
          </section>
        )}
      </div>

      {/* ---------- 模組說明遮罩 ---------- */}
      {mod &&
        <div className="lx-fm-modal" onClick={(e) => { if (e.target === e.currentTarget) setActive(null); }}>
          <div className="lx-fm-modal-panel" role="dialog" aria-modal="true" aria-label={mod.title}>
            <button className="lx-fm-modal-x" onClick={() => setActive(null)} aria-label="關閉">×</button>
            <div className="lx-fm-modal-kicker">{mod.category}<em>{mod.type}</em></div>
            <h2>{mod.title}</h2>
            <p className="lx-fm-modal-sum">{mod.summary}</p>
            <p className="lx-fm-modal-desc">{mod.desc}</p>
            <div className="lx-fm-modal-grid">
              <div>
                <h4>主要特色</h4>
                <ul>{mod.features.map((f, i) => <li key={i}>{f}</li>)}</ul>
              </div>
              <div className="is-pain">
                <h4>解決的痛點</h4>
                <ul>{mod.pain.map((f, i) => <li key={i}>{f}</li>)}</ul>
              </div>
            </div>
            <div className="lx-fm-modal-related">
              <h4>對應模組</h4>
              <div className="lx-fm-chips">
                {mod.related.map((r, i) => <span key={i}>{r}</span>)}
              </div>
            </div>
          </div>
        </div>
      }
    </div>
  );
}

window.FunctionMap = FunctionMap;
