/**
 * Quest System Engine
 * Handles branching narratives, quest progression, and dialogue
 */

import { musicianChallengeLookup } from '../data/musicians';

export class QuestEngine {
  store: any;
  
  constructor(gameStore: any) {
    this.store = gameStore;
  }

  /**
   * Check if a quest can start
   * @param {Object} quest - Quest definition
   * @returns {boolean} - Can start
   */
  canStartQuest(quest) {
    const { player, world } = this.store.getState();
    const { startCondition } = quest;

    // Check if already active or completed
    if (world.activeQuests.some(q => q.id === quest.id)) {
      return false;
    }
    if (world.completedQuests.includes(quest.id)) {
      return false;
    }

    // Check era
    if (startCondition.era && world.currentEra !== startCondition.era) {
      return false;
    }

    // Check if player has met musician
    if (startCondition.hasMetMusician) {
      const learnedStyles = player.learnedStyles || [];
      const rawId = startCondition.hasMetMusician;
      const hasStyle =
        learnedStyles.includes(rawId) ||
        learnedStyles.includes(`${rawId}_style`);

      if (!hasStyle) {
        return false;
      }
    }

    // Check player level
    if (startCondition.playerLevel && player.level < startCondition.playerLevel) {
      return false;
    }

    // Check reputation
    if (startCondition.reputation) {
      const { category, key, minimum } = startCondition.reputation;
      const reputation = player.reputation[category]?.[key] || 0;
      if (reputation < minimum) {
        return false;
      }
    }

    return true;
  }

  /**
   * Start a quest
   * @param {Object} quest - Quest definition
   * @returns {boolean} - Success
   */
  startQuest(quest) {
    if (!this.canStartQuest(quest)) {
      return false;
    }

    const activeQuest = {
      ...quest,
      currentStep: 0,
      startTime: Date.now(),
      progress: {}
    };

    this.store.getState().addActiveQuest(activeQuest);

    const currentState = this.store.getState();
    if (!currentState.player.activeQuestId) {
      this.store.setState((state) => ({
        player: {
          ...state.player,
          activeQuestId: quest.id
        }
      }));
    }
    return true;
  }

  /**
   * Get current step of a quest
   * @param {string} questId - Quest identifier
   * @returns {Object|null} - Current step or null
   */
  getCurrentStep(questId) {
    const { world } = this.store.getState();
    const quest = world.activeQuests.find(q => q.id === questId);

    if (!quest) return null;

    return quest.steps[quest.currentStep];
  }

  /**
   * Make a dialogue choice
   * @param {string} questId - Quest identifier
   * @param {number} choiceIndex - Choice index
   * @returns {boolean} - Success
   */
  makeChoice(questId, choiceIndex) {
    const { world } = this.store.getState();
    const quest = world.activeQuests.find(q => q.id === questId);

    if (!quest) return false;

    const currentStep = this.getCurrentStep(questId);
    if (!currentStep || !currentStep.choices) return false;

    const choice = currentStep.choices[choiceIndex];
    if (!choice) return false;

    // Record choice
    this.store.getState().recordQuestChoice(questId, currentStep.id, choiceIndex);

    // Apply reputation changes
    if (choice.reputationChange) {
      Object.entries(choice.reputationChange).forEach(([key, value]) => {
        this.store.getState().changeReputation('genres', key, value);
      });
    }

    // Move to next step
    const nextStepId = choice.nextStep;
    const nextStepIndex = quest.steps.findIndex(s => s.id === nextStepId);

    if (nextStepIndex === -1) {
      // Quest complete
      this.completeQuest(questId, choice.branch);
      return true;
    }

    // Update quest progress
    this.store.getState().updateQuest(questId, {
      currentStep: nextStepIndex
    });

    return true;
  }

  /**
   * Update quest objective progress
   * @param {string} questId - Quest identifier
   * @param {string} objectiveType - Objective type
   * @param {*} objectiveData - Objective data
   */
  updateObjective(questId: string, objectiveType: string, objectiveData: Record<string, any> = {}) {
    const { world } = this.store.getState();
    const quest = world.activeQuests.find(q => q.id === questId);

    if (!quest) return;

    const currentStep = this.getCurrentStep(questId);
    if (!currentStep) return;

    // Check if step has objectives
    if (currentStep.type === 'objective' && currentStep.objective) {
      const {
        type,
        count = 1,
        genre,
        musicianId,
        tags,
        recommendedChallengeIds,
        styleId
      } = currentStep.objective;

      const effectiveChallengeIds = Array.isArray(recommendedChallengeIds) && recommendedChallengeIds.length > 0
        ? recommendedChallengeIds
        : musicianId
          ? musicianChallengeLookup[musicianId] || []
          : [];

      if (type === objectiveType) {
        if (genre && objectiveData.genre && objectiveData.genre !== genre) {
          return;
        }

        if (
          musicianId &&
          objectiveData.musicianId &&
          objectiveData.musicianId !== musicianId
        ) {
          return;
        }

        if (
          musicianId &&
          !objectiveData.musicianId &&
          objectiveData.learnedStyle &&
          objectiveData.learnedStyle !== musicianId
        ) {
          return;
        }

        if (
          styleId &&
          objectiveData.styleId &&
          objectiveData.styleId !== styleId
        ) {
          return;
        }

        if (tags && tags.length > 0) {
          const hasTagMatch = Array.isArray(objectiveData.tags)
            ? objectiveData.tags.some(tag => tags.includes(tag))
            : false;
          if (!hasTagMatch) {
            return;
          }
        }

        if (effectiveChallengeIds.length > 0 && objectiveData.challengeId) {
          if (!effectiveChallengeIds.includes(objectiveData.challengeId)) {
            return;
          }
        }

        const increment = objectiveData.countIncrement || 1;
        const currentProgress = quest.progress[currentStep.id] || 0;
        const newProgress = currentProgress + increment;

        this.store.getState().updateQuest(questId, {
          progress: {
            ...quest.progress,
            [currentStep.id]: newProgress
          }
        });

        // Check if objective complete
        if (newProgress >= count) {
          this.advanceQuestStep(questId);
        }
      }
    }
  }

  /**
   * Handle challenge completion events and update quest objectives
   * @param {Object} payload - Data describing the completed challenge
   */
  handleChallengeCompletion(payload: Record<string, any> = {}) {
    const { world, player } = this.store.getState();

    if (!world.activeQuests || world.activeQuests.length === 0) {
      return;
    }

    const learnedStyles: string[] = player?.learnedStyles || [];

    world.activeQuests.forEach((quest) => {
      this.updateObjective(quest.id, 'complete_challenge', payload);

      if (payload.learnedStyle || payload.styleId) {
        this.updateObjective(quest.id, 'learn_style', payload);
      }

      const currentStep = this.getCurrentStep(quest.id);
      if (
        currentStep?.type === 'objective' &&
        currentStep.objective?.type === 'learn_style'
      ) {
        const { styleId, musicianId } = currentStep.objective;
        const normalizedStyleId =
          styleId || (musicianId ? `${musicianId}_style` : null);

        const hasLearnedStyle = normalizedStyleId
          ? learnedStyles.includes(normalizedStyleId)
          : musicianId
            ? learnedStyles.includes(musicianId) ||
              learnedStyles.includes(`${musicianId}_style`)
            : false;

        if (hasLearnedStyle) {
          this.updateObjective(quest.id, 'learn_style', {
            learnedStyle: musicianId || normalizedStyleId,
            styleId: normalizedStyleId || undefined
          });
        }
      }
    });
  }

  /**
   * Advance quest to next step
   * @param {string} questId - Quest identifier
   */
  advanceQuestStep(questId) {
    const { world } = this.store.getState();
    const quest = world.activeQuests.find(q => q.id === questId);

    if (!quest) return;

    const nextStepIndex = quest.currentStep + 1;

    if (nextStepIndex >= quest.steps.length) {
      // Quest complete
      this.completeQuest(questId);
      return;
    }

    this.store.getState().updateQuest(questId, {
      currentStep: nextStepIndex
    });

    const nextStep = quest.steps[nextStepIndex];
    if (nextStep && nextStep.type === 'conclusion') {
      this.completeQuest(questId, nextStep.branch || null);
    }
  }

  /**
   * Complete a quest
   * @param {string} questId - Quest identifier
   * @param {string} branch - Quest branch taken (optional)
   */
  completeQuest(questId, branch = null) {
    const { world } = this.store.getState();
    const quest = world.activeQuests.find(q => q.id === questId);

    if (!quest) return;

    // Award rewards based on branch
    const rewards = branch && quest.rewards[branch] 
      ? quest.rewards[branch]
      : quest.rewards.default || {};

    const {
      addFans,
      addCredits,
      addStreams,
      gainGenreXP,
      unlockFeature,
      addLearnedStyle
    } = this.store.getState();

    if (rewards.fans) addFans(rewards.fans);
    if (rewards.credits) addCredits(rewards.credits);
    if (rewards.streams) addStreams(rewards.streams);
    if (rewards.genreXp) {
      Object.entries(rewards.genreXp).forEach(([genreId, xp]) => {
        if (typeof xp === 'number' && xp > 0) {
          gainGenreXP(genreId, xp);
        }
      });
    }

    // Handle special rewards
    if (rewards.unlocks) {
      rewards.unlocks.forEach((featureId) => unlockFeature(featureId));
    }

    if (rewards.styles) {
      rewards.styles.forEach((styleId) => addLearnedStyle(styleId));
    }

    // Mark quest as completed
    this.store.getState().completeQuest(questId);

    const afterCompleteState = this.store.getState();
    if (afterCompleteState.player.activeQuestId === questId) {
      const nextActiveQuestId = afterCompleteState.world.activeQuests[0]?.id || null;
      this.store.setState((state) => ({
        player: {
          ...state.player,
          activeQuestId: nextActiveQuestId
        }
      }));
    }
  }

  /**
   * Get all available quests
   * @param {Array} allQuests - All quest definitions
   * @returns {Array} - Available quests
   */
  getAvailableQuests(allQuests) {
    return allQuests.filter(quest => this.canStartQuest(quest));
  }

  /**
   * Attempt to auto-start any quests whose conditions are now satisfied
   * @param {Array} allQuests - Quest definitions to check
   * @returns {Array<string>} - IDs of quests that were newly started
   */
  startEligibleQuests(allQuests = []) {
    const started = [];

    allQuests.forEach((quest) => {
      if (!quest.autoStart) {
        return;
      }

      if (this.canStartQuest(quest)) {
        const success = this.startQuest(quest);
        if (success) {
          started.push(quest.id);
        }
      }
    });

    return started;
  }

  /**
   * Get active quests
   * @returns {Array} - Active quests
   */
  getActiveQuests() {
    return this.store.getState().world.activeQuests;
  }

  /**
   * Get quest progress percentage
   * @param {string} questId - Quest identifier
   * @returns {number} - Progress percentage (0-100)
   */
  getQuestProgress(questId) {
    const { world } = this.store.getState();
    const quest = world.activeQuests.find(q => q.id === questId);

    if (!quest) return 0;

    const totalSteps = quest.steps?.length || 0;
    if (totalSteps === 0) {
      return 0;
    }

    if (quest.currentStep >= totalSteps) {
      return 100;
    }

    const currentStepIndex = Math.max(0, quest.currentStep || 0);
    const baseCompletedSteps = Math.min(currentStepIndex, totalSteps);
    const currentStepDef = quest.steps[currentStepIndex];

    let fractionalProgress = 0;
    if (currentStepDef?.type === 'objective' && currentStepDef.objective) {
      const required = Math.max(1, currentStepDef.objective.count || 1);
      const currentCount = quest.progress?.[currentStepDef.id] || 0;
      fractionalProgress = Math.min(currentCount / required, 1);
    } else if (currentStepDef?.type === 'conclusion') {
      fractionalProgress = 1;
    }

    const rawProgress = ((baseCompletedSteps + fractionalProgress) / totalSteps) * 100;
    return Math.min(100, Math.max(0, rawProgress));
  }
}

