/**
 * @fileoverview React component for GameStateContext.jsx
 * 
 * @module contexts.GameStateContext
 */


import React, {
  createContext,
  useContext,
  useEffect,
  useState,
  useCallback,
  useRef,
} from "react";
import PropTypes from "prop-types";
import { gameEventBus } from "../utils/gameEvents";
import { GameProgressProvider, useGameProgress } from "./GameProgressContext";
import { useGameActivityTracking } from "../services/analytics/useGameActivityTracking";
import { getAnalytics } from "../services/analytics/AnalyticsManager";

export const GAME_STATES = {
  PARADO: "parado",
  EXECUTANDO: "executando",
  SUCESSO: "sucesso",
  FALHA: "falha",
};

const GameStateContext = createContext();

/**
 * Provedor de contexto global para o estado de execução do jogo.
 * Gerencia: código gerado, estado de execução, fases completadas, mensagens de erro.
 *
 * @component
 * @param {Object} props - Componente provider props
 * @param {React.ReactNode} props.children - Componentes filhos
 * @param {Object} props.gameConfig - Configuração do jogo (gameId, fases, etc)
 * @param {string} props.gameConfig.gameId - ID único do jogo
 * @param {Array} props.gameConfig.fases - Array de fases do jogo
 *
 * @returns {React.Context} BaseExecution context with executor methods and state
 *
 * @example
 * <GameStateProvider gameConfig={config}>
 *   <GameArea />
 * </GameStateProvider>
 *
 * @context
 * - {@link useGameState} - Para consumir este contexto
 */
export function GameStateProvider({ children, gameConfig }) {
  return (
    <GameProgressProvider gameConfig={gameConfig}>
      <GameStateInnerProvider gameConfig={gameConfig}>
        {children}
      </GameStateInnerProvider>
    </GameProgressProvider>
  );
}

/**
 * Provider interno que consome GameProgressContext e gerencia estado de execução.
 * Expõe via GameStateContext todos os campos (progresso + execução) para
 * preservar compatibilidade com os consumidores existentes de useGameState().
 *
 * @private
 */
function GameStateInnerProvider({ children, gameConfig }) {
  const {
    currentPhase,
    setCurrentPhase,
    completedPhases,
    setCompletedPhases,
    changePhase: progressChangePhase,
    resetProgress: progressResetProgress,
  } = useGameProgress();

  const [executionState, setExecutionState] = useState(GAME_STATES.PARADO);
  const [generatedCode, setGeneratedCode] = useState("");
  const [currentBlockCount, setCurrentBlockCount] = useState(0);
  const [onWorkspaceChangeCallback, setOnWorkspaceChangeCallback] =
    useState(null);
  const [editorType, setEditorType] = useState("blockly"); // 'blockly' ou 'code'
  const [codeEditorContent, setCodeEditorContent] = useState("");
  const [failureMessage, setFailureMessage] = useState("");
  const [isDebugMode, setIsDebugMode] = useState(() => {
    // Try both window.location.search and hash-based query params (for HashRouter)
    let urlParams = new URLSearchParams(window.location.search);
    let debugKey = Array.from(urlParams.keys()).find(
      (k) => k.toLowerCase() === "debug",
    );

    // If not found in search, try hash (for HashRouter with ?debug=true after hash)
    if (!debugKey && window.location.hash.includes('?')) {
      const hashSearch = window.location.hash.split('?')[1];
      urlParams = new URLSearchParams(hashSearch);
      debugKey = Array.from(urlParams.keys()).find(
        (k) => k.toLowerCase() === "debug",
      );
    }

    const isDebug = urlParams.get(debugKey)?.toLowerCase() === "true";
    return isDebug;
  });

  const getCodeFromWorkspace = useRef(null);
  const getCodeFromEditor = useRef(null);

  // Rastrear atividade do jogo (analytics)
  useGameActivityTracking(gameConfig, {
    executionState,
    currentPhase,
    currentBlockCount,
    editorType,
  });

  const trackGenericEvent = useCallback(
    (eventName, extraData = {}) => {
      if (!gameConfig?.gameId) return;

      const analytics = getAnalytics();
      analytics.trackEvent(eventName, {
        atividade_id: gameConfig.gameId,
        atividade_nome: gameConfig.gameName || gameConfig.gameId,
        fase_numero: currentPhase,
        editor_tipo: editorType,
        blocos_atuais: currentBlockCount,
        ...extraData,
      });
    },
    [gameConfig?.gameId, gameConfig?.gameName, currentPhase, editorType, currentBlockCount],
  );

  /**
   * Executa o código/workspace do editor de forma síncrona.
   * Registra o código gerado e muda estado para EXECUTANDO.
   * Valida se o editor registrou a função de execução antes de processar.
   *
   * @function execute
   * @returns {void}
   * @throws {console.error} Se editor não registrou a função de execução
   */
  const execute = () => {
    trackGenericEvent("tentativa_execucao", {
      origem_acao: "botao_executar",
    });

    trackGenericEvent("blocos_usados", {
      origem_acao: "execucao",
    });

    if (editorType === "code") {
      if (getCodeFromEditor.current) {
        const codigo = getCodeFromEditor.current();

        if (codigo && codigo.trim()) {
          setGeneratedCode(codigo);
          setExecutionState(GAME_STATES.EXECUTANDO);
        } else {
          console.error(
            "CodeEditor ainda não registrou sua função de execução.",
          );
        }
      }
    } else {
      if (getCodeFromWorkspace.current) {
        const { codigo, workspace } = getCodeFromWorkspace.current();
        if (codigo && workspace) {
          setGeneratedCode({ codigo, workspace });
          setExecutionState(GAME_STATES.EXECUTANDO);
        }
      } else {
        console.error(
          "BlocklyEditor ainda não registrou sua função de execução.",
        );
      }
    }
  };

  /**
   * Marca a execução como bem-sucedida e registra a fase como completada.
   * Adiciona a fase ao array de fases concluídas se ainda não estiver incluída.
   * Persiste as mudanças no localStorage.
   *
   * @function finalizeWithSuccess
   * @returns {void}
   */
  const finalizeWithSuccess = () => {
    setExecutionState(GAME_STATES.SUCESSO);

    if (!completedPhases.includes(currentPhase)) {
      setCompletedPhases([...completedPhases, currentPhase]);
    }
  };

  /**
   * Marca a execução como falhada.
   * Atualiza o estado para FALHA sem limpar o código gerado.
   * Permite que o jogador veja o código e os erros para corrigir.
   *
   * @function finalizeWithFailure
   * @returns {void}
   */
  const finalizeWithFailure = () => {
    setExecutionState(GAME_STATES.FALHA);
  };

  /**
   * Reinicia o estado de execução e limpa o código gerado.
   * Usado para permitir nova execução após sucesso/falha sem mudar de fase.
   * Mantém a fase atual e fases completadas intactas.
   *
   * @function restart
   * @returns {void}
   */
  const restart = () => {
    setExecutionState(GAME_STATES.PARADO);
    setGeneratedCode("");
  };

  /**
   * Remove todo o progresso salvo e reseta fases para a primeira.
   * Persiste a remoção no `localStorage` usando a chave do jogo.
   *
   * @function resetProgress
   * @returns {void}
   */
  const resetProgress = () => {
    progressResetProgress();
  };

  /**
   * Navega para uma fase específica do jogo.
   * Reseta o estado de execução, limpa o código gerado e conta de blocos.
   * Usado quando o jogador seleciona uma nova fase no seletor.
   *
   * @function changePhase
   * @param {number} numeroFase - Número da fase para navegar (1-indexed)
   * @returns {void}
   */
  const changePhase = (numeroFase, source = "unknown") => {
    if (source === "manual_selector") {
      trackGenericEvent("troca_fase_manual", {
        fase_origem: currentPhase,
        fase_destino: numeroFase,
        origem_acao: source,
      });
    }

    progressChangePhase(numeroFase);
    setExecutionState(GAME_STATES.PARADO);
    setGeneratedCode("");
    setCurrentBlockCount(0);
    setCodeEditorContent("");
  };

  /**
   * Para a execução atual e limpa o código gerado sem alterar a fase.
   * Usado para interromper execuções em andamento pelo usuário ou pela UI.
   *
   * @function stop
   * @returns {void}
   */
  const stop = () => {
    // Avisar Phaser para parar antes de mudar estado React
    gameEventBus.stopExecution();
    setExecutionState(GAME_STATES.PARADO);
    setGeneratedCode("");
  };

  /**
   * Registra a função que extrai código/workspace do editor Blockly.
   * A função registrada deve retornar um objeto `{ codigo, workspace }`.
   *
   * @function registerExecutionFunction
   * @param {Function} func - Função que retorna { codigo, workspace }
   * @returns {void}
   */
  const registerExecutionFunction = useCallback((func) => {
    getCodeFromWorkspace.current = func;
  }, []);

  /**
   * Registra a função que retorna o código do editor de texto (code editor).
   * Usado quando `editorType` é `code` para obter o código atual.
   *
   * @function registerCodeEditorFunction
   * @param {Function} func - Função que retorna uma string com o código
   * @returns {void}
   */
  const registerCodeEditorFunction = useCallback((func) => {
    getCodeFromEditor.current = func;
  }, []);

  /**
   * Callback disparado quando a workspace do Blockly sofre alterações.
   * Atualiza contador de blocos e repassa para callback externo se fornecido.
   *
   * @function onWorkspaceChange
   * @param {number} blockCount - Quantidade atual de blocos na workspace
   * @returns {void}
   */
  const onWorkspaceChange = useCallback(
    (blockCount) => {
      setCurrentBlockCount(blockCount);
      if (onWorkspaceChangeCallback) {
        onWorkspaceChangeCallback(blockCount);
      }
    },
    [onWorkspaceChangeCallback],
  );

  /**
   * Callback para mudanças no editor de código (texto).
   * Atualiza o conteúdo e ajusta o contador de blocos (1 se houver código, 0 caso contrário).
   *
   * @function onCodeEditorChange
   * @param {string} content - Conteúdo atual do editor de código
   * @returns {void}
   */
  const onCodeEditorChange = useCallback((content) => {
    setCodeEditorContent(content);
    setCurrentBlockCount(content.trim() ? 1 : 0);
  }, []);

  useEffect(() => {
    if (editorType === "code" && getCodeFromEditor.current) {
      setCurrentBlockCount(
        codeEditorContent && codeEditorContent.trim() ? 1 : 0,
      );
    } else {
      setCodeEditorContent(0);
    }
  }, [editorType, codeEditorContent]);



  return (
    <GameStateContext.Provider
      value={{
        // English API
        executionState,
        setExecutionState,
        generatedCode,
        setGeneratedCode,
        currentBlockCount,
        execute,
        finalizeWithSuccess,
        finalizeWithFailure,
        restart,
        stop,
        currentPhase,
        // public: change phase using `changePhase` behavior
        setCurrentPhase: changePhase,
        completedPhases,
        setCompletedPhases,
        resetProgress,
        gameConfig,
        registerExecutionFunction,
        registerCodeEditorFunction,
        onWorkspaceChange,
        onCodeEditorChange,
        // Wrapping fn in arrow prevents React from treating it as a state updater.
        // useState(fn) calls fn(prevState) — storing a callback directly would cause
        // handleWorkspaceChange(prevCallback) to fire during render (illegal side-effect).
        setOnWorkspaceChange: (fn) => setOnWorkspaceChangeCallback(() => fn),
        editorType,
        setEditorType,
        codeEditorContent,
        failureMessage,
        setFailureMessage,
        isDebugMode,
        setIsDebugMode,
      }}
    >
      {children}
    </GameStateContext.Provider>
  );
}

/**
 * Hook customizado para consumir o contexto global de estado do jogo.
 * Fornece acesso aos estados de execução, progresso e métodos de controle do jogo.
 *
 * @hook
 * @returns {Object} Objeto com estados e métodos do jogo:
 *   - Execução: executionState, execute(), finalizeWithSuccess(), finalizeWithFailure(), restart(), stop()
 *   - Progresso: currentPhase, changePhase(fase), completedPhases, resetProgress()
 *   - Código: generatedCode, codeEditorContent
 *   - Registro: registerExecutionFunction(), registerCodeEditorFunction()
 *   - Callbacks: onWorkspaceChange(), onCodeEditorChange()
 *   - Mensagens: failureMessage, setFailureMessage()
 *   - Debug: isDebugMode, setIsDebugMode()
 *   - Compatibilidade: aliases em português (estadoExecucao, codigoGerado, etc)
 *
 * @throws {Error} Se usado fora de GameStateProvider
 *
 * @example
 * function MyComponent() {
 *   const { executionState, execute, currentPhase } = useGameState();
 *   return <button onClick={execute}>Executar</button>;
 * }
 */
export function useGameState() {
  const context = useContext(GameStateContext);
  if (!context) {
    throw new Error("useGameState deve ser usado dentro de GameStateProvider");
  }
  return context;
}

GameStateProvider.propTypes = {
  children: PropTypes.node.isRequired,
  gameConfig: PropTypes.shape({
    gameId: PropTypes.string.isRequired,
    fases: PropTypes.array.isRequired,
  }).isRequired,
};

GameStateInnerProvider.propTypes = {
  children: PropTypes.node.isRequired,
  gameConfig: PropTypes.shape({
    gameId: PropTypes.string.isRequired,
    fases: PropTypes.array.isRequired,
  }).isRequired,
};
