import os
import pygame

NPC_BASE_FOLDER = "client/assets/images/NPCs"

def npc_folder(name):
    return os.path.join(NPC_BASE_FOLDER, name)

class NPC(pygame.sprite.Sprite):
    def __init__(self, x, y, actions_dict, default_action="idle", direction="left", scale=1.0):
        super().__init__()
        self.repeat_states = {}
        self.x = x
        self.y = y
        self.direction = direction
        self.actions = actions_dict
        self.action = default_action
        self.frame_index = 0
        self.time = 0
        self.movement_queue = []
        self.current_instruction_index = 0
        self.instruction_timer = 0
        self.scale = scale
        self.loaded_frames = {}
        self.frames = []
        self.image = pygame.Surface((1, 1), pygame.SRCALPHA)
        self.rect = self.image.get_rect(topleft=(self.x, self.y))
        self.player_ref = None
        self.on_click_callback = None  # Callback function when NPC is clicked
        self.load_action_frames(default_action)

    def load_action_frames(self, action):
        if action in self.loaded_frames:
            self.frames = self.loaded_frames[action]
        else:
            data = self.actions.get(action)
            if not data:
                raise ValueError(f"Action {action} not found in actions dictionary")

            sheet = pygame.image.load(data["path"]).convert_alpha()
            sheet_width, sheet_height = sheet.get_size()
            frame_count = data["frame_count"]
            total_rows = data["total_rows"]
            skip_rows = data.get("skip_rows", [])
            if not isinstance(skip_rows, list):
                skip_rows = [skip_rows]

            frame_width = sheet_width // frame_count
            frame_height = sheet_height // total_rows

            frames = []
            for row in range(total_rows):
                if row in skip_rows:
                    continue
                for i in range(frame_count):
                    frame = sheet.subsurface(pygame.Rect(i * frame_width, row * frame_height, frame_width, frame_height))
                    if self.scale != 1.0:
                        frame = pygame.transform.scale(frame, (int(frame_width * self.scale), int(frame_height * self.scale)))
                    if self.direction == "left":
                        frame = pygame.transform.flip(frame, True, False)
                    frames.append(frame)

            self.loaded_frames[action] = frames
            self.frames = frames

        # Always reset frame_index to 0 when switching action
        self.frame_index = 0
        if self.frames:
            self.image = self.frames[self.frame_index]
            self.rect = self.image.get_rect(topleft=(self.x, self.y))


    def set_movement_queue(self, instructions):
        self.movement_queue = instructions
        self.current_instruction_index = 0
        self.instruction_timer = 0

    def process_instruction(self, instr, dt, timer=None):
        """Process a single instruction (move/wait). Returns True if instruction finished."""
        if timer is None:
            state = {"sub_timer": self.instruction_timer}
            self.instruction_timer += dt
        else:
            state = timer

        if instr["type"] == "move":
            target_x = instr["target_x"]
            speed = instr.get("speed", 2)
            
            if self.x < target_x:
                self.x += speed
                if self.x >= target_x:
                    self.x = target_x
                    return True
            elif self.x > target_x:
                self.x -= speed
                if self.x <= target_x:
                    self.x = target_x
                    return True
            else:
                return True

        elif instr["type"] == "wait":
            state["sub_timer"] += dt
            if state["sub_timer"] >= instr.get("duration", 1000):
                return True

        elif instr["type"] == "wait_for_player_x":
            player = getattr(self, "player_ref", None)
            if player and player.x >= instr["x"]:
                return True

        return False


    def update(self, dt):
        # --- Animate current action ---
        data = self.actions.get(self.action, {})
        frame_duration = data.get("frame_duration", 100)
        self.time += dt
        if self.time > frame_duration:
            self.time = 0
            if self.frames:
                self.frame_index = (self.frame_index + 1) % len(self.frames)
                self.image = self.frames[self.frame_index]

        # --- Movement queue processing ---
        if not self.movement_queue or self.current_instruction_index >= len(self.movement_queue):
            return

        instr = self.movement_queue[self.current_instruction_index]

        # --- REPEAT instruction ---
        if instr["type"] == "repeat":
            # Initialize repeat state
            if self.current_instruction_index not in self.repeat_states:
                self.repeat_states[self.current_instruction_index] = {
                    "sub_index": 0,
                    "sub_timer": 0
                }
            state = self.repeat_states[self.current_instruction_index]
            sub_queue = instr["instructions"]

            # Clamp sub_index
            if state["sub_index"] >= len(sub_queue):
                state["sub_index"] = 0

            sub_instr = sub_queue[state["sub_index"]]

            # Track if this is the first frame of this sub-instruction
            is_new_instruction = state["sub_timer"] == 0

            # Set direction at the start of sub-instruction (before loading frames)
            if is_new_instruction and "direction" in sub_instr:
                if sub_instr["direction"] != self.direction:
                    self.direction = sub_instr["direction"]
                    # Clear ALL cached frames so they reload with new direction
                    self.loaded_frames.clear()

            # Switch action if needed
            action = sub_instr.get("action", self.action)
            if action != self.action or not self.frames or action not in self.loaded_frames:
                self.action = action
                self.load_action_frames(action)
                self.frame_index = 0
                if self.frames:
                    self.image = self.frames[self.frame_index]

            # Process sub-instruction
            finished_sub = self.process_instruction(sub_instr, dt, timer=state)

            if finished_sub:
                state["sub_index"] += 1
                state["sub_timer"] = 0

                # Check if finished sub_queue
                if state["sub_index"] >= len(sub_queue):
                    # If 'until' is satisfied, go to next instruction
                    if "until" in instr and instr["until"](self):
                        self.next_instruction()
                        self.repeat_states.pop(self.current_instruction_index, None)
                        return  # stop processing this frame
                    else:
                        # Otherwise loop repeat
                        state["sub_index"] = 0

        # --- Normal instruction (move/wait/wait_for_player_x) ---
        else:
            # Set direction at the start of instruction
            if self.instruction_timer == 0 and "direction" in instr:
                self.set_direction(instr["direction"])
            
            # Switch action if needed
            action = instr.get("action", self.action)
            if action != self.action or not self.frames:
                self.action = action
                self.load_action_frames(action)
                self.frame_index = 0
                if self.frames:
                    self.image = self.frames[self.frame_index]

            finished = self.process_instruction(instr, dt)
            if finished:
                self.next_instruction()

        # Update rect position
        self.rect.topleft = (self.x, self.y)

    def next_instruction(self):
        self.current_instruction_index = (self.current_instruction_index + 1) % len(self.movement_queue)
        self.instruction_timer = 0

    def set_direction(self, direction):
        if direction != self.direction:
            self.direction = direction
            # Reload frames with new direction instead of flipping existing frames
            if self.action in self.loaded_frames:
                del self.loaded_frames[self.action]
            self.load_action_frames(self.action)

    def set_on_click(self, callback):
        """Set a callback function to be called when NPC is clicked."""
        self.on_click_callback = callback

    def is_clicked(self, mouse_pos):
        """Check if the mouse position is within the NPC's rect."""
        return self.rect.collidepoint(mouse_pos)

    def handle_click(self):
        """Call the on_click callback if it exists."""
        if self.on_click_callback:
            self.on_click_callback(self)
