/**
 * `useProjects` — view-model hook for the projects feature (L2).
 *
 * Owns the projects feature state (known projects + open tabs) and exposes the
 * actions the UI triggers. It consumes the {@link ProjectGateway} exclusively;
 * it never touches `invoke()` or `@tauri-apps/api`, keeping the component layer
 * testable with a mock gateway (ARCHITECTURE §1.3).
 */

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

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

/** What the projects UI needs from this hook. */
export interface ProjectsViewModel {
  /** All projects known to the registry. */
  projects: Project[];
  /** Projects currently open as tabs (in open order). */
  openTabs: Project[];
  /** Id of the active tab, or `null` when none is open. */
  activeTabId: string | null;
  /** Last error message, or `null`. */
  error: string | null;
  /** Whether a request is in flight. */
  busy: boolean;
  /** Reloads the known-projects list from the registry. */
  refresh: () => Promise<void>;
  /** Creates a project, refreshes the list and opens it as a tab. */
  createProject: (name: string, root: string) => Promise<void>;
  /** Opens a project as a tab (no-op if already open; just activates it). */
  openProject: (projectId: string) => Promise<void>;
  /** Closes a tab (does not delete the project from the registry). */
  closeTab: (projectId: string) => Promise<void>;
  /** Activates an already-open tab. */
  activateTab: (projectId: string) => void;
}

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

export function useProjects(): ProjectsViewModel {
  const { project } = useGateways();
  const [projects, setProjects] = useState<Project[]>([]);
  const [openTabs, setOpenTabs] = useState<Project[]>([]);
  const [activeTabId, setActiveTabId] = useState<string | null>(null);
  const [error, setError] = useState<string | null>(null);
  const [busy, setBusy] = useState(false);

  const refresh = useCallback(async () => {
    setBusy(true);
    setError(null);
    try {
      setProjects(await project.listProjects());
    } catch (e) {
      setError(describe(e));
    } finally {
      setBusy(false);
    }
  }, [project]);

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

  const addTab = useCallback((p: Project) => {
    setOpenTabs((prev) =>
      prev.some((t) => t.id === p.id) ? prev : [...prev, p],
    );
    setActiveTabId(p.id);
  }, []);

  const openProject = useCallback(
    async (projectId: string) => {
      setBusy(true);
      setError(null);
      try {
        const opened = await project.openProject(projectId);
        addTab(opened);
      } catch (e) {
        setError(describe(e));
      } finally {
        setBusy(false);
      }
    },
    [project, addTab],
  );

  const createProject = useCallback(
    async (name: string, root: string) => {
      setBusy(true);
      setError(null);
      try {
        const created = await project.createProject(name, root);
        setProjects((prev) => [...prev, created]);
        addTab(created);
      } catch (e) {
        setError(describe(e));
      } finally {
        setBusy(false);
      }
    },
    [project, addTab],
  );

  const closeTab = useCallback(
    async (projectId: string) => {
      setError(null);
      try {
        await project.closeProject(projectId);
      } catch (e) {
        setError(describe(e));
      }
      setOpenTabs((prev) => {
        const next = prev.filter((t) => t.id !== projectId);
        setActiveTabId((current) =>
          current === projectId ? (next.at(-1)?.id ?? null) : current,
        );
        return next;
      });
    },
    [project],
  );

  const activateTab = useCallback((projectId: string) => {
    setActiveTabId(projectId);
  }, []);

  return {
    projects,
    openTabs,
    activeTabId,
    error,
    busy,
    refresh,
    createProject,
    openProject,
    closeTab,
    activateTab,
  };
}
