"use client";

import { useParams } from "next/navigation";
import { useEffect, useLayoutEffect, useRef, useState } from "react";
import { Loader2 } from "lucide-react";
import { useAnatomy } from "@/components/project/use-anatomy";
import { usePreviewBridge } from "@/components/project/preview-bridge-context";
import { usePreviewToolbarStore } from "@/components/project/preview-toolbar/preview-toolbar-state";

const SAME_ORIGIN_SANDBOX =
  "allow-scripts allow-forms allow-same-origin allow-popups allow-modals allow-downloads" as const;

function sandboxIframe(src: string, origin: string): boolean {
  if (!src.startsWith("http://") && !src.startsWith("https://")) return true;
  try {
    return origin.length > 0 && new URL(src).origin === origin;
  } catch {
    return true;
  }
}

/** Elapsed time since an ISO string, formatted as "1m 23s". Capped at 59m 59s. */
function useElapsed(sinceIso: string | undefined) {
  const [elapsed, setElapsed] = useState("");
  useEffect(() => {
    if (!sinceIso) return;
    const update = () => {
      const ms = Date.now() - new Date(sinceIso).getTime();
      if (ms < 0) return;
      const s = Math.floor(ms / 1000);
      const m = Math.floor(s / 60);
      if (m > 59) {
        setElapsed("> 1h");
      } else {
        setElapsed(m > 0 ? `${m}m ${s % 60}s` : `${s}s`);
      }
    };
    update();
    const id = setInterval(update, 1000);
    return () => {
      clearInterval(id);
      setElapsed("");
    };
  }, [sinceIso]);
  return elapsed;
}

export default function PreviewTab() {
  const params = useParams();
  const projectId = params.projectId as string;

  // Poll every 5s so state transitions (starting→running, build complete, etc.)
  // surface without a manual refresh.
  const { anatomy, loading, reload } = useAnatomy(projectId, { pollMs: 5000 });

  const previews = anatomy?.hosting.previews ?? [];

  const [now, setNow] = useState<number>(() => Date.now());
  useEffect(() => {
    const id = setInterval(() => setNow(Date.now()), 10000);
    return () => clearInterval(id);
  }, []);

  // Identify all valid running/starting previews (ignoring stale starting ones)
  const validPreviews = previews
    .filter(
      (p) =>
        p.state === "running" ||
        (p.state === "starting" &&
          now - new Date(p.startedAt).getTime() < 15 * 60 * 1000),
    )
    .sort((a, b) => a.port - b.port); // sort ports ascending

  const selectedPort = usePreviewToolbarStore((s) => s.selectedPort);
  const setSelectedPort = usePreviewToolbarStore((s) => s.setSelectedPort);

  // Auto-select logic if selectedPort is not in validPreviews
  useEffect(() => {
    if (validPreviews.length === 0) return;
    const hasSelected = validPreviews.some((p) => p.port === selectedPort);
    if (!hasSelected) {
      // Prefer 3000 if available, else pick the first one
      const fallback =
        validPreviews.find((p) => p.port === 3000) ?? validPreviews[0];
      if (fallback) setSelectedPort(fallback.port);
    }
  }, [validPreviews, selectedPort, setSelectedPort]);

  // Derive the currently selected preview
  const activePreview = validPreviews.find((p) => p.port === selectedPort);

  // Split it into running and starting like before
  const primaryRunning =
    activePreview?.state === "running" ? activePreview : undefined;
  const primaryStarting =
    activePreview?.state === "starting" ? activePreview : undefined;

  // Derive in-flight / recently-failed build from prod apps.
  const liveApps = anatomy?.hosting.live ?? [];
  const inFlightApp = liveApps.find((a) => a.inFlightBuild);
  const failedApp = !inFlightApp
    ? liveApps.find((a) => a.lastBuild?.status === "failed")
    : undefined;

  // Fallback URL — the last deployed production app. Shown as a link while the
  // dev server is warming up so the user has something to interact with.
  const fallbackFqdn =
    liveApps.find((a) => a.fqdn && a.status === "running")?.fqdn ?? null;
  const fallbackUrl = fallbackFqdn
    ? fallbackFqdn.startsWith("http")
      ? fallbackFqdn
      : `https://${fallbackFqdn}`
    : null;

  // ── Auto-ensure: the single entry point that guarantees the preview is live.
  //    We call it on every mount — even when anatomy already says "running" —
  //    because a `running` row is only intent; the process may have died
  //    (idle-stop / OOM / crash / host restart) leaving a dead port behind a
  //    stale flag. The `ensure` endpoint verifies the port is ACTUALLY answering
  //    and resurrects it if not, but never bounces a healthy server. That makes
  //    "open the preview → it loads cleanly" reliable, and keeps the container
  //    warm (the liveness probe touches activity).
  const ensureCalledRef = useRef(false);
  const [ensureStatus, setEnsureStatus] = useState<
    "idle" | "calling" | "starting" | "running" | "no_history" | "error"
  >("idle");

  const [iframeSrc, setIframeSrc] = useState<string | null>(null);
  const iframeDomRef = useRef<HTMLIFrameElement | null>(null);
  const bridge = usePreviewBridge();
  const origin = typeof window !== "undefined" ? window.location.origin : "";

  const deviceMode = usePreviewToolbarStore((s) => s.deviceMode);
  const refreshKey = usePreviewToolbarStore((s) => s.refreshKey);
  const currentPath = usePreviewToolbarStore((s) => s.currentPath);

  const [isForceStarting, setIsForceStarting] = useState(false);

  // Auto-ensure + refresh-heal, in one effect.
  //
  // On mount (and whenever the refresh button bumps `refreshKey`) we hit the
  // `ensure` endpoint, which is the single entry point that guarantees the
  // preview is live. We call it even when anatomy already says "running",
  // because a `running` row is only intent — the process may have died
  // (idle-stop / OOM / crash / host restart) leaving a dead port behind a stale
  // flag. `ensure` verifies the port is ACTUALLY answering and resurrects it if
  // not, but never bounces a healthy server (and the unique index +
  // `startDevServer` idempotency mean it can't duplicate one). So "open the
  // preview" and "click refresh" both reliably land on a clean, loaded app.
  //
  // The re-arm is a ref write (not setState), so the effect body stays free of
  // synchronous state updates; the only setState calls live in async callbacks.
  const lastEnsuredRefreshKeyRef = useRef(refreshKey);
  useEffect(() => {
    if (refreshKey !== lastEnsuredRefreshKeyRef.current && !isForceStarting) {
      lastEnsuredRefreshKeyRef.current = refreshKey;
      ensureCalledRef.current = false;
    }

    if (ensureCalledRef.current) return;
    if (loading || !anatomy) return;
    ensureCalledRef.current = true;

    fetch(`/api/projects/${projectId}/dev-server/ensure`, {
      method: "POST",
      credentials: "include",
    })
      .then((r) => r.json())
      .then((data: { status?: string }) => {
        if (data.status === "no_history" || data.status === "no_container") {
          setEnsureStatus("no_history");
        } else if (data.status === "running") {
          // Verified live — keep showing the iframe.
          setEnsureStatus("running");
        } else if (data.status === "starting") {
          // Fresh start or resurrection of a dead server. Flip to warming-up and
          // force an immediate anatomy refetch: `ensure` has already marked any
          // stale/dead `running` row as stopped, so the refetch drops the
          // possibly-502 iframe and shows warming-up without waiting for the 5s
          // poll. The readiness probe then carries it to a clean load.
          setEnsureStatus("starting");
          reload();
        } else {
          setEnsureStatus("idle");
        }
      })
      .catch(() => setEnsureStatus("error"));
  }, [loading, anatomy, projectId, refreshKey, isForceStarting, reload]);

  useLayoutEffect(() => {
    if (!primaryRunning?.url) {
      setIframeSrc(null);
    } else {
      const base = primaryRunning.url.replace(/\/$/, "");
      const path = currentPath.startsWith("/")
        ? currentPath
        : `/${currentPath}`;
      setIframeSrc(`${base}${path}`);
    }
  }, [primaryRunning?.url, currentPath]);

  useEffect(() => {
    if (!bridge || !iframeSrc || !iframeDomRef.current) return;
    bridge.registerPreviewIframe(iframeDomRef.current, iframeSrc);
  }, [bridge, iframeSrc]);

  // Determine which empty state to show.
  const emptyContent = (() => {
    if (loading && !anatomy) return <InitialLoader />;
    if (inFlightApp) return <BuildingState app={inFlightApp} />;
    if (failedApp) return <FailedState app={failedApp} />;

    // Dev server is in the process of booting (either picked up from anatomy
    // or we just fired the ensure endpoint and are waiting for the DB row).
    // If isForceStarting is true, we know we clicked the manual start button
    // and are waiting for the DB to reflect 'starting'.
    if (primaryStarting || ensureStatus === "starting" || isForceStarting) {
      return (
        <WarmingUpState
          startedAt={primaryStarting?.startedAt}
          fallbackUrl={fallbackUrl}
          port={selectedPort}
        />
      );
    }
    if (ensureStatus === "calling") {
      return (
        <WarmingUpState
          startedAt={undefined}
          fallbackUrl={fallbackUrl}
          port={selectedPort}
        />
      );
    }
    // Never had a dev server — needs the AI to start one.
    return (
      <NotRunningState
        onStart={() => {
          setIsForceStarting(true);
          fetch(
            `/api/projects/${projectId}/dev-server/ensure?forceStart=true`,
            {
              method: "POST",
            },
          )
            .then((res) => {
              if (!res.ok) throw new Error("Failed to start");
            })
            .catch(() => setIsForceStarting(false));
        }}
      />
    );
  })();

  return (
    <div style={canvas}>
      <div
        style={{
          flex: 1,
          minHeight: 0,
          display: "flex",
          justifyContent: "center",
          alignItems: "center",
        }}
      >
        <div
          style={{
            ...(deviceMode === "desktop"
              ? desktopFrame
              : deviceMode === "tablet"
                ? tabletFrame
                : mobileFrame),
            transition: "all 0.3s ease",
            transform:
              deviceMode === "tablet"
                ? "scale(0.85)"
                : deviceMode === "mobile"
                  ? "scale(0.95)"
                  : "none",
            transformOrigin: "center center",
          }}
        >
          {deviceMode !== "desktop" && <MobileChrome />}

          {iframeSrc ? (
            <iframe
              key={`${iframeSrc}-${refreshKey}`}
              src={iframeSrc}
              title="Preview"
              ref={(el) => {
                iframeDomRef.current = el;
                if (el) bridge?.registerPreviewIframe(el, iframeSrc);
              }}
              onLoad={() => bridge?.notifyPreviewIframeLoaded()}
              style={{
                ...iframeStyle,
                borderRadius:
                  deviceMode === "mobile"
                    ? 44
                    : deviceMode === "tablet"
                      ? 28
                      : 0,
              }}
              {...(sandboxIframe(iframeSrc, origin)
                ? { sandbox: SAME_ORIGIN_SANDBOX }
                : {})}
            />
          ) : (
            <div style={loaderWrap}>{emptyContent}</div>
          )}

          {deviceMode !== "desktop" && (
            <div style={homeIndicator} aria-hidden />
          )}
        </div>
      </div>
    </div>
  );
}

// ── Empty-state sub-components ────────────────────────────────────────────────

function InitialLoader() {
  return (
    <Loader2
      className="animate-spin"
      style={{ width: 22, height: 22, color: "#9c9590" }}
    />
  );
}

function WarmingUpState({
  startedAt,
  fallbackUrl,
  port,
}: {
  startedAt: string | undefined;
  fallbackUrl: string | null;
  port?: number;
}) {
  const elapsed = useElapsed(startedAt);

  return (
    <div style={{ textAlign: "center", maxWidth: 280 }}>
      <div
        style={{ display: "flex", justifyContent: "center", marginBottom: 16 }}
      >
        <div style={buildRingOuter}>
          <div style={buildRingInner} className="animate-spin" />
        </div>
      </div>

      <p style={{ ...emptyTitle, color: "#6366f1" }}>
        Dev server {port ? `(:${port}) ` : ""}warming up
        {elapsed ? ` · ${elapsed}` : ""}
      </p>
      <p style={emptySubtext}>
        Your preview will appear here automatically once it&apos;s ready.
        Usually under 15 seconds.
      </p>

      {fallbackUrl && (
        <a
          href={fallbackUrl}
          target="_blank"
          rel="noopener noreferrer"
          style={{
            display: "inline-block",
            marginTop: 14,
            padding: "5px 12px",
            borderRadius: 20,
            background: "#f5f3ff",
            border: "1px solid #c4b5fd",
            fontSize: "0.72rem",
            color: "#7c3aed",
            textDecoration: "none",
          }}
        >
          View last deployed version →
        </a>
      )}
    </div>
  );
}

function BuildingState({
  app,
}: {
  app: {
    name: string;
    inFlightBuild?: { status: string; createdAt?: string; finishedAt?: string };
  };
}) {
  const elapsed = useElapsed(app.inFlightBuild?.createdAt);

  return (
    <div style={{ textAlign: "center", maxWidth: 280 }}>
      <div
        style={{ display: "flex", justifyContent: "center", marginBottom: 16 }}
      >
        <div style={buildRingOuter}>
          <div style={buildRingInner} className="animate-spin" />
        </div>
      </div>

      <p style={{ ...emptyTitle, color: "#3b82f6" }}>Building {app.name}</p>
      <p style={emptySubtext}>
        Your app is compiling and deploying. The preview will load automatically
        when it&apos;s ready.
      </p>

      <div style={statusPill}>
        <span
          style={{
            display: "inline-block",
            width: 6,
            height: 6,
            borderRadius: "50%",
            background: "#3b82f6",
            marginRight: 6,
            verticalAlign: "middle",
            animation: "pulse 2s cubic-bezier(0.4,0,0.6,1) infinite",
          }}
        />
        <span style={{ verticalAlign: "middle" }}>
          {app.inFlightBuild?.status ?? "building"}
          {elapsed ? ` · ${elapsed}` : ""}
        </span>
      </div>
    </div>
  );
}

function FailedState({
  app,
}: {
  app: { name: string; lastBuild?: { status: string; commit?: string } };
}) {
  return (
    <div style={{ textAlign: "center", maxWidth: 280 }}>
      <div
        style={{
          width: 40,
          height: 40,
          borderRadius: "50%",
          background: "#fef2f2",
          border: "1.5px solid #fecaca",
          display: "flex",
          alignItems: "center",
          justifyContent: "center",
          margin: "0 auto 14px",
          fontSize: 18,
        }}
      >
        ✕
      </div>

      <p style={{ ...emptyTitle, color: "#ef4444" }}>Build failed</p>
      <p style={emptySubtext}>
        The last deploy of <strong>{app.name}</strong> didn&apos;t succeed.
        {app.lastBuild?.commit ? ` (${app.lastBuild.commit.slice(0, 7)})` : ""}
      </p>
      <p style={{ ...emptySubtext, marginTop: 8 }}>
        Ask the AI to check the build logs and fix the issue.
      </p>
    </div>
  );
}

function NotRunningState({ onStart }: { onStart: () => void }) {
  return (
    <div style={{ textAlign: "center", maxWidth: 260 }}>
      <div
        style={{
          width: 40,
          height: 40,
          borderRadius: "50%",
          background: "#f4f4f5",
          border: "1.5px solid #e4e4e7",
          display: "flex",
          alignItems: "center",
          justifyContent: "center",
          margin: "0 auto 14px",
          fontSize: 18,
          color: "#a1a1aa",
        }}
      >
        ⏸
      </div>
      <p style={emptyTitle}>Preview not running</p>
      <p style={emptySubtext}>No dev server found on port 3000.</p>
      <button
        onClick={onStart}
        style={{
          marginTop: 16,
          background: "#18181b",
          color: "white",
          border: "none",
          padding: "8px 16px",
          borderRadius: 8,
          fontSize: "0.8rem",
          fontWeight: 500,
          cursor: "pointer",
          display: "inline-flex",
          alignItems: "center",
          gap: 6,
        }}
      >
        ⚡ Start Preview
      </button>
    </div>
  );
}

// ── Styles ────────────────────────────────────────────────────────────────────

const portPickerWrap: React.CSSProperties = {
  display: "flex",
  gap: 8,
  marginBottom: 12,
  padding: "0 4px",
  flexWrap: "wrap",
};

const portButtonBase: React.CSSProperties = {
  display: "flex",
  alignItems: "center",
  gap: 6,
  padding: "4px 12px",
  borderRadius: 20,
  fontSize: "0.75rem",
  fontWeight: 500,
  cursor: "pointer",
  transition: "all 0.15s ease",
  border: "1px solid transparent",
};

const portButtonActive: React.CSSProperties = {
  ...portButtonBase,
  background: "#fff",
  borderColor: "rgba(26, 26, 26, 0.08)",
  color: "#18181b",
  boxShadow: "0 1px 2px rgba(0,0,0,0.05)",
};

const portButtonInactive: React.CSSProperties = {
  ...portButtonBase,
  background: "transparent",
  color: "#71717a",
  border: "1px solid transparent",
};

const dotBase: React.CSSProperties = {
  width: 6,
  height: 6,
  borderRadius: "50%",
};

const dotRunning: React.CSSProperties = {
  ...dotBase,
  background: "#10b981", // green
  boxShadow: "0 0 6px rgba(16, 185, 129, 0.4)",
};

const dotStarting: React.CSSProperties = {
  ...dotBase,
  background: "#6366f1", // indigo
  boxShadow: "0 0 6px rgba(99, 102, 241, 0.4)",
};

const canvas: React.CSSProperties = {
  flex: 1,
  minHeight: 0,
  width: "100%",
  display: "flex",
  flexDirection: "column",
  alignSelf: "stretch",
  boxSizing: "border-box",
  padding: "14px 16px 18px",
  background: "linear-gradient(165deg, #faf8f5 0%, #f4f0ea 42%, #ebe7df 100%)",
};

const desktopFrame: React.CSSProperties = {
  flex: 1,
  width: "100%",
  height: "100%",
  display: "flex",
  flexDirection: "column",
  borderRadius: 14,
  overflow: "hidden",
  background: "#fff",
  border: "1px solid rgba(26, 26, 26, 0.07)",
  boxShadow:
    "0 1px 2px rgba(26, 26, 26, 0.04), 0 12px 40px rgba(26, 26, 26, 0.07), inset 0 1px 0 rgba(255, 255, 255, 0.85)",
};

const tabletFrame: React.CSSProperties = {
  position: "relative",
  width: 768,
  height: 1024,
  flexShrink: 0,
  borderRadius: 40,
  overflow: "hidden",
  background: "#000",
  border: "14px solid #1a1a1a",
  boxShadow:
    "0 0 0 1px rgba(255,255,255,0.1) inset, 0 24px 60px rgba(26, 26, 26, 0.15)",
  display: "flex",
  flexDirection: "column",
};

const mobileFrame: React.CSSProperties = {
  position: "relative",
  width: 390,
  height: 844,
  flexShrink: 0,
  borderRadius: 56,
  overflow: "hidden",
  background: "#000",
  border: "14px solid #1a1a1a",
  boxShadow:
    "0 0 0 1px rgba(255,255,255,0.1) inset, 0 24px 60px rgba(26, 26, 26, 0.15)",
  display: "flex",
  flexDirection: "column",
};

const iframeStyle: React.CSSProperties = {
  flex: 1,
  width: "100%",
  height: "100%",
  border: "none",
  background: "#fff",
  pointerEvents: "auto",
};

const loaderWrap: React.CSSProperties = {
  flex: 1,
  display: "flex",
  flexDirection: "column",
  alignItems: "center",
  justifyContent: "center",
  background: "#fff",
};

const emptyTitle: React.CSSProperties = {
  margin: "0 0 6px",
  fontSize: "0.9rem",
  fontWeight: 600,
  color: "#18181b",
  letterSpacing: "-0.01em",
};

const emptySubtext: React.CSSProperties = {
  margin: 0,
  fontSize: "0.78rem",
  color: "#71717a",
  lineHeight: 1.5,
};

const statusPill: React.CSSProperties = {
  display: "inline-block",
  marginTop: 14,
  padding: "4px 10px",
  borderRadius: 20,
  background: "#eff6ff",
  border: "1px solid #bfdbfe",
  fontSize: "0.72rem",
  color: "#1d4ed8",
};

const buildRingOuter: React.CSSProperties = {
  width: 40,
  height: 40,
  borderRadius: "50%",
  background: "#eff6ff",
  border: "2px solid #bfdbfe",
  display: "flex",
  alignItems: "center",
  justifyContent: "center",
};

const buildRingInner: React.CSSProperties = {
  width: 20,
  height: 20,
  borderRadius: "50%",
  borderTop: "2.5px solid #3b82f6",
  borderRight: "2.5px solid transparent",
  borderBottom: "2.5px solid transparent",
  borderLeft: "2.5px solid transparent",
};

function MobileChrome() {
  return (
    <div style={notchWrap}>
      <div style={notch} />
    </div>
  );
}

const notchWrap: React.CSSProperties = {
  position: "absolute",
  top: 0,
  left: 0,
  right: 0,
  height: 32,
  display: "flex",
  justifyContent: "center",
  zIndex: 10,
  pointerEvents: "none",
};

const notch: React.CSSProperties = {
  width: 120,
  height: 30,
  background: "#1a1a1a",
  borderBottomLeftRadius: 20,
  borderBottomRightRadius: 20,
};

const homeIndicator: React.CSSProperties = {
  position: "absolute",
  bottom: 8,
  left: "50%",
  transform: "translateX(-50%)",
  width: 140,
  height: 5,
  borderRadius: 10,
  background: "rgba(255, 255, 255, 0.4)",
  mixBlendMode: "difference",
  zIndex: 10,
  pointerEvents: "none",
};
