// SoritalkApp — main orchestrator (CDN version, fetches from /api/...)
//
// inspect flow : 'home' → 'tabs' → 'items'
// record  flow : 'recordMain' → 'recordDetail' → 'recordDefects'
//
// API hooks (fire-and-forget, UI stays responsive even if offline):
//   - on mount           : GET  /api/buildings/:id            → building meta
//   - on submit category : POST /api/inspection/save          → persist values+notes
//   - on month change    : GET  /api/monthly/:id/:yyyy/:mm    → load past records
//   - on facilities save : POST /api/facilities               → persist facility list

function SoritalkApp({ user, onLogout }) {
  const selectorStyle = 'chip'; // locked: chip toggle (final pick)
  // SoritalkApp.jsx 상단 state 선언부
  const [filteredGroups, setFilteredGroups] = React.useState(window.INSPECTION_GROUPS || []);

  const [stage, setStage]             = React.useState('home');
  const [tab, setTab]                 = React.useState('home');
  const [activeGroup, setActiveGroup] = React.useState('g1');
  const [categoryId, setCategoryId]   = React.useState(null);
  const [values, setValues]           = React.useState({});
  const [etcMode, setEtcMode]         = React.useState(null);
  const [etcText, setEtcText]         = React.useState('');
  const [saving, setSaving]           = React.useState(false);
  const [toast, setToast]             = React.useState(null);

  // building meta — falls back to spec default
  const [building, setBuilding] = React.useState(
    (window.BUILDING) || { id: 'b1', name: '현장 건축물' }
  );

  // record flow state
  const today = new Date();
  const [recYear, setRecYear]     = React.useState(today.getFullYear());
  const [recMonth, setRecMonth]   = React.useState(today.getMonth() + 1);
  const [recGroupId, setRecGroupId] = React.useState('g1');
  const [recReports, setRecReports] = React.useState({
    inspectionDate: '', performedAt: '',
    reportMethod: '정보통신', reportTo: '',
    actionMethod: '', writtenBy: (user && user.name) || '',
    inspectorConfirmer: '', photos: [],
    actionDetailFire: '', actionDetailEvac: '', actionDetailFireSup: '', actionDetailEtc: '',
  });
  const [monthlyData, setMonthlyData] = React.useState(null);
  const [recentAlarms, setRecentAlarms] = React.useState([]);

  // myinfo
  const [account, setAccount]       = React.useState(
    user || (window.MY_ACCOUNT) || { name: '', email: '', phone: '' }
  );
  const [facilities, setFacilities] = React.useState(
    (window.DEFAULT_FACILITIES) || []
  );

  const [dir, setDir]           = React.useState(1);
  const [stageKey, setStageKey] = React.useState(0);

  // 시설 현황에 따른 점검 항목 동적 필터링 적용
  // SoritalkApp.jsx 내의 useMemo 블록
React.useMemo(() => {
  if (!window.ORIGINAL_INSPECTION_GROUPS && window.INSPECTION_GROUPS) {
    window.ORIGINAL_INSPECTION_GROUPS = JSON.parse(JSON.stringify(window.INSPECTION_GROUPS));
  }
  
  if (window.ORIGINAL_INSPECTION_GROUPS && facilities) {
    const filtered = JSON.parse(JSON.stringify(window.ORIGINAL_INSPECTION_GROUPS));
    
 const CATEGORY_FACILITY_MAP = {
  '소화기구 및 자동소화장치':                '1. 소화기구 및 자동소화장치',
  '옥내소화전설비':                          '2. 옥내외 소화전설비',
  '스프링클러설비·물분무소화설비':           '3. 스프링클러·물분무·포소화설비',
  '포소화설비':                              '3. 스프링클러·물분무·포소화설비',
  '이산화탄소·할론·분말소화설비':           '4. 이산화탄소·할론·분말소화설비',
  '자동화재탐지설비':                        '5. 자탐·비상경보·방송·속보',
  '자동화재탐지설비·비상경보설비·시각경보기':'5. 자탐·비상경보·방송·속보',
  '비상방송설비':                            '5. 자탐·비상경보·방송·속보',
  '자동화재속보설비':                        '5. 자탐·비상경보·방송·속보',
  '피난기구':                                '6. 피난기구·유도등·비상조명',
  '유도등·비상조명등':                       '6. 피난기구·유도등·비상조명',
  '유도등·유도표지·비상조명등':              '6. 피난기구·유도등·비상조명',
  '제연설비':                                '7. 제연설비',
  '피난·방화시설·방염':                      '10. 기타사항 점검표',
  '연결송수관설비':                          '8. 연결송수관·살수설비',
  '연결살수설비':                            '8. 연결송수관·살수설비',
  '비상콘센트설비':                          '9. 비상콘센트·무선통신',
  '무선통신보조설비·지하구':                 '9. 비상콘센트·무선통신',
  '위험물 저장·취급시설':                   '11. 위험물 저장·취급시설',
  '화기시설':                                '12. 화기시설',
  '가연성 가스시설':                         '13. 가연성 가스시설',
  '전기시설':                                '14. 전기시설',
};

    filtered.forEach(g => {
      if (g.categories) {
        g.categories = g.categories.filter(cat => {
          const fKey = CATEGORY_FACILITY_MAP[cat.name];
          if (fKey && facilities[fKey] === 'no') {
            console.log('[카테고리 숨김]', cat.name, '→', fKey);
            return false;
          }
          return true;
        });
      }
    });
    
    console.log('[facilities 전체]', JSON.stringify(facilities));
    console.log('[filtered 결과]', filtered.map(g => {
      const itemsCount = g.categories ? g.categories.reduce((acc, c) => acc + (c.items ? c.items.length : 0), 0) : 0;
      return g.id + ':' + itemsCount;
    }));

    // 핵심 수정: 전역 변수 업데이트 대신 State 업데이트!
    window.INSPECTION_GROUPS = filtered;
    setFilteredGroups(filtered); 
    console.log("🚀 [화면 갱신] 필터링된 데이터가 State에 반영되었습니다.");
  }
}, [facilities]);

  const fetchMe = React.useCallback(() => {
    return fetch('/api/auth/me', {
      headers: { 'Authorization': `Bearer ${localStorage.getItem('soritalk:token') || localStorage.getItem('token')}` }
    })
    .then(r => r.json())
    .then(me => {
      if (me && me.building) setBuilding(prev => ({ ...prev, ...me.building }));
      
      let rawFac = (me && me.facilities) ? me.facilities : (me && me.building?.facility_settings);
      setFacilities(rawFac || window.DEFAULT_FACILITIES || {});
      
      if (me && me.account) setAccount(me.account);
    }).catch(() => { /* keep defaults */ });
  }, []);

  // ── initial load: building meta ──
  React.useEffect(() => {
    fetchMe();
  }, [fetchMe]);

  const fetchMonthly = React.useCallback(() => {
    if (!building?.id) return;
    fetch(`/api/monthly/${recYear}/${recMonth}`, {
      headers: { 'Authorization': `Bearer ${localStorage.getItem('soritalk:token') || localStorage.getItem('token')}` }
    })
    .then(r => r.json())
    .then(data => {
      setMonthlyData(data);
      if (data?.report) {
        const r = data.report;
        setRecReports(prev => ({
          ...prev,
          inspectionDate:     r.inspection_date     || prev.inspectionDate,
          performedAt:        r.performed_at         || prev.performedAt,
          reportMethod: r.report_method || '정보통신',
          reportTo:           r.report_to            || prev.reportTo,
          actionMethod:       r.action_method        || prev.actionMethod,
          writtenBy:          r.written_by           || prev.writtenBy,
          inspectorConfirmer: r.inspector_confirmer  || prev.inspectorConfirmer,
          actionDetailFire:    r.action_detail_fire     || prev.actionDetailFire    || '',
          actionDetailEvac:    r.action_detail_evac     || prev.actionDetailEvac    || '',
          actionDetailFireSup: r.action_detail_fire_sup || prev.actionDetailFireSup || '',
          actionDetailEtc:     r.action_detail_etc      || prev.actionDetailEtc     || '',
        }));
      }
    })
    .catch(() => setMonthlyData(null));

    // 점검 탭의 '미점검' 상태 동기화를 위해 개별 항목 상태 패치 (values 동기화)
    fetch(`/api/items/${recYear}/${recMonth}`, {
      headers: { 'Authorization': `Bearer ${localStorage.getItem('soritalk:token') || localStorage.getItem('token')}` }
    })
    .then(r => r.json())
    .then(data => {
      const rows = Array.isArray(data) ? data : (data?.results ? Object.entries(data.results).map(([code, obj]) => ({ code, ...obj })) : []);
      const filteredResults = {};
      rows.forEach(row => {
        if (row.result === 'O' || row.result === 'X' || row.result === '/') {
          filteredResults[row.code] = row.result;
        }
      });
      // E-001 포함 DB에서 읽은 값 그대로 반영
      setValues(prev => {
        const next = { ...filteredResults };
        if (prev['E-002'] && !next['E-002']) next['E-002'] = prev['E-002'];
        ['E-G1','E-G2','E-G3'].forEach(k => { if (prev[k] && !next[k]) next[k] = prev[k]; });
        return next;
      });
    })
    .catch(() => {});
  }, [building?.id, recYear, recMonth]);

  React.useEffect(() => {
    if (['home', 'tabs', 'items', 'recordMain', 'recordDetail', 'recordDefects'].includes(stage)) {
      fetchMonthly();
    }
  }, [stage, fetchMonthly]);

  React.useEffect(() => {
    fetch('/api/alarms/recent', {
      headers: { 'Authorization': `Bearer ${localStorage.getItem('soritalk:token') || localStorage.getItem('token')}` }
    })
    .then(r => r.json())
    .then(data => {
       if (Array.isArray(data)) {
         setRecentAlarms(data.map(a => ({
           type: a.alarm_type,
           time: a.occurred_at ? new Date(a.occurred_at).toLocaleString('ko-KR', { month: 'short', day: 'numeric', hour: '2-digit', minute: '2-digit' }) : '',
           v: a.volt
         })));
       }
    })
    .catch(() => {});
  }, []);

  // ── stage nav ──
  const go = (next, direction = 1) => {
    setDir(direction); setStage(next); setStageKey(k => k + 1);
  };

  const toastTimerRef = React.useRef(null);

  const flash = React.useCallback((msg) => {
    setToast(msg);
    if (toastTimerRef.current) clearTimeout(toastTimerRef.current);
    toastTimerRef.current = setTimeout(() => setToast(null), 2400);
  }, []);

  const handleStart = () => { setTab('inspect'); go('tabs', 1); };
  const handlePickCategory = (groupId, catId) => {
    setActiveGroup(groupId); setCategoryId(catId); go('items', 1);
  };
  const handleBack = () => {
    if (stage === 'items')              go('tabs', -1);
    else if (stage === 'tabs')          go('home', -1);
    else if (stage === 'recordDetail')  go('recordMain', -1);
    else if (stage === 'recordDefects') go('recordDetail', -1);
    else if (stage === 'recordMain')   { setTab('home'); go('home', -1); }
    else if (stage === 'facilities')    go('myinfo', -1);
    else if (stage === 'myinfo')       { setTab('home'); go('home', -1); }
    else if (stage === 'memos')        { setTab('home'); go('home', -1); }
  };
  const handleNav = (id) => {
    setTab(id);
    if (id === 'home')         go('home', -1);
    else if (id === 'inspect') go('tabs', stage === 'home' ? 1 : -1);
    else if (id === 'record')  go('recordMain', 1);
    else if (id === 'memo')    go('memos', 1);
    else if (id === 'me')      go('myinfo', 1);
  };

  const setValue = (itemId, v) => {
    setValues(prev => ({ ...prev, [itemId]: v }));
    // O(양호), X(불량), /(해당없음) 즉시 DB 저장
    if (v === 'O' || v === 'X' || v === '/') {
      fetch('/api/items/save', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${localStorage.getItem('soritalk:token') || localStorage.getItem('token')}` },
        body: JSON.stringify({ year: recYear, month: recMonth, results: [{ code: itemId, result: v }] })
      }).catch(() => {});
    }
    // 복구(미점검으로) 시 DB에서 삭제
    if (v === '') {
      fetch('/api/items/save', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${localStorage.getItem('soritalk:token') || localStorage.getItem('token')}` },
        body: JSON.stringify({ year: recYear, month: recMonth, results: [{ code: itemId, result: '' }] })
      }).catch(() => {});
    }
  };

  const allGood = async () => {
    const grp = filteredGroups.find(g => g.id === activeGroup);
    const cat = grp && grp.categories.find(c => c.id === categoryId);
    if (!cat) return;

    const nonNaItems = cat.items.filter(it => values[it.id] !== '/');
    const isAllOk = nonNaItems.length > 0 && nonNaItems.every(it => values[it.id] === 'O');
    const targetResult = isAllOk ? '' : 'O';

    const allItems = nonNaItems.map(it => ({ code: it.id, result: targetResult }));
    setValues(prev => {
      const next = { ...prev };
      for (const it of nonNaItems) {
        if (prev[it.id] === '/') continue; // 해당없음 보호
        if (isAllOk) {
          delete next[it.id];
        } else {
          next[it.id] = 'O';
        }
      }
      return next;
    });

    try {
      const payload = {
        year: recYear, 
        month: recMonth,
        results: allItems
      };

      const res = await fetch('/api/items/save', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${localStorage.getItem('soritalk:token') || localStorage.getItem('token')}` },
        body: JSON.stringify(payload)
      });

      if (!res.ok) throw new Error('서버 응답 오류');

      fetch(`/api/monthly/${recYear}/${recMonth}`, {
        headers: { 'Authorization': `Bearer ${localStorage.getItem('soritalk:token') || localStorage.getItem('token')}` }
      }).then(r => r.json()).then(setMonthlyData).catch(() => {});
      flash(`${cat.name} 항목 전체가 ${isAllOk ? '미점검' : '양호'} 처리되었습니다.`);
    } catch (e) {
      alert('일괄 처리에 실패했습니다.');
    }
  };

  const handleEtcModeChange = (mode) => {
    setEtcMode(mode);
  };


  const handleSaveMemo = async (groupId, memoText) => {
    const groupName = groupId === 'g1' ? '소방시설' : groupId === 'g2' ? '피난방화시설' : groupId === 'g3' ? '화기취급감독' : '기타사항';
    try {
      const res = await fetch('/api/defects/direct', {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
          'Authorization': `Bearer ${localStorage.getItem('soritalk:token') || localStorage.getItem('token')}`
        },
        body: JSON.stringify({
          group_id: groupId,
          major_category: groupName,
          item_text: memoText,
          year: recYear,
          month: recMonth
        })
      });
      if (!res.ok) throw new Error('서버 응답 오류');
      fetch(`/api/monthly/${recYear}/${recMonth}`, {
        headers: { 'Authorization': `Bearer ${localStorage.getItem('soritalk:token') || localStorage.getItem('token')}` }
      }).then(r => r.json()).then(setMonthlyData).catch(() => {});
      flash(`직접 입력 사항이 저장되었습니다.`);
    } catch (e) {
      alert('저장에 실패했습니다.');
    }
  };

  const handleAllGoodCategory = async (groupId) => {
    const group = filteredGroups.find(g => g.id === groupId);
    if (!group) return;

    if (group.isFreeform || group.id === 'g4' || group.short === '기타사항') {
      // 기타사항: E-001 코드로 DB 저장
      const isEtcOk = values['E-001'] === 'O';
      const targetResult = isEtcOk ? '' : 'O';
      try {
        await fetch('/api/items/save', {
          method: 'POST',
          headers: {
            'Content-Type': 'application/json',
            'Authorization': `Bearer ${localStorage.getItem('soritalk:token') || localStorage.getItem('token')}`
          },
          body: JSON.stringify({ year: recYear, month: recMonth, results: [{ code: 'E-001', result: targetResult }] })
        });
        setValues(prev => ({ ...prev, 'E-001': targetResult || null }));
        flash(`기타사항이 ${isEtcOk ? '미점검' : '이상없음'} 처리되었습니다.`);
      } catch(e) {
        flash('기타사항 저장 실패: ' + e.message);
      }
      return;
    }

    let totalItems = 0;
    let okItems = 0;
    group.categories.forEach(c => {
      c.items.forEach(it => {
        if (values[it.id] === '/') return; // 해당없음 제외
        totalItems++;
        if (values[it.id] === 'O') okItems++;
      });
    });
    const isAllOk = totalItems > 0 && okItems === totalItems;
    const targetResult = isAllOk ? '' : 'O';

    try {
      const allItems = [];
      group.categories.forEach(c => {
        c.items.forEach(it => {
          if (values[it.id] === '/') return; // 해당없음 제외
          allItems.push({ code: it.id, result: targetResult });
        });
      });

      setValues(prev => {
        const next = { ...prev };
        allItems.forEach(it => {
          if (prev[it.code] === '/') return; // 해당없음 보호
          if (isAllOk) {
            delete next[it.code];
          } else {
            next[it.code] = 'O';
          }
        });
        return next;
      });

      const payload = {
        year: recYear, 
        month: recMonth,
        results: allItems
      };

      const res = await fetch('/api/items/save', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${localStorage.getItem('soritalk:token') || localStorage.getItem('token')}` },
        body: JSON.stringify(payload)
      });

      if (!res.ok) throw new Error('서버 응답 오류');

      fetch(`/api/monthly/${recYear}/${recMonth}`, {
        headers: { 'Authorization': `Bearer ${localStorage.getItem('soritalk:token') || localStorage.getItem('token')}` }
      }).then(r => r.json()).then(setMonthlyData).catch(() => {});
      flash(`${group.name} 하위 항목 전체가 ${isAllOk ? '미점검' : '양호'} 처리되었습니다.`);
    } catch (e) {
      alert(e.error || e.message || '일괄 처리에 실패했습니다.');
    }
  };

  // Persist the current category, then navigate back.
  const handleSubmitCategory = async () => {
    const grp = filteredGroups.find(g => g.id === activeGroup);
    const cat = grp && grp.categories.find(c => c.id === categoryId);
    if (!cat) { handleBack(); return; }

    const payload = {
      year: recYear, 
      month: recMonth,
      results: cat.items.map(it => {
        const v = values[it.id];
        return {
          code: it.id,
          result: v || '',
        };
      })
    };

    setSaving(true);
      try {
      const res = await fetch('/api/items/save', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${localStorage.getItem('soritalk:token') || localStorage.getItem('token')}` },
        body: JSON.stringify(payload)
      });
      if (!res.ok) throw new Error('서버 응답 오류');
      flash('저장되었습니다');
    } catch (e) {
      flash('오프라인 저장 · 나중에 동기화됩니다');
      try {
        const key = 'soritalk:pending';
        const q = JSON.parse(localStorage.getItem(key) || '[]');
        q.push(payload);
        localStorage.setItem(key, JSON.stringify(q));
      } catch (_) {}
    } finally {
      setSaving(false);
      handleBack();
      fetchMonthly();
    }
  };
  const handlePickRecordGroup = (gid) => { setRecGroupId(gid); go('recordDetail', 1); };
  const handleOpenDefectOnly = () => go('recordDefects', 1);
  const handleFixDefect = async (id) => {
    const defectItem = monthlyData?.defects?.find(d => d.id === id);
    if (!defectItem) return;

    setMonthlyData(prev => {
      if (!prev) return prev;
      const nextDefects = prev.defects.filter(d => d.id !== id);
      const nextStatus = { ...prev.status };
      const gKey = Object.keys(nextStatus).find(k => nextStatus[k].label === defectItem.group);
      if (gKey && nextStatus[gKey].bad > 0) {
        nextStatus[gKey].bad -= 1;
      }
      return { ...prev, defects: nextDefects, status: nextStatus };
    });
    setValues(prev => ({ ...prev, [id]: 'O' }));

    try {
      const payload = {
        building_id: building.id, year: recYear, month: recMonth,
        items: [{ id: id, result: 'O' }]
      };
      const res = await fetch('/api/items/save', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${localStorage.getItem('soritalk:token') || localStorage.getItem('token')}` },
        body: JSON.stringify(payload)
      });
      if (!res.ok) throw new Error();
      fetchMonthly();
      flash('양호 상태로 변경되었습니다.');
    } catch (e) {
      fetchMonthly();
      flash('처리 중 오류가 발생했습니다.');
    }
  };
  const handleDeleteDefect = async (id) => {
    const directId = String(id).replace('direct_', '');
    setMonthlyData(prev => {
      if (!prev) return prev;
      return { ...prev, defects: prev.defects.filter(d => d.id !== id) };
    });
    try {
      await fetch(`/api/defects/${directId}`, {
        method: 'DELETE',
        headers: { 'Authorization': `Bearer ${localStorage.getItem('soritalk:token') || localStorage.getItem('token')}` }
      });
      fetchMonthly();
    } catch (e) {
      fetchMonthly();
    }
  };

  const handleSaveReport = async (reports, resetSign = false) => {
    // 점검일/보고일시 비어있으면 오늘 날짜로 자동 채우기
    const todayStr = new Date().toISOString().slice(0, 10); // YYYY-MM-DD
    const filledReports = {
      ...reports,
      inspectionDate: reports.inspectionDate || todayStr,
      performedAt:    reports.performedAt    || todayStr,
    };
    // UI에도 반영
    if (!reports.inspectionDate || !reports.performedAt) {
      setRecReports(filledReports);
    }
    try {
      const res = await fetch(`/api/monthly/${recYear}/${recMonth}`, {
        method: 'PUT',
        headers: {
          'Content-Type': 'application/json',
          'Authorization': `Bearer ${localStorage.getItem('soritalk:token') || localStorage.getItem('token')}`
        },
        body: JSON.stringify({
          performed_at: filledReports.performedAt,
          written_by: filledReports.writtenBy,
          report_method: filledReports.reportMethod,
          report_to: filledReports.reportTo,
          action_method: filledReports.actionMethod,
          inspector_confirmer: filledReports.inspectorConfirmer,
          inspection_date: filledReports.inspectionDate,
          action_detail_fire:     filledReports.actionDetailFire    || null,
          action_detail_evac:     filledReports.actionDetailEvac    || null,
          action_detail_fire_sup: filledReports.actionDetailFireSup || null,
          action_detail_etc:      filledReports.actionDetailEtc     || null,
          reset_sign: resetSign || undefined,
        })
      });
      if (!res.ok) throw new Error();
      if (resetSign) {
        // 서명 초기화 후 SMS 자동 재발송 (모달 없이)
        try {
          const token = localStorage.getItem('soritalk:token') || localStorage.getItem('token');
          await fetch(`/api/monthly/${recYear}/${recMonth}/complete`, {
            method: 'POST',
            headers: { 'Content-Type': 'application/json', 'Authorization': 'Bearer ' + token },
            credentials: 'include',
          });
        } catch(e) {}
        // monthlyData 갱신 (서명 초기화 반영)
        fetch(`/api/monthly/${recYear}/${recMonth}`, {
          headers: { 'Authorization': `Bearer ${localStorage.getItem('soritalk:token') || localStorage.getItem('token')}` },
          credentials: 'include',
        }).then(r => r.json()).then(setMonthlyData).catch(() => {});
        flash('저장 완료. 관계인에게 재서명 요청 SMS를 발송했습니다.');
      } else {
        flash('보고 내용이 저장되었습니다.');
      }
    } catch (e) {
      alert('저장에 실패했습니다.');
    }
  };

  const handleSignComplete = () => {
    const token = localStorage.getItem('soritalk:token') || localStorage.getItem('token');
    fetch(`/api/monthly/${recYear}/${recMonth}`, {
      headers: { 'Authorization': `Bearer ${token}` },
      credentials: 'include',
    }).then(r => r.json()).then(setMonthlyData).catch(() => {});
  };

  const handleComplete = async () => {
    const ok = await new Promise(resolve => {
      const modal = document.createElement('div');
      modal.style.cssText = 'position:fixed;inset:0;background:rgba(0,0,0,0.6);display:flex;align-items:center;justify-content:center;z-index:9999';
      modal.innerHTML = `<div style='background:#1c2130;border:1px solid #30363d;border-radius:12px;padding:24px;width:290px;max-width:90vw'>
        <div style='font-weight:700;font-size:15px;margin-bottom:10px;color:#e6edf3'>안전관리 완료</div>
        <div style='font-size:13px;color:#8b949e;margin-bottom:20px;line-height:1.6'>관계인에게 점검완료를<br>SMS로 발송합니다.</div>
        <div style='display:flex;gap:8px'>
          <button id='c-no' style='flex:1;padding:10px;border:1px solid #30363d;border-radius:8px;background:#21262d;color:#8b949e;font-size:14px;cursor:pointer'>아니오</button>
          <button id='c-yes' style='flex:1;padding:10px;border:none;border-radius:8px;background:#1f6feb;color:#fff;font-size:14px;font-weight:700;cursor:pointer'>예</button>
        </div></div>`;
      document.body.appendChild(modal);
      modal.querySelector('#c-yes').onclick = () => { modal.remove(); resolve(true); };
      modal.querySelector('#c-no').onclick  = () => { modal.remove(); resolve(false); };
      modal.onclick = e => { if (e.target === modal) { modal.remove(); resolve(false); } };
    });
    if (!ok) return;
    try {
      const token = localStorage.getItem('soritalk:token') || localStorage.getItem('token');
      const res = await fetch('/api/monthly/' + recYear + '/' + recMonth + '/complete', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json', 'Authorization': 'Bearer ' + token }
      });
      const data = await res.json();
      if (data.success) {
        flash(data.phone ? '완료! ' + data.phone + '으로 SMS 발송했습니다.' : '완료 처리되었습니다.');
      } else {
        alert('완료 처리 실패: ' + (data.error || ''));
      }
    } catch(e) {
      alert('오류: ' + e.message);
    }
  };


  const handlePdf = async (kind) => {
    const token = localStorage.getItem('soritalk:token') || localStorage.getItem('token');
    const downloadBlob = async (url, filename) => {
      const res = await fetch(url, {
        headers: { 'Authorization': `Bearer ${token}` }
      });
      if (!res.ok) {
        const text = await res.text().catch(() => '');
        throw new Error(`HTTP ${res.status} ${text.slice(0, 100)}`);
      }
      const blob = await res.blob();
      const blobUrl = URL.createObjectURL(blob);
      const a = document.createElement('a');
      a.href = blobUrl;
      a.download = filename;
      document.body.appendChild(a);
      a.click();
      document.body.removeChild(a);
      setTimeout(() => URL.revokeObjectURL(blobUrl), 1000);
    };

    if (kind === 'work') {
      try {
        await downloadBlob(
          `/api/monthly/${recYear}/${recMonth}/pdf`,
          `${building?.name}_업무수행기록표_${recYear}년_${recMonth}월.pdf`
        );
      } catch (e) {
        alert('업무수행기록표 출력 실패: ' + e.message);
      }
    } else {
      try {
        const year = document.getElementById('outer-year-select')?.value || recYear;
        await downloadBlob(
          `/api/monthly/${year}/inspection-outer-html?month=${recMonth}`,
          `${building?.name}_외관점검표_${year}년.pdf`
        );
      } catch (e) {
        alert('외관점검표 출력 실패: ' + e.message);
      }
    }
  };

  const handleSaveFacilities = async (next) => {
    setFacilities(next);
    try {
      const res = await fetch('/api/buildings/facility-settings', {
        method: 'PUT',
        headers: {
          'Content-Type': 'application/json',
          'Authorization': `Bearer ${localStorage.getItem('soritalk:token') || localStorage.getItem('token')}`
        },
        body: JSON.stringify({ facility_settings: next })
      });
      
      if (!res.ok) {
        const errorData = await res.json().catch(() => ({})); 
        console.error("❌ 서버 거절 사유:", errorData);
        throw new Error(errorData.error || "저장 실패");
      }

      await fetchMe();
      flash('시설 현황이 저장되었습니다');
      handleBack();
    } catch (e) {
      flash('저장 실패: ' + e.message);
    }
  };

  const handleLogout = () => {
    API.auth.logout();
    onLogout && onLogout();
  };

  const grp = (filteredGroups || []).find(g => g.id === activeGroup);
  const cat = grp && grp.categories.find(c => c.id === categoryId);

  // ── header config per stage ──
  const headerCfg = (() => {
    if (stage === 'home')          return { title: building.name, subtitle: null, onLogout: handleLogout };
    if (stage === 'tabs')          return { title: '점검 기록', subtitle: `${building.name} · ${recYear}년 ${recMonth}월`, onBack: handleBack, onLogout: handleLogout };
    if (stage === 'items')         return { title: cat ? cat.name : '', subtitle: grp ? grp.name : '', onBack: handleBack };
    if (stage === 'recordMain')    return { title: '기록부', subtitle: building.name, onLogout: handleLogout };
    if (stage === 'recordDetail') {
      const g = filteredGroups.find(x => x.id === recGroupId);
      return {
        title: `${recYear}년 ${recMonth}월 기록부`,
        subtitle: `${g ? g.short : ''} · ${building.name}`,
        onBack: handleBack,
      };
    }
    if (stage === 'recordDefects') return { title: '불량 모아보기', subtitle: `${recYear}년 ${recMonth}월`, onBack: handleBack };
    if (stage === 'memos')         return { title: '메모', subtitle: building.name, onLogout: handleLogout };
    if (stage === 'myinfo')        return { title: '내 정보', subtitle: building.name, onLogout: handleLogout };
    if (stage === 'facilities')    return { title: '시설 현황 설정', subtitle: building.name, onBack: handleBack };
    return {};
  })();

  const itemsNa      = cat ? cat.items.filter(it => values[it.id] === '/').length : 0;
  const itemsDone    = cat ? cat.items.filter(it => values[it.id] === 'O' || values[it.id] === 'X').length : 0;
  const itemsTotal   = cat ? cat.items.length - itemsNa : 0;
  const itemsAllDone = itemsTotal >= 0 && itemsDone >= itemsTotal && (itemsDone > 0 || itemsNa > 0);
  const showSubmit   = stage === 'items';

  const statsData = React.useMemo(() => {
    const summary = (filteredGroups || []).map(g => {
      let sub = '미점검';
      let color = '#8a8880';

      // 직접입력 불량 건수
      const directBad = (monthlyData?.defects || []).filter(d => d.type === 'direct' && d.group === g.name).length;

      if (g.isFreeform || g.id === 'g4' || g.short === '기타사항') {
        const etcDirect = (monthlyData?.defects || []).filter(d => d.group === g.name).length;
        if (etcDirect > 0) {
          sub = `직접기록 ${etcDirect}건`;
          color = '#cc2222';
        } else if (values['E-001'] === 'O') {
          sub = '이상없음';
          color = '#15803d';
        } else {
          sub = '미점검';
          color = '#8a8880';
        }
      } else {
        let total = 0;
        let done = 0;
        let bad = 0;

        g.categories.forEach(c => {
          c.items.forEach(it => {
            const v = values[it.id];
            if (v !== '/') total++;
            if (v === 'O' || v === 'X') done++;
            if (v === 'X' || v === 'bad') bad++;
          });
        });

        const totalBad = bad + directBad;
        if (total > 0) {
          if (done === 0 && directBad === 0) {
            sub = `미점검 (0/${total} 항목)`;
            color = '#8a8880';
          } else if (done === total && totalBad === 0) {
            sub = '이상없음';
            color = '#15803d';
          } else if (totalBad > 0) {
            sub = `불량 ${totalBad}건 (${done}/${total} 항목)`;
            color = '#cc2222';
          } else {
            sub = `점검중 (${done}/${total} 항목)`;
            color = '#2563eb';
          }
        } else {
          sub = '해당없음'; color = '#8a8880';
        }
      }
      return { label: g.name, sub, color };
    });
    return { summary, alarms: recentAlarms };
  }, [filteredGroups, values, monthlyData, recentAlarms]);

  // 컴포넌트 로드 실패 시 앱 크래시 방지 안전 장치
  const SafeRecordMain = window.RecordMainScreen || (() => <div style={{padding: 30, textAlign: 'center', color: '#cc2222', fontWeight: 800}}>⚠️ RecordBook.jsx 파일이 index.html에 추가되지 않았습니다.</div>);
  const SafeRecordDetail = window.RecordDetailScreen || (() => <div style={{padding: 30, textAlign: 'center', color: '#cc2222', fontWeight: 800}}>⚠️ RecordBook.jsx 로드 실패</div>);
  const SafeDefectOnly = window.DefectOnlyScreen || (() => <div style={{padding: 30, textAlign: 'center', color: '#cc2222', fontWeight: 800}}>⚠️ RecordBook.jsx 로드 실패</div>);

  return (
    <div style={{
      minHeight: '100dvh', display: 'flex', flexDirection: 'column',
      background: THEME.surfaceBg, overflow: 'hidden',
    }}>
      <SoritalkHeader {...headerCfg} theme={THEME} />

      <div style={{ flex: 1, position: 'relative', overflow: 'hidden' }}>
        <div
          key={stageKey}
          style={{
            position: 'absolute', inset: 0, overflow: 'auto',
            animation: `stageSlide${dir > 0 ? 'Fwd' : 'Back'} 340ms cubic-bezier(.4,1.2,.4,1) both`,
            paddingBottom: showSubmit ? 84 : 0,
          }}
        >
          {stage === 'home' && (
            <HomeScreen theme={THEME}
              onStart={handleStart}
              onHistory={() => handleNav('record')}
              stats={statsData}
              onPhotoYear={recYear}
              onPhotoMonth={recMonth}
              onPhotoToken={localStorage.getItem('soritalk:token') || localStorage.getItem('token')}
            />
          )}
          {stage === 'tabs' && (
            <GroupTabsScreen theme={THEME}
              groups={filteredGroups}
              activeGroup={activeGroup}
              onGroupChange={setActiveGroup}
              values={values}
              etcMode={etcMode} etcText={etcText}
              onEtcMode={handleEtcModeChange} onEtcText={setEtcText}
              onPickCategory={handlePickCategory}
              onAllGoodCategory={(groupId) => handleAllGoodCategory(groupId)}
              onSaveMemo={handleSaveMemo}
            />
          )}
          {stage === 'items' && (
            <ItemsScreen theme={THEME}
              groups={filteredGroups}
              groupId={activeGroup} categoryId={categoryId}
              values={values}
              onChange={setValue}
              onAllGood={allGood}
              selectorStyle={selectorStyle}
            />
          )}
          {stage === 'recordMain' && (
        <SafeRecordMain theme={THEME}
              year={recYear} month={recMonth}
              onMonthChange={(y, m) => { setRecYear(y); setRecMonth(m); }}
              onPick={handlePickRecordGroup}
              onPdf={handlePdf}
              onFixDefect={handleFixDefect}
              onDeleteDefect={handleDeleteDefect}
              monthly={monthlyData}
              values={values}
              statsData={statsData.summary || []}
              reports={recReports}
              onReportsChange={setRecReports}
              onSaveReport={handleSaveReport} onComplete={handleComplete}
              onSignComplete={handleSignComplete}
              building={building}
              account={account}
            />
          )}
          {stage === 'recordDetail' && (
        <SafeRecordDetail theme={THEME}
              groupId={recGroupId}
              year={recYear} month={recMonth}
              reports={recReports}
              onReportsChange={setRecReports}
              onFixDefect={handleFixDefect}
              onDefectOnly={handleOpenDefectOnly}
              onPdf={handlePdf}
              monthly={monthlyData}
              values={values}
            />
          )}
          {stage === 'recordDefects' && (
        <SafeDefectOnly theme={THEME}
              onFix={handleFixDefect}
            />
          )}
          {stage === 'memos' && (
            <MemosScreen theme={THEME} />
          )}
          {stage === 'myinfo' && (
            <MyInfoScreen theme={THEME}
              building={building}
              account={account} onAccountChange={setAccount}
              facilities={facilities}
              onOpenFacilities={() => go('facilities', 1)}
              onLogout={handleLogout}
            />
          )}
          {stage === 'facilities' && (
            <FacilitySettingsScreen theme={THEME}
              facilities={facilities}
              onChange={setFacilities}
              onSave={(localFacilities) => handleSaveFacilities(localFacilities || facilities)}
            />
          )}
        </div>

        {showSubmit && (
          <div style={{
            position: 'absolute', left: 12, right: 12, bottom: 12, zIndex: 10,
            animation: 'fadeUp 400ms cubic-bezier(.4,1.2,.4,1)',
          }}>
            <button
              disabled={(!itemsDone && !itemsNa) || saving}
              onClick={handleSubmitCategory}
              style={{
                width: '100%', height: 52,
                background: itemsAllDone ? '#15803d' : (grp ? grp.accent : THEME.accent),
                color: '#fff', border: 'none', borderRadius: 14,
                fontSize: 15, fontWeight: 800, fontFamily: 'inherit',
                letterSpacing: '-0.01em',
                cursor: (itemsDone || itemsNa) ? 'pointer' : 'not-allowed',
                boxShadow: itemsAllDone
                  ? '0 6px 20px rgba(21,128,61,.32), 0 2px 6px rgba(0,0,0,.08)'
                  : '0 6px 20px rgba(0,0,0,.18)',
                display: 'flex', alignItems: 'center', justifyContent: 'center', gap: 8,
                transition: 'background 300ms, box-shadow 300ms',
                opacity: (itemsDone || itemsNa) ? (saving ? 0.7 : 1) : 0.5,
                WebkitTapHighlightColor: 'transparent',
              }}
            >
              {saving
                ? '저장 중…'
                : itemsAllDone
                  ? '이 설비군 저장 · 목록으로'
                  : itemsDone
                    ? `${itemsTotal - itemsDone}개 남음 · 계속 진행`
                    : '항목을 선택해주세요'}
              {!saving && (itemsDone > 0 || itemsNa > 0) && (
                <svg width="16" height="16" viewBox="0 0 24 24" fill="none">
                  <path d={itemsAllDone ? 'M5 12.5l5 5 9-10' : 'M5 12h14M13 6l6 6-6 6'}
                    stroke="#fff" strokeWidth="2.6" strokeLinecap="round" strokeLinejoin="round"/>
                </svg>
              )}
            </button>
          </div>
        )}

        {toast && (
          <div style={{
            position: 'absolute', left: '50%', bottom: 88,
            transform: 'translateX(-50%)', zIndex: 20,
            background: '#0F172A', color: '#fff',
            padding: '10px 16px', borderRadius: 999,
            fontSize: 13, fontWeight: 600, letterSpacing: '-0.01em',
            boxShadow: '0 8px 24px rgba(0,0,0,.25)',
            animation: 'fadeUp 200ms ease-out',
            display: 'flex', alignItems: 'center', gap: 12,
            whiteSpace: 'nowrap',
          }}>
            {toast}
          </div>
        )}
      </div>

      <SoritalkTabBar active={tab} onNav={handleNav} theme={THEME} isLocked={
        recYear < today.getFullYear() ||
        (recYear === today.getFullYear() && recMonth < today.getMonth() + 1) ||
        !!monthlyData?.report?.is_locked
      } />

      <style>{`
        @keyframes stageSlideFwd {
          from { opacity: 0; transform: translateX(24px); }
          to   { opacity: 1; transform: translateX(0); }
        }
        @keyframes stageSlideBack {
          from { opacity: 0; transform: translateX(-24px); }
          to   { opacity: 1; transform: translateX(0); }
        }
        @keyframes fadeUp {
          from { opacity: 0; transform: translateY(8px); }
          to   { opacity: 1; transform: translateY(0); }
        }
      `}</style>
    </div>
  );
}

window.SoritalkApp = SoritalkApp;