/**
 * Provider Ordering Runtime Validator
 *
 * This utility validates that providers are correctly nested at runtime,
 * serving as a safety net for development when compile-time checks are bypassed
 * (e.g., through type assertions or dynamic composition).
 *
 * Usage:
 *  ```tsx
 *  function AppContents() {
 *    validateProviderOrder();  // Throws if called outside expected provider context
 *    // ... rest of component
 *  }
 *  ```
 */

import { useContext, createContext, useEffect } from "react";
import type { ReactNode } from "react";

// ============================================================================
// Provider Tracking Context
// ============================================================================

interface ProviderOrderContext {
  providers: Set<string>;
}

const ProviderOrderTrackingContext = createContext<ProviderOrderContext | null>(null);

/**
 * Get the current provider order tracking context
 */
function getProviderOrderTracking(): ProviderOrderContext | null {
  try {
    // eslint-disable-next-line react-hooks/rules-of-hooks
    return useContext(ProviderOrderTrackingContext);
  } catch {
    // useContext can only be called within a component
    return null;
  }
}

/**
 * Allowed provider order sequence. Any deviation is an error.
 */
const PROVIDER_SEQUENCE = [
  "ThemeProvider",
  "FluentProvider",
  "NotificationProvider",
  "ErrorBoundary",
  "BrowserRouter",
  "NavigationCancellationProvider",
  "AuthProvider",
  "TimezoneProvider",
] as const;

type ProviderName = (typeof PROVIDER_SEQUENCE)[number];

/**
 * Validate that a provider is in the correct position relative to its siblings.
 *
 * Throws a descriptive error if:
 * - A provider is nested in the wrong order
 * - A required parent provider is missing
 *
 * @param providerName The name of the provider being validated
 * @param expectedPosition The expected position in the provider sequence
 * @throws {Error} If provider ordering is violated
 *
 * @example
 * ```tsx
 * export function AuthProvider({ children }: { children: ReactNode }): JSX.Element {
 *   validateProviderPosition("AuthProvider", 6);
 *   // ... provider implementation
 * }
 * ```
 */
export function validateProviderPosition(
  providerName: ProviderName,
  expectedPosition: number
): void {
  if (expectedPosition < 0 || expectedPosition >= PROVIDER_SEQUENCE.length) {
    throw new Error(
      `[ProviderValidator] Invalid expected position ${String(expectedPosition)} for ${providerName}. ` +
      `Expected position between 0 and ${String(PROVIDER_SEQUENCE.length - 1)}.`
    );
  }

  const tracking = getProviderOrderTracking();

  if (tracking === null && expectedPosition > 0) {
    // Only allow position 0 (ThemeProvider) to have no tracking context
    throw new Error(
      `[ProviderValidator] ${providerName} (position ${String(expectedPosition)}) is missing required parent providers. ` +
      `Expected parent: ${String(PROVIDER_SEQUENCE[expectedPosition - 1])}. ` +
      `Provider order: ${PROVIDER_SEQUENCE.slice(0, expectedPosition).join(" → ")} → [${providerName}]`
    );
  }

  if (tracking !== null && expectedPosition > 0) {
    const requiredParent: ProviderName = PROVIDER_SEQUENCE[expectedPosition - 1] as ProviderName;

    if (!tracking.providers.has(requiredParent)) {
      const providerSlice: ProviderName[] = PROVIDER_SEQUENCE.slice(0, expectedPosition);
      const missingProviders = providerSlice.filter(
        (p: ProviderName) => !tracking.providers.has(p)
      );

      throw new Error(
        `[ProviderValidator] ${providerName} (position ${String(expectedPosition)}) is missing required parent providers. ` +
        `Missing: ${missingProviders.join(", ")}. ` +
        `Expected order: ${PROVIDER_SEQUENCE.slice(0, expectedPosition).join(" → ")} → [${providerName}]`
      );
    }
  }
}

/**
 * Validate that all required providers are present in the component tree.
 *
 * Use this in components that depend on multiple providers to ensure
 * they're all available.
 *
 * @param requiredProviders Array of provider names that must be present
 * @throws {Error} If any required provider is missing
 *
 * @example
 * ```tsx
 * function Dashboard(): JSX.Element {
 *   validateProvidersExist(["ThemeProvider", "AuthProvider", "NotificationProvider"]);
 *   // ... component that uses all three providers
 * }
 * ```
 */
export function validateProvidersExist(requiredProviders: ProviderName[]): void {
  const tracking = getProviderOrderTracking();

  if (tracking === null) {
    throw new Error(
      `[ProviderValidator] Missing required providers: ${requiredProviders.join(", ")}. ` +
      `Ensure your component is wrapped with: ${requiredProviders.join(" → ")}`
    );
  }

  const missing = requiredProviders.filter((p) => !tracking.providers.has(p));

  if (missing.length > 0) {
    throw new Error(
      `[ProviderValidator] Missing required providers: ${missing.join(", ")}. ` +
      `Ensure your component is wrapped with: ${requiredProviders.join(" → ")}`
    );
  }
}

/**
 * Check if a provider is present in the current component tree.
 *
 * @param providerName Name of the provider to check
 * @returns true if the provider is in the current tree, false otherwise
 *
 * @example
 * ```tsx
 * function MyComponent(): JSX.Element {
 *   if (hasProvider("AuthProvider")) {
 *     const auth = useAuth();
 *     // ... use auth
 *   }
 * }
 * ```
 */
export function hasProvider(providerName: ProviderName): boolean {
  const tracking = getProviderOrderTracking();
  return tracking !== null && tracking.providers.has(providerName);
}

/**
 * Get all providers currently in the component tree (in order).
 *
 * @returns Array of provider names from outermost to innermost
 *
 * @example
 * ```tsx
 * const providers = getCurrentProviders();
 * console.log(providers);  // ["ThemeProvider", "FluentProvider", "NotificationProvider", ...]
 * ```
 */
export function getCurrentProviders(): ProviderName[] {
  const tracking = getProviderOrderTracking();
  if (tracking === null) {
    return [];
  }

  // Return providers in the order they appear in PROVIDER_SEQUENCE
  return PROVIDER_SEQUENCE.filter((p) => tracking.providers.has(p));
}

/**
 * Create a tracking wrapper for any provider.
 *
 * This allows runtime validation of provider nesting order.
 * Call this from within any provider component.
 *
 * @param providerName Name of the provider
 * @param expectedPosition Position in the provider sequence
 * @param children Child components
 * @returns JSX element with provider order tracking
 *
 * @internal Use this in provider implementations to enable validation
 *
 * @example
 * ```tsx
 * export function CustomProvider({ children }: { children: ReactNode }): JSX.Element {
 *   return createProviderTracker("CustomProvider", 5, children);
 * }
 * ```
 */
export function createProviderTracker(
  providerName: ProviderName,
  expectedPosition: number,
  children: ReactNode
): React.JSX.Element {
  return <ProviderOrderTracker name={providerName} position={expectedPosition} children={children} />;
}

/**
 * Internal component that tracks provider nesting order.
 */
function ProviderOrderTracker({
  name,
  position,
  children,
}: {
  name: ProviderName;
  position: number;
  children: ReactNode;
}): React.JSX.Element {
  const parentTracking = getProviderOrderTracking();

  // Validate position
  if (position < 0 || position >= PROVIDER_SEQUENCE.length) {
    throw new Error(
      `[ProviderValidator] Invalid position ${String(position)} for provider ${name}. ` +
      `Expected 0-${String(PROVIDER_SEQUENCE.length - 1)}.`
    );
  }

  // Validate parent providers
  if (position > 0) {
    const expectedParent: ProviderName = PROVIDER_SEQUENCE[position - 1] as ProviderName;
    if (parentTracking === null || !parentTracking.providers.has(expectedParent)) {
      const providerSlice: ProviderName[] = PROVIDER_SEQUENCE.slice(0, position);
      const missing = providerSlice.filter(
        (p: ProviderName) => parentTracking === null || !parentTracking.providers.has(p)
      );

      throw new Error(
        `[ProviderValidator] ${name} (position ${String(position)}) has incorrect parent structure. ` +
        `Missing: ${missing.join(", ")}. ` +
        `Expected: ${PROVIDER_SEQUENCE.slice(0, position).join(" → ")} → [${name}]`
      );
    }
  }

  // Create new tracking set including this provider
  const newTracking: ProviderOrderContext = {
    providers: new Set(parentTracking?.providers ?? []),
  };
  newTracking.providers.add(name);

  return (
    <ProviderOrderTrackingContext.Provider value={newTracking}>
      {children}
    </ProviderOrderTrackingContext.Provider>
  );
}

/**
 * Development-only hook to verify a component is wrapped by required providers.
 *
 * Use this in development to catch provider ordering issues early.
 *
 * @param componentName Name of the component (for error messages)
 * @param requiredProviders Array of required provider names
 * @throws {Error} in development if any required provider is missing
 *
 * @example
 * ```tsx
 * function JailsPage(): JSX.Element {
 *   useProviderValidation("JailsPage", ["ThemeProvider", "AuthProvider"]);
 *   // ... component implementation
 * }
 * ```
 */
export function useProviderValidation(
  componentName: string,
  requiredProviders: ProviderName[]
): void {
  useEffect(() => {
    if (process.env.NODE_ENV === "development") {
      const tracking = getProviderOrderTracking();

      if (tracking === null) {
        console.warn(
          `[ProviderValidator] ${componentName} requires providers but none were found. ` +
          `Required: ${requiredProviders.join(", ")}`
        );
        return;
      }

      const missing = requiredProviders.filter((p) => !tracking.providers.has(p));

      if (missing.length > 0) {
        console.warn(
          `[ProviderValidator] ${componentName} is missing providers: ${missing.join(", ")}. ` +
          `Currently available: ${Array.from(tracking.providers).join(", ")}`
        );
      }
    }
  }, [componentName, requiredProviders]);
}

export { ProviderOrderTrackingContext };
