/**
 * ESLint Rule: no-incorrect-provider-order
 *
 * Validates that React context providers are nested in the correct order
 * according to the BanGUI provider dependency contract.
 *
 * Provider Order (enforced by this rule):
 *  1. ThemeProvider        (OUTERMOST — must be first)
 *  2. FluentProvider       (wraps all Fluent UI consumers)
 *  3. NotificationProvider (must wrap error boundaries)
 *  4. ErrorBoundary        (top-level error boundary)
 *  5. BrowserRouter        (enables routing)
 *  6. NavigationCancellationProvider (manages route cancellation)
 *  7. AuthProvider         (session validation)
 *  8. TimezoneProvider     (INNERMOST — wraps protected routes only)
 */

const PROVIDER_ORDER = [
  "ThemeProvider",
  "FluentProvider",
  "NotificationProvider",
  "ErrorBoundary",
  "BrowserRouter",
  "NavigationCancellationProvider",
  "AuthProvider",
  "TimezoneProvider",
];

/**
 * Get the provider name from a JSX element
 */
function getProviderName(node) {
  if (
    node.name &&
    node.name.type === "JSXIdentifier" &&
    PROVIDER_ORDER.includes(node.name.name)
  ) {
    return node.name.name;
  }
  return null;
}

/**
 * Get the position of a provider in the ordering sequence
 */
function getProviderPosition(name) {
  return PROVIDER_ORDER.indexOf(name);
}

/**
 * Check if a JSX element is a provider
 */
function isProvider(node) {
  return getProviderName(node) !== null;
}

/**
 * Extract all JSX provider elements from a component tree
 */
function extractProviderStack(node) {
  const stack = [];
  let current = node;

  while (current) {
    if (!current.openingElement) break;

    const providerName = getProviderName(current.openingElement);

    if (providerName) {
      stack.push({
        name: providerName,
        node: current.openingElement,
      });
    }

    // Find the next JSX element by looking at children
    if (current.children && current.children.length > 0) {
      const firstChild = current.children[0];

      if (firstChild && firstChild.type === "JSXElement") {
        current = firstChild;
      } else {
        break;
      }
    } else {
      break;
    }
  }

  return stack;
}

export default {
  meta: {
    type: "problem",
    docs: {
      description:
        "Enforce correct nesting order of React context providers in BanGUI",
      category: "Best Practices",
      recommended: "error",
      url: "https://github.com/bangui/frontend/blob/main/src/providers/PROVIDER_ORDER.md",
    },
    fixable: undefined,
    schema: [],
    messages: {
      incorrectOrder:
        "Provider '{{ outer }}' (position {{ outerPos }}) must wrap '{{ inner }}' (position {{ innerPos }}). Current nesting: {{ outer }} → {{ inner }}",
      missingParent:
        "Provider '{{ current }}' (position {{ currentPos }}) requires parent provider '{{ required }}' (position {{ requiredPos }}). Expected: {{ expected }}",
    },
  },
  create(context) {
    return {
      JSXElement(node) {
        // Skip non-provider elements
        if (!node.openingElement || !isProvider(node.openingElement)) {
          return;
        }

        // Extract the provider stack from this element
        const stack = extractProviderStack(node);

        if (stack.length < 2) {
          // No nesting to validate
          return;
        }

        // Check ordering from outermost to innermost
        for (let i = 0; i < stack.length - 1; i++) {
          const outer = stack[i];
          const inner = stack[i + 1];

          const outerPos = getProviderPosition(outer.name);
          const innerPos = getProviderPosition(inner.name);

          // Inner provider must have a higher position (come later in the sequence)
          if (innerPos <= outerPos) {
            context.report({
              node: inner.node,
              messageId: "incorrectOrder",
              data: {
                outer: outer.name,
                outerPos: String(outerPos),
                inner: inner.name,
                innerPos: String(innerPos),
              },
            });
          }
        }

        // Check that all providers before the first one in the stack are present
        const firstProvider = stack[0];
        const firstPos = getProviderPosition(firstProvider.name);

        if (firstPos > 0) {
          const required = PROVIDER_ORDER[firstPos - 1];
          context.report({
            node: firstProvider.node,
            messageId: "missingParent",
            data: {
              current: firstProvider.name,
              currentPos: String(firstPos),
              required,
              requiredPos: String(firstPos - 1),
              expected: PROVIDER_ORDER.slice(0, firstPos).join(" → "),
            },
          });
        }
      },
    };
  },
};
