// Edge rendering: arrow/heart/crack/dotted/wavy between two nodes.
// Also handles multi-edge offset between same pair so they don't overlap.

// Intersection: find point on circle (cx,cy,r) on the line from (cx,cy) toward (tx,ty)
function edgePointOnCircle(cx,cy,tx,ty,r){
  const dx = tx-cx, dy = ty-cy;
  const L = Math.hypot(dx,dy) || 1;
  return { x: cx + dx/L*r, y: cy + dy/L*r };
}

// Build a quadratic-bezier path between two points with a perpendicular offset.
function buildCurve(p1, p2, offset){
  const mx = (p1.x+p2.x)/2, my = (p1.y+p2.y)/2;
  const dx = p2.x-p1.x, dy = p2.y-p1.y;
  const L = Math.hypot(dx,dy) || 1;
  const nx = -dy/L, ny = dx/L;
  const cx = mx + nx*offset;
  const cy = my + ny*offset;
  return { d: `M ${p1.x} ${p1.y} Q ${cx} ${cy} ${p2.x} ${p2.y}`, cx, cy };
}

// Sample points along a quadratic bezier
function sampleQuad(p1, c, p2, n=60){
  const pts=[];
  for(let i=0;i<=n;i++){
    const t=i/n;
    const it=1-t;
    const x = it*it*p1.x + 2*it*t*c.x + t*t*p2.x;
    const y = it*it*p1.y + 2*it*t*c.y + t*t*p2.y;
    pts.push({x,y,t});
  }
  return pts;
}

// Convert a polyline into an SVG path d
function polyToPath(pts){
  return pts.map((p,i)=> (i?'L':'M') + p.x.toFixed(1) + ' ' + p.y.toFixed(1)).join(' ');
}

// Build a "wavy" path along the curve
function wavePathAlong(p1, c, p2, amp=7, freq=0.09){
  const samples = sampleQuad(p1,c,p2,120);
  const out=[];
  for(let i=0;i<samples.length;i++){
    const s = samples[i];
    // tangent
    const prev = samples[Math.max(0,i-1)];
    const nx = -(s.y - prev.y), ny = (s.x - prev.x);
    const L = Math.hypot(nx,ny) || 1;

    // 波が両端（矢印の根本）で綺麗に中央へ収束するように、両端15%で振幅をフェードアウトする
    const t = i / (samples.length - 1);
    const fade = Math.min(1, t / 0.15, (1 - t) / 0.15);

    const off = Math.sin(i*freq*Math.PI) * amp * fade;
    out.push({x: s.x + nx/L*off, y: s.y + ny/L*off});
  }
  return polyToPath(out);
}

// Build a zig-zag "crack" path along the curve
function crackPathAlong(p1, c, p2, amp=10){
  const samples = sampleQuad(p1,c,p2,14);
  const out=[];
  for(let i=0;i<samples.length;i++){
    const s = samples[i];
    if (i===0 || i===samples.length-1){ out.push({x:s.x,y:s.y}); continue; }
    const prev = samples[i-1];
    const nx = -(s.y - prev.y), ny = (s.x - prev.x);
    const L = Math.hypot(nx,ny) || 1;
    const off = (i%2===0?1:-1) * amp * (1 - Math.abs(i - samples.length/2)/(samples.length/2)*0.3);
    out.push({x: s.x + nx/L*off, y: s.y + ny/L*off});
  }
  return polyToPath(out);
}

// Pill-shaped label whose width is measured from the rendered <text> via getBBox,
// so long labels (especially CJK) don't visually spill past the white rounded rect.
function EdgeLabelBox({ text, x, y, color, selected, onSelect, onEdit, onDragStart }){
  const textRef = React.useRef(null);
  const [w, setW] = React.useState(() => Math.max(36, text.length * 12 + 24));
  React.useLayoutEffect(()=>{
    if (textRef.current) {
      try {
        const bb = textRef.current.getBBox();
        const next = Math.max(36, bb.width + 20);
        if (Math.abs(next - w) > 0.5) setW(next);
      } catch(_){}
    }
  }, [text]);
  return (
    <g
      transform={`translate(${x},${y})`}
      className={`edge-label-g${selected?' selected':''}`}
      onClick={(e)=>{e.stopPropagation(); onSelect && onSelect(e.shiftKey);}}
      onDoubleClick={(e)=>{e.stopPropagation(); onEdit && onEdit();}}
      onPointerDown={(e)=>{
        e.stopPropagation();
        if (e.shiftKey) return; // click handler will process shift+click (swap z-order)
        onSelect && onSelect(false);
        if(onDragStart) onDragStart(e);
      }}
      style={{cursor:'pointer'}}
    >
      <rect
        x={-w/2} y={-14} width={w} height={28} rx={14}
        fill="#fff"
        stroke={selected ? '#F56A92' : '#F2D4DD'}
        strokeWidth={2}
      />
      <text
        ref={textRef}
        x={0} y={1}
        textAnchor="middle"
        dominantBaseline="middle"
        fontFamily='"M PLUS Rounded 1c","Zen Maru Gothic",system-ui,sans-serif'
        fontWeight={800}
        fontSize={13}
        fill={color}
      >{text}</text>
    </g>
  );
}

function Edge({ edge, nodes, offset, selected, onSelect, onEdit, onDragStart }){
  const a = nodes[edge.from];
  const b = nodes[edge.to];
  if (!a || !b) return null;

  const nr = 48; // visual radius of node avatar (approx)

  const p1full = { x:a.x, y:a.y };
  const p2full = { x:b.x, y:b.y };
  const actualOffset = edge.customOffset !== undefined ? edge.customOffset : offset;
  const { cx, cy } = buildCurve(p1full, p2full, actualOffset);

  // shrink endpoints to node boundary using control point direction
  const p1 = edgePointOnCircle(a.x,a.y, cx, cy, nr);
  const p2 = edgePointOnCircle(b.x,b.y, cx, cy, nr);

  const curve = buildCurve(p1, p2, actualOffset * 0.6);

  const rel = RELATIONS.find(r => r.id === edge.relation) || RELATIONS[0];
  const color = edge.color || rel.color;
  const stroke = 4;
  const style = edge.style || 'oneway';

  let pathD = curve.d;
  let dash = '';
  if (style === 'dotted') dash = '0.1 12';
  if (style === 'wavy') pathD = wavePathAlong(p1, {x:curve.cx,y:curve.cy}, p2, 6, 0.12);
  if (style === 'cracked') pathD = crackPathAlong(p1, {x:curve.cx,y:curve.cy}, p2, 10);

  // Build arrowhead polygons instead of SVG markers (html-to-image breaks markers)
  const arrowSize = 18; // Increased from 10
  const needEnd = (style === 'oneway' || style === 'twoway' || style === 'dotted' || style === 'wavy');
  const needStart = (style === 'twoway');

  const endArrow = needEnd ? buildArrowhead(p1, {x:curve.cx,y:curve.cy}, p2, 1.0, arrowSize) : null;
  const startArrow = needStart ? buildArrowhead(p1, {x:curve.cx,y:curve.cy}, p2, 0.0, arrowSize) : null;

  // label position: mid of curve
  const labelX = 0.25*p1.x + 0.5*curve.cx + 0.25*p2.x;
  const labelY = 0.25*p1.y + 0.5*curve.cy + 0.25*p2.y;

  // hearts along curve (for heart style)
  const hearts = [];
  if (style === 'heart'){
    for (let t of [0.25,0.5,0.75]){
      const it=1-t;
      const x = it*it*p1.x + 2*it*t*curve.cx + t*t*p2.x;
      const y = it*it*p1.y + 2*it*t*curve.cy + t*t*p2.y;
      hearts.push({x,y,t});
    }
  }

  const labelText = edge.label ?? rel.label;
  const stamp = edge.stamp || '';

  return (
    <g className={`edge-group${selected?' selected':''}`} data-edge-id={edge.id}>
      <path className="edge-hit" d={curve.d}
            onClick={(e)=>onSelect(edge.id, e.shiftKey)}
            onDoubleClick={()=>onEdit(edge.id)}
            onPointerDown={(e)=>{
              if(onDragStart && !e.shiftKey) onDragStart(edge.id, e);
            }}/>
      <path
        className="edge-path"
        d={pathD}
        stroke={color}
        strokeWidth={stroke}
        strokeLinecap="round"
        strokeLinejoin="round"
        strokeDasharray={dash || undefined}
        fill="none"
      />

      {endArrow && <polygon points={endArrow} fill={color} />}
      {startArrow && <polygon points={startArrow} fill={color} />}

      {hearts.map((h,i)=>(
        <g key={i} transform={`translate(${h.x},${h.y}) scale(${0.9 + i*0.05})`}>
          <HeartShape color={color} />
        </g>
      ))}

      {labelText || stamp ? (
        <EdgeLabelBox
          text={(stamp ? stamp + ' ' : '') + (labelText || '')}
          x={labelX} y={labelY}
          color={color}
          selected={selected}
          onSelect={(shiftKey)=>onSelect(edge.id, shiftKey)}
          onEdit={()=>onEdit(edge.id)}
          onDragStart={onDragStart ? (e)=>onDragStart(edge.id, e) : null}
        />
      ) : null}
    </g>
  );
}

// Build an arrowhead polygon at a point along a quadratic bezier.
// tPos: 0.0 = start, 1.0 = end
function buildArrowhead(p1, c, p2, tPos, size){
  size = size || 10;
  // Sample the tangent near the target point
  const eps = 0.02;
  let tA, tB;
  if (tPos >= 1.0) { tA = 1.0 - eps; tB = 1.0; }
  else { tA = 0.0; tB = eps; }

  const evalBez = (t) => {
    const it = 1 - t;
    return { x: it*it*p1.x + 2*it*t*c.x + t*t*p2.x,
             y: it*it*p1.y + 2*it*t*c.y + t*t*p2.y };
  };

  const pA = evalBez(tA);
  const pB = evalBez(tB);
  // tangent direction (pointing from A toward B)
  let dx = pB.x - pA.x, dy = pB.y - pA.y;
  const L = Math.hypot(dx, dy) || 1;
  dx /= L; dy /= L;

  // For start arrow, reverse direction
  if (tPos < 0.5) { dx = -dx; dy = -dy; }

  const tip = evalBez(tPos);
  // Two base points perpendicular to the tangent
  const nx = -dy, ny = dx;
  const halfW = size * 0.6; // Slightly wider base (0.6 instead of 0.5)
  const backDist = size;

  // 線の端の丸み(ラウンドキャップ)が矢印の先端からはみ出ないように、矢印自体を6px前方にずらす
  tip.x += dx * 6;
  tip.y += dy * 6;

  const bx = tip.x - dx * backDist;
  const by = tip.y - dy * backDist;

  const x1 = bx + nx * halfW;
  const y1 = by + ny * halfW;
  const x2 = bx - nx * halfW;
  const y2 = by - ny * halfW;

  return `${tip.x},${tip.y} ${x1},${y1} ${x2},${y2}`;
}

function HeartShape({color}){
  return (
    <path d="M0,-2 C -4,-10 -14,-6 -14,2 C -14,10 0,16 0,16 C 0,16 14,10 14,2 C 14,-6 4,-10 0,-2 Z"
          fill={color}
          stroke="#fff" strokeWidth="2"
          transform="scale(0.7)"/>
  );
}

window.Edge = Edge;
// Expose helpers reused by Writing connection lines (Canvas.jsx) — they share
// the exact same quadratic-bezier math as edges so behavior stays consistent.
window.buildCurve = buildCurve;
