"use client";

/**
 * Single-fetch anatomy hook shared by the Product / Hosting tabs.
 * Hardened against silent failure: 10s timeout, error surfacing, and
 * graceful unmount.
 */

import { useEffect, useState } from "react";

export interface Anatomy {
  project: { id: string; name: string; gitea?: string; coolifyProjectUuid?: string };
  codebasesReason?: "no_repo" | "empty_repo";
  product: {
    codebases: Array<{ id: string; label: string; path: string; hint?: string }>;
    images: Array<{
      uuid: string;
      name: string;
      image: string;
      version: string;
      serviceType?: string;
      status?: string;
    }>;
  };
  hosting: {
    live: Array<{
      uuid: string;
      name: string;
      source: "repo" | "image";
      sourceLabel: string;
      status: string;
      fqdn?: string;
      domains: string[];
      branch?: string;
      buildPack?: string;
      lastBuild?: { status: string; finishedAt?: string; commit?: string };
      inFlightBuild?: { status: string; finishedAt?: string; commit?: string };
    }>;
    previews: Array<{
      id: string;
      name: string;
      command?: string;
      port: number;
      url: string;
      state: string;
      startedAt: string;
    }>;
  };
  infrastructure: {
    databases: Array<{
      uuid: string;
      name: string;
      type: string;
      status: string;
      isPublic: boolean;
      publicPort?: number;
      internalAddress?: string;
      consumerEnvKey: string;
    }>;
    providers: Array<{
      id: string;
      category: "auth" | "email" | "payments" | "llm" | "storage";
      vendor: string;
      attachments: Array<{
        resourceUuid: string;
        resourceName: string;
        resourceKind: "app" | "service";
        keys: string[];
      }>;
    }>;
    bundledStorage: {
      status: "ready" | "pending" | "partial" | "error" | "unprovisioned";
      bucketName?: string;
      hmacAccessId?: string;
      region?: string;
      errorMessage?: string;
    };
    secrets: {
      total: number;
      byResource: Array<{
        resourceUuid: string;
        resourceName: string;
        resourceKind: "app" | "service";
        count: number;
        keys: string[];
      }>;
    };
  };
}

export interface UseAnatomyResult {
  anatomy: Anatomy | null;
  loading: boolean;
  error: string | null;
  reload: () => void;
}

export interface UseAnatomyOptions {
  /** When set, re-fetch anatomy every N ms while the component is
   *  mounted. Used by the project-header status pill so it surfaces
   *  Coolify build state transitions live (e.g. queued → in_progress
   *  → success) without the user having to refresh. Pass undefined or
   *  0 to disable polling. */
  pollMs?: number;
}

export function useAnatomy(projectId: string, options: UseAnatomyOptions = {}): UseAnatomyResult {
  const { pollMs } = options;
  const [anatomy, setAnatomy] = useState<Anatomy | null>(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState<string | null>(null);
  const [tick, setTick] = useState(0);

  // Background poll. We bump `tick` on an interval, which re-runs the
  // fetch effect below. Skipping the timer entirely when pollMs is
  // zero/undefined keeps the default render path identical to before.
  useEffect(() => {
    if (!pollMs || pollMs <= 0) return;
    const id = setInterval(() => setTick((t) => t + 1), pollMs);
    return () => clearInterval(id);
  }, [pollMs]);

  useEffect(() => {
    let cancelled = false;
    const controller = new AbortController();
    const timeout = setTimeout(() => controller.abort(), 10_000);

    setLoading(true);
    setError(null);

    fetch(`/api/projects/${projectId}/anatomy`, {
      credentials: "include",
      signal: controller.signal,
    })
      .then(async r => {
        let body: unknown = {};
        try { body = await r.json(); } catch { /* keep {} */ }
        if (!r.ok) {
          const msg = (body as { error?: string }).error || `HTTP ${r.status} ${r.statusText}`.trim();
          throw new Error(msg);
        }
        return body as Anatomy;
      })
      .then(data => {
        if (!cancelled) setAnatomy(data);
      })
      .catch(err => {
        if (cancelled) return;
        if (err?.name === "AbortError") setError("Request timed out after 10s.");
        else setError(err?.message || "Failed to load project anatomy");
      })
      .finally(() => {
        clearTimeout(timeout);
        if (!cancelled) setLoading(false);
      });

    return () => {
      cancelled = true;
      controller.abort();
      clearTimeout(timeout);
    };
  }, [projectId, tick]);

  return { anatomy, loading, error, reload: () => setTick(t => t + 1) };
}
