// LP AppGraph — 実アプリ画面（GraphView.tsx）の原価グラフを再現（データ駆動 auto-layout）。
// ・ノードは位置を持たず、種別と接続から列・座標を自動計算（graphLayout.ts の考え方を移植）。
//   → メニュー/仕込み品/食材の数を変えても自動で再配置。viewBox も中身から算出。
// ・1枚SVG・静的描画。ベージュのキャンバスのみ（周辺チャーム無し）。固有名詞は使わない。
// ・デモカーソル＋カメラ（ズーム/パン）で「○クリック→展開／ノードが増える」をループ。

const AG_COL_DEF = { bg: '#FBE3DA', border: '#E8542F', text: '#C73E1D', light: '#F3B5A3' }; // 炎

// ノード（位置なし）。kind: menu / mid（仕込み品）/ ing（食材）
const AG_N = {
  m_shoyu: { kind: 'menu', name: '醤油ラーメン', price: 1000, cost: 303, rate: 30.3 },
  m_ajitama: { kind: 'menu', name: '味玉醤油ラーメン', price: 1100, cost: 348, rate: 31.6 },
  p_soup: { kind: 'mid', name: 'スープ', unit: '¥0.34/ml', sub: '完成量 1,000ml　歩留 95%' },
  p_kaeshi: { kind: 'mid', name: 'かえし', unit: '¥0.77/ml', sub: '完成量 1,000ml　歩留 90%' },
  p_char: { kind: 'mid', name: 'チャーシュー', unit: '¥2.10/g', sub: '完成量 800g　歩留 80%' },
  p_menma: { kind: 'mid', name: 'メンマ', unit: '¥1.20/g', sub: '完成量 500g　歩留 90%' },
  p_ajitama: { kind: 'mid', name: '味玉', unit: '¥45/個', sub: '完成量 50個　歩留 100%' },
  p_aroma: { kind: 'mid', name: '香味油', unit: '¥0.90/ml', sub: '完成量 800ml　歩留 80%' },
  i_kombu: { kind: 'ing', name: '昆布', unit: '¥2.40/g', sub: '仕入 ¥2400/1000g' },
  i_katsuo: { kind: 'ing', name: 'かつお節', unit: '¥2.72/g', sub: '仕入 ¥1360/500g' },
  i_men: { kind: 'ing', name: '麺', unit: '¥80.00/玉', sub: '仕入 ¥80/1玉' },
  i_shoyu: { kind: 'ing', name: '濃口醤油', unit: '¥0.63/ml', sub: '仕入 ¥630/1000ml' },
  i_pork: { kind: 'ing', name: '豚肩ロース', unit: '¥1.65/g', sub: '仕入 ¥1650/1000g' },
  i_negi: { kind: 'ing', name: 'ネギ', unit: '¥0.80/g', sub: '仕入 ¥240/300g' },
};

// from（子・材料側）→ to（親・完成側）, 使用量, 金額
const AG_E = [
  ['i_kombu', 'p_soup', '70g', '¥168'],
  ['i_katsuo', 'p_soup', '15g', '¥41'],
  ['i_shoyu', 'p_kaeshi', '900ml', '¥567'],
  ['i_pork', 'p_char', '1000g', '¥1650'],
  ['i_negi', 'p_aroma', '200g', '¥160'],
  ['p_kaeshi', 'p_menma', '60ml', '¥46'],   // 仕込み品→仕込み品（かえしで味付け）
  ['p_kaeshi', 'p_ajitama', '300ml', '¥231'],
  // 醤油ラーメン
  ['p_soup', 'm_shoyu', '280ml', '¥95'],
  ['p_kaeshi', 'm_shoyu', '35ml', '¥27'],
  ['p_char', 'm_shoyu', '40g', '¥84'],
  ['p_menma', 'm_shoyu', '20g', '¥24'],
  ['p_aroma', 'm_shoyu', '8ml', '¥7'],
  ['i_men', 'm_shoyu', '1玉', '¥80'],
  ['i_negi', 'm_shoyu', '5g', '¥4'],
  // 味玉醤油ラーメン（＝醤油＋味玉）
  ['p_soup', 'm_ajitama', '280ml', '¥95'],
  ['p_kaeshi', 'm_ajitama', '35ml', '¥27'],
  ['p_char', 'm_ajitama', '40g', '¥84'],
  ['p_menma', 'm_ajitama', '20g', '¥24'],
  ['p_ajitama', 'm_ajitama', '1個', '¥45'],
  ['p_aroma', 'm_ajitama', '8ml', '¥7'],
  ['i_men', 'm_ajitama', '1玉', '¥80'],
  ['i_negi', 'm_ajitama', '5g', '¥4'],
];

const AG_NODE_W = 170;
const AG_COL_GAP = 100;
const AG_ROW_GAP = 16;
const AG_PAD = 28;
const AG_FULL_H = 92;
const AG_SHORT_H = 74;
const AG_LABEL_W = 72;
const AG_LABEL_H = 32;
const AG_LABEL_GAP = 4;
const AG_NAME_MAX_W = AG_NODE_W - 20; // ノード名の折返し幅（左右padを除く）

// 単位（"¥2.40/g" → "g"）と単価（→2.40）を取り出す。並び順の判定に使う。
const agUnitOf = (n) => { const m = String(n.unit || '').match(/\/\s*(.+)$/); return m ? m[1].trim() : ''; };
const agUnitPriceOf = (n) => { const m = String(n.unit || '').match(/¥\s*([\d.]+)/); return m ? parseFloat(m[1]) : 0; };

// ノード名を最大幅で1〜2行に折る（SVGは自動折返ししないため手動。半角・記号は狭めに概算）。
const agCharW = (ch, fs) => (/[\x20-\x7E｡-ﾟ]/.test(ch) ? fs * 0.6 : fs);
function agWrapName(name, fs, maxW) {
  const lines = [];
  let cur = '', curW = 0;
  for (const ch of String(name)) {
    const cw = agCharW(ch, fs);
    if (curW + cw > maxW && cur) { lines.push(cur); cur = ch; curW = cw; }
    else { cur += ch; curW += cw; }
  }
  if (cur) lines.push(cur);
  return lines.length <= 2 ? lines : [lines[0], lines.slice(1).join('')];
}

// ---- auto-layout（種別→列、親の重心→縦位置） ----
function agLayout() {
  const ids = Object.keys(AG_N);
  const node = {};
  ids.forEach((id) => {
    const d = AG_N[id];
    node[id] = Object.assign({ id, h: d.kind === 'menu' ? AG_FULL_H : AG_SHORT_H, level: d.kind === 'menu' ? 0 : -1, y: null }, d);
  });
  // parents[child] = [parent...]（from→to の to が親）
  const parents = {};
  ids.forEach((id) => { parents[id] = []; });
  AG_E.forEach((e) => { parents[e[0]].push(e[1]); });

  // 仕込み品のレベル＝親レベル+1 の最大（収束まで反復）
  for (let iter = 0; iter < ids.length + 2; iter++) {
    let changed = false;
    ids.forEach((id) => {
      const n = node[id];
      if (n.kind !== 'mid') return;
      let lv = -1;
      parents[id].forEach((pid) => { if (node[pid].level >= 0) lv = Math.max(lv, node[pid].level + 1); });
      if (lv > n.level) { n.level = lv; changed = true; }
    });
    if (!changed) break;
  }
  ids.forEach((id) => { if (node[id].kind === 'mid' && node[id].level < 0) node[id].level = 1; });
  const midLevels = ids.filter((id) => node[id].kind === 'mid').map((id) => node[id].level);
  const maxMid = midLevels.length ? Math.max.apply(null, midLevels) : 0;
  const ingLevel = maxMid + 1;
  ids.forEach((id) => { if (node[id].kind === 'ing') node[id].level = ingLevel; });

  // x = レベル×(幅+余白)
  ids.forEach((id) => { node[id].x = node[id].level * (AG_NODE_W + AG_COL_GAP) + AG_PAD; });

  // y = 親（左側・配置済み）の重心。レベル順に処理し、重なりを下へ逃がして列を中央寄せ。
  for (let L = 0; L <= ingLevel; L++) {
    const group = ids.filter((id) => node[id].level === L).map((id) => node[id]);
    if (group.length === 0) continue;
    if (L === 0) {
      let cur = AG_PAD;
      group.forEach((n) => { n.y = cur; cur += n.h + AG_ROW_GAP; });
      continue;
    }
    group.forEach((n) => {
      const ps = parents[n.id].map((pid) => node[pid]).filter((p) => p.level < L && p.y != null);
      n._by = ps.length ? ps.reduce((s, p) => s + p.y + p.h / 2, 0) / ps.length - n.h / 2 : AG_PAD;
    });
    // 並び順: 単位ごとにまとめ、各単位内は原価（単価）の高いものを上に。
    // 単位グループ同士の順序は親の重心順（自然な位置）に従う。
    const gPosSum = {}, gPosCnt = {};
    group.forEach((n) => { const u = agUnitOf(n); gPosSum[u] = (gPosSum[u] || 0) + n._by; gPosCnt[u] = (gPosCnt[u] || 0) + 1; });
    const gPos = (u) => gPosSum[u] / gPosCnt[u];
    group.sort((a, b) => {
      const ua = agUnitOf(a), ub = agUnitOf(b);
      if (ua !== ub) return gPos(ua) - gPos(ub);
      return agUnitPriceOf(b) - agUnitPriceOf(a);
    });
    let prevBottom = -1e9;
    group.forEach((n) => { const y = Math.max(n._by, prevBottom + AG_ROW_GAP); n.y = y; prevBottom = y + n.h; });
    // 列を希望重心の中央へ寄せる
    const want = group.reduce((s, n) => s + n._by + n.h / 2, 0) / group.length;
    const got = group.reduce((s, n) => s + n.y + n.h / 2, 0) / group.length;
    const shift = want - got;
    group.forEach((n) => { n.y += shift; });
  }

  // 全体を上方向に正規化
  const minY = Math.min.apply(null, ids.map((id) => node[id].y));
  const dy = AG_PAD - minY;
  if (dy !== 0) ids.forEach((id) => { node[id].y += dy; });

  const W = Math.max.apply(null, ids.map((id) => node[id].x)) + AG_NODE_W + AG_PAD;
  const H = Math.max.apply(null, ids.map((id) => node[id].y + node[id].h)) + AG_PAD;
  return { node, W: W, H: H, maxLevel: ingLevel };
}

const AG_L = agLayout();
const AG_DELAY = (y) => `${(y / AG_L.H) * 0.25}s`;

// ---- デモのカメラ/カーソル（ノードIDから座標算出。データが変わっても追従） ----
function agPhases(L) {
  const node = L.node;
  const W = L.W, H = L.H, NW = AG_NODE_W, maxL = L.maxLevel;
  const list = (k) => Object.keys(node).map((id) => node[id]).filter((n) => n.kind === k);
  const menu = list('menu').sort((a, b) => a.y - b.y)[0];
  const mid = list('mid').sort((a, b) => a.level - b.level || a.y - b.y)[0];
  const ing = list('ing').sort((a, b) => a.y - b.y)[0];
  const ctr = (n) => [n.x + NW / 2, n.y + n.h / 2];
  const circ = (n) => [n.x + NW - 11, n.y + 11]; // メニューの○
  const tri = (n) => [n.x + NW - 11, n.y + 15];  // 仕込み品の▼
  const mid2 = [mid.x + NW + AG_COL_GAP / 2, mid.y + mid.h / 2]; // 仕込み品の少し右（食材が出る所）
  const wide = [W / 2, H / 2];
  const idle = [W * 0.12, H * 0.52];
  return [
    { rl: 0, exp: false, s: 1, focus: wide, cur: idle, click: false },         // 0 全体・待機
    { rl: 0, exp: false, s: 1.18, focus: ctr(menu), cur: circ(menu), click: false }, // 1 ○へ移動
    { rl: 1, exp: true, s: 1.18, focus: ctr(menu), cur: circ(menu), click: true },   // 2 ○クリック→仕込み品
    { rl: 1, exp: true, s: 1.22, focus: ctr(mid), cur: tri(mid), click: false },     // 3 ○から離れ仕込み品へ
    { rl: maxL, exp: true, s: 1.22, focus: mid2, cur: tri(mid), click: true },        // 4 クリック→食材まで展開
    { rl: maxL, exp: true, s: 1.5, focus: ctr(ing), cur: ctr(ing), click: false },    // 5 食材・原価をズーム
    { rl: maxL, exp: true, s: 1, focus: wide, cur: idle, click: false },              // 6 引いて全体
    { rl: 0, exp: false, s: 1, focus: wide, cur: idle, click: false },                // 7 折りたたみ→先頭
  ];
}
const AG_PHASES = agPhases(AG_L);

// 商品名: 上端(y+16)から表示。1行なら1行ぶん、2行なら折り返して2行ぶん（箱の高さも行数に追従）。
function AgNodeName({ x, y, name, fontWeight, fill }) {
  const lines = agWrapName(name, 12, AG_NAME_MAX_W);
  if (lines.length === 1) {
    return <text x={x} y={y + 16} fontSize={12} fontWeight={fontWeight} fill={fill}>{lines[0]}</text>;
  }
  return (
    <text x={x} y={y + 16} fontSize={12} fontWeight={fontWeight} fill={fill}>
      <tspan x={x}>{lines[0]}</tspan>
      <tspan x={x} dy={14}>{lines[1]}</tspan>
    </text>
  );
}

// ---- ノード描画（GraphView の NodeEl 準拠） ----
function AgNode({ n, vis, expanded }) {
  const c = AG_COL_DEF;
  const w = AG_NODE_W;
  const pad = 10;
  // 名前の最終行ベースライン（2行なら+30、1行なら+16）を基準に下の行を積む。
  const nameBottom = n.y + (n.nLines === 2 ? 30 : 16);
  const line2Y = nameBottom + 20;
  const line3Y = nameBottom + 36;
  const gStyle = { opacity: vis ? 1 : 0, transition: 'opacity .55s ease', transitionDelay: AG_DELAY(n.y) };

  if (n.kind === 'menu') {
    const barY = line3Y + 6;
    const barW = w - pad * 2;
    return (
      <g style={gStyle}>
        <rect x={n.x} y={n.y} width={w} height={n.h} rx={8} fill={c.bg} stroke={c.border} strokeWidth={2} />
        <AgNodeName x={n.x + pad} y={n.y} name={n.name} fontWeight="700" fill={c.text} />
        <text x={n.x + pad} y={line2Y} fontSize={10} fill={c.text}>¥{n.cost} / ¥{n.price.toLocaleString()}</text>
        <text x={n.x + pad} y={line3Y} fontSize={10} fill={c.text}>原価率 {n.rate.toFixed(1)}%</text>
        <rect x={n.x + pad} y={barY} width={barW} height={6} rx={3} fill="rgba(255,255,255,0.5)" />
        <rect x={n.x + pad} y={barY} width={Math.min((n.rate / 100) * barW, barW)} height={6} rx={3} fill={c.border} />
        <text x={n.x + w - 29} y={n.y + 15} fontSize={10} fill={c.border} textAnchor="middle">{expanded ? '▲' : '▼'}</text>
        <circle cx={n.x + w - 11} cy={n.y + 11} r={expanded ? 8 : 7} fill={expanded ? c.bg : 'white'} stroke={c.border} strokeWidth={expanded ? 2 : 1.5} />
        <circle cx={n.x + w - 11} cy={n.y + 11} r={expanded ? 3.5 : 3} fill={c.border} />
      </g>
    );
  }
  if (n.kind === 'mid') {
    return (
      <g style={gStyle}>
        <rect x={n.x} y={n.y} width={w} height={n.h} rx={8} fill={c.bg} stroke={c.border} strokeWidth={1.5} />
        <AgNodeName x={n.x + pad} y={n.y} name={n.name} fontWeight="700" fill={c.text} />
        <text x={n.x + pad} y={line2Y} fontSize={10} fill="#7E7565">{n.unit}</text>
        <text x={n.x + pad} y={line3Y} fontSize={10} fill={c.border}>{n.sub}</text>
        <text x={n.x + w - 11} y={n.y + 15} fontSize={10} fill={c.border} textAnchor="middle">▼</text>
      </g>
    );
  }
  return (
    <g style={gStyle}>
      <rect x={n.x} y={n.y} width={w} height={n.h} rx={8} fill="#FAF8F4" stroke={c.light} strokeWidth={1.5} />
      <AgNodeName x={n.x + pad} y={n.y} name={n.name} fontWeight="400" fill="#443E34" />
      <text x={n.x + pad} y={line2Y} fontSize={10} fill="#7E7565">{n.unit}</text>
      <text x={n.x + pad} y={line3Y} fontSize={9} fill="#A89E8C">{n.sub}</text>
    </g>
  );
}

function YLAppGraph() {
  const node = AG_L.node;
  const [reduce] = React.useState(
    () => !!(window.matchMedia && window.matchMedia('(prefers-reduced-motion: reduce)').matches),
  );
  const [pi, setPi] = React.useState(0);
  React.useEffect(() => {
    if (reduce) return;
    let i = 0;
    const id = setInterval(() => { i = (i + 1) % AG_PHASES.length; setPi(i); }, 1600);
    return () => clearInterval(id);
  }, [reduce]);

  const ph = reduce ? { rl: AG_L.maxLevel, exp: true, s: 1, focus: [AG_L.W / 2, AG_L.H / 2], cur: [0, 0], click: false } : AG_PHASES[pi];
  const camTx = AG_L.W / 2 - ph.s * ph.focus[0];
  const camTy = AG_L.H / 2 - ph.s * ph.focus[1];
  const camStyle = {
    transform: `translate(${camTx}px, ${camTy}px) scale(${ph.s})`,
    transformOrigin: '0 0',
    transition: 'transform 1.25s cubic-bezier(0.4, 0, 0.2, 1)',
  };
  const cursorStyle = {
    transform: `translate(${ph.cur[0]}px, ${ph.cur[1]}px)`,
    transition: 'transform 1.05s cubic-bezier(0.5, 0, 0.2, 1)',
  };

  const nodeVis = (n) => n.level <= ph.rl;
  const edgeVis = (fromId) => node[fromId].level <= ph.rl;

  const byFrom = {};
  AG_E.forEach((e) => { (byFrom[e[0]] = byFrom[e[0]] || []).push(e); });

  return (
    <div style={{ background: 'var(--app-canvas)', borderRadius: 16, padding: 12, overflow: 'hidden' }}>
      <svg viewBox={`0 0 ${AG_L.W} ${AG_L.H}`} style={{ display: 'block', width: '100%', height: 'auto' }}>
        <g style={camStyle}>
          {/* エッジ線 */}
          {AG_E.map((e, i) => {
            const f = node[e[0]], t = node[e[1]];
            const x1 = f.x, y1 = f.y + f.h / 2;
            const x2 = t.x + AG_NODE_W, y2 = t.y + t.h / 2;
            return (
              <path key={`l${i}`} d={`M ${x1} ${y1} C ${x1 - 40} ${y1}, ${x2 + 40} ${y2}, ${x2} ${y2}`}
                fill="none" stroke={AG_COL_DEF.light} strokeWidth={1.8}
                style={{ opacity: edgeVis(e[0]) ? 1 : 0, transition: 'opacity .55s ease', transitionDelay: AG_DELAY(y1) }} />
            );
          })}
          {/* エッジラベル（子ノード左横・使用量＋金額） */}
          {Object.keys(byFrom).map((fromId) => {
            const f = node[fromId];
            const edges = byFrom[fromId];
            const c = AG_COL_DEF;
            const totalH = edges.length * AG_LABEL_H + (edges.length - 1) * AG_LABEL_GAP;
            const lx = f.x - AG_LABEL_W - 6;
            const startY = f.y + f.h / 2 - totalH / 2;
            return (
              <g key={`g-${fromId}`} style={{ opacity: edgeVis(fromId) ? 1 : 0, transition: 'opacity .55s ease', transitionDelay: AG_DELAY(f.y) }}>
                {edges.map((e, idx) => {
                  const ly = startY + idx * (AG_LABEL_H + AG_LABEL_GAP);
                  return (
                    <g key={idx}>
                      <rect x={lx} y={ly} width={AG_LABEL_W} height={AG_LABEL_H} rx={7} fill={c.bg} stroke={c.border} strokeWidth={1} />
                      <text x={lx + AG_LABEL_W / 2} y={ly + 13} textAnchor="middle" fontSize={11} fontWeight="600" fill="#2C2823">{e[2]}</text>
                      <text x={lx + AG_LABEL_W / 2} y={ly + 26} textAnchor="middle" fontSize={10} fill="#7E7565">{e[3]}</text>
                    </g>
                  );
                })}
              </g>
            );
          })}
          {/* ノード（前面） */}
          {Object.keys(node).map((id) => (
            <AgNode key={id} n={node[id]} vis={nodeVis(node[id])} expanded={ph.exp && node[id].kind === 'menu'} />
          ))}

          {/* デモカーソル（カメラ群の内側＝ノードと座標共有） */}
          {!reduce && (
            <g style={cursorStyle}>
              {ph.click && (
                <circle key={pi} className="ag-ripple" cx={0} cy={0} r={3} fill="none" stroke="#E8542F" strokeWidth={2} />
              )}
              <path d="M0 0 L0 17 L4.2 12.8 L7 18.6 L9.6 17.4 L6.8 11.7 L12.2 11.5 Z"
                fill="#FFFFFF" stroke="#1C1A17" strokeWidth={1.2} strokeLinejoin="round" />
            </g>
          )}
        </g>
      </svg>
    </div>
  );
}

window.YLAppGraph = YLAppGraph;
