import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import ObjViewer from './ObjViewer.jsx';
import { GOOGLE_CLIENT_ID } from './config.js';
import { downloadObjText, listObjFiles } from './driveApi.js';
import { useGoogleDriveAuth } from './useGoogleDriveAuth.js';

const ORIGINAL_FILE_PATTERN = /^ldsm_fit_original_(\d{5})_([^_]+)_(\d{8})\.obj$/i;

function parseOriginalFile(file) {
  const match = file.name.match(ORIGINAL_FILE_PATTERN);
  if (!match) return null;

  const [, patientId, label, date] = match;
  return {
    ...file,
    patientId,
    label,
    studyDate: date,
    studyDateLabel: `${date.slice(0, 4)}-${date.slice(4, 6)}-${date.slice(6, 8)}`,
  };
}

function formatBytes(value) {
  if (!value) return 'size unknown';
  const number = Number(value);
  if (!Number.isFinite(number)) return 'size unknown';

  const units = ['B', 'KB', 'MB', 'GB'];
  let size = number;
  let unitIndex = 0;

  while (size >= 1024 && unitIndex < units.length - 1) {
    size /= 1024;
    unitIndex += 1;
  }

  return `${size.toFixed(size >= 10 || unitIndex === 0 ? 0 : 1)} ${units[unitIndex]}`;
}

export default function App() {
  const {
    idToken,
    accessToken,
    user,
    authReady,
    authError,
    signIn,
    signOut,
  } = useGoogleDriveAuth();
  const viewerRef = useRef(null);
  const [files, setFiles] = useState([]);
  const [selectedPatientId, setSelectedPatientId] = useState('');
  const [patientSearch, setPatientSearch] = useState('');
  const [selectedFile, setSelectedFile] = useState(null);
  const [objText, setObjText] = useState('');
  const [isListing, setIsListing] = useState(false);
  const [isLoadingObj, setIsLoadingObj] = useState(false);
  const [isPreparingAnimation, setIsPreparingAnimation] = useState(false);
  const [error, setError] = useState('');

  const canUseDrive = Boolean(GOOGLE_CLIENT_ID);
  const isAuthenticated = Boolean(idToken && accessToken);
  const tokens = useMemo(() => ({ accessToken, idToken }), [accessToken, idToken]);

  const loadFiles = useCallback(async () => {
    if (!isAuthenticated) return;

    setIsListing(true);
    setError('');

    try {
      const result = await listObjFiles(tokens);
      setFiles(result);
    } catch (loadError) {
      setError(loadError.message || 'OBJ一覧の取得に失敗しました。');
    } finally {
      setIsListing(false);
    }
  }, [isAuthenticated, tokens]);

  useEffect(() => {
    loadFiles();
  }, [loadFiles]);

  const originalFiles = useMemo(
    () =>
      files
        .map(parseOriginalFile)
        .filter(Boolean)
        .sort((a, b) => a.patientId.localeCompare(b.patientId) || a.studyDate.localeCompare(b.studyDate)),
    [files],
  );

  const patients = useMemo(() => {
    const groups = new Map();

    for (const file of originalFiles) {
      if (!groups.has(file.patientId)) {
        groups.set(file.patientId, []);
      }
      groups.get(file.patientId).push(file);
    }

    return [...groups.entries()].map(([patientId, patientFiles]) => ({
      patientId,
      files: patientFiles.sort((a, b) => a.studyDate.localeCompare(b.studyDate)),
    }));
  }, [originalFiles]);

  const selectedPatient = useMemo(
    () => patients.find((patient) => patient.patientId === selectedPatientId) || null,
    [patients, selectedPatientId],
  );

  const visiblePatients = useMemo(() => {
    const query = patientSearch.trim();
    if (!query) return patients;
    return patients.filter((patient) => patient.patientId.includes(query));
  }, [patientSearch, patients]);

  useEffect(() => {
    if (!patients.length) {
      setSelectedPatientId('');
      setSelectedFile(null);
      setObjText('');
      return;
    }

    const currentPatient = patients.find((patient) => patient.patientId === selectedPatientId);
    if (!currentPatient) {
      setSelectedPatientId(patients[0].patientId);
      setSelectedFile(patients[0].files[0]);
      return;
    }

    if (!selectedFile || selectedFile.patientId !== currentPatient.patientId) {
      setSelectedFile(currentPatient.files[0]);
    }
  }, [patients, selectedFile, selectedPatientId]);

  const handleSelectPatient = useCallback(
    (patientId) => {
      const patient = patients.find((item) => item.patientId === patientId);
      setSelectedPatientId(patientId);
      setSelectedFile(patient?.files[0] || null);
      setObjText('');
      setError('');
    },
    [patients],
  );

  const handleSelectFile = useCallback(
    async (file) => {
      setSelectedFile(file);
      setObjText('');
      setIsLoadingObj(true);
      setError('');

      try {
        const text = await downloadObjText(file.id, tokens);
        setObjText(text);
      } catch (loadError) {
        setError(loadError.message || 'OBJファイルの読み込みに失敗しました。');
      } finally {
        setIsLoadingObj(false);
      }
    },
    [tokens],
  );

  useEffect(() => {
    if (selectedFile && isAuthenticated) {
      handleSelectFile(selectedFile);
    }
  }, [handleSelectFile, isAuthenticated, selectedFile?.id]);

  const loadPatientFrames = useCallback(async () => {
    if (!selectedPatient || selectedPatient.files.length < 2) return [];

    setIsPreparingAnimation(true);
    setError('');

    try {
      return await Promise.all(
        selectedPatient.files.map(async (file) => ({
          file,
          text: await downloadObjText(file.id, tokens),
        })),
      );
    } catch (loadError) {
      setError(loadError.message || 'アニメーション用OBJの取得に失敗しました。');
      return [];
    } finally {
      setIsPreparingAnimation(false);
    }
  }, [selectedPatient, tokens]);

  const handlePlayAnimation = useCallback(async () => {
    const frames = await loadPatientFrames();
    if (frames.length < 2) return;
    viewerRef.current?.playMorph(frames);
  }, [loadPatientFrames]);

  const handleDownloadMp4 = useCallback(async () => {
    const frames = await loadPatientFrames();
    if (frames.length < 2) return;
    const filename = `patient_${selectedPatient.patientId}_original_morph.mp4`;
    await viewerRef.current?.downloadMorphMp4(frames, filename);
  }, [loadPatientFrames, selectedPatient]);

  const animationDisabled =
    !selectedPatient || selectedPatient.files.length < 2 || isLoadingObj || isPreparingAnimation;

  return (
    <main className="app-shell">
      <aside className="sidebar">
        <div className="brand-block">
          <p className="eyebrow">Google Drive</p>
          <h1>OBJ Drive Viewer</h1>
          <p className="subtle">originalデータを患者IDと日付で確認します。</p>
        </div>

        <div className="auth-actions">
          {!isAuthenticated && (
            <button className="primary-button" onClick={signIn} disabled={!authReady || !canUseDrive}>
              Googleで認証
            </button>
          )}

          {isAuthenticated && (
            <>
              <button className="primary-button" onClick={loadFiles} disabled={isListing}>
                {isListing ? '更新中...' : '一覧を更新'}
              </button>
              <button className="ghost-button" onClick={signOut}>
                サインアウト
              </button>
            </>
          )}
        </div>

        {user?.email && (
          <div className="signed-in-user">
            <span>Signed in</span>
            <strong>{user.email}</strong>
          </div>
        )}

        {!canUseDrive && (
          <div className="notice">
            <strong>OAuth Client IDが未設定です</strong>
            <span>GOOGLE_CLIENT_ID を設定してください。</span>
          </div>
        )}

        {authError && (
          <div className="notice error">
            <strong>認証エラー</strong>
            <span>{authError}</span>
          </div>
        )}

        {error && (
          <div className="notice error">
            <strong>読み込みエラー</strong>
            <span>{error}</span>
          </div>
        )}

        <div className="selector-grid">
          <div className="file-section">
            <div className="section-heading">
              <h2>Patient ID</h2>
              <span>{isAuthenticated ? `${patients.length} patients` : 'sign in required'}</span>
            </div>

            <input
              className="search-input"
              inputMode="numeric"
              maxLength={5}
              placeholder="IDで検索"
              value={patientSearch}
              onChange={(event) => setPatientSearch(event.target.value.replace(/\D/g, '').slice(0, 5))}
              disabled={!isAuthenticated || isListing}
            />

            <div className="file-list patient-list">
              {isListing && <div className="list-row muted">Driveを確認しています...</div>}
              {!isListing && isAuthenticated && patients.length === 0 && (
                <div className="list-row muted">original OBJが見つかりません。</div>
              )}
              {!isAuthenticated && <div className="list-row muted">認証後に表示します。</div>}

              {visiblePatients.map((patient) => (
                <button
                  key={patient.patientId}
                  className={`file-row compact ${patient.patientId === selectedPatientId ? 'active' : ''}`}
                  onClick={() => handleSelectPatient(patient.patientId)}
                  disabled={isLoadingObj || isPreparingAnimation}
                >
                  <span className="file-name">{patient.patientId}</span>
                  <span className="file-meta">{patient.files.length} dates</span>
                </button>
              ))}
            </div>
          </div>

          <div className="file-section">
            <div className="section-heading">
              <h2>Date</h2>
              <span>{selectedPatient ? `${selectedPatient.files.length} originals` : '-'}</span>
            </div>

            <div className="file-list date-list">
              {!selectedPatient && <div className="list-row muted">患者を選択してください。</div>}
              {selectedPatient?.files.map((file) => (
                <button
                  key={file.id}
                  className={`file-row ${selectedFile?.id === file.id ? 'active' : ''}`}
                  onClick={() => handleSelectFile(file)}
                  disabled={isLoadingObj || isPreparingAnimation}
                >
                  <span className="file-name">{file.studyDateLabel}</span>
                  <span className="file-meta">
                    {file.label} · {formatBytes(file.size)}
                  </span>
                </button>
              ))}
            </div>
          </div>
        </div>

        <div className="animation-panel">
          <div className="section-heading">
            <h2>Morph</h2>
            <span>{selectedPatient?.files.length > 1 ? 'available' : 'single date'}</span>
          </div>
          <div className="auth-actions">
            <button className="ghost-button" onClick={handlePlayAnimation} disabled={animationDisabled}>
              日付順に再生
            </button>
            <button className="ghost-button" onClick={handleDownloadMp4} disabled={animationDisabled}>
              MP4を保存
            </button>
          </div>
        </div>
      </aside>

      <ObjViewer ref={viewerRef} objText={objText} fileName={selectedFile?.name} />

      {(isLoadingObj || isPreparingAnimation) && (
        <div className="loading-overlay">
          {isPreparingAnimation ? 'アニメーションを準備中...' : 'OBJを読み込み中...'}
        </div>
      )}
    </main>
  );
}
