"use client";

/**
 * 推論工作區 Client — /workspace/[deviceId]
 *
 * 對齊 pages.md §8.4、flow-offline-handling.md §6。
 *
 * 塊 1 範圍（Camera MJPEG 串流顯示 + 開始/停止狀態機）：
 *   - 頂部：返回 + 裝置名稱 + RemoteDeviceBadge + 開始/停止推論按鈕
 *   - Tabs：Camera / Image / Video / Batch（Camera 已接真串流，其餘塊 3 補）
 *   - Camera tab 內：CameraFeed 顯示 MJPEG stream（start 成功後）
 *   - start/stop 契約修正：改打 `/api/camera/start` + `/api/camera/stop`（見評估 §3.1 R-C1）
 *   - 裝置掉線（remoteStatus != online）：顯示全頁遮罩 + 返回按鈕；掉線時自動清串流
 *
 * 塊 1 不做（後續塊補）：
 *   - InferencePanel 的 classification result / performance metrics（塊 2 接 WS）
 *   - canvas overlay 邊界框繪製（塊 2）
 *   - Image / Video / Batch tab 上傳與顯示（塊 3）
 *   - Camera 來源選擇（SourceSelector）
 *
 * 重要：雲端版 Workspace 對「裝置離線」極敏感 —
 *   任何時刻若收到 remoteStatus != online 都要立刻顯示 offline 遮罩並停止串流。
 *
 * ADR-018 serial 路由（WP-C）：
 *   - 路由到 local agent 的操作（camera start/stop、media upload、inference WS）
 *     識別值一律帶 `device.serialNumber`（kn_number），不是雲端 UUID。
 *   - 路由段 `[deviceId]` 仍是雲端 UUID（fetchDevice 是純 DB 操作、FE-A 混合模型），
 *     serial 從 fetch 回來的 selectedDevice 取。
 *   - serial 為空（舊資料未回報 / 假序號）→ 無法路由：顯示提示 banner、
 *     「開始推論」disable、media tab 顯示提示不給上傳。
 */

import { useCallback, useEffect, useRef, useState } from "react";
import Link from "next/link";
import { ArrowLeft, WifiOff } from "lucide-react";

import { RemoteDeviceBadge } from "@/components/cloud/remote-device-badge";
import { CameraFeed } from "@/components/workspace/camera-feed";
import { CameraOverlay } from "@/components/workspace/camera-overlay";
import { InferencePanel } from "@/components/workspace/inference-panel";
import { MediaTab } from "@/components/workspace/media-tab";
import { Button } from "@/components/ui/button";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { Skeleton } from "@/components/ui/skeleton";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
import { useInferenceStream } from "@/hooks/use-inference-stream";
import { api, ApiError } from "@/lib/api";
import { buildStreamUrl } from "@/lib/camera";
import {
  IMAGE_ACCEPT,
  VIDEO_ACCEPT,
  uploadBatchImages,
  uploadImage,
  uploadVideo,
  validateBatchFiles,
  validateImageFile,
  validateVideoFile,
} from "@/lib/media";
import { useT } from "@/lib/i18n/context";
import { useDeviceStore } from "@/stores/device-store";
import { useInferenceStore } from "@/stores/inference-store";
import type { MediaUploadResponse } from "@/types/camera";
import { toast } from "sonner";

/** 影片上傳 timeout 拉長（大檔經 tunnel，評估 R-M2）；0 = 不限。 */
const VIDEO_UPLOAD_TIMEOUT_MS = 0;

interface WorkspaceClientProps {
  deviceId: string;
}

export function WorkspaceClient({ deviceId }: WorkspaceClientProps) {
  const t = useT();
  const selectedDevice = useDeviceStore((s) => s.selectedDevice);
  const isLoading = useDeviceStore((s) => s.isLoading);
  const fetchDevice = useDeviceStore((s) => s.fetchDevice);
  const [isRunning, setIsRunning] = useState(false);
  const [busy, setBusy] = useState(false);
  // 目前選中的 tab（camera / image / video / batch）。
  // 用於：離開 camera tab 時停掉 camera 串流 + WS，避免與 media tab 的 WS 訂閱同時寫入同一個 store。
  const [activeTab, setActiveTab] = useState("camera");
  // 塊 1：Camera MJPEG 串流 URL（start 成功後由後端回傳的 streamUrl 組出）
  const [streamUrl, setStreamUrl] = useState("");
  // 塊 2：CameraFeed `<img>` 實際 render 尺寸（供 overlay 換算 normalized bbox → px）。
  // review M1：初值為 null（尚未從 ResizeObserver 拿到真實顯示尺寸）。overlay 在拿到
  // 真實尺寸前不繪製，避免用寫死猜測（640×480）換算 → MJPEG 非 4:3 時 bbox 框錯位。
  const [feedSize, setFeedSize] = useState<{ w: number; h: number } | null>(null);

  const resetInference = useInferenceStore((s) => s.reset);
  // handleStop 最新引用（handleTabChange 用；見下方 handleStop 定義後賦值）
  const handleStopRef = useRef<() => Promise<void>>(async () => {});

  useEffect(() => {
    if (deviceId) void fetchDevice(deviceId);
  }, [deviceId, fetchDevice]);

  /** 停止串流的共用清理（stop / unmount 都用）。 */
  const clearStream = useCallback(() => {
    setStreamUrl("");
    setIsRunning(false);
    setFeedSize(null); // 重置尺寸，下次 start 重新等 ResizeObserver 回報真實值
    resetInference();
  }, [resetInference]);

  const handleDimensionsChange = useCallback((w: number, h: number) => {
    setFeedSize({ w, h });
  }, []);

  // 塊 2：裝置線上性（hooks 必須無條件呼叫，故在此處先算，早退前）
  const isOnline = selectedDevice?.remoteStatus === "online";

  // WP-C（ADR-018）：路由到 local agent 的操作一律用 serial 當識別值。
  // serial 為空（舊資料 / 未回報序號）→ 操作全部 disable（無法路由）。
  const serialNumber = selectedDevice?.serialNumber ?? null;
  const hasSerial = !!serialNumber;

  // 塊 3：切換 tab。離開 camera tab 時若正在推論 → 停掉（避免 camera 與 media 兩套 WS 同時灌 store）。
  const handleTabChange = useCallback(
    (value: string) => {
      setActiveTab(value);
      if (value !== "camera" && isRunning) {
        void handleStopRef.current();
      }
    },
    [isRunning],
  );

  // 塊 2/3：訂閱 camera WS `inference:<serial>`（只在 camera tab + 推論中 + 線上 + 有 serial 時連線）。
  // media tab 有自己的 useInferenceStream（在 MediaTab 內），兩者靠 activeTab 互斥。
  // WP-C：WS 識別值用 serial（與 camera start 的 body deviceId 一致，room key 才對得上）。
  useInferenceStream(
    serialNumber ?? "",
    activeTab === "camera" && isRunning && isOnline && hasSerial,
  );

  // 塊 2：最新結果的偵測框（供 overlay 繪製）
  const liveResult = useInferenceStore((s) => s.result);
  const confidenceThreshold = useInferenceStore((s) => s.confidenceThreshold);

  async function handleStart() {
    // WP-C：serial 為空無法路由（按鈕已 disable；此為防禦性 guard）。
    if (!serialNumber) return;
    setBusy(true);
    try {
      // 契約修正（評估 §3.1 R-C1）：camera pipeline 入口為 POST /api/camera/start
      // body 帶 deviceId；後端回傳 { streamUrl, sourceType }（camera_handler.go:119-125）
      // WP-C（ADR-018）：deviceId 欄位帶 serial（欄位名不動、值換 serialNumber）。
      const data = await api.post<MediaUploadResponse>("/api/camera/start", {
        deviceId: serialNumber,
      });
      const url = data?.streamUrl ?? "/api/camera/stream";
      // cache-bust：每次 start 換一個值，避免瀏覽器重用已結束的 MJPEG 連線
      setStreamUrl(buildStreamUrl(url, `${serialNumber}-${Date.now()}`));
      setIsRunning(true);
    } catch (err) {
      // 後端尚未接上 proxy 時可能 501；不當致命錯誤，僅提示
      if (err instanceof ApiError && err.code === "NOT_IMPLEMENTED") {
        toast.info(t("workspace.camera.startNotWired"));
      } else {
        toast.error(err instanceof Error ? err.message : String(err));
      }
    } finally {
      setBusy(false);
    }
  }

  async function handleStop() {
    setBusy(true);
    try {
      // WP-C（ADR-018）：與 start 一致，deviceId 欄位帶 serial。
      // serial 為空時不可能已 start（handleStart 有 guard），直接清本地串流即可。
      if (serialNumber) {
        await api.post("/api/camera/stop", { deviceId: serialNumber });
      }
    } catch (err) {
      if (!(err instanceof ApiError && err.code === "NOT_IMPLEMENTED")) {
        toast.error(err instanceof Error ? err.message : String(err));
      }
    } finally {
      clearStream();
      setBusy(false);
    }
  }
  // handleTabChange 需要在切離 camera tab 時呼叫 handleStop；用 ref 保持最新引用，
  // 避免 handleTabChange 的 deps 依賴每次 render 重建的 handleStop。
  // 於 effect 內同步 ref（不在 render 期寫 ref，符合 react-hooks/refs 規則）。
  useEffect(() => {
    handleStopRef.current = handleStop;
  });

  if (isLoading && !selectedDevice) {
    return (
      <div className="mx-auto max-w-7xl space-y-4 px-6 py-8">
        <Skeleton className="h-8 w-48" />
        <Skeleton className="h-[60vh] rounded-lg" />
      </div>
    );
  }

  // 裝置尚未載入（雛形：selectedDevice 可能為 null）— 簡易占位
  const device = selectedDevice;
  // 裝置掉線時不顯示串流（雲端版對離線極敏感；避免 <img> 卡在已死的 MJPEG 連線）。
  // 用 render 期衍生而非 effect + setState，避免 cascading render。
  const effectiveStreamUrl = isOnline ? streamUrl : "";

  // WP-C：media tab 在 serial 為空時的占位提示（無法上傳——上傳的識別值就是 serial）。
  const noSerialNotice = (
    <Card className="min-h-[60vh]">
      <CardContent className="grid h-full min-h-[60vh] place-items-center p-6">
        <div className="max-w-md space-y-2 text-center" data-testid="media-no-serial">
          <p className="text-sm font-medium">{t("workspace.noSerial.title")}</p>
          <p className="text-muted-foreground text-sm">
            {t("workspace.noSerial.description")}
          </p>
        </div>
      </CardContent>
    </Card>
  );

  return (
    <div className="mx-auto max-w-7xl space-y-4 px-6 py-8">
      <div className="flex flex-wrap items-center justify-between gap-3">
        <div className="flex items-center gap-3">
          <Link href={device ? `/devices/${device.id}` : "/devices"}>
            <Button variant="ghost" size="sm">
              <ArrowLeft aria-hidden="true" className="mr-2 size-4" />
              {t("workspace.header.backToDevices")}
            </Button>
          </Link>
          <h1 className="text-xl font-bold">
            {t("workspace.header.title")} · {device?.alias || device?.name || deviceId}
          </h1>
          {device && (
            <RemoteDeviceBadge
              status={device.remoteStatus}
              lastSeenAt={device.lastSeenAt ?? null}
              size="sm"
            />
          )}
        </div>
        <div className="flex gap-2">
          {isRunning ? (
            <Button variant="destructive" onClick={handleStop} disabled={busy}>
              {t("workspace.inference.stop")}
            </Button>
          ) : (
            // WP-C：serial 為空無法路由 → disable（原因見下方 no-serial banner）
            <Button onClick={handleStart} disabled={busy || !isOnline || !hasSerial}>
              {t("workspace.inference.start")}
            </Button>
          )}
        </div>
      </div>

      {/* WP-C（ADR-018）：serial 為空 → 無法路由到 local agent，說明原因與解法 */}
      {device && !hasSerial && (
        <div
          role="alert"
          className="bg-warning-subtle border-warning text-warning-foreground flex items-start gap-3 rounded-lg border p-4"
          data-testid="workspace-no-serial-banner"
        >
          <div className="flex-1 space-y-1">
            <p className="text-sm font-medium">{t("workspace.noSerial.title")}</p>
            <p className="text-xs opacity-90">
              {t("workspace.noSerial.description")}
            </p>
          </div>
        </div>
      )}

      <Tabs value={activeTab} onValueChange={handleTabChange} className="w-full">
        <TabsList>
          <TabsTrigger value="camera">{t("workspace.tabs.camera")}</TabsTrigger>
          <TabsTrigger value="image">{t("workspace.tabs.image")}</TabsTrigger>
          <TabsTrigger value="video">{t("workspace.tabs.video")}</TabsTrigger>
          <TabsTrigger value="batch">{t("workspace.tabs.batch")}</TabsTrigger>
        </TabsList>
        <TabsContent value="camera" className="space-y-4">
          <div className="grid grid-cols-1 gap-4 lg:grid-cols-[1fr_20rem]">
            <Card className="min-h-[60vh]">
              <CardContent className="grid h-full min-h-[60vh] place-items-center p-6">
                {effectiveStreamUrl ? (
                  <CameraFeed
                    streamUrl={effectiveStreamUrl}
                    sourceType="camera"
                    width={640}
                    onDimensionsChange={handleDimensionsChange}
                    overlay={
                      // M1：feedSize 未就緒（ResizeObserver 尚未回報真實顯示尺寸）前不繪製 overlay，
                      // 避免用寫死猜測換算 bbox → 框錯位。
                      feedSize ? (
                        <CameraOverlay
                          detections={liveResult?.detections ?? []}
                          width={feedSize.w}
                          height={feedSize.h}
                          confidenceThreshold={confidenceThreshold}
                        />
                      ) : undefined
                    }
                  />
                ) : (
                  <div className="text-center">
                    <p className="text-muted-foreground text-sm">
                      {t("workspace.camera.idleHint")}
                    </p>
                  </div>
                )}
              </CardContent>
            </Card>
            <Card className="min-h-[60vh]">
              <CardHeader>
                <CardTitle className="text-base">
                  {t("workspace.inference.panelTitle")}
                </CardTitle>
              </CardHeader>
              <CardContent>
                {/* 塊 2：接 WS `inference:<deviceId>` → 效能指標 + 結果清單 */}
                <InferencePanel isRunning={isRunning && isOnline} />
              </CardContent>
            </Card>
          </div>
        </TabsContent>

        {/* 塊 3：圖片 / 影片 / 批次 — 共用 MediaTab（上傳 → CameraFeed + overlay + panel）
            WP-C（ADR-018）：上傳識別值帶 serial；serial 為空 → 顯示提示、不給上傳。 */}
        <TabsContent value="image" className="space-y-4">
          {!hasSerial ? (
            noSerialNotice
          ) : (
            <MediaTab
              deviceId={serialNumber!}
              isOnline={!!isOnline}
              sourceType="image"
              accept={IMAGE_ACCEPT}
              validate={(files) =>
                files[0] ? validateImageFile(files[0]) : { code: "EMPTY" }
              }
              upload={async (files, ctx) => {
                return uploadImage(serialNumber!, files[0]!, {
                  onProgress: ctx.onProgress,
                  signal: ctx.signal,
                });
              }}
            />
          )}
        </TabsContent>

        <TabsContent value="video" className="space-y-4">
          {!hasSerial ? (
            noSerialNotice
          ) : (
            <MediaTab
              deviceId={serialNumber!}
              isOnline={!!isOnline}
              sourceType="video"
              accept={VIDEO_ACCEPT}
              validate={(files) =>
                files[0] ? validateVideoFile(files[0]) : { code: "EMPTY" }
              }
              upload={async (files, ctx) => {
                return uploadVideo(serialNumber!, files[0]!, {
                  onProgress: ctx.onProgress,
                  signal: ctx.signal,
                  timeoutMs: VIDEO_UPLOAD_TIMEOUT_MS,
                });
              }}
            />
          )}
        </TabsContent>

        <TabsContent value="batch" className="space-y-4">
          {!hasSerial ? (
            noSerialNotice
          ) : (
            <MediaTab
              deviceId={serialNumber!}
              isOnline={!!isOnline}
              sourceType="batch_image"
              accept={IMAGE_ACCEPT}
              multiple
              validate={validateBatchFiles}
              upload={async (files, ctx) => {
                return uploadBatchImages(serialNumber!, files, {
                  onProgress: ctx.onProgress,
                  signal: ctx.signal,
                });
              }}
            />
          )}
        </TabsContent>
      </Tabs>

      {/* 裝置掉線遮罩（flow-offline-handling §6.2） */}
      {device && !isOnline && (
        <div
          role="alertdialog"
          aria-labelledby="workspace-offline-title"
          className="bg-background/80 fixed inset-0 z-50 grid place-items-center backdrop-blur-sm"
          data-testid="workspace-offline-overlay"
        >
          <Card className="max-w-md">
            <CardContent className="space-y-4 py-8 text-center">
              <WifiOff
                aria-hidden="true"
                className="text-muted-foreground mx-auto size-12"
              />
              <h2 id="workspace-offline-title" className="text-xl font-semibold">
                {t("workspace.offline.title")}
              </h2>
              <p className="text-muted-foreground text-sm">
                {t("workspace.offline.description").replace(
                  "{deviceName}",
                  device.alias || device.name,
                )}
              </p>
              <Link href="/devices">
                <Button>{t("workspace.offline.backToList")}</Button>
              </Link>
            </CardContent>
          </Card>
        </div>
      )}
    </div>
  );
}
