class ReactFiber {
  constructor(selector, options = {}) {
    this.selector = selector;
    this.debug = options.debug || false;
    this.nodes = [...document.querySelectorAll(selector)]; // Support multiple elements
    this.fibers = this.nodes.map((node) => this.getFiberNode(node));
    this.components = this.fibers.map((fiber) => this.getOwnerComponent(fiber));

    if (this.debug) {
      console.log("Selected Nodes:", this.nodes);
      console.log("🔍 Found Fibers:", this.fibers);
      console.log("🛠 Found Components:", this.components);
      
      // Debug fiber info
      this.fibers.forEach((fiber, index) => {
        if (fiber) {
          console.log(`Fiber ${index}:`, {
            tag: fiber.tag,
            type: fiber.type?.name || fiber.type,
            elementType: fiber.elementType,
            stateNode: fiber.stateNode,
            hasState: !!fiber.stateNode?.state,
            hasMemoizedState: !!fiber.memoizedState
          });
        }
      });
    }
  }

  static find(selector, options = {}) {
    return new ReactFiber(selector, options);
  }

  getFiberNode(node) {
    if (!node) return null;
    
    // Try multiple property name patterns for different React versions
    const possibleKeys = [
      '__reactFiber$',           // React 16+
      '__reactInternalFiber$',   // React 15
      '__reactInternalInstance$', // Older versions
      '__reactFiber',
      '__reactInternalInstance'
    ];
    
    // Check for exact matches first
    for (const key of possibleKeys) {
      if (node[key]) return node[key];
    }
    
    // Fall back to pattern matching
    const fiberKey = Object.getOwnPropertyNames(node).find(
      (name) =>
        name.startsWith("__reactFiber") ||
        name.startsWith("__reactInternalInstance") ||
        name.startsWith("__reactInternalFiber")
    );
    return fiberKey ? node[fiberKey] : null;
  }

  getOwnerComponent(fiberNode) {
    let current = fiberNode;
    while (current) {
      // Use React's internal tag system to identify component types
      // Based on React's WorkTags: ClassComponent = 1, FunctionComponent = 0
      if (current.tag === 1) { // ClassComponent
        return current.stateNode; // For class components, stateNode is the component instance
      }
      
      // For function components, look for hooks in memoizedState
      if (current.tag === 0 || current.tag === 15) { // FunctionComponent or MemoComponent
        // Function components don't have setState, but we can still track them
        if (current.memoizedState && current.type) {
          return {
            type: 'function',
            hooks: current.memoizedState,
            fiber: current,
            forceUpdate: () => {
              // Trigger re-render by updating fiber
              if (current.alternate) {
                current.alternate.expirationTime = 1;
              }
              current.expirationTime = 1;
            }
          };
        }
      }
      
      // Legacy fallback: check if stateNode has React component methods
      if (
        current.stateNode &&
        current.stateNode !== null &&
        typeof current.stateNode === 'object' &&
        (current.stateNode.setState || current.stateNode.forceUpdate)
      ) {
        return current.stateNode;
      }
      
      current = current.return;
    }
    return null;
  }

  getState(key) {
    if (!this.components.length && !this.fibers.length) return null;
    
    const component = this.components[0];
    const fiber = this.fibers[0];
    let state = null;
    
    // Handle class components
    if (component?.state) {
      state = component.state;
    }
    // Handle function components with hooks - look directly at fiber
    else if (fiber?.memoizedState) {
      if (this.debug) {
        console.log("🔍 Raw fiber.memoizedState:", fiber.memoizedState);
      }
      // Extract useState values from the hook chain
      const states = this.extractStateFromHooks(fiber.memoizedState);
      state = states.length === 1 ? states[0] : states;
    }
    // Fallback: try component hooks if available
    else if (component?.type === 'function' && component?.hooks) {
      const states = this.extractStateFromHooks(component.hooks);
      state = states.length === 1 ? states[0] : states;
    }

    if (key === undefined) {
      return state;
    } else if (typeof key === "string") {
      return state?.[key];
    } else if (Array.isArray(key)) {
      const filteredState = {};
      for (const k of key) {
        if (state && Object.hasOwn(state, k)) {
          filteredState[k] = state[k];
        }
      }
      return filteredState;
    }
    return null;
  }

  extractStateFromHooks(hookChain) {
    const states = [];
    let mainStateFound = false;
    let currentHook = hookChain;
    let hookIndex = 0;
    
    if (this.debug) {
      console.log("🔍 Hook chain analysis:");
    }
    
    while (currentHook) {
      if (this.debug) {
        console.log(`Hook ${hookIndex}:`, {
          type: currentHook.tag || 'unknown',
          memoizedState: currentHook.memoizedState,
          queue: currentHook.queue,
          next: !!currentHook.next
        });
      }
      
      // Try different approaches to extract state
      if (currentHook.memoizedState !== undefined && currentHook.memoizedState !== null) {
        const state = currentHook.memoizedState;
        
        // Priority 1: Check for useRef hooks with complex state in .current
        if (!currentHook.queue && 
            typeof state === 'object' && 
            state !== null && 
            state.current !== undefined &&
            typeof state.current === 'object' &&
            state.current !== null) {
          
          // Check if this looks like a substantial state object (has multiple properties)
          const currentKeys = Object.keys(state.current);
          if (currentKeys.length > 2) {
            states.push(state.current);
            mainStateFound = true;
            if (this.debug) console.log(`  🎯 Found main state in useRef:`, state.current);
          }
        }
        // Priority 2: useState hooks with queue
        else if (currentHook.queue && typeof state !== 'function') {
          states.push(state);
          if (this.debug) console.log(`  ✅ Found useState state:`, state);
        }
        // Priority 3: Other potential state objects (only if we haven't found main state)
        else if (!mainStateFound && !currentHook.queue && typeof state === 'object' && state !== null) {
          // Skip useEffect hooks (they have tag 36)
          if (!(state.tag === 36 && state.create)) {
            states.push(state);
            if (this.debug) console.log(`  📦 Found potential state object:`, state);
          }
        }
        // Priority 4: Simple primitive state
        else if (typeof state !== 'function' && typeof state !== 'object') {
          states.push(state);
          if (this.debug) console.log(`  🔹 Found primitive state:`, state);
        }
      }
      
      currentHook = currentHook.next;
      hookIndex++;
    }
    
    if (this.debug) {
      console.log(`🎯 Extracted ${states.length} state values:`, states);
    }
    
    // If we found main state objects, prioritize and deduplicate them
    if (mainStateFound && states.length > 1) {
      const mainStates = states.filter(state => 
        typeof state === 'object' && 
        state !== null && 
        Object.keys(state).length > 2
      );
      
      if (mainStates.length > 1) {
        // If we have multiple main state objects, find the most comprehensive one
        // or merge them if they seem complementary
        const largestState = mainStates.reduce((largest, current) => {
          const largestKeys = Object.keys(largest).length;
          const currentKeys = Object.keys(current).length;
          
          // Prefer the one with more properties
          if (currentKeys > largestKeys) return current;
          
          // If same number of properties, prefer the one with more complex data
          if (currentKeys === largestKeys) {
            const largestComplexity = this.calculateStateComplexity(largest);
            const currentComplexity = this.calculateStateComplexity(current);
            return currentComplexity > largestComplexity ? current : largest;
          }
          
          return largest;
        });
        
        if (this.debug) {
          console.log(`🎯 Selected most comprehensive state from ${mainStates.length} candidates:`, largestState);
        }
        
        return [largestState];
      }
      
      return mainStates;
    }
    
    return states;
  }

  calculateStateComplexity(state) {
    if (!state || typeof state !== 'object') return 0;
    
    let complexity = 0;
    for (const [key, value] of Object.entries(state)) {
      complexity += 1; // Base point for each property
      
      if (Array.isArray(value)) {
        complexity += value.length * 0.1; // Arrays get points based on length
      } else if (typeof value === 'object' && value !== null) {
        complexity += Object.keys(value).length * 0.5; // Nested objects get points
      } else if (typeof value === 'function') {
        complexity += 2; // Functions are valuable
      }
    }
    
    return complexity;
  }

  setState(update) {
    this.components.forEach((component) => {
      // Handle class components
      if (component?.setState) {
        if (typeof update === "function") {
          // Functional update
          component.setState((prevState) => {
            const newState = update(prevState);
            if (this.debug)
              console.log("✅ Updated State (Functional):", newState);
            return newState;
          });
        } else {
          // Object update (merge with existing state)
          component.setState((prevState) => {
            const newState = {
              ...prevState,
              ...update,
            };
            if (this.debug)
              console.log("✅ Updated State (Object Merge):", newState);
            return newState;
          });
        }
      }
      // Handle function components - force re-render since we can't directly update hooks
      else if (component?.type === 'function' && component?.forceUpdate) {
        if (this.debug) {
          console.log("⚠️  Function component detected - triggering re-render. Direct state update not possible.");
        }
        component.forceUpdate();
      }
    });
    return this;
  }

  getProp(propName) {
    if (!this.fibers.length) return null;

    if (propName === undefined) {
      return this.fibers[0]?.memoizedProps;
    }

    return this.fibers[0]?.memoizedProps?.[propName];
  }

  setProp(propName, value) {
    this.fibers.forEach((fiber) => {
      if (fiber?.memoizedProps) {
        fiber.memoizedProps[propName] = value;
      }
    });
    return this; // Enable chaining
  }

  forceUpdate() {
    this.components.forEach((component) => {
      if (component?.forceUpdate) {
        component.forceUpdate();
        if (this.debug) console.log("🔄 Forced React Re-render");
      }
    });
    return this; // Enable chaining
  }
}

function makeSerializable(obj, visited = new WeakSet(), depth = 0, maxDepth = 10) {
  // Handle primitives first
  if (obj === null || obj === undefined) {
    return obj;
  }
  
  // Catch ALL functions early
  if (typeof obj === "function") {
    return `[Function: ${obj.name || 'anonymous'}]`;
  }
  
  if (typeof obj !== "object") {
    // Handle other primitives
    if (typeof obj === "symbol") return obj.toString();
    if (typeof obj === "bigint") return obj.toString() + "n";
    return obj;
  }

  // Prevent infinite recursion - depth limit
  if (depth > maxDepth) {
    return "[Max Depth Reached]";
  }

  // Prevent circular references
  if (visited.has(obj)) {
    return "[Circular Reference]";
  }
  visited.add(obj);

  try {
    // Handle special objects first
    if (obj instanceof HTMLElement) {
      return {
        type: "HTMLElement",
        tagName: obj.tagName,
        id: obj.id || null,
        className: obj.className || null,
        attributes: obj.attributes ? Array.from(obj.attributes).map(attr => ({ name: attr.name, value: attr.value })) : []
      };
    }

    if (obj instanceof Event) {
      return {
        type: "Event",
        eventType: obj.type,
        target: obj.target?.tagName || null
      };
    }

    if (obj instanceof Date) {
      return { type: "Date", value: obj.toISOString() };
    }

    if (obj instanceof RegExp) {
      return { type: "RegExp", source: obj.source, flags: obj.flags };
    }

    if (obj instanceof Error) {
      return { type: "Error", message: obj.message, name: obj.name };
    }

    // Handle React Fiber nodes - these are super circular
    if (obj.tag !== undefined && obj.elementType !== undefined) {
      return {
        type: "ReactFiber",
        tag: obj.tag,
        elementType: typeof obj.elementType === 'function' ? obj.elementType.name || 'AnonymousComponent' : String(obj.elementType),
        key: obj.key,
        hasState: !!obj.stateNode?.state,
        hasMemoizedState: !!obj.memoizedState,
        hasProps: !!obj.memoizedProps
      };
    }

    // Handle arrays
    if (Array.isArray(obj)) {
      return obj.slice(0, 50).map((item, index) => {
        if (index >= 25) return "[...truncated]"; // Smaller limit
        return makeSerializable(item, visited, depth + 1, maxDepth);
      });
    }

    // Handle regular objects
    const serializableObj = {};
    
    // Get own enumerable properties only to avoid prototype pollution
    const ownKeys = Object.getOwnPropertyNames(obj).filter(key => {
      try {
        return obj.propertyIsEnumerable(key);
      } catch {
        return false;
      }
    });
    
    // Limit number of properties to avoid huge objects
    const maxKeys = 30; // Smaller limit for safety
    const processedKeys = ownKeys.slice(0, maxKeys);
    
    for (const key of processedKeys) {
      try {
        // Skip problematic keys early
        const dangerousKeys = [
          'parentNode', 'parentElement', 'ownerDocument', 'children', 'childNodes',
          'return', 'child', 'sibling', 'alternate', 'ref', // React Fiber circular refs
          '_owner', '_source', '_self', '_debugOwner', '_debugSource', // React internals
          'window', 'document', 'global', 'self', 'top', 'parent', // Global objects
          'constructor', 'prototype', '__proto__', // Constructor/prototype chains
          'addEventListener', 'removeEventListener', // Event handlers
          'setState', 'forceUpdate', 'render' // React methods that might be functions
        ];
        
        if (dangerousKeys.includes(key)) {
          serializableObj[key] = `[Skipped: ${key}]`;
          continue;
        }

        const descriptor = Object.getOwnPropertyDescriptor(obj, key);
        if (descriptor && (descriptor.get || descriptor.set)) {
          serializableObj[key] = "[Getter/Setter]";
          continue;
        }

        let value = obj[key];

        // Handle symbols specifically (React context symbols)
        if (typeof value === "symbol") {
          value = `[Symbol: ${value.description || 'anonymous'}]`;
        }
        // Extra function check
        else if (typeof value === "function") {
          value = `[Function: ${value.name || 'anonymous'}]`;
        } else if (value && typeof value === "object") {
          value = makeSerializable(value, visited, depth + 1, maxDepth);
        }

        serializableObj[key] = value;
      } catch (error) {
        serializableObj[key] = `[Error: ${error.message}]`;
      }
    }
    
    if (ownKeys.length > maxKeys) {
      serializableObj['...'] = `[${ownKeys.length - maxKeys} more properties]`;
    }

    return serializableObj;
  } catch (error) {
    return `[Serialization Error: ${error.message}]`;
  } finally {
    visited.delete(obj); // Clean up for potential reuse
  }
}

// Final safety check - recursively scan for any remaining functions
function deepFunctionCheck(obj, path = "") {
  if (typeof obj === "function") {
    throw new Error(`Found function at path: ${path}`);
  }
  
  if (obj && typeof obj === "object") {
    if (Array.isArray(obj)) {
      obj.forEach((item, index) => {
        deepFunctionCheck(item, `${path}[${index}]`);
      });
    } else {
      Object.keys(obj).forEach(key => {
        deepFunctionCheck(obj[key], path ? `${path}.${key}` : key);
      });
    }
  }
}

function isTrustedMessage(event) {
  return event.source === window && event.origin === window.location.origin;
}

function executeReactFiberAction(fiberInstance, action, payload) {
  switch (action) {
    case "getState":
      return fiberInstance.getState(payload.key);
    case "setState":
      if (
        payload.updateObject &&
        typeof payload.updateObject === "object" &&
        !Array.isArray(payload.updateObject)
      ) {
        fiberInstance.setState(payload.updateObject);
      } else {
        console.warn("[pageState] setState rejected: only plain objects are allowed");
      }
      return {};
    case "getProp":
      return fiberInstance.getProp(payload.propName);
    case "setProp":
      fiberInstance.setProp(payload.propName, payload.value);
      return {};
    case "forceUpdate":
      fiberInstance.forceUpdate();
      return {};
    default:
      console.warn(`[pageState] Unknown action: ${action}`);
      return null;
  }
}

function sanitizeResponseForPostMessage(response) {
  if (response !== null && typeof response === "object") {
    response = makeSerializable(response);
  }

  try {
    deepFunctionCheck(response);
  } catch (functionError) {
    console.warn("[pageState] Function detected in response, cleaning:", functionError.message);
    return `[Cleaned Response - Function found at: ${functionError.message}]`;
  }

  try {
    if (typeof structuredClone === "function") {
      structuredClone(response);
    } else {
      JSON.parse(JSON.stringify(response));
    }
  } catch (cloneError) {
    console.warn("[pageState] Response not cloneable, fallback:", cloneError.message);
    return `[Uncloneable Response: ${cloneError.message}]`;
  }

  return response;
}

function handleReactFiberRequest(data) {
  const { selector, action, payload, debug, messageId } = data;
  const fiberInstance = ReactFiber.find(selector, { debug });
  const response = sanitizeResponseForPostMessage(
    executeReactFiberAction(fiberInstance, action, payload),
  );

  window.postMessage(
    {
      type: "reactFiberResponse",
      response,
      messageId,
    },
    window.location.origin,
  );
}

function handleTriggerKeyboardEvent(data) {
  const { key, code, altKey, ctrlKey, metaKey, shiftKey, keyCode } = data;
  const keyboardEvent = new KeyboardEvent("keydown", {
    key,
    code,
    keyCode: keyCode || 0,
    which: keyCode || 0,
    altKey: altKey || false,
    ctrlKey: ctrlKey || false,
    metaKey: metaKey || false,
    shiftKey: shiftKey || false,
    bubbles: true,
    cancelable: true,
  });
  document.dispatchEvent(keyboardEvent);
}

window.addEventListener("message", (event) => {
  if (!isTrustedMessage(event)) return;

  if (event.data.type === "reactFiberRequest") {
    handleReactFiberRequest(event.data);
    return;
  }

  if (event.data.type === "triggerKeyboardEvent") {
    handleTriggerKeyboardEvent(event.data);
  }
});
