/**
 * Artifact Browser Page
 * 左侧 Registry/Repository 树（自动加载并展开），右侧 Artifact 卡片。
 */
import React, { useCallback, useEffect, useMemo, useState } from "react";
import {
  Package,
  Database,
  RefreshCw,
  Filter,
  Search,
  ChevronRight,
  ChevronDown,
  ChevronLeft,
  LayoutGrid,
} from "lucide-react";
import { useToast } from "@/shared";
import {
  Button,
  LoadingState,
  EmptyState,
  Badge,
} from "@/shared/components";
import {
  listRegistries,
  listRepositories,
  listArtifacts,
} from "@/api";
import type {
  RegistryResponse,
  ListRepositories200Item,
  ArtifactListItem,
  ListArtifactsFilter,
} from "@/api";
import { TagCard } from "../components/TagCard";
import { globalCache } from "@/shared/services/artifact-cache";
import { RegistryErrors, SuccessMessages, formatApiError } from "@/shared/utils";

interface RepositoryNode {
  name: string;
  registryId: string;
  registryName: string;
  artifactCount?: number;
}

interface RegistryNode {
  registry: RegistryResponse;
  repositories: RepositoryNode[];
  expanded: boolean;
}

const FILTER_OPTIONS: Array<{ value: ListArtifactsFilter | undefined; label: string }> = [
  { value: "chart", label: "Charts" },
  { value: undefined, label: "All tags" },
];

const ArtifactBrowserPage: React.FC = () => {
  const { info: toastInfo, success: toastSuccess, error: toastError } = useToast();

  const [registryNodes, setRegistryNodes] = useState<RegistryNode[]>([]);
  const [loadingRegistries, setLoadingRegistries] = useState(true);
  const [loadingRepositories, setLoadingRepositories] = useState(true);
  const [repositoryError, setRepositoryError] = useState<string | null>(null);
  const [refreshing, setRefreshing] = useState(false);

  const [selectedRepository, setSelectedRepository] = useState<RepositoryNode | null>(null);
  const [artifacts, setArtifacts] = useState<ArtifactListItem[]>([]);
  const [loadingArtifacts, setLoadingArtifacts] = useState(false);
  const [artifactError, setArtifactError] = useState<string | null>(null);
  const [filter, setFilter] = useState<ListArtifactsFilter | undefined>("chart");

  const [sidebarCollapsed, setSidebarCollapsed] = useState(false);
  const [searchTerm, setSearchTerm] = useState("");
  const [tagSearchTerm, setTagSearchTerm] = useState("");

  const loadArtifacts = useCallback(
    async (
      registryId: string,
      repositoryName: string,
      skipCache = false,
      isRefresh = false
    ) => {
      if (!isRefresh) {
        setLoadingArtifacts(true);
      }
      setArtifactError(null);
      const filterKey = filter ?? "all";
      try {
        let data: ArtifactListItem[] | null = null;
        if (!skipCache) {
          data = globalCache.get<ArtifactListItem[]>("tags", registryId, repositoryName, filterKey);
        }
        if (!data) {
          data = await listArtifacts(
            { registryId, repositoryName },
            filter ? { filter } : undefined
          );
          globalCache.set("tags", data, registryId, repositoryName, filterKey);
        }
        setArtifacts(data ?? []);
      } catch (err) {
        const errorMsg = formatApiError(err) || "Failed to load artifacts";
        setArtifactError(errorMsg);
        toastError(errorMsg);
      } finally {
        if (!isRefresh) {
          setLoadingArtifacts(false);
        }
      }
    },
    [filter, toastError]
  );

  const fetchRegsAndRepos = useCallback(async (isRefresh = false) => {
    if (!isRefresh) {
      setLoadingRegistries(true);
      setLoadingRepositories(true);
    }
    setRepositoryError(null);
    let succeeded = false;

    try {
      let registries = globalCache.get<RegistryResponse[]>("registries");
      if (!registries) {
        registries = await listRegistries();
        globalCache.set("registries", registries);
      }

      const baseNodes: RegistryNode[] = registries.map((registry) => ({
        registry,
        repositories: [],
        expanded: true,
      }));
      setRegistryNodes(baseNodes);
      if (!isRefresh) {
        setLoadingRegistries(false);
      }

      const repoMap = await Promise.all(
        registries.map(async (registry) => {
          const registryId = registry.id;
          if (!registryId) {
            return { id: registryId, repos: [] };
          }
          try {
            let repoNodes = globalCache.get<RepositoryNode[]>("repositories", registryId);
            if (!repoNodes) {
              const response = await listRepositories({ registryId }, { artifactType: "chart" });
              repoNodes = normalizeRepositories(registry, response);
              globalCache.set("repositories", repoNodes, registryId);
            }
            return { id: registryId, repos: repoNodes };
          } catch (err) {
            console.error(`Failed to load repositories for ${registry.name}`, err);
            return { id: registryId, repos: [] };
          }
        })
      );

      setRegistryNodes((prev) =>
        prev.map((node) => {
          const mapping = repoMap.find((entry) => entry.id === node.registry.id);
          if (!mapping) return node;
          return { ...node, repositories: mapping.repos };
        })
      );

      const firstRepo = repoMap.find((entry) => entry.repos.length > 0)?.repos[0];
      if (firstRepo) {
        setSelectedRepository(firstRepo);
        await loadArtifacts(firstRepo.registryId, firstRepo.name, false, isRefresh);
      } else {
        setSelectedRepository(null);
        setArtifacts([]);
      }
      succeeded = true;
    } catch (err) {
      const msg = formatApiError(err) || RegistryErrors.LOAD_FAILED;
      toastError(msg);
      setRepositoryError(msg);
    } finally {
      if (!isRefresh) {
        setLoadingRepositories(false);
      }
    }
    return succeeded;
  }, [toastError, loadArtifacts]);

  const handleRefresh = async () => {
    if (refreshing) return;
    toastInfo("Refreshing registries & repositories...", {
      title: "Artifact Browser Refresh",
      durationMs: 1800,
      mergeKey: "artifact-browser-refresh",
    });
    setRefreshing(true);
    globalCache.clearAll();
    try {
      const refreshed = await fetchRegsAndRepos(true);
      if (refreshed) {
        toastSuccess(SuccessMessages.DATA_REFRESHED);
      }
    } finally {
      setRefreshing(false);
    }
  };

  const handleRepositoryClick = (repo: RepositoryNode) => {
    setSelectedRepository(repo);
    loadArtifacts(repo.registryId, repo.name, true);
  };

  const toggleRegistry = (registryId?: string) => {
    if (!registryId) return;
    setRegistryNodes((prev) =>
      prev.map((node) =>
        node.registry.id === registryId ? { ...node, expanded: !node.expanded } : node
      )
    );
  };

  const hasInitialized = React.useRef(false);
  useEffect(() => {
    if (hasInitialized.current) {
      return;
    }
    hasInitialized.current = true;
    fetchRegsAndRepos();
  }, [fetchRegsAndRepos]);

  useEffect(() => {
    if (selectedRepository) {
      loadArtifacts(selectedRepository.registryId, selectedRepository.name);
    }
  }, [filter, selectedRepository, loadArtifacts]);

  const filteredNodes = useMemo(() => {
    const term = searchTerm.toLowerCase();
    if (!term) return registryNodes;
    return registryNodes
      .map((node) => ({
        ...node,
        repositories: node.repositories.filter((repo) => {
          const registryMatch = node.registry.name?.toLowerCase().includes(term);
          const repoMatch = repo.name.toLowerCase().includes(term);
          return registryMatch || repoMatch;
        }),
      }))
      .filter(
        (node) =>
          node.repositories.length > 0 ||
          node.registry.name?.toLowerCase().includes(term)
      );
  }, [registryNodes, searchTerm]);

  const filteredArtifacts = useMemo(() => {
    const term = tagSearchTerm.trim().toLowerCase();
    if (!term) return artifacts;
    return artifacts.filter((a) => (a.tag || "").toLowerCase().includes(term));
  }, [artifacts, tagSearchTerm]);

  const selectedRegistryName = selectedRepository
    ? registryNodes.find((node) => node.registry.id === selectedRepository.registryId)?.registry
        .name
    : null;
  const selectedRegistryUrl = selectedRepository
    ? registryNodes.find((node) => node.registry.id === selectedRepository.registryId)?.registry
        .url
    : undefined;

  return (
    <div className="h-[calc(100vh-8rem)] -m-6 flex flex-col">
      <div className="flex-shrink-0 border-b border-slate-200 bg-white px-6 py-4">
        <div className="flex items-center justify-between">
          <div className="flex items-center gap-3">
            <Package className="w-6 h-6 text-blue-600" />
            <div>
              <h1 className="text-xl font-semibold text-slate-900">Chart Browser</h1>
              <p className="text-sm text-slate-500">
                Select a Harbor chart and launch it into a Kubernetes cluster
              </p>
            </div>
          </div>
            <Button
              variant="secondary"
              icon={RefreshCw}
              onClick={handleRefresh}
              loading={refreshing}
              spinIcon
            >
            Refresh
          </Button>
        </div>
      </div>
      <div className="flex-1 flex overflow-hidden bg-slate-50">
        {/* Collapsible side panel */}
        <aside
          className={`border-r border-slate-200 bg-white flex flex-col transition-all duration-200 ${
            sidebarCollapsed ? "w-12" : "w-80"
          }`}
        >
          {sidebarCollapsed ? (
            /* Collapsed state: narrow strip with just a toggle */
            <div className="flex flex-col items-center pt-3 gap-4">
              <button
                onClick={() => setSidebarCollapsed(false)}
                className="p-1.5 hover:bg-slate-100 rounded-md transition"
                title="Expand sidebar"
              >
                <ChevronRight className="w-4 h-4 text-slate-500" />
              </button>
              {registryNodes.slice(0, 5).map((node) => (
                <div
                  key={node.registry.id || node.registry.name}
                  className="w-7 h-7 rounded-md bg-blue-50 flex items-center justify-center"
                  title={node.registry.name || "Registry"}
                >
                  <Database className="w-3.5 h-3.5 text-blue-600" />
                </div>
              ))}
              {registryNodes.length > 5 && (
                <span className="text-[10px] text-slate-400">
                  +{registryNodes.length - 5}
                </span>
              )}
            </div>
          ) : (
            /* Expanded state: full sidebar */
            <>
              <div className="p-4 border-b border-slate-200 space-y-2">
                <div className="flex items-center gap-2">
                  <button
                    onClick={() => setSidebarCollapsed(true)}
                    className="p-1 hover:bg-slate-100 rounded-md transition flex-shrink-0"
                    title="Collapse sidebar"
                  >
                    <ChevronLeft className="w-4 h-4 text-slate-500" />
                  </button>
                  <div className="relative flex-1">
                    <Search className="absolute left-2 top-1/2 -translate-y-1/2 w-4 h-4 text-slate-500" />
                    <input
                      type="text"
                      placeholder="Search registries / repositories..."
                      value={searchTerm}
                      onChange={(e) => setSearchTerm(e.target.value)}
                      className="w-full pl-8 pr-3 py-2 rounded-lg bg-white border border-slate-200 text-sm text-slate-900 placeholder-gray-500 focus:outline-none focus:ring-2 focus:ring-blue-500"
                    />
                  </div>
                </div>
                {repositoryError && (
                  <p className="text-xs text-red-400">{repositoryError}</p>
                )}
                <div className="flex items-center justify-between text-xs text-slate-500">
                  <span>Registries</span>
                  <Badge variant="secondary">{registryNodes.length}</Badge>
                </div>
              </div>

              <div className="flex-1 overflow-y-auto custom-scrollbar">
                {loadingRegistries ? (
                  <div className="p-4">
                    <LoadingState message="Loading registries..." />
                  </div>
                ) : filteredNodes.length === 0 ? (
                  <div className="p-4">
                    <EmptyState
                      icon={Database}
                      title="No registries"
                      description="Add a Harbor registry to browse deployable charts."
                    />
                  </div>
                ) : (
                  filteredNodes.map((node) => (
                    <div key={node.registry.id || node.registry.name}>
                      <button
                        onClick={() => toggleRegistry(node.registry.id)}
                        className="w-full flex items-center justify-between px-4 py-3 hover:bg-slate-50 transition"
                      >
                        <div className="flex items-center gap-2 min-w-0">
                          {node.expanded ? (
                            <ChevronDown className="w-4 h-4 text-slate-500 flex-shrink-0" />
                          ) : (
                            <ChevronRight className="w-4 h-4 text-slate-500 flex-shrink-0" />
                          )}
                          <Database className="w-4 h-4 text-blue-600 flex-shrink-0" />
                          <div className="text-left min-w-0">
                            <p className="text-sm text-slate-900 truncate">
                              {node.registry.name || "Unnamed"}
                            </p>
                            <p className="text-[11px] text-slate-500 truncate">
                              {node.registry.url}
                            </p>
                          </div>
                        </div>
                        <Badge variant="secondary" className="flex-shrink-0">
                          {node.repositories.length}
                        </Badge>
                      </button>
                      {node.expanded && (
                        <div className="bg-slate-50/60">
                          {node.repositories.length === 0 ? (
                            <p className="px-8 py-3 text-xs text-slate-500">
                              {loadingRepositories
                                ? "Loading repositories..."
                                : "No chart repositories found."}
                            </p>
                          ) : (
                            node.repositories.map((repo) => {
                              const isSelected =
                                selectedRepository?.registryId === repo.registryId &&
                                selectedRepository?.name === repo.name;
                              return (
                                <button
                                  key={`${repo.registryId}-${repo.name}`}
                                  onClick={() => handleRepositoryClick(repo)}
                                  className={`w-full text-left px-8 py-2 flex items-center justify-between text-sm transition ${
                                    isSelected
                                      ? "bg-blue-50 text-blue-700"
                                      : "hover:bg-white/80 text-slate-700"
                                  }`}
                                >
                                  <span className="truncate">{repo.name}</span>
                                  {repo.artifactCount !== undefined && (
                                    <span className="text-xs text-slate-500 flex-shrink-0">
                                      {repo.artifactCount}
                                    </span>
                                  )}
                                </button>
                              );
                            })
                          )}
                        </div>
                      )}
                    </div>
                  ))
                )}
              </div>
            </>
          )}
        </aside>

        <main className="flex-1 flex flex-col bg-white overflow-hidden">
          {!selectedRepository ? (
            /* Placeholder when no repo is selected */
            <div className="flex-1 flex items-center justify-center">
              <EmptyState
                icon={LayoutGrid}
                title="Select a chart"
                description="Select a chart from the left panel to view versions"
              />
            </div>
          ) : (
            <>
              {/* Right panel header */}
              <div className="flex-shrink-0 border-b border-slate-200 p-5 bg-slate-50">
                <div className="flex items-center justify-between flex-wrap gap-4">
                  <div>
                    <p className="text-xs uppercase text-slate-500">Chart repository</p>
                    <h2 className="text-2xl font-semibold text-slate-900 truncate">
                      {selectedRepository.name}
                    </h2>
                    <p className="text-sm text-slate-500">
                      {selectedRegistryName || selectedRepository.registryId}
                    </p>
                  </div>
                  <div className="flex items-center gap-2 flex-wrap">
                    <Filter className="w-4 h-4 text-slate-500" />
                    {FILTER_OPTIONS.map((option) => (
                      <button
                        key={option.label}
                        onClick={() => setFilter(option.value)}
                        className={`px-3 py-1.5 text-xs rounded-full border transition ${
                          filter === option.value
                            ? "bg-blue-600 text-white border-blue-600"
                            : "border-slate-200 text-slate-700 hover:border-slate-400"
                        }`}
                      >
                        {option.label}
                      </button>
                    ))}
                  </div>
                </div>
              </div>

              {/* Tag search bar */}
              <div className="flex-shrink-0 px-5 pt-4 pb-2">
                <div className="relative max-w-xs">
                  <Search className="absolute left-2 top-1/2 -translate-y-1/2 w-4 h-4 text-slate-400" />
                  <input
                    type="text"
                    placeholder="Filter tags by version..."
                    value={tagSearchTerm}
                    onChange={(e) => setTagSearchTerm(e.target.value)}
                    className="w-full pl-8 pr-3 py-1.5 rounded-lg bg-white border border-slate-200 text-sm text-slate-900 placeholder-gray-400 focus:outline-none focus:ring-2 focus:ring-blue-500"
                  />
                </div>
                {tagSearchTerm && (
                  <p className="text-xs text-slate-500 mt-1.5">
                    Showing {filteredArtifacts.length} of {artifacts.length} tags
                  </p>
                )}
              </div>

              {/* Tag grid */}
              <div className="flex-1 overflow-y-auto p-5 pt-2">
                {artifactError && (
                  <p className="text-sm text-red-400 mb-3">{artifactError}</p>
                )}
                {loadingArtifacts ? (
                  <LoadingState message="Loading artifacts..." />
                ) : artifacts.length === 0 ? (
                  <EmptyState
                    icon={Package}
                    title="No artifacts"
                    description={
                      filter
                        ? `No ${filter} tags found for this repository.`
                        : "This repository doesn't contain any tagged artifacts yet."
                    }
                  />
                ) : filteredArtifacts.length === 0 ? (
                  <EmptyState
                    icon={Search}
                    title="No matching tags"
                    description={`No tags matching "${tagSearchTerm}" found.`}
                  />
                ) : (
                  <div className="grid grid-cols-1 sm:grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
                    {filteredArtifacts.map((artifact, index) => (
                      <TagCard
                        key={`${artifact.repositoryName || "repo"}-${artifact.tag || index}`}
                        registryId={selectedRepository.registryId}
                        registryUrl={selectedRegistryUrl}
                        tag={artifact}
                        isLatest={index === 0}
                      />
                    ))}
                  </div>
                )}
              </div>
            </>
          )}
        </main>
      </div>
    </div>
  );
};

export default ArtifactBrowserPage;

function normalizeRepositories(
  registry: RegistryResponse,
  payload: ListRepositories200Item[] | { repositories?: string[] } | null | undefined
): RepositoryNode[] {
  const registryId = registry.id || "";
  const registryName = registry.name || registryId;

  if (Array.isArray(payload)) {
    return payload
      .map((repo): RepositoryNode | null => {
        if (typeof repo === "string") {
          return {
            name: repo,
            registryId,
            registryName,
            artifactCount: undefined,
          };
        }
        const name = repo.name || "";
        if (!name) {
          return null;
        }
        return {
          name,
          registryId,
          registryName,
          artifactCount: (repo as any).artifact_count ?? (repo as any).artifactCount,
        };
      })
      .filter((repo): repo is RepositoryNode => Boolean(repo));
  }

  if (payload && typeof payload === "object" && Array.isArray((payload as any).repositories)) {
    const repoEntries = (payload as any).repositories.map(
      (name: unknown): RepositoryNode | null =>
        typeof name === "string"
          ? {
              name,
              registryId,
              registryName,
              artifactCount: undefined,
            }
          : null
    );
    return repoEntries.filter(
      (repo: RepositoryNode | null): repo is RepositoryNode => Boolean(repo)
    );
  }

  return [];
}
