./kaisetsu-app/src/app/page.tsx

'use client';

import { useState, useCallback, useRef } from 'react';
import Header from '@/components/Header';
import VideoPlayer, { VideoPlayerHandle } from '@/components/VideoPlayer';
import Timeline from '@/components/Timeline';
import DownloadModal from '@/components/DownloadModal';
import FileUpload from '@/components/FileUpload';
import { TimelineBlockData, VoiceTypeId } from '@/components/TimelineBlock';

const DICTIONARY_STORAGE_KEY = 'kaisetsu-user-dictionary';

const DEFAULT_TC_OFFSET = '09;59;45;00';
const DEFAULT_TC_OFFSET_SECONDS = 9 * 3600 + 59 * 60 + 45; // 35985

function getStepLabel(step: string): string {
  switch (step) {
    case 'queued': return 'ジョブをキューに追加中...';
    case 'whisper': return 'Step 1/3: Whisper で音声認識中...';
    case 'audio_meta': return 'Step 2/3: 音声メタデータを生成中...';
    case 'visual_meta': return 'Step 2/3: 映像メタデータを生成中...';
    case 'narration': return 'Step 3/3: 解説音声原稿を生成中...';
    case 'saving': return '結果を保存中...';
    case 'done': return '完了';
    default: return '処理中...';
  }
}

export default function Home() {
  const [hasFile, setHasFile] = useState(false);
  const [isAnalyzing, setIsAnalyzing] = useState(false);
  const [analysisProgress, setAnalysisProgress] = useState(0);
  const [analysisStep, setAnalysisStep] = useState('解析中...');
  const [analysisError, setAnalysisError] = useState<string | null>(null);
  const [fileName, setFileName] = useState('');
  const [lastModified, setLastModified] = useState('');
  const [videoUrl, setVideoUrl] = useState<string>('');
  const [videoFile, setVideoFile] = useState<File | null>(null);
  const [currentTime, setCurrentTime] = useState(0);
  const [blocks, setBlocks] = useState<TimelineBlockData[]>([]);
  const [isDownloadOpen, setIsDownloadOpen] = useState(false);
  const [tcOffsetSeconds, setTcOffsetSeconds] = useState(DEFAULT_TC_OFFSET_SECONDS);
  const [tcOffsetDisplay, setTcOffsetDisplay] = useState(DEFAULT_TC_OFFSET);
  const [debugData, setDebugData] = useState<{ whisperSegments?: unknown[]; preGeneratedMeta?: string; audioMetaRaw?: string; visualMetaRaw?: string } | null>(null);

  const videoPlayerRef = useRef<VideoPlayerHandle>(null);

  const handleTcOffsetChange = useCallback((value: string) => {
    setTcOffsetDisplay(value);
    const parts = value.split(';').map(Number);
    if (parts.length === 4 && parts.every(p => !isNaN(p))) {
      setTcOffsetSeconds(parts[0] * 3600 + parts[1] * 60 + parts[2] + parts[3] / 30);
    }
  }, []);

  const handleFileSelect = useCallback(async (file: File, generateNarration: boolean, tcOffset?: string, genre?: string) => {
    // Parse TC offset string
    if (tcOffset) {
      setTcOffsetDisplay(tcOffset);
      const parts = tcOffset.split(';').map(Number);
      if (parts.length === 4) {
        const offsetSec = parts[0] * 3600 + parts[1] * 60 + parts[2] + parts[3] / 30;
        setTcOffsetSeconds(offsetSec);
      }
    }
    setFileName(file.name);
    setLastModified(
      new Date(file.lastModified).toLocaleString('ja-JP', {
        year: 'numeric',
        month: '2-digit',
        day: '2-digit',
        hour: '2-digit',
        minute: '2-digit'
      })
    );

    const url = URL.createObjectURL(file);
    setVideoUrl(url);
    setVideoFile(file);

    setIsAnalyzing(true);
    setAnalysisProgress(0);
    setAnalysisError(null);
    setAnalysisStep('アップロード中...');

    try {
      let mimeType = file.type;
      if (!mimeType || mimeType === 'application/octet-stream') {
        const ext = file.name.toLowerCase().split('.').pop();
        const mimeTypes: Record<string, string> = {
          'mp4': 'video/mp4',
          'mov': 'video/quicktime',
          'avi': 'video/x-msvideo',
          'mkv': 'video/x-matroska',
          'webm': 'video/webm',
          'm4v': 'video/x-m4v',
        };
        mimeType = mimeTypes[ext || ''] || 'video/mp4';
      }

      const isLocal = window.location.hostname === 'localhost' || window.location.hostname === '127.0.0.1';
      let gcsUri: string;

      if (isLocal) {
        setAnalysisStep('サーバー経由でアップロード中...');
        const formData = new FormData();
        formData.append('video', file);
        const uploadRes = await fetch('/api/upload', {
          method: 'POST',
          body: formData,
        });
        if (!uploadRes.ok) {
          const err = await uploadRes.json();
          throw new Error(err.error || 'Upload failed');
        }
        const uploadData = await uploadRes.json();
        gcsUri = uploadData.gcsUri;
      } else {
        setAnalysisStep('GCS にアップロード中...');
        const uploadUrlRes = await fetch('/api/upload-url', {
          method: 'POST',
          headers: { 'Content-Type': 'application/json' },
          body: JSON.stringify({ fileName: file.name, contentType: mimeType }),
        });
        if (!uploadUrlRes.ok) {
          const err = await uploadUrlRes.json();
          throw new Error(err.error || 'Failed to get upload URL');
        }
        const { signedUrl, gcsUri: uri } = await uploadUrlRes.json();
        const gcsUploadRes = await fetch(signedUrl, {
          method: 'PUT',
          headers: { 'Content-Type': mimeType },
          body: file,
        });
        if (!gcsUploadRes.ok) {
          throw new Error(`GCS upload failed: ${gcsUploadRes.status}`);
        }
        gcsUri = uri;
      }

      // Submit analysis job
      setAnalysisStep('解析ジョブを作成中...');
      setAnalysisProgress(5);

      const response = await fetch('/api/analyze', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({
          gcsUri,
          mimeType,
          generateNarration,
          tcOffset: tcOffset || DEFAULT_TC_OFFSET,
          genre: genre || 'variety',
        }),
      });

      if (!response.ok) {
        const errorData = await response.json();
        throw new Error(errorData.error || 'Analysis failed');
      }

      const { jobId } = await response.json();

      // Poll for job status
      setAnalysisStep('ジョブをキューに追加中...');
      let pollCount = 0;
      let retryCount = 0;
      const MAX_RETRIES = 10;

      while (true) {
        await new Promise(resolve => setTimeout(resolve, 3000 + retryCount * 2000));
        pollCount++;

        const statusRes = await fetch(`/api/analyze/status?jobId=${jobId}`);
        if (!statusRes.ok) {
          if (statusRes.status === 404) {
            // Job not found yet, keep waiting
            if (pollCount > 5) throw new Error('Job not found');
            continue;
          }
          if (statusRes.status === 429 || statusRes.status === 503) {
            retryCount++;
            if (retryCount > MAX_RETRIES) throw new Error('サーバーが混雑しています。しばらく待ってから再試行してください。');
            continue;
          }
          throw new Error('Status check failed');
        }
        retryCount = 0;

        const status = await statusRes.json();

        // Update progress UI
        setAnalysisProgress(status.progress || 0);
        setAnalysisStep(getStepLabel(status.step));

        if (status.status === 'completed') {
          // Save debug data for download
          setDebugData({
            whisperSegments: status.whisperSegments,
            preGeneratedMeta: status.preGeneratedMeta,
            audioMetaRaw: status.audioMetaRaw,
            visualMetaRaw: status.visualMetaRaw,
          });

          // Process result blocks
          if (status.blocks && Array.isArray(status.blocks)) {
            const narrationBlocks = status.blocks.filter(
              (block: TimelineBlockData) => block.type === 'narration' && block.text
            );

            if (narrationBlocks.length > 0) {
              setAnalysisStep(`音声合成中... (0/${narrationBlocks.length})`);
              setAnalysisProgress(90);

              let completedTts = 0;
              const blocksWithAudio = await Promise.all(
                status.blocks.map(async (block: TimelineBlockData) => {
                  if (block.type === 'narration' && block.text) {
                    try {
                      const savedDict = localStorage.getItem(DICTIONARY_STORAGE_KEY);
                      const dict = savedDict ? JSON.parse(savedDict) : [];

                      const ttsResponse = await fetch('/api/tts', {
                        method: 'POST',
                        headers: { 'Content-Type': 'application/json' },
                        body: JSON.stringify({
                          text: block.text,
                          voiceType: block.voiceType || '7nO7lVCISGqz9Dhm3AAx',
                          dictionary: dict,
                        }),
                      });

                      if (ttsResponse.ok) {
                        const audioBlob = await ttsResponse.blob();
                        const audioUrl = URL.createObjectURL(audioBlob);
                        completedTts++;
                        setAnalysisStep(`音声合成中... (${completedTts}/${narrationBlocks.length})`);
                        return { ...block, audioUrl };
                      }
                    } catch (error) {
                      console.error('TTS error for block:', block.id, error);
                    }
                  }
                  return block;
                })
              );

              setBlocks(blocksWithAudio);
            } else {
              setBlocks(status.blocks);
            }
          }
          break;
        }

        if (status.status === 'error') {
          throw new Error(status.error || 'Processing failed');
        }

        // Safety timeout: 15 minutes
        if (pollCount > 300) {
          throw new Error('Processing timeout (15 minutes exceeded)');
        }
      }

      setAnalysisProgress(100);
      setIsAnalyzing(false);
      setHasFile(true);
    } catch (error) {
      console.error('Analysis error:', error);
      setAnalysisError(error instanceof Error ? error.message : 'Analysis failed');
      setIsAnalyzing(false);
      setHasFile(true);
      setBlocks([]);
    }
  }, []);

  const handleTimeUpdate = useCallback((time: number) => {
    setCurrentTime(time);
  }, []);

  const handleAddNarration = useCallback((afterTime: number) => {
    const newBlock: TimelineBlockData = {
      id: Date.now().toString(),
      type: 'narration',
      startTime: afterTime,
      voiceType: '7nO7lVCISGqz9Dhm3AAx',
      text: ''
    };

    setBlocks((prev) => {
      const newBlocks = [...prev, newBlock].sort(
        (a, b) => a.startTime - b.startTime
      );
      return newBlocks;
    });
  }, []);

  const handleBlockTextChange = useCallback((id: string, text: string) => {
    setBlocks((prev) =>
      prev.map((block) => (block.id === id ? { ...block, text, audioUrl: undefined } : block))
    );
  }, []);

  

  const handleBlockVoiceTypeChange = useCallback((id: string, voiceType: VoiceTypeId) => {
    setBlocks((prev) =>
      prev.map((block) => (block.id === id ? { ...block, voiceType, audioUrl: undefined } : block))
    );
  }, []);

  const handleBlockSpeakerChange = useCallback((id: string, speaker: string) => {
    setBlocks((prev) =>
      prev.map((block) => (block.id === id ? { ...block, speaker } : block))
    );
  }, []);

  const handleBlockDelete = useCallback((id: string) => {
    setBlocks((prev) => prev.filter((block) => block.id !== id));
  }, []);

  const handleTimestampClick = useCallback((time: number, blockType: 'dialogue' | 'narration' | 'silence') => {
    const offset = blockType === 'narration' ? 2 : 0;
    const seekTime = Math.max(0, time - offset);
    videoPlayerRef.current?.seekTo(seekTime);
    videoPlayerRef.current?.play();
    setCurrentTime(seekTime);
  }, []);

  return (
    <div className="min-h-screen bg-gray-100">
      {/* Header */}
      <Header
        fileName={fileName}
        lastModified={lastModified}
        tcOffsetDisplay={tcOffsetDisplay}
        onTcOffsetChange={handleTcOffsetChange}
        onDownload={() => setIsDownloadOpen(true)}
      />

      {/* Main content */}
      <main className="p-6">
        {!hasFile && !isAnalyzing ? (
          <div className="max-w-3xl mx-auto">
            <FileUpload onFileSelect={handleFileSelect} />
          </div>
        ) : isAnalyzing ? (
          <div className="max-w-3xl mx-auto">
            <FileUpload
              onFileSelect={handleFileSelect}
              isAnalyzing={true}
              progress={Math.min(100, Math.round(analysisProgress))}
              analysisStep={analysisStep}
            />
            {analysisError && (
              <div className="mt-4 p-4 bg-red-50 border border-red-200 rounded-lg text-red-700 text-sm">
                {analysisError}
              </div>
            )}
          </div>
        ) : (
          <div className="flex gap-6">
            {/* Left column: Video (sticky) */}
            <div className="w-[600px] flex-shrink-0">
              <div className="sticky top-6 space-y-3">
                <VideoPlayer
                  ref={videoPlayerRef}
                  videoUrl={videoUrl}
                  onTimeUpdate={handleTimeUpdate}
                  blocks={blocks}
                  tcOffsetSeconds={tcOffsetSeconds}
                />
              </div>
            </div>

            {/* Right column: Timeline (scrollable) */}
            <div className="flex-1 min-w-0">
              <Timeline
                blocks={blocks}
                currentTime={currentTime}
                tcOffsetSeconds={tcOffsetSeconds}
                onBlockTextChange={handleBlockTextChange}
                onBlockVoiceTypeChange={handleBlockVoiceTypeChange}
                onBlockSpeakerChange={handleBlockSpeakerChange}
                onBlockDelete={handleBlockDelete}
                onTimestampClick={handleTimestampClick}
                onAddNarration={handleAddNarration}
              />
            </div>
          </div>
        )}
      </main>

      {/* Modals */}
      <DownloadModal
        isOpen={isDownloadOpen}
        onClose={() => setIsDownloadOpen(false)}
        blocks={blocks}
        videoFile={videoFile}