/* ============================================================
   外部連携（Integrations）— 外部サービスとの接続ハブ
   ・カレンダー/議事録/通知/ストレージ：各メンバーが自分のアカウントを連結
   ・案件ソース（レディクル）：ワークスペース共通・管理者のみ管理
   接続状態は DB に保存（user_integrations）
   ============================================================ */

/* サービスごとの詳細定義：設定項目とアクティビティログ */
const INTEG_DETAILS = {
  gcal: {
    accountLabel: t('integ.gcal.accountLabel'), accountPlaceholder: 'you@alion.jp',
    settings: [
      { key: 'autoCreate', type: 'toggle', label: t('integ.gcal.autoCreate.label'), desc: t('integ.gcal.autoCreate.desc'), def: true },
      { key: 'twoWay', type: 'toggle', label: t('integ.gcal.twoWay.label'), desc: t('integ.gcal.twoWay.desc'), def: true },
      { key: 'target', type: 'select', label: t('integ.gcal.target.label'), options: [t('integ.gcal.target.shared'), t('integ.gcal.target.personal')], def: t('integ.gcal.target.shared') },
      { key: 'reminder', type: 'select', label: t('integ.gcal.reminder.label'), options: [t('integ.reminder.15min'), t('integ.reminder.30min'), t('integ.reminder.1hour'), t('integ.reminder.none')], def: t('integ.reminder.15min') },
    ],
    log: [], // 実アクティビティのログ基盤が無いため、ダミー履歴は表示しない
  },
  fireflies: {
    accountLabel: t('integ.fireflies.accountLabel'), accountPlaceholder: t('integ.fireflies.accountPlaceholder'),
    settings: [
      { key: 'autoImport', type: 'toggle', label: t('integ.fireflies.autoImport.label'), desc: t('integ.fireflies.autoImport.desc'), def: true },
      { key: 'nextAction', type: 'toggle', label: t('integ.fireflies.nextAction.label'), desc: t('integ.fireflies.nextAction.desc'), def: true },
      { key: 'scope', type: 'select', label: t('integ.fireflies.scope.label'), options: [t('integ.fireflies.scope.all'), t('integ.fireflies.scope.mine')], def: t('integ.fireflies.scope.all') },
      { key: 'keywords', type: 'toggle', label: t('integ.fireflies.keywords.label'), desc: t('integ.fireflies.keywords.desc'), def: true },
    ],
    log: [], // ダミー履歴は表示しない
  },
  zoom: {
    accountLabel: t('integ.zoom.accountLabel'), accountPlaceholder: 'you@alion.jp',
    settings: [
      { key: 'autoLink', type: 'toggle', label: t('integ.zoom.autoLink.label'), desc: t('integ.zoom.autoLink.desc'), def: true },
      { key: 'recording', type: 'toggle', label: t('integ.zoom.recording.label'), desc: t('integ.zoom.recording.desc'), def: false },
    ],
    log: [],
  },
  slack: {
    accountLabel: t('integ.slack.accountLabel'), accountPlaceholder: '#sales-anken',
    settings: [
      { key: 'newCase', type: 'toggle', label: t('integ.notify.newCase.label'), desc: t('integ.slack.newCase.desc'), def: true },
      { key: 'assign', type: 'toggle', label: t('integ.slack.assign.label'), desc: t('integ.slack.assign.desc'), def: true },
      { key: 'remind', type: 'toggle', label: t('integ.notify.remind.label'), desc: t('integ.slack.remind.desc'), def: true },
      { key: 'meetingReminder', type: 'toggle', get label(){return window.t("label.extra55");}, desc: '商談日の前日18時に、担当者をSlackで@メンションして通知（商談時間・会議リンク・案件詳細URL）。Bot連携が必要', def: true },
    ],
    log: [], // ダミー履歴は表示しない
  },
  teams: {
    accountLabel: t('integ.teams.accountLabel'), accountPlaceholder: t('integ.teams.accountPlaceholder'),
    settings: [
      { key: 'newCase', type: 'toggle', label: t('integ.notify.newCase.label'), def: true },
      { key: 'remind', type: 'toggle', label: t('integ.notify.remind.label'), def: true },
    ],
    log: [],
  },
  gdrive: {
    accountLabel: t('integ.gcal.accountLabel'), accountPlaceholder: 'you@alion.jp',
    settings: [
      { key: 'folder', type: 'select', label: t('integ.gdrive.folder.label'), options: [t('integ.gdrive.folder.cases'), t('integ.gdrive.folder.proposals'), t('integ.gdrive.folder.myDrive')], def: t('integ.gdrive.folder.cases') },
      { key: 'autoShare', type: 'toggle', label: t('integ.gdrive.autoShare.label'), desc: t('integ.gdrive.autoShare.desc'), def: true },
    ],
    log: [],
  },
  source: {
    // ReadyCrew はサーバー巡回ではなくブラウザのブックマークレットで取込む実装。
    // 架空の「定時スクレイパー設定（ログインID/巡回間隔/自動登録…）」は廃止し、案内パネル（isSource）で実機能へ誘導する。
    accountLabel: t('integ.source.accountLabel'), accountPlaceholder: 'alion.partner.readycrew.cloud',
    settings: [],
    log: [],
  },
};

/* 詳細モーダル内の設定1行 */
function IntegSettingRow({ s, value, onChange }) {
  return (
    <div style={{ display: 'flex', alignItems: 'center', gap: 14, padding: '11px 0', borderBottom: '1px solid #f4f5f7' }}>
      <div style={{ flex: 1, minWidth: 0 }}>
        <div style={{ fontSize: 13, fontWeight: 600, color: '#2b2f38' }}>{s.label}</div>
        {s.desc && <div style={{ fontSize: 12, color: '#9aa1ab', marginTop: 2 }}>{s.desc}</div>}
      </div>
      {s.type === 'toggle'
        ? <Toggle on={value} onClick={() => onChange(!value)} />
        : s.type === 'text'
          ? <div style={{ width: 200 }}><TextInput value={value || ''} onChange={(e) => onChange(e.target.value)} /></div>
          : <div style={{ width: 200 }}><SelectInput value={value} onChange={(e) => onChange(e.target.value)} options={s.options} /></div>}
    </div>
  );
}

/* OpenAI 詳細：API キーはサーバー（Railway 環境変数）で管理されるため、状態表示のみ */
function OpenAIDetailModal({ item, onClose }) {
  const on = !!(window.__APP_CFG && window.__APP_CFG.openai);
  return (
    <Modal open onClose={onClose} width={560} title={null}
      footer={<Button variant="subtle" onClick={onClose}>{t('btn.close')}</Button>}>
      <div style={{ display: 'flex', alignItems: 'center', gap: 14, marginBottom: 14 }}>
        <div style={{ width: 50, height: 50, borderRadius: 13, background: item.color, display: 'flex', alignItems: 'center', justifyContent: 'center', flex: '0 0 auto', boxShadow: '0 2px 8px ' + item.color + '44' }}>
          <Icon name="spark" size={24} stroke={2} fill="#fff" style={{ color: '#fff' }} />
        </div>
        <div style={{ flex: 1 }}>
          <div style={{ fontSize: 17, fontWeight: 700, color: '#1c1f26', display: 'flex', alignItems: 'center', gap: 8 }}>
            OpenAI
            <span style={{ fontSize: 12, fontWeight: 700, color: item.color, background: item.color + '18', padding: '1px 7px', borderRadius: 4 }}>AI</span>
            <span style={{ fontSize: 12, fontWeight: 700, color: '#7b828d', background: '#f0f1f4', padding: '1px 7px', borderRadius: 4 }}>{t('integ.adminManaged')}</span>
          </div>
          <div style={{ fontSize: 12, marginTop: 3, display: 'inline-flex', alignItems: 'center', gap: 6, color: on ? '#16a34a' : '#9aa1ab', fontWeight: 600 }}>
            <span style={{ width: 7, height: 7, borderRadius: '50%', background: on ? '#16a34a' : '#cbd0d7' }} />
            {on ? t('integ.openai.connectedKeySet') : t('integ.notConfigured')}
          </div>
        </div>
      </div>
      <div style={{ fontSize: 13, color: '#6b727c', lineHeight: 1.7, paddingBottom: 14, borderBottom: '1px solid #f0f1f4' }}>
        {t('integ.openai.desc')}
      </div>
      <div style={{ margin: '16px 0 4px', padding: '14px 16px', background: '#f8f9fb', borderRadius: 10, fontSize: 12.5, color: '#5b626d', lineHeight: 1.7 }}>
        {t('integ.openai.keyStorageNote')}
        <div style={{ marginTop: 6 }}>{t('integ.openai.modelLabel')}<b style={{ color: '#2b2f38' }}>gpt-5-mini</b>{t('integ.openai.modelSuffix')}</div>
      </div>
    </Modal>
  );
}

/* Google カレンダー取込みブックマークレット：月表示の予定（レディクル/発注ナビ/顧客名一致）を push */
function buildGcalImportScript(apiBase, token) {
  return `(async function(){try{
var API=${JSON.stringify(apiBase)},H={'Content-Type':'application/json',Authorization:'Bearer '+${JSON.stringify(token)}};
if(location.hostname.indexOf('calendar.google')<0){alert(${JSON.stringify(t('integ.gcalImport.runInMonthView'))});return}
var bs=await fetch(API+'/api/bootstrap',{headers:H});
if(bs.status===401){alert(${JSON.stringify(t('integ.gcalImport.authExpired'))});return}
var data=await bs.json();
var norm=function(s){return (s||'').replace(/株式会社|（株）|\\(株\\)|\\s+/g,'')};
var cores=data.customers.map(function(cu){return norm(cu.company)}).filter(function(s){return s.length>=3});
var hit=function(title){if(/レディクル|発注ナビ/.test(title))return true;var h=norm(title);
 for(var i=0;i<cores.length;i++){var co=cores[i];if(h.indexOf(co)>=0||(co.length>=4&&h.indexOf(co.slice(0,4))>=0))return true;}return false;};
var p=function(n){return ('0'+n).slice(-2)};
var parse=function(s){
 var dm=s.match(/(\\d{4})年\\s*(\\d{1,2})月\\s*(\\d{1,2})日/);if(!dm)return null;
 var tm=s.match(/(上午|下午|午前|午後)\\s*(\\d{1,2})(?:[:：點時](\\d{0,2}))?[^\\d]{0,4}(?:至|~|～|〜)\\s*(上午|下午|午前|午後)?\\s*(\\d{1,2})(?:[:：點時](\\d{0,2}))?/);
 if(!tm)return null;
 var h=parseInt(tm[2],10);if((tm[1]==='下午'||tm[1]==='午後')&&h<12)h+=12;if((tm[1]==='上午'||tm[1]==='午前')&&h===12)h=0;
 var mi=tm[3]==='半'?30:(parseInt(tm[3],10)||0);
 var eh=tm[5]?parseInt(tm[5],10):null;if(eh!=null&&(tm[4]==='下午'||tm[4]==='午後')&&eh<12)eh+=12;
 var emi=tm[6]==='半'?30:(parseInt(tm[6],10)||0);
 // タイトル：、または ，区切りの2番目の要素を採用（1番目は時間表記）
 var parts=s.split(/，|、/);var title='';
 for(var i2=1;i2<parts.length;i2++){var t2=parts[i2].trim();if(t2&&!/^(上午|下午|午前|午後|\\d)/.test(t2)){title=t2;break;}}
 if(!title)return null;
 var d=dm[1]+'-'+p(dm[2])+'-'+p(dm[3]);
 return {title:title.slice(0,200),datetime:d+'T'+p(h)+':'+p(mi),end:eh!=null?d+'T'+p(eh)+':'+p(emi):null};
};
var out={};
document.querySelectorAll('[data-eventid]').forEach(function(el){
 var ev=parse(el.textContent||'');if(!ev||!hit(ev.title))return;
 var uid=(el.getAttribute('data-eventid')||'').split(' ')[0]||ev.title+ev.datetime;
 out[uid]=Object.assign({uid:uid},ev);
});
var evs=Object.keys(out).map(function(k){return out[k]});
if(!evs.length){alert(${JSON.stringify(t('integ.gcalImport.noEvents'))});return}
var r=await fetch(API+'/api/gcal-events/push',{method:'POST',headers:H,body:JSON.stringify({events:evs,replace:false})}).then(function(r){return r.json()});
alert(${JSON.stringify(t('integ.gcalImport.importedPrefix'))}+r.count+${JSON.stringify(t('integ.gcalImport.importedSuffix'))});
}catch(e){alert(${JSON.stringify(t('integ.gcalImport.failedPrefix'))}+(e&&e.message||e))}})();`;
}

/* Google カレンダー詳細：ワンクリック接続。既定はサーバー側オフライン同期（2026-08-27）。
   旧方式（ブラウザの GIS トークン）は約1時間で失効し、人がボタンを押さない限り取込が止まったため、
   Gmail と同じ code popup で refresh_token をサーバーに預ける方式を主経路にした。詳細設定は折りたたみ */
function GcalDetailModal({ item, onClose }) {
  const { currentUser, loginAs, showToast } = useStore();
  const [srv, setSrv] = React.useState(null); // /api/gcal/status の結果（サーバーが唯一の真実）
  const [busy, setBusy] = React.useState(false);
  const [ready, setReady] = React.useState(false);
  const [showAdvanced, setShowAdvanced] = React.useState(false);
  const loadStatus = React.useCallback(() => { API.gcalServerStatus().then(setSrv).catch(() => setSrv({ serverReady: false })); }, []);
  React.useEffect(() => { loadStatus(); }, [loadStatus]);
  // モーダルを開いた時点で GIS を準備（クリック前に済ませる＝ポップアップブロック回避）
  React.useEffect(() => {
    let live = true;
    window.gcalPrepare().then(() => { if (live) setReady(true); }).catch(() => { if (live) setReady(true); });
    return () => { live = false; };
  }, []);
  const serverMode = !!(srv && srv.serverReady);
  const serverSync = !!(srv && srv.serverSync);
  const legacyConnected = !!(window.gcalAutoEnabled && window.gcalAutoEnabled());
  const hasToken = !!(window.gcalHasToken && window.gcalHasToken());
  const connected = serverMode ? (serverSync || !!(srv && srv.lastSyncAt) || legacyConnected) : legacyConnected;
  // 「本当に自動同期できているか」を正直に3段階で出す
  const syncing = serverMode ? serverSync : (legacyConnected && hasToken);
  const stale = connected && !syncing;
  const stColor = syncing ? '#16a34a' : stale ? '#d97706' : '#9aa1ab';
  const stDot = syncing ? '#16a34a' : stale ? '#d97706' : '#cbd0d7';
  const stLabel = syncing ? (serverMode ? t('integ.gcal.serverSyncing') : t('integ.gcal.connectedAutoSync'))
    : stale ? t('integ.gcal.reauthNeeded') : t('integ.notConnected');
  const fmtLast = (() => {
    // サーバー由来は JST 'YYYY-MM-DDTHH:mm'、旧経路は ISO(UTC)。混ぜると9時間ずれるので経路ごとに扱う
    const sv = srv && srv.lastSyncAt;
    if (serverMode && sv) return `${String(sv).slice(5, 7)}/${String(sv).slice(8, 10)} ${String(sv).slice(11, 16)}`;
    const lastSync = (window.gcalLastSync && window.gcalLastSync()) || null;
    if (!lastSync) return null;
    const d = new Date(lastSync); if (isNaN(d)) return null;
    try { return new Intl.DateTimeFormat('ja-JP', { timeZone: 'Asia/Tokyo', month: 'numeric', day: 'numeric', hour: '2-digit', minute: '2-digit', hour12: false }).format(d); } catch (_) { return null; }
  })();
  const rehydrate = async () => { try { const data = await API.bootstrap(); window.hydrateAppData(data); loginAs(currentUser.id); } catch (_) {} };
  const connect = async () => {
    if (busy) return; setBusy(true);
    try {
      if (serverMode) {
        await window.gcalConnectOffline();          // 一度きり。以後はサーバーが15分ごとに取り込む
        showToast(t('integ.gcal.serverConnected'));
        loadStatus();
        setTimeout(() => { rehydrate(); }, 3500);   // 連携直後の初回同期を待ってから反映
      } else {
        const n = await window.syncGoogleCalendar(true); // クリック直後・同期で認証ダイアログを開く
        await rehydrate();
        showToast(t('integ.gcal.connected', { n }));
        onClose();
      }
    } catch (e) { showToast(e.message, 'x'); }
    setBusy(false);
  };
  return (
    <Modal open onClose={onClose} width={480} title={null}
      footer={<Button variant="subtle" onClick={onClose}>{t('btn.close')}</Button>}>
      <div style={{ display: 'flex', alignItems: 'center', gap: 14, marginBottom: 16 }}>
        <div style={{ width: 50, height: 50, borderRadius: 13, background: '#f4f5f7', display: 'flex', alignItems: 'center', justifyContent: 'center', flex: '0 0 auto' }}>
          <Icon name="google" size={26} />
        </div>
        <div style={{ flex: 1 }}>
          <div style={{ fontSize: 17, fontWeight: 700, color: '#1c1f26' }}>{t('integ.gcal.name')}</div>
          <div style={{ fontSize: 12, marginTop: 3, display: 'inline-flex', alignItems: 'center', gap: 6, color: stColor, fontWeight: 600 }}>
            <span style={{ width: 7, height: 7, borderRadius: '50%', background: stDot }} />
            {stLabel}
            {fmtLast && <span style={{ color: '#9aa1ab', fontWeight: 500 }}>· {t('integ.gcal.lastSyncLabel')} {fmtLast}</span>}
          </div>
        </div>
      </div>

      <div style={{ fontSize: 12.5, color: '#6b727c', lineHeight: 1.7, marginBottom: stale ? 12 : 16 }}>
        {serverMode ? t('integ.gcal.serverDesc')
          : <React.Fragment>{t('integ.gcal.detailDescPre')}<b>{t('integ.gcal.detailDescBold')}</b>{t('integ.gcal.detailDescPost')}</React.Fragment>}
      </div>

      {stale && (
        <div style={{ display: 'flex', alignItems: 'flex-start', gap: 9, padding: '10px 13px', background: '#fffaf0', border: '1px solid #f3e7c9', borderRadius: 10, marginBottom: 16 }}>
          <Icon name="alert" size={15} stroke={2} style={{ color: '#b45309', flex: '0 0 auto', marginTop: 1 }} />
          <div style={{ fontSize: 12, color: '#8a6d1f', lineHeight: 1.6 }}>{serverMode ? t('integ.gcal.serverHint') : t('integ.gcal.staleHint')}</div>
        </div>
      )}

      {/* メイン：ワンクリック接続（サーバー同期が使える環境では失効しない恒久接続） */}
      <div style={{ display: 'flex', gap: 10, alignItems: 'center' }}>
        <Button variant="primary" size="lg" icon="google" onClick={connect} disabled={busy || !ready} style={{ flex: connected ? 'none' : 1 }}>
          {busy ? t('integ.connecting') : !ready ? t('integ.preparing')
            : serverMode ? (serverSync ? t('integ.gcal.serverReconnect') : t('integ.gcal.serverConnect'))
            : connected ? t('integ.gcal.resyncNow') : t('integ.gcal.connectWithGoogle')}
        </Button>
        {serverMode && serverSync && (
          <Button variant="subtle" disabled={busy} onClick={async () => {
            setBusy(true);
            try { const r = await API.gcalSyncNow(); await rehydrate(); showToast(t('integ.gcal.connected', { n: r.count || 0 })); loadStatus(); }
            catch (e) { showToast(e.message || t('gcal.stale.failed'), 'x'); }
            setBusy(false);
          }}>{t('integ.gcal.resyncNow')}</Button>
        )}
        {connected && !serverMode && <Button variant="subtle" onClick={() => { window.gcalDisableAuto(); showToast(t('integ.gcal.autoSyncStopped'), 'x'); onClose(); }}>{t('integ.stop')}</Button>}
        {serverMode && serverSync && (
          <Button variant="subtle" disabled={busy} onClick={async () => {
            try { await API.gcalDisconnect(); } catch (e) { showToast(e.message, 'x'); return; }
            window.gcalDisableAuto(); showToast(t('integ.gcal.autoSyncStopped'), 'x'); onClose();
          }}>{t('integ.stop')}</Button>
        )}
      </div>

      {/* 詳細設定（iCal / ブックマークレット）は折りたたみ */}
      <button onClick={() => setShowAdvanced(s => !s)}
        style={{ display: 'inline-flex', alignItems: 'center', gap: 5, marginTop: 16, border: 'none', background: 'transparent', color: '#9aa1ab', fontSize: 12, fontWeight: 600, cursor: 'pointer', fontFamily: 'inherit', padding: 0 }}>
        <Icon name={showAdvanced ? 'chevronDown' : 'chevronRight'} size={13} stroke={2.2} />{t('integ.gcal.advancedToggle')}
      </button>
      {showAdvanced && (
        <div style={{ marginTop: 10, padding: '12px 14px', background: '#f8f9fb', borderRadius: 10, fontSize: 12, color: '#7b828d', lineHeight: 1.7 }}>
          {t('integ.gcal.advancedHelp')}
        </div>
      )}
    </Modal>
  );
}

/* Gmail サーバー側 自動同期：各ユーザーが一度連携すれば、毎日1回サーバーが担当案件の顧客メールを自動取得 */
function GmailDetailModal({ item, onClose }) {
  const { currentUser, loginAs, showToast } = useStore();
  const [st, setSt] = React.useState(null);
  const [busy, setBusy] = React.useState(false);
  const [syncing, setSyncing] = React.useState(false);
  const loadStatus = React.useCallback(() => { API.gmailServerStatus().then(setSt).catch(() => setSt({ connected: false })); }, []);
  React.useEffect(() => { loadStatus(); }, [loadStatus]);
  const connected = !!(st && st.connected);
  const serverReady = !st || st.serverReady !== false;
  const rehydrate = async () => { try { const d = await API.bootstrap(); window.hydrateAppData(d); loginAs(currentUser.id); } catch (_) {} };
  const connect = async () => {
    if (busy) return; setBusy(true);
    try {
      const r = await window.gmailConnectOffline();
      showToast('Gmail を連携しました' + (r && r.account ? '（' + r.account + '）' : '') + '。これから毎日自動で同期されます');
      loadStatus();
      setTimeout(rehydrate, 4000); // 初回同期が裏で走るので少し待ってメールを反映
    } catch (e) { showToast(e.message || 'Gmail 連携に失敗しました', 'x'); }
    setBusy(false);
  };
  const syncNow = async () => {
    if (syncing) return; setSyncing(true);
    try { const r = await API.gmailServerSyncNow(); await rehydrate(); showToast('同期しました（' + (r.count || 0) + '件）'); loadStatus(); }
    catch (e) { showToast(e.message || '同期に失敗しました', 'x'); }
    setSyncing(false);
  };
  const disconnect = async () => {
    try { await API.gmailServerDisconnect(); showToast(window.t("label.extra56"), 'x'); loadStatus(); }
    catch (e) { showToast(e.message, 'x'); }
  };
  return (
    <Modal open onClose={onClose} width={480} title={null}
      footer={<Button variant="subtle" onClick={onClose}>{t('btn.close')}</Button>}>
      <div style={{ display: 'flex', alignItems: 'center', gap: 14, marginBottom: 16 }}>
        <div style={{ width: 50, height: 50, borderRadius: 13, background: '#f4f5f7', display: 'flex', alignItems: 'center', justifyContent: 'center', flex: '0 0 auto' }}>
          <Icon name="mail" size={24} stroke={2} style={{ color: '#EA4335' }} />
        </div>
        <div style={{ flex: 1 }}>
          <div style={{ fontSize: 17, fontWeight: 700, color: '#1c1f26' }}>{window.t("extra.gmail.autoSync")}</div>
          <div style={{ fontSize: 12, marginTop: 3, display: 'inline-flex', alignItems: 'center', gap: 6, color: connected ? '#16a34a' : '#9aa1ab', fontWeight: 600 }}>
            <span style={{ width: 7, height: 7, borderRadius: '50%', background: connected ? '#16a34a' : '#cbd0d7' }} />
            {connected ? (window.t("extra.common.connected") + (st.account ? '（' + st.account + '）' : '')) : window.t("integ.notConnected")}
          </div>
        </div>
      </div>
      <div style={{ fontSize: 12.5, color: '#6b727c', lineHeight: 1.7, marginBottom: 16 }}>{window.t("extra.gmail.scope")}<b>{window.t("extra.gmail.daily")}</b>{window.t("extra.gmail.dailyHint")}</div>
      {!serverReady && (
        <div style={{ marginBottom: 14, padding: '11px 14px', background: '#fdf0db', border: '1px solid #f5d9a8', borderRadius: 10, fontSize: 12.5, color: '#92600c', lineHeight: 1.7 }}>{window.t("extra.integration.serverPrefix")}<span style={{ fontFamily: 'var(--mono)' }}>GOOGLE_CLIENT_SECRET</span>{window.t("extra.integration.missingEnvSuffix")}</div>
      )}
      {connected && st.needsReconnect && (
        <div style={{ marginBottom: 14, padding: '11px 14px', background: '#fdecec', border: '1px solid #f3cdcd', borderRadius: 10, fontSize: 12.5, color: '#b91c1c', lineHeight: 1.7 }}>{window.t("extra.integration.expired")}</div>
      )}
      <div style={{ display: 'flex', gap: 10, alignItems: 'center', flexWrap: 'wrap' }}>
        <Button variant="primary" size="lg" icon="mail" onClick={connect} disabled={busy || !serverReady} style={{ flex: connected ? 'none' : 1 }}>
          {busy ? window.t("extra.integration.connecting") : connected ? window.t("extra.integration.reconnect") : window.t("extra.gmail.enable")}
        </Button>
        {connected && <Button variant="default" size="lg" icon="refresh" onClick={syncNow} disabled={syncing}>{syncing ? window.t("gcal.stale.syncing") : window.t("btn.refresh")}</Button>}
        {connected && <Button variant="subtle" onClick={disconnect}>{window.t("btn.remove")}</Button>}
      </div>
      {connected && st.lastSyncAt && (
        <div style={{ marginTop: 12, fontSize: 12, color: '#9aa1ab' }}>{window.t("extra.integration.lastSync")}{String(st.lastSyncAt).replace('T', ' ')}</div>
      )}
    </Modal>
  );
}

/* freee 連携詳細：管理者がワンクリックで自社 freee を OAuth 連結し、見積書作成の既定（事業所・テンプレート）を設定 */
const FREEE_REDIRECT_URI = 'https://api-production-c40f.up.railway.app/api/freee/oauth/callback';
/* Canva 連携（ワークスペース共通）。提案書デザインの自動作成と、過去提案の学習に使う。
   開発モードの統合は「承認した本人のCanva」に紐づくため、実際にデザインを仕上げる担当者のアカウントで接続する。 */
function CanvaDetailModal({ item, onClose }) {
  const { showToast } = useStore();
  const [st, setSt] = React.useState(null);
  const [busy, setBusy] = React.useState(false);
  const refresh = React.useCallback(async () => { try { setSt(await API.canvaStatus()); } catch (e) { setSt({ error: true }); } }, []);
  React.useEffect(() => { refresh(); const id = setInterval(refresh, 5000); return () => clearInterval(id); }, [refresh]);
  const connect = async () => {
    if (busy) return; setBusy(true);
    try {
      const r = await API.canvaAuthUrl();
      const w = window.open(r.url, '_blank');
      if (!w) showToast(window.t("label.extra57"), 'x');
      else showToast(window.t("label.extra58"));
    } catch (e) { showToast((e && e.message) || '接続を開始できませんでした', 'x'); }
    setBusy(false);
  };
  const disconnect = async () => {
    if (!window.confirm(window.t("label.extra59"))) return;
    try { await API.canvaDisconnect(); showToast(window.t("label.extra60")); refresh(); } catch (e) { showToast(e.message, 'x'); }
  };
  const connected = !!(st && st.connected);
  return (
    <Modal open onClose={onClose} width={620} title={window.t("extra.canva.title")}>
      <div style={{ padding: '4px 2px 8px' }}>
        {!st ? <div style={{ fontSize: 12.5, color: '#9aa1ab' }}>{window.t("integ.checking")}</div> : st.error ? <div style={{ fontSize: 12.5, color: '#c0392b' }}>{window.t("extra.integration.statusFailed")}</div> : !st.configured ? (
          <div style={{ background: '#fef7e8', border: '1px solid #f5e5c0', borderRadius: 10, padding: '12px 14px', fontSize: 12.5, color: '#9a6b15', lineHeight: 1.8 }}>{window.t("extra.canva.missingConfig")}</div>
        ) : (st.tokenError || (st.shared && st.shared.tokenError)) ? (
          /* 失効（リフレッシュトークンの lineage 切れ等）。以前はここでも緑の「接続中」を出していたため、
             実際には23日間止まっていたのに画面上は正常に見えていた（2026-08-31 修正）。 */
          <div style={{ display: 'flex', alignItems: 'center', gap: 10, padding: '12px 14px', background: '#fdecea', border: '1px solid #f5c6c0', borderRadius: 10, flexWrap: 'wrap' }}>
            <Icon name="alert" size={16} stroke={2.4} style={{ color: '#c0392b', flex: '0 0 auto' }} />
            <div style={{ flex: 1, minWidth: 200 }}>
              <div style={{ fontSize: 13, fontWeight: 700, color: '#a5342a' }}>{window.t("extra.canva.revoked")}{st.account || (st.shared && st.shared.account) || window.t("extra.canva.unknownAccount")}）</div>
              <div style={{ fontSize: 11.5, color: '#b4695f', marginTop: 2, lineHeight: 1.6 }}>{window.t("extra.canva.revokedHint")}</div>
            </div>
            <Button variant="primary" size="sm" icon="spark" onClick={connect} disabled={busy}>{busy ? window.t("integ.connecting") : window.t("integ.gcal.serverReconnect")}</Button>
          </div>
        ) : connected ? (
          <div style={{ display: 'flex', alignItems: 'center', gap: 10, padding: '12px 14px', background: '#eef6f0', border: '1px solid #d8ecdd', borderRadius: 10 }}>
            <Icon name="check2" size={16} stroke={2.4} style={{ color: '#16a34a', flex: '0 0 auto' }} />
            <div style={{ flex: 1, minWidth: 0 }}>
              <div style={{ fontSize: 13, fontWeight: 700, color: '#1f7a43' }}>{window.t("extra.canva.connectedAs")}{st.account || window.t("extra.canva.noAccountName")}</div>
              <div style={{ fontSize: 11.5, color: '#7ba98a', marginTop: 2 }}>{String(st.connectedAt || '').slice(0, 16).replace('T', ' ')}</div>
            </div>
            <Button variant="default" size="sm" onClick={connect} disabled={busy}>{window.t("extra.canva.changeAccount")}</Button>
            <Button variant="subtle" size="sm" onClick={disconnect}>{window.t("btn.remove")}</Button>
          </div>
        ) : (
          <div style={{ display: 'flex', alignItems: 'center', gap: 12, padding: '14px', background: '#f8f9fb', borderRadius: 10, flexWrap: 'wrap' }}>
            <span style={{ fontSize: 12.5, color: '#5a616c', flex: 1, minWidth: 200 }}>
              {st.shared && st.shared.account
                ? <>{window.t("extra.canva.sharedPrefix")}<b>{st.shared.account}</b>{window.t("extra.canva.sharedSuffix")}</>
                : window.t("extra.canva.connectOwnHint")}
            </span>
            <Button variant="primary" size="sm" icon="spark" onClick={connect} disabled={busy}>{busy ? window.t("integ.connecting") : window.t("extra.canva.connect")}</Button>
          </div>
        )}
      </div>
    </Modal>
  );
}

function FreeeDetailModal({ item, onClose }) {
  const { currentUser, showToast, can } = useStore();
  const isAdmin = currentUser.role === 'admin'; // freee はサーバ側 adminOnly に合わせ管理者のみ
  const configured = !!(window.__APP_CFG && window.__APP_CFG.freeeConfigured); // サーバーに client_id/secret があるか
  const [st, setSt] = React.useState(null);
  const [loading, setLoading] = React.useState(true);
  const [busy, setBusy] = React.useState(false);
  const [companies, setCompanies] = React.useState(null);
  const [templates, setTemplates] = React.useState(null);
  const [showSetup, setShowSetup] = React.useState(false);

  const refresh = React.useCallback(async () => {
    try { const s = await API.freeeStatus(); setSt(s); return s; } catch (e) { showToast(e.message, 'x'); }
    finally { setLoading(false); }
  }, [showToast]);
  const loadCompanies = React.useCallback(async () => { try { const r = await API.freeeCompanies(); setCompanies(r.companies || []); } catch (e) {} }, []);
  const loadTemplates = React.useCallback(async (companyId) => { if (!companyId) return; try { const r = await API.freeeQuotationTemplates(companyId); setTemplates(r.templates || []); } catch (e) { setTemplates([]); } }, []);

  React.useEffect(() => { (async () => { const s = await refresh(); if (s && s.connected) { loadCompanies(); loadTemplates(s.companyId); } })(); }, [refresh, loadCompanies, loadTemplates]);

  const connect = async () => {
    if (busy) return; setBusy(true);
    let timer = null;
    try {
      const { url } = await API.freeeAuthUrl();
      const w = window.open(url, 'freee-oauth', 'width=600,height=760');
      const onMsg = async (ev) => {
        if (!ev.data || ev.data.source !== 'freee') return;
        window.removeEventListener('message', onMsg); if (timer) clearInterval(timer);
        setBusy(false);
        if (ev.data.status === 'connected') { showToast(t('integ.freee.connected')); const s = await refresh(); if (s) { loadCompanies(); loadTemplates(s.companyId); } }
        else showToast(ev.data.message || t('integ.freee.connectFailed'), 'x');
      };
      window.addEventListener('message', onMsg);
      // ポップアップが閉じられたら（postMessage 取りこぼし対策）状態を再取得
      timer = setInterval(() => { if (w && w.closed) { clearInterval(timer); window.removeEventListener('message', onMsg); setBusy(false); refresh().then(s => { if (s && s.connected) { loadCompanies(); loadTemplates(s.companyId); } }); } }, 1000);
    } catch (e) { showToast(e.message, 'x'); setBusy(false); }
  };

  const pickCompany = async (id) => {
    const c = (companies || []).find(x => String(x.id) === String(id));
    try { await API.freeeSaveConfig({ companyId: c ? c.id : id, companyName: c ? c.name : '' }); showToast(t('integ.freee.companySet')); setTemplates(null); await refresh(); loadTemplates(c ? c.id : id); }
    catch (e) { showToast(e.message, 'x'); }
  };
  const pickTemplate = async (id) => {
    const tpl = (templates || []).find(x => String(x.id) === String(id));
    try { await API.freeeSaveConfig({ templateId: id ? (tpl ? tpl.id : id) : null, templateName: tpl ? tpl.name : '' }); showToast(id ? t('integ.freee.templateSet') : t('integ.freee.templateReset')); await refresh(); }
    catch (e) { showToast(e.message, 'x'); }
  };
  const disconnect = async () => {
    if (!window.confirm(t('integ.freee.disconnectConfirm'))) return;
    try { await API.freeeDisconnect(); showToast(t('integ.freee.disconnected'), 'x'); setCompanies(null); setTemplates(null); await refresh(); }
    catch (e) { showToast(e.message, 'x'); }
  };

  const connected = !!(st && st.connected);
  const dot = (on) => <span style={{ width: 7, height: 7, borderRadius: '50%', background: on ? '#16a34a' : '#cbd0d7' }} />;

  return (
    <Modal open onClose={onClose} width={600} title={null}
      footer={connected
        ? <><Button variant="subtle" onClick={disconnect}>{t('integ.disconnect')}</Button><div style={{ flex: 1 }} /><Button variant="subtle" onClick={onClose}>{t('btn.close')}</Button></>
        : <Button variant="subtle" onClick={onClose}>{t('btn.close')}</Button>}>
      <div style={{ display: 'flex', alignItems: 'center', gap: 14, marginBottom: 14 }}>
        <div style={{ width: 50, height: 50, borderRadius: 13, background: item.color, display: 'flex', alignItems: 'center', justifyContent: 'center', flex: '0 0 auto', boxShadow: '0 2px 8px ' + item.color + '44' }}>
          <Icon name={item.icon || 'inbox'} size={24} stroke={2} style={{ color: '#fff' }} />
        </div>
        <div style={{ flex: 1, minWidth: 0 }}>
          <div style={{ fontSize: 17, fontWeight: 700, color: '#1c1f26', display: 'flex', alignItems: 'center', gap: 8 }}>
            {item.name}
            <span style={{ fontSize: 12, fontWeight: 700, color: '#7b828d', background: '#f0f1f4', padding: '1px 7px', borderRadius: 4 }}>{t('integ.adminManaged')}</span>
          </div>
          <div style={{ fontSize: 12, marginTop: 3, display: 'inline-flex', alignItems: 'center', gap: 6, color: connected ? '#16a34a' : '#9aa1ab', fontWeight: 600 }}>
            {dot(connected)}{loading ? t('integ.checking') : connected ? t('integ.connectedWithName', { name: st.companyName ? ' · ' + st.companyName : '' }) : t('integ.notConnected')}
          </div>
        </div>
      </div>
      <div style={{ fontSize: 13, color: '#6b727c', lineHeight: 1.7, paddingBottom: 14, borderBottom: '1px solid #f0f1f4' }}>
        {t('integ.freee.descPre')}<b>{t('integ.freee.descFeatureList')}</b>{t('integ.freee.descMid')}<b>{t('integ.freee.descQuoteDraft')}</b>{t('integ.freee.descPost')}
      </div>

      {!isAdmin ? (
        <div style={{ margin: '16px 0 4px', padding: '14px 16px', background: '#f8f9fb', borderRadius: 10, fontSize: 12.5, color: '#5b626d', lineHeight: 1.7 }}>
          {t('integ.freee.adminOnlyNote')}{connected && <div style={{ marginTop: 6 }}>{t('integ.freee.connectedTo')}<b style={{ color: '#2b2f38' }}>{st.companyName || '—'}</b></div>}
        </div>
      ) : !connected ? (
        <div style={{ margin: '18px 0 4px', display: 'flex', flexDirection: 'column', gap: 12 }}>
          <Button variant="primary" size="lg" icon="link" onClick={connect} disabled={busy} style={{ alignSelf: 'flex-start' }}>
            {busy ? t('integ.freee.connectingWindow') : t('integ.freee.connectWithFreee')}
          </Button>
          <button onClick={() => setShowSetup(s => !s)}
            style={{ display: 'inline-flex', alignItems: 'center', gap: 5, alignSelf: 'flex-start', border: 'none', background: 'transparent', color: '#9aa1ab', fontSize: 12, fontWeight: 600, cursor: 'pointer', fontFamily: 'inherit', padding: 0 }}>
            <Icon name={showSetup ? 'chevronDown' : 'chevronRight'} size={13} stroke={2.2} />{t('integ.freee.setupToggle')}
          </button>
          {showSetup && (
            <div style={{ padding: '12px 14px', background: '#f8f9fb', borderRadius: 10, fontSize: 12.5, color: '#5b626d', lineHeight: 1.85 }}>
              <ol style={{ margin: 0, paddingLeft: 18 }}>
                <li><a href="https://app.secure.freee.co.jp/developers/applications" target="_blank" rel="noreferrer" style={{ color: '#4a5af0' }}>{t('integ.freee.setup.appMgmtLink')}</a>{t('integ.freee.setup.step1Suffix')}</li>
                <li>{t('integ.freee.setup.step2')}
                  <div style={{ display: 'flex', gap: 6, alignItems: 'center', margin: '4px 0' }}>
                    <code style={{ fontSize: 11.5, fontFamily: 'var(--mono)', color: '#5b54b8', background: '#eef0f4', padding: '4px 8px', borderRadius: 6, wordBreak: 'break-all', flex: 1 }}>{FREEE_REDIRECT_URI}</code>
                    <Button size="sm" variant="default" onClick={() => { navigator.clipboard && navigator.clipboard.writeText(FREEE_REDIRECT_URI); showToast(t('integ.copied')); }}>{t('integ.copy')}</Button>
                  </div>
                </li>
                <li>{t('integ.freee.setup.step3')}</li>
                <li>{t('integ.freee.setup.step4Pre')}<b>api</b>{t('integ.freee.setup.step4Mid')}<span style={{ fontFamily: 'var(--mono)' }}>FREEE_CLIENT_ID / FREEE_CLIENT_SECRET</span>{t('integ.freee.setup.step4Post')}</li>
              </ol>
            </div>
          )}
        </div>
      ) : (
        <div style={{ margin: '16px 0 4px' }}>
          <div style={{ fontSize: 12, fontWeight: 700, color: '#9aa1ab', letterSpacing: '.03em', marginBottom: 8 }}>{t('integ.freee.quoteTarget')}</div>
          <Field label={t('integ.freee.companyField')}>
            <select value={st.companyId || ''} onChange={(e) => pickCompany(e.target.value)} style={{ ...inputStyle, appearance: 'none', WebkitAppearance: 'none', cursor: 'pointer' }}>
              {(companies || (st.companyId ? [{ id: st.companyId, name: st.companyName || t('integ.freee.loading') }] : [])).map(c => <option key={c.id} value={c.id}>{c.name}</option>)}
            </select>
          </Field>
          <Field label={t('integ.freee.templateField')}>
            <select value={st.templateId || ''} onChange={(e) => pickTemplate(e.target.value)} style={{ ...inputStyle, appearance: 'none', WebkitAppearance: 'none', cursor: 'pointer' }}>
              <option value="">{t('integ.freee.defaultTemplate')}</option>
              {(templates || (st.templateId ? [{ id: st.templateId, name: st.templateName || t('integ.freee.selected') }] : [])).map(tpl => <option key={tpl.id} value={tpl.id}>{tpl.name}</option>)}
            </select>
          </Field>
          <div style={{ marginTop: 8, padding: '11px 14px', background: '#f0f9f4', border: '1px solid #cdeed9', borderRadius: 10, fontSize: 12.5, color: '#2f7d4f', lineHeight: 1.7 }}>
            <Icon name="check2" size={14} stroke={2.2} style={{ color: '#16a34a', verticalAlign: '-2px', marginRight: 6 }} />
            {t('integ.freee.completeNotePre')}<b>{t('integ.freee.completeNoteTab')}</b>{t('integ.freee.completeNotePost')}
          </div>
        </div>
      )}
    </Modal>
  );
}

/* Fireflies録画→アプリ内再生のブックマークレット（2026-08-02）。Tampermonkey不要版：
   トークンを埋め込んだ javascript: リンクをブックマークバーへ1回ドラッグ→録画ページでクリックすると
   録画URLをサーバ /api/ff-media-capture へ送り、サーバが即DLして会議記録に永久保存（videoAttId）。 */
function FfCaptureBookmarkletBox() {
  const token = useImportToken();
  const api = 'https://api-production-c40f.up.railway.app';
  const code = "(function(){var T='" + token + "';var A='" + api + "';var m=(location.pathname.match(/\/view\/([A-Za-z0-9]+)/)||[])[1];if(!m){alert('Firefliesの録画ページ（/view/…）で実行してください');return;}var u='';var els=document.querySelectorAll('video,audio');for(var i=0;i<els.length;i++){var s=els[i].currentSrc||els[i].src||'';if(/^https?:/.test(s)){u=s;break;}}if(!u){var rs=performance.getEntriesByType('resource').map(function(e){return e.name}).filter(function(x){return /\.(mp4|webm|m4a|mp3)(\?|$)/i.test(x)});if(rs.length)u=rs[rs.length-1];}if(!u){alert('録画URLが見つかりません。動画を数秒再生してからもう一度お試しください');return;}fetch(A+'/api/ff-media-capture',{method:'POST',headers:{'Content-Type':'application/json',Authorization:'Bearer '+T},body:JSON.stringify({ffId:m,url:u,how:'bookmarklet',title:document.title})}).then(function(r){return r.json().then(function(j){return {ok:r.ok,j:j}})}).then(function(x){alert(x.ok?'保存しました。アプリの会議記録で再生できます':'保存失敗：'+((x.j&&x.j.error)||'不明なエラー'))}).catch(function(e){alert('通信エラー：'+e.message)})})();";
  const href = 'javascript:' + encodeURIComponent(code);
  return (
    <div style={{ background: '#fff4ee', border: '1px solid #f5d8c8', borderRadius: 10, padding: 13, marginTop: 14 }}>
      <div style={{ fontSize: 12.5, fontWeight: 700, color: '#b4551c', marginBottom: 5 }}>{window.t("extra.fireflies.captureTitle")}</div>
      <div style={{ fontSize: 11.5, color: '#7b6a5f', lineHeight: 1.7, marginBottom: 10 }}>{window.t("extra.fireflies.buttonPrefix")}<b>{window.t("cases2.import.step1bold")}</b>{window.t("extra.fireflies.captureHint")}</div>
      {token
        ? <a href={href} onClick={(e) => e.preventDefault()} draggable
            style={{ display: 'inline-block', textDecoration: 'none', background: '#ef5a3c', color: '#fff', fontSize: 12.5, fontWeight: 700, padding: '8px 14px', borderRadius: 8, cursor: 'grab' }}
            title={window.t("extra.fireflies.dragHint")}>{window.t("extra.fireflies.capture")}</a>
        : <span style={{ fontSize: 12, color: '#9aa1ab' }}>{window.t("extra.fireflies.tokenLoading")}</span>}
      <div style={{ fontSize: 10.5, color: '#a08c7f', marginTop: 7 }}>{window.t("extra.fireflies.storageHint")}</div>
    </div>
  );
}

/* 連携詳細モーダル（アカウント連結・設定・アクティビティ） */
function IntegrationDetailModal({ item, onClose }) {
  if (item.id === 'openai') return <OpenAIDetailModal item={item} onClose={onClose} />;
  if (item.id === 'gcal') return <GcalDetailModal item={item} onClose={onClose} />;
  if (item.id === 'gmail') return <GmailDetailModal item={item} onClose={onClose} />;
  if (item.id === 'freee') return <FreeeDetailModal item={item} onClose={onClose} />;
  if (item.id === 'canva') return <CanvaDetailModal item={item} onClose={onClose} />;
  return <GenericIntegrationDetailModal key={item.id} item={item} onClose={onClose} />;
}
function GenericIntegrationDetailModal({ item, onClose }) {
  const { currentUser, integOf, integScope, connectIntegration, disconnectIntegration, saveIntegrationSettings, showToast, can, navigate } = useStore();
  const D = window.APP_DATA;
  const det = INTEG_DETAILS[item.id] || { settings: [], log: [] };
  const rec = integOf(item.id);
  const connected = !!(rec && rec.connected);
  const isSource = item.id === 'source';
  const isWorkspace = integScope(item.id) === 'workspace';
  const canManage = !isWorkspace || can('workspaceInteg');
  const [acct, setAcct] = React.useState('');
  const [syncing, setSyncing] = React.useState(false);
  const [vals, setVals] = React.useState(() => ({
    ...Object.fromEntries(det.settings.map(s => [s.key, s.def])),
    ...((rec && rec.settings) || {}),
  }));
  const setVal = (k) => (v) => setVals(s => ({ ...s, [k]: v }));

  const logRows = (det.log || []).map(l => ({ ...l, fail: false }));

  const connect = () => { if (!acct.trim()) return; connectIntegration(item.id, acct, item.name); onClose(); };

  return (
    <Modal open onClose={onClose} width={640}
      title={null}
      footer={!canManage ? (
        <Button variant="subtle" onClick={onClose}>{t('btn.close')}</Button>
      ) : connected ? (
        <>
          <Button variant="subtle" onClick={() => { disconnectIntegration(item.id, item.name); onClose(); }}>{t('integ.disconnect')}</Button>
          <div style={{ flex: 1 }} />
          {item.id === 'fireflies' && (
            <Button variant="default" icon="refresh" onClick={async () => {
              if (syncing) return; setSyncing(true);
              try { const r = await API.syncFireflies(); showToast(t('integ.fireflies.synced', { count: r.count })); }
              catch (e) { showToast(e.message, 'x'); }
              setSyncing(false);
            }}>{syncing ? t('integ.syncing') : t('integ.syncNow')}</Button>
          )}
          <Button variant="subtle" onClick={onClose}>{t('btn.cancel')}</Button>
          <Button variant="primary" icon="check" onClick={() => { saveIntegrationSettings(item.id, vals, item.name); onClose(); }}>{t('integ.saveSettings')}</Button>
        </>
      ) : (
        <>
          <Button variant="subtle" onClick={onClose}>{t('btn.close')}</Button>
          <Button variant="primary" icon="link" onClick={connect} disabled={!acct.trim()}>{t('integ.connectAccount')}</Button>
        </>
      )}>
      {/* ヘッダー */}
      <div style={{ display: 'flex', alignItems: 'center', gap: 14, marginBottom: 14 }}>
        <div style={{ width: 50, height: 50, borderRadius: 13, background: item.icon === 'google' ? '#f4f5f7' : item.color, display: 'flex', alignItems: 'center', justifyContent: 'center', flex: '0 0 auto', boxShadow: item.icon === 'google' ? 'none' : '0 2px 8px ' + item.color + '44' }}>
          {item.icon === 'google' ? <Icon name="google" size={26} /> : <Icon name={item.icon} size={24} stroke={2} fill={item.icon === 'spark' ? '#fff' : 'none'} style={{ color: '#fff' }} />}
        </div>
        <div style={{ flex: 1, minWidth: 0 }}>
          <div style={{ fontSize: 17, fontWeight: 700, color: '#1c1f26', display: 'flex', alignItems: 'center', gap: 8 }}>
            {item.name}
            {item.badge && <span style={{ fontSize: 12, fontWeight: 700, color: item.color, background: item.color + '18', padding: '1px 7px', borderRadius: 4 }}>{item.badge}</span>}
            {isWorkspace && <span style={{ fontSize: 12, fontWeight: 700, color: '#7b828d', background: '#f0f1f4', padding: '1px 7px', borderRadius: 4 }}>{t('integ.adminManaged')}</span>}
          </div>
          <div style={{ fontSize: 12, marginTop: 3, display: 'inline-flex', alignItems: 'center', gap: 6, color: connected ? '#16a34a' : '#9aa1ab', fontWeight: 600 }}>
            <span style={{ width: 7, height: 7, borderRadius: '50%', background: connected ? '#16a34a' : '#cbd0d7' }} />
            {connected ? t('integ.connectedWithAccount', { account: rec.account }) : t('integ.notConnected')}
          </div>
        </div>
        {connected && item.url && (
          <a href={item.url} target="_blank" rel="noreferrer" style={{ textDecoration: 'none' }} onClick={(e) => e.stopPropagation()}>
            <Button variant="default" size="sm" icon="link">{t('btn.open')}</Button>
          </a>
        )}
      </div>
      <div style={{ fontSize: 13, color: '#6b727c', lineHeight: 1.7, paddingBottom: 14, borderBottom: '1px solid #f0f1f4' }}>
        {item.desc}{isWorkspace ? t('integ.scope.workspaceSuffix') : t('integ.scope.userSuffix')}
      </div>

      {!canManage ? (
        /* 一般メンバーから見たワークスペース連携：状態のみ */
        <div style={{ margin: '16px 0 4px', padding: '14px 16px', background: '#f8f9fb', borderRadius: 10, fontSize: 12.5, color: '#5b626d', lineHeight: 1.7 }}>
          {t('integ.workspaceAdminOnly')}
          {connected && <div style={{ marginTop: 6 }}>{t('integ.currentConnection')}<b style={{ color: '#2b2f38' }}>{rec.account}</b></div>}
        </div>
      ) : connected ? (
        <>
          {/* 連結アカウント */}
          <div style={{ fontSize: 12, fontWeight: 700, color: '#9aa1ab', letterSpacing: '.03em', margin: '16px 0 8px' }}>{t('integ.linkedAccount')}</div>
          <div style={{ display: 'flex', alignItems: 'center', gap: 10, padding: '10px 13px', background: '#f8f9fb', borderRadius: 9 }}>
            <Icon name="check2" size={15} stroke={2.2} style={{ color: '#16a34a', flex: '0 0 auto' }} />
            <div style={{ flex: 1, minWidth: 0 }}>
              <div style={{ fontSize: 13, fontWeight: 600, color: '#2b2f38' }}>{rec.account}</div>
              <div style={{ fontSize: 12, color: '#9aa1ab', marginTop: 1 }}>{det.accountLabel}{rec.connectedAt ? <span> · <span style={{ color: '#4a5af0' }}>{fmtDate(rec.connectedAt)}</span>{t('integ.linkedAtSuffix')}</span> : ''}</div>
            </div>
            <Button variant="default" size="sm" onClick={() => { disconnectIntegration(item.id, item.name); }}>{t('integ.unlink')}</Button>
          </div>

          {/* Fireflies：録画をアプリ内で見るためのブックマークレット（拡張機能不要・トークン埋込済み） */}
          {item.id === 'fireflies' && <FfCaptureBookmarkletBox />}

          {/* 設定（項目が無いサービスは見出しごと省略） */}
          {det.settings.length > 0 && (
            <div style={{ fontSize: 12, fontWeight: 700, color: '#9aa1ab', letterSpacing: '.03em', margin: '16px 0 2px' }}>{t('integ.settings')}</div>
          )}
          {det.settings.map(s => <IntegSettingRow key={s.key} s={s} value={vals[s.key]} onChange={setVal(s.key)} />)}

          {/* ReadyCrew は「サーバー巡回」ではなくブラウザのブックマークレットで取込む。実態に合わせた案内＋本機能への導線。 */}
          {isSource && (
            <div style={{ marginTop: 16, background: '#f6f6fe', border: '1px solid #e4e3fb', borderRadius: 10, padding: '14px 16px' }}>
              <div style={{ fontSize: 13, fontWeight: 700, color: '#5b54b8', marginBottom: 6 }}>{t('integ.source.bookmarkletTitle')}</div>
              <div style={{ fontSize: 12.5, color: '#5a616c', lineHeight: 1.7, marginBottom: 12 }}>{t('integ.source.bookmarkletDesc')}</div>
              <Button variant="primary" size="sm" icon="download" onClick={() => { onClose(); navigate('cases'); }}>{t('integ.source.openImport')}</Button>
            </div>
          )}

          {/* アクティビティ */}
          {logRows.length > 0 && (
            <>
              <div style={{ fontSize: 12, fontWeight: 700, color: '#9aa1ab', letterSpacing: '.03em', margin: '18px 0 8px' }}>{t('integ.recentActivity')}</div>
              <div style={{ background: '#f8f9fb', borderRadius: 10, padding: '4px 14px' }}>
                {logRows.map((l, i) => (
                  <div key={i} style={{ display: 'flex', gap: 12, alignItems: 'flex-start', padding: '9px 0', borderBottom: i === logRows.length - 1 ? 'none' : '1px solid #eef0f3' }}>
                    <span style={{ fontFamily: 'var(--mono)', fontSize: 12, color: '#9aa1ab', flex: '0 0 auto', marginTop: 1 }}>{l.at}</span>
                    <span style={{ fontSize: 12.5, color: l.fail ? '#dc2626' : '#3b414b', lineHeight: 1.55 }}>{l.text}</span>
                  </div>
                ))}
              </div>
            </>
          )}
        </>
      ) : (
        /* 未連携：アカウント連結フォーム + できること */
        <div style={{ margin: '16px 0 4px' }}>
          <div style={{ fontSize: 12, fontWeight: 700, color: '#9aa1ab', letterSpacing: '.03em', marginBottom: 8 }}>{t('integ.connectAccount')}</div>
          <Field label={det.accountLabel} required>
            <TextInput value={acct} onChange={(e) => setAcct(e.target.value)} placeholder={det.accountPlaceholder}
              onKeyDown={(e) => { if (enterSubmits(e)) connect(); }} />
          </Field>
          <div style={{ fontSize: 12, fontWeight: 700, color: '#9aa1ab', letterSpacing: '.03em', margin: '6px 0 10px' }}>{t('integ.whatYouCanDo')}</div>
          {det.settings.map(s => (
            <div key={s.key} style={{ display: 'flex', gap: 9, alignItems: 'flex-start', padding: '6px 0' }}>
              <Icon name="check2" size={15} stroke={2.2} style={{ color: '#16a34a', flex: '0 0 auto', marginTop: 2 }} />
              <div>
                <span style={{ fontSize: 13, color: '#2b2f38', fontWeight: 600 }}>{s.label}</span>
                {s.desc && <span style={{ fontSize: 12, color: '#9aa1ab' }}> — {s.desc}</span>}
              </div>
            </div>
          ))}
        </div>
      )}
    </Modal>
  );
}

function IntegrationsScreen() {
  const { currentUser, integOf, integScope, can } = useStore();
  // OpenAI / Fireflies はサーバーの API キーで動くため、接続状態は /api/config から取得
  const [cfg, setCfg] = React.useState(window.__APP_CFG || {});
  const [freeeConn, setFreeeConn] = React.useState(null);
  const [canvaConn, setCanvaConn] = React.useState(null);
  React.useEffect(() => {
    if (window.__APP_CFG) setCfg(window.__APP_CFG);
    else API.fetchConfig().then(c => { window.__APP_CFG = c; setCfg(c); }).catch(() => {});
    if (can('workspaceInteg')) API.freeeStatus().then(setFreeeConn).catch(() => {});
    API.canvaStatus().then(setCanvaConn).catch(() => {});   // Canvaは全メンバーが状態を見られる（接続操作は管理者のみ）
  }, [currentUser.role]);
  const sections = [
    { title: t('integ.section.calendar'), items: [
      { id: 'gcal', name: t('integ.gcal.name'), desc: t('integ.card.gcal.desc'), color: '#4285F4', icon: 'google' },
    ]},
    { get title(){return window.t("cases.mail");}, items: [
      { id: 'gmail', name: 'Gmail（自動同期）', desc: '担当案件の顧客との往来メールを毎日1回サーバーが自動取得し、メール履歴に反映します。', color: '#EA4335', icon: 'mail' },
    ]},
    { title: t('integ.section.meetingAI'), items: [
      { id: 'fireflies', name: 'Fireflies.ai', desc: t('integ.card.fireflies.desc'), color: '#ef5a3c', icon: 'spark', badge: 'AI' },
      { id: 'openai', name: 'OpenAI', desc: t('integ.card.openai.desc'), color: '#10a37f', icon: 'spark', badge: 'AI' },
      { id: 'zoom', name: 'Zoom', desc: t('integ.card.zoom.desc'), color: '#2D8CFF', icon: 'video' },
      { id: 'canva', name: 'Canva', desc: '提案書デザインを自分のCanvaへ自動作成。過去提案の学習にも使います（各自のアカウントで接続）', color: '#00C4CC', icon: 'spark' },
    ]},
    { title: t('integ.section.notify'), items: [
      { id: 'slack', name: 'Slack', desc: t('integ.card.slack.desc'), color: '#611f69', icon: 'inbox' },
      { id: 'teams', name: 'Microsoft Teams', desc: t('integ.card.teams.desc'), color: '#5059C9', icon: 'customers' },
    ]},
    { title: t('integ.section.storage'), items: [
      { id: 'gdrive', name: 'Google Drive', desc: t('integ.card.gdrive.desc'), color: '#16a34a', icon: 'link' },
    ]},
    { title: t('integ.section.accounting'), items: [
      { id: 'freee', name: t('integ.card.freee.name'), desc: t('integ.card.freee.desc'), color: '#2864f0', icon: 'inbox' },
    ]},
    { title: t('integ.section.source'), items: [
      { id: 'source', name: t('integ.card.source.name'), desc: t('integ.card.source.desc'), color: '#4a5af0', icon: 'refresh', url: 'https://alion.partner.readycrew.cloud/matchings?status=IN_PROGRESS_ALL' },
    ]},
  ];
  // Fireflies の実測ステータス：接続＝サーバAPIキー(cfg.fireflies)、表示＝最終同期時刻＋取込議事録数（保存ラベルではなく実データ）
  const ffRow = (window.APP_DATA.userIntegrations || []).find(u => String(u.id || '').indexOf('fireflies') >= 0);
  const ffLast = (ffRow && (ffRow.lastSyncAt || (ffRow.data && ffRow.data.lastSyncAt))) || '';
  const ffCount = (window.APP_DATA.firefliesMeetings || []).length;
  const ffStale = !ffLast || (Date.now() - (parseDT(ffLast) ? parseDT(ffLast).getTime() : 0)) > 24 * 3600 * 1000;
  const isAdmin = can('workspaceInteg');
  // 一般メンバーには自分個別の連携（Google カレンダー等）だけ表示。ワークスペース共通（管理者管理）は非表示
  const visibleSections = sections
    .map(sec => ({ ...sec, items: sec.items.filter(it => isAdmin || integScope(it.id) === 'user') }))
    .filter(sec => sec.items.length > 0);
  const allItems = visibleSections.flatMap(s => s.items);
  const [detail, setDetail] = React.useState(null);
  const connectedCount = allItems.filter(i => { const r = integOf(i.id); return r && r.connected; }).length;

  return (
    <Page title={t('page.integrations')}>
      <div style={{ marginBottom: 20 }}>
        <div style={{ fontSize: 20, fontWeight: 700, color: '#1c1f26' }}>{t('integ.pageTitle')}</div>
        <div style={{ fontSize: 13.5, color: '#7b828d', marginTop: 4 }}>{isAdmin ? t('integ.subtitle.admin') : t('integ.subtitle.member')}{t('integ.connectedCount', { n: connectedCount })}</div>
      </div>

      {visibleSections.map(sec => (
        <div key={sec.title} style={{ marginBottom: 26 }}>
          <div style={{ fontSize: 12.5, fontWeight: 700, color: '#9aa1ab', letterSpacing: '.03em', marginBottom: 12 }}>{sec.title}</div>
          <div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fill, minmax(330px, 1fr))', gap: 14 }}>
            {sec.items.map(item => {
              const rec = integOf(item.id);
              // OpenAI/freee はサーバー側の状態で接続が決まる（user_integrations には無い）
              const on = item.id === 'openai' ? !!cfg.openai
                : item.id === 'fireflies' ? !!cfg.fireflies
                : item.id === 'freee' ? !!(freeeConn && freeeConn.connected)
                : item.id === 'canva' ? !!(canvaConn && canvaConn.connected)
                : !!(rec && rec.connected);
              const canManage = integScope(item.id) === 'user' || isAdmin;
              return (
                <div key={item.id} className="lift" onClick={() => setDetail(item)}
                  style={{ background: '#fff', border: '1px solid #ecedf0', borderRadius: 12, padding: 18, boxShadow: '0 1px 2px rgba(20,22,40,.04)', display: 'flex', flexDirection: 'column', cursor: 'pointer' }}>
                  <div style={{ display: 'flex', alignItems: 'flex-start', gap: 12, marginBottom: 10 }}>
                    <div style={{ width: 42, height: 42, borderRadius: 11, background: item.icon === 'google' ? '#f4f5f7' : item.color, display: 'flex', alignItems: 'center', justifyContent: 'center', flex: '0 0 auto', boxShadow: item.icon === 'google' ? 'none' : '0 2px 6px ' + item.color + '44' }}>
                      {item.icon === 'google' ? <Icon name="google" size={22} /> : <Icon name={item.icon} size={20} stroke={2} fill={item.icon === 'spark' ? '#fff' : 'none'} style={{ color: '#fff' }} />}
                    </div>
                    <div style={{ flex: 1, minWidth: 0 }}>
                      <div style={{ fontSize: 14.5, fontWeight: 700, color: '#1c1f26', display: 'flex', alignItems: 'center', gap: 7 }}>
                        {item.name}
                        {item.badge && <span style={{ fontSize: 12, fontWeight: 700, color: item.color, background: item.color + '18', padding: '1px 6px', borderRadius: 4 }}>{item.badge}</span>}
                      </div>
                      <div style={{ fontSize: 12, marginTop: 4, display: 'inline-flex', alignItems: 'center', gap: 5, color: on ? '#16a34a' : '#9aa1ab', fontWeight: 500 }}>
                        <span style={{ width: 7, height: 7, borderRadius: '50%', background: on ? '#16a34a' : '#cbd0d7' }} />
                        {on ? (item.id === 'openai' ? t('integ.card.openaiKeySet')
                          : item.id === 'fireflies' ? <span style={{ color: ffStale ? '#d97706' : 'inherit' }}>{(ffLast ? (ffStale ? (""+window.t("extra.integration.pausedLast")+" ") : (""+window.t("integ.gcal.lastSyncLabel")+" ")) + ffLast.replace('T', ' ').slice(5, 16) : window.t("extra.integration.noSync")) + window.t("extra.common.minutesSuffix") + ffCount + window.t("unit.count")}</span>
                          : item.id === 'freee' ? ((freeeConn && freeeConn.companyName) || t('integ.connected'))
                          : item.id === 'canva' ? ((canvaConn && canvaConn.account) || t('integ.connected')) : (rec && rec.account) || t('integ.connected')) : t('integ.notConnected')}
                      </div>
                    </div>
                    <Icon name="chevronRight" size={16} stroke={2} style={{ color: '#cbd0d7', marginTop: 4 }} />
                  </div>
                  <div style={{ fontSize: 12.5, color: '#6b727c', lineHeight: 1.6, flex: 1 }}>{item.desc}</div>
                  <div style={{ display: 'flex', gap: 8, marginTop: 14 }} onClick={(e) => e.stopPropagation()}>
                    {on && item.url && <a href={item.url} target="_blank" rel="noreferrer" style={{ textDecoration: 'none' }}><Button variant="default" size="sm" icon="link">{t('btn.open')}</Button></a>}
                    {item.id === 'openai' || !canManage
                      ? <Button variant="default" size="sm" full={!on || !item.url} onClick={() => setDetail(item)}>{t('integ.viewDetail')}</Button>
                      : on
                        ? <Button variant="default" size="sm" icon="settings" full={!item.url} onClick={() => setDetail(item)}>{t('integ.settings')}</Button>
                        : <Button variant="primary" size="sm" full icon="link" onClick={() => setDetail(item)}>{t('integ.connectAccount')}</Button>}
                  </div>
                </div>
              );
            })}
          </div>
        </div>
      ))}

      {detail && <IntegrationDetailModal key={detail.id + '-' + String(!!(integOf(detail.id) || {}).connected)} item={detail} onClose={() => setDetail(null)} />}
    </Page>
  );
}
Object.assign(window, { IntegrationsScreen, IntegrationDetailModal, GcalDetailModal, GmailDetailModal });
