/**
 * ChatDetailSplit — ワークスペース会話の「チャット ＋ 詳細」2ペイン分割。
 *
 * 広い画面で、左にチャットを常時表示したまま、右に選択中の詳細タブ
 * （ブラウザ / SSH / 概要 など）を並べる。タスクページの集中モードに近い操作感を、
 * rail やダッシュボードを持ち込まずに実現する。
 *
 * 設計のキモ:
 * - 左ペイン（チャット）は rightVisible の切り替えで remount しない。`right` を
 *   出し入れするだけなので、詳細タブを開閉してもチャットの入力中テキストや
 *   スクロール位置が保たれる。
 * - 幅はコンテナ ref + インライン style（px）で持ち、ドラッグ中は React 再 render
 *   を避けるため ref で直接 style を書き換える。確定時に localStorage へ保存。
 * - 右ペイン非表示時は左を 100% にし、ハンドルも出さない。
 *
 * spec: docs/superpowers/specs/2026-06-19-space-invite-links-design.md は招待リンク用。
 *       本コンポーネントはワークスペース統合（タスクページ廃止に向けた操作感寄せ）の一部。
 */
import { useCallback, useEffect, useRef, useState } from 'react';
import { useTranslation } from 'react-i18next';

export const SPLIT_MIN_LEFT = 320; // チャットの最小幅(px)
export const SPLIT_MIN_RIGHT = 360; // 詳細の最小幅(px)
export const SPLIT_HANDLE_PX = 6;
/** 2ペインを成立させるのに必要な最小コンテナ幅。これ未満では分割しない。 */
export const SPLIT_MIN_TOTAL = SPLIT_MIN_LEFT + SPLIT_HANDLE_PX + SPLIT_MIN_RIGHT;

/**
 * 与えられたコンテナ幅で2ペイン分割が成立するか（両最小幅＋ハンドルが収まるか）。
 * 計測前（width=0）は楽観的に true を返し、ResizeObserver 確定後に絞り込む。
 */
export function splitFitsWidth(containerPx: number): boolean {
  return containerPx === 0 || containerPx >= SPLIT_MIN_TOTAL;
}

/**
 * 望ましい左ペイン幅を、最小幅制約の中にクランプする純関数（テスト対象）。
 * total が両最小幅 + ハンドルを収めきれない狭さなら、左は最小幅に倒す。
 */
export function clampSplitLeft(
  desiredLeftPx: number,
  totalPx: number,
  minLeft = SPLIT_MIN_LEFT,
  minRight = SPLIT_MIN_RIGHT,
  handle = SPLIT_HANDLE_PX,
): number {
  const maxLeft = Math.max(minLeft, totalPx - handle - minRight);
  return Math.max(minLeft, Math.min(maxLeft, desiredLeftPx));
}

function loadStoredWidth(key: string): number | null {
  try {
    const raw = localStorage.getItem(key);
    const n = raw == null ? NaN : Number(raw);
    return Number.isFinite(n) && n > 0 ? n : null;
  } catch {
    return null;
  }
}

interface ChatDetailSplitProps {
  left: React.ReactNode;
  right: React.ReactNode;
  /** false のとき右ペイン・ハンドルを出さず、左を全幅にする。 */
  rightVisible: boolean;
  /** 左ペイン幅を保存する localStorage キー。 */
  storageKey: string;
}

export function ChatDetailSplit({ left, right, rightVisible, storageKey }: ChatDetailSplitProps) {
  const { t } = useTranslation('spaces');
  const containerRef = useRef<HTMLDivElement>(null);
  const leftPaneRef = useRef<HTMLDivElement>(null);
  const draggingRef = useRef(false);
  const lastLeftPxRef = useRef<number | null>(null);
  // 初期幅: 保存値があれば px、無ければ null（= デフォルトの割合 fraction）。
  const [leftPx, setLeftPx] = useState<number | null>(() => loadStoredWidth(storageKey));
  // 現在のコンテナ幅。保存済み px を狭い画面で clamp し直すために計測する。
  const [containerW, setContainerW] = useState(0);

  // コンテナ幅を監視（ResizeObserver）。広い画面で保存した px を狭い画面で開いたとき
  // 右ペインが最小幅を割らないよう、描画時に clampSplitLeft へ通すための材料。
  useEffect(() => {
    const el = containerRef.current;
    if (!el || typeof ResizeObserver === 'undefined') return;
    const ro = new ResizeObserver((entries) => {
      const w = entries[0]?.contentRect.width ?? 0;
      if (w > 0) setContainerW(w);
    });
    ro.observe(el);
    return () => ro.disconnect();
  }, []);

  // ドラッグ終了処理（pointerup / unmount / 右ペイン非表示化のいずれでも呼ぶ）。
  // listener 削除だけだと draggingRef・body の cursor/userSelect が stuck するため、
  // ここで必ず巻き戻す。persist=true のときだけ確定幅を保存する。
  const endDrag = useCallback((persist: boolean) => {
    if (!draggingRef.current) return;
    draggingRef.current = false;
    document.body.style.cursor = '';
    document.body.style.userSelect = '';
    if (persist && lastLeftPxRef.current != null) {
      const px = lastLeftPxRef.current;
      setLeftPx(px);
      try { localStorage.setItem(storageKey, String(Math.round(px))); } catch { /* ignore */ }
    }
  }, [storageKey]);

  // ドラッグ中の pointer 処理。ref-based で再 render せず style を直接書き換える。
  useEffect(() => {
    // 右ペインが無いときは drag できない。途中で非表示化されたら状態を巻き戻す。
    if (!rightVisible) { endDrag(false); return; }
    const onMove = (e: PointerEvent) => {
      if (!draggingRef.current) return;
      const el = containerRef.current;
      const pane = leftPaneRef.current;
      if (!el || !pane) return;
      const rect = el.getBoundingClientRect();
      const next = clampSplitLeft(e.clientX - rect.left, rect.width);
      lastLeftPxRef.current = next;
      pane.style.flex = `0 0 ${next}px`;
    };
    const onUp = () => endDrag(true);
    window.addEventListener('pointermove', onMove);
    window.addEventListener('pointerup', onUp);
    window.addEventListener('pointercancel', onUp);
    return () => {
      window.removeEventListener('pointermove', onMove);
      window.removeEventListener('pointerup', onUp);
      window.removeEventListener('pointercancel', onUp);
      // unmount / rightVisible 変化が drag 中なら body スタイルを戻す（保存はしない）。
      endDrag(false);
    };
  }, [rightVisible, endDrag]);

  const handlePointerDown = useCallback((e: React.PointerEvent<HTMLDivElement>) => {
    e.preventDefault();
    draggingRef.current = true;
    document.body.style.cursor = 'col-resize';
    document.body.style.userSelect = 'none';
  }, []);

  const handleReset = useCallback(() => {
    setLeftPx(null);
    try { localStorage.removeItem(storageKey); } catch { /* ignore */ }
  }, [storageKey]);

  // 幅が足りないと2ペインにせず、選んだ詳細を単独表示にフォールバックする
  // （チャットは mount を保ったまま hidden にして下書きを失わない）。
  const canSplit = splitFitsWidth(containerW);
  const showSplit = rightVisible && canSplit;        // 真の2ペイン
  const showRightOnly = rightVisible && !canSplit;   // 狭い: 詳細を全幅、チャットは隠す

  // 左ペインの flex。右非表示なら全幅、2ペイン時は px 指定（無ければ 42% 相当の割合）。
  // 保存 px は現在のコンテナ幅で clamp し、狭い画面でも右の最小幅を死守する。
  const appliedLeftPx = leftPx != null && containerW > 0 ? clampSplitLeft(leftPx, containerW) : leftPx;
  const leftFlex = !rightVisible
    ? '1 1 100%'
    : appliedLeftPx != null
      ? `0 0 ${appliedLeftPx}px`
      : '0 0 42%';

  return (
    <div ref={containerRef} className="flex h-full min-h-0 w-full" data-testid="chat-detail-split">
      <div
        ref={leftPaneRef}
        className="min-w-0 min-h-0 overflow-hidden"
        // 狭くて詳細単独表示のときはチャットを hidden（mount 維持で下書き保持）。
        style={showRightOnly ? { display: 'none' } : { flex: leftFlex }}
      >
        {left}
      </div>
      {showSplit && (
        <div
          role="separator"
          aria-orientation="vertical"
          aria-label={t('chatDetailSplit.resizeLabel')}
          data-testid="chat-detail-resize"
          onPointerDown={handlePointerDown}
          onDoubleClick={handleReset}
          className="group flex shrink-0 cursor-col-resize items-stretch bg-transparent transition-colors hover:bg-slate-300/60"
          style={{ width: SPLIT_HANDLE_PX, touchAction: 'none' }}
        >
          <div className="mx-auto w-px bg-hairline group-hover:bg-slate-500/40" />
        </div>
      )}
      {rightVisible && (
        <div className="min-w-0 min-h-0 flex-1 overflow-hidden">{right}</div>
      )}
    </div>
  );
}
