/**
 * Hypen Router - Declarative routing system
 * Integrated with Hypen's reactive state management
 */

import { createObservableState, getStateSnapshot } from "./state.js";

export type RouteMatch = {
  params: Record<string, string>;
  query: Record<string, string>;
  path: string;
};

export type RouteState = {
  currentPath: string;
  params: Record<string, string>;
  query: Record<string, string>;
  previousPath: string | null;
};

export type RouteChangeCallback = (route: RouteState) => void;

/**
 * Hypen Router - Manages application routing with pattern matching
 */
export class HypenRouter {
  private state: RouteState;
  private subscribers = new Set<RouteChangeCallback>();
  private isInitialized = false;
  private isUpdating = false;

  constructor() {
    // Create observable state for reactivity
    this.state = createObservableState<RouteState>(
      {
        currentPath: "/",
        params: {},
        query: {},
        previousPath: null,
      },
      {
        onChange: () => {
          this.notifySubscribers();
        },
      }
    );

    // Initialize from browser if available
    if (typeof window !== "undefined") {
      this.initializeBrowserSync();
    }
  }

  /**
   * Initialize browser history sync
   */
  private initializeBrowserSync() {
    // Get initial path from hash or pathname
    const initialPath = this.getPathFromBrowser();
    this.state.currentPath = initialPath;
    this.state.params = {};
    this.state.query = this.parseQuery();

    // Listen for browser back/forward
    window.addEventListener("popstate", () => {
      const newPath = this.getPathFromBrowser();
      this.updatePath(newPath, false); // Don't push to history again
    });

    // Listen for hash changes
    window.addEventListener("hashchange", () => {
      // Don't respond to hashchange events we triggered ourselves
      if (this.isUpdating) return;
      const newPath = this.getPathFromBrowser();
      this.updatePath(newPath, false);
    });

    this.isInitialized = true;
    console.log("Router initialized at:", initialPath);
  }

  /**
   * Get path from browser URL (supports both hash and pathname)
   */
  private getPathFromBrowser(): string {
    if (typeof window === "undefined") return "/";

    // Prefer hash-based routing for simplicity
    const hash = window.location.hash.slice(1);
    if (hash) return hash;

    // Fallback to pathname
    return window.location.pathname;
  }

  /**
   * Parse query string from URL
   */
  private parseQuery(): Record<string, string> {
    if (typeof window === "undefined") return {};

    const query: Record<string, string> = {};
    const searchParams = new URLSearchParams(window.location.search);

    searchParams.forEach((value, key) => {
      query[key] = value;
    });

    return query;
  }

  /**
   * Navigate to a new path
   */
  push(path: string) {
    console.log("Router.push:", path);
    this.updatePath(path, true);
  }

  /**
   * Replace current path without adding to history
   */
  replace(path: string) {
    console.log("Router.replace:", path);
    this.updatePath(path, true, true);
  }

  /**
   * Go back in history
   */
  back() {
    console.log("Router.back");
    if (typeof window !== "undefined") {
      window.history.back();
    }
  }

  /**
   * Go forward in history
   */
  forward() {
    console.log("Router.forward");
    if (typeof window !== "undefined") {
      window.history.forward();
    }
  }

  /**
   * Update the current path
   */
  private updatePath(
    path: string,
    updateBrowser: boolean,
    replace: boolean = false
  ) {
    // Prevent re-entrant updates
    if (this.isUpdating) return;

    this.isUpdating = true;
    try {
      const oldPath = this.state.currentPath;
      this.state.previousPath = oldPath;
      this.state.currentPath = path;
      this.state.query = this.parseQuery();

      // Notify subscribers synchronously before any browser events
      this.notifySubscribers();

      // Update browser URL if needed
      if (updateBrowser && typeof window !== "undefined") {
        const url = "#" + path;
        if (replace) {
          window.history.replaceState(null, "", url);
        } else {
          window.history.pushState(null, "", url);
        }

        // Manually trigger hashchange event
        const hashChangeEvent = new HashChangeEvent("hashchange", {
          oldURL: window.location.href.replace(window.location.hash, "#" + oldPath),
          newURL: window.location.href,
        });
        window.dispatchEvent(hashChangeEvent);
      }
    } finally {
      this.isUpdating = false;
    }
  }

  /**
   * Get current path
   */
  getCurrentPath(): string {
    return this.state.currentPath;
  }

  /**
   * Get current route params
   */
  getParams(): Record<string, string> {
    return { ...this.state.params };
  }

  /**
   * Get current query params
   */
  getQuery(): Record<string, string> {
    return { ...this.state.query };
  }

  /**
   * Get full route state snapshot
   */
  getState(): RouteState {
    return getStateSnapshot(this.state);
  }

  /**
   * Match a pattern against a path
   */
  matchPath(pattern: string, path: string): RouteMatch | null {
    // Handle invalid inputs gracefully
    if (!pattern || typeof pattern !== 'string') {
      return null;
    }
    if (!path || typeof path !== 'string') {
      return null;
    }

    // Exact match
    if (pattern === path) {
      return {
        params: {},
        query: this.state.query,
        path,
      };
    }

    // Wildcard match: /dashboard/* matches /dashboard/anything
    if (pattern.endsWith("/*")) {
      const prefix = pattern.slice(0, -2);
      if (path === prefix || path.startsWith(prefix + "/")) {
        return {
          params: {},
          query: this.state.query,
          path,
        };
      }
      return null;
    }

    // Parameter match: /users/:id matches /users/123
    const paramNames: string[] = [];
    const regexPattern = pattern
      .replace(/:([a-zA-Z_][a-zA-Z0-9_]*)/g, (_, name) => {
        paramNames.push(name);
        return "([^/]+)";
      })
      .replace(/\*/g, ".*");

    const regex = new RegExp(`^${regexPattern}$`);
    const match = path.match(regex);

    if (!match) return null;

    // Extract params
    const params: Record<string, string> = {};
    paramNames.forEach((name, i) => {
      const value = match[i + 1];
      if (value !== undefined) {
        params[name] = decodeURIComponent(value);
      }
    });

    return {
      params,
      query: this.state.query,
      path,
    };
  }

  /**
   * Subscribe to route changes
   */
  onNavigate(callback: RouteChangeCallback): () => void {
    this.subscribers.add(callback);

    // Call immediately with current state
    try {
      callback(this.getState());
    } catch (error) {
      console.error("[HypenRouter] Error in route subscriber:", error);
    }

    // Return unsubscribe function
    return () => {
      this.subscribers.delete(callback);
    };
  }

  /**
   * Notify all subscribers of route change
   */
  private notifySubscribers() {
    const routeState = this.getState();
    this.subscribers.forEach((callback) => {
      try {
        callback(routeState);
      } catch (error) {
        console.error("[HypenRouter] Error in route subscriber:", error);
      }
    });
  }

  /**
   * Check if a path matches the current route
   */
  isActive(pattern: string): boolean {
    return this.matchPath(pattern, this.state.currentPath) !== null;
  }

  /**
   * Get a URL with query params
   */
  buildUrl(path: string, query?: Record<string, string>): string {
    if (!query || Object.keys(query).length === 0) {
      return path;
    }

    const queryString = new URLSearchParams(query).toString();
    return `${path}?${queryString}`;
  }
}
