"use client";

/**
 * Lazy-expanding file tree for a Gitea repo path.
 *
 * Wraps `GET /api/projects/[projectId]/file?path=…` which returns a
 * directory listing. Directories expand inline on click and lazy-load
 * their children; files render as leaves and (for now) link out to
 * Gitea's web UI on click.
 */

import { useEffect, useState, useCallback } from "react";
import { Loader2, AlertCircle } from "lucide-react";
import { Tree, Folder, File } from "@/components/ui/file-tree";
import { THEME } from "@/components/project/dashboard-ui";
import { getFileIconAndColor } from "@/components/project/file-icons";

interface TreeItem {
  name: string;
  path: string;
  type: "file" | "dir" | "symlink";
  size?: number;
}

interface ApiOk {
  type: "dir";
  items: TreeItem[];
}

interface GiteaFileTreeProps {
  projectId: string;
  /** Repo path to root the tree at, e.g. "apps/web" */
  rootPath: string;
  /** Fires when the user clicks a file row. When omitted, files
   *  are not interactive. */
  onSelectFile?: (path: string) => void;
  /** Path of the currently-selected file, used to highlight the row. */
  selectedPath?: string;
}

// ── In-memory cache to persist tree state across tab navigations ──
const treeCache: Record<
  string,
  {
    rootItems: TreeItem[];
    childrenByPath: Record<string, TreeItem[]>;
    expanded: Set<string>;
  }
> = {};

export function GiteaFileTree({
  projectId,
  rootPath,
  onSelectFile,
  selectedPath,
}: GiteaFileTreeProps) {
  const cacheKey = `${projectId}::${rootPath}`;

  const [rootItems, setRootItems] = useState<TreeItem[] | null>(() => {
    return treeCache[cacheKey]?.rootItems ?? null;
  });
  const [loading, setLoading] = useState(() => !treeCache[cacheKey]);
  const [error, setError] = useState<string | null>(null);
  const [expanded, setExpanded] = useState<Set<string>>(() => {
    return treeCache[cacheKey]?.expanded ?? new Set();
  });
  const [childrenByPath, setChildrenByPath] = useState<
    Record<string, TreeItem[]>
  >(() => {
    return treeCache[cacheKey]?.childrenByPath ?? {};
  });
  const [loadingPaths, setLoadingPaths] = useState<Set<string>>(new Set());

  // Keep cache synced with state updates
  useEffect(() => {
    if (rootItems) {
      treeCache[cacheKey] = {
        rootItems,
        childrenByPath,
        expanded,
      };
    }
  }, [cacheKey, rootItems, childrenByPath, expanded]);

  const fetchPath = useCallback(
    async (path: string): Promise<TreeItem[]> => {
      const res = await fetch(
        `/api/projects/${projectId}/file?path=${encodeURIComponent(path)}`,
        { credentials: "include" },
      );
      if (!res.ok) {
        const body = await res.json().catch(() => ({}));
        throw new Error(body.error || `HTTP ${res.status}`);
      }
      const data = (await res.json()) as ApiOk;
      return data.items ?? [];
    },
    [projectId],
  );

  // Load root whenever projectId or rootPath changes
  useEffect(() => {
    // If we already loaded this from cache on mount, skip fetching again
    if (treeCache[cacheKey]) return;

    let cancelled = false;
    setLoading(true);
    setError(null);

    fetchPath(rootPath)
      .then(async (items) => {
        if (cancelled) return;

        const newChildrenByPath: Record<string, TreeItem[]> = {};
        const newExpanded = new Set<string>();

        const rootDirs = items.filter((i) => i.type === "dir");
        const rootDirNames = rootDirs.map((i) => i.name);

        const hasSrc = rootDirNames.includes("src");
        const hasAppOrComponents =
          rootDirNames.includes("app") ||
          rootDirNames.includes("components") ||
          rootDirNames.includes("pages");

        if (hasSrc || hasAppOrComponents) {
          // Smart default: expand app/components/pages, and src -> app/components/pages
          if (hasSrc) {
            const srcItem = rootDirs.find((i) => i.name === "src")!;
            try {
              const srcItems = await fetchPath(srcItem.path);
              newChildrenByPath[srcItem.path] = srcItems;
              newExpanded.add(srcItem.path);

              const srcDirs = srcItems.filter((i) => i.type === "dir");
              const subPromises = [];
              for (const target of ["app", "components", "pages", "lib"]) {
                const subItem = srcDirs.find((i) => i.name === target);
                if (subItem) {
                  subPromises.push(
                    fetchPath(subItem.path).then((children) => {
                      newChildrenByPath[subItem.path] = children;
                      newExpanded.add(subItem.path);
                    }),
                  );
                }
              }
              await Promise.all(subPromises);
            } catch (e) {
              console.warn(`[gitea-file-tree] failed to auto-expand src`);
            }
          }

          if (hasAppOrComponents) {
            const subPromises = [];
            for (const target of ["app", "components", "pages", "lib"]) {
              const rootItem = rootDirs.find((i) => i.name === target);
              if (rootItem) {
                subPromises.push(
                  fetchPath(rootItem.path).then((children) => {
                    newChildrenByPath[rootItem.path] = children;
                    newExpanded.add(rootItem.path);
                  }),
                );
              }
            }
            await Promise.all(subPromises);
          }
        } else {
          // Fallback: auto-expand up to 10 top-level directories so it doesn't look empty
          const dirsToExpand = rootDirs.map((i) => i.path).slice(0, 10);
          await Promise.all(
            dirsToExpand.map(async (dirPath) => {
              try {
                const children = await fetchPath(dirPath);
                newChildrenByPath[dirPath] = children;
                newExpanded.add(dirPath);
              } catch (e) {
                console.warn(
                  `[gitea-file-tree] failed to auto-expand ${dirPath}`,
                );
              }
            }),
          );
        }

        if (cancelled) return;

        setRootItems(items);
        setChildrenByPath(newChildrenByPath);
        setExpanded(newExpanded);
      })
      .catch((err) => {
        if (!cancelled) setError(err.message || "Failed to load");
      })
      .finally(() => {
        if (!cancelled) setLoading(false);
      });

    return () => {
      cancelled = true;
    };
  }, [projectId, rootPath, fetchPath, cacheKey]);

  const toggleDir = useCallback(
    async (path: string) => {
      // is already open?
      const isOpen = expanded.has(path);
      const next = new Set(expanded);
      if (isOpen) {
        next.delete(path);
        setExpanded(next);
        return;
      }
      next.add(path);
      setExpanded(next);

      if (childrenByPath[path]) return;

      setLoadingPaths((prev) => new Set(prev).add(path));
      try {
        const items = await fetchPath(path);
        setChildrenByPath((prev) => ({ ...prev, [path]: items }));
      } catch (err) {
        console.warn(`[gitea-file-tree] failed to load ${path}:`, err);
      } finally {
        setLoadingPaths((prev) => {
          const n = new Set(prev);
          n.delete(path);
          return n;
        });
      }
    },
    [expanded, childrenByPath, fetchPath],
  );

  if (loading) {
    return (
      <div
        style={{
          display: "flex",
          alignItems: "center",
          gap: 8,
          padding: 12,
          color: THEME.muted,
          fontSize: "0.85rem",
        }}
      >
        <Loader2 size={14} className="animate-spin" /> Loading…
      </div>
    );
  }

  if (error) {
    const isMissingRepo = /no gitea repo/i.test(error);
    return (
      <div
        style={{
          display: "flex",
          alignItems: "center",
          gap: 8,
          padding: 12,
          color: THEME.danger,
          fontSize: "0.85rem",
        }}
      >
        <AlertCircle size={14} />
        {isMissingRepo ? "No Gitea repo connected to this project." : error}
      </div>
    );
  }

  if (!rootItems || rootItems.length === 0) {
    return (
      <div
        style={{
          padding: 12,
          color: THEME.muted,
          fontSize: "0.85rem",
          fontStyle: "italic",
        }}
      >
        <code
          style={{
            fontFamily: "ui-monospace, monospace",
            fontSize: "0.8rem",
            padding: "2px 6px",
            background: THEME.subtleBg,
            borderRadius: 4,
            fontStyle: "normal",
            color: THEME.ink,
          }}
        >
          {rootPath}
        </code>{" "}
        is empty (or doesn&apos;t exist yet).
      </div>
    );
  }

  const renderItem = (item: TreeItem) => {
    const isDir = item.type === "dir";
    if (isDir) {
      const children = childrenByPath[item.path];
      return (
        <Folder key={item.path} value={item.path} element={item.name}>
          {loadingPaths.has(item.path) && (
            <div
              style={{
                display: "flex",
                alignItems: "center",
                gap: 6,
                padding: "4px 8px",
                color: THEME.muted,
                fontSize: "0.75rem",
              }}
            >
              <Loader2 size={12} className="animate-spin" /> Loading…
            </div>
          )}
          {children?.map(renderItem)}
        </Folder>
      );
    }

    const { icon: FileIconComponent, color } = getFileIconAndColor(item.name);

    return (
      <File
        key={item.path}
        value={item.path}
        handleSelect={() => onSelectFile?.(item.path)}
        isSelectable={!!onSelectFile}
        isSelect={selectedPath === item.path}
        fileIcon={
          <FileIconComponent size={15} style={{ color, marginRight: 2 }} />
        }
      >
        <span>{item.name}</span>
      </File>
    );
  };

  return (
    <div
      style={{
        flex: 1,
        minHeight: 0,
        display: "flex",
        flexDirection: "column",
      }}
    >
      <Tree
        className="w-full h-full overflow-hidden bg-transparent"
        initialExpandedItems={Array.from(expanded)}
        onExpandItem={toggleDir}
        indicator={true}
      >
        {rootItems.map(renderItem)}
      </Tree>
    </div>
  );
}

// Clean up unused styles
