import { useSyncExternalStore } from 'react';

import { store } from '@grafana/data';
import { type Scope, type ScopeNode } from 'app/api/clients/scope/v0alpha1/endpoints.gen';

import {
  RECENT_SCOPES_CHANGED_EVENT,
  RECENT_SCOPES_KEY,
  readStoredRecentScopes,
  type StoredRecentScopeSet,
} from './recentScopesStorage';
import { type RecentScopeSet } from './types';
import { useScopesById, useScopeNodesByName } from './useScopesApi';

function subscribe(callback: () => void) {
  const onStorage = (e: StorageEvent) => {
    if (e.key === RECENT_SCOPES_KEY) {
      callback();
    }
  };
  window.addEventListener(RECENT_SCOPES_CHANGED_EVENT, callback);
  window.addEventListener('storage', onStorage);
  return () => {
    window.removeEventListener(RECENT_SCOPES_CHANGED_EVENT, callback);
    window.removeEventListener('storage', onStorage);
  };
}

// Cache by serialized value to ensure stable references from useSyncExternalStore.
// React uses Object.is to compare snapshots; a new array on every call would cause
// infinite re-renders in React 18 concurrent mode.
let cachedJsonKey: string | undefined;
let cachedSnapshot: StoredRecentScopeSet[] = [];

function getSnapshot(): StoredRecentScopeSet[] {
  const fresh = readStoredRecentScopes(store);
  const jsonKey = JSON.stringify(fresh);
  if (jsonKey !== cachedJsonKey) {
    cachedJsonKey = jsonKey;
    cachedSnapshot = fresh;
  }
  return cachedSnapshot;
}

function resolveParentNodeTitle(
  set: StoredRecentScopeSet,
  scopesById: Record<string, Scope | undefined>,
  leafNodesById: Record<string, ScopeNode | undefined>,
  parentNodesById: Record<string, ScopeNode | undefined>
): string | undefined {
  const defaultPath = scopesById[set.scopeIds[0]]?.spec?.defaultPath ?? [];
  if (defaultPath.length > 1) {
    const parentId = defaultPath[defaultPath.length - 2];
    return parentId !== '' ? parentNodesById[parentId]?.spec?.title : undefined;
  }
  if (!set.scopeNodeId) {
    return undefined;
  }
  const leafNode = leafNodesById[set.scopeNodeId];
  // Prefer the leaf node's own subTitle (no extra request needed)
  const parentName = leafNode?.spec?.parentName;
  return leafNode?.spec?.subTitle ?? (parentName ? parentNodesById[parentName]?.spec?.title : undefined);
}

export function useRecentScopes(appliedScopeIds: string[]): RecentScopeSet[] {
  const storedScopes = useSyncExternalStore(subscribe, getSnapshot, () => []);

  // Filter out the currently applied scopes to avoid duplicates
  const appliedSet = new Set(appliedScopeIds);
  const filteredScopes = storedScopes.filter((set) => {
    if (set.scopeIds.length !== appliedScopeIds.length) {
      return true;
    }
    return !set.scopeIds.every((id) => appliedSet.has(id));
  });

  // Collect all unique scope IDs to fetch
  const allScopeIds = [...new Set(filteredScopes.flatMap((s) => s.scopeIds))];
  const scopesById = useScopesById(allScopeIds);

  // For sets without defaultPath, fetch the stored leaf node so we can read its parentName
  const leafNodeIds = [
    ...new Set(
      filteredScopes.flatMap((set) => {
        const defaultPath = scopesById[set.scopeIds[0]]?.spec?.defaultPath ?? [];
        return defaultPath.length <= 1 && set.scopeNodeId ? [set.scopeNodeId] : [];
      })
    ),
  ];
  const leafNodesById = useScopeNodesByName(leafNodeIds);

  // Derive parent node IDs: from defaultPath[length-2] (preferred) or from leaf node's parentName
  // (only needed when the leaf node has no subTitle to use directly)
  const parentNodeIds = [
    ...new Set(
      filteredScopes.flatMap((set) => {
        const defaultPath = scopesById[set.scopeIds[0]]?.spec?.defaultPath ?? [];
        if (defaultPath.length > 1) {
          const parentId = defaultPath[defaultPath.length - 2];
          return parentId !== '' ? [parentId] : [];
        }
        if (set.scopeNodeId) {
          const leafNode = leafNodesById[set.scopeNodeId];
          // If the leaf node has a subTitle, we use it directly — no parent fetch needed
          const parentName = leafNode?.spec?.subTitle ? undefined : leafNode?.spec?.parentName;
          return parentName ? [parentName] : [];
        }
        return [];
      })
    ),
  ];

  const parentNodesById = useScopeNodesByName(parentNodeIds);

  return filteredScopes.map((set) => {
    const scopes = set.scopeIds.flatMap((id) => {
      const scope = scopesById[id];
      return scope?.spec ? [{ id, title: scope.spec.title }] : [];
    });

    return {
      scopeIds: set.scopeIds,
      scopes,
      scopeNodeId: set.scopeNodeId,
      parentNodeTitle: resolveParentNodeTitle(set, scopesById, leafNodesById, parentNodesById),
    };
  });
}
