/**
 * GroupLink - Manages bidirectional links between actors and groups
 *
 * This class handles bidirectional relationships between:
 * - Characters and their groups/encounters
 * - Groups and their members/encounters
 *
 * @author Vermine2047 System
 */

export class GroupLink {
  
  /**
   * Updates actors when a group is modified.
   * @param {Actor} group - The modified group
   * @param {Object} changes - The changes made
   */
  static async updateActorsOnGroupChange(group, changes) {
    if (group?.type !== 'group') return;
    
    const groupData = group.system;
    const members = [...(groupData.members || [])];
    const encounters = [...(groupData.encounters || [])];
    
    // Update group members and encounters
    if (changes?.members !== undefined || changes?.encounters !== undefined) {
      await this._updateMembersInGroup(group, members);
      await this._updateEncountersInGroup(group, encounters);
    }
    
    // Sync data to member actors
    await this._syncGroupToMembers(group, members);
    await this._syncGroupToEncounters(group, encounters);
  }
  
  /**
   * Updates groups when an actor is modified.
   * @param {Actor} actor - The modified actor
   * @param {Object} changes - The changes made
   */
  static async updateGroupsOnActorChange(actor, changes) {
    if (actor?.type === 'group') return;
    
    const actorData = actor.system;
    const encounters = [...(actorData.encounters || [])];
    
    // If actor's encounters have changed
    if (changes?.encounters !== undefined) {
      // Update each group in encounters
      for (const groupId of encounters) {
        const group = game.actors.get(groupId);
        if (group && group.type === 'group') {
          await this._updateActorInGroupMembers(group, actor.id);
        }
      }
    }
  }
  
  /**
   * Syncs group data to member actors.
   * @param {Actor} group - The group
   * @param {Array<string>} memberIds - List of member IDs
   */
  static async _syncGroupToMembers(group, memberIds) {
    const groupId = group.id;
    
    for (const memberId of memberIds) {
      const member = game.actors.get(memberId);
      if (!member) continue;
      
      const memberEncounters = [...(member.system.encounters || [])];
      if (!memberEncounters.includes(groupId)) {
        memberEncounters.push(groupId);
        await member.update({
          'system.encounters': memberEncounters
        });
      }
    }
  }
  
  /**
   * Syncs group data to encounter actors.
   * @param {Actor} group - The group
   * @param {Array<string>} encounterIds - List of encounter IDs
   */
  static async _syncGroupToEncounters(group, encounterIds) {
    const groupId = group.id;
    
    for (const encounterId of encounterIds) {
      const encounter = game.actors.get(encounterId);
      if (!encounter) continue;
      
      const encounterGroups = [...(encounter.system.encounters || [])];
      if (!encounterGroups.includes(groupId)) {
        encounterGroups.push(groupId);
        await encounter.update({
          'system.encounters': encounterGroups
        });
      }
    }
  }
  
  /**
   * Updates members in a group.
   * @param {Actor} group - The group
   * @param {Array<string>} memberIds - List of member IDs
   */
  static async _updateMembersInGroup(group, memberIds) {
    const groupId = group.id;
    const currentMembers = [...(group.system.members || [])];
    
    // Convert to Sets for O(1) lookups
    const currentMembersSet = new Set(currentMembers);
    const memberIdsSet = new Set(memberIds);
    
    // Find members to remove and add
    const membersToRemove = [...currentMembersSet].filter(id => !memberIdsSet.has(id));
    const membersToAdd = [...memberIdsSet].filter(id => !currentMembersSet.has(id));
    
    // Update actors that were removed
    for (const memberId of membersToRemove) {
      const member = game.actors.get(memberId);
      if (member) {
        const memberEncounters = (member.system.encounters || []).filter(id => id !== groupId);
        await member.update({
          'system.encounters': memberEncounters
        });
      }
    }
    
    // Update new members
    for (const memberId of membersToAdd) {
      const member = game.actors.get(memberId);
      if (member) {
        const memberEncounters = [...(member.system.encounters || [])];
        if (!memberEncounters.includes(groupId)) {
          memberEncounters.push(groupId);
          await member.update({
            'system.encounters': memberEncounters
          });
        }
      }
    }
  }
  
  /**
   * Updates encounters in a group.
   * @param {Actor} group - The group
   * @param {Array<string>} encounterIds - List of encounter IDs
   */
  static async _updateEncountersInGroup(group, encounterIds) {
    const groupId = group.id;
    const currentEncounters = [...(group.system.encounters || [])];
    
    // Convert to Sets for O(1) lookups
    const currentEncountersSet = new Set(currentEncounters);
    const encounterIdsSet = new Set(encounterIds);
    
    // Find encounters to remove and add
    const encountersToRemove = [...currentEncountersSet].filter(id => !encounterIdsSet.has(id));
    const encountersToAdd = [...encounterIdsSet].filter(id => !currentEncountersSet.has(id));
    
    // Update actors that were removed from encounters
    for (const encounterId of encountersToRemove) {
      const encounter = game.actors.get(encounterId);
      if (encounter) {
        const encounterGroups = (encounter.system.encounters || []).filter(id => id !== groupId);
        await encounter.update({
          'system.encounters': encounterGroups
        });
      }
    }
    
    // Update new encounters
    for (const encounterId of encountersToAdd) {
      const encounter = game.actors.get(encounterId);
      if (encounter) {
        const encounterGroups = [...(encounter.system.encounters || [])];
        if (!encounterGroups.includes(groupId)) {
          encounterGroups.push(groupId);
          await encounter.update({
            'system.encounters': encounterGroups
          });
        }
      }
    }
  }
  
  /**
   * Updates an actor in group members.
   * @param {Actor} group - The group
   * @param {string} actorId - The actor ID
   */
  static async _updateActorInGroupMembers(group, actorId) {
    const groupMembers = [...(group.system.members || [])];
    if (!groupMembers.includes(actorId)) {
      groupMembers.push(actorId);
      await group.update({
        'system.members': groupMembers
      });
    }
  }
  
  /**
   * Updates an actor in group encounters.
   * @param {Actor} group - The group
   * @param {string} actorId - The actor ID
   */
  static async _updateActorInGroupEncounters(group, actorId) {
    const groupEncounters = [...(group.system.encounters || [])];
    if (!groupEncounters.includes(actorId)) {
      groupEncounters.push(actorId);
      await group.update({
        'system.encounters': groupEncounters
      });
    }
  }
  
  /**
   * Returns Actor objects for a list of IDs.
   * @param {Array<string>} actorIds - List of actor IDs
   * @returns {Array<Actor>} List of Actor objects
   */
  static getActorObjects(actorIds) {
    if (!Array.isArray(actorIds)) return [];
    return actorIds
      .map(id => game.actors.get(id))
      .filter(actor => actor !== undefined);
  }

  /**
   * Returns Actor objects for group members.
   * @param {Actor} group - The group
   * @returns {Array<Actor>} List of Actor objects
   */
  static getGroupMembers(group) {
    const memberIds = group?.system?.members || [];
    return this.getActorObjects(memberIds);
  }
  
  /**
   * Returns Actor objects for group encounters
   * @param {Actor} group - The group
   * @returns {Array} - List of Actor objects
   */
  static getGroupEncounters(group) {
    const encounterIds = group.system.encounters || [];
    return this.getActorObjects(encounterIds);
  }
  
  /**
   * Returns groups that an actor belongs to
   * @param {Actor} actor - The actor
   * @returns {Array} - List of Actor objects (groups)
   */
  static getActorGroups(actor) {
    const groupIds = actor.system.encounters || [];
    return this.getActorObjects(groupIds).filter(a => a.type === 'group');
  }
  
  /**
   * Returns encounters (NPC/Creatures) for an actor
   * @param {Actor} actor - The actor
   * @returns {Array} - List of Actor objects (NPC/Creatures)
   */
  static getActorEncounters(actor) {
    const encounterIds = actor.system.encounters || [];
    return this.getActorObjects(encounterIds).filter(a => a.type !== 'group');
  }
  
  /**
   * Removes an actor from all groups.
   * @param {string} actorId - The actor ID to remove
   */
  static async removeActorFromAllGroups(actorId) {
    const allGroups = game.actors.filter(a => a.type === 'group');
    
    for (const group of allGroups) {
      const members = [...(group.system.members || [])];
      const encounters = [...(group.system.encounters || [])];
      
      // Use Set for O(1) lookups
      const membersSet = new Set(members);
      const encountersSet = new Set(encounters);
      
      const hasActorInMembers = membersSet.has(actorId);
      const hasActorInEncounters = encountersSet.has(actorId);
      
      if (hasActorInMembers || hasActorInEncounters) {
        const newMembers = hasActorInMembers ? members.filter(id => id !== actorId) : members;
        const newEncounters = hasActorInEncounters ? encounters.filter(id => id !== actorId) : encounters;
        
        await group.update({
          'system.members': newMembers,
          'system.encounters': newEncounters
        });
      }
    }
    
    // Remove groups from actor encounters
    const actor = game.actors.get(actorId);
    if (actor) {
      await actor.update({
        'system.encounters': []
      });
    }
  }
  
  /**
   * Adds an actor to a group.
   * @param {string} actorId - The actor ID
   * @param {string} groupId - The group ID
   */
  static async addActorToGroup(actorId, groupId) {
    const actor = game.actors.get(actorId);
    const group = game.actors.get(groupId);
    
    if (!actor || !group || group.type !== 'group') return;
    
    // Add actor to group members using spread operator
    const groupMembers = [...(group.system.members || [])];
    if (!groupMembers.includes(actorId)) {
      groupMembers.push(actorId);
      await group.update({
        'system.members': groupMembers
      });
    }
    
    // Add group to actor encounters using spread operator
    const actorEncounters = [...(actor.system.encounters || [])];
    if (!actorEncounters.includes(groupId)) {
      actorEncounters.push(groupId);
      await actor.update({
        'system.encounters': actorEncounters
      });
    }
  }
  
  /**
   * Removes an actor from a group.
   * @param {string} actorId - The actor ID
   * @param {string} groupId - The group ID
   */
  static async removeActorFromGroup(actorId, groupId) {
    const actor = game.actors.get(actorId);
    const group = game.actors.get(groupId);
    
    if (!actor || !group || group.type !== 'group') return;
    
    // Remove actor from group members using spread operator and filter
    const groupMembers = [...(group.system.members || [])].filter(id => id !== actorId);
    await group.update({
      'system.members': groupMembers
    });
    
    // Remove group from actor encounters using spread operator and filter
    const actorEncounters = [...(actor.system.encounters || [])].filter(id => id !== groupId);
    await actor.update({
      'system.encounters': actorEncounters
    });
  }
  
  /**
   * Initializes hooks for automatic synchronization between actors and groups.
   * Sets up event listeners for actor creation, updates, and deletion.
   */
  static registerHooks() {
    // Hook on actor update - synchronize group memberships
    Hooks.on('updateActor', async (actor, changes, options, userId) => {
      if (!game.user.isGM && userId !== game.userId) return;
      
      // If it is a group being updated, sync its members
      if (actor.type === 'group') {
        await this.updateActorsOnGroupChange(actor, changes);
      } 
      // If it is another actor being updated, sync its groups
      else {
        await this.updateGroupsOnActorChange(actor, changes);
      }
    });
    
    // Hook on actor creation - clean up invalid group references
    Hooks.on('createActor', async (actor, options, userId) => {
      if (!game.user.isGM && userId !== game.userId) return;
      
      // If a character is created, check for invalid group references
      if (actor.type !== 'group') {
        const encounters = [...(actor.system.encounters || [])];
        const validGroups = new Set(
          encounters.filter(id => game.actors.get(id))
        );
        
        // Only update if there are invalid references
        if (validGroups.size !== encounters.length) {
          await actor.update({
            'system.encounters': [...validGroups]
          });
        }
      }
    });
    
    // Hook on actor deletion - clean up references
    Hooks.on('deleteActor', async (actor, options, userId) => {
      if (!game.user.isGM && userId !== game.userId) return;
      
      if (actor.type === 'group') {
        // If a group is deleted, clean up references in its members and encounters
        const memberIds = [...(actor.system.members || [])];
        const encounterIds = [...(actor.system.encounters || [])];
        
        for (const id of [...memberIds, ...encounterIds]) {
          const a = game.actors.get(id);
          if (a) {
            const encounters = (a.system.encounters || []).filter(eid => eid !== actor.id);
            await a.update({
              'system.encounters': encounters
            });
          }
        }
      } else {
        // If an actor is deleted, remove it from all groups
        await this.removeActorFromAllGroups(actor.id);
      }
    });
  }
}

// Exporter pour utilisation globale
export default GroupLink;
