import React, {
  createContext,
  useContext,
  useState,
  useCallback,
  useEffect,
  useRef,
} from 'react';
import { useLocation } from 'react-router-dom';
import { InspectedElementInfo } from './inspectorActions';
import { getRouteSourceDir } from 'config/routeRegistry';

export type PageType = 'list' | 'create' | 'edit' | 'show' | 'unknown';

export interface PageInfo {
  /** Resource name, e.g., 'user', 'audit' */
  resourceName: string;
  /** Page type */
  pageType: PageType;
  /** Full path */
  path: string;
  /**
   * Source file path relative to web/ (e.g. "src/routes/user/list.tsx").
   * Null when the route cannot be resolved to a known routes/ directory.
   */
  sourceFilePath: string | null;
}

/**
 * True when this app is running inside the dev-shell iframe.
 * In that case, open/close/sendPrompt are forwarded to the parent shell
 * via postMessage instead of managing local state.
 */
const isInsideDevShell = window.parent !== window && import.meta.env.DEV;

interface AgentContextValue {
  /** Whether the agent panel is visible */
  visible: boolean;
  /** Open the agent panel */
  open: () => void;
  /** Close the agent panel */
  close: () => void;
  /** Toggle the agent panel */
  toggle: () => void;
  /**
   * Open the agent panel and send a prompt directly.
   * Registered by AgentPanel on mount; no-op if SDK is not ready.
   */
  sendPrompt: (prompt: string) => void;
  /** Internal: used by AgentPanel to register the actual send implementation */
  _registerSendPrompt: (fn: (prompt: string) => void) => void;
  /** Current page info */
  pageInfo: PageInfo;
  /** Selected skill ID */
  selectedSkillId: string | null;
  /** Set selected skill */
  setSelectedSkillId: (id: string | null) => void;
  /** User's additional prompt */
  userPrompt: string;
  /** Set user prompt */
  setUserPrompt: (prompt: string) => void;
  /** Active tab */
  activeTab: 'page' | 'project';
  /** Set active tab */
  setActiveTab: (tab: 'page' | 'project') => void;
  /** Whether inspector overlay is active */
  inspecting: boolean;
  /** Enter inspect mode: close drawer, activate overlay */
  startInspecting: () => void;
  /** Exit inspect mode */
  stopInspecting: () => void;
  /** Info about the element selected by inspector */
  inspectedElement: InspectedElementInfo | null;
  /** Set inspected element info */
  setInspectedElement: (info: InspectedElementInfo | null) => void;
  /** Selected sub-skill ID (lifted from AgentPanel for inspector integration) */
  selectedSubSkillId: string | null;
  /** Set selected sub-skill */
  setSelectedSubSkillId: (id: string | null) => void;
}

const AgentContext = createContext<AgentContextValue | null>(null);

/**
 * Map a pageType to its conventional filename inside routes/<dir>/.
 * Falls back to null for unknown types (e.g. custom standalone pages).
 */
function pageTypeToFile(pageType: PageType): string | null {
  switch (pageType) {
    case 'list':
      return 'list.tsx';
    case 'create':
      return 'create.tsx';
    case 'edit':
      return 'edit.tsx';
    case 'show':
      return 'show.tsx';
    default:
      return null;
  }
}

/**
 * Parse current route to extract page info.
 * Supports nested routes like /system/game, /system/game/edit/123.
 */
function parsePageInfo(pathname: string): PageInfo {
  // Remove leading slash and split
  const parts = pathname.replace(/^\//, '').split('/').filter(Boolean);

  // Default values
  let resourceName = '';
  let pageType: PageType = 'unknown';

  if (parts.length === 0) {
    return { resourceName, pageType, path: pathname, sourceFilePath: null };
  }

  // Find the resource name and page type by scanning from the end
  // URL patterns:
  // /user                    -> resource: user, type: list
  // /user/create             -> resource: user, type: create
  // /user/edit/123           -> resource: user, type: edit
  // /user/show/123           -> resource: user, type: show
  // /system/game             -> resource: game, type: list
  // /system/game/create      -> resource: game, type: create
  // /system/game/edit/123    -> resource: game, type: edit

  const lastPart = parts[parts.length - 1];
  const secondLastPart = parts.length >= 2 ? parts[parts.length - 2] : null;

  // Check if last part is a number (ID)
  if (/^\d+$/.test(lastPart)) {
    // URL ends with ID, check the part before it
    if (secondLastPart === 'edit') {
      pageType = 'edit';
      resourceName = parts.length >= 3 ? parts[parts.length - 3] : '';
    } else if (secondLastPart === 'show') {
      pageType = 'show';
      resourceName = parts.length >= 3 ? parts[parts.length - 3] : '';
    } else if (secondLastPart === 'clone') {
      pageType = 'edit'; // clone uses edit page
      resourceName = parts.length >= 3 ? parts[parts.length - 3] : '';
    } else {
      // /user/123 pattern
      pageType = 'show';
      resourceName = secondLastPart || '';
    }
  } else if (lastPart === 'create') {
    pageType = 'create';
    resourceName = secondLastPart || '';
  } else if (
    lastPart === 'edit' ||
    lastPart === 'show' ||
    lastPart === 'clone'
  ) {
    // Shouldn't happen normally, but handle it
    pageType = lastPart === 'clone' ? 'edit' : (lastPart as PageType);
    resourceName = secondLastPart || '';
  } else {
    // Last part is the resource name, it's a list page
    pageType = 'list';
    resourceName = lastPart;
  }

  // Derive the source file path from the route registry — this resolves
  // slugs like "game-list" → "game" and camelCase dirs like "iTActivityDay".
  const sourceDir = getRouteSourceDir(pathname);

  // sourceDir === null means this path is NOT in routeRegistry (e.g. an iframe
  // or external-redirect page whose path comes from the DB). In that case we
  // suppress resourceName and pageType so the context only shows the path.
  if (sourceDir === null) {
    return {
      resourceName: '',
      pageType: 'unknown',
      path: pathname,
      sourceFilePath: null,
    };
  }

  const fileName = pageTypeToFile(pageType);
  const sourceFilePath = fileName
    ? `src/routes/${sourceDir}/${fileName}`
    : null;

  return {
    resourceName,
    pageType,
    path: pathname,
    sourceFilePath,
  };
}

export const AgentProvider: React.FC<{ children: React.ReactNode }> = ({
  children,
}) => {
  const location = useLocation();
  const [visible, setVisible] = useState(false);
  const [selectedSkillId, setSelectedSkillId] = useState<string | null>(null);
  const [userPrompt, setUserPrompt] = useState('');
  const [activeTab, setActiveTab] = useState<'page' | 'project'>('page');
  const [inspecting, setInspecting] = useState(false);
  const [inspectedElement, setInspectedElement] =
    useState<InspectedElementInfo | null>(null);
  const [selectedSubSkillId, setSelectedSubSkillId] = useState<string | null>(
    null,
  );

  // Holds the actual sendPrompt implementation registered by AgentPanel
  const sendPromptImplRef = useRef<((prompt: string) => void) | null>(null);

  const _registerSendPrompt = useCallback((fn: (prompt: string) => void) => {
    sendPromptImplRef.current = fn;
  }, []);

  const sendPrompt = useCallback((prompt: string) => {
    // In dev-shell mode, forward to the parent shell which owns the SDK
    if (isInsideDevShell) {
      // INSPECTOR_SEND: shell will skip additionContext injection for this prompt
      window.parent.postMessage(
        { type: 'SEND_PROMPT', payload: prompt, inspectorSend: true },
        '*',
      );
      return;
    }
    setVisible(true);
    setTimeout(() => {
      sendPromptImplRef.current?.(prompt);
    }, 300);
  }, []);

  const pageInfo = parsePageInfo(location.pathname);

  // Broadcast page context to dev-shell whenever route changes
  useEffect(() => {
    if (!isInsideDevShell) return;
    window.parent.postMessage(
      {
        type: 'PAGE_CONTEXT_CHANGED',
        payload: {
          path: location.pathname,
          resourceName: pageInfo.resourceName,
          pageType: pageInfo.pageType,
          sourceFilePath: pageInfo.sourceFilePath,
        },
      },
      '*',
    );
  }, [
    location.pathname,
    pageInfo.resourceName,
    pageInfo.pageType,
    pageInfo.sourceFilePath,
  ]);

  const open = useCallback(() => {
    if (isInsideDevShell) {
      window.parent.postMessage({ type: 'OPEN_AGENT' }, '*');
      return;
    }
    setVisible(true);
  }, []);
  const close = useCallback(() => {
    if (isInsideDevShell) {
      window.parent.postMessage({ type: 'CLOSE_AGENT' }, '*');
      return;
    }
    setVisible(false);
  }, []);
  const toggle = useCallback(() => {
    if (isInsideDevShell) {
      window.parent.postMessage({ type: 'TOGGLE_AGENT' }, '*');
      return;
    }
    setVisible((v) => !v);
  }, []);

  const startInspecting = useCallback(() => {
    setVisible(false);
    setInspecting(true);
    if (isInsideDevShell)
      window.parent.postMessage({ type: 'INSPECT_STATE', payload: true }, '*');
  }, []);

  const stopInspecting = useCallback(() => {
    setInspecting(false);
    if (isInsideDevShell)
      window.parent.postMessage({ type: 'INSPECT_STATE', payload: false }, '*');
  }, []);

  // Alt+I shortcut to toggle inspect mode
  useEffect(() => {
    const handleKeyDown = (e: KeyboardEvent) => {
      if (e.altKey && e.code === 'KeyI') {
        e.preventDefault();
        setInspecting((prev) => {
          const next = !prev;
          if (next) setVisible(false);
          if (isInsideDevShell)
            window.parent.postMessage(
              { type: 'INSPECT_STATE', payload: next },
              '*',
            );
          return next;
        });
      }
    };
    document.addEventListener('keydown', handleKeyDown);
    return () => document.removeEventListener('keydown', handleKeyDown);
  }, []);

  // Listen for TOGGLE_INSPECT / STOP_INSPECT from dev-shell
  useEffect(() => {
    if (!isInsideDevShell) return;
    const handleMessage = (e: MessageEvent) => {
      if (e.data?.type === 'TOGGLE_INSPECT') {
        setInspecting((prev) => {
          const next = !prev;
          if (next) setVisible(false);
          window.parent.postMessage(
            { type: 'INSPECT_STATE', payload: next },
            '*',
          );
          return next;
        });
      }
      if (e.data?.type === 'STOP_INSPECT') {
        setInspecting(false);
        window.parent.postMessage(
          { type: 'INSPECT_STATE', payload: false },
          '*',
        );
      }
    };
    window.addEventListener('message', handleMessage);
    return () => window.removeEventListener('message', handleMessage);
  }, []);

  const value: AgentContextValue = {
    visible,
    open,
    close,
    toggle,
    sendPrompt,
    _registerSendPrompt,
    pageInfo,
    selectedSkillId,
    setSelectedSkillId,
    userPrompt,
    setUserPrompt,
    activeTab,
    setActiveTab,
    inspecting,
    startInspecting,
    stopInspecting,
    inspectedElement,
    setInspectedElement,
    selectedSubSkillId,
    setSelectedSubSkillId,
  };

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

export function useAgent(): AgentContextValue | null {
  const context = useContext(AgentContext);
  // Return null instead of throwing error when not in AgentProvider
  // This allows safe usage in production where AgentProvider is not rendered
  return context;
}

export default AgentContext;
