/**
 * `useModelServers` — view-model for the local model server registry (F35.2).
 * Owns the declared-servers list and the CRUD actions, consuming the
 * {@link ModelServerGateway} exclusively (never `invoke()`), so the UI is
 * testable with a mock.
 *
 * Delete surfaces the backend `model_server_in_use` rejection as a dedicated,
 * human-readable message (a server still referenced by a profile cannot be
 * removed).
 */

import { useCallback, useEffect, useState } from "react";

import type {
  GatewayError,
  LocalModelServerConfig,
  ModelServerCommandPreview,
} from "@/domain";
import { useGateways } from "@/app/di";

/** What the model-server UI needs from this hook. */
export interface ModelServersViewModel {
  /** The declared local model servers. */
  servers: LocalModelServerConfig[];
  /** Last error message, or `null`. */
  error: string | null;
  /** Last non-blocking success message, or `null`. */
  notice: string | null;
  /** Whether a request is in flight. */
  busy: boolean;
  /** Server id whose managed artifact is currently being deleted, or `null`. */
  deletingArtifactId: string | null;
  /** Reloads the server list. */
  reload: () => Promise<void>;
  /** Creates or updates a server; returns the persisted config (or `null` on error). */
  save: (config: LocalModelServerConfig) => Promise<LocalModelServerConfig | null>;
  /** Deletes a server by id; returns `true` on success. */
  remove: (serverId: string) => Promise<boolean>;
  /** Deletes only the managed downloaded model artifact; returns `true` on success. */
  deleteArtifact: (serverId: string) => Promise<boolean>;
  /**
   * Asks the backend to build the `llama-server` command line for a draft
   * (never reconstructed client-side). Returns `null` when the draft is
   * invalid — the editor shows a placeholder instead of a stale command.
   */
  preview: (
    config: LocalModelServerConfig,
  ) => Promise<ModelServerCommandPreview | null>;
  /** Clears the current error banner. */
  clearError: () => void;
}

function describe(e: unknown): string {
  if (e && typeof e === "object" && "message" in e) {
    return String((e as GatewayError).message);
  }
  return String(e);
}

function codeOf(e: unknown): string | undefined {
  if (e && typeof e === "object" && "code" in e) {
    return String((e as GatewayError).code);
  }
  return undefined;
}

export function useModelServers(): ModelServersViewModel {
  const { modelServer } = useGateways();
  const [servers, setServers] = useState<LocalModelServerConfig[]>([]);
  const [error, setError] = useState<string | null>(null);
  const [notice, setNotice] = useState<string | null>(null);
  const [busy, setBusy] = useState(false);
  const [deletingArtifactId, setDeletingArtifactId] = useState<string | null>(null);

  const reload = useCallback(async () => {
    setBusy(true);
    setError(null);
    setNotice(null);
    try {
      setServers(await modelServer.listModelServers());
    } catch (e) {
      setError(describe(e));
    } finally {
      setBusy(false);
    }
  }, [modelServer]);

  useEffect(() => {
    void reload();
  }, [reload]);

  const save = useCallback(
    async (config: LocalModelServerConfig) => {
      setBusy(true);
      setError(null);
      setNotice(null);
      try {
        const saved = await modelServer.saveModelServer(config);
        setServers((prev) => {
          const i = prev.findIndex((s) => s.id === saved.id);
          if (i >= 0) {
            const next = prev.slice();
            next[i] = saved;
            return next;
          }
          return [...prev, saved];
        });
        return saved;
      } catch (e) {
        setError(describe(e));
        return null;
      } finally {
        setBusy(false);
      }
    },
    [modelServer],
  );

  const remove = useCallback(
    async (serverId: string) => {
      setBusy(true);
      setError(null);
      setNotice(null);
      try {
        await modelServer.deleteModelServer(serverId);
        setServers((prev) => prev.filter((s) => s.id !== serverId));
        return true;
      } catch (e) {
        // A referenced server cannot be deleted — turn the stable backend code
        // into an actionable message rather than a raw error string.
        if (codeOf(e) === "model_server_in_use") {
          setError(
            "Ce serveur est encore utilisé par un profil : détache-le d'abord (choisis « aucun » ou un autre serveur) avant de le supprimer.",
          );
        } else {
          setError(describe(e));
        }
        return false;
      } finally {
        setBusy(false);
      }
    },
    [modelServer],
  );

  const deleteArtifact = useCallback(
    async (serverId: string) => {
      setBusy(true);
      setDeletingArtifactId(serverId);
      setError(null);
      setNotice(null);
      try {
        await modelServer.deleteModelArtifact(serverId);
        setServers(await modelServer.listModelServers());
        setNotice("Modèle téléchargé supprimé. Le serveur reste configuré.");
        return true;
      } catch (e) {
        const code = codeOf(e);
        if (code === "model_server_in_use") {
          setError("Impossible de supprimer ce modèle : téléchargement en cours ou agent actif.");
        } else if (code === "invalid") {
          setError("Aucun modèle téléchargé géré à supprimer.");
        } else if (code === "not_configured") {
          setError("Serveur introuvable.");
        } else {
          setError(describe(e));
        }
        return false;
      } finally {
        setDeletingArtifactId(null);
        setBusy(false);
      }
    },
    [modelServer],
  );

  const preview = useCallback(
    async (config: LocalModelServerConfig) => {
      try {
        return await modelServer.previewModelServerCommand(config);
      } catch {
        // A preview is best-effort UI sugar: an invalid draft (e.g. no source)
        // simply yields no command, without touching the CRUD error banner.
        return null;
      }
    },
    [modelServer],
  );

  const clearError = useCallback(() => {
    setError(null);
    setNotice(null);
  }, []);

  return {
    servers,
    error,
    notice,
    busy,
    deletingArtifactId,
    reload,
    save,
    remove,
    deleteArtifact,
    preview,
    clearError,
  };
}
