import React, { useState, useEffect, useRef } from 'react';
import { createRoot } from 'react-dom/client';
import { Plus, Edit2, Trash2, X, Users, FolderPlus, Save, ChevronDown, ChevronLeft, Shirt, Trophy, RefreshCw, Target, AlertTriangle, Link2, Unlink, Undo2, Redo2, Home } from 'lucide-react';

// window.storage アダプタ：artifact 環境固有の API を localStorage に差し替える
// （元の squad_builder.jsx の Storage セクションを変更せずに動かすためのシム）
if (typeof window !== 'undefined' && !window.storage) {
  window.storage = {
    async get(key) {
      const v = localStorage.getItem(key);
      return v != null ? { value: v } : null;
    },
    async set(key, value) {
      localStorage.setItem(key, value);
    },
  };
}

// ============================================================
//   Formations
// ============================================================
const FORMATIONS = {
  '4-3-3': [
    { id: 'gk',  label: 'GK', x: 50, y: 93 },
    { id: 'lb',  label: 'LB', x: 12, y: 76 },
    { id: 'lcb', label: 'CB', x: 35, y: 81 },
    { id: 'rcb', label: 'CB', x: 65, y: 81 },
    { id: 'rb',  label: 'RB', x: 88, y: 76 },
    { id: 'lcm', label: 'CM', x: 28, y: 56 },
    { id: 'cm',  label: 'CM', x: 50, y: 58 },
    { id: 'rcm', label: 'CM', x: 72, y: 56 },
    { id: 'lw',  label: 'LW', x: 18, y: 24 },
    { id: 'st',  label: 'ST', x: 50, y: 14 },
    { id: 'rw',  label: 'RW', x: 82, y: 24 },
  ],
  '4-4-2': [
    { id: 'gk',  label: 'GK', x: 50, y: 93 },
    { id: 'lb',  label: 'LB', x: 12, y: 76 },
    { id: 'lcb', label: 'CB', x: 35, y: 81 },
    { id: 'rcb', label: 'CB', x: 65, y: 81 },
    { id: 'rb',  label: 'RB', x: 88, y: 76 },
    { id: 'lm',  label: 'LM', x: 12, y: 50 },
    { id: 'lcm', label: 'CM', x: 38, y: 54 },
    { id: 'rcm', label: 'CM', x: 62, y: 54 },
    { id: 'rm',  label: 'RM', x: 88, y: 50 },
    { id: 'lst', label: 'ST', x: 38, y: 18 },
    { id: 'rst', label: 'ST', x: 62, y: 18 },
  ],
  '4-2-3-1': [
    { id: 'gk',  label: 'GK', x: 50, y: 93 },
    { id: 'lb',  label: 'LB', x: 12, y: 76 },
    { id: 'lcb', label: 'CB', x: 35, y: 81 },
    { id: 'rcb', label: 'CB', x: 65, y: 81 },
    { id: 'rb',  label: 'RB', x: 88, y: 76 },
    { id: 'ldm', label: 'DM', x: 35, y: 62 },
    { id: 'rdm', label: 'DM', x: 65, y: 62 },
    { id: 'lam', label: 'LAM', x: 18, y: 36 },
    { id: 'cam', label: 'CAM', x: 50, y: 38 },
    { id: 'ram', label: 'RAM', x: 82, y: 36 },
    { id: 'st',  label: 'ST',  x: 50, y: 13 },
  ],
  '3-5-2': [
    { id: 'gk',  label: 'GK',  x: 50, y: 93 },
    { id: 'lcb', label: 'CB',  x: 25, y: 80 },
    { id: 'cb',  label: 'CB',  x: 50, y: 83 },
    { id: 'rcb', label: 'CB',  x: 75, y: 80 },
    { id: 'lwb', label: 'LWB', x: 8,  y: 55 },
    { id: 'lcm', label: 'CM',  x: 32, y: 56 },
    { id: 'cm',  label: 'CM',  x: 50, y: 50 },
    { id: 'rcm', label: 'CM',  x: 68, y: 56 },
    { id: 'rwb', label: 'RWB', x: 92, y: 55 },
    { id: 'lst', label: 'ST',  x: 38, y: 18 },
    { id: 'rst', label: 'ST',  x: 62, y: 18 },
  ],
  '3-4-3': [
    { id: 'gk',  label: 'GK', x: 50, y: 93 },
    { id: 'lcb', label: 'CB', x: 25, y: 80 },
    { id: 'cb',  label: 'CB', x: 50, y: 83 },
    { id: 'rcb', label: 'CB', x: 75, y: 80 },
    { id: 'lm',  label: 'LM', x: 12, y: 55 },
    { id: 'lcm', label: 'CM', x: 38, y: 58 },
    { id: 'rcm', label: 'CM', x: 62, y: 58 },
    { id: 'rm',  label: 'RM', x: 88, y: 55 },
    { id: 'lw',  label: 'LW', x: 20, y: 22 },
    { id: 'st',  label: 'ST', x: 50, y: 14 },
    { id: 'rw',  label: 'RW', x: 80, y: 22 },
  ],
  // ─ 超攻撃: 2-3-5（古典的攻撃ピラミッド） ─
  '2-3-5': [
    { id: 'gk',  label: 'GK',  x: 50, y: 93 },
    // 2DF
    { id: 'lb',  label: 'LB',  x: 25, y: 78 },
    { id: 'rb',  label: 'RB',  x: 75, y: 78 },
    // 3MF
    { id: 'lm',  label: 'LM',  x: 18, y: 58 },
    { id: 'cm',  label: 'CM',  x: 50, y: 58 },
    { id: 'rm',  label: 'RM',  x: 82, y: 58 },
    // 5FW（インサイドフォワード2人を含む扇形）
    { id: 'lw',  label: 'LW',  x: 10, y: 26 },
    { id: 'lif', label: 'LIF', x: 30, y: 16 },
    { id: 'st',  label: 'ST',  x: 50, y: 10 },
    { id: 'rif', label: 'RIF', x: 70, y: 16 },
    { id: 'rw',  label: 'RW',  x: 90, y: 26 },
  ],
  // ─ 超守備: 5-4-1（守備ブロック / バス停め） ─
  '5-4-1': [
    { id: 'gk',  label: 'GK',  x: 50, y: 93 },
    // 5DF（中央3CB + ウイングバック2）
    { id: 'lwb', label: 'LWB', x: 8,  y: 70 },
    { id: 'lcb', label: 'CB',  x: 28, y: 82 },
    { id: 'cb',  label: 'CB',  x: 50, y: 86 },
    { id: 'rcb', label: 'CB',  x: 72, y: 82 },
    { id: 'rwb', label: 'RWB', x: 92, y: 70 },
    // 4MF（フラット）
    { id: 'lm',  label: 'LM',  x: 15, y: 50 },
    { id: 'lcm', label: 'CM',  x: 38, y: 54 },
    { id: 'rcm', label: 'CM',  x: 62, y: 54 },
    { id: 'rm',  label: 'RM',  x: 85, y: 50 },
    // 1FW（ターゲットマン）
    { id: 'st',  label: 'ST',  x: 50, y: 18 },
  ],
};

// フォーメーションのトーン（UI色付け用）と説明文
const FORMATION_TONE = {
  '2-3-5': 'attack',
  '5-4-1': 'defense',
};
const FORMATION_HINT = {
  '4-3-3':   'バランス型',
  '4-4-2':   'クラシック',
  '4-2-3-1': 'モダン中盤',
  '3-5-2':   '中盤厚い',
  '3-4-3':   '攻撃寄り',
  '2-3-5':   '⚡ 超攻撃（古典ピラミッド）',
  '5-4-1':   '🛡 超守備（守備ブロック）',
};

// ============================================================
//   Role / Skill candidates
// ============================================================
const ROLE_GROUPS = [
  { label: '経営・C-level', options: [
    'Founder', 'Co-founder',
    'CEO', 'COO', 'CTO', 'CFO', 'CMO', 'CPO', 'CRO', 'CSO', 'CIO',
    'Chief Architect', 'Chief Scientist',
    'VP of Engineering', 'VP of Product', 'VP of Sales', 'VP of Marketing',
  ] },
  { label: '取締役会・経営層', options: [
    'Chairman / 会長', '取締役 / Director', '社外取締役 / Outside Director',
    '監査役 / Auditor', '執行役員 / Executive Officer',
    '本部長', '部長 / General Manager', '課長 / Section Manager', '主任 / Chief',
  ] },
  { label: 'エンジニア (役職レベル)', options: [
    'Engineering Manager', 'Tech Lead', 'Principal Engineer',
    'Staff Engineer', 'Senior Engineer', 'Engineer', 'Junior Engineer',
  ] },
  { label: 'エンジニア (専門領域)', options: [
    'Edge Engineer', 'Embedded Engineer', 'Firmware Engineer',
    'Hardware Engineer', 'Electrical Engineer', 'Mechanical Engineer',
    'PCB Designer', 'FPGA Engineer', 'Robotics Engineer',
    'Backend Engineer', 'Frontend Engineer', 'Full-stack Engineer', 'Mobile Engineer',
    'ML / AI Engineer', 'ML Researcher', 'Vision / VLM Engineer',
    'Data Engineer', 'Data Scientist',
    'DevOps / SRE', 'Platform Engineer', 'Cloud Engineer',
    'Security Engineer', 'QA / Test Engineer',
    'Solutions Architect', 'Sales Engineer', 'Field Application Engineer (FAE)',
    'Manufacturing Engineer', 'Field / Service Engineer',
  ] },
  { label: '製造・組立', options: [
    '工場長 / Plant Manager', '製造責任者 / Production Manager',
    '生産技術 / Industrial Engineer', '工程エンジニア / Process Engineer',
    '生産計画 / Production Planner',
    '製造リーダー / Production Lead', '現場監督 / Shop Floor Supervisor',
    '組立技術者 / Assembly Technician', '製造オペレーター / Production Operator',
    '治具設計 / Jig Designer', 'ライン技術者 / Line Engineer',
  ] },
  { label: '品質保証・テスト', options: [
    '品質保証責任者 / QA Manager', '品質管理 / QC Manager',
    'QC技術者 / QC Technician', 'テスト技術者 / Test Technician',
    '校正エンジニア / Calibration Engineer', '信頼性エンジニア / Reliability Engineer',
    '故障解析エンジニア / Failure Analysis Engineer',
    'インスペクター / Inspector', '受入検査 / Incoming Inspector',
    '出荷検査 / Final Inspector',
  ] },
  { label: '調達・SCM・物流', options: [
    'SCM責任者 / Supply Chain Manager',
    '購買責任者 / Procurement Manager', 'バイヤー / Buyer',
    '資材管理 / Materials Manager', 'ベンダー管理 / Vendor Manager',
    'BOM管理 / BOM Manager',
    '物流責任者 / Logistics Manager', '倉庫管理 / Warehouse Manager',
    '在庫管理 / Inventory Control', '出荷受入 / Shipping & Receiving',
  ] },
  { label: 'フィールド・保守', options: [
    'フィールドサービス責任者 / Field Service Manager',
    'サービス技術者 / Service Technician',
    '設置技術者 / Installation Technician',
    '修理技術者 / Repair Technician',
    '巡回保守 / On-site Maintenance',
    'カスタマーサービスエンジニア / Customer Service Engineer',
    'コールセンター / Help Desk',
  ] },
  { label: 'プロダクト', options: [
    'Chief Product Officer', 'Head of Product',
    'Senior Product Manager', 'Product Manager', 'Associate PM',
    'Technical PM', 'Product Marketing Manager',
  ] },
  { label: 'デザイン', options: [
    'Design Lead', 'Product Designer', 'UX Designer', 'UI Designer',
    'Visual Designer', 'Brand Designer', 'Industrial Designer', 'Motion Designer',
  ] },
  { label: '事業・セールス', options: [
    'Head of Sales', 'Sales / 営業',
    'Enterprise Sales', 'Inside Sales', 'Field Sales',
    'Account Executive', 'Account Manager',
    'Sales Development (SDR)', 'Business Development / 事業開発',
    'Channel / Partner Sales',
    'Customer Success', 'Customer Support', 'Technical Support',
  ] },
  { label: 'マーケティング・PR', options: [
    'Head of Marketing', 'Marketing',
    'Product Marketing', 'Content Marketing',
    'Growth Marketer', 'Demand Gen', 'SEO Specialist',
    'Brand / PR', 'Communications', 'Community Manager',
  ] },
  { label: '法務・知財', options: [
    'General Counsel / 法務責任者', 'Legal / Compliance',
    'Patent Attorney / 弁理士', 'IP Manager / 知財責任者',
    'Compliance Officer',
  ] },
  { label: '財務・経理', options: [
    'Head of Finance', 'Finance / 経理', 'Controller',
    'FP&A Analyst', 'Accountant / 会計士', 'Tax Manager',
  ] },
  { label: '人事・総務', options: [
    'Head of People', 'HR / 人事', 'HR Business Partner',
    'Recruiter / 採用', 'Talent Acquisition', 'People Ops',
    'Operations / 総務', 'Office Manager', 'Executive Assistant',
  ] },
  { label: 'データ・分析', options: [
    'Head of Data', 'Data Analyst', 'Business Analyst',
    'Analytics Engineer', 'BI Engineer',
  ] },
  { label: '社外・パートナー', options: [
    'Strategic Partner', 'Advisor / 顧問', 'Board Member',
    'Investor / 投資家', 'Mentor',
    'Contractor / 業務委託', 'Intern / インターン',
  ] },
  { label: 'その他', options: ['Other'] },
];

// ============================================================
//   役職の解説（マウスオーバー＆選択時のヒント）
// ============================================================
const ROLE_DESCRIPTIONS = {
  // 経営・C-level
  'Founder':            '創業者。会社を立ち上げた人。',
  'Co-founder':         '共同創業者。創業初期からの中核メンバー。',
  'CEO':                '最高経営責任者（Chief Executive Officer）。会社経営全体の最終責任者。',
  'COO':                '最高執行責任者（Chief Operating Officer）。日々のオペレーション統括。',
  'CTO':                '最高技術責任者（Chief Technology Officer）。技術戦略・技術組織の最高責任者。',
  'CFO':                '最高財務責任者（Chief Financial Officer）。財務・資金調達・投資家対応。',
  'CMO':                '最高マーケティング責任者（Chief Marketing Officer）。マーケ全体を統括。',
  'CPO':                '最高プロダクト責任者（Chief Product Officer）。プロダクト戦略を統括。',
  'CRO':                '最高収益責任者（Chief Revenue Officer）。売上・収益全般を統括。',
  'CSO':                '最高戦略責任者 / 最高セキュリティ責任者（Chief Strategy/Security Officer）。',
  'CIO':                '最高情報責任者（Chief Information Officer）。情報システム・IT統括。',
  'Chief Architect':    '最高アーキテクト。技術設計の頂点。',
  'Chief Scientist':    '最高科学責任者。研究・科学的探索を統括。',
  'VP of Engineering':  'エンジニアリング担当VP。技術組織のマネジメント責任者。',
  'VP of Product':      'プロダクト担当VP。PM組織を統括。',
  'VP of Sales':        'セールス担当VP。営業組織を統括。',
  'VP of Marketing':    'マーケティング担当VP。マーケ組織を統括。',
  // 取締役会・経営層
  'Chairman / 会長':                    '取締役会の議長。経営トップの一人。',
  '取締役 / Director':                  '取締役。経営の意思決定機関の一員。',
  '社外取締役 / Outside Director':      '社外から招かれた取締役。客観的視点を提供。',
  '監査役 / Auditor':                   '取締役の業務を監査。',
  '執行役員 / Executive Officer':       '取締役会の決定を執行する役員。',
  '本部長':                             '部門の最高責任者。複数部を束ねる。',
  '部長 / General Manager':             '部の責任者。',
  '課長 / Section Manager':             '課の責任者。',
  '主任 / Chief':                       'チームの主任。現場リーダー。',
  // エンジニア (役職レベル)
  'Engineering Manager': 'エンジニアチームのマネージャー。',
  'Tech Lead':           '技術リード。技術判断を主導する。',
  'Principal Engineer':  'プリンシパル。組織横断の技術リーダー。',
  'Staff Engineer':      'スタッフ。シニア以上の高度技術職。',
  'Senior Engineer':     'シニア。中堅以上の技術職。',
  'Engineer':            '一般エンジニア。',
  'Junior Engineer':     'ジュニア。経験浅めのエンジニア。',
  // エンジニア (専門領域)
  'Edge Engineer':                     'エッジAI・端末側で動作するシステムを開発。',
  'Embedded Engineer':                 '組み込みシステム開発。',
  'Firmware Engineer':                 'ファームウェア開発。',
  'Hardware Engineer':                 'ハードウェア設計全般。',
  'Electrical Engineer':               '電気回路設計。',
  'Mechanical Engineer':               '機械設計。',
  'PCB Designer':                      'プリント基板（PCB）設計。',
  'FPGA Engineer':                     'FPGA論理設計。',
  'Robotics Engineer':                 'ロボティクス開発。',
  'Backend Engineer':                  'サーバーサイド開発。',
  'Frontend Engineer':                 'クライアント側 UI 開発。',
  'Full-stack Engineer':               'フロントエンド・バックエンド両方を扱う。',
  'Mobile Engineer':                   'iOS / Android アプリ開発。',
  'ML / AI Engineer':                  '機械学習・AI モデル実装。',
  'ML Researcher':                     'ML研究者。新手法の探索。',
  'Vision / VLM Engineer':             '画像認識・VLM 専門エンジニア。',
  'Data Engineer':                     'データ基盤・パイプライン構築。',
  'Data Scientist':                    'データ分析・モデリング。',
  'DevOps / SRE':                      'インフラ・信頼性エンジニアリング。',
  'Platform Engineer':                 '社内プラットフォーム開発。',
  'Cloud Engineer':                    'クラウドインフラ。',
  'Security Engineer':                 'セキュリティ設計・運用。',
  'QA / Test Engineer':                '品質保証・テスト自動化。',
  'Solutions Architect':               '顧客向けソリューション設計。',
  'Sales Engineer':                    '営業同行・技術提案。',
  'Field Application Engineer (FAE)':  '現場での技術サポート（半導体・機器）。',
  'Manufacturing Engineer':            '製造工程設計。',
  'Field / Service Engineer':          '現場保守・修理。',
  // 製造・組立
  '工場長 / Plant Manager':                       '工場全体の責任者。',
  '製造責任者 / Production Manager':              '製造業務の統括。',
  '生産技術 / Industrial Engineer':               '生産技術・工程効率化。',
  '工程エンジニア / Process Engineer':            '工程設計・改善。',
  '生産計画 / Production Planner':                '生産計画立案。',
  '製造リーダー / Production Lead':               '製造チームのリーダー。',
  '現場監督 / Shop Floor Supervisor':             '現場の監督。',
  '組立技術者 / Assembly Technician':             '組立作業の技術者。',
  '製造オペレーター / Production Operator':       '製造ライン作業者。',
  '治具設計 / Jig Designer':                      '治具・工具の設計。',
  'ライン技術者 / Line Engineer':                 '製造ラインの技術者。',
  // 品質保証・テスト
  '品質保証責任者 / QA Manager':                            'QA部門の責任者。',
  '品質管理 / QC Manager':                                  'QC部門の責任者。',
  'QC技術者 / QC Technician':                               '品質管理技術者。',
  'テスト技術者 / Test Technician':                         'テスト実施担当。',
  '校正エンジニア / Calibration Engineer':                  '計測機器の校正。',
  '信頼性エンジニア / Reliability Engineer':                '信頼性試験・解析。',
  '故障解析エンジニア / Failure Analysis Engineer':         '故障原因の解析。',
  'インスペクター / Inspector':                             '検査担当。',
  '受入検査 / Incoming Inspector':                          '受入時の検査。',
  '出荷検査 / Final Inspector':                             '出荷前の最終検査。',
  // 調達・SCM・物流
  'SCM責任者 / Supply Chain Manager':       'サプライチェーン全体の統括。',
  '購買責任者 / Procurement Manager':       '購買部門の責任者。',
  'バイヤー / Buyer':                       '購買担当。',
  '資材管理 / Materials Manager':           '資材の管理。',
  'ベンダー管理 / Vendor Manager':          'ベンダー対応・管理。',
  'BOM管理 / BOM Manager':                  'BOM（部品表）管理。',
  '物流責任者 / Logistics Manager':         '物流の統括。',
  '倉庫管理 / Warehouse Manager':           '倉庫管理。',
  '在庫管理 / Inventory Control':           '在庫管理。',
  '出荷受入 / Shipping & Receiving':        '出荷・受入業務。',
  // フィールド・保守
  'フィールドサービス責任者 / Field Service Manager':                'フィールドサービスの統括。',
  'サービス技術者 / Service Technician':                             '現場サービス技術者。',
  '設置技術者 / Installation Technician':                            '設置担当。',
  '修理技術者 / Repair Technician':                                  '修理担当。',
  '巡回保守 / On-site Maintenance':                                  '巡回保守。',
  'カスタマーサービスエンジニア / Customer Service Engineer':        '顧客向け技術サポート。',
  'コールセンター / Help Desk':                                      '電話・メール窓口。',
  // プロダクト
  'Chief Product Officer':       '最高プロダクト責任者。',
  'Head of Product':             'プロダクト統括長。',
  'Senior Product Manager':      'シニアPM。',
  'Product Manager':             'プロダクトマネージャー。',
  'Associate PM':                'アソシエイトPM（PM見習い・若手）。',
  'Technical PM':                '技術寄りのPM。',
  'Product Marketing Manager':   'プロダクトマーケティングPM。',
  // デザイン
  'Design Lead':         'デザインリード。',
  'Product Designer':    'プロダクトデザイナー。',
  'UX Designer':         'UX設計担当。',
  'UI Designer':         'UI設計担当。',
  'Visual Designer':     'ビジュアル特化のデザイナー。',
  'Brand Designer':      'ブランド・グラフィック。',
  'Industrial Designer': '工業・プロダクトデザイン。',
  'Motion Designer':     'モーション・アニメーション。',
  // 事業・セールス
  'Head of Sales':                 'セールス統括長。',
  'Sales / 営業':                  '一般営業職。',
  'Enterprise Sales':              '大企業向け営業。',
  'Inside Sales':                  '内勤営業。',
  'Field Sales':                   '外勤営業。',
  'Account Executive':             'AE。案件成約担当。',
  'Account Manager':               'AM。既存顧客の継続担当。',
  'Sales Development (SDR)':       'リードの育成・初期接触。',
  'Business Development / 事業開発': '新規事業・パートナー開発。',
  'Channel / Partner Sales':       'パートナー経由の営業。',
  'Customer Success':              'CS。顧客の活用支援。',
  'Customer Support':              'サポート対応。',
  'Technical Support':             '技術サポート。',
  // マーケティング・PR
  'Head of Marketing':   'マーケ統括長。',
  'Marketing':           'マーケ担当。',
  'Product Marketing':   'プロダクトマーケ。',
  'Content Marketing':   'コンテンツマーケ。',
  'Growth Marketer':     'グロース担当。',
  'Demand Gen':          'デマンドジェネレーション（需要創出）。',
  'SEO Specialist':      'SEO専門。',
  'Brand / PR':          'ブランド・広報。',
  'Communications':      'コミュニケーション全般。',
  'Community Manager':   'コミュニティ運営。',
  // 法務・知財
  'General Counsel / 法務責任者':       '法務部門のトップ。',
  'Legal / Compliance':                 '法務・コンプラ担当。',
  'Patent Attorney / 弁理士':           '特許・商標の専門家。',
  'IP Manager / 知財責任者':            '知財全般を統括。',
  'Compliance Officer':                 'コンプライアンス担当。',
  // 財務・経理
  'Head of Finance':       '財務統括長。',
  'Finance / 経理':        '財務・経理担当。',
  'Controller':            'コントローラー（会計責任者）。',
  'FP&A Analyst':          'FP&A（財務計画・分析）アナリスト。',
  'Accountant / 会計士':   '会計士。',
  'Tax Manager':           '税務担当。',
  // 人事・総務
  'Head of People':         '人事統括長。',
  'HR / 人事':              '人事担当。',
  'HR Business Partner':    'HRBP。事業部に張り付く人事。',
  'Recruiter / 採用':       '採用担当。',
  'Talent Acquisition':     'タレントアクイジション（採用戦略）。',
  'People Ops':             '人事オペレーション。',
  'Operations / 総務':      '総務担当。',
  'Office Manager':         'オフィスマネージャー。',
  'Executive Assistant':    'EA。役員秘書。',
  // データ・分析
  'Head of Data':         'データ統括長。',
  'Data Analyst':         'データ分析担当。',
  'Business Analyst':     'BA。業務分析。',
  'Analytics Engineer':   'アナリティクスエンジニア（dbt等）。',
  'BI Engineer':          'BI設計担当。',
  // 社外・パートナー
  'Strategic Partner':         '戦略的パートナー。',
  'Advisor / 顧問':            '顧問。',
  'Board Member':              'ボードメンバー。',
  'Investor / 投資家':         '投資家。',
  'Mentor':                    'メンター。',
  'Contractor / 業務委託':     '業務委託契約。',
  'Intern / インターン':       'インターン。',
  // その他
  'Other': 'リストにない役職用のフリー枠。',
};

const SKILL_GROUPS = [
  { label: '技術 (コア)',         options: ['システム設計', 'カスケード推論', 'VLM / RAG', 'YOLO / 物体検出', 'CLIP / Embedding', 'AI / ML実装', '推論最適化'] },
  { label: '技術 (Edge/HW)',     options: ['組み込み / Embedded', 'RK3588 / Edge HW', 'GStreamer', 'NVMe / Storage', 'カメラ / 画像処理', 'ハードウェア検証'] },
  { label: '技術 (Backend)',     options: ['バックエンド', 'MQTT / IoT通信', 'フリート管理 / OTA', 'DB設計', 'DevOps', 'セキュリティ', 'クラウド'] },
  { label: '技術 (Frontend)',    options: ['フロントエンド', 'モバイル', 'UI実装'] },
  { label: '技術 (製造/品質)',   options: [
    // 設計
    '機械設計', 'CAD / 3D', '構造設計', '回路設計', '基板設計',
    // 試作・治具
    '試作 / プロトタイピング', '治具設計',
    // 量産・工程
    '生産技術', '工程設計', '量産設計', '製造装置',
    // 品質
    '品質管理 / QC', '品質保証 / QA', '検査・テスト', '信頼性試験', '安全規格 / EMC',
    // 調達・サプライチェーン
    '調達 / SCM', '購買', 'サプライヤー管理',
    // 工場運営
    '工場立ち上げ', 'リーン製造 / TPS', '5S / 改善',
  ] },
  { label: '設計・ドキュメント',   options: ['UI/UXデザイン', 'PoC設計', '技術文書', 'ドキュメント整備', 'API設計'] },
  { label: '事業・法務',          options: ['特許戦略', '法務 / コンプラ', '営業 / セールス', 'プレゼン / ピッチ', '顧客開拓', '資金調達', 'マーケティング'] },
  { label: 'ドメイン知識',        options: [
    // ホスピタリティ・サービス
    'ホスピタリティ設計', '飲食業界', '観光 / 旅行', 'スポーツ / フィットネス', '美容 / コスメ', 'ファッション',
    // 医療・福祉・教育・公共
    '医療業界', '介護業界', '保育', '教育 / 学習', '公共 / 行政',
    // 小売・流通・物流
    '小売業界', 'EC / D2C', '物流 / 配送', '倉庫 / 仕分け',
    // 製造・素材・モビリティ
    '工場 / 産業', '製造業 / メーカー', '半導体', '化学 / 素材', '精密機器', '自動車 / モビリティ',
    // 建設・インフラ・エネルギー・通信
    '建設 / 建築', '不動産', 'エネルギー / 電力', '太陽光発電', '通信 / キャリア',
    // 金融
    '金融 / 銀行', '保険', 'フィンテック',
    // メディア・エンタメ
    'メディア / 出版', 'エンタメ / ゲーム', 'イベント',
    // 一次産業
    '農業', '畜産', '水産 / 漁業', '林業',
    // 専門・公共
    '警備 / 防災', 'リサイクル / 環境', 'ペット / 動物',
    // テック・業態
    'SaaS / B2B Tech', 'コンシューマー Tech', 'スタートアップ',
  ] },
  { label: 'ソフトスキル',        options: ['リーダーシップ', 'メンタリング', 'スピード実装', 'プロジェクト管理', 'チームビルディング', '英語'] },
];

// ============================================================
//   Color palette — 14色（役職カテゴリ用）
// ============================================================
const COLORS = {
  amber:   { bg: 'bg-amber-500',   chip: 'bg-amber-500/20 text-amber-200 border-amber-400/40' },
  orange:  { bg: 'bg-orange-500',  chip: 'bg-orange-500/20 text-orange-200 border-orange-400/40' },
  yellow:  { bg: 'bg-yellow-500',  chip: 'bg-yellow-500/20 text-yellow-200 border-yellow-400/40' },
  emerald: { bg: 'bg-emerald-500', chip: 'bg-emerald-500/20 text-emerald-200 border-emerald-400/40' },
  teal:    { bg: 'bg-teal-500',    chip: 'bg-teal-500/20 text-teal-200 border-teal-400/40' },
  green:   { bg: 'bg-green-500',   chip: 'bg-green-500/20 text-green-200 border-green-400/40' },
  lime:    { bg: 'bg-lime-500',    chip: 'bg-lime-500/20 text-lime-200 border-lime-400/40' },
  sky:     { bg: 'bg-sky-500',     chip: 'bg-sky-500/20 text-sky-200 border-sky-400/40' },
  blue:    { bg: 'bg-blue-500',    chip: 'bg-blue-500/20 text-blue-200 border-blue-400/40' },
  cyan:    { bg: 'bg-cyan-500',    chip: 'bg-cyan-500/20 text-cyan-200 border-cyan-400/40' },
  indigo:  { bg: 'bg-indigo-500',  chip: 'bg-indigo-500/20 text-indigo-200 border-indigo-400/40' },
  violet:  { bg: 'bg-violet-500',  chip: 'bg-violet-500/20 text-violet-200 border-violet-400/40' },
  fuchsia: { bg: 'bg-fuchsia-500', chip: 'bg-fuchsia-500/20 text-fuchsia-200 border-fuchsia-400/40' },
  pink:    { bg: 'bg-pink-500',    chip: 'bg-pink-500/20 text-pink-200 border-pink-400/40' },
  rose:    { bg: 'bg-rose-500',    chip: 'bg-rose-500/20 text-rose-200 border-rose-400/40' },
  red:     { bg: 'bg-red-500',     chip: 'bg-red-500/20 text-red-200 border-red-400/40' },
  slate:   { bg: 'bg-slate-500',   chip: 'bg-slate-500/20 text-slate-200 border-slate-400/40' },
  stone:   { bg: 'bg-stone-500',   chip: 'bg-stone-500/20 text-stone-200 border-stone-400/40' },
};

// 役職カテゴリ → カラー対応
const CATEGORY_TO_COLOR = {
  '経営・C-level':           'amber',
  '取締役会・経営層':         'orange',
  'エンジニア (役職レベル)':   'emerald',
  'エンジニア (専門領域)':     'teal',
  '製造・組立':              'blue',
  '品質保証・テスト':         'green',
  '調達・SCM・物流':         'yellow',
  'フィールド・保守':         'red',
  'プロダクト':              'sky',
  'デザイン':                'violet',
  '事業・セールス':           'rose',
  'マーケティング・PR':       'fuchsia',
  '法務・知財':              'slate',
  '財務・経理':              'lime',
  '人事・総務':              'cyan',
  'データ・分析':             'indigo',
  '社外・パートナー':         'pink',
  'その他':                  'stone',
};

// role 文字列 → カテゴリ名
const ROLE_TO_CATEGORY = (() => {
  const map = {};
  ROLE_GROUPS.forEach((g) => {
    g.options.forEach((opt) => { map[opt] = g.label; });
  });
  return map;
})();

const getCategory = (role) => ROLE_TO_CATEGORY[role] || 'その他';
const getColorKey = (role) => CATEGORY_TO_COLOR[getCategory(role)] || 'stone';
const getColors   = (role) => COLORS[getColorKey(role)];

// ============================================================
//   Persona 推定 — スキル組み合わせから一行ペルソナを生成
// ============================================================
const SKILL_TO_CATEGORY = (() => {
  const map = {};
  SKILL_GROUPS.forEach((g) => g.options.forEach((opt) => { map[opt] = g.label; }));
  return map;
})();

const getPersona = (member) => {
  const skills = member?.skills || [];
  if (skills.length === 0) return '';
  const has = (s) => skills.includes(s);

  // 特徴的なスキル組み合わせ → 名付きペルソナ（評価順序が優先度）
  // ─ AI / Vision / Edge コア技術
  if (has('カスケード推論') || has('VLM / RAG') || has('AI / ML実装')) return 'AIビルダー';
  if (has('YOLO / 物体検出') || has('CLIP / Embedding')) return 'ビジョンエンジニア';
  if (has('RK3588 / Edge HW') || has('カメラ / 画像処理')) return 'Edgeエンジニア';
  if (has('ハードウェア検証') && has('組み込み / Embedded')) return 'ハードエンジニア';

  // ─ アーキ / リード
  if (has('システム設計') && (has('リーダーシップ') || has('プロジェクト管理'))) return 'アーキテクト';

  // ─ 製造・品質系
  if (has('生産技術') || has('工程設計') || has('量産設計') || has('工場立ち上げ')) return 'プロダクションエンジニア';
  if (has('機械設計') || has('CAD / 3D') || has('構造設計')) return 'メカエンジニア';
  if (has('回路設計') || has('基板設計')) return '回路エンジニア';
  if (has('品質管理 / QC') || has('品質保証 / QA') || has('検査・テスト') || has('信頼性試験')) return 'QAエキスパート';
  if (has('調達 / SCM') || has('購買') || has('サプライヤー管理')) return 'SCMマネージャー';
  if (has('リーン製造 / TPS') || has('5S / 改善')) return 'カイゼンリーダー';
  if (has('試作 / プロトタイピング') || has('治具設計')) return 'プロトメーカー';

  // ─ UI / Frontend
  if (has('UI/UXデザイン') && (has('フロントエンド') || has('UI実装'))) return 'UIエンジニア';

  // ─ 事業 / 法務
  if (has('特許戦略') || has('法務 / コンプラ')) return 'IPストラテジスト';
  if (has('資金調達') || has('プレゼン / ピッチ')) return 'ピッチャー';
  if (has('営業 / セールス') || has('顧客開拓')) return 'セールスリード';

  // ─ マネジメント
  if (has('プロジェクト管理') && has('リーダーシップ')) return 'PMリード';
  if (has('チームビルディング') || has('メンタリング')) return 'メンター';
  if (has('スピード実装')) return 'スプリンター';

  // ─ その他テック
  if (has('技術文書') || has('ドキュメント整備')) return 'ドキュメンタリスト';
  if (has('セキュリティ')) return 'セキュリティ番';
  if (has('DB設計') || has('バックエンド')) return 'バックエンド型';
  if (has('フロントエンド') || has('モバイル')) return 'フロントエンド型';

  // フォールバック: 最多カテゴリから雰囲気を出す
  const counts = {};
  skills.forEach((s) => {
    const cat = SKILL_TO_CATEGORY[s] || 'カスタム';
    counts[cat] = (counts[cat] || 0) + 1;
  });
  const top = Object.entries(counts).sort((a, b) => b[1] - a[1])[0]?.[0];
  const fallback = {
    '技術 (コア)':       'コア技術派',
    '技術 (Edge/HW)':    'Edge派',
    '技術 (Backend)':    'Backend派',
    '技術 (Frontend)':   'Frontend派',
    '技術 (製造/品質)':  '製造系',
    '設計・ドキュメント': '設計派',
    '事業・法務':        '事業派',
    'ドメイン知識':      '業界エキスパート',
    'ソフトスキル':      'リード型',
    'カスタム':          'スペシャリスト',
  };
  return fallback[top] || 'スペシャリスト';
};

// スキルから人物像の短文サマリーを生成（INFO バーで役職解説と入れ替え）
const generateMemberSummary = (member) => {
  const skills = member?.skills || [];
  if (skills.length === 0) return '';

  const has = (s) => skills.includes(s);
  const hasAny = (...arr) => arr.some((s) => has(s));
  const persona = getPersona(member);

  // ── 技術系の主軸 ──
  const tech = [];
  if (hasAny('カスケード推論', 'VLM / RAG', 'AI / ML実装')) tech.push('AI実装');
  if (hasAny('YOLO / 物体検出', 'CLIP / Embedding')) tech.push('画像認識');
  if (has('推論最適化')) tech.push('推論最適化');
  if (hasAny('組み込み / Embedded', 'RK3588 / Edge HW', 'カメラ / 画像処理', 'GStreamer')) tech.push('Edgeハード');
  if (has('ハードウェア検証')) tech.push('ハード検証');
  if (hasAny('バックエンド', 'DB設計', 'API設計')) tech.push('バックエンド');
  if (hasAny('MQTT / IoT通信', 'フリート管理 / OTA')) tech.push('IoT通信');
  if (hasAny('クラウド', 'DevOps')) tech.push('クラウド');
  if (has('セキュリティ')) tech.push('セキュリティ');
  if (hasAny('フロントエンド', 'モバイル', 'UI実装')) tech.push('UI実装');
  if (has('UI/UXデザイン')) tech.push('UI/UX');
  // 製造・品質
  if (hasAny('機械設計', 'CAD / 3D', '構造設計')) tech.push('メカ設計');
  if (hasAny('回路設計', '基板設計')) tech.push('回路設計');
  if (hasAny('生産技術', '工程設計', '量産設計', '工場立ち上げ')) tech.push('量産化');
  if (hasAny('品質管理 / QC', '品質保証 / QA', '検査・テスト', '信頼性試験')) tech.push('品質管理');
  if (hasAny('調達 / SCM', '購買', 'サプライヤー管理')) tech.push('SCM');
  if (hasAny('リーン製造 / TPS', '5S / 改善')) tech.push('改善活動');
  if (hasAny('試作 / プロトタイピング', '治具設計')) tech.push('試作');

  // ── 事業系 ──
  const biz = [];
  if (has('特許戦略')) biz.push('知財戦略');
  if (has('法務 / コンプラ')) biz.push('法務');
  if (hasAny('営業 / セールス', '顧客開拓')) biz.push('営業');
  if (hasAny('プレゼン / ピッチ', '資金調達')) biz.push('ピッチ/資金調達');
  if (has('マーケティング')) biz.push('マーケ');

  // ── ソフトスキル ──
  const soft = [];
  if (has('リーダーシップ')) soft.push('リーダー気質');
  if (has('プロジェクト管理')) soft.push('PM志向');
  if (has('メンタリング')) soft.push('メンター気質');
  if (has('スピード実装')) soft.push('スピード重視');
  if (has('チームビルディング')) soft.push('チーム作り');
  if (has('英語')) soft.push('英語可');

  // ── ドメイン ──
  const domains = skills.filter((s) => SKILL_TO_CATEGORY[s] === 'ドメイン知識');

  // ── 文章組み立て ──
  const parts = [];
  if (tech.length > 0) parts.push(`${tech.slice(0, 3).join('・')}を主軸`);
  if (biz.length > 0)  parts.push(`${biz.slice(0, 2).join('・')}にも明るい`);
  if (soft.length > 0) parts.push(soft[0]);
  if (domains.length > 0) parts.push(`${domains.slice(0, 2).join('・')}領域の経験`);

  let body;
  if (parts.length > 0) {
    body = parts.join('・');
  } else {
    body = `${skills.slice(0, 4).join('・')}を持つ`;
  }

  return persona ? `【${persona}】${body}。` : `${body}。`;
};

// 1〜99 から空き背番号をランダムに1つ返す（埋まっていれば null）
const findFreeNumber = (taken) => {
  const free = [];
  for (let i = 1; i <= 99; i++) if (!taken.has(i)) free.push(i);
  if (free.length === 0) return null;
  return free[Math.floor(Math.random() * free.length)];
};

// ============================================================
//   カスタムスキル → 既存カテゴリへ自動振り分け（キーワード推定）
// ============================================================
const guessSkillCategory = (skill) => {
  const s = String(skill).toLowerCase();
  // 技術 (コア) ─ AI / ML / 推論基盤
  if (/(\bai\b|\bml\b|llm|gpt|claude|gemini|rag|embedding|機械学習|深層学習|推論|生成ai|モデル|プロンプト|nlp|chat\s*bot|transformer|拡散|diffusion|stable|sora)/i.test(s)) return '技術 (コア)';
  // 技術 (Edge/HW) ─ 端末 / センサー / 組み込み
  if (/(arm|risc|fpga|asic|gpu|cuda|tegra|jetson|\bedge\b|esp32|esp8266|raspberry|sensor|imu|lidar|radar|microcontroller|stm32|\bmcu\b|rtos|firmware|gstreamer|nvme|ssd|hdmi|csi|usb|i2c|spi|uart)/i.test(s)) return '技術 (Edge/HW)';
  // 技術 (Backend) ─ サーバー / クラウド / DB
  if (/(api|rest|graphql|grpc|kubernetes|\bk8s\b|docker|terraform|ansible|aws|gcp|azure|gke|eks|lambda|cloud\s*run|sql|nosql|mongo|postgres|mysql|redis|kafka|rabbit|bigquery|snowflake|databricks|airflow|spark|hadoop|node\.?js|python|django|flask|fastapi|\bgo\b|golang|rust|spring|laravel|backend|server)/i.test(s)) return '技術 (Backend)';
  // 技術 (Frontend) ─ Web / モバイル
  if (/(react|vue|angular|svelte|solid|nextjs|next\.js|nuxt|remix|tailwind|css|html|sass|webpack|vite|three\.?js|frontend|typescript|javascript|jsx|tsx|swift|swiftui|kotlin|flutter|jetpack|android|\bios\b|reactnative|expo|electron|web\s*gl)/i.test(s)) return '技術 (Frontend)';
  // 技術 (製造/品質)
  if (/(製造|生産|加工|金型|溶接|プレス|樹脂|金属|塗装|めっき|鋳造|射出成形|cad|cam|cae|solidworks|fusion\s*360|catia|品質|qc|qa|iso\s*9001|jis|検査|試験|smt|半田|はんだ|プリント基板|\bpcb\b|3d\s*プリン|サプライヤ|調達|scm|発注|工程|治具|品証|内製|外注|\biatf\b)/i.test(s)) return '技術 (製造/品質)';
  // 設計・ドキュメント
  if (/(設計|仕様|spec|figma|sketch|adobe\s*xd|miro|notion|confluence|markdown|アーキテクチャ|architecture|プロトタイプ|ワイヤーフレーム|api\s*設計|er\s*図|dfd)/i.test(s)) return '設計・ドキュメント';
  // 事業・法務
  if (/(営業|法務|契約|特許|商標|意匠|セールス|マーケ|広告|\bpr\b|ピッチ|資金|ファイナンス|事業|商談|提案書|crm|hubspot|salesforce|nda|mou|ipo|m&a|デューデリ)/i.test(s)) return '事業・法務';
  // ドメイン知識
  if (/(業界|医療|介護|福祉|保育|飲食|小売|工場|製造業|不動産|金融|銀行|保険|教育|学校|塾|農業|畜産|漁業|林業|物流|配送|倉庫|建設|建築|土木|エネルギー|電力|ガス|通信|キャリア|半導体|化学|素材|精密|自動車|モビリティ|観光|旅行|スポーツ|フィットネス|美容|コスメ|ファッション|アパレル|メディア|出版|エンタメ|ゲーム|イベント|警備|防災|リサイクル|環境|ペット|動物|saas|スタートアップ|公共|行政|自治体|フィンテック|fintech|d2c|\bec\b)/i.test(s)) return 'ドメイン知識';
  // ソフトスキル
  if (/(リーダー|leadership|管理|マネジメント|management|英語|外国語|多言語|交渉|negotiation|プレゼン|presentation|コーチ|coaching|teach|教える|傾聴|ファシリ|オンボーディング|チームビルディング|メンタリング|スピード|hustle)/i.test(s)) return 'ソフトスキル';

  return 'その他（カスタム）';
};

// ============================================================
//   Defaults
// ============================================================
const DEFAULT_MEMBERS = [
  { id: 'm-yuma',   name: 'Yuma',     role: 'CTO',                number: 10, skills: ['システム設計', 'カスケード推論', '特許戦略', 'リーダーシップ'] },
  { id: 'm-shinjo', name: '新城',      role: 'Advisor / 顧問',      number: 4,  skills: ['ハードウェア検証', '組み込み / Embedded', 'メンタリング'] },
  { id: 'm-edge',   name: 'Edge Dev', role: 'Edge Engineer',      number: 6,  skills: ['RK3588 / Edge HW', '組み込み / Embedded', 'バックエンド'] },
  { id: 'm-ui',     name: 'UI Dev',   role: 'UI Designer',        number: 7,  skills: ['UI/UXデザイン', 'フロントエンド'] },
  { id: 'm-tada',   name: '多田精機',   role: 'Strategic Partner',  number: 2,  skills: ['ハードウェア検証', '工場 / 産業'] },
];

const DEFAULT_PROJECT = {
  id: 'p-default',
  name: 'EdgeSense Phase 1',
  formation: '4-3-3',
  assignments: {},
  connections: [],
  target: '',
  risk: '',
};

// ============================================================
//   Storage
// ============================================================
const KEYS = {
  members:      'esai-squad:members',
  projects:     'esai-squad:projects',
  current:      'esai-squad:current',
  customSkills: 'esai-squad:custom-skills',
};

async function loadKey(key, fallback) {
  try {
    const r = await window.storage.get(key);
    if (!r) return fallback;
    return JSON.parse(r.value);
  } catch {
    return fallback;
  }
}
async function saveKey(key, value) {
  try { await window.storage.set(key, JSON.stringify(value)); } catch {}
}

// ============================================================
//   Utils
// ============================================================
const initials = (name) => {
  if (!name) return '?';
  const parts = name.trim().split(/\s+/);
  if (parts.length === 1) return parts[0].slice(0, 2).toUpperCase();
  return (parts[0][0] + parts[parts.length - 1][0]).toUpperCase();
};
const newId = (prefix) => `${prefix}-${Math.random().toString(36).slice(2, 8)}-${Date.now().toString(36).slice(-3)}`;

// ============================================================
//   Member Card (bench)
// ============================================================
function MemberCard({ member, onDragStart, onClick, selected, onEdit }) {
  const c = getColors(member.role);
  const skills = member.skills || [];
  return (
    <div
      draggable
      onDragStart={(e) => onDragStart && onDragStart(e, member.id)}
      onClick={() => onClick && onClick(member.id)}
      className={`group relative cursor-grab active:cursor-grabbing select-none transition-all duration-200 w-full
        ${selected ? 'scale-105 -translate-y-1' : 'hover:-translate-y-0.5'}`}
    >
      <div className={`relative rounded-xl border bg-slate-900/80 backdrop-blur-sm overflow-hidden
        ${selected ? 'border-amber-400 shadow-lg shadow-amber-500/30' : 'border-slate-700 hover:border-slate-500'}`}
      >
        <div className={`h-1 ${c.bg}`} />
        <div className="p-3 flex items-start gap-3">
          <div className={`shrink-0 w-11 h-11 rounded-lg ${c.bg} flex items-center justify-center font-bold text-white text-base shadow-inner`}
               style={{ fontFamily: "'Oswald', sans-serif" }}>
            {member.number != null && member.number !== '' ? member.number : initials(member.name)}
          </div>
          <div className="min-w-0 flex-1">
            <div className="font-bold text-slate-100 text-sm truncate" style={{ fontFamily: "'Oswald', sans-serif", letterSpacing: '0.02em' }}>
              {member.name}
            </div>
            <div className="text-[10px] text-slate-400 truncate uppercase tracking-wider mb-1">{member.role || '—'}</div>
            {skills.length > 0 && (
              <div className="flex flex-wrap gap-1">
                {skills.slice(0, 2).map((s) => (
                  <span key={s} className={`text-[9px] px-1.5 py-0.5 rounded border ${c.chip} font-medium tracking-tight`}>
                    {s}
                  </span>
                ))}
                {skills.length > 2 && (
                  <span className="text-[9px] px-1 py-0.5 text-slate-500 font-medium">+{skills.length - 2}</span>
                )}
              </div>
            )}
          </div>
          {onEdit && (
            <button
              onClick={(e) => { e.stopPropagation(); onEdit(member); }}
              className="opacity-0 group-hover:opacity-100 text-slate-400 hover:text-amber-300 transition-opacity p-1"
              title="編集"
            >
              <Edit2 size={14} />
            </button>
          )}
        </div>
      </div>
    </div>
  );
}

// ============================================================
//   Field Slot (on pitch) - hover で skills を上アーク展開、connect mode 対応
// ============================================================
function FieldSlot({ slot, member, onDrop, onDragStart, onDragOverSlot, onDragLeaveSlot, onClick, onClear, onEdit, isHover, isSwapTarget, connectMode = false, isConnectFirst = false }) {
  const [hovered, setHovered] = useState(false);
  const c = member ? getColors(member.role) : null;
  const skills = member ? (member.skills || []).slice(0, 8) : [];

  return (
    <div
      onDragOver={(e) => {
        e.preventDefault();
        onDragOverSlot && onDragOverSlot(slot.id);
      }}
      onDragLeave={(e) => {
        // 子要素移動誤発火を防ぐため relatedTarget チェック
        if (e.currentTarget.contains(e.relatedTarget)) return;
        onDragLeaveSlot && onDragLeaveSlot(slot.id);
      }}
      onDrop={(e) => { e.preventDefault(); onDrop(slot.id, e); }}
      onClick={() => onClick(slot.id)}
      className="absolute group pointer-events-auto"
      style={{
        left: `${slot.x}%`,
        top: `${slot.y}%`,
        transform: 'translate(-50%, -50%)',
        zIndex: hovered || isConnectFirst || isSwapTarget ? 40 : 10,
      }}
    >
      {member ? (
        <div
          draggable={!connectMode}
          onDragStart={(e) => !connectMode && onDragStart(e, member.id, slot.id)}
          onMouseEnter={() => setHovered(true)}
          onMouseLeave={() => setHovered(false)}
          className={`relative ${connectMode ? 'cursor-pointer' : 'cursor-grab active:cursor-grabbing'}`}
        >
          {/* Skill ring — hover で上アーク (200°) に展開、軌道大きめ */}
          {skills.length > 0 && (
            <div
              className="absolute left-1/2 top-1/2 pointer-events-none"
              style={{ '--orbit': 'clamp(82px, 15vw, 110px)' }}
            >
              {skills.map((skill, i) => {
                const N = skills.length;
                // 上方向 200° のアーク。中心は -π/2 (真上)
                const arcSpan = (200 * Math.PI) / 180;
                const arcCenter = -Math.PI / 2;
                const arcStart = arcCenter - arcSpan / 2;
                const angle = N === 1 ? arcCenter : arcStart + (i / (N - 1)) * arcSpan;
                const cos = Math.cos(angle).toFixed(3);
                const sin = Math.sin(angle).toFixed(3);
                return (
                  <div
                    key={skill}
                    className="absolute whitespace-nowrap font-bold px-2 py-0.5 rounded-full backdrop-blur-md bg-slate-900/95 text-white border-2 border-white/70 shadow-xl"
                    style={{
                      left: 0,
                      top: 0,
                      fontSize: 'clamp(9px, 1.6vw, 11px)',
                      fontFamily: "'Inter', sans-serif",
                      maxWidth: '160px',
                      transform: hovered
                        ? `translate(-50%, -50%) translate(calc(${cos} * var(--orbit)), calc(${sin} * var(--orbit))) scale(1)`
                        : 'translate(-50%, -50%) scale(0.2)',
                      opacity: hovered ? 1 : 0,
                      transition: `transform 350ms cubic-bezier(.34,1.56,.64,1), opacity 250ms ease-out`,
                      transitionDelay: hovered ? `${i * 30}ms` : '0ms',
                    }}
                  >
                    {skill}
                  </div>
                );
              })}
            </div>
          )}

          {/* ポジション名（常時表示） */}
          <div
            className="absolute left-1/2 -top-3 -translate-x-1/2 px-1.5 py-0 rounded bg-amber-400 text-slate-900 font-black tracking-widest shadow pointer-events-none z-10"
            style={{ fontFamily: "'Oswald', sans-serif", fontSize: 'clamp(9px, 1.7vw, 11px)' }}
          >
            {slot.label}
          </div>

          {/* Connect-first 用パルスリング */}
          {isConnectFirst && (
            <div className="absolute inset-0 rounded-full pointer-events-none">
              <div className="absolute -inset-2 rounded-full border-4 border-amber-400 animate-ping" />
              <div className="absolute -inset-1.5 rounded-full border-2 border-amber-300" />
            </div>
          )}

          {/* 円本体 */}
          <div
            className={`relative rounded-full ${c.bg} border-[3px] border-white/95 shadow-2xl shadow-black/70 flex items-center justify-center font-black text-white transition-transform group-hover:scale-110
              ${isConnectFirst ? 'ring-4 ring-amber-300' : 'ring-2 ring-black/40'}
              ${connectMode && !isConnectFirst ? 'hover:ring-4 hover:ring-amber-400/60' : ''}
              ${isSwapTarget ? 'esai-wiggle scale-110 ring-4 ring-amber-300' : ''}`}
            style={{
              width: 'clamp(60px, 13vw, 84px)',
              height: 'clamp(60px, 13vw, 84px)',
              fontSize: 'clamp(22px, 4.8vw, 30px)',
              fontFamily: "'Oswald', sans-serif",
              letterSpacing: '0.02em',
            }}
          >
            {member.number != null && member.number !== '' ? member.number : initials(member.name)}
            {!connectMode && (
              <>
                <button
                  onClick={(e) => { e.stopPropagation(); onEdit && onEdit(member); }}
                  className="absolute -top-1.5 -left-1.5 w-6 h-6 rounded-full bg-slate-900 border border-slate-600 text-slate-300 hover:bg-amber-400 hover:text-slate-900 hover:border-amber-300 opacity-0 group-hover:opacity-100 transition-all flex items-center justify-center z-20"
                  title="メンバー編集"
                >
                  <Edit2 size={11} strokeWidth={3} />
                </button>
                <button
                  onClick={(e) => { e.stopPropagation(); onClear(slot.id); }}
                  className="absolute -top-1.5 -right-1.5 w-6 h-6 rounded-full bg-slate-900 border border-slate-600 text-slate-300 hover:bg-rose-600 hover:text-white opacity-0 group-hover:opacity-100 transition-opacity flex items-center justify-center z-20"
                  title="ベンチに戻す"
                >
                  <X size={12} strokeWidth={3} />
                </button>
              </>
            )}
          </div>

          {/* 名前 + 役職（常時表示） */}
          <div
            className="absolute left-1/2 top-full mt-1.5 -translate-x-1/2 px-2 py-1 rounded bg-black/85 backdrop-blur-sm text-white shadow-lg pointer-events-none text-center"
            style={{ fontFamily: "'Oswald', sans-serif", letterSpacing: '0.04em' }}
          >
            <div className="font-bold whitespace-nowrap leading-tight" style={{ fontSize: 'clamp(11px, 2.2vw, 14px)' }}>
              {member.name}
            </div>
            {member.role && (
              <div
                className="font-medium whitespace-nowrap text-amber-200/95 uppercase leading-tight mt-0.5"
                style={{ fontSize: 'clamp(8px, 1.4vw, 10px)', letterSpacing: '0.08em' }}
              >
                {member.role}
              </div>
            )}
            {(() => {
              const persona = getPersona(member);
              return persona ? (
                <div
                  className="whitespace-nowrap text-emerald-300/90 leading-tight mt-0.5 italic font-medium"
                  style={{ fontSize: 'clamp(7px, 1.2vw, 9px)', letterSpacing: '0.04em', fontFamily: "'Inter', sans-serif" }}
                >
                  {persona}
                </div>
              ) : null;
            })()}
          </div>
        </div>
      ) : (
        <div className={`relative transition-all ${isHover ? 'scale-110' : ''}`}>
          <div
            className={`rounded-full border-2 border-dashed flex items-center justify-center transition-all
              ${isHover ? 'border-amber-300 bg-amber-400/25' : 'border-white/55 bg-white/5 hover:border-white/85 hover:bg-white/15'}`}
            style={{
              width: 'clamp(60px, 13vw, 84px)',
              height: 'clamp(60px, 13vw, 84px)',
            }}
          >
            <span
              className="text-white/85 font-black tracking-widest"
              style={{ fontFamily: "'Oswald', sans-serif", fontSize: 'clamp(13px, 2.6vw, 17px)' }}
            >
              {slot.label}
            </span>
          </div>
        </div>
      )}
    </div>
  );
}

// ============================================================
//   Goal Banner (target / risk) — ピッチの外に配置
// ============================================================
function GoalBanner({ kind, value, onChange }) {
  const isTarget = kind === 'target';
  const Icon = isTarget ? Target : AlertTriangle;
  const labelText = isTarget ? '目標 / TARGET' : '最大リスク / TOP RISK';
  const placeholder = isTarget ? '達成したい目標を入力...' : '回避したい最大リスクを入力...';
  const containerCls = isTarget
    ? 'bg-gradient-to-r from-amber-400 to-amber-500 text-slate-900 border-amber-300'
    : 'bg-gradient-to-r from-rose-600 to-rose-700 text-white border-rose-400';
  const placeholderCls = isTarget ? 'placeholder:text-slate-700/55' : 'placeholder:text-rose-100/55';
  const dividerCls = isTarget ? 'bg-slate-800/30' : 'bg-white/30';

  return (
    <div className={`relative w-full rounded-xl border-2 shadow-lg ${containerCls} px-3 py-2`}>
      <div className="flex items-center gap-2">
        <Icon size={15} strokeWidth={3} className="shrink-0" />
        <div
          className="text-[10px] font-black tracking-widest shrink-0"
          style={{ fontFamily: "'Oswald', sans-serif" }}
        >
          {labelText}
        </div>
        <div className={`w-px h-4 ${dividerCls} shrink-0`} />
        <input
          value={value || ''}
          onChange={(e) => onChange(e.target.value)}
          placeholder={placeholder}
          className={`flex-1 min-w-0 bg-transparent border-none outline-none text-sm font-bold ${placeholderCls}`}
        />
      </div>
    </div>
  );
}

// ============================================================
//   Connection Badge (selected connection の中点に表示される編集UI)
// ============================================================
function ConnectionBadge({ line, members, rewireMode, onDelete, onRewire, onCancelRewire }) {
  const memberA = members.find((m) => m.id === line.memberA);
  const memberB = members.find((m) => m.id === line.memberB);
  if (!memberA || !memberB) return null;
  const isRewiring = rewireMode && rewireMode.key === line.key;
  const replacingName = isRewiring
    ? (rewireMode.replaceMember === memberA.id ? memberA.name : memberB.name)
    : null;

  return (
    <div
      className="absolute z-50 pointer-events-auto"
      style={{
        left: `${line.midXPct}%`,
        top: `${line.midYPct}%`,
        transform: 'translate(-50%, -50%)',
      }}
    >
      <div className="bg-slate-950/95 backdrop-blur-md border-2 border-amber-400 rounded-lg shadow-2xl shadow-amber-500/40 p-1.5 flex items-center gap-1 whitespace-nowrap">
        {isRewiring ? (
          <>
            <span className="text-[10px] font-bold text-amber-300 px-2"
                  style={{ fontFamily: "'Oswald', sans-serif", letterSpacing: '0.04em' }}>
              <span className="text-amber-100">{replacingName}</span> を入れ替える選手をクリック
            </span>
            <button
              onClick={(e) => { e.stopPropagation(); onCancelRewire(); }}
              className="px-2 py-1 text-[10px] font-bold text-slate-300 hover:text-white bg-slate-800 hover:bg-slate-700 rounded"
            >
              キャンセル
            </button>
          </>
        ) : (
          <>
            <button
              onClick={(e) => { e.stopPropagation(); onRewire(memberA.id); }}
              className="px-2 py-1 text-[10px] font-bold text-slate-100 bg-slate-800 hover:bg-amber-400 hover:text-slate-900 rounded transition-colors"
              style={{ fontFamily: "'Oswald', sans-serif", letterSpacing: '0.04em' }}
              title={`${memberA.name} を別の選手に入れ替え`}
            >
              {memberA.name}
            </button>
            <span className="text-amber-300 font-black text-xs px-0.5">⇔</span>
            <button
              onClick={(e) => { e.stopPropagation(); onRewire(memberB.id); }}
              className="px-2 py-1 text-[10px] font-bold text-slate-100 bg-slate-800 hover:bg-amber-400 hover:text-slate-900 rounded transition-colors"
              style={{ fontFamily: "'Oswald', sans-serif", letterSpacing: '0.04em' }}
              title={`${memberB.name} を別の選手に入れ替え`}
            >
              {memberB.name}
            </button>
            <div className="w-px h-4 bg-slate-700 mx-0.5" />
            <button
              onClick={(e) => { e.stopPropagation(); onDelete(); }}
              className="p-1 rounded text-rose-300 hover:text-white hover:bg-rose-600 transition-colors"
              title="このラインを削除"
            >
              <Trash2 size={12} strokeWidth={3} />
            </button>
          </>
        )}
      </div>
    </div>
  );
}

// ============================================================
//   Pitch — フィールド本体 + 連携ライン (clickable)
// ============================================================
function Pitch({ children, lines = [], selectedKey = null, onLineClick, members = [], rewireMode = null, onDeleteConnection, onRewireStart, onCancelRewire }) {
  const selectedLine = lines.find((l) => l.key === selectedKey) || null;

  return (
    <div className="relative w-full" style={{ aspectRatio: '5 / 7' }}>
      {/* Pitch surface (clipped) */}
      <div className="absolute inset-0 rounded-2xl overflow-hidden shadow-2xl shadow-black/60 border border-emerald-950/60">
        <div className="absolute inset-0 bg-gradient-to-b from-emerald-600 via-emerald-700 to-emerald-800" />
        <div className="absolute inset-0 opacity-30 pointer-events-none"
             style={{ backgroundImage: 'repeating-linear-gradient(0deg, rgba(255,255,255,0.07) 0px, rgba(255,255,255,0.07) 12.5%, transparent 12.5%, transparent 25%)' }} />
        <div className="absolute inset-0 opacity-[0.06] mix-blend-overlay pointer-events-none"
             style={{ backgroundImage: 'radial-gradient(circle at 1px 1px, white 1px, transparent 0)', backgroundSize: '3px 3px' }} />

        <svg viewBox="0 0 100 140" className="absolute inset-0 w-full h-full" preserveAspectRatio="none">
          {/* Field markings */}
          <g fill="none" stroke="rgba(255,255,255,0.9)" strokeWidth="0.4" pointerEvents="none">
            <rect x="2" y="2" width="96" height="136" />
            <line x1="2" y1="70" x2="98" y2="70" />
            <circle cx="50" cy="70" r="10" />
            <circle cx="50" cy="70" r="0.7" fill="rgba(255,255,255,0.9)" />
            <rect x="20" y="2" width="60" height="21" />
            <rect x="37" y="2" width="26" height="7" />
            <rect x="44" y="0" width="12" height="2" stroke="rgba(255,255,255,0.7)" />
            <circle cx="50" cy="16" r="0.7" fill="rgba(255,255,255,0.9)" />
            <path d="M 44.5 23 A 10 10 0 0 1 55.5 23" />
            <rect x="20" y="117" width="60" height="21" />
            <rect x="37" y="131" width="26" height="7" />
            <rect x="44" y="138" width="12" height="2" stroke="rgba(255,255,255,0.7)" />
            <circle cx="50" cy="124" r="0.7" fill="rgba(255,255,255,0.9)" />
            <path d="M 44.5 117 A 10 10 0 0 0 55.5 117" />
            <path d="M 2 4 A 2 2 0 0 1 4 2" />
            <path d="M 96 2 A 2 2 0 0 1 98 4" />
            <path d="M 2 136 A 2 2 0 0 0 4 138" />
            <path d="M 96 138 A 2 2 0 0 0 98 136" />
          </g>

          {/* Zone labels — Y軸（業務性質）+ X軸（地理＋コントロール/実行） */}
          {/* 注: CENTER/EXECUTION ラベルは中央スロット情報と必ず被るため SVG からは省略。
               中央=実行 の意味は凡例カード側でカバーする。 */}
          <g pointerEvents="none" style={{ userSelect: 'none' }}>
            {/* ↑ GROWTH（前線/攻め） */}
            <text x="50" y="11.5" textAnchor="middle"
                  fill="rgba(252,211,77,0.6)" fontSize="3.6" fontWeight="900"
                  letterSpacing="0.5"
                  style={{ fontFamily: "'Oswald', sans-serif" }}>
              ↑ GROWTH
            </text>
            {/* ↓ STABILITY（後衛/守り） */}
            <text x="50" y="129" textAnchor="middle"
                  fill="rgba(147,197,253,0.6)" fontSize="3.6" fontWeight="900"
                  letterSpacing="0.5"
                  style={{ fontFamily: "'Oswald', sans-serif" }}>
              ↓ STABILITY
            </text>
            {/* ← 西日本（外側 = CONTROL）— センターラインの上、サイド寄り */}
            <text x="18" y="66" textAnchor="middle"
                  fill="rgba(167,243,208,0.6)" fontSize="2.5" fontWeight="800"
                  letterSpacing="0.4"
                  style={{ fontFamily: "'Oswald', sans-serif" }}>
              ← 西日本
            </text>
            <text x="18" y="68.8" textAnchor="middle"
                  fill="rgba(167,243,208,0.45)" fontSize="1.4" fontWeight="600"
                  letterSpacing="0.18"
                  style={{ fontFamily: "'Oswald', sans-serif" }}>
              GAME CONTROL
            </text>
            {/* 東日本 →（外側 = CONTROL）— センターラインの上、サイド寄り */}
            <text x="82" y="66" textAnchor="middle"
                  fill="rgba(196,181,253,0.6)" fontSize="2.5" fontWeight="800"
                  letterSpacing="0.4"
                  style={{ fontFamily: "'Oswald', sans-serif" }}>
              東日本 →
            </text>
            <text x="82" y="68.8" textAnchor="middle"
                  fill="rgba(196,181,253,0.45)" fontSize="1.4" fontWeight="600"
                  letterSpacing="0.18"
                  style={{ fontFamily: "'Oswald', sans-serif" }}>
              GAME CONTROL
            </text>
          </g>

          {/* Connection lines（クリック可能） */}
          {lines.length > 0 && (
            <g>
              {lines.map((l) => {
                const isSel = l.key === selectedKey;
                return (
                  <g key={l.key}>
                    {/* Hit area (transparent, wider) */}
                    <line
                      x1={l.x1} y1={l.y1} x2={l.x2} y2={l.y2}
                      stroke="transparent"
                      strokeWidth="4"
                      strokeLinecap="round"
                      style={{ pointerEvents: 'stroke', cursor: 'pointer' }}
                      onClick={(e) => { e.stopPropagation(); onLineClick && onLineClick(l.key); }}
                    />
                    {/* Shadow */}
                    <line
                      x1={l.x1} y1={l.y1} x2={l.x2} y2={l.y2}
                      stroke="rgba(0,0,0,0.5)"
                      strokeWidth={isSel ? 2 : 1.4}
                      strokeLinecap="round"
                      pointerEvents="none"
                    />
                    {/* Main line */}
                    <line
                      x1={l.x1} y1={l.y1} x2={l.x2} y2={l.y2}
                      stroke={isSel ? 'rgba(253,224,71,1)' : 'rgba(251,191,36,0.95)'}
                      strokeWidth={isSel ? 1.5 : 0.85}
                      strokeLinecap="round"
                      strokeDasharray={isSel ? '' : '3 1.2'}
                      pointerEvents="none"
                    />
                    {/* End-point dots */}
                    <circle cx={l.x1} cy={l.y1} r={isSel ? 1.5 : 0.9}
                            fill={isSel ? 'white' : 'rgba(251,191,36,1)'}
                            stroke={isSel ? 'rgba(253,224,71,1)' : 'none'}
                            strokeWidth={isSel ? 0.5 : 0}
                            pointerEvents="none" />
                    <circle cx={l.x2} cy={l.y2} r={isSel ? 1.5 : 0.9}
                            fill={isSel ? 'white' : 'rgba(251,191,36,1)'}
                            stroke={isSel ? 'rgba(253,224,71,1)' : 'none'}
                            strokeWidth={isSel ? 0.5 : 0}
                            pointerEvents="none" />
                  </g>
                );
              })}
            </g>
          )}
        </svg>
      </div>

      {/* Slots layer (NOT clipped, pointer-events: none で空間はクリック透過) */}
      <div className="absolute inset-0 pointer-events-none">
        {children}
        {selectedLine && (
          <ConnectionBadge
            line={selectedLine}
            members={members}
            rewireMode={rewireMode}
            onDelete={onDeleteConnection}
            onRewire={onRewireStart}
            onCancelRewire={onCancelRewire}
          />
        )}
      </div>
    </div>
  );
}

// ============================================================
//   Skill Picker (chips) — predefined + custom skills
// ============================================================
function SkillPicker({ selected, onToggle, customSkills, onAddCustomSkill, onRemoveCustomSkill }) {
  const [input, setInput] = useState('');

  const allPredefined = SKILL_GROUPS.flatMap((g) => g.options);
  // 選択済みだがどこにも属していないスキル（過去に追加されたが customSkills から消えた等）
  const orphans = selected.filter((s) => !allPredefined.includes(s) && !customSkills.includes(s));
  const customAll = Array.from(new Set([...customSkills, ...orphans]));

  const addNew = () => {
    const s = input.trim();
    if (!s) return;
    // 既存ならトグルだけ、新規なら追加してトグル
    if (!allPredefined.includes(s) && !customSkills.includes(s)) {
      onAddCustomSkill(s);
    }
    if (!selected.includes(s)) onToggle(s);
    setInput('');
  };

  return (
    <div className="rounded-lg bg-slate-950/50 border border-slate-800 p-2.5">
      <div className="space-y-2.5 max-h-[220px] overflow-y-auto pr-1">
        {SKILL_GROUPS.map((g) => (
          <div key={g.label}>
            <div className="text-[10px] uppercase tracking-widest text-slate-500 font-bold mb-1">{g.label}</div>
            <div className="flex flex-wrap gap-1.5">
              {g.options.map((opt) => {
                const on = selected.includes(opt);
                return (
                  <button
                    key={opt}
                    type="button"
                    onClick={() => onToggle(opt)}
                    className={`text-[11px] px-2 py-1 rounded-md border transition-all font-medium
                      ${on
                        ? 'bg-amber-400 border-amber-300 text-slate-900 shadow shadow-amber-500/30'
                        : 'bg-slate-800 border-slate-700 text-slate-300 hover:border-slate-500 hover:text-slate-100'}`}
                  >
                    {opt}
                  </button>
                );
              })}
            </div>
          </div>
        ))}

        {customAll.length > 0 && (() => {
          // カスタムスキルをキーワードベースで既存カテゴリに自動振り分け
          const groups = {};
          customAll.forEach((opt) => {
            const cat = guessSkillCategory(opt);
            (groups[cat] = groups[cat] || []).push(opt);
          });
          // 各カテゴリ内は五十音/アルファベット順
          Object.values(groups).forEach((arr) => arr.sort((a, b) => a.localeCompare(b, 'ja')));
          // 表示順は SKILL_GROUPS と同順、最後に「その他（カスタム）」
          const orderedCats = [...SKILL_GROUPS.map((g) => g.label), 'その他（カスタム）']
            .filter((cat) => groups[cat]);

          return (
            <div>
              <div className="text-[10px] uppercase tracking-widest text-amber-400/90 font-bold mb-1.5 flex items-center gap-1">
                <Plus size={10} strokeWidth={3} />
                <span>あなたが追加（自動分類）</span>
              </div>
              <div className="space-y-2 pl-2 border-l-2 border-amber-400/20">
                {orderedCats.map((cat) => (
                  <div key={cat}>
                    <div className="text-[9px] tracking-wider text-amber-300/65 font-semibold mb-1">
                      └ {cat}
                    </div>
                    <div className="flex flex-wrap gap-1.5">
                      {groups[cat].map((opt) => {
                        const on = selected.includes(opt);
                        const inGlobal = customSkills.includes(opt);
                        return (
                          <div key={opt} className="relative inline-flex group/skill">
                            <button
                              type="button"
                              onClick={() => onToggle(opt)}
                              className={`text-[11px] px-2 py-1 rounded-md border transition-all font-medium
                                ${on
                                  ? 'bg-amber-400 border-amber-300 text-slate-900 shadow shadow-amber-500/30'
                                  : 'bg-slate-800 border-amber-400/40 text-slate-300 hover:border-amber-400/70 hover:text-slate-100'}`}
                            >
                              {opt}
                            </button>
                            {inGlobal && (
                              <button
                                type="button"
                                onClick={(e) => { e.stopPropagation(); onRemoveCustomSkill(opt); }}
                                className="absolute -top-1.5 -right-1.5 w-4 h-4 rounded-full bg-slate-900 border border-slate-600 text-slate-400 hover:bg-rose-600 hover:text-white opacity-0 group-hover/skill:opacity-100 transition-opacity flex items-center justify-center"
                                title="リストから削除"
                              >
                                <X size={9} strokeWidth={3} />
                              </button>
                            )}
                          </div>
                        );
                      })}
                    </div>
                  </div>
                ))}
              </div>
            </div>
          );
        })()}
      </div>

      {/* 追加入力欄 */}
      <div className="flex gap-1.5 pt-2.5 mt-2 border-t border-slate-800">
        <input
          value={input}
          onChange={(e) => setInput(e.target.value)}
          onKeyDown={(e) => { if (e.key === 'Enter') { e.preventDefault(); addNew(); } }}
          placeholder="新しいスキルを追加 (Enter or 追加)"
          className="flex-1 px-2.5 py-1.5 text-xs rounded-md bg-slate-800 border border-slate-700 text-slate-100 focus:outline-none focus:border-amber-400 placeholder:text-slate-500"
        />
        <button
          type="button"
          onClick={addNew}
          disabled={!input.trim()}
          className="px-2.5 py-1.5 text-xs rounded-md bg-amber-400 text-slate-900 font-bold hover:bg-amber-300 disabled:opacity-40 disabled:cursor-not-allowed flex items-center gap-1"
        >
          <Plus size={12} strokeWidth={3} /> 追加
        </button>
      </div>
    </div>
  );
}

// ============================================================
//   Member Editor Modal
// ============================================================
function MemberEditor({ open, onClose, onSave, onDelete, initial, existingMembers = [], customSkills = [], onAddCustomSkill, onRemoveCustomSkill }) {
  const [draft, setDraft] = useState(initial);
  useEffect(() => { setDraft(initial); }, [initial]);

  // ─── ここから全フック・派生値（early return より前に必ず置くこと）───

  // 変更検知: initial と draft の差分を比較
  const hasUnsavedChanges = (() => {
    if (!initial || !draft) return false;
    const norm = (m) => ({
      name: (m.name || '').trim(),
      role: m.role || '',
      number: m.number == null || m.number === '' ? null : Number(m.number),
      skills: [...(m.skills || [])].sort().join('|'),
    });
    const a = norm(initial);
    const b = norm(draft);
    return a.name !== b.name || a.role !== b.role || a.number !== b.number || a.skills !== b.skills;
  })();

  // 閉じるリクエスト
  const requestClose = () => {
    if (hasUnsavedChanges) {
      const ok = window.confirm('編集中の変更があります。\n破棄して閉じますか？\n\n（保存するには「保存」ボタンを押してください）');
      if (!ok) return;
    }
    onClose();
  };

  // ESC キーで閉じる（open=true のときだけ動作）
  useEffect(() => {
    if (!open) return;
    const onKey = (e) => {
      if (e.key === 'Escape') requestClose();
    };
    window.addEventListener('keydown', onKey);
    return () => window.removeEventListener('keydown', onKey);
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [open, hasUnsavedChanges]);

  // ─── ここから条件分岐 / early return が許される ───
  if (!open || !draft) return null;

  const numberConflict =
    draft.number != null && draft.number !== '' &&
    existingMembers.some((m) => m.id !== draft.id && m.number === draft.number);
  const conflictMember = numberConflict
    ? existingMembers.find((m) => m.id !== draft.id && m.number === draft.number)
    : null;
  const nameEmpty = !draft.name?.trim();
  // 背番号被りでも保存可。saveMember 側で被害者を別番号に自動再割当する。
  const canSave = !nameEmpty;

  const skills = draft.skills || [];
  const toggleSkill = (s) => {
    if (skills.includes(s)) setDraft({ ...draft, skills: skills.filter((x) => x !== s) });
    else setDraft({ ...draft, skills: [...skills, s] });
  };

  return (
    <div
      className="fixed inset-0 z-50 flex items-center justify-center p-4 bg-black/75 backdrop-blur-sm"
      onClick={requestClose}
    >
      <div
        className="bg-slate-900 border border-slate-700 rounded-2xl w-full max-w-lg shadow-2xl max-h-[90vh] overflow-hidden flex flex-col"
        onClick={(e) => e.stopPropagation()}
      >
        <div className="flex items-center justify-between px-5 py-4 border-b border-slate-700 shrink-0">
          <h3 className="text-lg font-bold text-slate-100 flex items-center gap-2" style={{ fontFamily: "'Oswald', sans-serif", letterSpacing: '0.04em' }}>
            <span>{initial.id ? 'メンバー編集' : '新規メンバー'}</span>
            {hasUnsavedChanges && (
              <span
                className="text-[9px] px-1.5 py-0.5 rounded-full bg-amber-400/20 text-amber-300 border border-amber-400/40 font-bold tracking-widest"
                title="未保存の変更があります"
              >
                未保存
              </span>
            )}
          </h3>
          <button onClick={requestClose} className="text-slate-400 hover:text-slate-200"><X size={20} /></button>
        </div>

        <div className="p-5 space-y-4 overflow-y-auto">
          <div>
            <label className="text-xs uppercase tracking-wider text-slate-400 font-bold">名前 *</label>
            <input
              value={draft.name || ''}
              onChange={(e) => setDraft({ ...draft, name: e.target.value })}
              className="mt-1 w-full px-3 py-2 rounded-lg bg-slate-800 border border-slate-700 text-slate-100 focus:outline-none focus:border-amber-400"
              placeholder="例: Yuma"
            />
          </div>

          <div>
            <label className="text-xs uppercase tracking-wider text-slate-400 font-bold">役職 / Role</label>
            <div className="relative mt-1">
              <select
                value={draft.role || ''}
                onChange={(e) => setDraft({ ...draft, role: e.target.value })}
                className="appearance-none w-full px-3 py-2 pr-9 rounded-lg bg-slate-800 border border-slate-700 text-slate-100 focus:outline-none focus:border-amber-400"
                title={draft.role && ROLE_DESCRIPTIONS[draft.role] ? `${draft.role} — ${ROLE_DESCRIPTIONS[draft.role]}` : ''}
              >
                <option value="">— 選択してください —</option>
                {ROLE_GROUPS.map((g) => (
                  <optgroup key={g.label} label={g.label}>
                    {g.options.map((opt) => (
                      <option
                        key={opt}
                        value={opt}
                        title={ROLE_DESCRIPTIONS[opt] ? `${opt} — ${ROLE_DESCRIPTIONS[opt]}` : opt}
                      >
                        {opt}
                      </option>
                    ))}
                  </optgroup>
                ))}
              </select>
              <ChevronDown size={16} className="absolute right-3 top-1/2 -translate-y-1/2 pointer-events-none text-slate-400" />
            </div>
            {/* 人物像バー: スキルがあればスキル分析サマリー、なければ役職解説にフォールバック */}
            {(() => {
              const skillSummary = generateMemberSummary(draft);
              const roleDesc = draft.role ? ROLE_DESCRIPTIONS[draft.role] : '';
              const hasSummary = skillSummary && skillSummary.length > 0;
              const hasRole = roleDesc && roleDesc.length > 0;
              if (!hasSummary && !hasRole) return null;
              const isProfile = hasSummary;
              const text = isProfile ? skillSummary : roleDesc;
              const label = isProfile ? 'PROFILE' : 'INFO';
              return (
                <div className={`mt-1.5 px-2.5 py-1.5 rounded-md bg-slate-950/50 border ${isProfile ? 'border-emerald-400/20' : 'border-amber-400/15'}`}>
                  <div className="flex items-start gap-1.5">
                    <span
                      className={`text-[9px] font-bold tracking-wider uppercase shrink-0 mt-0.5 ${isProfile ? 'text-emerald-300/90' : 'text-amber-300/85'}`}
                      style={{ fontFamily: "'Oswald', sans-serif", letterSpacing: '0.15em' }}
                    >
                      {label}
                    </span>
                    <span className="text-[11px] text-slate-300 leading-snug">
                      {text}
                    </span>
                  </div>
                </div>
              );
            })()}
          </div>

          <div>
            <label className="text-xs uppercase tracking-wider text-slate-400 font-bold">背番号 (任意)</label>
            <input
              type="number"
              value={draft.number ?? ''}
              onChange={(e) => setDraft({ ...draft, number: e.target.value === '' ? null : parseInt(e.target.value, 10) })}
              className={`mt-1 w-full px-3 py-2 rounded-lg bg-slate-800 border text-slate-100 focus:outline-none
                ${numberConflict ? 'border-amber-500 focus:border-amber-400' : 'border-slate-700 focus:border-amber-400'}`}
              placeholder="例: 10"
            />
            {numberConflict && (
              <div className="mt-1.5 text-xs text-amber-300 flex items-start gap-1.5">
                <span className="font-bold shrink-0">⚠</span>
                <span>
                  背番号 <span className="font-bold">{draft.number}</span> は <span className="font-bold">{conflictMember?.name}</span> が使用中。
                  保存すると <span className="font-bold">{conflictMember?.name}</span> は別の空き番号に自動で再割当されます。
                </span>
              </div>
            )}
          </div>

          <div>
            <label className="text-xs uppercase tracking-wider text-slate-400 font-bold">カラー（役職カテゴリから自動）</label>
            <div className="mt-2 flex items-center gap-3 px-3 py-2 rounded-lg bg-slate-950/50 border border-slate-800">
              <div className={`w-9 h-9 rounded-lg shadow-inner ${getColors(draft.role).bg}`} />
              <div className="min-w-0 flex-1">
                <div className="text-[10px] uppercase tracking-widest text-slate-500 font-bold">カテゴリ</div>
                <div className="text-sm font-bold text-slate-200 truncate" style={{ fontFamily: "'Oswald', sans-serif" }}>
                  {draft.role ? getCategory(draft.role) : '— 役職未設定 —'}
                </div>
              </div>
              <div className="text-[10px] uppercase tracking-widest text-slate-500 font-bold">{getColorKey(draft.role)}</div>
            </div>
          </div>

          <div>
            <div className="flex items-center justify-between mb-2">
              <label className="text-xs uppercase tracking-wider text-slate-400 font-bold">得意技 / Skills</label>
              <span className="text-[10px] text-slate-500">{skills.length} 選択中</span>
            </div>
            <SkillPicker
              selected={skills}
              onToggle={toggleSkill}
              customSkills={customSkills}
              onAddCustomSkill={onAddCustomSkill}
              onRemoveCustomSkill={onRemoveCustomSkill}
            />
          </div>
        </div>

        <div className="flex items-center justify-between gap-2 px-5 py-4 border-t border-slate-700 bg-slate-900/50 shrink-0">
          {initial.id ? (
            <button
              onClick={() => onDelete(initial.id)}
              className="px-3 py-2 text-sm text-rose-300 hover:text-rose-200 hover:bg-rose-500/10 rounded-lg flex items-center gap-1.5"
            >
              <Trash2 size={14} /> 削除
            </button>
          ) : <div />}
          <div className="flex gap-2">
            <button onClick={requestClose} className="px-4 py-2 text-sm text-slate-300 hover:text-slate-100 rounded-lg">キャンセル</button>
            <button
              onClick={() => onSave(draft)}
              disabled={!canSave}
              className="px-4 py-2 text-sm font-bold text-slate-900 bg-amber-400 hover:bg-amber-300 rounded-lg disabled:opacity-40 disabled:cursor-not-allowed flex items-center gap-1.5"
              style={{ fontFamily: "'Oswald', sans-serif", letterSpacing: '0.04em' }}
            >
              <Save size={14} /> 保存
            </button>
          </div>
        </div>
      </div>
    </div>
  );
}

// ============================================================
//   Main App
// ============================================================
export default function SquadBuilder() {
  const [members, setMembers] = useState([]);
  const [projects, setProjects] = useState([]);
  const [currentProjectId, setCurrentProjectId] = useState(null);
  const [customSkills, setCustomSkills] = useState([]);
  const [loaded, setLoaded] = useState(false);
  const [editorOpen, setEditorOpen] = useState(false);
  const [editorInitial, setEditorInitial] = useState(null);
  const [selectedMemberId, setSelectedMemberId] = useState(null);
  const [hoverSlot, setHoverSlot] = useState(null);
  const [connectMode, setConnectMode] = useState(false);
  const [connectFirst, setConnectFirst] = useState(null); // memberId
  const [selectedConnectionKey, setSelectedConnectionKey] = useState(null);
  const [rewireMode, setRewireMode] = useState(null); // null | { key, replaceMember }
  const dragData = useRef({ memberId: null, fromSlot: null });

  useEffect(() => {
    (async () => {
      const m = await loadKey(KEYS.members, DEFAULT_MEMBERS);
      const p = await loadKey(KEYS.projects, [DEFAULT_PROJECT]);
      const c = await loadKey(KEYS.current, p[0]?.id);
      const cs = await loadKey(KEYS.customSkills, []);
      setMembers(m);
      setProjects(p);
      setCurrentProjectId(c || p[0]?.id);
      setCustomSkills(cs);
      setLoaded(true);
    })();
  }, []);

  useEffect(() => { if (loaded) saveKey(KEYS.members, members); }, [members, loaded]);
  useEffect(() => { if (loaded) saveKey(KEYS.projects, projects); }, [projects, loaded]);
  useEffect(() => { if (loaded && currentProjectId) saveKey(KEYS.current, currentProjectId); }, [currentProjectId, loaded]);
  useEffect(() => { if (loaded) saveKey(KEYS.customSkills, customSkills); }, [customSkills, loaded]);

  // ─ アンドゥ / リドゥ ─
  // 状態 (members + projects + currentProjectId + customSkills) のスナップショットを履歴に積む。
  // ユーザー操作に応じてバッチで1スナップショット → Ctrl+Z で前の状態に、Ctrl+Shift+Z / Ctrl+Y で次に。
  const HISTORY_LIMIT = 80;
  const historyRef = useRef([]);
  const historyIndexRef = useRef(-1);
  const skipHistoryRef = useRef(false);
  const [historyVersion, setHistoryVersion] = useState(0); // canUndo/canRedo を再評価させる用

  useEffect(() => {
    if (!loaded) return;
    // Undo/Redo 自体による state 変化は履歴に積まない
    if (skipHistoryRef.current) {
      skipHistoryRef.current = false;
      return;
    }
    const snap = JSON.stringify({ members, projects, currentProjectId, customSkills });
    // 同じ snapshot が2回続けて来たら積まない（まれな重複対策）
    const last = historyRef.current[historyIndexRef.current];
    if (last === snap) return;
    // 現在位置以降の future history は破棄
    historyRef.current = historyRef.current.slice(0, historyIndexRef.current + 1);
    historyRef.current.push(snap);
    if (historyRef.current.length > HISTORY_LIMIT) {
      historyRef.current = historyRef.current.slice(-HISTORY_LIMIT);
    }
    historyIndexRef.current = historyRef.current.length - 1;
    setHistoryVersion((v) => v + 1);
  }, [members, projects, currentProjectId, customSkills, loaded]);

  const restoreSnapshot = (snapStr) => {
    try {
      const snap = JSON.parse(snapStr);
      skipHistoryRef.current = true;
      setMembers(snap.members);
      setProjects(snap.projects);
      setCurrentProjectId(snap.currentProjectId);
      setCustomSkills(snap.customSkills);
    } catch {}
  };

  const undo = () => {
    if (historyIndexRef.current <= 0) return;
    historyIndexRef.current -= 1;
    restoreSnapshot(historyRef.current[historyIndexRef.current]);
    setHistoryVersion((v) => v + 1);
  };
  const redo = () => {
    if (historyIndexRef.current >= historyRef.current.length - 1) return;
    historyIndexRef.current += 1;
    restoreSnapshot(historyRef.current[historyIndexRef.current]);
    setHistoryVersion((v) => v + 1);
  };
  const canUndo = historyIndexRef.current > 0;
  const canRedo = historyIndexRef.current < historyRef.current.length - 1;
  // historyVersion を参照することで再レンダリング時に canUndo/canRedo が反映される
  void historyVersion;

  // キーボードショートカット: Ctrl+Z / Cmd+Z = undo, +Shift or Ctrl+Y = redo
  useEffect(() => {
    const onKey = (e) => {
      // 入力中はスキップ（input/textarea/contenteditable）
      const t = e.target;
      const tag = (t?.tagName || '').toLowerCase();
      if (tag === 'input' || tag === 'textarea' || t?.isContentEditable) return;
      const mod = e.metaKey || e.ctrlKey;
      if (!mod) return;
      if (e.key === 'z' || e.key === 'Z') {
        e.preventDefault();
        if (e.shiftKey) redo(); else undo();
      } else if (e.key === 'y' || e.key === 'Y') {
        e.preventDefault();
        redo();
      }
    };
    window.addEventListener('keydown', onKey);
    return () => window.removeEventListener('keydown', onKey);
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, []);

  const addCustomSkill = (s) => {
    setCustomSkills((prev) => prev.includes(s) ? prev : [...prev, s]);
  };
  const removeCustomSkill = (s) => {
    setCustomSkills((prev) => prev.filter((x) => x !== s));
  };

  const currentProject = projects.find((p) => p.id === currentProjectId);
  const formation = currentProject ? FORMATIONS[currentProject.formation] : FORMATIONS['4-3-3'];
  const assignedMemberIds = currentProject ? Object.values(currentProject.assignments || {}).filter(Boolean) : [];
  const benchMembers = members.filter((m) => !assignedMemberIds.includes(m.id));

  const updateProject = (mut) => {
    setProjects((prev) => prev.map((p) => (p.id === currentProjectId ? mut(p) : p)));
  };
  const addProject = () => {
    const name = prompt('新規プロジェクト名:', `Project ${projects.length + 1}`);
    if (!name) return;
    const np = { id: newId('p'), name, formation: '4-3-3', assignments: {}, connections: [], target: '', risk: '' };
    setProjects((prev) => [...prev, np]);
    setCurrentProjectId(np.id);
  };
  const renameProject = () => {
    if (!currentProject) return;
    const name = prompt('プロジェクト名を編集:', currentProject.name);
    if (!name) return;
    updateProject((p) => ({ ...p, name }));
  };
  const deleteProject = () => {
    if (!currentProject || projects.length <= 1) { alert('最低1つのプロジェクトが必要'); return; }
    if (!confirm(`「${currentProject.name}」を削除しますか？`)) return;
    const remaining = projects.filter((p) => p.id !== currentProjectId);
    setProjects(remaining);
    setCurrentProjectId(remaining[0].id);
  };
  // フォーメーション変更時：既存配置を新スロットへ距離ベースで自動再割当
  // connections は memberId ベースなので何もしなくても保持される
  const setFormation = (f) => {
    updateProject((p) => {
      if (p.formation === f) return p;
      const oldF = FORMATIONS[p.formation] || [];
      const newF = FORMATIONS[f] || [];
      const oldA = p.assignments || {};

      // 旧 assignments → [{ memberId, x, y }]
      const placed = Object.entries(oldA)
        .map(([slotId, memberId]) => {
          if (!memberId) return null;
          const s = oldF.find((x) => x.id === slotId);
          if (!s) return null;
          return { memberId, x: s.x, y: s.y };
        })
        .filter(Boolean);

      if (placed.length === 0) {
        return { ...p, formation: f, assignments: {} };
      }

      // 全 (member, slot) ペアを距離順に並べ、近い順から greedy で確定
      const candidates = [];
      placed.forEach((pm) => {
        newF.forEach((slot) => {
          const dx = slot.x - pm.x;
          const dy = slot.y - pm.y;
          candidates.push({ memberId: pm.memberId, slotId: slot.id, dist: dx * dx + dy * dy });
        });
      });
      candidates.sort((a, b) => a.dist - b.dist);

      const newA = {};
      const usedMembers = new Set();
      const usedSlots = new Set();
      for (const c of candidates) {
        if (usedMembers.has(c.memberId) || usedSlots.has(c.slotId)) continue;
        newA[c.slotId] = c.memberId;
        usedMembers.add(c.memberId);
        usedSlots.add(c.slotId);
      }
      // あぶれたメンバー（新フォメのスロット数 < 旧メンバー数）は自然にベンチに戻る
      // connections は memberId ベースなので、配置に残ったメンバー間の関係は自動保持
      return { ...p, formation: f, assignments: newA };
    });
  };
  const clearAll = () => {
    if (!confirm('全配置をベンチに戻しますか？（連携ラインも消えます）')) return;
    updateProject((p) => ({ ...p, assignments: {}, connections: [] }));
  };
  const setTarget = (v) => updateProject((p) => ({ ...p, target: v }));
  const setRisk   = (v) => updateProject((p) => ({ ...p, risk: v }));

  // 連携ライン
  const toggleConnection = (memberA, memberB) => {
    if (!memberA || !memberB || memberA === memberB) return;
    const key = [memberA, memberB].sort().join('::');
    updateProject((p) => {
      const conns = p.connections || [];
      return {
        ...p,
        connections: conns.includes(key) ? conns.filter((k) => k !== key) : [...conns, key],
      };
    });
  };
  const clearConnections = () => {
    if (!(currentProject?.connections || []).length) return;
    if (!confirm('全連携ラインを削除しますか？')) return;
    updateProject((p) => ({ ...p, connections: [] }));
    setSelectedConnectionKey(null);
    setRewireMode(null);
  };

  // ライン選択 / 削除 / 繋ぎ変え
  const selectConnection = (key) => {
    setSelectedConnectionKey((prev) => (prev === key ? null : key));
    setRewireMode(null);
    // 選択モードに入ったら他モードを解除
    if (connectMode) {
      setConnectMode(false);
      setConnectFirst(null);
    }
  };
  const deleteSelectedConnection = () => {
    if (!selectedConnectionKey) return;
    updateProject((p) => ({
      ...p,
      connections: (p.connections || []).filter((k) => k !== selectedConnectionKey),
    }));
    setSelectedConnectionKey(null);
    setRewireMode(null);
  };
  const startRewire = (replaceMember) => {
    if (!selectedConnectionKey) return;
    setRewireMode({ key: selectedConnectionKey, replaceMember });
  };
  const cancelRewire = () => setRewireMode(null);
  const performRewire = (newMemberId) => {
    if (!rewireMode) return;
    const { key, replaceMember } = rewireMode;
    const [a, b] = key.split('::');
    const otherMember = a === replaceMember ? b : a;
    if (newMemberId === otherMember || newMemberId === replaceMember) {
      setRewireMode(null);
      return;
    }
    const newKey = [otherMember, newMemberId].sort().join('::');
    updateProject((p) => {
      const conns = (p.connections || []).filter((k) => k !== key);
      if (!conns.includes(newKey)) conns.push(newKey);
      return { ...p, connections: conns };
    });
    setRewireMode(null);
    setSelectedConnectionKey(newKey);
  };

  // 描画用ライン座標（viewBox 100x140 基準 + 中点 % 座標）
  const connectionLines = (() => {
    if (!currentProject) return [];
    const conns = currentProject.connections || [];
    const memberToSlot = {};
    Object.entries(currentProject.assignments || {}).forEach(([slotId, mid]) => {
      if (mid) memberToSlot[mid] = slotId;
    });
    return conns
      .map((key) => {
        const [a, b] = key.split('::');
        const sa = formation.find((s) => s.id === memberToSlot[a]);
        const sb = formation.find((s) => s.id === memberToSlot[b]);
        if (!sa || !sb) return null;
        return {
          key,
          memberA: a, memberB: b,
          x1: sa.x, y1: sa.y * 1.4, x2: sb.x, y2: sb.y * 1.4,
          midXPct: (sa.x + sb.x) / 2,
          midYPct: (sa.y + sb.y) / 2,
        };
      })
      .filter(Boolean);
  })();

  const handleBenchDragStart = (e, memberId) => {
    dragData.current = { memberId, fromSlot: null };
    e.dataTransfer.effectAllowed = 'move';
  };
  const handleSlotDragStart = (e, memberId, fromSlot) => {
    dragData.current = { memberId, fromSlot };
    e.dataTransfer.effectAllowed = 'move';
  };
  const handleSlotDragOver = (slotId) => {
    setHoverSlot((prev) => (prev === slotId ? prev : slotId));
  };
  const handleSlotDragLeave = (slotId) => {
    setHoverSlot((prev) => (prev === slotId ? null : prev));
  };
  const handleSlotDrop = (slotId) => {
    const { memberId, fromSlot } = dragData.current;
    if (!memberId) return;
    updateProject((p) => {
      const a = { ...p.assignments };
      if (fromSlot) delete a[fromSlot];
      const displaced = a[slotId];
      a[slotId] = memberId;
      // 既存メンバーが居たら旧スロットへスワップ（リンクは memberId ベースで自動保持）
      if (displaced && fromSlot) a[fromSlot] = displaced;
      return { ...p, assignments: a };
    });
    dragData.current = { memberId: null, fromSlot: null };
    setHoverSlot(null);
  };
  const handleClearSlot = (slotId) => {
    updateProject((p) => {
      const a = { ...p.assignments };
      delete a[slotId];
      return { ...p, assignments: a };
    });
  };
  const handleBenchClick = (memberId) => {
    setSelectedMemberId((prev) => (prev === memberId ? null : memberId));
  };
  const handleSlotClick = (slotId) => {
    // リワイヤーモード時: 選手をクリックして endpoint を入れ替え
    if (rewireMode) {
      const mid = currentProject?.assignments?.[slotId];
      if (!mid) return;
      performRewire(mid);
      return;
    }
    // 連携モード時：選手2人クリックでライン作成/削除
    if (connectMode) {
      const mid = currentProject?.assignments?.[slotId];
      if (!mid) return;
      if (!connectFirst) {
        setConnectFirst(mid);
      } else if (connectFirst === mid) {
        setConnectFirst(null);
      } else {
        toggleConnection(connectFirst, mid);
        setConnectFirst(null);
      }
      return;
    }
    // 通常モード：ベンチ選択 → スロット配置
    if (!selectedMemberId) return;
    updateProject((p) => {
      const a = { ...p.assignments };
      Object.keys(a).forEach((k) => { if (a[k] === selectedMemberId) delete a[k]; });
      a[slotId] = selectedMemberId;
      return { ...p, assignments: a };
    });
    setSelectedMemberId(null);
  };

  const openNewMember = () => {
    setEditorInitial({ id: null, name: '', role: '', number: null, skills: [] });
    setEditorOpen(true);
  };
  const openEditMember = (m) => { setEditorInitial({ ...m, skills: m.skills || [] }); setEditorOpen(true); };
  const saveMember = (draft) => {
    const isNew = !draft.id;
    const finalDraft = isNew ? { ...draft, id: newId('m') } : draft;

    // 背番号被り検出 → 被害者を空き番号に振り直し
    let victimId = null;
    let victimNewNumber = null;
    if (finalDraft.number != null && finalDraft.number !== '') {
      const victim = members.find((m) => m.id !== finalDraft.id && m.number === finalDraft.number);
      if (victim) {
        victimId = victim.id;
        const taken = new Set(
          members
            .filter((m) => m.id !== victim.id && m.id !== finalDraft.id)
            .map((m) => m.number)
            .filter((n) => n != null && n !== '')
        );
        taken.add(finalDraft.number);
        victimNewNumber = findFreeNumber(taken);
        const msg = victimNewNumber != null
          ? `背番号 ${finalDraft.number} を ${finalDraft.name} に再割当てしました。\n${victim.name} は ${victimNewNumber} 番に変更しました。`
          : `背番号 ${finalDraft.number} を ${finalDraft.name} に再割当てしました。\n${victim.name} は空き番号がないため背番号なしになりました。`;
        // 通知（保存自体は実行）
        try { alert(msg); } catch {}
      }
    }

    setMembers((prev) => {
      let next = isNew
        ? [...prev, finalDraft]
        : prev.map((m) => (m.id === finalDraft.id ? finalDraft : m));
      if (victimId) {
        next = next.map((m) => (m.id === victimId ? { ...m, number: victimNewNumber } : m));
      }
      return next;
    });

    setEditorOpen(false);
  };
  const deleteMember = (id) => {
    if (!confirm('このメンバーを削除しますか？（全プロジェクトの配置からも除外）')) return;
    setMembers((prev) => prev.filter((m) => m.id !== id));
    setProjects((prev) =>
      prev.map((p) => {
        const a = { ...p.assignments };
        Object.keys(a).forEach((k) => { if (a[k] === id) delete a[k]; });
        return { ...p, assignments: a };
      })
    );
    setEditorOpen(false);
  };

  if (!loaded) {
    return <div className="min-h-screen bg-slate-950 flex items-center justify-center text-slate-400">Loading…</div>;
  }

  return (
    <div className="min-h-screen bg-slate-950 text-slate-100">
      <style>{`
        @import url('https://fonts.googleapis.com/css2?family=Oswald:wght@400;500;600;700&family=Inter:wght@400;500;600&display=swap');
        body { font-family: 'Inter', system-ui, sans-serif; }
        @keyframes esai-wiggle {
          0%, 100% { transform: rotate(-2deg); }
          50% { transform: rotate(2deg); }
        }
        .esai-wiggle { animation: esai-wiggle 250ms ease-in-out infinite; transform-origin: center; }
      `}</style>

      <header className="sticky top-0 z-40 backdrop-blur-md bg-slate-950/80 border-b border-slate-800">
        <div className="max-w-7xl mx-auto px-4 py-3 flex items-center gap-3 flex-wrap">
          <a
            href="/"
            className="flex items-center gap-1 px-2 py-1.5 rounded-lg bg-slate-800 border border-slate-700 hover:border-amber-400/60 text-slate-300 hover:text-amber-300 text-xs font-bold transition-all shrink-0"
            title="EdgeSense Fit Finder（ホーム）へ戻る"
            style={{ fontFamily: "'Oswald', sans-serif", letterSpacing: '0.06em' }}
          >
            <ChevronLeft size={14} strokeWidth={2.5} />
            <Home size={13} strokeWidth={2.5} />
            <span className="hidden sm:inline ml-0.5">HOME</span>
          </a>
          <div className="flex items-center gap-2">
            <Trophy className="text-amber-400" size={22} />
            <h1 className="text-xl sm:text-2xl font-black tracking-tight"
                style={{ fontFamily: "'Oswald', sans-serif", letterSpacing: '0.04em' }}>
              EDGE<span className="text-amber-400">SENSE</span> SQUAD
            </h1>
          </div>
          <div className="ml-auto flex items-center gap-2 flex-wrap">
            <div className="relative">
              <select
                value={currentProjectId || ''}
                onChange={(e) => setCurrentProjectId(e.target.value)}
                className="appearance-none pl-3 pr-9 py-2 rounded-lg bg-slate-800 border border-slate-700 text-sm font-bold text-slate-100 hover:border-slate-500 focus:outline-none focus:border-amber-400"
                style={{ fontFamily: "'Oswald', sans-serif", letterSpacing: '0.04em' }}
              >
                {projects.map((p) => (
                  <option key={p.id} value={p.id}>{p.name}</option>
                ))}
              </select>
              <ChevronDown size={16} className="absolute right-2 top-1/2 -translate-y-1/2 pointer-events-none text-slate-400" />
            </div>
            <button onClick={renameProject} className="p-2 rounded-lg bg-slate-800 border border-slate-700 hover:border-slate-500 text-slate-300" title="プロジェクト名編集"><Edit2 size={14} /></button>
            <button onClick={addProject} className="p-2 rounded-lg bg-slate-800 border border-slate-700 hover:border-slate-500 text-slate-300" title="新規プロジェクト"><FolderPlus size={14} /></button>
            <button onClick={deleteProject} className="p-2 rounded-lg bg-slate-800 border border-slate-700 hover:border-rose-500 text-slate-300 hover:text-rose-300" title="プロジェクト削除"><Trash2 size={14} /></button>
            <div className="w-px h-5 bg-slate-700 mx-0.5" />
            <button
              onClick={undo}
              disabled={!canUndo}
              className="p-2 rounded-lg bg-slate-800 border border-slate-700 hover:border-amber-400/60 text-slate-300 hover:text-amber-300 disabled:opacity-30 disabled:cursor-not-allowed disabled:hover:border-slate-700 disabled:hover:text-slate-300"
              title="アンドゥ (Ctrl+Z)"
            >
              <Undo2 size={14} />
            </button>
            <button
              onClick={redo}
              disabled={!canRedo}
              className="p-2 rounded-lg bg-slate-800 border border-slate-700 hover:border-amber-400/60 text-slate-300 hover:text-amber-300 disabled:opacity-30 disabled:cursor-not-allowed disabled:hover:border-slate-700 disabled:hover:text-slate-300"
              title="リドゥ (Ctrl+Shift+Z / Ctrl+Y)"
            >
              <Redo2 size={14} />
            </button>
          </div>
        </div>

        <div className="max-w-7xl mx-auto px-4 pb-3 flex items-center gap-2 flex-wrap">
          <span className="text-xs uppercase tracking-widest text-slate-500 font-bold mr-1">Formation</span>
          {Object.keys(FORMATIONS).map((f) => {
            const isActive = currentProject?.formation === f;
            const tone = FORMATION_TONE[f]; // 'attack' | 'defense' | undefined
            const activeCls =
              tone === 'attack'  ? 'bg-rose-500 text-white shadow-lg shadow-rose-500/40 ring-2 ring-rose-300/60' :
              tone === 'defense' ? 'bg-blue-500 text-white shadow-lg shadow-blue-500/40 ring-2 ring-blue-300/60' :
                                   'bg-amber-400 text-slate-900 shadow-lg shadow-amber-500/30';
            const inactiveCls =
              tone === 'attack'  ? 'bg-slate-800 text-rose-300/85 border border-rose-500/35 hover:text-rose-200 hover:border-rose-400/70' :
              tone === 'defense' ? 'bg-slate-800 text-blue-300/85 border border-blue-500/35 hover:text-blue-200 hover:border-blue-400/70' :
                                   'bg-slate-800 text-slate-400 border border-slate-700 hover:text-slate-200 hover:border-slate-500';
            const prefix = tone === 'attack' ? '⚡' : tone === 'defense' ? '🛡' : null;
            return (
              <button
                key={f}
                onClick={() => setFormation(f)}
                title={FORMATION_HINT[f] || f}
                className={`px-3 py-1.5 rounded-md text-xs font-bold transition-all flex items-center gap-1 ${isActive ? activeCls : inactiveCls}`}
                style={{ fontFamily: "'Oswald', sans-serif", letterSpacing: '0.06em' }}
              >
                {prefix && <span className="text-[10px] leading-none">{prefix}</span>}
                <span>{f}</span>
              </button>
            );
          })}
          <div className="w-px h-5 bg-slate-700 mx-1" />
          <button
            onClick={() => {
              setConnectMode((v) => !v);
              setConnectFirst(null);
              setSelectedConnectionKey(null);
              setRewireMode(null);
            }}
            className={`px-3 py-1.5 rounded-md text-xs font-bold transition-all flex items-center gap-1.5
              ${connectMode
                ? 'bg-amber-400 text-slate-900 shadow-lg shadow-amber-500/40 ring-2 ring-amber-300'
                : 'bg-slate-800 text-slate-400 border border-slate-700 hover:text-slate-200 hover:border-slate-500'}`}
            style={{ fontFamily: "'Oswald', sans-serif", letterSpacing: '0.06em' }}
            title="連携ライン作成モード"
          >
            <Link2 size={12} strokeWidth={3} /> Connect
          </button>
          {(currentProject?.connections || []).length > 0 && (
            <button
              onClick={clearConnections}
              className="px-2 py-1.5 rounded-md text-xs font-bold bg-slate-800 border border-slate-700 text-slate-400 hover:text-rose-300 hover:border-rose-500/50 flex items-center gap-1.5"
              style={{ fontFamily: "'Oswald', sans-serif", letterSpacing: '0.06em' }}
              title="全連携ラインを削除"
            >
              <Unlink size={12} strokeWidth={3} /> {(currentProject?.connections || []).length}
            </button>
          )}
          <button
            onClick={clearAll}
            className="ml-auto px-3 py-1.5 rounded-md text-xs font-bold bg-slate-800 border border-slate-700 text-slate-400 hover:text-rose-300 hover:border-rose-500/50 flex items-center gap-1.5"
            style={{ fontFamily: "'Oswald', sans-serif", letterSpacing: '0.06em' }}
          >
            <RefreshCw size={12} /> Reset
          </button>
        </div>
      </header>

      <main className="max-w-7xl mx-auto px-4 py-6 space-y-6">
        <section>
          <div className="max-w-[600px] mx-auto space-y-2">
            <GoalBanner
              kind="target"
              value={currentProject?.target || ''}
              onChange={setTarget}
            />
            {/* Y軸（GROWTH）+ X軸（地理／コントロール｜実行）凡例 */}
            <div className="grid grid-cols-1 sm:grid-cols-2 gap-2">
              <div className="px-2.5 py-1.5 rounded-lg bg-amber-500/8 border border-amber-400/20">
                <div className="text-[10px] font-black tracking-widest text-amber-300/90"
                     style={{ fontFamily: "'Oswald', sans-serif", letterSpacing: '0.2em' }}>
                  ↑ GROWTH（攻め）
                </div>
                <div className="text-[8.5px] text-amber-100/65 leading-snug mt-0.5">
                  新規開拓·プレゼン·方針決定·リクルート·契約締結·資金調達
                </div>
              </div>
              <div className="px-2.5 py-1.5 rounded-lg bg-slate-800/40 border border-slate-700/50 flex items-center gap-2">
                <div className="flex-1 min-w-0">
                  <div className="text-[10px] font-black tracking-widest text-emerald-300/90 truncate"
                       style={{ fontFamily: "'Oswald', sans-serif", letterSpacing: '0.2em' }}>
                    ← 西日本
                  </div>
                  <div className="text-[8px] text-emerald-200/55 tracking-wider"
                       style={{ fontFamily: "'Oswald', sans-serif", letterSpacing: '0.18em' }}>
                    GAME CONTROL
                  </div>
                </div>
                <div className="text-center text-[7.5px] text-slate-400/70 italic px-1 leading-tight shrink-0">
                  中央<br/>=実行
                </div>
                <div className="flex-1 min-w-0 text-right">
                  <div className="text-[10px] font-black tracking-widest text-violet-300/90 truncate"
                       style={{ fontFamily: "'Oswald', sans-serif", letterSpacing: '0.2em' }}>
                    東日本 →
                  </div>
                  <div className="text-[8px] text-violet-200/55 tracking-wider"
                       style={{ fontFamily: "'Oswald', sans-serif", letterSpacing: '0.18em' }}>
                    GAME CONTROL
                  </div>
                </div>
              </div>
            </div>
            <Pitch
              lines={connectionLines}
              selectedKey={selectedConnectionKey}
              onLineClick={selectConnection}
              members={members}
              rewireMode={rewireMode}
              onDeleteConnection={deleteSelectedConnection}
              onRewireStart={startRewire}
              onCancelRewire={cancelRewire}
            >
              {formation.map((slot) => {
                const memberId = currentProject?.assignments?.[slot.id];
                const member = members.find((m) => m.id === memberId);
                return (
                  <FieldSlot
                    key={slot.id}
                    slot={slot}
                    member={member}
                    onDrop={handleSlotDrop}
                    onDragStart={handleSlotDragStart}
                    onDragOverSlot={handleSlotDragOver}
                    onDragLeaveSlot={handleSlotDragLeave}
                    onClick={handleSlotClick}
                    onClear={handleClearSlot}
                    onEdit={openEditMember}
                    isHover={hoverSlot === slot.id}
                    isSwapTarget={hoverSlot === slot.id && !!member && dragData.current.memberId != null && dragData.current.memberId !== memberId}
                    connectMode={connectMode}
                    isConnectFirst={connectMode && connectFirst === memberId && memberId != null}
                  />
                );
              })}
            </Pitch>
            {/* Y軸（STABILITY）凡例 */}
            <div className="px-2.5 py-1.5 rounded-lg bg-blue-500/8 border border-blue-400/20">
              <div className="text-[8.5px] text-blue-100/65 leading-snug">
                製造·設計·スケジュール管理·製造計画·総務·法務·チームビルディング
              </div>
              <div className="text-[10px] font-black tracking-widest text-blue-300/90 mt-0.5"
                   style={{ fontFamily: "'Oswald', sans-serif", letterSpacing: '0.2em' }}>
                ↓ STABILITY（守り）
              </div>
            </div>
            <GoalBanner
              kind="risk"
              value={currentProject?.risk || ''}
              onChange={setRisk}
            />
          </div>
          {connectMode && (
            <div className="mt-3 text-center text-xs font-bold text-amber-300 flex items-center justify-center gap-2">
              <Link2 size={12} strokeWidth={3} />
              <span>連携モード: 選手2人をクリックで作成 / 同じペア再選択で削除</span>
              {connectFirst && <span className="text-amber-400">（1人目: {members.find((m) => m.id === connectFirst)?.name} 選択中）</span>}
            </div>
          )}
          {!connectMode && rewireMode && (
            <div className="mt-3 text-center text-xs font-bold text-amber-300 flex items-center justify-center gap-2 animate-pulse">
              <Link2 size={12} strokeWidth={3} />
              <span>繋ぎ変えモード: 入れ替え先の選手をクリックしてください</span>
            </div>
          )}
          {!connectMode && !rewireMode && selectedConnectionKey && (
            <div className="mt-3 text-center text-xs text-amber-300 flex items-center justify-center gap-2">
              <Link2 size={12} strokeWidth={3} />
              <span>ライン選択中: 名前ボタンで繋ぎ変え / 🗑で削除 / もう一度クリックで解除</span>
            </div>
          )}
          {!connectMode && !rewireMode && !selectedConnectionKey && selectedMemberId && (
            <div className="mt-3 text-center text-xs text-amber-300 animate-pulse">
              ▲ ピッチ上のポジションをタップして配置
            </div>
          )}
        </section>

        <section className="rounded-2xl border border-slate-800 bg-slate-900/50 backdrop-blur-sm">
          <div className="flex items-center justify-between px-4 py-3 border-b border-slate-800">
            <div className="flex items-center gap-2">
              <Shirt size={18} className="text-slate-400" />
              <h2 className="text-sm font-black tracking-widest text-slate-200" style={{ fontFamily: "'Oswald', sans-serif" }}>
                BENCH
                <span className="ml-2 text-xs text-slate-500 font-normal">{benchMembers.length} / {members.length}</span>
              </h2>
            </div>
            <button
              onClick={openNewMember}
              className="px-3 py-1.5 rounded-md text-xs font-bold bg-amber-400 text-slate-900 hover:bg-amber-300 flex items-center gap-1.5"
              style={{ fontFamily: "'Oswald', sans-serif", letterSpacing: '0.06em' }}
            >
              <Plus size={14} /> Add Member
            </button>
          </div>
          <div className="p-4">
            {benchMembers.length === 0 ? (
              <div className="text-center py-6 text-slate-500 text-sm">全員ピッチに配置済み</div>
            ) : (
              <div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-3">
                {benchMembers.map((m) => (
                  <MemberCard
                    key={m.id}
                    member={m}
                    onDragStart={handleBenchDragStart}
                    onClick={handleBenchClick}
                    selected={selectedMemberId === m.id}
                    onEdit={openEditMember}
                  />
                ))}
              </div>
            )}
          </div>
        </section>

        <section className="rounded-2xl border border-slate-800 bg-slate-900/30">
          <div className="flex items-center justify-between px-4 py-3 border-b border-slate-800">
            <div className="flex items-center gap-2">
              <Users size={18} className="text-slate-400" />
              <h2 className="text-sm font-black tracking-widest text-slate-200" style={{ fontFamily: "'Oswald', sans-serif" }}>
                ROSTER
                <span className="ml-2 text-xs text-slate-500 font-normal">全{members.length}名</span>
              </h2>
            </div>
          </div>
          <div className="p-4">
            <div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 gap-2">
              {members.map((m) => {
                const placed = assignedMemberIds.includes(m.id);
                const c = getColors(m.role);
                return (
                  <div key={m.id} className="flex items-center gap-3 px-3 py-2 rounded-lg bg-slate-800/50 border border-slate-700/50">
                    <div className={`w-9 h-9 rounded-md ${c.bg} flex items-center justify-center font-bold text-white text-sm shrink-0`}
                         style={{ fontFamily: "'Oswald', sans-serif" }}>
                      {m.number != null && m.number !== '' ? m.number : initials(m.name)}
                    </div>
                    <div className="min-w-0 flex-1">
                      <div className="text-sm font-bold text-slate-100 truncate" style={{ fontFamily: "'Oswald', sans-serif", letterSpacing: '0.02em' }}>{m.name}</div>
                      <div className="text-[10px] text-slate-400 truncate uppercase tracking-wider">{m.role || '—'}</div>
                    </div>
                    {placed && (
                      <span className={`text-[9px] px-1.5 py-0.5 rounded border ${c.chip} font-bold tracking-wider`}>ON</span>
                    )}
                    <button onClick={() => openEditMember(m)} className="p-1.5 text-slate-500 hover:text-amber-300"><Edit2 size={14} /></button>
                  </div>
                );
              })}
            </div>
          </div>
        </section>

        <footer className="text-center text-xs text-slate-600 py-4">
          EdgeSense AI · Squad Builder · ドラッグ&ドロップ または タップで配置
        </footer>
      </main>

      <MemberEditor
        open={editorOpen}
        onClose={() => setEditorOpen(false)}
        onSave={saveMember}
        onDelete={deleteMember}
        initial={editorInitial || { id: null, name: '', role: '', number: null, skills: [] }}
        existingMembers={members}
        customSkills={customSkills}
        onAddCustomSkill={addCustomSkill}
        onRemoveCustomSkill={removeCustomSkill}
      />
    </div>
  );
}

// ============================================================
//   Mount
// ============================================================
const rootEl = document.getElementById('root');
if (rootEl) {
  createRoot(rootEl).render(<SquadBuilder />);
}
